127 lines
3.2 KiB
TypeScript
127 lines
3.2 KiB
TypeScript
import { EventSlot } from '../library-ts/browser/events/EventSlot.js';
|
|
import { FileEditorRegistry } from './FileEditorRegistry.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 = '';
|
|
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 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 } );
|
|
}
|
|
|
|
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>
|
|
{
|
|
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>
|
|
{
|
|
const doc = this.openDocs.get( filePath );
|
|
|
|
if ( ! doc || ! doc.dirty )
|
|
{
|
|
return;
|
|
}
|
|
|
|
await fetch(
|
|
`/api/files/${this.projectId}/${filePath}`,
|
|
{
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'text/plain' },
|
|
body: doc.content
|
|
}
|
|
);
|
|
|
|
doc.dirty = false;
|
|
this.onDocumentSaved.dispatch( { path: filePath } );
|
|
}
|
|
}
|