import { Editor } from '../../editor/Editor.js'; import { EditorPanelDefinition, FileEditorPanelDefinition } from '../../editor/editor-panel.js'; import { ContextMenuDirectory, ContextMenuReadOnlyEntry } from '../context-menu/context-menu.js'; import { csharpMode } from './CSharpMode.js'; declare const CodeMirror: any; csharpMode.name = 'rokojori-cs'; CodeMirror.defineMode( 'rokojori-cs', () => csharpMode.cmMode ); class CodePanel extends HTMLElement { __interfaces__ = [ EditorPanelDefinition.type, FileEditorPanelDefinition.type ]; currentPath: string | null = null; _dirty = false; _pinned: boolean = false; _cm: any = null; _initialized = false; _ignoreChange = false; connectedCallback(): void { if ( this._initialized ) return; this._initialized = true; this.className = 'code-panel'; this.innerHTML = `
Open a file from the file tree
`; const editorDiv = this.querySelector( '.cp-editor' ) as HTMLElement; this._cm = CodeMirror( editorDiv, { value: '', lineNumbers: true, theme: 'cp-dark', indentWithTabs: false, tabSize: 2, indentUnit: 2, lineWrapping: false, readOnly: false, } ); this._cm.on( 'change', () => this._onContentChanged() ); this.querySelector( '.cp-pin' )!.addEventListener( 'click', () => this._togglePin() ); this.querySelector( '.cp-save' )!.addEventListener( 'click', () => this._save() ); this.querySelector( '.cp-undo' )!.addEventListener( 'click', () => this._cm.undo() ); this.querySelector( '.cp-redo' )!.addEventListener( 'click', () => this._cm.redo() ); Editor.get().onDocumentOpened.addListener( ( e ) => { if ( 'code-panel' !== e.editorTag ) return; if ( e.targetElement && e.targetElement !== this ) return; if ( ! this._pinned ) this._loadDocument( e.path, e.content ); } ); document.addEventListener( 'keydown', ( e: KeyboardEvent ) => { if ( ! this.currentPath ) return; if ( e.ctrlKey && e.key === 's' ) { e.preventDefault(); this._save(); } } ); } _loadDocument( path: string, content: string ): void { this.currentPath = path; this._updateTabLabel( path ); this.querySelector( '.cp-empty' )!.setAttribute( 'style', 'display:none' ); ( this.querySelector( '.cp-editor' ) as HTMLElement ).style.display = ''; this._ignoreChange = true; this._cm.setValue( content ); this._cm.clearHistory(); this._cm.setOption( 'mode', this._resolveMode( path ) ); this._ignoreChange = false; this._updateButtons( false ); this._cm.refresh(); } _resolveMode( filePath: string ): string { const name = filePath.slice( filePath.lastIndexOf( '/' ) + 1 ); if ( name.endsWith( '.js' ) || name.endsWith( '.json' ) ) return 'javascript'; if ( name.endsWith( '.ts' ) ) return 'javascript'; if ( name.endsWith( '.css' ) ) return 'css'; if ( name.endsWith( '.html' ) || name.endsWith( '.htm' ) ) return 'htmlmixed'; if ( name.endsWith( '.xml' ) || name.endsWith( '.svg' ) ) return 'xml'; if ( name.endsWith( '.md' ) ) return 'markdown'; if ( name.endsWith( '.yaml' ) || name.endsWith( '.yml' ) ) return 'yaml'; if ( name.endsWith( '.sh' ) ) return 'shell'; if ( name.endsWith( '.gd' ) ) return 'python'; if ( name.endsWith( '.cs' ) ) return 'rokojori-cs'; if ( name.endsWith( '.glsl' ) || name.endsWith( '.gdshader' ) || name.endsWith( '.gdshaderinc' ) ) return 'clike'; return 'null'; } _onContentChanged(): void { if ( this._ignoreChange || ! this.currentPath ) return; const content = this._cm.getValue(); Editor.get().markDirty( this.currentPath, content ); this._updateButtons( true ); } _togglePin(): void { this._pinned = ! this._pinned; this.querySelector( '.cp-pin' )!.classList.toggle( 'pinned', this._pinned ); } async _save(): Promise { if ( ! this.currentPath ) return; await Editor.get().save( this.currentPath ); this._updateButtons( false ); } _updateTabLabel( path: string ): void { const name = path ? path.slice( path.lastIndexOf( '/' ) + 1 ) : ''; this.dispatchEvent( new CustomEvent( 'panel:label-change', { bubbles: true, detail: { label: '📝 ' + name } } ) ); } hasUnsavedChanges(): boolean { return this._dirty; } getCurrentFile(): string | null { return this.currentPath; } _updateButtons( dirty: boolean ): void { this._dirty = dirty; ( this.querySelector( '.cp-save' ) as HTMLButtonElement ).disabled = ! dirty; ( this.querySelector( '.cp-undo' ) as HTMLButtonElement ).disabled = this._cm.historySize().undo < 1; ( this.querySelector( '.cp-redo' ) as HTMLButtonElement ).disabled = this._cm.historySize().redo < 1; } addContextMenuEntries( dir: ContextMenuDirectory ): void { if ( this.currentPath ) { dir.add( new ContextMenuReadOnlyEntry( dir, `Editing: ${this.currentPath}` ) ); } else { dir.add( new ContextMenuReadOnlyEntry( dir, 'No file open' ) ); } } } customElements.define( 'code-panel', CodePanel );