From 98729fc33316fa77fb35a959f8af0ea7f7e3cd8d Mon Sep 17 00:00:00 2001 From: Rokojori Date: Fri, 17 Jul 2026 22:22:42 +0200 Subject: [PATCH] Editor: auth redirect, targeted file open, Godot file types - editor-shell: redirect to / if not authenticated on load - file-tree: open file in first clean matching panel, create new panel if all dirty - Editor: add openDocumentIn() with targetElement for precise panel targeting - panels: skip onDocumentOpened if targeted at a different panel - FileEditorRegistry: add Godot extensions (gd, gdshader, gdshaderinc, gdinclude, glsl, tscn, tres, cs, gdextension) Co-Authored-By: Claude Sonnet 4.6 --- source/components/code-panel/code-panel.ts | 1 + .../components/editor-shell/editor-shell.ts | 3 + .../file-tree-panel/file-tree-panel.ts | 57 ++++++++++++++++++- .../html-editor-panel/html-editor-panel.ts | 1 + .../rojo-settings-panel.ts | 1 + source/editor/Editor.ts | 24 ++++++++ source/editor/FileEditorRegistry.ts | 13 ++++- 7 files changed, 97 insertions(+), 3 deletions(-) diff --git a/source/components/code-panel/code-panel.ts b/source/components/code-panel/code-panel.ts index b9c40e5..30a8c0c 100644 --- a/source/components/code-panel/code-panel.ts +++ b/source/components/code-panel/code-panel.ts @@ -52,6 +52,7 @@ class CodePanel extends HTMLElement 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 ); } ); diff --git a/source/components/editor-shell/editor-shell.ts b/source/components/editor-shell/editor-shell.ts index f396101..1acb0fc 100644 --- a/source/components/editor-shell/editor-shell.ts +++ b/source/components/editor-shell/editor-shell.ts @@ -58,6 +58,9 @@ class EditorShell extends HTMLElement { private _saveTimer: ReturnType | null = null; async connectedCallback(): Promise { + const authRes = await fetch( '/api/auth/me' ); + if ( !authRes.ok ) { location.href = '/'; return; } + const params = new URLSearchParams(location.search); const projectId = params.get('project') ?? ''; const projectName = params.get('name') ?? 'Project'; diff --git a/source/components/file-tree-panel/file-tree-panel.ts b/source/components/file-tree-panel/file-tree-panel.ts index eda4f4e..6d60e04 100644 --- a/source/components/file-tree-panel/file-tree-panel.ts +++ b/source/components/file-tree-panel/file-tree-panel.ts @@ -106,11 +106,66 @@ class FileTreePanel extends HTMLElement { bindTree( tree: Element ): void { const state = Editor.get(); tree.querySelectorAll( '.ftp-file' ).forEach( el => { - el.addEventListener( 'click', () => { + el.addEventListener( 'click', async () => { const path = ( el as HTMLElement ).dataset.path!; this.selectedPath = path; tree.querySelectorAll( '.ftp-file, .ftp-dir-label' ).forEach( f => f.classList.remove( 'active' ) ); el.classList.add( 'active' ); + + await state.fileEditorRegistry.load( state.projectId ); + const editorTag = state.fileEditorRegistry.resolve( path ); + + if ( editorTag ) + { + const containers = Array.from( document.querySelectorAll( 'tab-container' ) ); + let found: { panel: HTMLElement; tc: any; tabId: string } | null = null; + + for ( const tc of containers ) + { + const tabs = ( tc as any ).tabs as Array<{ id: string; element: HTMLElement; dirty: boolean }>; + for ( const tab of tabs ) + { + if ( tab.element.tagName.toLowerCase() === editorTag && !tab.dirty ) + { + found = { panel: tab.element, tc, tabId: tab.id }; + break; + } + } + if ( found ) break; + } + + if ( found ) + { + ( found.tc as any ).activateTab( found.tabId ); + state.openDocumentIn( path, found.panel ); + return; + } + + const panelTypeMap: Record = + { + 'html-editor-panel': { panelType: 'html-editor', label: 'HTML Editor' }, + 'code-panel': { panelType: 'code-panel', label: 'Code' }, + 'rojo-settings-panel': { panelType: 'rojo-settings', label: 'Rojo' }, + }; + const info = panelTypeMap[ editorTag ]; + + if ( info ) + { + const targetTc = ( containers.find( tc => + ( tc as any ).tabs.some( ( t: any ) => t.element.tagName.toLowerCase() === editorTag ) + ) ?? document.querySelector( '[data-panel="center"] tab-container' ) ?? containers[ 0 ] ) as any; + + if ( targetTc ) + { + const newId = info.panelType + '-' + Math.random().toString( 36 ).slice( 2 ); + targetTc.addTab( { id: newId, label: info.label, panelType: info.panelType }, () => document.createElement( editorTag ) ); + const newPanel = targetTc.tabs[ targetTc.tabs.length - 1 ].element as HTMLElement; + state.openDocumentIn( path, newPanel ); + return; + } + } + } + state.openDocument( path ); } ); el.addEventListener( 'contextmenu', ( e: Event ) => { diff --git a/source/components/html-editor-panel/html-editor-panel.ts b/source/components/html-editor-panel/html-editor-panel.ts index 6e3c7ed..d1d4d35 100644 --- a/source/components/html-editor-panel/html-editor-panel.ts +++ b/source/components/html-editor-panel/html-editor-panel.ts @@ -41,6 +41,7 @@ class HtmlEditorPanel extends HTMLElement Editor.get().onDocumentOpened.addListener( ( e ) => { if ( 'html-editor-panel' !== e.editorTag ) return; + if ( e.targetElement && e.targetElement !== this ) return; if ( ! this._pinned ) this._loadDocument( e.path, e.content ); } ); diff --git a/source/components/rojo-settings-panel/rojo-settings-panel.ts b/source/components/rojo-settings-panel/rojo-settings-panel.ts index 1bcd7b0..bef5a75 100644 --- a/source/components/rojo-settings-panel/rojo-settings-panel.ts +++ b/source/components/rojo-settings-panel/rojo-settings-panel.ts @@ -200,6 +200,7 @@ class RojoSettingsPanel extends HTMLElement Editor.get().onDocumentOpened.addListener( e => { if ( 'rojo-settings-panel' !== e.editorTag ) return; + if ( e.targetElement && e.targetElement !== this ) return; this._loadDocument( e.path, e.content ); } ); diff --git a/source/editor/Editor.ts b/source/editor/Editor.ts index 92d682c..23964be 100644 --- a/source/editor/Editor.ts +++ b/source/editor/Editor.ts @@ -7,6 +7,7 @@ export interface DocumentOpenedEvent path: string; content: string; editorTag: string; + targetElement?: HTMLElement; } export interface DocumentPathEvent @@ -78,6 +79,29 @@ export class Editor this.onDocumentDirty.dispatch( { path: filePath } ); } + async openDocumentIn( filePath: string, panelElement: HTMLElement ): Promise + { + await this.fileEditorRegistry.load( this.projectId ); + const editorTag = this.fileEditorRegistry.resolve( filePath ); + + if ( editorTag === null ) + { + this.onFileTypeUnknown.dispatch( { path: filePath } ); + return; + } + + if ( !this.openDocs.has( filePath ) ) + { + const res = await fetch( `/api/files/${this.projectId}/${filePath}` ); + const content = await res.text(); + this.openDocs.set( filePath, { content, dirty: false } ); + } + + this.activeDoc = filePath; + const entry = this.openDocs.get( filePath )!; + this.onDocumentOpened.dispatch( { path: filePath, content: entry.content, editorTag, targetElement: panelElement } ); + } + async save( filePath: string ): Promise { const doc = this.openDocs.get( filePath ); diff --git a/source/editor/FileEditorRegistry.ts b/source/editor/FileEditorRegistry.ts index a55cca2..0ef1667 100644 --- a/source/editor/FileEditorRegistry.ts +++ b/source/editor/FileEditorRegistry.ts @@ -21,8 +21,17 @@ export class FileEditorRegistry { suffix: 'xml', editor: 'CodePanel' }, { suffix: 'yaml', editor: 'CodePanel' }, { suffix: 'yml', editor: 'CodePanel' }, - { suffix: 'sh', editor: 'CodePanel' }, - { suffix: 'py', editor: 'CodePanel' }, + { suffix: 'sh', editor: 'CodePanel' }, + { suffix: 'py', editor: 'CodePanel' }, + { suffix: 'cs', editor: 'CodePanel' }, + { suffix: 'gd', editor: 'CodePanel' }, + { suffix: 'gdshader', editor: 'CodePanel' }, + { suffix: 'gdshaderinc', editor: 'CodePanel' }, + { suffix: 'gdinclude', editor: 'CodePanel' }, + { suffix: 'glsl', editor: 'CodePanel' }, + { suffix: 'tscn', editor: 'CodePanel' }, + { suffix: 'tres', editor: 'CodePanel' }, + { suffix: 'gdextension', editor: 'CodePanel' }, ]; static readonly EditorTagNames: Record =