diff options
14 files changed, 572 insertions, 618 deletions
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index fc2d7f572..b335432c9 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -65,8 +65,8 @@ import { PresBox, PresElementBox } from './nodes/trails'; import { FaceRecognitionHandler } from './search/FaceRecognitionHandler'; import { SearchBox } from './search/SearchBox'; import { StickerPalette } from './smartdraw/StickerPalette'; -import { Field } from './nodes/DataVizBox/DocCreatorMenu/FieldTypes/Field'; -import { FieldUtils } from './nodes/DataVizBox/DocCreatorMenu/FieldTypes/FieldUtils'; +import { TemplateField } from './nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField'; +import { TemplateFieldUtils } from './nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateFieldUtils'; dotenv.config(); @@ -102,7 +102,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' }; new PingManager(); new KeyManager(); new FaceRecognitionHandler(); - Field.initField = FieldUtils.initField; // set the init function for fields + TemplateField.initField = TemplateFieldUtils.initField; // set the init function for fields // initialize plugins and classes that require plugins CollectionDockingView.Init(TabDocView); diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx index a702218b0..97faf01c2 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx @@ -27,7 +27,7 @@ import { DocumentView, DocumentViewInternal } from '../../DocumentView'; import { OpenWhere } from '../../OpenWhere'; import { DataVizBox } from '../DataVizBox'; import './DocCreatorMenu.scss'; -import { Field, ViewType } from './FieldTypes/Field'; +import { TemplateField, ViewType } from './TemplateFieldTypes/TemplateField'; import { Template } from './Template'; import { TemplateFieldSize, TemplateFieldType, TemplateLayouts } from './TemplateBackend'; import { TemplateManager } from './TemplateManager'; @@ -371,7 +371,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> } @action updateRenderedPreviewCollection = async (template: Template) => { - this._fullyRenderedDocs = (await this.createDocsFromTemplate(template)) ?? []; + this._fullyRenderedDocs = ((await this.createDocsFromTemplate(template)) ?? []).filter(doc => doc).map(doc => doc!); console.log(this._fullyRenderedDocs); this.updateRenderedDocCollection(); }; @@ -630,7 +630,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> await Promise.all(renderedTemplatePromises); } - templates.forEach(template => template.mainField.initializeDocument({ subfields: [], title: template.title, opts: {}, viewType: ViewType.FREEFORM, tl: [0, 0], br: [900, 900] })); + templates.forEach(template => template.mainField.initializeDocument({ title: template.title, opts: {}, viewType: ViewType.FREEFORM, tl: [0, 0], br: [900, 900] }, [])); setTimeout(() => { this.setSuggestedTemplates(templates); @@ -638,10 +638,10 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> }); }; - renderGPTImageCall = async (template: Template, col: Col, fieldNumber: number): Promise<boolean> => { + renderGPTImageCall = async (template: Template, col: Col, fieldNumber: number | undefined): Promise<boolean> => { const generateAndLoadImage = async (fieldNum: string, column: Col, prompt: string) => { const url = await this.generateGPTImage(prompt); - const field: Field = template.getFieldByID(Number(fieldNum)); + const field: TemplateField = template.getFieldByID(Number(fieldNum)); field.setContent(url ?? '', ViewType.IMG); field.setTitle(col.title); @@ -665,7 +665,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> return true; }; - renderGPTTextCall = async (template: Template, col: Col, fieldNum: number): Promise<boolean> => { + renderGPTTextCall = async (template: Template, col: Col, fieldNum: number | undefined): Promise<boolean> => { const wordLimit = (size: TemplateFieldSize) => { switch (size) { case TemplateFieldSize.TINY: @@ -697,7 +697,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> if (res) { const assignments: { [title: string]: { number: string; content: string } } = JSON.parse(res); Object.entries(assignments).forEach(([, /* title */ info]) => { - const field: Field = template.getFieldByID(Number(info.number)); + const field: TemplateField = template.getFieldByID(Number(info.number)); // const column = this.getColByTitle(title); field.setContent(info.content ?? '', ViewType.TEXT); @@ -771,7 +771,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> return templateCopy.mainField.renderedDoc; }; - let docs: Promise<Doc>[]; + let docs: Promise<Doc | undefined>[]; if (this.DEBUG_MODE) { docs = [1, 2, 3, 4].map(() => processContent({})); } else { @@ -868,30 +868,31 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> get templatesPreviewContents() { const GPTOptions = <div></div>; - const previewDoc = (doc: Doc, template: Template) => ( - <DocumentView - Document={doc} - isContentActive={emptyFunction} // !!! should be return false - addDocument={returnFalse} - moveDocument={returnFalse} - removeDocument={returnFalse} - PanelWidth={() => (this._selectedTemplate === template ? 104 : 111)} - PanelHeight={() => (this._selectedTemplate === template ? 104 : 111)} - ScreenToLocalTransform={() => new Transform(-this._pageX - 5, -this._pageY - 35, 1)} - renderDepth={1} - whenChildContentsActiveChanged={emptyFunction} - focus={emptyFunction} - styleProvider={DefaultStyleProvider} - addDocTab={this._props.addDocTab} - pinToPres={() => undefined} - childFilters={returnEmptyFilter} - childFiltersByRanges={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - fitContentsToBox={returnFalse} - fitWidth={returnFalse} - hideDecorations={true} - /> - ); + const previewDoc = (doc: Doc | undefined, template: Template) => + !doc ? null : ( + <DocumentView + Document={doc} + isContentActive={emptyFunction} // !!! should be return false + addDocument={returnFalse} + moveDocument={returnFalse} + removeDocument={returnFalse} + PanelWidth={() => (this._selectedTemplate === template ? 104 : 111)} + PanelHeight={() => (this._selectedTemplate === template ? 104 : 111)} + ScreenToLocalTransform={() => new Transform(-this._pageX - 5, -this._pageY - 35, 1)} + renderDepth={1} + whenChildContentsActiveChanged={emptyFunction} + focus={emptyFunction} + styleProvider={DefaultStyleProvider} + addDocTab={this._props.addDocTab} + pinToPres={() => undefined} + childFilters={returnEmptyFilter} + childFiltersByRanges={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + fitContentsToBox={returnFalse} + fitWidth={returnFalse} + hideDecorations={true} + /> + ); //<img className='docCreatorMenu-preview-image expanded' src={this._expandedPreview.icon!.url.href.replace(".png", "_o.png")} /> @@ -1096,19 +1097,13 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> const collectionFactory = (): ((docs: Doc[], options: DocumentOptions) => Doc) => { switch (this._layout.type) { - case LayoutType.CAROUSEL3D: - return Docs.Create.Carousel3DDocument; - case LayoutType.FREEFORM: - return Docs.Create.FreeformDocument; - case LayoutType.CARD: - return Docs.Create.CardDeckDocument; - case LayoutType.MASONRY: - return Docs.Create.MasonryDocument; - case LayoutType.CAROUSEL: - return Docs.Create.CarouselDocument; - default: - return Docs.Create.FreeformDocument; - } + case LayoutType.CAROUSEL3D: return Docs.Create.Carousel3DDocument; + case LayoutType.FREEFORM: return Docs.Create.FreeformDocument; + case LayoutType.CARD: return Docs.Create.CardDeckDocument; + case LayoutType.MASONRY: return Docs.Create.MasonryDocument; + case LayoutType.CAROUSEL: return Docs.Create.CarouselDocument; + default: return Docs.Create.FreeformDocument; + } // prettier-ignore }; const collection = collectionFactory()(this._fullyRenderedDocs, { @@ -1241,9 +1236,9 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> emptyFunction, undoable(clickEv => { clickEv.stopPropagation(); - if (!this._selectedTemplate) return; + if (!this._selectedTemplate || !this._selectedTemplate.getRenderedDoc()) return; const layout: DataVizTemplateLayout = { - template: this._selectedTemplate.getRenderedDoc(), + template: this._selectedTemplate.getRenderedDoc()!, layout: { type: this._layout.type, xMargin: this._layout.xMargin, yMargin: this._layout.yMargin, repeat: 0 }, columns: this.columnsCount, rows: this.rowsCount, diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/DecorationField.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/DecorationField.tsx deleted file mode 100644 index 98a9dc7a6..000000000 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/DecorationField.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { DynamicField } from './DynamicField'; - -export class DecorationField extends DynamicField {} diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/DynamicField.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/DynamicField.tsx deleted file mode 100644 index f100830d3..000000000 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/DynamicField.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import { Doc, DocListCast } from '../../../../../../fields/Doc'; -import { Docs } from '../../../../../documents/Documents'; -import { Field, FieldSettings, ViewType } from './Field'; -import { List } from '../../../../../../fields/List'; -import { DocData } from '../../../../../../fields/DocSymbols'; -import { DocumentType } from '../../../../../documents/DocumentTypes'; -import { reaction } from 'mobx'; -import { IDisposer } from 'mobx-utils'; - -export class DynamicField extends Field { - protected subfields!: Field[]; - - protected disposers: { [name: string]: IDisposer } = {}; - init(settings: FieldSettings, id: number, parent?: DynamicField) { - super.baseinit(undefined as unknown as Doc, settings, parent, id); - const { doc, subFields } = this.initializeDocument(settings); - this.Document = doc; - this.subfields = subFields; - - this.disposers.fieldList = reaction( - () => DocListCast(this.Document[Doc.LayoutFieldKey(this.Document)]), - docs => { - console.log('updated'); - this.handleFieldUpdate(docs); - } - ); - return this; - } - - static setupSubfields(subFieldSettings: FieldSettings[] | undefined, parent?: DynamicField): Field[] { - return subFieldSettings?.map((fieldSettings, index) => Field.initField!(fieldSettings, index, parent)) || []; - } - - get getSubfields(): Field[] { - return this.subfields ?? []; - } - get getAllSubfields() { - let fields: Field[] = []; - this.subfields?.forEach(field => { - fields.push(field); - fields = fields.concat(field instanceof DynamicField ? field.getAllSubfields : ([] as Field[])); - }); - return fields; - } - - setSubFields = (fields: Field[]) => { - this.subfields = fields; - }; - handleFieldUpdate = (newDocsList: Doc[]) => { - const currRenderedDocs: Set<Doc> = new Set(); - this.subfields.forEach(field => currRenderedDocs.add(field.Document)); - newDocsList.forEach(doc => { - if (!currRenderedDocs.has(doc)) { - this.addFieldFromDoc(doc); - } - }); - currRenderedDocs.forEach(doc => { - if (!newDocsList.includes(doc)) { - const fields = this.subfields.filter(field => field.Document === doc); - fields.forEach(field => this.removeField(field)); - } - }); - }; - - addFieldFromDoc = (doc: Doc) => { - const par = this.Document; - const settings: FieldSettings = { - tl: [Number(doc._x) / Number(par._width), Number(doc._y) / Number(par._height)], - br: [(Number(doc._x) + Number(doc._width)) / Number(par._width), (Number(doc._y) + Number(doc._height)) / Number(par._height)], - viewType: doc.type === DocumentType.COL ? ViewType.FREEFORM : ViewType.STATIC, - opts: {}, - }; - - const newField: Field = Field.initField!(settings, this.subfields.length, this); - this.subfields.push(newField); - }; - - addField = (field: Field) => { - if (!this.subfields.includes(field)) { - this.subfields.push(field); - console.log('field added'); - // Doc.SetContainer(field.Document, this.Document); - } - }; - - dispose = () => { - Object.values(this.disposers).forEach(disposer => disposer?.()); - }; - - removeField = (field: Field) => { - // var childDocs: Doc[] = DocListCast(this.Document[Doc.LayoutFieldKey(this.Document)]); - // this.Document[Doc.LayoutFieldKey(this.Document)] = new List<Doc>([...childDocs.splice(childDocs.indexOf(field.Document), 1)]); - this.subfields.splice(this.subfields.indexOf(field), 1); - field instanceof DynamicField && field.dispose(); - }; - - exchangeFields = (newField: Field, oldField: Field) => { - this.subfields.splice(this.subfields.indexOf(oldField), 1, newField); - // this.renderedDoc. - }; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - setContent = (content: string, type: ViewType) => { - return; - }; - getContent = () => { - return ''; - }; - get isContentField(): boolean { - return false; - } - - addChildToDocument = (doc: Doc) => { - Doc.SetContainer(doc, this.Document); - }; - - matches = (): Array<number> => { - return []; - }; - - async makeClone(parent?: DynamicField): Promise<Field> { - const field = (await super.makeClone(parent)) as unknown as DynamicField; - field.subfields = await Promise.all(this.subfields.map(async cloneField => await cloneField.makeClone(field))); - field.Document[DocData].data = new List<Doc>(field.subfields.map(subfield => subfield.renderedDoc)); - return field; - } - initializeDocument = (settings: FieldSettings) => { - const subFields = DynamicField.setupSubfields(settings.subfields, this); - const renderedSubfields = subFields.map(field => field.renderedDoc); - settings.opts.title = settings.title; - switch (settings.viewType) { - case ViewType.CAROUSEL3D: - return { doc: Docs.Create.Carousel3DDocument(renderedSubfields, settings.opts), subFields }; - case ViewType.FREEFORM: - return { doc: Docs.Create.FreeformDocument(renderedSubfields, settings.opts), subFields }; - default: - return { doc: Docs.Create.FreeformDocument(renderedSubfields, settings.opts), subFields }; - } - }; -} - -// export class DynamicField extends Field { -// protected subfields: Field[]; - -// protected Document!: Doc; - -// constructor(settings: FieldSettings, id: number, parent?: Field) { -// super(settings, id, parent); -// this.subfields = this.setupSubfields(this); -// this.initializeDocument(); -// } - -// setContent = (content: string, type: ViewType) => { return }; -// getContent = () => { return '' }; -// get isContentField(): boolean { return false }; - -// addChildToDocument = (doc: Doc) => { -// Doc.SetContainer(doc, this.Document); -// } - -// matches = (cols: Col[]): Array<number> => { -// return []; -// } - -// initializeDocument = (): Doc => { -// let doc: Doc; -// const renderedSubfields: Doc[] = this.subfields.map(field => field.renderedDoc); -// switch (this.settings.viewType) { -// case ViewType.CAROUSEL3D: -// doc = Docs.Create.Carousel3DDocument(renderedSubfields, { -// title: this.title, -// }); -// break; -// case ViewType.FREEFORM: -// doc = Docs.Create.FreeformDocument(renderedSubfields, { -// title: this.title, -// }); -// break; -// default: -// doc = Docs.Create.FreeformDocument(renderedSubfields, { -// title: this.title, -// }); -// break; -// } - -// this.Document = doc; -// return doc; -// } - -// } diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/Field.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/Field.tsx deleted file mode 100644 index 111ad8328..000000000 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/Field.tsx +++ /dev/null @@ -1,167 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable no-use-before-define */ -import { computed } from 'mobx'; -import { Doc } from '../../../../../../fields/Doc'; -import { DocData } from '../../../../../../fields/DocSymbols'; -import { Copy } from '../../../../../../fields/FieldSymbols'; -import { ObjectField } from '../../../../../../fields/ObjectField'; -import { ComputedField } from '../../../../../../fields/ScriptField'; -import { DocumentOptions } from '../../../../../documents/Documents'; -import { Col } from '../DocCreatorMenu'; -import { TemplateFieldSize, TemplateFieldType } from '../TemplateBackend'; - -export abstract class Field { - Document!: Doc; - - static initField?: (settings: FieldSettings, index: number, parent: any /* DynamicField */ | undefined, sameId?: boolean) => Field = undefined; - - protected parent?: any; - protected id!: number; - settings!: FieldSettings; - protected title: string = ''; - dimensions!: FieldDimensions; - - constructor() {} - - baseinit(doc: Doc, settings: FieldSettings, parent?: any, id: number = 1) { - this.id = id; - this.Document = doc; - this.parent = parent; - this.settings = settings; - this.title = settings.title ?? ''; - this.dimensions = this.getLocalDimensions({ tl: this.settings.tl, br: this.settings.br }, this.parent?.getDimensions); - this.applyBasicOpts(this.dimensions, settings); - return this; - } - - get renderedDoc() { - return this.Document; - } - get getDimensions() { - return this.dimensions; - } - get getID() { - return this.id; - } - get getDescription(): string { - return this.settings.description ?? ''; - } - get viewType(): ViewType { - return this.settings.viewType; - } - - setTitle = (title: string) => { - this.title = title; - this.Document.title = title; - }; - getTitle = () => { - return this.title; - }; - - abstract get isContentField(): boolean; - abstract setContent(content: string, type: ViewType): void; - abstract getContent(): string; - - async makeClone(parent?: any /* DynamicField*/): Promise<Field> { - const field = Field.initField!(this.settings, this.id, parent, true); // create a value for this.Document/subfields that we want to ignore - field.Document = (await Doc.MakeClone(this.Document)).clone; - field.title = this.title; - field.dimensions = this.dimensions; - return field; - } - - @computed get documentOptions(): DocumentOptions { - const opts: DocumentOptions = {}; - Object.assign(opts, this.Document[DocData]); - Object.entries(opts).forEach(([key, field]) => { - if (field instanceof ObjectField) { - Object.assign(opts, { [key]: ObjectField.MakeCopy(field) }); - } else if (field instanceof ComputedField) { - Object.assign(opts, { [key]: field[Copy]() }); - } - }); - return opts; - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - changeFieldType = (newType: ViewType): Field => { - const newField = Field.initField!(this.settings, this.id, this.parent, true); - this.parent?.exchangeFields(newField, this); - return newField; - }; - - matches = (cols: Col[]): number[] => { - const colMatchesField = (col: Col) => { - const isMatch: boolean = (this.settings.sizes?.some(size => col.sizes?.includes(size)) && this.settings.types?.includes(col.type)) ?? false; - return isMatch; - }; - - const matches: Array<number> = []; - - cols.forEach((col, v) => { - if (colMatchesField(col)) { - matches.push(v); - } - }); - - return matches; - }; - - private getLocalDimensions = (coords: { tl: [number, number]; br: [number, number] }, parentDimensions?: FieldDimensions): FieldDimensions => { - if (!parentDimensions) { - return { width: coords.br[0] - coords.tl[0], height: coords.br[1] - coords.tl[1], coord: { x: coords.tl[0], y: coords.tl[1] } }; - } - const l = (coords.tl[0] * parentDimensions.width) / 2; - const t = coords.tl[1] * parentDimensions.height / 2; //prettier-ignore - const r = (coords.br[0] * parentDimensions.width) / 2; - const b = coords.br[1] * parentDimensions.height / 2; //prettier-ignore - const width = r - l; - const height = b - t; - const coord = { x: l, y: t }; - return { width, height, coord }; - }; - - private applyBasicOpts = (dimensions: FieldDimensions, settings: FieldSettings) => { - const opts: DocumentOptions = settings.opts; - opts.isDefaultTemplateDoc ??= true; - opts._layout_hideScroll ??= true; - opts.x ??= dimensions.coord.x; - opts.y ??= dimensions.coord.y; - opts._height ??= dimensions.height; - opts._width ??= dimensions.width; - opts._nativeWidth ??= dimensions.width; - opts._nativeHeight ??= dimensions.height; - opts._layout_nativeDimEditable ??= true; - }; -} - -export type FieldSettings = { - tl: [number, number]; - br: [number, number]; - opts: DocumentOptions; - viewType: ViewType; - title?: string; - subfields?: FieldSettings[]; - types?: TemplateFieldType[]; - sizes?: TemplateFieldSize[]; - description?: string; -}; - -export enum ViewType { - CAROUSEL3D = 'carousel3d', - FREEFORM = 'freeform', - STATIC = 'static', - DEC = 'decoration', - IMG = 'image', - TEXT = 'text', -} - -export type FieldDimensions = { - width: number; - height: number; - coord: { x: number; y: number }; -}; - -export type FieldTree = { - node: { field: Field; subfields: FieldTree[] }; -}; diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/StaticContentField.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/StaticContentField.tsx deleted file mode 100644 index 75aea5440..000000000 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/StaticContentField.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { DocData } from '../../../../../../fields/DocSymbols'; -import { RichTextField } from '../../../../../../fields/RichTextField'; -import { ImageField } from '../../../../../../fields/URLField'; -import { Docs } from '../../../../../documents/Documents'; -import { Field, FieldSettings, ViewType } from './Field'; -import { FieldUtils } from './FieldUtils'; -import { DynamicField } from './DynamicField'; - -export abstract class StaticContentField extends Field { - protected content: string = ''; - - abstract setContent(content: string, type?: ViewType): void; - getContent = () => { - return this.content ?? 'unset'; - }; - get isContentField(): boolean { - return true; - } -} - -export class ImageTemplateField extends StaticContentField { - init(settings: FieldSettings, id: number, parent: DynamicField | undefined) { - super.baseinit(this.initializeDocument(settings), settings, parent, id); - return this; - } - - setContent = (url: string, type?: ViewType) => { - this.settings.viewType = type ?? this.settings.viewType; - - if (type === ViewType.IMG || type === undefined) { - const imgField = new ImageField(url); - this.Document[DocData].data = imgField; - this.content = url; - } else { - const updatedField = this.changeFieldType(type); - updatedField.setContent(url, type); - } - }; - - initializeDocument = (settings: FieldSettings) => { - settings.opts.title = settings.title; - settings.opts._layout_fitWidth = false; - - return Docs.Create.ImageDocument('', settings.opts); - }; -} - -export class TextTemplateField extends StaticContentField { - init(settings: FieldSettings, id: number, parent?: DynamicField) { - super.baseinit(this.initializeDocument(settings), settings, parent, id); - this.Document.text_fontSize = `${FieldUtils.calculateFontSize(this.dimensions.width, this.dimensions.height, '', true)}`; - return this; - } - setContent = (text: string, type?: ViewType) => { - this.settings.viewType = type ?? this.settings.viewType; - - if (type === ViewType.TEXT || type === undefined) { - const rtf = { - doc: { - type: 'doc', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text, - }, - ], - }, - ], - }, - selection: { type: 'text', anchor: 1, head: 1 }, - storedMarks: [], - }; - this.content = text; - const field = new RichTextField(JSON.stringify(rtf), text); - this.Document[DocData]['text'] = field; - } else { - const updatedField = this.changeFieldType(type); - updatedField.setContent(text, type); - } - }; - - initializeDocument = (settings: FieldSettings) => { - const opts = settings.opts; - opts.title = settings.title; - return Docs.Create.TextDocument('', opts); - }; -} diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts index 3c610b36a..ef6867e32 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts @@ -1,7 +1,7 @@ import { makeAutoObservable } from 'mobx'; import { Col } from './DocCreatorMenu'; -import { DynamicField } from './FieldTypes/DynamicField'; -import { Field, FieldSettings } from './FieldTypes/Field'; +import { DynamicField } from './TemplateFieldTypes/DynamicField'; +import { TemplateField, FieldSettings } from './TemplateFieldTypes/TemplateField'; import { TemplateLayouts } from './TemplateBackend'; export class Template { @@ -14,13 +14,13 @@ export class Template { this.mainField = this.setupMainField(templateInfo); } - get childFields(): Field[] { + get childFields(): TemplateField[] { return this.mainField.getSubfields; } - get allFields(): Field[] { + get allFields(): TemplateField[] { return this.mainField.getAllSubfields; } - get contentFields(): Field[] { + get contentFields(): TemplateField[] { return this.allFields.filter(field => field.isContentField); } get doc() { @@ -37,15 +37,15 @@ export class Template { cloneBase = async (): Promise<Template> => { const clone: Template = new Template(TemplateLayouts.BasicSettings); clone.mainField = (await this.mainField.makeClone(undefined)) as unknown as DynamicField; - clone.mainField.renderedDoc._width = this.mainField.renderedDoc._width; - clone.mainField.renderedDoc._height = this.mainField.renderedDoc._height; + // clone.mainField.renderedDoc._width = this.mainField.renderedDoc._width; + // clone.mainField.renderedDoc._height = this.mainField.renderedDoc._height; return clone; }; printFieldInfo = () => { this.allFields.forEach(field => { const doc = field.renderedDoc; - console.log('title: ', field.getTitle(), ' width: ', doc.width); + console.log('title: ', field.getTitle(), ' width: ', doc?.width); }); }; @@ -53,7 +53,7 @@ export class Template { return this.doc; }; - getFieldByID = (id: number): Field => { + getFieldByID = (id: number): TemplateField => { return this.allFields.filter(field => field.getID === id)[0]; }; @@ -62,7 +62,7 @@ export class Template { }; setupMainField = (templateInfo: FieldSettings) => { - return new DynamicField().init(templateInfo, 1); + return DynamicField.Create(templateInfo, 1); }; get descriptionSummary(): string { @@ -82,8 +82,7 @@ export class Template { } isValidTemplate = (cols: Col[]) => { - const matches: number[][] = this.getMatches(cols); - const maxMatches: number = this.maxMatches(matches); + const maxMatches = this.maxMatches(this.getMatches(cols)); return maxMatches === this.contentFields.length; }; diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateBackend.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateBackend.ts index 2b32d49aa..9b0fac3a6 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateBackend.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateBackend.ts @@ -1,4 +1,4 @@ -import { FieldSettings, ViewType } from "./FieldTypes/Field"; +import { FieldSettings, ViewType } from './TemplateFieldTypes/TemplateField'; export enum TemplateFieldType { TEXT = 'text', @@ -24,8 +24,8 @@ export class TemplateLayouts { tl: [0, 0], br: [400, 700], viewType: ViewType.FREEFORM, - opts: {} - } + opts: {}, + }; public static FourField001: FieldSettings = { title: 'fourfield001', @@ -100,7 +100,7 @@ export class TemplateLayouts { public static FourField002: FieldSettings = { title: 'fourfield002', viewType: ViewType.FREEFORM, - tl: [0,0], + tl: [0, 0], br: [425, 778], opts: { backgroundColor: '#242425', @@ -273,8 +273,8 @@ export class TemplateLayouts { public static FourField004: FieldSettings = { title: 'fourfield04', viewType: ViewType.FREEFORM, - tl: [0,0], - br: [414,583], + tl: [0, 0], + br: [414, 583], opts: { backgroundColor: '#6CCAF0', //borderColor: '#1088C3', @@ -350,44 +350,44 @@ export class TemplateLayouts { public static FourField005: FieldSettings = { title: 'fourfield05', viewType: ViewType.FREEFORM, - tl: [0,0], - br: [400,550], + tl: [0, 0], + br: [400, 550], opts: { backgroundColor: '#95A575', }, subfields: [ { viewType: ViewType.STATIC, - tl: [-0.9, -.925], - br: [-.075, -.775], + tl: [-0.9, -0.925], + br: [-0.075, -0.775], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], description: 'A small text field for a title or word(s) that categorize the rest of the content.', opts: { borderColor: '#3B4A2C', borderWidth: 8, - hCentering: "h-center", + hCentering: 'h-center', backgroundColor: '#B8DC90', }, }, { viewType: ViewType.STATIC, - tl: [.075, -.925], - br: [.9, -.775], + tl: [0.075, -0.925], + br: [0.9, -0.775], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], description: 'A small text field for a title that categorizes the rest of the content.', opts: { borderColor: '#3B4A2C', borderWidth: 8, - hCentering: "h-center", + hCentering: 'h-center', backgroundColor: '#B8DC90', }, }, { viewType: ViewType.DEC, - tl: [-.82, -.4], - br: [-.5, -.2], + tl: [-0.82, -0.4], + br: [-0.5, -0.2], opts: { backgroundColor: '#94B058', borderColor: '#3B4A2C', @@ -396,8 +396,8 @@ export class TemplateLayouts { }, { viewType: ViewType.STATIC, - tl: [-0.66, -.65], - br: [0.66, .25], + tl: [-0.66, -0.65], + br: [0.66, 0.25], types: [TemplateFieldType.VISUAL], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE], description: 'A medium to large field in the center of the template, for the main visual content.', @@ -409,22 +409,22 @@ export class TemplateLayouts { }, { viewType: ViewType.STATIC, - tl: [-.875, .425], - br: [0.875, .925], + tl: [-0.875, 0.425], + br: [0.875, 0.925], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE], description: 'A medium to large field at the bottom of the template, for the main text content.', opts: { borderColor: '#3B4A2C', borderWidth: 8, - hCentering: "h-center", + hCentering: 'h-center', backgroundColor: '#B8DC90', }, }, { viewType: ViewType.DEC, - tl: [-1.1, -.62], - br: [-.9, -.5], + tl: [-1.1, -0.62], + br: [-0.9, -0.5], opts: { backgroundColor: '#7A9D31', borderColor: '#3B4A2C', @@ -434,7 +434,7 @@ export class TemplateLayouts { { viewType: ViewType.DEC, tl: [-1.1, 0], - br: [-.9, .15], + br: [-0.9, 0.15], opts: { backgroundColor: '#94B058', borderColor: '#3B4A2C', @@ -443,8 +443,8 @@ export class TemplateLayouts { }, { viewType: ViewType.DEC, - tl: [-.93, -.265], - br: [-.715, -.125], + tl: [-0.93, -0.265], + br: [-0.715, -0.125], opts: { backgroundColor: '#728745', borderColor: '#3B4A2C', @@ -453,8 +453,8 @@ export class TemplateLayouts { }, { viewType: ViewType.DEC, - tl: [.7, -.45], - br: [.85, -.3], + tl: [0.7, -0.45], + br: [0.85, -0.3], opts: { backgroundColor: '#7A9D31', borderColor: '#3B4A2C', @@ -463,8 +463,8 @@ export class TemplateLayouts { }, { viewType: ViewType.DEC, - tl: [.8, .03], - br: [1.2, .33], + tl: [0.8, 0.03], + br: [1.2, 0.33], opts: { backgroundColor: '#728745', borderColor: '#3B4A2C', @@ -473,43 +473,43 @@ export class TemplateLayouts { }, { viewType: ViewType.DEC, - tl: [.875, -.13], - br: [1.2, .12], + tl: [0.875, -0.13], + br: [1.2, 0.12], opts: { backgroundColor: '#94B058', borderColor: '#3B4A2C', borderWidth: 8, }, }, - ] - } + ], + }; public static FourFieldCarousel: FieldSettings = { title: 'title_fourfieldcarousel', viewType: ViewType.FREEFORM, - tl:[0,0], - br:[500, 600], + tl: [0, 0], + br: [500, 600], opts: { backgroundColor: '#D7CBAB', }, subfields: [ { viewType: ViewType.STATIC, - tl: [-0.8, -.9], - br: [0.8, -.5], + tl: [-0.8, -0.9], + br: [0.8, -0.5], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], description: 'A small text field for a title that categorizes the rest of the content.', opts: { - hCentering: "h-center", + hCentering: 'h-center', backgroundColor: 'transparent', text_transform: 'uppercase', }, }, { viewType: ViewType.CAROUSEL3D, - tl: [-0.9, -.5], - br: [0.9, .25], + tl: [-0.9, -0.5], + br: [0.9, 0.25], opts: { borderColor: '#847F69', borderWidth: 8, @@ -518,8 +518,8 @@ export class TemplateLayouts { subfields: [ { viewType: ViewType.STATIC, - tl: [-.4, -.6], - br: [.4, .6], + tl: [-0.4, -0.6], + br: [0.4, 0.6], types: [TemplateFieldType.VISUAL, TemplateFieldType.TEXT], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], description: 'A medium to large field for content that will share central focus with other content in the carousel.', @@ -530,8 +530,8 @@ export class TemplateLayouts { }, { viewType: ViewType.STATIC, - tl: [-.4, -.6], - br: [.4, .6], + tl: [-0.4, -0.6], + br: [0.4, 0.6], types: [TemplateFieldType.VISUAL, TemplateFieldType.TEXT], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], description: 'A medium to large field for content that will share central focus with other content in the carousel.', @@ -542,8 +542,8 @@ export class TemplateLayouts { }, { viewType: ViewType.STATIC, - tl: [-.4, -.6], - br: [.4, .6], + tl: [-0.4, -0.6], + br: [0.4, 0.6], types: [TemplateFieldType.VISUAL, TemplateFieldType.TEXT], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], description: 'A medium to large field for content that will share central focus with other content in the carousel.', @@ -552,28 +552,27 @@ export class TemplateLayouts { //borderWidth: '8', }, }, - ] + ], }, { viewType: ViewType.STATIC, - tl: [-0.9, .35], - br: [0.9, .9], + tl: [-0.9, 0.35], + br: [0.9, 0.9], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE], description: 'A medium text field for a description of the content in the carousel.', opts: { - hCentering: "h-center", + hCentering: 'h-center', backgroundColor: 'transparent', }, }, - ] - } - + ], + }; public static ThreeField001: FieldSettings = { title: 'threefield001', viewType: ViewType.FREEFORM, - tl: [0,0], + tl: [0, 0], br: [575, 770], opts: { backgroundColor: '#DDD3A9', @@ -592,17 +591,17 @@ export class TemplateLayouts { }, subfields: [ { - viewType: ViewType.STATIC, - tl: [-1.25, -1.25], - br: [1.25, 1.25], - types: [TemplateFieldType.VISUAL], - sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], - description: 'A medium to large field for visual content that is the central focus.', - opts: { - _rotation: -45, + viewType: ViewType.STATIC, + tl: [-1.25, -1.25], + br: [1.25, 1.25], + types: [TemplateFieldType.VISUAL], + sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], + description: 'A medium to large field for visual content that is the central focus.', + opts: { + _rotation: -45, + }, }, - }, - ] + ], }, { viewType: ViewType.STATIC, @@ -639,13 +638,13 @@ export class TemplateLayouts { subfields: [ { viewType: ViewType.DEC, - tl: [-1, -.7], - br: [1, -.625], + tl: [-1, -0.7], + br: [1, -0.625], opts: { backgroundColor: 'yellow', }, }, - ] + ], }, { viewType: ViewType.FREEFORM, @@ -658,13 +657,13 @@ export class TemplateLayouts { subfields: [ { viewType: ViewType.DEC, - tl: [-1, -.7], - br: [1, -.625], + tl: [-1, -0.7], + br: [1, -0.625], opts: { backgroundColor: 'yellow', }, }, - ] + ], }, { viewType: ViewType.FREEFORM, @@ -677,13 +676,13 @@ export class TemplateLayouts { subfields: [ { viewType: ViewType.DEC, - tl: [-1, -.7], - br: [1, -.625], + tl: [-1, -0.7], + br: [1, -0.625], opts: { backgroundColor: 'yellow', }, }, - ] + ], }, { viewType: ViewType.FREEFORM, @@ -696,13 +695,13 @@ export class TemplateLayouts { subfields: [ { viewType: ViewType.DEC, - tl: [-1, -.7], - br: [1, -.625], + tl: [-1, -0.7], + br: [1, -0.625], opts: { backgroundColor: 'yellow', }, }, - ] + ], }, ], }; @@ -710,7 +709,7 @@ export class TemplateLayouts { public static ThreeField002: FieldSettings = { title: 'threefield002', viewType: ViewType.FREEFORM, - tl: [0,0], + tl: [0, 0], br: [477, 662], opts: { backgroundColor: '#9E9C95', @@ -767,5 +766,3 @@ export class TemplateLayouts { ], }; } - - diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DecorationField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DecorationField.ts new file mode 100644 index 000000000..4f5ad89b6 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DecorationField.ts @@ -0,0 +1,11 @@ +import { DynamicField } from './DynamicField'; +import { FieldSettings, TemplateField } from './TemplateField'; + +export class DecorationField extends DynamicField { + private constructor(settings: FieldSettings, parent?: TemplateField, id: number = 1) { + super(settings, parent, id); + } + static Create(settings: FieldSettings, id: number | undefined, parent?: TemplateField) { + return DynamicField.Create(settings, id, parent); + } +} diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts new file mode 100644 index 000000000..0917093b1 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts @@ -0,0 +1,164 @@ +import { reaction } from 'mobx'; +import { IDisposer } from 'mobx-utils'; +import { Doc, DocListCast } from '../../../../../../fields/Doc'; +import { DocData } from '../../../../../../fields/DocSymbols'; +import { List } from '../../../../../../fields/List'; +import { NumCast } from '../../../../../../fields/Types'; +import { Docs } from '../../../../../documents/Documents'; +import { DocumentType } from '../../../../../documents/DocumentTypes'; +import { FieldSettings, TemplateField, ViewType } from './TemplateField'; + +export class DynamicField extends TemplateField { + protected _disposers: { [name: string]: IDisposer } = {}; + protected _subfields: TemplateField[] | undefined; + + protected constructor(settings: FieldSettings, parent?: TemplateField, id: number = 1) { + super(settings, parent, id); + } + static Create(settings: FieldSettings, id: number | undefined, parent?: TemplateField) { + const field = new DynamicField(settings, parent, id); + field._subfields = settings.subfields?.map((fieldSettings, index) => TemplateField.initField(fieldSettings, index, field)) || []; + field._renderDoc = field.initializeDocument(settings, field._subfields); + field._disposers.fieldList = reaction(() => DocListCast(field._renderDoc?.[Doc.LayoutFieldKey(field._renderDoc)]), field.handleFieldUpdate); + return field; + } + + get getSubfields(): TemplateField[] { + return this._subfields ?? []; + } + get getAllSubfields() { + return ( + this._subfields?.reduce((fields, field) => { + fields.push(field, ...((field as DynamicField).getAllSubfields ?? [])); + return fields; + }, [] as TemplateField[]) ?? [] + ); + } + + setSubFields = (fields: TemplateField[]) => (this._subfields = fields); + + handleFieldUpdate = (newDocsList: Doc[]) => { + const currRenderedDocs = new Set(this._subfields?.filter(field => field.renderedDoc).map(field => field.renderedDoc!)); + newDocsList.forEach(doc => !currRenderedDocs.has(doc) && this.addFieldFromDoc(doc)); + currRenderedDocs.forEach(doc => { + if (!newDocsList.includes(doc)) { + this._subfields?.forEach(field => field.renderedDoc === doc && this.removeField(field)); + } + }); + }; + + addFieldFromDoc = (doc: Doc) => { + const par = this._renderDoc; + const settings: FieldSettings = { + tl: [Number(doc._x) / NumCast(par?._width, 1), Number(doc?._y) / NumCast(par?._height, 1)], + br: [(Number(doc._x) + Number(doc._width)) / NumCast(par?._width, 1), (Number(doc._y) + Number(doc._height)) / NumCast(par?._height, 1)], + viewType: doc.type === DocumentType.COL ? ViewType.FREEFORM : ViewType.STATIC, + opts: {}, + }; + + this._subfields?.push(TemplateField.initField(settings, this._subfields?.length ?? 0, this)); + }; + + addField = (field: TemplateField) => { + if (!this._subfields?.includes(field)) { + this._subfields?.push(field); + // Doc.SetContainer(field.Document, this.Document); + } + }; + + dispose = () => Object.values(this._disposers).forEach(disposer => disposer?.()); + + removeField = (field: TemplateField) => { + // var childDocs: Doc[] = DocListCast(this.Document[Doc.LayoutFieldKey(this.Document)]); + // this.Document[Doc.LayoutFieldKey(this.Document)] = new List<Doc>([...childDocs.splice(childDocs.indexOf(field.Document), 1)]); + this._subfields?.splice(this._subfields?.indexOf(field), 1); + (field as DynamicField).dispose?.(); + }; + + // implement Field's abstract method for replacing a subfield with a new one + exchangeFields(newField: TemplateField, oldField: TemplateField) { + this._subfields?.splice(this._subfields?.indexOf(oldField), 1, newField); + } + + get isContentField(): boolean { + return false; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + setContent(content: string, type: ViewType) {} + + getContent = () => ''; + + addChildToDocument = (doc: Doc) => this._renderDoc && Doc.SetContainer(doc, this._renderDoc); + + matches = (): Array<number> => []; + + async makeClone(parent?: DynamicField): Promise<TemplateField> { + const clone = (await super.makeClone(parent)) as unknown as DynamicField; + if (this._subfields) { + clone._subfields = await Promise.all(this._subfields.map(async cloneField => await cloneField.makeClone(clone))); + } + clone._renderDoc && (clone._renderDoc[DocData].data = new List<Doc>(clone._subfields?.filter(sub => sub.renderedDoc).map(sub => sub.renderedDoc!) ?? [])); + return clone; + } + + initializeDocument = (settings: FieldSettings, subFields: TemplateField[]) => { + const renderedSubfields = subFields?.filter(field => field.renderedDoc).map(field => field.renderedDoc!) ?? []; + settings.opts.title = settings.title; + switch (settings.viewType) { + case ViewType.CAROUSEL3D: return Docs.Create.Carousel3DDocument(renderedSubfields, settings.opts); + case ViewType.FREEFORM: + default: return Docs.Create.FreeformDocument(renderedSubfields, settings.opts); + } // prettier-ignore + }; +} + +// export class DynamicField extends Field { +// protected subfields: Field[]; + +// protected Document!: Doc; + +// constructor(settings: FieldSettings, id: number, parent?: Field) { +// super(settings, id, parent); +// this.subfields = this.setupSubfields(this); +// this.initializeDocument(); +// } + +// setContent = (content: string, type: ViewType) => { return }; +// getContent = () => { return '' }; +// get isContentField(): boolean { return false }; + +// addChildToDocument = (doc: Doc) => { +// Doc.SetContainer(doc, this.Document); +// } + +// matches = (cols: Col[]): Array<number> => { +// return []; +// } + +// initializeDocument = (): Doc => { +// let doc: Doc; +// const renderedSubfields: Doc[] = this.subfields.map(field => field.renderedDoc); +// switch (this.settings.viewType) { +// case ViewType.CAROUSEL3D: +// doc = Docs.Create.Carousel3DDocument(renderedSubfields, { +// title: this.title, +// }); +// break; +// case ViewType.FREEFORM: +// doc = Docs.Create.FreeformDocument(renderedSubfields, { +// title: this.title, +// }); +// break; +// default: +// doc = Docs.Create.FreeformDocument(renderedSubfields, { +// title: this.title, +// }); +// break; +// } + +// this.Document = doc; +// return doc; +// } + +// } diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts new file mode 100644 index 000000000..b8839efab --- /dev/null +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts @@ -0,0 +1,72 @@ +import { DocData } from '../../../../../../fields/DocSymbols'; +import { RichTextField } from '../../../../../../fields/RichTextField'; +import { ImageField } from '../../../../../../fields/URLField'; +import { Docs } from '../../../../../documents/Documents'; +import { FieldSettings, TemplateField, ViewType } from './TemplateField'; +import { TemplateFieldUtils } from './TemplateFieldUtils'; + +export abstract class StaticContentField extends TemplateField { + protected _content: string = ''; + + getContent = () => this._content ?? 'unset'; + get isContentField(): boolean { + return true; + } +} + +export class ImageTemplateField extends StaticContentField { + private constructor(settings: FieldSettings, parent?: TemplateField, id: number = 1) { + super(settings, parent, id); + } + static Create(settings: FieldSettings, id: number | undefined, parent: TemplateField | undefined) { + const field = new ImageTemplateField(settings, parent, id); + field._renderDoc = field.initializeDocument(settings); + return field; + } + + setContent(url: string, type?: ViewType) { + super.setContent(url, type); + + if (type === ViewType.IMG || type === undefined) { + this._renderDoc && (this._renderDoc[DocData].data = new ImageField(url)); + this._content = url; + } else { + this.changeFieldType(type).setContent(url, type); + } + } + + initializeDocument = (settings: FieldSettings) => { + settings.opts.title = settings.title; + settings.opts._layout_fitWidth = false; + + return Docs.Create.ImageDocument('', settings.opts); + }; +} + +export class TextTemplateField extends StaticContentField { + private constructor(settings: FieldSettings, parent?: TemplateField, id: number = 1) { + super(settings, parent, id); + } + static Create(settings: FieldSettings, id: number, parent?: TemplateField) { + const field = new TextTemplateField(settings, parent, id); + field._renderDoc = field.initializeDocument(settings); + field._renderDoc.text_fontSize = `${TemplateFieldUtils.calculateFontSize(field._dimensions?.width ?? 10, field._dimensions?.height ?? 10, '', true)}`; + return field; + } + setContent(text: string, type?: ViewType) { + super.setContent(text, type); + + if (type === ViewType.TEXT || type === undefined) { + this._content = text; + this._renderDoc && (this._renderDoc[DocData].text = RichTextField.textToRtf(text)); + } else { + this.changeFieldType(type).setContent(text, type); + } + } + + initializeDocument = (settings: FieldSettings | undefined) => { + const opts = settings?.opts; + opts && (opts.title = settings?.title ?? ''); + return Docs.Create.TextDocument('', opts); + }; +} diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts new file mode 100644 index 000000000..318bffde5 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts @@ -0,0 +1,159 @@ +/* eslint-disable no-use-before-define */ +import { computed } from 'mobx'; +import { Doc } from '../../../../../../fields/Doc'; +import { DocData } from '../../../../../../fields/DocSymbols'; +import { Copy } from '../../../../../../fields/FieldSymbols'; +import { ObjectField } from '../../../../../../fields/ObjectField'; +import { DocumentOptions } from '../../../../../documents/Documents'; +import { Col } from '../DocCreatorMenu'; +import { TemplateFieldSize, TemplateFieldType } from '../TemplateBackend'; + +export abstract class TemplateField { + /** + * Creates and initializes a new TemplateField based on the settings and parameters + * + * implemented in FieldUtils and assigned in main (to avoid import cycles) + * + * @param settings - specification of the field type and other parameters + * @param index - + * @param parent - TemplateField that contains the new field + * @param sameId - + * @returns TemplateField + */ + static initField: (settings: FieldSettings | undefined, index: number | undefined, parent: TemplateField | undefined, sameId?: boolean) => TemplateField; + + protected _parent?: TemplateField; + protected _id: number; + protected _title: string = ''; + protected _settings: FieldSettings; + protected _renderDoc: Doc | undefined; + protected _dimensions: FieldDimensions | undefined; + + constructor(settings: FieldSettings, parent?: TemplateField, id: number = 1) { + this._id = id; + this._parent = parent; + this._settings = settings; + this._title = settings.title ?? ''; + this._dimensions = this.getLocalDimensions(this._settings, this._parent?.getDimensions); + this.applyBasicOpts(this._dimensions, settings); + return this; + } + + get renderedDoc() { + return this._renderDoc; + } + get getDimensions() { + return this._dimensions; + } + get getID() { + return this._id; + } + get getDescription(): string { + return this._settings?.description ?? ''; + } + get viewType(): ViewType | undefined { + return this._settings?.viewType; + } + + setTitle = (title: string) => { + this._title = title; + this._renderDoc && (this._renderDoc.title = title); + }; + getTitle = () => this._title; + + abstract get isContentField(): boolean; + setContent(content: string, type?: ViewType) { + this._settings && (this._settings.viewType = type ?? this._settings.viewType); + } + + abstract getContent(): string; + + async makeClone(parent?: TemplateField): Promise<TemplateField> { + const clone = TemplateField.initField(this._settings, this._id, parent, true); // create a value for this.Document/subfields that we want to ignore + clone._renderDoc = this._renderDoc ? (await Doc.MakeClone(this._renderDoc)).clone : undefined; + clone._title = this._title; + clone._dimensions = this._dimensions; + return clone; + } + + @computed get documentOptions(): DocumentOptions { + const opts: DocumentOptions = {}; + Object.assign(opts, this._renderDoc?.[DocData]); + Object.entries(opts).forEach(([key, field]) => { + Object.assign(opts, { [key]: field instanceof Object ? ObjectField.MakeCopy(field) : field[Copy]() }); + }); + return opts; + } + exchangeFields(newField: TemplateField, oldField: TemplateField) { + throw new Error('Only DynamicField can exchange fields.' + newField._title + ' ' + oldField._title); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + changeFieldType = (newType: ViewType): TemplateField => { + const newField = TemplateField.initField(this._settings, this._id, this._parent, true); + this._parent?.exchangeFields(newField, this); + return newField; + }; + + matches = (cols: Col[]): number[] => { + const colMatchesField = (col: Col) => (this._settings?.sizes?.some(size => col.sizes?.includes(size)) && this._settings.types?.includes(col.type)) ?? false; + + const matches: Array<number> = []; + cols.forEach((col, v) => colMatchesField(col) && matches.push(v)); + return matches; + }; + + private getLocalDimensions = (coords: { tl: [number, number]; br: [number, number] }, parentDimensions?: FieldDimensions): FieldDimensions => { + if (!parentDimensions) { + return { width: coords.br[0] - coords.tl[0], height: coords.br[1] - coords.tl[1], coord: { x: coords.tl[0], y: coords.tl[1] } }; + } + const l = (coords.tl[0] * parentDimensions.width) / 2; + const t = coords.tl[1] * parentDimensions.height / 2; //prettier-ignore + const r = (coords.br[0] * parentDimensions.width) / 2; + const b = coords.br[1] * parentDimensions.height / 2; //prettier-ignore + return { width: r-l, height: b-t, coord: { x: l, y: t } }; //prettier-ignore + }; + + private applyBasicOpts = (dimensions: FieldDimensions, settings: FieldSettings | undefined) => { + const opts: DocumentOptions = settings?.opts ?? {}; + opts.isDefaultTemplateDoc ??= true; + opts._layout_hideScroll ??= true; + opts.x ??= dimensions.coord.x; + opts.y ??= dimensions.coord.y; + opts._height ??= dimensions.height; + opts._width ??= dimensions.width; + opts._nativeWidth ??= dimensions.width; + opts._nativeHeight ??= dimensions.height; + opts._layout_nativeDimEditable ??= true; + }; +} + +export type FieldSettings = { + tl: [number, number]; + br: [number, number]; + opts: DocumentOptions; + viewType: ViewType; + title?: string; + subfields?: FieldSettings[]; + types?: TemplateFieldType[]; + sizes?: TemplateFieldSize[]; + description?: string; +}; + +export enum ViewType { + CAROUSEL3D = 'carousel3d', + FREEFORM = 'freeform', + STATIC = 'static', + DEC = 'decoration', + IMG = 'image', + TEXT = 'text', +} + +export type FieldDimensions = { + width: number; + height: number; + coord: { x: number; y: number }; +}; + +export type FieldTree = { + node: { field: TemplateField; subfields: FieldTree[] }; +}; diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/FieldUtils.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateFieldUtils.ts index 0582c9ed3..c38d769cd 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/FieldUtils.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateFieldUtils.ts @@ -1,23 +1,30 @@ -import { Field, FieldSettings, ViewType } from './Field'; -import { TextTemplateField, ImageTemplateField } from './StaticContentField'; import { DecorationField } from './DecorationField'; import { DynamicField } from './DynamicField'; +import { ImageTemplateField, TextTemplateField } from './StaticContentField'; +import { FieldSettings, TemplateField, ViewType } from './TemplateField'; -export class FieldUtils { - public static initField = (settings: FieldSettings, index: number, parent: DynamicField | undefined, sameId: boolean = false): Field => { +export class TemplateFieldUtils { + /** + * Creates and initializes a new TemplateField based on the settings and parameters + * + * implements Field.initField ... see main.tsx + * + * @param settings - specification of the field type and other parameters + * @param index - + * @param parent - TemplateField that contains the new field + * @param sameId - + * @returns TemplateField + */ + public static initField = (settings: FieldSettings, index: number, parent: TemplateField | undefined, sameId: boolean = false): TemplateField => { const id = sameId ? index : parent ? Number(`${parent.getID}${index}`) : 1; - switch (settings.viewType) { + switch (settings?.viewType) { case ViewType.FREEFORM: - case ViewType.CAROUSEL3D: - return new DynamicField().init(settings, id, parent); - case ViewType.IMG: - return new ImageTemplateField().init(settings, id, parent); - case ViewType.TEXT: - return new TextTemplateField().init(settings, id, parent); - case ViewType.DEC: - return new DecorationField().init(settings, id, parent); - } - return new TextTemplateField().init(settings, id, parent); + case ViewType.CAROUSEL3D: return DynamicField.Create(settings, id, parent); + case ViewType.IMG: return ImageTemplateField.Create(settings, id, parent); + case ViewType.TEXT: return TextTemplateField.Create(settings, id, parent); + case ViewType.DEC: return DecorationField.Create(settings, id, parent); + default: return TextTemplateField.Create(settings, id, parent); + } // prettier-ignore }; // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateManager.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateManager.ts index 09b4ce029..1959cf9d6 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateManager.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateManager.ts @@ -1,10 +1,9 @@ -import { makeAutoObservable } from "mobx"; -import { Col } from "./DocCreatorMenu"; -import { FieldSettings } from "./FieldTypes/Field"; -import { Template } from "./Template"; +import { makeAutoObservable } from 'mobx'; +import { Col } from './DocCreatorMenu'; +import { FieldSettings } from './TemplateFieldTypes/TemplateField'; +import { Template } from './Template'; export class TemplateManager { - templates: Template[] = []; constructor(templateSettings: FieldSettings[]) { @@ -16,19 +15,19 @@ export class TemplateManager { const initializedTemplates: Template[] = []; templateSettings.forEach(settings => initializedTemplates.push(new Template(settings))); return initializedTemplates; - } + }; getValidTemplates = (cols: Col[]): Template[] => { - console.log('called in manager with templates: ', this.templates) + console.log('called in manager with templates: ', this.templates); return this.templates.filter(template => template.isValidTemplate(cols)); - } + }; - addTemplate = (newTemplate: Template) =>{ + addTemplate = (newTemplate: Template) => { this.templates.push(newTemplate); - } + }; removeTemplate = (template: Template) => { this.templates.splice(this.templates.indexOf(template), 1); template.cleanup(); - } -}
\ No newline at end of file + }; +} |