import { AtomBinder } from "@web-atoms/core/dist/core/AtomBinder"; import { AtomDisposableList } from "@web-atoms/core/dist/core/AtomDisposableList"; import IconService from "../services/IconService"; export interface IFileMessage extends monaco.languages.typescript.Diagnostic { model?: FileModel; } export default class FileModel { public name: string; public ext?: string; public isBuilding: boolean = false; public isOpen?: boolean = false; public get fileIcon(): string { return this.icon || (this.icon = IconService.getIcon(this)); } public get isPacked() { return this.content && this.content.indexOf("@web-atoms-pack") !== -1; } public icon?: string; // public errors: IFileMessage[] = []; // public warnings: IFileMessage[] = []; public messages: IFileMessage[] = []; public navigationItems: any[]; // if this is folder... public children?: FileModel[]; public snapshot?: string; public remoteUrl?: string; public tempUrl?: string; public rename: boolean = false; public get canEdit(): boolean { return this.isFolder ? false : (/\.(ts|tsx|js|jsx|json|map|md|txt)$/.test(this.name)); } public get dirtyChild(): boolean { if (this.url.startsWith("node_modules/")) { return false; } if (this.children) { for (const iterator of this.children) { if (iterator.dirtyChild) { return true; } } } else { if (!this.snapshot) { return true; } } return false; } public get isReadOnly(): boolean { return /^(dist|node\_modules|tslint\.json|waconfig\.json)$/.test(this.url) ? true : (this.parent ? this.parent.isReadOnly : false); } public get indent(): number { return this.parent ? this.parent.indent + 1 : 0; } private mContent?: string; public get content(): string { return this.mContent; } public set content(value: string) { this.mContent = value; AtomBinder.refreshValue(this, "content"); AtomBinder.refreshValue(this, "dirty"); AtomBinder.refreshValue(this, "isPacked"); } private mTextModel: monaco.editor.ITextModel; public get model(): monaco.editor.ITextModel { if (!this.mTextModel) { const name = this.name; let language = "html"; if (/\.(ts|tsx)$/i.test(name)) { language = "typescript"; } else if (/\.(js|jsx)$/i.test(name)) { language = "javascript"; } else if (/\.(json)$/i.test(name)) { language = "json"; } else if (/\.(css)$/i.test(name)) { language = "css"; } const uri = monaco.Uri.parse(`http://a/${this.url}`); const m = monaco.editor.getModel(uri); this.mTextModel = m || monaco.editor.createModel( this.content, language, uri); // this.mTextModel.onDidChangeContent(() => { // this.content = this.mTextModel.getValue(); // }); } return this.mTextModel; } private mOriginalContent?: string; public get originalContent(): string { return this.mOriginalContent; } public set originalContent(value: string) { this.mOriginalContent = value; AtomBinder.refreshValue(this, "originalContent"); AtomBinder.refreshValue(this, "dirty"); } public get dirty() { return this.isFolder ? false : this.content !== this.originalContent; } public get isFolder(): boolean { return this.children ? true : false; } public get url(): string { return (this.parent && this.parent.name) ? `${this.parent.url}/${this.name}` : ( this.name || ""); } public get descendants(): FileModel[] { const a = []; this.fillDescendants(a); return a; } constructor(public readonly parent: FileModel) { } public forEach(action: (file: FileModel) => void) { if (!this.children) { return; } for (const iterator of this.children) { if (!iterator.children) { action(iterator); } else { iterator.forEach(action); } } } public create(path: string, isFolder?: boolean): FileModel { return this.get(path, true, isFolder); } public get(path: string, create?: boolean, isFolder?: boolean): FileModel { const tokens = path.split("/"); let start: FileModel = this; for (let i = 0; i < tokens.length - 1; i++) { const element = tokens[i]; start = start.resolve(element, create, true); } return start.resolve(tokens[tokens.length - 1], create, isFolder); } public watchChanges(update: () => void) { const disposables = new AtomDisposableList(); let id = 0; disposables.add(this.model.onDidChangeContent(() => { if (id) { clearTimeout(id); } id = setTimeout(() => { update(); id = 0; }, 500); })); disposables.add(this.model.onWillDispose(() => { disposables.dispose(); })); return disposables; } private sort(): void { // only issue refresh if children were changed... const ids = this.children.map((i) => i.url).join("\n"); this.children.sort((a, b) => a.isFolder ? (b.isFolder ? a.name.localeCompare(b.name) : -1) : (b.isFolder) ? 1 : a.name.localeCompare(b.name)); const ids2 = this.children.map((i) => i.url).join("\n"); if (ids !== ids2) { this.children.refresh(); } } private fillDescendants(a: FileModel[]) { if (!this.children) { return; } for (const iterator of this.children) { if (!iterator.isFolder) { a.push(iterator); } iterator.fillDescendants(a); } } private resolve(name: string, create?: boolean, isFolder?: boolean): FileModel { if (name.indexOf("/") !== -1) { throw new Error("Path must not contain folder"); } const c = this.children || (this.children = []); let child = c.find((x) => x.name === name); if (!child && create) { child = new FileModel(this); child.name = name; if (isFolder) { child.children = []; } this.children.push(child); this.sort(); AtomBinder.refreshValue(this, "fileIcon"); } return child; } }