170 lines
5.5 KiB
TypeScript
170 lines
5.5 KiB
TypeScript
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 = `
|
|
<div class="cp-toolbar">
|
|
<button class="cp-pin" title="Pin — keep this file when selecting others">Pin</button>
|
|
<button class="cp-undo" title="Undo (Ctrl+Z)" disabled>↩</button>
|
|
<button class="cp-redo" title="Redo (Ctrl+Y)" disabled>↪</button>
|
|
<button class="cp-save" title="Save (Ctrl+S)" disabled>Save</button>
|
|
</div>
|
|
<div class="cp-empty">Open a file from the file tree</div>
|
|
<div class="cp-editor" style="display:none"></div>
|
|
`;
|
|
|
|
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<void>
|
|
{
|
|
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 );
|