diff options
Diffstat (limited to 'src')
17 files changed, 882 insertions, 759 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 317bb7feb..45d02e822 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -290,6 +290,7 @@ export class DocumentOptions { _layout_showSidebar?: BOOLt = new BoolInfo('whether an annotationsidebar should be displayed for text docuemnts'); _layout_showCaption?: string; // which field to display in the caption area. leave empty to have no caption _layout_showTags?: BOOLt = new BoolInfo('whether to show the list of document tags at the bottom of a DocView'); + _layout_hideScroll?: BOOLt = new BoolInfo('whether to hide scroll bars on the document'); _chromeHidden?: BOOLt = new BoolInfo('whether the editing chrome for a document is hidden'); hideClickBehaviors?: BOOLt = new BoolInfo('whether to hide click behaviors in context menu'); @@ -742,6 +743,7 @@ export namespace Docs { } // users placeholderDoc as proto if it exists + console.log('placeholder: ', placeholderDocIn, ' data: ', dataProps['data']) const dataDoc = Doc.assign(placeholderDoc ? Doc.GetProto(placeholderDoc) : Doc.MakeDelegate(proto, protoId), dataProps, undefined, true); if (placeholderDoc) { diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 22725a2b9..b335432c9 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -65,6 +65,8 @@ import { PresBox, PresElementBox } from './nodes/trails'; import { FaceRecognitionHandler } from './search/FaceRecognitionHandler'; import { SearchBox } from './search/SearchBox'; import { StickerPalette } from './smartdraw/StickerPalette'; +import { TemplateField } from './nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField'; +import { TemplateFieldUtils } from './nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateFieldUtils'; dotenv.config(); @@ -100,6 +102,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' }; new PingManager(); new KeyManager(); new FaceRecognitionHandler(); + 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 64416c26d..97faf01c2 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx @@ -1,5 +1,6 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Colors } from '@dash/components'; +import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { IDisposer } from 'mobx-utils'; @@ -11,26 +12,25 @@ import { Doc, NumListCast, StrListCast, returnEmptyDoclist } from '../../../../. import { Id } from '../../../../../fields/FieldSymbols'; import { ImageCast, StrCast } from '../../../../../fields/Types'; import { ImageField } from '../../../../../fields/URLField'; +import { Upload } from '../../../../../server/SharedMediaTypes'; import { Networking } from '../../../../Network'; import { GPTCallType, gptAPICall, gptImageCall } from '../../../../apis/gpt/GPT'; import { Docs, DocumentOptions } from '../../../../documents/Documents'; import { DragManager } from '../../../../util/DragManager'; import { SnappingManager } from '../../../../util/SnappingManager'; +import { Transform } from '../../../../util/Transform'; import { UndoManager, undoable } from '../../../../util/UndoManager'; import { ObservableReactComponent } from '../../../ObservableReactComponent'; +import { DefaultStyleProvider } from '../../../StyleProvider'; import { CollectionFreeFormView } from '../../../collections/collectionFreeForm/CollectionFreeFormView'; import { DocumentView, DocumentViewInternal } from '../../DocumentView'; import { OpenWhere } from '../../OpenWhere'; import { DataVizBox } from '../DataVizBox'; import './DocCreatorMenu.scss'; -import { DefaultStyleProvider } from '../../../StyleProvider'; -import { Transform } from '../../../../util/Transform'; +import { TemplateField, ViewType } from './TemplateFieldTypes/TemplateField'; +import { Template } from './Template'; import { TemplateFieldSize, TemplateFieldType, TemplateLayouts } from './TemplateBackend'; import { TemplateManager } from './TemplateManager'; -import { Template } from './Template'; -import { Field, FieldContentType } from './FieldTypes/Field'; -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { Upload } from '../../../../../server/SharedMediaTypes'; export enum LayoutType { FREEFORM = 'Freeform', @@ -72,18 +72,19 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> // eslint-disable-next-line no-use-before-define static Instance: DocCreatorMenu; + private DEBUG_MODE: boolean = false; + private _disposers: { [name: string]: IDisposer } = {}; private _ref: HTMLDivElement | null = null; private templateManager: TemplateManager; - @observable _fullyRenderedDocs: Doc[] = []; - @observable _renderedDocCollectionPreview: Doc | undefined = undefined; - @observable _renderedDocCollection: Doc | undefined = undefined; - @observable _docsRendering: boolean = false; + @observable _fullyRenderedDocs: Doc[] = []; // collection of templates filled in with content + @observable _renderedDocCollection: Doc | undefined = undefined; // fullyRenderedDocs in a parent collection + @observable _docsRendering: boolean = false; // dictates loading symbol - @observable _userTemplates: { template: Template; doc: Doc }[] = []; //!!! used to keep track of all templates, should be refactored to work with actual templates and not docs + @observable _userTemplates: Template[] = []; @observable _selectedTemplate: Template | undefined = undefined; @observable _currEditingTemplate: Template | undefined = undefined; @@ -91,7 +92,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> @observable _selectedCols: { title: string; type: string; desc: string }[] | undefined = []; @observable _layout: { type: LayoutType; yMargin: number; xMargin: number; columns?: number; repeat: number } = { type: LayoutType.FREEFORM, yMargin: 10, xMargin: 10, columns: 3, repeat: 0 }; - @observable _layoutPreviewScale: number = 1; @observable _savedLayouts: DataVizTemplateLayout[] = []; @observable _expandedPreview: Doc | undefined = undefined; @@ -100,6 +100,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> @observable _GPTOpt: boolean = false; @observable _callCount: number = 0; @observable _GPTLoading: boolean = false; + @observable _DOCCC: Doc | undefined; @observable _pageX: number = 0; @observable _pageY: number = 0; @@ -135,31 +136,26 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> this._dataViz = dataViz; this._selectedTemplate = undefined; this._renderedDocCollection = undefined; - this._renderedDocCollectionPreview = undefined; this._fullyRenderedDocs = []; - this._suggestedTemplatePreviews = []; this._suggestedTemplates = []; this._userCreatedFields = []; }; @action addUserTemplate = (template: Template) => { - this._userTemplates.push({ template: template.cloneBase(), doc: template.getRenderedDoc() }); + this._userTemplates.push(template); }; @action removeUserTemplate = (template: Template) => { - this._userTemplates = this._userTemplates.filter(info => info.template !== template); - }; - @action updateTemplatePreview = (template: Template) => { - template.renderUpdates(); - const preview = { template: template, doc: template.getRenderedDoc() }; - this._suggestedTemplatePreviews = this._suggestedTemplatePreviews.map(t => { return t.template === preview.template ? preview : t }); //prettier-ignore - this._userTemplates = this._userTemplates.map(t => { return t.template === preview.template ? preview : t }); //prettier-ignore + this._userTemplates.splice(this._userTemplates.indexOf(template), 1); }; @action setSuggestedTemplates = (templates: Template[]) => { - this._suggestedTemplates = templates; - this._suggestedTemplatePreviews = templates.map(template => {return {template: template, doc: template.getRenderedDoc()}}); //prettier-ignore + this._suggestedTemplates = templates; //prettier-ignore }; @computed get docsToRender() { - return this._selectedTemplate ? NumListCast(this._dataViz?.layoutDoc.dataViz_selectedRows) : []; + if (this.DEBUG_MODE) { + return [1, 2, 3, 4]; + } else { + return this._selectedTemplate ? NumListCast(this._dataViz?.layoutDoc.dataViz_selectedRows) : []; + } } @computed get rowsCount() { @@ -374,15 +370,20 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> return undefined; } + @action updateRenderedPreviewCollection = async (template: Template) => { + this._fullyRenderedDocs = ((await this.createDocsFromTemplate(template)) ?? []).filter(doc => doc).map(doc => doc!); + console.log(this._fullyRenderedDocs); + this.updateRenderedDocCollection(); + }; + @action updateSelectedTemplate = async (template: Template) => { if (this._selectedTemplate === template) { this._selectedTemplate = undefined; return; } else { this._selectedTemplate = template; - template.renderUpdates(); - this._fullyRenderedDocs = (await this.createDocsFromTemplate(template)) ?? []; - this.updateRenderedDocCollection(); + //template.renderUpdates(); + this.updateRenderedPreviewCollection(template); } }; @@ -403,6 +404,33 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> Doc.UnBrushDoc(doc); }; + testTemplate = async () => { + this._suggestedTemplates = this.templateManager.templates; //prettier-ignore + + //console.log(this.templateManager.templates) + + // const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView; + + // this.templateManager.templates.forEach(template => { + // const doc = template.mainField.renderedDoc(); + // mainCollection.addDocument(doc); + // }) + + // this.forceUpdate(); + + // try { + // const res = await gptImageCall('Image of panda eating a cookie'); + + // if (res) { + // const result = await Networking.PostToServer('/uploadRemoteImage', { sources: res }); + + // console.log(result); + // } + // } catch (e) { + // console.log(e); + // } + }; + @action addField = () => { const newFields: Col[] = this._userCreatedFields.concat([{ title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [] }]); this._userCreatedFields = newFields; @@ -515,7 +543,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> compileFieldDescriptions = (templates: Template[]): string => { let descriptions: string = ''; templates.forEach(template => { - descriptions += `---------- NEW TEMPLATE TO INCLUDE: The title is: ${template.mainField.getTitle()}. Its fields are: `; + descriptions += `---------- NEW TEMPLATE TO INCLUDE: The title is: ${template.title}. Its fields are: `; descriptions += template.descriptionSummary; }); @@ -555,15 +583,15 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> const brokenDownAssignments: [Template, { [fieldID: number]: Col }][] = []; Object.entries(assignments).forEach(([tempTitle, assignment]) => { - const template = templates.filter(t => t.mainField.getTitle() === tempTitle)[0]; + const template = templates.filter(temp => temp.title === tempTitle)[0]; if (!template) return; const toObj = Object.entries(assignment).reduce( (a, [fieldID, colTitle]) => { const col = this.getColByTitle(colTitle); if (!this._userCreatedFields.includes(col)) { - // do the following for any fields not added by the user; will change in the future, for now only GPT content works with user-added fields + // do the following for any fields not added by the user; will change in the future, for now only GPT content works with user-added field const field = template.getFieldByID(Number(fieldID)); - field.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? FieldContentType.IMAGE : FieldContentType.STRING); + field.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? ViewType.IMG : ViewType.TEXT); field.setTitle(col.title); } else { a[Number(fieldID)] = this.getColByTitle(colTitle); @@ -585,16 +613,24 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> }; generatePresetTemplates = async () => { - this._dataViz?.updateColDefaults(); + const templates: Template[] = []; + + if (this.DEBUG_MODE) { + templates.push(...this.templateManager.templates); + } else { + this._dataViz?.updateColDefaults(); + + const cols = this.fieldsInfos; + templates.push(...this.templateManager.getValidTemplates(cols)); - const cols = this.fieldsInfos; - const templates = this.templateManager.getValidTemplates(cols); + const assignments: [Template, { [field: number]: Col }][] = await this.assignColsToFields(templates, cols); - const assignments: [Template, { [field: number]: Col }][] = await this.assignColsToFields(templates, cols); + const renderedTemplatePromises: Promise<Template | undefined>[] = assignments.map(([template, assgns]) => this.applyGPTContentToTemplate(template, assgns)); - const renderedTemplatePromises: Promise<Template | undefined>[] = assignments.map(([template, asns]) => this.applyGPTContentToTemplate(template, asns)); + await Promise.all(renderedTemplatePromises); + } - await Promise.all(renderedTemplatePromises); + templates.forEach(template => template.mainField.initializeDocument({ title: template.title, opts: {}, viewType: ViewType.FREEFORM, tl: [0, 0], br: [900, 900] }, [])); setTimeout(() => { this.setSuggestedTemplates(templates); @@ -602,13 +638,13 @@ 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 ?? '', FieldContentType.IMAGE); - field.setTitle(column.title); + field.setContent(url ?? '', ViewType.IMG); + field.setTitle(col.title); }; const fieldContent: string = template.compiledContent; @@ -629,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: @@ -656,14 +692,16 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> const res = await gptAPICall(`${++this._callCount}: ${prompt}`, GPTCallType.FILL); + // console.log('prompt: ', prompt, ' response: ', res); + 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 column = this.getColByTitle(title); + Object.entries(assignments).forEach(([, /* title */ info]) => { + const field: TemplateField = template.getFieldByID(Number(info.number)); + // const column = this.getColByTitle(title); - field.setContent(info.content ?? '', FieldContentType.STRING); - field.setTitle(column.title); + field.setContent(info.content ?? '', ViewType.TEXT); + field.setTitle(col.title); }); } } catch (err) { @@ -693,16 +731,14 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> }); const processContent = async (content: { [title: string]: string }) => { - const templateCopy = template.cloneBase(); + const templateCopy = await template.cloneBase(); fields .filter(title => title) .forEach(title => { const field = templateCopy.getFieldByTitle(title); - if (field === undefined) { - return; - } - field.setContent(content[title]); + if (field === undefined) return; + field.setContent(content[title], field.viewType); }); const gptPromises = this._userCreatedFields @@ -710,9 +746,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> .map(field => { const title = field.title; const templateField = templateCopy.getFieldByTitle(title); - if (templateField === undefined) { - return; - } + if (templateField === undefined) return; const templatefieldID = templateField.getID; return this.renderGPTTextCall(templateCopy, field, templatefieldID); @@ -723,9 +757,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> .map(field => { const title = field.title; const templateField = templateCopy.getFieldByTitle(title); - if (templateField === undefined) { - return; - } + if (templateField === undefined) return; const templatefieldID = templateField.getID; return this.renderGPTImageCall(templateCopy, field, templatefieldID); @@ -735,14 +767,20 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> await Promise.all(imagePromises); - return templateCopy.getRenderedDoc(); + this._DOCCC = templateCopy.mainField.renderedDoc; + return templateCopy.mainField.renderedDoc; }; - const promises = rowContents.map(content => processContent(content)); + let docs: Promise<Doc | undefined>[]; + if (this.DEBUG_MODE) { + docs = [1, 2, 3, 4].map(() => processContent({})); + } else { + docs = rowContents.map(content => processContent(content)); + } - const renderedDocs = await Promise.all(promises); + const renderedDocs = await Promise.all(docs); - this._docsRendering = false; + this._docsRendering = false; // removes loading indicator return renderedDocs; }; @@ -760,7 +798,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> @action setExpandedView = (template: Template | undefined) => { if (template) { this._currEditingTemplate = template; - this._expandedPreview = template.mainField.renderedDoc(); //Docs.Create.FreeformDocument([doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: ''}); + this._expandedPreview = template.doc; //Docs.Create.FreeformDocument([doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: ''}); } else { this._currEditingTemplate = undefined; this._expandedPreview = undefined; @@ -803,7 +841,10 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> className="docCreatorMenu-menu-button section-reveal-options top-right" onPointerDown={e => this.setUpButtonClick(e, () => { - this._currEditingTemplate && this.updateTemplatePreview(this._currEditingTemplate); + if (!this._currEditingTemplate) return; + if (this._currEditingTemplate === this._selectedTemplate) { + this.updateRenderedPreviewCollection(this._currEditingTemplate); + } this.setExpandedView(undefined); }) }> @@ -813,8 +854,8 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> className="docCreatorMenu-menu-button section-reveal-options top-right-lower" onPointerDown={e => this.setUpButtonClick(e, () => { - this._currEditingTemplate?.resetToBase(); - this.setExpandedView(this._currEditingTemplate); + this._currEditingTemplate?.printFieldInfo(); + /*this._currEditingTemplate?.resetToBase();*/ this.setExpandedView(this._currEditingTemplate); }) }> <FontAwesomeIcon icon="arrows-rotate" color="white" /> @@ -827,6 +868,34 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> get templatesPreviewContents() { const GPTOptions = <div></div>; + 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")} /> + return ( <div className={`docCreatorMenu-templates-view`}> {this._expandedPreview ? ( @@ -846,10 +915,10 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} /> </div> ) : ( - this._suggestedTemplatePreviews.map(({ doc, template }) => ( + this._suggestedTemplates.map(template => ( <div + key={template.toString()} className="docCreatorMenu-preview-window" - key="0" style={{ border: this._selectedTemplate === template ? `solid 3px ${Colors.MEDIUM_BLUE}` : '', boxShadow: this._selectedTemplate === template ? `0 0 15px rgba(68, 118, 247, .8)` : '', @@ -867,28 +936,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> <button className="option-button right" onPointerDown={e => this.setUpButtonClick(e, () => this.addUserTemplate(template))}> <FontAwesomeIcon icon="plus" color="white" /> </button> - <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} - /> + {previewDoc(template.getRenderedDoc(), template)} </div> )) )} @@ -914,10 +962,10 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> <div className="docCreatorMenu-preview-window empty"> <FontAwesomeIcon icon="plus" color="rgb(160, 160, 160)" /> </div> - {this._userTemplates.map(({ template, doc }) => ( + {this._userTemplates.map(template => ( <div + key={template.toString()} className="docCreatorMenu-preview-window" - key="0" style={{ border: this._selectedTemplate === template ? `solid 3px ${Colors.MEDIUM_BLUE}` : '', boxShadow: this._selectedTemplate === template ? `0 0 15px rgba(68, 118, 247, .8)` : '', @@ -935,28 +983,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> <button className="option-button right" onPointerDown={e => this.setUpButtonClick(e, () => this.removeUserTemplate(template))}> <FontAwesomeIcon icon="minus" color="white" /> </button> - <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} - /> + {previewDoc(template.getRenderedDoc(), template)} </div> ))} </div> @@ -1017,11 +1044,9 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> collection._height = verticalSpan; collection._width = horizontalSpan; - const layout = this._layout; - const columns: number = layout.columns ?? this.columnsCount; - const xGap: number = layout.xMargin; - const yGap: number = layout.yMargin; - // const repeat: number = templateInfo.layout.repeat; + const columns: number = this._layout.columns ?? this.columnsCount; + const xGap: number = this._layout.xMargin; + const yGap: number = this._layout.yMargin; const startX: number = -Number(collection._width) / 2; const startY: number = -Number(collection._height) / 2; const docHeight: number = Number(docs[0]._height); @@ -1070,36 +1095,35 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> updateRenderedDocCollection = () => { if (!this._fullyRenderedDocs) return; - const { horizontalSpan, verticalSpan } = this.previewInfo; - 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: Doc = collectionFactory()(this._fullyRenderedDocs, { + const collection = collectionFactory()(this._fullyRenderedDocs, { isDefaultTemplateDoc: true, - _height: verticalSpan, - _width: horizontalSpan, title: 'title', backgroundColor: 'gray', + x: 200, + y: 200, + _width: 4000, + _height: 4000, }); this.applyLayout(collection, this._fullyRenderedDocs); this._renderedDocCollection = collection; + + const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView; + mainCollection.addDocument(collection); + + console.log('changed to: ', collection); }; layoutPreviewContents = () => { @@ -1212,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/DynamicField.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/DynamicField.tsx deleted file mode 100644 index c5254c17d..000000000 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/DynamicField.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { Doc } from "../../../../../../fields/Doc"; -import { Docs } from "../../../../../documents/Documents"; -import { Field, FieldDimensions, FieldSettings, ViewType } from "./Field"; -import { FieldUtils } from "./FieldUtils"; -import { StaticField } from "./StaticField"; - -export class DynamicField implements Field { - private subfields: Field[] = []; - - private id: number; - private settings: FieldSettings; - private title: string = ''; - - private parent: Field; - private dimensions: FieldDimensions; - - constructor(settings: FieldSettings, id: number, parent?: Field) { - this.id = id; - this.settings = settings; - if (settings.title) { this.title = settings.title }; - if (!parent) { - this.parent = this; - this.dimensions = {width: this.settings.br[0] - this.settings.tl[0], height: this.settings.br[1] - this.settings.tl[1], coord: {x: this.settings.tl[0], y: this.settings.tl[1]}}; - } else { - this.parent = parent; - this.dimensions = FieldUtils.getLocalDimensions({tl: settings.tl, br: settings.br}, this.parent.getDimensions); - } - this.subfields = this.setupSubfields(); - } - - setContent = () => { return }; - getContent = () => { return '' }; - - setTitle = (title: string) => { this.title = title }; - getTitle = () => { return this.title }; - - get getSubfields() { return this.subfields }; - get getAllSubfields() { - let fields: Field[] = []; - this.subfields?.forEach(field => { - fields.push(field); - fields = fields.concat(field.getAllSubfields) - }); - return fields; - }; - - get getDimensions() { return this.dimensions }; - get getID() { return this.id }; - get getViewType() { return this.settings.viewType }; - - get getDescription(): string { - return this.settings.description ?? ''; - } - - matches = (): Array<number> => { - return []; - } - - updateRenderedDoc = () => { - return new Doc(); - } - - setupSubfields = (): Field[] => { - const fields: Field[] = []; - this.settings.subfields?.forEach((fieldSettings, index) => { - let field: Field; - const type = fieldSettings.viewType; - - const id = Number(String(this.id) + String(index)); - - if (type == ViewType.CAROUSEL3D || type === ViewType.FREEFORM) { - field = new DynamicField(fieldSettings, id, this); - } else { - field = new StaticField(fieldSettings, this, id); - } - fields.push(field); - }); - return fields; - } - - applyAttributes = (field: Field) => { - field.setTitle(this.title); - field.updateRenderedDoc(this.renderedDoc()); - } - - getChildDimensions = (coords: { tl: [number, number]; br: [number, number] }): FieldDimensions => { - const l = (coords.tl[0] * this.dimensions.height) / 2; - const t = coords.tl[1] * this.dimensions.width / 2; //prettier-ignore - const r = (coords.br[0] * this.dimensions.height) / 2; - const b = coords.br[1] * this.dimensions.width / 2; //prettier-ignore - const width = r - l; - const height = b - t; - const coord = { x: l, y: t }; - return { width, height, coord }; - }; - - renderedDoc = (): Doc => { - let doc: Doc; - switch (this.settings.viewType) { - case ViewType.CAROUSEL3D: - doc = Docs.Create.Carousel3DDocument(this.subfields.map(field => field.renderedDoc()), { - title: this.title, - }); - FieldUtils.applyBasicOpts(doc, this.dimensions, this.settings); - return doc; - case ViewType.FREEFORM: - doc = Docs.Create.FreeformDocument(this.subfields.map(field => field.renderedDoc()), { - title: this.title, - }); - FieldUtils.applyBasicOpts(doc, this.dimensions, this.settings); - return doc; - default: - return new 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 ea9b566b3..000000000 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/Field.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { Doc } from "../../../../../../fields/Doc"; -import { Col } from "../DocCreatorMenu"; -import { TemplateFieldSize, TemplateFieldType } from "../TemplateBackend"; - -export enum FieldContentType { - STRING = 'string', - IMAGE = 'image', -} - -export enum ViewType { - CAROUSEL3D = 'carousel3d', - FREEFORM = 'freeform', - STATIC = 'static', - DEC = 'decoration' -} - -export type FieldDimensions = { - width: number; - height: number; - coord: {x: number, y: number}; -} - -export interface FieldOpts { - backgroundColor?: string; - color?: string; - cornerRounding?: number; - borderWidth?: string; - borderColor?: string; - contentXCentering?: 'h-left' | 'h-center' | 'h-right'; - contentYCentering?: 'top' | 'center' | 'bottom'; - opacity?: number; - rotation?: number; - fontBold?: boolean; - fontTransform?: 'uppercase' | 'lowercase'; - fieldViewType?: 'freeform' | 'stacked'; -} - -export type FieldSettings = { - tl: [number, number]; - br: [number, number]; - opts: FieldOpts; - viewType: ViewType; - title?: string; - subfields?: FieldSettings[]; - types?: TemplateFieldType[]; - sizes?: TemplateFieldSize[]; - description?: string; -}; - -export interface Field { - getContent: () => string; - setContent: (content: string, type?: FieldContentType) => void; - getDimensions: FieldDimensions; - getSubfields: Field[]; - getAllSubfields: Field[]; - getID: number; - getViewType: ViewType; - getDescription: string; - getTitle: () => string; - setTitle: (title: string) => void; - setupSubfields: () => Field[]; - applyAttributes: (field: Field) => void; - renderedDoc: () => Doc; - matches: (cols: Col[]) => number[]; - updateRenderedDoc: (oldDoc?: Doc) => Doc; -}
\ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/FieldUtils.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/FieldUtils.tsx deleted file mode 100644 index 3886774d2..000000000 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/FieldUtils.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { Doc } from "../../../../../../fields/Doc"; -import { ComputedField, ScriptField } from "../../../../../../fields/ScriptField"; -import { Col } from "../DocCreatorMenu"; -import { TemplateFieldSize, TemplateFieldType, TemplateLayouts } from "../TemplateBackend"; -import { FieldDimensions, FieldSettings } from "./Field"; - -export class FieldUtils { - public static getLocalDimensions = (coords: { tl: [number, number]; br: [number, number] }, parentDimensions: FieldDimensions): FieldDimensions => { - 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 }; - }; - - public static applyBasicOpts = (doc: Doc, parentDimensions: FieldDimensions, settings: FieldSettings, oldDoc?: Doc) => { - const opts = settings.opts; - doc.isDefaultTemplateDoc = oldDoc ? oldDoc.isDefaultTemplateDoc : true; - doc._layout_hideScroll = oldDoc ? oldDoc._layout_hideScroll : true; - doc.x = oldDoc ? oldDoc.x : parentDimensions.coord.x; - doc.y = oldDoc ? oldDoc.y : parentDimensions.coord.y; - doc._height = oldDoc ? oldDoc.height : parentDimensions.height; - doc._width = oldDoc ? oldDoc.width : parentDimensions.width; - doc.backgroundColor = oldDoc ? oldDoc.backgroundColor : opts.backgroundColor ?? ''; - doc._layout_borderRounding = !opts.cornerRounding ? '0px' : ScriptField.MakeFunction(`${opts.cornerRounding} * this.width + 'px'`); - doc.borderColor = oldDoc ? oldDoc.borderColor : opts.borderColor; - doc.borderWidth = oldDoc ? oldDoc.borderWidth : opts.borderWidth; - doc.opacity = oldDoc ? oldDoc.opacity : opts.opacity; - doc._rotation = oldDoc ? oldDoc._rotation : opts.rotation; - doc.hCentering = oldDoc ? oldDoc.hCentering : opts.contentXCentering; - doc.nativeWidth = parentDimensions.width; - doc.nativeHeight = parentDimensions.height; - doc._layout_nativeDimEditable = true; - }; - - public static calculateFontSize = (contWidth: number, contHeight: number, text: string, uppercase: boolean): number => { - const words: string[] = text.split(/\s+/).filter(Boolean); - - let currFontSize = 1; - let rowsCount = 1; - let currTextHeight = currFontSize * rowsCount * 2; - - while (currTextHeight <= contHeight) { - let wordIndex = 0; - let currentRowWidth = 0; - let wordsInCurrRow = 0; - rowsCount = 1; - - while (wordIndex < words.length) { - const word = words[wordIndex]; - const wordWidth = word.length * currFontSize * 0.7; - - if (currentRowWidth + wordWidth <= contWidth) { - currentRowWidth += wordWidth; - ++wordsInCurrRow; - } else { - if (words.length !== 1 && words.length > wordsInCurrRow) { - rowsCount++; - currentRowWidth = wordWidth; - wordsInCurrRow = 1; - } else { - break; - } - } - - wordIndex++; - } - - currTextHeight = rowsCount * currFontSize * 2; - - currFontSize += 1; - } - - return currFontSize - 1; - }; -}
\ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/StaticField.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/StaticField.tsx deleted file mode 100644 index 47b43f051..000000000 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/FieldTypes/StaticField.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { Doc } from "../../../../../../fields/Doc"; -import { Docs } from "../../../../../documents/Documents"; -import { Col } from "../DocCreatorMenu"; -import { DynamicField } from "./DynamicField"; -import { FieldUtils } from "./FieldUtils"; -import { Field, FieldContentType, FieldDimensions, FieldSettings, ViewType } from "./Field"; - -export class StaticField { - private content: string; - private contentType: FieldContentType | undefined; - private subfields: Field[] = []; - private renderedDocument: Doc; - - private id: number; - private title: string = ''; - - private settings: FieldSettings; - - private parent: Field; - private dimensions: FieldDimensions; - - constructor(settings: FieldSettings, parent: Field, id: number) { - this.settings = settings; - if (settings.title) { this.title = settings.title }; - this.id = id; - this.parent = parent; - this.dimensions = FieldUtils.getLocalDimensions({tl: settings.tl, br: settings.br}, this.parent.getDimensions); - this.content = ''; - this.subfields = this.setupSubfields(); - this.renderedDocument = this.updateRenderedDoc(); - }; - - get getSubfields(): Field[] { return this.subfields ?? []; }; - - get getAllSubfields(): Field[] { - let fields: Field[] = []; - this.subfields?.forEach(field => { - fields.push(field); - fields = fields.concat(field.getAllSubfields); - }); - return fields; - }; - - get getDimensions() { return this.dimensions }; - get getID() { return this.id }; - get getViewType() { return this.settings.viewType }; - - get getDescription(): string { - return this.settings.description ?? ''; - } - - renderedDoc = () => { - return this.renderedDocument; - } - - setContent = (newContent: string, type?: FieldContentType) => { - this.content = newContent; - if (type) this.contentType = type; - this.updateRenderedDoc(this.renderedDocument); - }; - getContent() { return this.content }; - - setTitle = (title: string) => { - this.title = title; - this.renderedDocument.title = title; - this.updateRenderedDoc(this.renderedDocument); - }; - getTitle = () => { return this.title }; - - applyAttributes = (field: Field) => { //!!! can be updated later for more robust clonign; this is all ythat's needed now - field.setTitle(this.title); - field.setContent('', this.contentType); - field.updateRenderedDoc(this.renderedDoc()); - } - - setupSubfields = (): Field[] => { - const fields: Field[] = []; - this.settings.subfields?.forEach((fieldSettings, index) => { - let field: Field; - const type = fieldSettings.viewType; - - const id = Number(String(this.id) + String(index)); - - if (type === ViewType.FREEFORM || type === ViewType.CAROUSEL3D) { - field = new DynamicField(fieldSettings, id, this); - } else { - field = new StaticField(fieldSettings, this, id); - }; - - fields.push(field); - }); - return fields; - }; - - 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; - }; - - updateRenderedDoc = (oldDoc?: Doc): Doc => { - const opts = this.settings.opts; - - if (!this.contentType) { this.contentType = FieldContentType.STRING }; - - let doc: Doc; - - switch (this.contentType) { - case FieldContentType.STRING: - doc = Docs.Create.TextDocument(String(this.content), { - title: this.title, - text_fontColor: oldDoc ? String(oldDoc.color) : opts.color, - contentBold: oldDoc ? Boolean(oldDoc.fontBold) : opts.fontBold, - textTransform: oldDoc ? String(oldDoc.fontTransform) : opts.fontTransform, - color: oldDoc ? String(oldDoc.color) : opts.color, - _text_fontSize: `${FieldUtils.calculateFontSize(this.dimensions.width, this.dimensions.height, String(this.content), true)}` - }); - FieldUtils.applyBasicOpts(doc, this.dimensions, this.settings, oldDoc); - break; - case FieldContentType.IMAGE: - doc = Docs.Create.ImageDocument(String(this.content), { - title: this.title, - _layout_fitWidth: false, - }); - FieldUtils.applyBasicOpts(doc, this.dimensions, this.settings, oldDoc); - break; - } - - this.renderedDocument = doc; - - return doc; - }; -}
\ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts index 0a5097d4a..ef6867e32 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts @@ -1,56 +1,69 @@ -import { Doc, FieldType } from "../../../../../fields/Doc"; -import { Col } from "./DocCreatorMenu"; -import { DynamicField } from "./FieldTypes/DynamicField"; -import { Field, FieldSettings, ViewType } from "./FieldTypes/Field"; -import { } from "./FieldTypes/FieldUtils"; -import { } from "./FieldTypes/StaticField"; +import { makeAutoObservable } from 'mobx'; +import { Col } from './DocCreatorMenu'; +import { DynamicField } from './TemplateFieldTypes/DynamicField'; +import { TemplateField, FieldSettings } from './TemplateFieldTypes/TemplateField'; +import { TemplateLayouts } from './TemplateBackend'; export class Template { - mainField: DynamicField; - settings: FieldSettings; + private settings: FieldSettings; constructor(templateInfo: FieldSettings) { - this.mainField = this.setupMainField(templateInfo); + makeAutoObservable(this); this.settings = templateInfo; + this.mainField = this.setupMainField(templateInfo); } - get childFields(): Field[] { return this.mainField.getSubfields }; - get allFields(): Field[] { return this.mainField.getAllSubfields }; - get contentFields(): Field[] { return this.allFields.filter(field => field.getViewType === ViewType.STATIC) }; - get doc(){ return this.mainField.renderedDoc(); }; - - cloneBase = () => { - const clone: Template = new Template(this.settings); - clone.allFields.forEach(field => { - const matchingField: Field = this.allFields.filter(f => f.getID === field.getID)[0]; - matchingField.applyAttributes(field); - }) - return clone; + get childFields(): TemplateField[] { + return this.mainField.getSubfields; + } + get allFields(): TemplateField[] { + return this.mainField.getAllSubfields; + } + get contentFields(): TemplateField[] { + return this.allFields.filter(field => field.isContentField); + } + get doc() { + return this.mainField.renderedDoc; + } + get title() { + return this.mainField.getTitle(); } - getRenderedDoc = () => { - const doc: Doc = this.mainField.renderedDoc(); - this.contentFields.forEach(field => { - const title: string = field.getTitle(); - const val: FieldType = field.getContent() as FieldType; - if (!title || !val) return; - doc[title] = val; + cleanup = () => { + //dispose each subfields disposers, etc. + }; + + 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; + return clone; + }; + + printFieldInfo = () => { + this.allFields.forEach(field => { + const doc = field.renderedDoc; + console.log('title: ', field.getTitle(), ' width: ', doc?.width); }); - return doc; - } + }; - getFieldByID = (id: number): Field => { + getRenderedDoc = () => { + return this.doc; + }; + + getFieldByID = (id: number): TemplateField => { return this.allFields.filter(field => field.getID === id)[0]; - } + }; getFieldByTitle = (title: string) => { return this.allFields.filter(field => field.getTitle() === title)[0]; - } + }; setupMainField = (templateInfo: FieldSettings) => { - return new DynamicField(templateInfo, 1); - } + return DynamicField.Create(templateInfo, 1); + }; get descriptionSummary(): string { let summary: string = ''; @@ -68,23 +81,10 @@ export class Template { return summary; } - renderUpdates = () => { - this.allFields.forEach(field => { - field.updateRenderedDoc(field.renderedDoc()); - }); - }; - - resetToBase = () => { - this.allFields.forEach(field => { - field.updateRenderedDoc(); - }) - } - 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; - } + }; getMatches = (cols: Col[]): number[][] => { const numFields = this.contentFields.length; @@ -96,11 +96,11 @@ export class Template { .map(() => []); this.contentFields.forEach((field, i) => { - matches[i] = (field.matches(cols)); + matches[i] = field.matches(cols); }); return matches; - } + }; maxMatches = (matches: number[][]) => { if (matches.length === 0) return 0; @@ -135,5 +135,4 @@ export class Template { return count; }; - -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateBackend.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateBackend.ts index d3282eda3..9b0fac3a6 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateBackend.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateBackend.ts @@ -1,5 +1,4 @@ -import { FieldSettings, ViewType } from "./FieldTypes/Field"; -import { } from "./FieldTypes/StaticField"; +import { FieldSettings, ViewType } from './TemplateFieldTypes/TemplateField'; export enum TemplateFieldType { TEXT = 'text', @@ -20,6 +19,14 @@ export class TemplateLayouts { return Object.values(TemplateLayouts); } + public static BasicSettings: FieldSettings = { + title: 'template_framework', + tl: [0, 0], + br: [400, 700], + viewType: ViewType.FREEFORM, + opts: {}, + }; + public static FourField001: FieldSettings = { title: 'fourfield001', tl: [0, 0], @@ -27,7 +34,7 @@ export class TemplateLayouts { viewType: ViewType.FREEFORM, opts: { backgroundColor: '#C0B887', - cornerRounding: .05, + _layout_borderRounding: '.05', //borderColor: '#6B461F', //borderWidth: '12', }, @@ -41,9 +48,9 @@ export class TemplateLayouts { description: 'A title field for very short text that contextualizes the content.', opts: { backgroundColor: 'transparent', - color: '#F1F0E9', - contentXCentering: 'h-center', - fontBold: true, + text_fontColor: '#F1F0E9', + hCentering: 'h-center', + contentBold: true, }, }, { @@ -54,9 +61,9 @@ export class TemplateLayouts { sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], description: 'The main focus of the template; could be an image, long text, etc.', opts: { - cornerRounding: .05, + _layout_borderRounding: '.05', borderColor: '#8F5B25', - borderWidth: '6', + borderWidth: 6, backgroundColor: '#CECAB9', }, }, @@ -69,8 +76,8 @@ export class TemplateLayouts { description: 'A caption for field #2, very short text.', opts: { backgroundColor: 'transparent', - contentXCentering: 'h-center', - color: '#F1F0E9', + hCentering: 'h-center', + text_fontColor: '#F1F0E9', }, }, { @@ -81,9 +88,9 @@ export class TemplateLayouts { sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], description: 'A medium-sized field for medium/long text.', opts: { - cornerRounding: .05, + _layout_borderRounding: '.05', borderColor: '#8F5B25', - borderWidth: '6', + borderWidth: 6, backgroundColor: '#CECAB9', }, }, @@ -93,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', @@ -107,10 +114,10 @@ export class TemplateLayouts { sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE], description: 'A medium to large-sized field suitable for an image or longer text that should be the main focus.', opts: { - borderWidth: '8', + borderWidth: 8, borderColor: '#F8E71C', backgroundColor: '#242425', - color: 'white', + text_fontColor: 'white', }, }, { @@ -122,9 +129,9 @@ export class TemplateLayouts { description: 'A tiny field for just a word or two of plain text.', opts: { backgroundColor: 'transparent', - color: 'white', - contentXCentering: 'h-center', - fontTransform: 'uppercase', + text_fontColor: 'white', + hCentering: 'h-center', + text_transform: 'uppercase', }, }, { @@ -136,9 +143,9 @@ export class TemplateLayouts { description: 'A tiny field for just a word or two of plain text.', opts: { backgroundColor: 'transparent', - color: 'white', - contentXCentering: 'h-center', - fontTransform: 'uppercase', + text_fontColor: 'white', + hCentering: 'h-center', + text_transform: 'uppercase', }, }, { @@ -149,9 +156,9 @@ export class TemplateLayouts { sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], description: 'A medium to large-sized field suitable for longer text that should contextualize field 1.', opts: { - borderWidth: '8', + borderWidth: 8, borderColor: '#F8E71C', - color: 'white', + text_fontColor: 'white', backgroundColor: '#242425', }, }, @@ -161,7 +168,7 @@ export class TemplateLayouts { br: [-0.525, 0.075], opts: { backgroundColor: '#F8E71C', - rotation: 45, + _rotation: 45, }, }, { @@ -170,7 +177,7 @@ export class TemplateLayouts { br: [-0.2175, 0.0245], opts: { backgroundColor: '#F8E71C', - rotation: 45, + _rotation: 45, }, }, { @@ -179,7 +186,7 @@ export class TemplateLayouts { br: [0.045, 0.0245], opts: { backgroundColor: '#F8E71C', - rotation: 45, + _rotation: 45, }, }, { @@ -188,7 +195,7 @@ export class TemplateLayouts { br: [0.3075, 0.0245], opts: { backgroundColor: '#F8E71C', - rotation: 45, + _rotation: 45, }, }, { @@ -197,7 +204,7 @@ export class TemplateLayouts { br: [0.8, 0.075], opts: { backgroundColor: '#F8E71C', - rotation: 45, + _rotation: 45, }, }, ], @@ -266,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', @@ -283,9 +290,9 @@ export class TemplateLayouts { description: 'A tiny field for just a word or two of plain text.', opts: { backgroundColor: '#E2B4F5', - borderWidth: '9', + borderWidth: 9, borderColor: '#9222F1', - contentXCentering: 'h-center', + hCentering: 'h-center', }, }, { @@ -297,9 +304,9 @@ export class TemplateLayouts { description: 'A tiny field for just a word or two of plain text.', opts: { backgroundColor: '#F5B4DD', - borderWidth: '9', + borderWidth: 9, borderColor: '#E260F3', - contentXCentering: 'h-center', + hCentering: 'h-center', }, }, { @@ -310,7 +317,7 @@ export class TemplateLayouts { sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], description: 'A large to huge field for visual content that is the main content of the template.', opts: { - borderWidth: '16', + borderWidth: 16, borderColor: '#A2BD77', }, }, @@ -322,7 +329,7 @@ export class TemplateLayouts { sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE], description: 'A medium to large field for text that describes the visual content above', opts: { - borderWidth: '9', + borderWidth: 9, borderColor: '#F0D601', backgroundColor: '#F3F57D', }, @@ -334,7 +341,7 @@ export class TemplateLayouts { opts: { backgroundColor: 'transparent', borderColor: '#007C0C', - borderWidth: '10', + borderWidth: 10, }, }, ], @@ -343,218 +350,229 @@ 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', - contentXCentering: "h-center", + borderWidth: 8, + 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', - contentXCentering: "h-center", + borderWidth: 8, + 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', - borderWidth: '8', + borderWidth: 8, }, }, { 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.', opts: { borderColor: '#3B4A2C', - borderWidth: '8', + borderWidth: 8, backgroundColor: '#B8DC90', }, }, { 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', - contentXCentering: "h-center", + borderWidth: 8, + 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', - borderWidth: '8', + borderWidth: 8, }, }, { viewType: ViewType.DEC, tl: [-1.1, 0], - br: [-.9, .15], + br: [-0.9, 0.15], opts: { backgroundColor: '#94B058', borderColor: '#3B4A2C', - borderWidth: '8', + borderWidth: 8, }, }, { viewType: ViewType.DEC, - tl: [-.93, -.265], - br: [-.715, -.125], + tl: [-0.93, -0.265], + br: [-0.715, -0.125], opts: { backgroundColor: '#728745', borderColor: '#3B4A2C', - borderWidth: '8', + borderWidth: 8, }, }, { viewType: ViewType.DEC, - tl: [.7, -.45], - br: [.85, -.3], + tl: [0.7, -0.45], + br: [0.85, -0.3], opts: { backgroundColor: '#7A9D31', borderColor: '#3B4A2C', - borderWidth: '8', + borderWidth: 8, }, }, { 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', - borderWidth: '8', + borderWidth: 8, }, }, { 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', + 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: '#DDD3A9', + 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: { - borderColor: 'yellow', - borderWidth: '8', - contentXCentering: "h-center", + hCentering: 'h-center', backgroundColor: 'transparent', + text_transform: 'uppercase', }, }, { viewType: ViewType.CAROUSEL3D, - tl: [-0.9, -.3], - br: [0.9, .9], + tl: [-0.9, -0.5], + br: [0.9, 0.25], opts: { - borderColor: 'yellow', - borderWidth: '8', - backgroundColor: 'transparent', + borderColor: '#847F69', + borderWidth: 8, + backgroundColor: '#C8BA94', }, subfields: [ { viewType: ViewType.STATIC, - tl: [-.3, -.6], - br: [.3, .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.', opts: { - borderColor: 'yellow', - borderWidth: '8', + //borderColor: 'yellow', + //borderWidth: '8', }, }, { viewType: ViewType.STATIC, - tl: [-.3, -.6], - br: [.3, .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.', opts: { - borderColor: 'black', - borderWidth: '8', + //borderColor: 'black', + //borderWidth: '8', }, }, { viewType: ViewType.STATIC, - tl: [-.3, -.6], - br: [.3, .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.', opts: { - borderColor: 'yellow', - borderWidth: '8', + //borderColor: 'yellow', + //borderWidth: '8', }, }, - ] + ], }, - ] - } + { + viewType: ViewType.STATIC, + 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', + backgroundColor: 'transparent', + }, + }, + ], + }; public static ThreeField001: FieldSettings = { title: 'threefield001', viewType: ViewType.FREEFORM, - tl: [0,0], + tl: [0, 0], br: [575, 770], opts: { backgroundColor: '#DDD3A9', @@ -567,23 +585,23 @@ export class TemplateLayouts { description: 'A medium to large field for visual content that is the central focus.', opts: { borderColor: 'yellow', - borderWidth: '8', + borderWidth: 8, backgroundColor: '#DDD3A9', - rotation: 45, + _rotation: 45, }, 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, @@ -594,7 +612,7 @@ export class TemplateLayouts { description: 'A very small text field for one to a few words. A good caption for the image.', opts: { backgroundColor: 'transparent', - contentXCentering: 'h-center', + hCentering: 'h-center', }, }, { @@ -606,7 +624,7 @@ export class TemplateLayouts { description: 'A medium to large text field for a thorough description of the image. ', opts: { backgroundColor: 'transparent', - color: 'white', + text_fontColor: 'white', }, }, { @@ -615,18 +633,18 @@ export class TemplateLayouts { br: [1.8, -0.66], opts: { backgroundColor: '#CEB155', - rotation: 45, + _rotation: 45, }, subfields: [ { viewType: ViewType.DEC, - tl: [-1, -.7], - br: [1, -.625], + tl: [-1, -0.7], + br: [1, -0.625], opts: { backgroundColor: 'yellow', }, }, - ] + ], }, { viewType: ViewType.FREEFORM, @@ -634,18 +652,18 @@ export class TemplateLayouts { br: [-0.2, -0.66], opts: { backgroundColor: '#CEB155', - rotation: 135, + _rotation: 135, }, subfields: [ { viewType: ViewType.DEC, - tl: [-1, -.7], - br: [1, -.625], + tl: [-1, -0.7], + br: [1, -0.625], opts: { backgroundColor: 'yellow', }, }, - ] + ], }, { viewType: ViewType.FREEFORM, @@ -653,18 +671,18 @@ export class TemplateLayouts { br: [1.66, 1.25], opts: { backgroundColor: '#CEB155', - rotation: 135, + _rotation: 135, }, subfields: [ { viewType: ViewType.DEC, - tl: [-1, -.7], - br: [1, -.625], + tl: [-1, -0.7], + br: [1, -0.625], opts: { backgroundColor: 'yellow', }, }, - ] + ], }, { viewType: ViewType.FREEFORM, @@ -672,18 +690,18 @@ export class TemplateLayouts { br: [-0.33, 1.25], opts: { backgroundColor: '#CEB155', - rotation: 45, + _rotation: 45, }, subfields: [ { viewType: ViewType.DEC, - tl: [-1, -.7], - br: [1, -.625], + tl: [-1, -0.7], + br: [1, -0.625], opts: { backgroundColor: 'yellow', }, }, - ] + ], }, ], }; @@ -691,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', @@ -705,7 +723,7 @@ export class TemplateLayouts { sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], description: 'A medium to large visual field for the main content of the template', opts: { - borderWidth: '15', + borderWidth: 15, borderColor: '#E0E0DA', }, }, @@ -718,10 +736,10 @@ export class TemplateLayouts { description: 'A very small text field for one to a few words. The content should represent a general categorization of the image.', opts: { backgroundColor: 'transparent', - color: '#AF0D0D', - fontTransform: 'uppercase', - fontBold: true, - contentXCentering: 'h-left', + text_fontColor: '#AF0D0D', + text_transform: 'uppercase', + contentBold: true, + hCentering: 'h-left', }, }, { @@ -733,8 +751,8 @@ export class TemplateLayouts { description: 'A very small text field for one to a few words. The content should contextualize field 2.', opts: { backgroundColor: 'transparent', - color: 'black', - contentXCentering: 'h-right', + text_fontColor: 'black', + hCentering: 'h-right', }, }, { @@ -748,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/TemplateFieldTypes/TemplateFieldUtils.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateFieldUtils.ts new file mode 100644 index 000000000..c38d769cd --- /dev/null +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateFieldUtils.ts @@ -0,0 +1,71 @@ +import { DecorationField } from './DecorationField'; +import { DynamicField } from './DynamicField'; +import { ImageTemplateField, TextTemplateField } from './StaticContentField'; +import { FieldSettings, TemplateField, ViewType } from './TemplateField'; + +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) { + case ViewType.FREEFORM: + 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 + public static calculateFontSize = (contWidth: number, contHeight: number, text: string, uppercase: boolean): number => { + const words: string[] = text.split(/\s+/).filter(Boolean); + + let currFontSize = 1; + let rowsCount = 1; + let currTextHeight = currFontSize * rowsCount * 2; + + while (currTextHeight <= contHeight) { + let wordIndex = 0; + let currentRowWidth = 0; + let wordsInCurrRow = 0; + rowsCount = 1; + + while (wordIndex < words.length) { + const word = words[wordIndex]; + const wordWidth = word.length * currFontSize * 0.7; + + if (currentRowWidth + wordWidth <= contWidth) { + currentRowWidth += wordWidth; + ++wordsInCurrRow; + } else { + if (words.length !== 1 && words.length > wordsInCurrRow) { + rowsCount++; + currentRowWidth = wordWidth; + wordsInCurrRow = 1; + } else { + break; + } + } + + wordIndex++; + } + + currTextHeight = rowsCount * currFontSize * 2; + + currFontSize += 1; + } + + return currFontSize - 1; + }; +} diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateManager.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateManager.ts index 50ae4d72a..1959cf9d6 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateManager.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateManager.ts @@ -1,12 +1,13 @@ -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[]) { + makeAutoObservable(this); this.templates = this.initializeTemplates(templateSettings); } @@ -14,9 +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); return this.templates.filter(template => template.isValidTemplate(cols)); - } -}
\ No newline at end of file + }; + + addTemplate = (newTemplate: Template) => { + this.templates.push(newTemplate); + }; + + removeTemplate = (template: Template) => { + this.templates.splice(this.templates.indexOf(template), 1); + template.cleanup(); + }; +} diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx index c672bc718..f92037e93 100644 --- a/src/client/views/smartdraw/DrawingFillHandler.tsx +++ b/src/client/views/smartdraw/DrawingFillHandler.tsx @@ -14,7 +14,7 @@ import { AspectRatioLimits, FireflyDimensionsMap, FireflyImageDimensions, Firefl const DashDropboxId = '2m86iveqdr9vzsa'; export class DrawingFillHandler { - static drawingToImage = async (drawing: Doc, strength: number, user_prompt: string, styleDoc?: Doc) => { + static drawingToImage = async (drawing: Doc, strength: number, user_prompt: string, styleDoc?: Doc, flexDimensions?: boolean) => { const docData = drawing[DocData]; const tags = StrListCast(docData.tags).map(tag => tag.slice(1)); const styles = tags.filter(tag => FireflyStylePresets.has(tag)); @@ -44,7 +44,7 @@ export class DrawingFillHandler { return imageUrlToBase64(structureUrl) .then(gptDescribeImage) .then((prompt, newPrompt = user_prompt || prompt) => - Networking.PostToServer('/queryFireflyImageFromStructure', { prompt: `${newPrompt}`, width: dims.width, height: dims.height, structureUrl, strength, presets: styles, styleUrl }) + Networking.PostToServer('/queryFireflyImageFromStructure', { prompt: `${newPrompt}`, width: flexDimensions ? aspectRatio * 2000 : dims.width, height: flexDimensions ? (1 - aspectRatio) * 2000 : dims.height, structureUrl, strength, presets: styles, styleUrl }) .then(res => { const genratedDocs = DocCast(drawing.ai_firefly_generatedDocs) ?? Docs.Create.MasonryDocument([], { _width: 400, _height: 400 }); drawing[DocData].ai_firefly_generatedDocs = genratedDocs; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index fc89dcbe7..bb7df75fc 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -573,7 +573,7 @@ export namespace Doc { if (!skipUndefineds || value !== undefined) { // Do we want to filter out undefineds? if (typeof value === 'object' && 'values' in value) { - console.log(value); + //console.log(value); } doc[key] = value; } |