import { makeAutoObservable } from 'mobx'; import { Col } from './DocCreatorMenu'; import { TemplateFieldType, TemplateLayouts } from './TemplateBackend'; import { DynamicField } from './TemplateFieldTypes/DynamicField'; import { FieldSettings, TemplateField, ViewType } from './TemplateFieldTypes/TemplateField'; import { Conditional } from './Backend/TemplateManager'; import { TemplateDataField } from './TemplateFieldTypes/DataField'; export class Template { _mainField: DynamicField; private _dataFields: TemplateDataField[] = []; /** * A Template can be created from a description of its fields (FieldSettings) or from a DynamicField * @param definition definition of template as settings or DynamicField */ constructor(definition: FieldSettings | DynamicField) { makeAutoObservable(this); this._mainField = definition instanceof DynamicField ? definition : this.setupMainField(definition); } get childFields() { return this._mainField?.getSubfields ?? []; } // prettier-ignore get allFields() { return this._mainField?.getAllSubfields ?? []; } // prettier-ignore get contentFields() { return this.allFields.filter(field => field.isContentField); } // prettier-ignore get doc() { return this._mainField?.renderedDoc; } // prettier-ignore get title() { return this._mainField?.getTitle(); } // prettier-ignore get descriptionSummary() { return this.contentFields.map(f => `--- Field #${f.getID} (title: ${f.getTitle()}): ${f.getDescription ?? ''} ---`).join(); } // prettier-ignore get compiledContent() { return this.contentFields.map(f => `--- Field #${f.getID} (title: ${f.getTitle()}): ${f.getContent() ?? ''} ---`).join(); } // prettier-ignore cleanup = () => { //dispose each subfields disposers, etc. }; clone = (withContent: boolean = false) => { const clone = new Template(this._mainField?.makeClone(undefined, withContent) ?? TemplateLayouts.BasicSettings); this._dataFields.forEach(field => clone.addDataField(field.title)); return clone; }; getRenderedDoc = () => this.doc; getFieldByID = (id: number): TemplateField | undefined => this.allFields.filter(field => field.getID === id)[0]; getFieldByTitle = (title: string) => [...this.allFields, ...this._dataFields].filter(field => field.getTitle() === title)[0]; setupMainField = (templateInfo: FieldSettings) => TemplateField.CreateField(templateInfo, 1, undefined) as DynamicField; assignColToField = (fieldID: number, col: Col) => { const field = this.getFieldByID(fieldID); field?.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? ViewType.IMG : ViewType.TEXT); field?.setTitle(col.title); }; addDataField = (title: string, content?: string) => this._dataFields.push(new TemplateDataField(title, content)); removeDataField = (title: string) => (this._dataFields = this._dataFields.filter(field => field.title !== title)); isValidTemplate = (cols: Col[]) => this.title !== 'template_framework' && this.maxMatches(this.getMatches(cols)) === this.contentFields.length; applyConditionalLogicToField = (field: TemplateField | TemplateDataField, logic: Record) => { if (field instanceof DynamicField) return; const fieldStatements = logic[field.getTitle()]; const content = field.getContent(); fieldStatements?.forEach(statement => { if (content === statement.condition) { if (statement.target === 'Template') { if (this._mainField.renderedDoc) { this._mainField.renderedDoc[statement.attribute] = statement.value; Object.assign(this._mainField.settings.opts, { [statement.attribute]: statement.value }); } } else { const targetField = this.getFieldByTitle(statement.target); if (targetField instanceof TemplateField && targetField.renderedDoc) { targetField.renderedDoc[statement.attribute] = statement.value; Object.assign(targetField.settings.opts, { [statement.attribute]: statement.value }); } } } }); }; applyConditionalLogic = (logic: Record) => { [...this.allFields, ...this._dataFields].forEach(field => this.applyConditionalLogicToField(field, logic)); return this.getRenderedDoc(); }; setImageAsBackground(url: string, makeTransparent: boolean = false) { const fieldSettings: FieldSettings = { tl: [-1, -1], br: [1, 1], opts: {}, viewType: ViewType.IMG, }; const field = TemplateField.CreateField(fieldSettings, Math.random() * 100 + 100, this._mainField); field.setContent(url); if (makeTransparent) { this.allFields.forEach(aField => { aField.updateDocSetting('backgroundColor', 'transparent'); aField.updateDocSetting('borderWidth', '0'); }); } this._mainField.makeBackgroundField(field); } /** * This function is just a hack for now to get around weird document icon stuff (specifically it misses the background) */ setMatteBackground(makeTransparent: boolean = false) { if (this._mainField.hasBackground) { return; } const fieldSettings: FieldSettings = { tl: [-1, -1], br: [1, 1], opts: { backgroundColor: String(this._mainField.renderedDoc!.backgroundColor) }, viewType: ViewType.TEXT, }; const field = TemplateField.CreateField(fieldSettings, Math.random() * 100 + 100, this._mainField); makeTransparent && this.allFields.forEach(aField => { aField.updateDocSetting('backgroundColor', 'transparent'); aField.updateDocSetting('borderWidth', '0'); }); this._mainField.makeBackgroundField(field); } getMatches = (cols: Col[]): number[][] => { const numFields = this.contentFields.length; if (cols.length !== numFields) return []; const matches = Array(numFields); this.contentFields.forEach((field, i) => (matches[i] = field.matches(cols))); return matches; }; maxMatches = (matches: number[][]) => { if (matches.length === 0) return 0; const fieldsCt = this.contentFields.length; const used = Array(fieldsCt).fill(false); const mt = Array(fieldsCt).fill(-1); const augmentingPath = (v: number): boolean => { if (!used[v]) { used[v] = true; for (const to of matches[v]) { if (mt[to] === -1 || augmentingPath(mt[to])) { mt[to] = v; return true; } } } return false; }; for (let v = 0; v < fieldsCt; ++v) { used.fill(false); augmentingPath(v); } let count: number = 0; for (let i = 0; i < fieldsCt; ++i) { if (mt[i] !== -1) ++count; } return count; }; }