rojects/source/editor/Editor.ts

147 lines
4.0 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 = '';
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 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>
{
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( 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 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 }`;
}
}