Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion apps/frontend/src/app/components/editor/editor.component.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
/* Monaco Editor Deep Styling */
/* Editor Container Styles */
.editor-container {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

/* Monaco Editor Custom Styles */
:host ::ng-deep .monaco-editor {
padding-top: 0px;
}

:host ::ng-deep .monaco-editor .margin {
background-color: #1f2937;
}
Expand Down Expand Up @@ -64,6 +73,29 @@
@apply focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-offset-2 focus:ring-offset-gray-900 rounded-lg;
}

/* Editor Footer */
.editor-footer {
backdrop-filter: blur(10px);
background-color: rgba(31, 41, 55, 0.95);
}

/* Output panel animations */
.animate-slide-down {
animation: slide-in-from-top 0.3s ease-out;
}

/* Custom animations for smooth transitions */
@keyframes slide-in-from-top {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

/* Code syntax highlighting enhancements */
.code-highlight {
@apply font-mono text-sm leading-relaxed;
Expand Down
48 changes: 48 additions & 0 deletions apps/frontend/src/app/components/editor/editor.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,54 @@ <h2 class="text-xl sm:text-2xl font-bold text-white tracking-tight">Soroban Smar
</div>
</div>

<!-- Output Panel -->
<div *ngIf="errorMessage || outputMessage" class="border-t border-gray-700/50 animate-slide-down">
<div [ngClass]="{
'bg-red-900/20 border-l-red-500 text-red-100': outputType === 'error',
'bg-green-900/20 border-l-green-500 text-green-100': outputType === 'success',
'bg-blue-900/20 border-l-blue-500 text-blue-100': outputType === 'info'
}" class="p-4 border-l-4 bg-gray-800/30 backdrop-blur-sm">
<div class="flex justify-between items-start gap-4">
<div class="flex items-start gap-3 flex-1">
<!-- Icon -->
<div class="flex-shrink-0 mt-0.5">
<svg *ngIf="outputType === 'error'" class="h-5 w-5 text-red-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
</svg>
<svg *ngIf="outputType === 'success'" class="h-5 w-5 text-green-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg>
<svg *ngIf="outputType === 'info'" class="h-5 w-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
</svg>
</div>

<!-- Message -->
<div class="flex-1">
<p class="text-sm font-medium" [ngClass]="{
'text-red-200': outputType === 'error',
'text-green-200': outputType === 'success',
'text-blue-200': outputType === 'info'
}">
{{ errorMessage || outputMessage }}
</p>
</div>
</div>

<!-- Close button -->
<button
(click)="clearOutput()"
class="flex-shrink-0 p-1 rounded-md text-gray-400 hover:text-white hover:bg-gray-700/50 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-gray-500"
aria-label="Clear output message"
>
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
</svg>
</button>
</div>
</div>
</div>

<!-- Status bar -->
<div class="editor-footer status-bar-glass px-4 sm:px-6 py-3" role="status" aria-live="polite" aria-label="Editor status">
<div class="flex items-center justify-between text-sm">
Expand Down
71 changes: 69 additions & 2 deletions apps/frontend/src/app/components/editor/editor.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ export class EditorComponent implements OnDestroy {
isLoading = false;
isBrowser = isPlatformBrowser(inject(PLATFORM_ID));

// Validation and output properties
errorMessage: string = '';
outputMessage: string = '';
outputType: 'error' | 'success' | 'info' = 'info';

editorOptions = {
theme: 'vs-dark',
language: 'rust',
Expand All @@ -50,35 +55,97 @@ export class EditorComponent implements OnDestroy {
this.timeoutIds.clear();
}

private validateCode(): boolean {
// Clear previous messages
this.clearOutput();

// Check if code is empty or only whitespace
if (!this.code || !this.code.trim()) {
this.errorMessage = 'Error: Code cannot be empty or contain only whitespace';
this.outputType = 'error';
return false;
}

// Check code length (50KB limit)
if (this.code.length > 50000) {
this.errorMessage = 'Error: Code exceeds maximum length (50KB). Please reduce code size.';
this.outputType = 'error';
return false;
}

// Basic Rust syntax check - look for common Rust keywords
const rustKeywords = ['fn', 'impl', 'pub', 'struct', 'enum', 'mod', 'use', 'let', 'const', 'static'];
const hasRustKeyword = rustKeywords.some(keyword => this.code.includes(keyword));

if (!hasRustKeyword && this.code.trim().length > 10) {
this.errorMessage = 'Warning: Code may not be valid Rust. Please ensure you\'re writing Rust code.';
this.outputType = 'error';
return false;
}

// Check for basic contract structure for Soroban
if (!this.code.includes('contract') && !this.code.includes('soroban')) {
this.errorMessage = 'Info: Consider using Soroban contract structure for smart contract development.';
this.outputType = 'info';
// This is just a warning, still allow compilation
}

return true;
}

clearOutput(): void {
this.errorMessage = '';
this.outputMessage = '';
this.outputType = 'info';
}

onCompile(): void {
if (this.isLoading || !this.code.trim()) {
if (this.isLoading) {
return;
}

// Validate code before proceeding
if (!this.validateCode()) {
return;
}

this.isLoading = true;
this.outputMessage = 'Compiling Rust smart contract...';
this.outputType = 'info';
console.log('Compiling Rust smart contract code:', this.code);

// TODO: Implement API call to backend compiler
const timeoutId = setTimeout(() => {
this.isLoading = false;
this.timeoutIds.delete(timeoutId);
this.outputMessage = 'Compilation completed successfully!';
this.outputType = 'success';
console.log('Compilation complete');
}, 2000) as unknown as number;
this.timeoutIds.add(timeoutId);
}

onTest(): void {
if (this.isLoading || !this.code.trim()) {
if (this.isLoading) {
return;
}

// Validate code before proceeding
if (!this.validateCode()) {
return;
}

this.isLoading = true;
this.outputMessage = 'Running tests for smart contract...';
this.outputType = 'info';
console.log('Testing Rust smart contract code:', this.code);

// TODO: Implement API call to backend test runner
const timeoutId = setTimeout(() => {
this.isLoading = false;
this.timeoutIds.delete(timeoutId);
this.outputMessage = 'All tests passed successfully!';
this.outputType = 'success';
console.log('Testing complete');
}, 2000) as unknown as number;
this.timeoutIds.add(timeoutId);
Expand Down
Loading