141 lines
3.9 KiB
TypeScript
141 lines
3.9 KiB
TypeScript
import { EventSlot } from '../library-ts/browser/events/EventSlot.js';
|
|
import { FileEditorRegistry } from './FileEditorRegistry.js';
|
|
import { GuardedCall } from '../auth/GuardedCall.js';
|
|
|
|
|
|
export interface DocumentOpenedEvent
|
|
{
|
|
path: string;
|
|
content: string;
|
|
editorTag: string;
|
|
targetElement?: HTMLElement;
|
|
}
|
|
|
|
export interface DocumentPathEvent
|
|
{
|
|
path: string;
|
|
}
|
|
|
|
export class Editor
|
|
{
|
|
static _instance: Editor = null;
|
|
|
|
static get(): Editor
|
|
{
|
|
if ( ! this._instance )
|
|
{
|
|
this._instance = new Editor();
|
|
}
|
|
|
|
return this._instance;
|
|
}
|
|
|
|
projectId: string = '';
|
|
projectName: string = '';
|
|
localRoot: string = '';
|
|
remoteProject: string = '';
|
|
openDocs: Map<string, { content: string; dirty: boolean }> = new Map();
|
|
activeDoc: string | null = null;
|
|
fileEditorRegistry: FileEditorRegistry = new FileEditorRegistry();
|
|
|
|
readonly onDocumentOpened: EventSlot<DocumentOpenedEvent> = new EventSlot();
|
|
readonly onDocumentDirty: EventSlot<DocumentPathEvent> = new EventSlot();
|
|
readonly onDocumentSaved: EventSlot<DocumentPathEvent> = new EventSlot();
|
|
readonly onFilesChanged: EventSlot<void> = new EventSlot();
|
|
readonly onFileTypeUnknown: EventSlot<DocumentPathEvent> = new EventSlot();
|
|
|
|
async openDocument( filePath: string ): 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 GuardedCall.get().call( 'editor', () => fetch( this._readUrl( 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 } );
|
|
}
|
|
|
|
markDirty( filePath: string, content: string ): void
|
|
{
|
|
const doc = this.openDocs.get( filePath );
|
|
|
|
if ( ! doc )
|
|
{
|
|
return;
|
|
}
|
|
|
|
doc.content = content;
|
|
doc.dirty = true;
|
|
this.onDocumentDirty.dispatch( { path: filePath } );
|
|
}
|
|
|
|
async openDocumentIn( filePath: string, panelElement: HTMLElement ): Promise<void>
|
|
{
|
|
const editorTag = panelElement.tagName.toLowerCase();
|
|
|
|
if ( !this.openDocs.has( filePath ) )
|
|
{
|
|
const res = await GuardedCall.get().call( 'editor', () => fetch( this._readUrl( 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>
|
|
{
|
|
const doc = this.openDocs.get( filePath );
|
|
|
|
if ( ! doc || ! doc.dirty )
|
|
{
|
|
return;
|
|
}
|
|
|
|
await GuardedCall.get().call( 'user', () => fetch(
|
|
this._writeUrl( filePath ),
|
|
{
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'text/plain' },
|
|
body: doc.content
|
|
}
|
|
) );
|
|
|
|
doc.dirty = false;
|
|
this.onDocumentSaved.dispatch( { path: filePath } );
|
|
}
|
|
|
|
private _readUrl( filePath: string ): string
|
|
{
|
|
if ( this.localRoot )
|
|
return `/api/local/read?root=${ encodeURIComponent( this.localRoot ) }&path=${ encodeURIComponent( filePath ) }`;
|
|
if ( this.remoteProject )
|
|
return `/api/remote/files/${ this.remoteProject }/${ filePath }`;
|
|
return `/api/files/${ this.projectId }/${ filePath }`;
|
|
}
|
|
|
|
private _writeUrl( filePath: string ): string
|
|
{
|
|
if ( this.localRoot )
|
|
return `/api/local/write?root=${ encodeURIComponent( this.localRoot ) }&path=${ encodeURIComponent( filePath ) }`;
|
|
if ( this.remoteProject )
|
|
return `/api/remote/files/${ this.remoteProject }/${ filePath }`;
|
|
return `/api/files/${ this.projectId }/${ filePath }`;
|
|
}
|
|
}
|