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 <noreply@anthropic.com>
This commit is contained in:
parent
bbf208b3be
commit
98729fc333
|
|
@ -52,6 +52,7 @@ class CodePanel extends HTMLElement
|
||||||
Editor.get().onDocumentOpened.addListener( ( e ) =>
|
Editor.get().onDocumentOpened.addListener( ( e ) =>
|
||||||
{
|
{
|
||||||
if ( 'code-panel' !== e.editorTag ) return;
|
if ( 'code-panel' !== e.editorTag ) return;
|
||||||
|
if ( e.targetElement && e.targetElement !== this ) return;
|
||||||
if ( ! this._pinned ) this._loadDocument( e.path, e.content );
|
if ( ! this._pinned ) this._loadDocument( e.path, e.content );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,9 @@ class EditorShell extends HTMLElement {
|
||||||
private _saveTimer: ReturnType<typeof setTimeout> | null = null;
|
private _saveTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
async connectedCallback(): Promise<void> {
|
async connectedCallback(): Promise<void> {
|
||||||
|
const authRes = await fetch( '/api/auth/me' );
|
||||||
|
if ( !authRes.ok ) { location.href = '/'; return; }
|
||||||
|
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
const projectId = params.get('project') ?? '';
|
const projectId = params.get('project') ?? '';
|
||||||
const projectName = params.get('name') ?? 'Project';
|
const projectName = params.get('name') ?? 'Project';
|
||||||
|
|
|
||||||
|
|
@ -106,11 +106,66 @@ class FileTreePanel extends HTMLElement {
|
||||||
bindTree( tree: Element ): void {
|
bindTree( tree: Element ): void {
|
||||||
const state = Editor.get();
|
const state = Editor.get();
|
||||||
tree.querySelectorAll( '.ftp-file' ).forEach( el => {
|
tree.querySelectorAll( '.ftp-file' ).forEach( el => {
|
||||||
el.addEventListener( 'click', () => {
|
el.addEventListener( 'click', async () => {
|
||||||
const path = ( el as HTMLElement ).dataset.path!;
|
const path = ( el as HTMLElement ).dataset.path!;
|
||||||
this.selectedPath = path;
|
this.selectedPath = path;
|
||||||
tree.querySelectorAll( '.ftp-file, .ftp-dir-label' ).forEach( f => f.classList.remove( 'active' ) );
|
tree.querySelectorAll( '.ftp-file, .ftp-dir-label' ).forEach( f => f.classList.remove( 'active' ) );
|
||||||
el.classList.add( '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<string, { panelType: string; label: string }> =
|
||||||
|
{
|
||||||
|
'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 );
|
state.openDocument( path );
|
||||||
} );
|
} );
|
||||||
el.addEventListener( 'contextmenu', ( e: Event ) => {
|
el.addEventListener( 'contextmenu', ( e: Event ) => {
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ class HtmlEditorPanel extends HTMLElement
|
||||||
Editor.get().onDocumentOpened.addListener( ( e ) =>
|
Editor.get().onDocumentOpened.addListener( ( e ) =>
|
||||||
{
|
{
|
||||||
if ( 'html-editor-panel' !== e.editorTag ) return;
|
if ( 'html-editor-panel' !== e.editorTag ) return;
|
||||||
|
if ( e.targetElement && e.targetElement !== this ) return;
|
||||||
if ( ! this._pinned ) this._loadDocument( e.path, e.content );
|
if ( ! this._pinned ) this._loadDocument( e.path, e.content );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,7 @@ class RojoSettingsPanel extends HTMLElement
|
||||||
Editor.get().onDocumentOpened.addListener( e =>
|
Editor.get().onDocumentOpened.addListener( e =>
|
||||||
{
|
{
|
||||||
if ( 'rojo-settings-panel' !== e.editorTag ) return;
|
if ( 'rojo-settings-panel' !== e.editorTag ) return;
|
||||||
|
if ( e.targetElement && e.targetElement !== this ) return;
|
||||||
this._loadDocument( e.path, e.content );
|
this._loadDocument( e.path, e.content );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ export interface DocumentOpenedEvent
|
||||||
path: string;
|
path: string;
|
||||||
content: string;
|
content: string;
|
||||||
editorTag: string;
|
editorTag: string;
|
||||||
|
targetElement?: HTMLElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DocumentPathEvent
|
export interface DocumentPathEvent
|
||||||
|
|
@ -78,6 +79,29 @@ export class Editor
|
||||||
this.onDocumentDirty.dispatch( { path: filePath } );
|
this.onDocumentDirty.dispatch( { path: filePath } );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async openDocumentIn( filePath: string, panelElement: HTMLElement ): Promise<void>
|
||||||
|
{
|
||||||
|
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<void>
|
async save( filePath: string ): Promise<void>
|
||||||
{
|
{
|
||||||
const doc = this.openDocs.get( filePath );
|
const doc = this.openDocs.get( filePath );
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,17 @@ export class FileEditorRegistry
|
||||||
{ suffix: 'xml', editor: 'CodePanel' },
|
{ suffix: 'xml', editor: 'CodePanel' },
|
||||||
{ suffix: 'yaml', editor: 'CodePanel' },
|
{ suffix: 'yaml', editor: 'CodePanel' },
|
||||||
{ suffix: 'yml', editor: 'CodePanel' },
|
{ suffix: 'yml', editor: 'CodePanel' },
|
||||||
{ suffix: 'sh', editor: 'CodePanel' },
|
{ suffix: 'sh', editor: 'CodePanel' },
|
||||||
{ suffix: 'py', 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<string, string> =
|
static readonly EditorTagNames: Record<string, string> =
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue