diff options
Diffstat (limited to 'src')
12 files changed, 621 insertions, 764 deletions
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index c4373aaa7..897a86e7f 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -584,9 +584,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { miniMapColor = () => Colors.MEDIUM_GRAY; tabView = () => this._view; disableMinimap = () => !this._document; - whenChildContentActiveChanges = (isActive: boolean) => { - this._isAnyChildContentActive = isActive; - }; + whenChildContentActiveChanges = (isActive: boolean) => (this._isAnyChildContentActive = isActive); isContentActiveFunc = () => this.isContentActive; waitForDoubleClick = () => (SnappingManager.ExploreMode ? 'never' : undefined); renderDocView = (doc: Doc) => ( diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts index 78235d000..526fcf9c4 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts @@ -14,87 +14,64 @@ export type Conditional = { target: string; attribute: string; value: string; -} +}; export class TemplateManager { + _templates: Template[] = []; - templates: Template[] = []; - - conditionalFieldLogic: Record<string, Conditional[]> = {}; + _conditionalFieldLogic: Record<string, Conditional[]> = {}; constructor(templateSettings: FieldSettings[]) { makeAutoObservable(this); - this.templates = this.initializeTemplates(templateSettings); + this._templates = templateSettings.map(settings => new Template(settings)); } - initializeTemplates = (templateSettings: FieldSettings[]) => templateSettings.map(settings => { - return new Template(settings)}); - - getValidTemplates = (cols: Col[]) => this.templates.filter(template => template.isValidTemplate(cols)); + getValidTemplates = (cols: Col[]) => this._templates.filter(template => template.isValidTemplate(cols)); - addTemplate = (newTemplate: Template) => this.templates.push(newTemplate); + addTemplate = (newTemplate: Template) => this._templates.push(newTemplate); removeTemplate = (template: Template) => { - this.templates.splice(this.templates.indexOf(template), 1); + if (this._templates.includes(template)) { + this._templates.splice(this._templates.indexOf(template), 1); + } template.cleanup(); }; addFieldCondition = (fieldTitle: string, condition: Conditional) => { - if (this.conditionalFieldLogic[fieldTitle] === undefined) { - this.conditionalFieldLogic[fieldTitle] = [condition]; + if (this._conditionalFieldLogic[fieldTitle] === undefined) { + this._conditionalFieldLogic[fieldTitle] = [condition]; } else { - this.conditionalFieldLogic[fieldTitle].push(condition); + this._conditionalFieldLogic[fieldTitle].push(condition); } - } + }; - removeFieldCondition = (fieldTitle: string, condition: Conditional) => { - if (this.conditionalFieldLogic[fieldTitle]) { - this.conditionalFieldLogic[fieldTitle] = this.conditionalFieldLogic[fieldTitle].filter(cond => cond !== condition); - } - } + removeFieldCondition = (fieldTitle: string, condition: Conditional) => (this._conditionalFieldLogic[fieldTitle] = this._conditionalFieldLogic[fieldTitle]?.filter(cond => cond !== condition)); - addDataField = (title: string) => { - this.templates.forEach(template => template.addDataField(title)); - } + addDataField = (title: string) => this._templates.forEach(template => template.addDataField(title)); - removeDataField = (title: string) => { - this.templates.forEach(template => template.removeDataField(title)); - } + removeDataField = (title: string) => this._templates.forEach(template => template.removeDataField(title)); createDocsFromTemplate = action((dv: DataVizBox, template: Template, cols: Col[], debug: boolean = false) => { const csvFields = Array.from(Object.keys(dv.records[0])); - const processContent = async (content: { [title: string]: string }) => { + const processContent = (content: { [title: string]: string }) => { const templateCopy = template.clone(); csvFields .filter(title => title) .forEach(title => { const field = templateCopy.getFieldByTitle(title); - field && field.setContent(content[title], field.viewType); + field?.setContent(content[title], field.viewType); }); const gptFunc = (type: TemplateFieldType) => (type === TemplateFieldType.VISUAL ? TemplateMenuAIUtils.renderGPTImageCall : TemplateMenuAIUtils.renderGPTTextCall); - const applyGPTContent = async () => { - const promises = cols - .filter(field => field.AIGenerated) - .map(field => { - const templateField: TemplateField = templateCopy.getFieldByTitle(field.title) as TemplateField; - if (templateField !== undefined) { - return gptFunc(field.type)(templateCopy, field, templateField.getID); - } - return null; - }) - .filter(p => p !== null); - - await Promise.all(promises); - }; - - await applyGPTContent(); - - templateCopy.applyConditionalLogic(this.conditionalFieldLogic); - - return templateCopy.getRenderedDoc(); + + const generateGptContent = cols + .map(field => ({ field, templateField: field?.AIGenerated && templateCopy.getFieldByTitle(field.title) })) + .filter(({ templateField }) => templateField instanceof TemplateField) + .map(({ field, templateField }) => gptFunc(field.type)(templateCopy, field, (templateField as TemplateField).getID)); + + return Promise.all(generateGptContent).then(() => templateCopy.applyConditionalLogic(this._conditionalFieldLogic)); }; const rowContents = debug @@ -109,10 +86,6 @@ export class TemplateManager { ) ); - return Promise.all(rowContents.map(processContent)).then( - action(renderedDocs => { - return renderedDocs; - }) - ); + return Promise.all(rowContents.map(processContent)); }); } diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts index 162b7a1b1..08818dd6c 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts @@ -1,13 +1,13 @@ -import { ClientUtils } from "../../../../../../ClientUtils"; -import { Networking } from "../../../../../Network"; -import { gptImageCall, gptAPICall, GPTCallType } from "../../../../../apis/gpt/GPT"; -import { Col } from "../DocCreatorMenu"; -import { TemplateFieldSize, TemplateFieldType } from "../TemplateBackend"; -import { TemplateField, ViewType } from "../TemplateFieldTypes/TemplateField"; -import { Template } from "../Template"; +import { ClientUtils } from '../../../../../../ClientUtils'; +import { Networking } from '../../../../../Network'; +import { gptImageCall, gptAPICall, GPTCallType } from '../../../../../apis/gpt/GPT'; +import { Col } from '../DocCreatorMenu'; +import { TemplateFieldSize, TemplateFieldType } from '../TemplateBackend'; +import { ViewType } from '../TemplateFieldTypes/TemplateField'; +import { Template } from '../Template'; +import { Upload } from '../../../../../../server/SharedMediaTypes'; export class TemplateMenuAIUtils { - public static generateGPTImage = async (prompt: string): Promise<string | undefined> => { try { const res = await gptImageCall(prompt); @@ -24,13 +24,10 @@ export class TemplateMenuAIUtils { public static renderGPTImageCall = async (template: Template, col: Col, fieldNumber: number): Promise<boolean> => { const generateAndLoadImage = async (id: number, prompt: string) => { + const field = template.getFieldByID(id); const url = await this.generateGPTImage(prompt); - // eslint-disable-next-line - var field: TemplateField = template.getFieldByID(id); - - field.setContent(url ?? '', ViewType.IMG); - field = template.getFieldByID(id); - field.setTitle(col.title); + field?.setContent(url ?? '', ViewType.IMG); + field?.setTitle(col.title); }; const fieldContent: string = template.compiledContent; @@ -81,10 +78,10 @@ export class TemplateMenuAIUtils { if (res) { const assignments: { [title: string]: { number: string; content: string } } = JSON.parse(res); Object.entries(assignments).forEach(([, /* title */ info]) => { - const field: TemplateField = template.getFieldByID(Number(info.number)); + const field = template.getFieldByID(Number(info.number)); - field.setContent(info.content ?? '', ViewType.TEXT); - field.setTitle(col.title); + field?.setContent(info.content ?? '', ViewType.TEXT); + field?.setTitle(col.title); }); } } catch (err) { @@ -122,5 +119,4 @@ export class TemplateMenuAIUtils { return template; }; - -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss index 463e69c67..6eb7fa96a 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss @@ -9,10 +9,10 @@ position: absolute; z-index: 1000; // box-shadow: 0px 3px 4px rgba(0, 0, 0, 30%); - // background: whitesmoke; + // background: whitesmoke; // color: black; border-radius: 3px; -} +} .docCreatorMenu-menu { display: flex; @@ -24,7 +24,7 @@ .docCreatorMenu-menu-button { width: 25px; height: 25px; - background: whitesmoke; + background: whitesmoke; background-color: rgb(50, 50, 50); border-radius: 5px; padding: 0px; @@ -53,8 +53,8 @@ position: absolute; right: 0px; } - - &.right{ + + &.right { margin-left: 0px; font-size: 12px; } @@ -103,12 +103,12 @@ } } - &:hover::before{ + &:hover::before { border-bottom: 20px solid rgb(82, 82, 82); } &::before { - content: ""; + content: ''; position: absolute; top: 0; left: 0; @@ -120,7 +120,7 @@ } &::after { - content: ""; + content: ''; position: absolute; top: -1px; left: -1px; @@ -143,7 +143,7 @@ color: white; } -.docCreatorMenu-menu-hr{ +.docCreatorMenu-menu-hr { margin-top: 0px; margin-bottom: 0px; color: rgb(180, 180, 180); @@ -173,14 +173,14 @@ align-items: center; width: 40px; height: 40px; - background-color: rgb(99, 148, 238); + background-color: rgb(99, 148, 238); border: 2px solid rgb(80, 107, 152); border-radius: 5px; margin-bottom: 20px; font-size: 25px; - &:hover{ - background-color: rgb(59, 128, 255); + &:hover { + background-color: rgb(59, 128, 255); border: 2px solid rgb(53, 80, 127); } } @@ -188,7 +188,7 @@ .docCreatorMenu-create-docs-button { width: 40px; height: 40px; - background-color: rgb(176, 229, 149); + background-color: rgb(176, 229, 149); border: 2px solid rgb(126, 219, 80); border-radius: 5px; padding: 0px; @@ -199,7 +199,7 @@ &:hover { background-color: rgb(129, 223, 83); - border: 2px solid rgb(80, 185, 28); + border: 2px solid rgb(80, 185, 28); } } @@ -230,17 +230,22 @@ position: absolute; background-color: none; - &.top, &.bottom { + &.top, + &.bottom { height: 10px; cursor: ns-resize; } - &.right, &.left { + &.right, + &.left { width: 10px; cursor: ew-resize; } - &.topRight, &.topLeft, &.bottomRight, &.bottomLeft { + &.topRight, + &.topLeft, + &.bottomRight, + &.bottomLeft { height: 15px; width: 15px; background-color: none; @@ -263,7 +268,7 @@ height: calc(100% - 30px); border: 1px solid rgb(180, 180, 180); border-radius: 5px; - -ms-overflow-style: none; + -ms-overflow-style: none; scrollbar-width: none; } @@ -277,7 +282,7 @@ height: 100%; flex-grow: 1; - .top-panel{ + .top-panel { width: 100%; height: 10px; } @@ -302,13 +307,14 @@ justify-content: center; align-items: center; height: 100%; + width: 100%; aspect-ratio: 1; color: none; border: 1px solid rgb(163, 163, 163); border-radius: 5px; box-shadow: 5px 5px rgb(29, 29, 31); - &:hover{ + &:hover { background-color: rgb(72, 72, 73); } @@ -353,20 +359,18 @@ &:hover .option-button { display: block; } - } -.docCreatorMenu-preview-image{ +.docCreatorMenu-preview-image { background-color: transparent; height: 100%; display: block; object-fit: contain; border-radius: 5px; - } .docCreatorMenu-variations-tab { - flex-grow: .5; + flex-grow: 0.5; } .docCreatorMenu-section { @@ -400,7 +404,7 @@ display: grid; justify-content: space-evenly; row-gap: 2rem; - grid-template-columns: repeat(auto-fill, minmax(150px, 30%)); + grid-template-columns: repeat(auto-fill, minmax(150px, 50%)); margin: 5px; width: calc(100% - 10px); height: 100%; @@ -413,7 +417,7 @@ } } -.divvv{ +.div { width: 200; height: 200; border: solid 1px white; @@ -448,7 +452,7 @@ .docCreatorMenu-GPT-generate { height: 30px; width: 30px; - background-color: rgb(176, 229, 149); + background-color: rgb(176, 229, 149); border: 1px solid rgb(126, 219, 80); border-radius: 5px; padding: 0px; @@ -459,7 +463,7 @@ &:hover { background-color: rgb(129, 223, 83); - border: 2px solid rgb(80, 185, 28); + border: 2px solid rgb(80, 185, 28); } } @@ -477,7 +481,7 @@ // DocCreatorMenu options CSS //-------------------------------------------------------------------------------------------------------------------------------------------- -.docCreatorMenu-option-container{ +.docCreatorMenu-option-container { display: flex; width: 180px; height: 30px; @@ -487,16 +491,16 @@ margin-top: 10px; margin-bottom: 10px; - &.layout{ + &.layout { z-index: 5; } } -.docCreatorMenu-option-title{ +.docCreatorMenu-option-title { display: flex; width: 140px; height: 30px; - background: whitesmoke; + background: whitesmoke; background-color: rgb(34, 34, 37); border-radius: 5px; border: 1px solid rgb(180, 180, 180); @@ -513,7 +517,7 @@ border-radius: 0px; width: auto; text-transform: none; - + &.small { height: 20px; transform: translateY(-5px); @@ -607,38 +611,38 @@ height: calc(100% - 30px); border: 1px solid rgb(180, 180, 180); border-radius: 5px; - -ms-overflow-style: none; + -ms-overflow-style: none; scrollbar-width: none; - .docCreatorMenu-option-container{ + .docCreatorMenu-option-container { width: 180px; height: 30px; .docCreatorMenu-dropdown-hoverable { width: 140px; height: 30px; - + &:hover .docCreatorMenu-dropdown-content { display: block; } - + &:hover .docCreatorMenu-option-title { border-bottom-left-radius: 0px; border-bottom-right-radius: 0px; } - + .docCreatorMenu-dropdown-content { display: none; min-width: 100px; height: 75px; overflow-y: scroll; - -ms-overflow-style: none; + -ms-overflow-style: none; scrollbar-width: none; border-bottom: 1px solid rgb(180, 180, 180); border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; - - .docCreatorMenu-dropdown-option{ + + .docCreatorMenu-dropdown-option { display: flex; background-color: rgb(42, 42, 46); border-left: 1px solid rgb(180, 180, 180); @@ -649,13 +653,13 @@ justify-content: center; justify-items: center; padding-top: 3px; - + &:hover { background-color: rgb(68, 68, 74); cursor: pointer; } } - } + } } } } @@ -710,7 +714,7 @@ border: 1px solid rgb(180, 180, 180); border-radius: 5px; background-color: rgb(34, 34, 37); - -ms-overflow-style: none; + -ms-overflow-style: none; scrollbar-width: none; &.small { @@ -742,10 +746,10 @@ z-index: 999; } - .docCreatorMenu-zoom-button{ + .docCreatorMenu-zoom-button { width: 15px; height: 15px; - background: whitesmoke; + background: whitesmoke; background-color: rgb(34, 34, 37); border-radius: 3px; border: 1px solid rgb(180, 180, 180); @@ -759,8 +763,6 @@ } } - - //------------------------------------------------------------------------------------------------------------------------------------------ // DocCreatorMenu dashboard CSS //-------------------------------------------------------------------------------------------------------------------------------------------- @@ -778,7 +780,7 @@ height: calc(100% - 30px); border: 1px solid rgb(180, 180, 180); border-radius: 5px; - -ms-overflow-style: none; + -ms-overflow-style: none; scrollbar-width: none; .panels-container { @@ -836,8 +838,8 @@ background-color: rgb(72, 72, 72); cursor: pointer; } - } - + } + .opts-bar { display: flex; flex-direction: row; @@ -880,11 +882,11 @@ flex-direction: row; align-items: flex-start; } - + &:hover .type-display { display: none; } - + .bubble { margin: 3px; } @@ -919,7 +921,7 @@ flex-direction: row; align-items: center; } - + .bubble { margin: 3px; margin-right: 4px; @@ -942,10 +944,8 @@ border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; resize: none; - } } - } .conditionals-section { @@ -972,7 +972,7 @@ flex-direction: row; justify-content: flex-start; align-items: flex-start; - color: whitesmoke; + color: whitesmoke; width: 100%; height: fit-content; margin-bottom: 15px; @@ -1041,7 +1041,6 @@ overflow-y: scroll; white-space: nowrap; } - } .form { @@ -1057,10 +1056,8 @@ align-items: center; margin: 3px; cursor: pointer; - } } - } //------------------------------------------------------------------------------------------------------------------------------------------ @@ -1113,38 +1110,38 @@ display: flex; align-items: center; justify-content: center; - gap: 2rem; + gap: 2rem; padding: 0.5rem 1rem; background: rgb(50, 50, 50); color: whitesmoke; font-family: system-ui, sans-serif; font-size: 0.9rem; - flex-wrap: wrap; - } - - .menu‑item { + flex-wrap: wrap; +} + +.menu‑item { display: flex; align-items: center; gap: 0.5rem; white-space: nowrap; - } - - .menu‑item input[type="range"] { +} + +.menu‑item input[type='range'] { width: 7rem; - accent-color: whitesmoke; - } - - .value { + accent-color: whitesmoke; +} + +.value { min-width: 2ch; text-align: right; - } - - .switch { +} + +.switch { gap: 0.75rem; margin-bottom: 0px; - } - - .switch .slider { +} + +.switch .slider { position: relative; width: 2.2rem; height: 1.1rem; @@ -1152,9 +1149,9 @@ border-radius: 1rem; cursor: pointer; transition: background 0.2s; - } - - .switch .slider::before { +} + +.switch .slider::before { content: ''; position: absolute; top: 0.1rem; @@ -1164,27 +1161,25 @@ background: rgb(50, 50, 50); border-radius: 50%; transition: transform 0.2s; - } - - .switch input { +} + +.switch input { display: none; - } - - .switch input:checked + .slider { +} + +.switch input:checked + .slider { background: #78c2f1; - } - - .switch input:checked + .slider::before { +} + +.switch input:checked + .slider::before { transform: translateX(1.1rem); - } +} .firefly-option-label { - padding: .2em .6em .3em; + padding: 0.2em 0.6em 0.3em; font-size: 100%; color: whitesmoke; text-align: center; margin-bottom: 0px; font-weight: 500; } - - diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx index ebfa3fc65..fb083ea75 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx @@ -1,4 +1,3 @@ - import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; @@ -22,7 +21,7 @@ import { ViewType } from './TemplateFieldTypes/TemplateField'; import { Template } from './Template'; import { TemplateFieldSize, TemplateFieldType, TemplateLayouts } from './TemplateBackend'; import { TemplateManager } from './Backend/TemplateManager'; -import { TemplateMenuAIUtils } from './Backend/TemplateMenuAIUtils' +import { TemplateMenuAIUtils } from './Backend/TemplateMenuAIUtils'; import { TemplatePreviewGrid } from './Menu/TemplatePreviewGrid'; import { FireflyStructureOptions, TemplateEditingWindow } from './Menu/TemplateEditingWindow'; import { DocCreatorMenuButton } from './Menu/DocCreatorMenuButton'; @@ -158,18 +157,12 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> return bounds; } - setUpButtonClick = (e: React.PointerEvent, func: (...args: any) => void) => { - setupMoveUpEvents( - this, - e, - returnFalse, - emptyFunction, - undoable(clickEv => { - clickEv.stopPropagation(); - clickEv.preventDefault(); - func(); - }, 'create docs') - ); + setUpButtonClick = (e: React.PointerEvent, func: () => void) => { + setupMoveUpEvents(this, e, returnFalse, emptyFunction, clickEv => { + clickEv.stopPropagation(); + clickEv.preventDefault(); + undoable(func, 'create docs')(); + }); }; @action @@ -301,16 +294,13 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> this._pageX = x + translation.x; this._pageY = y + translation.y; }; - - async createDocsForPreview(): Promise<Doc[]> { return this._dataViz && this._selectedTemplate ? ((await this.templateManager.createDocsFromTemplate(this._dataViz, this._selectedTemplate, this.fieldsInfos, this.DEBUG_MODE)).filter(doc => doc).map(doc => doc!) ?? []) as unknown as Doc[] : []; } - @action updateSelectedTemplate = async (template: Template) => { - if (this._selectedTemplate === template) { - this._selectedTemplate = undefined; - return; - } else { - this._selectedTemplate = template; - } + async createDocsForPreview() { + return this._dataViz && this._selectedTemplate ? ((await this.templateManager.createDocsFromTemplate(this._dataViz, this._selectedTemplate, this.fieldsInfos, this.DEBUG_MODE)).filter(doc => doc).map(doc => doc!) ?? []) : []; + } + + @action updateSelectedTemplate = (template: Template) => { + this._selectedTemplate = this._selectedTemplate === template ? undefined : template; // toggle selection }; // testTemplate = async () => { @@ -318,27 +308,16 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> // }; @action addField = () => { - const newFields: Col[] = this._userCreatedFields.concat([{ title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [], AIGenerated: true }]); - this._userCreatedFields = newFields; + this._userCreatedFields = this._userCreatedFields.concat([{ title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [], AIGenerated: true }]); }; @action removeField = (field: { title: string; type: string; desc: string }) => { if (this._dataViz?.axes.includes(field.title)) { this._dataViz.selectAxes(this._dataViz.axes.filter(col => col !== field.title)); } else { - const toRemove = this._userCreatedFields.filter(f => f === field); - if (!toRemove) return; - - if (toRemove.length > 1) { - while (toRemove.length > 1) { - toRemove.pop(); - } - } - - if (this._userCreatedFields.length === 1) { - this._userCreatedFields = []; - } else { - this._userCreatedFields.splice(this._userCreatedFields.indexOf(toRemove[0]), 1); + const toRemove = this._userCreatedFields.findIndex(f => f === field); + if (toRemove !== -1) { + this._userCreatedFields.splice(toRemove, 1); } } }; @@ -390,35 +369,22 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> this.forceUpdate(); }; - compileFieldDescriptions = (templates: Template[]): string => { - let descriptions: string = ''; - templates.forEach(template => { - descriptions += `---------- NEW TEMPLATE TO INCLUDE: The title is: ${template.title}. Its fields are: `; - descriptions += template.descriptionSummary; - }); - - return descriptions; - }; - - compileColDescriptions = (cols: Col[]): string => { - let descriptions: string = ' ------------- COL DESCRIPTIONS START HERE:'; - cols.forEach(col => (descriptions += `{title: ${col.title}, sizes: ${String(col.sizes)}, type: ${col.type}, descreiption: ${col.desc}} `)); + compileFieldDescriptions = (templates: Template[]) => + templates.map(template => `---------- NEW TEMPLATE TO INCLUDE: The title is: ${template.title}. Its fields are: ` + template.descriptionSummary).join(''); // prettier-ignore - return descriptions; - }; + compileColDescriptions = (cols: Col[]) => + ' ------------- COL DESCRIPTIONS START HERE:' + cols.map(col => `{title: ${col.title}, sizes: ${String(col.sizes)}, type: ${col.type}, descreiption: ${col.desc}} `).join(''); // prettier-ignore - getColByTitle = (title: string) => { - return this.fieldsInfos.filter(col => col.title === title)[0]; - }; + getColByTitle = (title: string): Col | undefined => this.fieldsInfos.filter(col => col.title === title)[0]; @action assignColsToFields = async (templates: Template[], cols: Col[]): Promise<[Template, { [field: number]: Col }][]> => { - const fieldDescriptions: string = this.compileFieldDescriptions(templates); - const colDescriptions: string = this.compileColDescriptions(cols); + const fieldDescriptions = this.compileFieldDescriptions(templates); + const colDescriptions = this.compileColDescriptions(cols); const inputText = fieldDescriptions.concat(colDescriptions); - const prompt: string = `(${Math.random() * 100000}) ${inputText}`; + const prompt = `(${Math.random() * 100000}) ${inputText}`; this._GPTLoading = true; @@ -431,23 +397,25 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> Object.entries(assignments).forEach(([tempTitle, assignment]) => { 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 (!col.AIGenerated) { - var field = template.getFieldByID(Number(fieldID)); - field.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? ViewType.IMG : ViewType.TEXT); - field = template.getFieldByID(Number(fieldID)); - field.setTitle(col.title); - } else { - a[Number(fieldID)] = this.getColByTitle(colTitle); - } - return a; - }, - {} as { [field: number]: Col } - ); - brokenDownAssignments.push([template, toObj]); + if (template) { + const toObj = Object.entries(assignment).reduce( + (a, [fieldID, colTitle]) => { + const col = this.getColByTitle(colTitle); + if (col) { + if (!col.AIGenerated) { + const field = template.getFieldByID(Number(fieldID)); + field?.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? ViewType.IMG : ViewType.TEXT); + field?.setTitle(col.title); + } else { + a[Number(fieldID)] = col; + } + } + return a; + }, + {} as { [field: number]: Col } + ); + brokenDownAssignments.push([template, toObj]); + } }); return brokenDownAssignments; @@ -459,39 +427,32 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> return []; }; - generatePresetTemplates = async () => { - const templates: Template[] = []; - + generatePresetTemplates = action(() => { if (this.DEBUG_MODE) { - templates.push(...this.templateManager.templates); + this.setSuggestedTemplates(this.templateManager._templates); + this._GPTLoading = false; } else { this._dataViz?.updateColDefaults(); - const contentFields = this.fieldsInfos.filter(field => field.type !== TemplateFieldType.DATA); - - templates.push(...this.templateManager.getValidTemplates(contentFields)); - - const assignments = await this.assignColsToFields(templates, contentFields); - - const renderedTemplatePromises = assignments.map(([template, assgns]) => TemplateMenuAIUtils.applyGPTContentToTemplate(template, assgns)); - - await Promise.all(renderedTemplatePromises); + const templates = this.templateManager.getValidTemplates(contentFields); + + return this.assignColsToFields(templates, contentFields) + .then(pairs => + Promise.all(pairs.map(([templ, assgns]) => TemplateMenuAIUtils.applyGPTContentToTemplate(templ, assgns)))) + .then(action(() => { + this.setSuggestedTemplates(templates); + this._GPTLoading = false; + })); // prettier-ignore } + }); - setTimeout( - action(() => { - this.setSuggestedTemplates(templates); - this._GPTLoading = false; - }) - ); - }; - - generateVariations = async (onDoc: Doc, prompt: string, options: FireflyStructureOptions): Promise<string[]> => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + generateVariations = async (onDoc: Doc, prompt: string, options: FireflyStructureOptions) => { // const { numVariations, temperature, useStyleRef } = options; this.variations = []; const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView; - const clone: Doc = (await Doc.MakeClone(onDoc)).clone; + const clone = Doc.MakeClone(onDoc).clone; mainCollection.addDocument(clone); clone.x = 10000; clone.y = 10000; @@ -499,13 +460,13 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> // await DrawingFillHandler.drawingToImage(clone, 100 - temperature, prompt, useStyleRef ? clone : undefined, this, numVariations) return this.variations; - } + }; - variations: string[] = [] + variations: string[] = []; @action addVariation = (url: string) => { this.variations.push(url); - } + }; addRenderedCollectionToMainview = (collection: Doc) => { if (collection) { @@ -517,10 +478,11 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> } }; - @action editLastTemplate = () => { if (this._editedTemplateTrail.length) this._currEditingTemplate = this._editedTemplateTrail.pop()} + @action editLastTemplate = () => { + if (this._editedTemplateTrail.length) this._currEditingTemplate = this._editedTemplateTrail.pop(); + }; @action setExpandedView = (template: Template | undefined) => { - if (template) { this._menuContent = 'templateEditing'; this._currEditingTemplate && this._editedTemplateTrail.push(this._currEditingTemplate); @@ -533,27 +495,23 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> //Docs.Create.FreeformDocument([doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: ''}); }; - @computed - get templatesView() { return ( - <div className='docCreatorMenu-templates-view'> - <div className="docCreatorMenu-templates-displays"> - <TemplatePreviewGrid - title={'Suggested Templates'} - menu={this} - loading={this._GPTLoading} - optionsButtonOpts={this.optionsButtonOpts} - templates={this._suggestedTemplates} - /> - <div className="docCreatorMenu-GPT-options"> - <div className="docCreatorMenu-GPT-options-container"> - <DocCreatorMenuButton icon={'arrows-rotate'} styles={'border'} function={this.generatePresetTemplates}/> + @computed + get templatesView() { + return ( + <div className="docCreatorMenu-templates-view"> + <div className="docCreatorMenu-templates-displays"> + <TemplatePreviewGrid title={'Suggested Templates'} menu={this} loading={this._GPTLoading} optionsButtonOpts={this.optionsButtonOpts} templates={this._suggestedTemplates} /> + <div className="docCreatorMenu-GPT-options"> + <div className="docCreatorMenu-GPT-options-container"> + <DocCreatorMenuButton icon={'arrows-rotate'} styles={'border'} function={this.generatePresetTemplates} /> + </div> </div> </div> </div> - </div> - )}; - - private optionsButtonOpts: [IconProp, () => any] = ['gear', () => (this._menuContent = 'dashboard')]; + ); + } + + private optionsButtonOpts: [IconProp, () => void] = ['gear', () => (this._menuContent = 'dashboard')]; get renderSelectedViewType() { switch (this._menuContent) { @@ -634,7 +592,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> {topButton('magnifying-glass', 'options', onOptionsSelected, 'middle')} {topButton('bars', 'saved', onSavedSelected, 'right')} </div> - <DocCreatorMenuButton icon={'minus'} styles={'float-right'} function={this.closeMenu}/> + <DocCreatorMenuButton icon={'minus'} styles={'float-right'} function={this.closeMenu} /> </div> {this.renderSelectedViewType} </div> @@ -642,4 +600,4 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> </div> ); } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx index b50fff9e0..c35099e82 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx @@ -1,25 +1,25 @@ -import { action, makeObservable, observable, reaction, runInAction } from "mobx"; -import React from "react"; -import { returnFalse, returnEmptyFilter } from "../../../../../../ClientUtils"; -import { emptyFunction } from "../../../../../../Utils"; -import { Doc, returnEmptyDoclist } from "../../../../../../fields/Doc"; -import { DefaultStyleProvider } from "../../../../StyleProvider"; -import { DocumentView, DocumentViewInternal } from "../../../DocumentView"; -import { DocCreatorMenu } from "../DocCreatorMenu"; -import { TemplatePreviewGrid } from "./TemplatePreviewGrid"; -import { observer } from "mobx-react"; -import { Transform } from "../../../../../util/Transform"; -import { Template } from "../Template"; -import { ObservableReactComponent } from "../../../../ObservableReactComponent"; -import { IDisposer } from "mobx-utils"; -import { DocCreatorMenuButton } from "./DocCreatorMenuButton"; -import { IconProp } from "@fortawesome/fontawesome-svg-core"; +import { action, makeObservable, observable, reaction } from 'mobx'; +import React from 'react'; +import { returnFalse, returnEmptyFilter } from '../../../../../../ClientUtils'; +import { emptyFunction } from '../../../../../../Utils'; +import { returnEmptyDoclist } from '../../../../../../fields/Doc'; +import { DefaultStyleProvider } from '../../../../StyleProvider'; +import { DocumentView, DocumentViewInternal } from '../../../DocumentView'; +import { DocCreatorMenu } from '../DocCreatorMenu'; +import { TemplatePreviewGrid } from './TemplatePreviewGrid'; +import { observer } from 'mobx-react'; +import { Transform } from '../../../../../util/Transform'; +import { Template } from '../Template'; +import { ObservableReactComponent } from '../../../../ObservableReactComponent'; +import { IDisposer } from 'mobx-utils'; +import { DocCreatorMenuButton } from './DocCreatorMenuButton'; +import { IconProp } from '@fortawesome/fontawesome-svg-core'; export type FireflyStructureOptions = { numVariations: number; temperature: number; useStyleRef: boolean; -} +}; interface FireflyVariationsTabProps { menu: DocCreatorMenu; @@ -28,112 +28,93 @@ interface FireflyVariationsTabProps { @observer export class FireflyVariationsTab extends ObservableReactComponent<FireflyVariationsTabProps> { + private _prompt: string = 'Use this template to generate an empty baseball card template.'; + private _optionsButtonOpts: [IconProp, () => void] = ['gear', emptyFunction]; + private _previewBoxRightButtonOpts: [IconProp, () => void] = ['gear', () => this.forceUpdate()]; - private prompt: string = 'Use this template to generate an empty baseball card template.'; - - @observable private promptInput: HTMLTextAreaElement | null = null; - + @observable _fireflyOptions: FireflyStructureOptions = { numVariations: 3, temperature: 0, useStyleRef: false }; + @observable _promptInput: HTMLTextAreaElement | null = null; @observable _loading: boolean = false; @observable _variationsTabOpen: boolean = false; @observable _variationURLs: string[] = []; - @observable private fireflyOptions: FireflyStructureOptions = {numVariations: 3, temperature: 0, useStyleRef: false}; - constructor(props: FireflyVariationsTabProps) { super(props); makeObservable(this); } - generateVariations = async () => { + generateVariations = action(async () => { this._props.menu._variations = []; this._loading = true; const cloneTemplate = this._props.template.clone(false); cloneTemplate.setMatteBackground(); - const doc: Doc = cloneTemplate.getRenderedDoc()!; - this._variationURLs = await this._props.menu.generateVariations(doc, this.prompt, this.fireflyOptions); - this._variationURLs.forEach(() => { - const newTemplate: Template = this._props.template.clone(true); - this._props.menu._variations.push(newTemplate); - }); - setTimeout(() => { - this._variationURLs.forEach((url, i) => { - this._props.menu._variations[i].setImageAsBackground(url, true); - }); - this._loading = false; - }); - } - - setPromptInputRef: React.LegacyRef<HTMLTextAreaElement> = (node) => { - this.promptInput = node; - } - - // eslint-disable-next-line - private optionsButtonOpts: [IconProp, () => any] = ['gear', () => {}]; - // eslint-disable-next-line - private previewBoxRightButtonOpts: [IconProp, () => any] = ['gear', () => this.forceUpdate()]; + const doc = cloneTemplate.getRenderedDoc()!; + this._props.menu.generateVariations(doc, this._prompt, this._fireflyOptions).then( + action((urls: string[]) => { + (this._variationURLs = urls).forEach(url => { + const template = this._props.template.clone(true); + template.setImageAsBackground(url, true); + this._props.menu._variations.push(template); + }); + this._loading = false; + }) + ); + }); render() { return ( - <div className='docCreatorMenu-editing-firefly-section'> - <div className="docCreatorMenu-option-divider full no-margin-bottom"/> + <div className="docCreatorMenu-editing-firefly-section"> + <div className="docCreatorMenu-option-divider full no-margin-bottom" /> <TemplatePreviewGrid menu={this._props.menu} - title={'Generate Variations'} + title="Generate Variations" loading={this._loading} - styles={'scrolling'} + styles="scrolling" templates={this._props.menu._variations} - optionsButtonOpts={this.optionsButtonOpts} - previewBoxRightButtonOpts={this.previewBoxRightButtonOpts} + optionsButtonOpts={this._optionsButtonOpts} + previewBoxRightButtonOpts={this._previewBoxRightButtonOpts} /> <div className="docCreatorMenu-firefly-options"> <div className="docCreatorMenu-variation-prompt-row"> <textarea className="docCreatorMenu-variation-prompt-input-textbox" - ref={this.setPromptInputRef} - onChange={e => { this.prompt = e.target.value }} + ref={action((node: HTMLTextAreaElement | null) => (this._promptInput = node))} + onChange={e => (this._prompt = e.target.value)} onInput={() => { - if (this.promptInput !== null) { - this.promptInput.style.height = 'auto'; - this.promptInput.style.height = this.promptInput.scrollHeight + 'px'; + if (this._promptInput !== null) { + this._promptInput.style.height = 'auto'; + this._promptInput.style.height = this._promptInput.scrollHeight + 'px'; } }} - defaultValue={''} - placeholder={'Enter a custom prompt here (optional)'} + defaultValue="" + placeholder="Enter a custom prompt here (optional)" /> - <DocCreatorMenuButton icon={'arrows-rotate'} styles={'border'} function={this.generateVariations}/> + <DocCreatorMenuButton icon={'arrows-rotate'} styles={'border'} function={this.generateVariations} /> </div> - <nav className="options‑menu"> - <label className="menu‑item switch"> - <input type="checkbox" checked={this.fireflyOptions.useStyleRef} - onChange={(e) => runInAction(() => { this.fireflyOptions.useStyleRef = e.target.checked })} - /> - <span className="slider round"></span> - <span className="firefly-option-label">Use template as style guide</span> - </label> - <div className="menu‑item"> - <span className="firefly-option-label">Variations</span> - <input type="range" id="variations" - min="1" - max="5" - value={this.fireflyOptions.numVariations} - onChange={(e) => runInAction(() => { this.fireflyOptions.numVariations = Number(e.target.value) })} - /> - <span className="value" id="varVal">{this.fireflyOptions.numVariations}</span> - </div> - <div className="menu‑item"> - <span className="firefly-option-label">Temperature</span> - <input type="range" id="temperature" - min="1" - max="100" - value={this.fireflyOptions.temperature} - onChange={(e) => runInAction(() => { this.fireflyOptions.temperature = Number(e.target.value) })} - /> - <span className="value" id="tempVal">{this.fireflyOptions.temperature}</span> - </div> - </nav> + <nav className="options‑menu"> + <label className="menu‑item switch"> + <input type="checkbox" checked={this._fireflyOptions.useStyleRef} onChange={action(e => (this._fireflyOptions.useStyleRef = e.target.checked))} /> + <span className="slider round"></span> + <span className="firefly-option-label">Use template as style guide</span> + </label> + <div className="menu‑item"> + <span className="firefly-option-label">Variations</span> + <input type="range" id="variations" min="1" max="5" value={this._fireflyOptions.numVariations} onChange={action(e => (this._fireflyOptions.numVariations = Number(e.target.value)))} /> + <span className="value" id="varVal"> + {this._fireflyOptions.numVariations} + </span> + </div> + <div className="menu‑item"> + <span className="firefly-option-label">Temperature</span> + <input type="range" id="temperature" min="1" max="100" value={this._fireflyOptions.temperature} onChange={action(e => (this._fireflyOptions.temperature = Number(e.target.value)))} /> + <span className="value" id="tempVal"> + {this._fireflyOptions.temperature} + </span> + </div> + </nav> </div> </div> - ) + ); } } @@ -144,11 +125,9 @@ interface TemplateEditingWindowProps { @observer export class TemplateEditingWindow extends ObservableReactComponent<TemplateEditingWindowProps> { - private disposers: { [name: string]: IDisposer } = {}; - @observable private previewWindow: HTMLDivElement | null = null; - + @observable private _previewWindow: HTMLDivElement | null = null; @observable _variationsTabOpen: boolean = false; constructor(props: TemplateEditingWindowProps) { @@ -157,83 +136,85 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit } componentDidMount(): void { - this.disposers.windowDimensions = reaction(() => - this._props.menu._resizing, - () => { this.forceUpdate() }, + this.disposers.windowDimensions = reaction( + () => this._props.menu._resizing, + () => this.forceUpdate(), { fireImmediately: true } - ); + ); } componentWillUnmount() { Object.values(this.disposers).forEach(disposer => disposer?.()); } - setContainerRef: React.LegacyRef<HTMLDivElement> = (node) => { - this.previewWindow = node; - } - @action setVariationTab = (open: boolean) => { this._variationsTabOpen = open; - if (this.previewWindow && open) { - this.previewWindow.style.height = String(Number(this.previewWindow.clientHeight) * .6); - } else if (this.previewWindow && !open) { - this.previewWindow.style.height = String(Number(this.previewWindow.clientHeight) * 5/3); + if (this._previewWindow && open) { + this._previewWindow.style.height = String(Number(this._previewWindow.clientHeight) * 0.6); + } else if (this._previewWindow && !open) { + this._previewWindow.style.height = String((Number(this._previewWindow.clientHeight) * 5) / 3); } - } - - get renderedDocPreview(){ - const doc: Doc = this._props.template.getRenderedDoc() as unknown as Doc; + }; + previewPanelWidth = () => this._previewWindow?.clientWidth ?? 500; + previewPanelHeight = () => this._previewWindow?.clientHeight ?? 500; + previewScreenToLocalXf = () => new Transform(-this._props.menu._pageX - 5, -this._props.menu._pageY - 35, 1); + get renderedDocPreview() { + const doc = this._props.template.getRenderedDoc(); return ( - <div className="docCreatorMenu-expanded-template-preview" ref={this.setContainerRef}> - {this.previewWindow ? <DocumentView - Document={doc} - isContentActive={emptyFunction} - addDocument={returnFalse} - moveDocument={returnFalse} - removeDocument={returnFalse} - PanelWidth={() => this.previewWindow?.clientWidth ?? 500} - PanelHeight={() => this.previewWindow?.clientHeight ?? 500} - ScreenToLocalTransform={() => new Transform(-this._props.menu._pageX - 5, -this._props.menu._pageY - 35, 1)} - renderDepth={5} - whenChildContentsActiveChanged={emptyFunction} - focus={emptyFunction} - styleProvider={DefaultStyleProvider} - addDocTab={DocumentViewInternal.addDocTabFunc} - pinToPres={() => undefined} - childFilters={returnEmptyFilter} - childFiltersByRanges={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - fitContentsToBox={returnFalse} - fitWidth={returnFalse} - /> : null} + <div className="docCreatorMenu-expanded-template-preview" ref={action((node: HTMLDivElement | null) => (this._previewWindow = node))}> + {this._previewWindow && doc ? ( + <DocumentView + Document={doc} + isContentActive={emptyFunction} + addDocument={returnFalse} + moveDocument={returnFalse} + removeDocument={returnFalse} + PanelWidth={this.previewPanelWidth} + PanelHeight={this.previewPanelHeight} + ScreenToLocalTransform={this.previewScreenToLocalXf} + renderDepth={5} + whenChildContentsActiveChanged={emptyFunction} + focus={emptyFunction} + styleProvider={DefaultStyleProvider} + addDocTab={DocumentViewInternal.addDocTabFunc} + pinToPres={emptyFunction} + childFilters={returnEmptyFilter} + childFiltersByRanges={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + // fitContentsToBox={returnFalse} + // fitWidth={returnFalse} + /> + ) : null} </div> - ) + ); } + expandFunc = () => { + // if (this._props.template === this._props.menu._selectedTemplate) { + // this._props.menu.updateRenderedPreviewCollection(this._props.template); + // } + this._props.menu.setExpandedView(undefined); + }; + lastFunc = () => { + this._props.menu.editLastTemplate(); + this.forceUpdate(); + }; + variationFunc = () => this.setVariationTab(!this._variationsTabOpen); render() { return ( - <div className='docCreatorMenu-templates-view'> + <div className="docCreatorMenu-templates-view"> <div className="docCreatorMenu-expanded-template-preview"> - <div className="top-panel"/> + <div className="top-panel" /> {this.renderedDocPreview} - {this._variationsTabOpen ? <FireflyVariationsTab - menu={this._props.menu} - template={this._props.template} - /> - : null} + {this._variationsTabOpen ? <FireflyVariationsTab menu={this._props.menu} template={this._props.template} /> : null} <div className="right-buttons-panel"> - <DocCreatorMenuButton icon={'minimize'} function={() => { - // if (this._props.template === this._props.menu._selectedTemplate) { - // this._props.menu.updateRenderedPreviewCollection(this._props.template); - // } - this._props.menu.setExpandedView(undefined); - }}/> - <DocCreatorMenuButton icon={'lightbulb'} function={() => this.setVariationTab(!this._variationsTabOpen)}/> - <DocCreatorMenuButton icon={'arrow-rotate-backward'} function={() => { this._props.menu.editLastTemplate(); this.forceUpdate(); }}/> + <DocCreatorMenuButton icon="minimize" function={this.expandFunc} /> + <DocCreatorMenuButton icon="lightbulb" function={this.variationFunc} /> + <DocCreatorMenuButton icon="arrow-rotate-backward" function={this.lastFunc} /> </div> </div> </div> ); } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateMenuFieldOptions.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateMenuFieldOptions.tsx index a4da54392..f0e20837c 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateMenuFieldOptions.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateMenuFieldOptions.tsx @@ -1,11 +1,11 @@ -import { makeObservable, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { ObservableReactComponent } from "../../../../ObservableReactComponent"; -import { Col, DocCreatorMenu } from "../DocCreatorMenu"; -import React from "react"; -import { Conditional, TemplateManager } from "../Backend/TemplateManager"; -import { TemplateFieldType, TemplateFieldSize } from "../TemplateBackend"; -import { DocCreatorMenuButton } from "./DocCreatorMenuButton"; +import { action, makeObservable, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import { ObservableReactComponent } from '../../../../ObservableReactComponent'; +import { Col, DocCreatorMenu } from '../DocCreatorMenu'; +import React from 'react'; +import { Conditional, TemplateManager } from '../Backend/TemplateManager'; +import { TemplateFieldType, TemplateFieldSize } from '../TemplateBackend'; +import { DocCreatorMenuButton } from './DocCreatorMenuButton'; interface TemplateMenuFieldOptionsProps { menu: DocCreatorMenu; @@ -14,7 +14,6 @@ interface TemplateMenuFieldOptionsProps { @observer export class TemplateMenuFieldOptions extends ObservableReactComponent<TemplateMenuFieldOptionsProps> { - @observable _collapsedCols: string[] = []; //any columns whose options panels are hidden constructor(props: TemplateMenuFieldOptionsProps) { @@ -34,160 +33,153 @@ export class TemplateMenuFieldOptions extends ObservableReactComponent<TemplateM condition: '', target: 'Own', attribute: '', - value: '' + value: '', }); } return this._newCondCache[title]; }; conditionForm = (title: string, parameters?: Conditional, empty: boolean = false) => { - - const contentFieldTitles = this._props.menu.fieldsInfos.filter(field => field.type !== TemplateFieldType.DATA).map(field => field.title).concat('Template'); + const contentFieldTitles = this._props.menu.fieldsInfos + .filter(field => field.type !== TemplateFieldType.DATA) + .map(field => field.title) + .concat('Template'); const params: Conditional = this.getParams(title, parameters); return ( - <div className='form'> - <div className='form-row'> - <div className='form-row-plain-text'>If</div> - <div className='form-row-plain-text'>{title}</div> - <div className="operator-options-dropdown"> - <span className="operator-dropdown-current">{params.operator ?? '='}</span> - <div className='operator-dropdown-option' onPointerDown={() => {params.operator = '='}}>{'='}</div> - </div> - <input - className="form-row-textarea" - onChange={e => runInAction(() => { params.condition = e.target.value })} - placeholder='value' - value={params.condition} - /> - <div className='form-row-plain-text'>then</div> - <div className="operator-options-dropdown"> - <span className="operator-dropdown-current">{params.target ?? 'Own'}</span> - {contentFieldTitles.map((fieldTitle, i) => - <div className='operator-dropdown-option' key={i} onPointerDown={() => {params.target = fieldTitle}}>{fieldTitle === title ? 'Own' : fieldTitle}</div> - )} + <div className="form"> + <div className="form-row"> + <div className="form-row-plain-text">If</div> + <div className="form-row-plain-text">{title}</div> + <div className="operator-options-dropdown"> + <span className="operator-dropdown-current">{params.operator ?? '='}</span> + <div className="operator-dropdown-option" onPointerDown={() => (params.operator = '=')}> + {'='} + </div> + </div> + <input className="form-row-textarea" onChange={action(e => (params.condition = e.target.value))} placeholder="value" value={params.condition} /> + <div className="form-row-plain-text">then</div> + <div className="operator-options-dropdown"> + <span className="operator-dropdown-current">{params.target ?? 'Own'}</span> + {contentFieldTitles.map((fieldTitle, i) => ( + <div className="operator-dropdown-option" key={i} onPointerDown={() => (params.target = fieldTitle)}> + {fieldTitle === title ? 'Own' : fieldTitle} + </div> + ))} + </div> + <input className="form-row-textarea" onChange={action(e => (params.attribute = e.target.value))} placeholder="attribute" value={params.attribute} /> + <div className="form-row-plain-text">{'becomes'}</div> + <input className="form-row-textarea" onChange={action(e => (params.value = e.target.value))} placeholder="value" value={params.value} /> </div> - <input - className="form-row-textarea" - onChange={e => runInAction(() => { params.attribute = e.target.value })} - placeholder='attribute' - value={params.attribute} - /> - <div className='form-row-plain-text'>{'becomes'}</div> - <input - className="form-row-textarea" - onChange={e => runInAction(() => { params.value = e.target.value })} - placeholder='value' - value={params.value} - /> + {empty ? ( + <DocCreatorMenuButton + icon={'plus'} + styles={'float-right border'} + function={() => { + this._newCondCache[title] = observable<Conditional>({ + field: title, + operator: '=', + condition: '', + target: 'Own', + attribute: '', + value: '', + }); + this._props.templateManager.addFieldCondition(title, params); + }} + /> + ) : ( + <DocCreatorMenuButton icon={'minus'} styles={'float-right border'} function={() => this._props.templateManager.removeFieldCondition(title, params)} /> + )} </div> - {empty ? - <DocCreatorMenuButton icon={'plus'} styles={'float-right border'} function={() => { - this._newCondCache[title] = observable<Conditional>({ - field: title, - operator: '=', - condition: '', - target: 'Own', - attribute: '', - value: '' - }); - this._props.templateManager.addFieldCondition(title, params); - }}/> - : - <DocCreatorMenuButton icon={'minus'} styles={'float-right border'} function={() => this._props.templateManager.removeFieldCondition(title, params)}/> - } - </div> - ) - } + ); + }; fieldPanel = (field: Col, id: number) => ( <div className="field-panel" key={id}> - <div className="top-bar" onPointerDown={e => this._props.menu.setUpButtonClick(e, runInAction(() => () => { - if (this._collapsedCols.includes(field.title)) { - this._collapsedCols = this._collapsedCols.filter(col => col !== field.title); - } else { - this._collapsedCols.push(field.title); - } - }))}> + <div + className="top-bar" + onPointerDown={e => + this._props.menu.setUpButtonClick( + e, + action(() => { + if (this._collapsedCols.includes(field.title)) { + this._collapsedCols = this._collapsedCols.filter(col => col !== field.title); + } else { + this._collapsedCols.push(field.title); + } + }) + ) + }> <span className="field-title">{`${field.title} Field`}</span> - <DocCreatorMenuButton icon={'minus'} styles={'no-margin absolute-right'} function={() => this._props.menu.removeField(field)}/> + <DocCreatorMenuButton icon={'minus'} styles={'no-margin absolute-right'} function={() => this._props.menu.removeField(field)} /> </div> - { this._collapsedCols.includes(field.title) ? null : + {this._collapsedCols.includes(field.title) ? null : ( <> - <div className="opts-bar"> - <div className="opt-box"> - <div className="top-bar"> Title </div> - <textarea className="content" style={{ width: '100%', height: 'calc(100% - 20px)' }} value={field.title} placeholder={'Enter title'} onChange={e => this._props.menu.setColTitle(field, e.target.value)} /> - </div> - <div className="opt-box"> - <div className="top-bar"> Type </div> - <div className="content"> - <span className="type-display">{ - field.type === TemplateFieldType.TEXT ? 'Text Field' - : field.type === TemplateFieldType.VISUAL ? 'File Field' - : field.type === TemplateFieldType.DATA ? 'Data Field' - : '' - }</span> - <div className="bubbles"> - <input className="bubble" type="radio" name="type" onClick={() => this._props.menu.setColType(field, TemplateFieldType.TEXT)} /> - <div className="text">Text</div> - <input className="bubble" type="radio" name="type" onClick={() => this._props.menu.setColType(field, TemplateFieldType.VISUAL)} /> - <div className="text">File</div> - <input className="bubble" type="radio" name="type" onClick={() => this._props.menu.setColType(field, TemplateFieldType.DATA)} /> - <div className="text">Data</div> - </div> + <div className="opts-bar"> + <div className="opt-box"> + <div className="top-bar"> Title </div> + <textarea className="content" style={{ width: '100%', height: 'calc(100% - 20px)' }} value={field.title} placeholder={'Enter title'} onChange={e => this._props.menu.setColTitle(field, e.target.value)} /> </div> - </div> - </div> - { field.type === TemplateFieldType.DATA ? null : - (<> - <div className="sizes-box"> - <div className="top-bar"> Valid Sizes </div> + <div className="opt-box"> + <div className="top-bar"> Type </div> <div className="content"> + <span className="type-display">{field.type === TemplateFieldType.TEXT ? 'Text Field' : field.type === TemplateFieldType.VISUAL ? 'File Field' : field.type === TemplateFieldType.DATA ? 'Data Field' : ''}</span> <div className="bubbles"> - {Object.values(TemplateFieldSize).map(size => ( - <div key={field + size}> - <input className="bubble" type="checkbox" name="type" checked={field.sizes.includes(size)} onChange={e => this._props.menu.modifyColSizes(field, size, e.target.checked)} /> - <div className="text">{size}</div> - </div> - ))} + <input className="bubble" type="radio" name="type" onClick={() => this._props.menu.setColType(field, TemplateFieldType.TEXT)} /> + <div className="text">Text</div> + <input className="bubble" type="radio" name="type" onClick={() => this._props.menu.setColType(field, TemplateFieldType.VISUAL)} /> + <div className="text">File</div> + <input className="bubble" type="radio" name="type" onClick={() => this._props.menu.setColType(field, TemplateFieldType.DATA)} /> + <div className="text">Data</div> </div> </div> </div> - <div className="desc-box"> - <div className="top-bar"> Prompt </div> - <textarea - className="content" - onChange={e => this._props.menu.setColDesc(field, e.target.value)} - defaultValue={field.desc === this._props.menu._dataViz?.GPTSummary?.get(field.title)?.desc ? '' : field.desc} - placeholder={this._props.menu._dataViz?.GPTSummary?.get(field.title)?.desc ?? 'Add a description/prompt to help with template generation.'} - /> - </div> - </>) - } - <div className="conditionals-section"> - <span className="conditionals-title">Conditional Logic</span> - {this.conditionForm(field.title, undefined, true)} - {this._props.templateManager.conditionalFieldLogic[field.title]?.map(condition => this.conditionForm(condition.field, condition))} - </div> + </div> + {field.type === TemplateFieldType.DATA ? null : ( + <> + <div className="sizes-box"> + <div className="top-bar"> Valid Sizes </div> + <div className="content"> + <div className="bubbles"> + {Object.values(TemplateFieldSize).map(size => ( + <div key={field + size}> + <input className="bubble" type="checkbox" name="type" checked={field.sizes.includes(size)} onChange={e => this._props.menu.modifyColSizes(field, size, e.target.checked)} /> + <div className="text">{size}</div> + </div> + ))} + </div> + </div> + </div> + <div className="desc-box"> + <div className="top-bar"> Prompt </div> + <textarea + className="content" + onChange={e => this._props.menu.setColDesc(field, e.target.value)} + defaultValue={field.desc === this._props.menu._dataViz?.GPTSummary?.get(field.title)?.desc ? '' : field.desc} + placeholder={this._props.menu._dataViz?.GPTSummary?.get(field.title)?.desc ?? 'Add a description/prompt to help with template generation.'} + /> + </div> + </> + )} + <div className="conditionals-section"> + <span className="conditionals-title">Conditional Logic</span> + {this.conditionForm(field.title, undefined, true)} + {this._props.templateManager._conditionalFieldLogic[field.title]?.map(condition => this.conditionForm(condition.field, condition))} + </div> </> - } + )} </div> ); - - render() { return ( <div className="docCreatorMenu-dashboard-view"> <div className="topbar"> - <DocCreatorMenuButton icon={'plus'} function={this._props.menu.addField}/> - <DocCreatorMenuButton icon={'arrow-left'} styles={'float-right'} function={() => runInAction(() => (this._props.menu._menuContent = 'templates'))}/> + <DocCreatorMenuButton icon="plus" function={this._props.menu.addField} /> + <DocCreatorMenuButton icon="arrow-left" styles="float-right" function={action(() => (this._props.menu._menuContent = 'templates'))} /> </div> <div className="panels-container">{this._props.menu.fieldsInfos.map((field, i) => this.fieldPanel(field, i))}</div> </div> ); } - - -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx index dc4c35789..7d02fff12 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx @@ -1,35 +1,30 @@ -import { Colors } from "@dash/components/src"; -import { FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import { Template } from "../Template"; -import { makeObservable, observable } from "mobx"; -import React from "react"; -import { ObservableReactComponent } from "../../../../ObservableReactComponent"; -import { DocCreatorMenu } from "../DocCreatorMenu"; -import { IconProp } from "@fortawesome/fontawesome-svg-core"; -import { DocumentView } from "../../../DocumentView"; -import { emptyFunction } from "../../../../../../Utils"; -import { returnEmptyFilter, returnFalse } from "../../../../../../ClientUtils"; -import { Transform } from "../../../../../util/Transform"; -import { DefaultStyleProvider } from "../../../../StyleProvider"; -import { Doc, returnEmptyDoclist } from "../../../../../../fields/Doc"; -import { observer } from "mobx-react"; +import { Colors } from '@dash/components/src'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Template } from '../Template'; +import { action, makeObservable, observable } from 'mobx'; +import React from 'react'; +import { ObservableReactComponent } from '../../../../ObservableReactComponent'; +import { DocCreatorMenu } from '../DocCreatorMenu'; +import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { DocumentView } from '../../../DocumentView'; +import { emptyFunction } from '../../../../../../Utils'; +import { returnEmptyFilter, returnFalse } from '../../../../../../ClientUtils'; +import { Transform } from '../../../../../util/Transform'; +import { DefaultStyleProvider } from '../../../../StyleProvider'; +import { Doc, returnEmptyDoclist } from '../../../../../../fields/Doc'; +import { observer } from 'mobx-react'; export interface TemplatePreviewBoxProps { template: Template; menu: DocCreatorMenu; // eslint-disable-next-line - leftButtonOpts?: [icon: IconProp, func: (...args: any) => void] // eslint-disable-next-line - rightButtonOpts?: [icon: IconProp, func: (...args: any) => void] + leftButtonOpts?: [icon: IconProp, func: (...args: any) => void]; // eslint-disable-next-line + rightButtonOpts?: [icon: IconProp, func: (...args: any) => void]; } @observer export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreviewBoxProps> { - @observable private previewWindow: HTMLDivElement | null = null; - setContainerRef: React.LegacyRef<HTMLDivElement> = (node) => { - this.previewWindow = node; - } - constructor(props: TemplatePreviewBoxProps) { super(props); makeObservable(this); @@ -39,6 +34,10 @@ export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreview return this._props.template.getRenderedDoc() as Doc; } + docPanelWidth = () => this.previewWindow?.clientWidth ?? this._props.menu._menuDimensions.height * 0.3; + docPanelHeight = () => this.previewWindow?.clientHeight ?? this._props.menu._menuDimensions.height * 0.3; + docScreenToLocalXf = () => new Transform(-this._props.menu._pageX - 5, -this._props.menu._pageY - 35, 1); + render() { const template = this._props.template; @@ -46,49 +45,45 @@ export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreview <div key={template.title} className="docCreatorMenu-preview-window" - ref={this.setContainerRef} + ref={action((node: HTMLDivElement | null) => (this.previewWindow = node))} style={{ border: this._props.menu._selectedTemplate === template ? `solid 3px ${Colors.MEDIUM_BLUE}` : '', boxShadow: this._props.menu._selectedTemplate === template ? `0 0 15px rgba(68, 118, 247, .8)` : '', }} - onPointerDown={e => this._props.menu.setUpButtonClick(e, () => this._props.menu.updateSelectedTemplate(template))} - > - { this._props.leftButtonOpts ? - <button - className="option-button left" - onPointerDown={e => - this._props.menu.setUpButtonClick(e, () => this._props.leftButtonOpts) - }> + onPointerDown={e => this._props.menu.setUpButtonClick(e, () => this._props.menu.updateSelectedTemplate(template))}> + {this._props.leftButtonOpts ? ( + <button className="option-button left" onPointerDown={e => this._props.menu.setUpButtonClick(e, () => this._props.leftButtonOpts)}> <FontAwesomeIcon icon={this._props.leftButtonOpts![0]} color="white" /> - </button> : null - } - { this._props.rightButtonOpts ? + </button> + ) : null} + {this._props.rightButtonOpts ? ( <button className="option-button right" onPointerDown={e => this._props.menu.setUpButtonClick(e, () => this._props.rightButtonOpts)}> <FontAwesomeIcon icon={this._props.rightButtonOpts![0]} color="white" /> - </button> : null } - <DocumentView - Document={this.doc} - isContentActive={emptyFunction} // !!! should be return false - addDocument={returnFalse} - moveDocument={returnFalse} - removeDocument={returnFalse} - PanelWidth={() => this.previewWindow?.clientWidth ?? this._props.menu._menuDimensions.height * .3} - PanelHeight={() => this.previewWindow?.clientHeight ?? this._props.menu._menuDimensions.height * .3} - ScreenToLocalTransform={() => new Transform(-this._props.menu._pageX - 5, -this._props.menu._pageY - 35, 1)} - renderDepth={1} - whenChildContentsActiveChanged={emptyFunction} - focus={emptyFunction} - styleProvider={DefaultStyleProvider} - addDocTab={this._props.menu._props.addDocTab} - pinToPres={() => undefined} - childFilters={returnEmptyFilter} - childFiltersByRanges={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - fitContentsToBox={returnFalse} - fitWidth={returnFalse} - hideDecorations={true} - /> + </button> + ) : null} + <DocumentView + Document={this.doc} + isContentActive={emptyFunction} // !!! should be return false + addDocument={returnFalse} + moveDocument={returnFalse} + removeDocument={returnFalse} + PanelWidth={this.docPanelWidth} + PanelHeight={this.docPanelHeight} + ScreenToLocalTransform={this.docScreenToLocalXf} + renderDepth={1} + whenChildContentsActiveChanged={emptyFunction} + focus={emptyFunction} + styleProvider={DefaultStyleProvider} + addDocTab={this._props.menu._props.addDocTab} + pinToPres={emptyFunction} + childFilters={returnEmptyFilter} + childFiltersByRanges={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + // fitContentsToBox={returnFalse} + // fitWidth={returnFalse} + hideDecorations={true} + /> </div> - ) + ); } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateRenderPreviewWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateRenderPreviewWindow.tsx index f281f770e..9222d7349 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateRenderPreviewWindow.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateRenderPreviewWindow.tsx @@ -1,20 +1,20 @@ -import { action, computed, makeObservable, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { ObservableReactComponent } from "../../../../ObservableReactComponent"; -import { DocCreatorMenu, LayoutType } from "../DocCreatorMenu"; -import React from "react"; -import { IconProp } from "@fortawesome/fontawesome-svg-core"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { setupMoveUpEvents, returnFalse, returnEmptyFilter } from "../../../../../../ClientUtils"; -import { emptyFunction } from "../../../../../../Utils"; -import { undoable } from "../../../../../util/UndoManager"; -import ReactLoading from "react-loading"; -import { Doc, NumListCast, returnEmptyDoclist } from "../../../../../../fields/Doc"; -import { StrCast } from "../../../../../../fields/Types"; -import { DefaultStyleProvider } from "../../../../StyleProvider"; -import { DocumentView } from "../../../DocumentView"; -import { Transform } from "../../../../../util/Transform"; -import { Docs, DocumentOptions } from "../../../../../documents/Documents"; +import { action, computed, makeObservable, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { ObservableReactComponent } from '../../../../ObservableReactComponent'; +import { DocCreatorMenu, LayoutType } from '../DocCreatorMenu'; +import React from 'react'; +import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { setupMoveUpEvents, returnFalse, returnEmptyFilter } from '../../../../../../ClientUtils'; +import { emptyFunction } from '../../../../../../Utils'; +import { undoable } from '../../../../../util/UndoManager'; +import ReactLoading from 'react-loading'; +import { Doc, NumListCast, returnEmptyDoclist } from '../../../../../../fields/Doc'; +import { NumCast, StrCast } from '../../../../../../fields/Types'; +import { DefaultStyleProvider } from '../../../../StyleProvider'; +import { DocumentView } from '../../../DocumentView'; +import { Transform } from '../../../../../util/Transform'; +import { Docs, DocumentOptions } from '../../../../../documents/Documents'; interface TemplatesRenderPreviewWindowProps { menu: DocCreatorMenu; @@ -22,8 +22,7 @@ interface TemplatesRenderPreviewWindowProps { @observer export class TemplatesRenderPreviewWindow extends ObservableReactComponent<TemplatesRenderPreviewWindowProps> { - - @observable private _layout: { type: LayoutType; yMargin: number; xMargin: number; columns?: number; repeat: number } = { type: LayoutType.FREEFORM, yMargin: 10, xMargin: 10, columns: 0, repeat: 0 }; + @observable private _layout: { type: LayoutType; yMargin: number; xMargin: number; columns?: number; repeat: number } = { type: LayoutType.FREEFORM, yMargin: 10, xMargin: 10, columns: 1, repeat: 0 }; @observable private renderedDocs: Doc[] = []; @observable private renderedDocCollection: Doc | undefined = undefined; @@ -62,7 +61,7 @@ export class TemplatesRenderPreviewWindow extends ObservableReactComponent<Templ @computed get columnsCount() { switch (this._layout.type) { case LayoutType.FREEFORM: - return this._layout.columns ?? 0; + return this._layout.columns ?? 1; case LayoutType.CAROUSEL3D: return 3; default: @@ -130,28 +129,27 @@ export class TemplatesRenderPreviewWindow extends ObservableReactComponent<Templ const { horizontalSpan, verticalSpan } = this.previewInfo; collection._height = verticalSpan; collection._width = horizontalSpan; - - 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); - const docWidth: number = Number(docs[0]._width); - - if (columns === 0 || docs.length === 0) { - return; - } - - let i: number = 0; - let docsChanged: number = 0; - let curX: number = startX; - let curY: number = startY; + collection.layout_fitWidth = true; + collection.freeform_fitContentsToBox = true; + + const columns = (this._layout.columns ?? this.columnsCount) || 1; + const xGap = this._layout.xMargin; + const yGap = this._layout.yMargin; + const startX = -collection._width / 2; + const startY = -collection._height / 2; + const docHeight = NumCast(docs[0]?._height); + const docWidth = NumCast(docs[0]?._width); + + let i = 0; + let docsChanged = 0; + let curX = startX; + let curY = startY; while (docsChanged < docs.length) { while (i < columns && docsChanged < docs.length) { docs[docsChanged].x = curX; docs[docsChanged].y = curY; + docs[docsChanged].layout_fitWidth = false; curX += docWidth + xGap; ++docsChanged; ++i; @@ -164,8 +162,8 @@ export class TemplatesRenderPreviewWindow extends ObservableReactComponent<Templ @computed get previewInfo() { - const docHeight: number = Number(this.renderedDocs[0]._height); - const docWidth: number = Number(this.renderedDocs[0]._width); + const docHeight = NumCast(this.renderedDocs[0]?._height); + const docWidth = NumCast(this.renderedDocs[0]?._width); const layout = this._layout; return { docHeight: docHeight, @@ -201,6 +199,10 @@ export class TemplatesRenderPreviewWindow extends ObservableReactComponent<Templ } } + layoutPanelWidth = () => this._props.menu._menuDimensions.width - 80; + layoutPanelHeight = () => this._props.menu._menuDimensions.height - 105; + layoutScreenToLocalXf = () => new Transform(-this._props.menu._pageX - 5, -this._props.menu._pageY - 35, 1); + layoutPreviewContents = action(() => { return this.loading ? ( <div className="docCreatorMenu-layout-preview-window-wrapper loading"> @@ -216,20 +218,18 @@ export class TemplatesRenderPreviewWindow extends ObservableReactComponent<Templ addDocument={returnFalse} moveDocument={returnFalse} removeDocument={returnFalse} - PanelWidth={() => this._props.menu._menuDimensions.width - 80} - PanelHeight={() => this._props.menu._menuDimensions.height - 105} - ScreenToLocalTransform={() => new Transform(-this._props.menu._pageX - 5, -this._props.menu._pageY - 35, 1)} + PanelWidth={this.layoutPanelWidth} + PanelHeight={this.layoutPanelHeight} + ScreenToLocalTransform={this.layoutScreenToLocalXf} renderDepth={5} whenChildContentsActiveChanged={emptyFunction} focus={emptyFunction} styleProvider={DefaultStyleProvider} addDocTab={this._props.menu._props.addDocTab} - pinToPres={() => undefined} + pinToPres={emptyFunction} childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - fitContentsToBox={returnFalse} - fitWidth={returnFalse} hideDecorations={true} /> </div> @@ -273,7 +273,6 @@ export class TemplatesRenderPreviewWindow extends ObservableReactComponent<Templ }; get optionsMenuContents() { - const repeatOptions = [0, 1, 2, 3, 4, 5]; return ( @@ -341,5 +340,7 @@ export class TemplatesRenderPreviewWindow extends ObservableReactComponent<Templ ); } - render() { return this.optionsMenuContents } -}
\ No newline at end of file + render() { + return this.optionsMenuContents; + } +} diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts index 7591740e0..e2a2a3c1c 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts @@ -9,7 +9,7 @@ import { TemplateDataField } from './TemplateFieldTypes/DataField'; export class Template { _mainField: DynamicField; - private dataFields: TemplateDataField[] = []; + private _dataFields: TemplateDataField[] = []; /** * A Template can be created from a description of its fields (FieldSettings) or from a DynamicField @@ -20,37 +20,13 @@ export class Template { this._mainField = definition instanceof DynamicField ? definition : this.setupMainField(definition); } - 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(); - } - - get descriptionSummary(): string { - let summary: string = ''; - this.contentFields.forEach(field => { - summary += `--- Field #${field.getID} (title: ${field.getTitle()}): ${field.getDescription ?? ''} ---`; - }); - return summary; - } - - get compiledContent(): string { - let summary: string = ''; - this.contentFields.forEach(field => { - summary += `--- Field #${field.getID} (title: ${field.getTitle()}): ${field.getContent() ?? ''} ---`; - }); - return summary; - } + get childFields() { return this._mainField?.getSubfields ?? []; } // prettier-ignore + get allFields() { return this._mainField?.getAllSubfields ?? []; } // prettier-ignore + get contentFields() { return this.allFields.filter(field => field.isContentField); } // prettier-ignore + get doc() { return this._mainField?.renderedDoc; } // prettier-ignore + get title() { return this._mainField?.getTitle(); } // prettier-ignore + get descriptionSummary() { return this.contentFields.map(f => `--- Field #${f.getID} (title: ${f.getTitle()}): ${f.getDescription ?? ''} ---`).join(); } // prettier-ignore + get compiledContent() { return this.contentFields.map(f => `--- Field #${f.getID} (title: ${f.getTitle()}): ${f.getContent() ?? ''} ---`).join(); } // prettier-ignore cleanup = () => { //dispose each subfields disposers, etc. @@ -58,61 +34,56 @@ export class Template { clone = (withContent: boolean = false) => { const clone = new Template(this._mainField?.makeClone(undefined, withContent) ?? TemplateLayouts.BasicSettings); - this.dataFields.forEach(field => clone.addDataField(field.title)); + this._dataFields.forEach(field => clone.addDataField(field.title)); return clone; }; getRenderedDoc = () => this.doc; - getFieldByID = (id: number): TemplateField => this.allFields.filter(field => field.getID === id)[0]; + getFieldByID = (id: number): TemplateField | undefined => this.allFields.filter(field => field.getID === id)[0]; - getFieldByTitle = (title: string) => [...this.allFields, ...this.dataFields].filter(field => field.getTitle() === title)[0]; + getFieldByTitle = (title: string) => [...this.allFields, ...this._dataFields].filter(field => field.getTitle() === title)[0]; setupMainField = (templateInfo: FieldSettings) => TemplateField.CreateField(templateInfo, 1, undefined) as DynamicField; assignColToField = (fieldID: number, col: Col) => { const field = this.getFieldByID(fieldID); - field.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? ViewType.IMG : ViewType.TEXT); - field.setTitle(col.title); - } + field?.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? ViewType.IMG : ViewType.TEXT); + field?.setTitle(col.title); + }; - addDataField = (title: string, content?: string) => { - this.dataFields.push(new TemplateDataField(title, content)); - } + addDataField = (title: string, content?: string) => this._dataFields.push(new TemplateDataField(title, content)); - removeDataField = (title: string) => { - this.dataFields = this.dataFields.filter(field => !(field.title === title)); - } + removeDataField = (title: string) => (this._dataFields = this._dataFields.filter(field => field.title !== title)); - isValidTemplate = (cols: Col[]) => { - const maxMatches = this.maxMatches(this.getMatches(cols)); - return maxMatches === this.contentFields.length && this.title !== 'template_framework'; - }; + isValidTemplate = (cols: Col[]) => this.title !== 'template_framework' && this.maxMatches(this.getMatches(cols)) === this.contentFields.length; applyConditionalLogicToField = (field: TemplateField | TemplateDataField, logic: Record<string, Conditional[]>) => { if (field instanceof DynamicField) return; - const fieldStatements: Conditional[] = logic[field.getTitle()]; - const content = field.getContent() - fieldStatements && fieldStatements.forEach(statement => { + const fieldStatements = logic[field.getTitle()]; + const content = field.getContent(); + fieldStatements?.forEach(statement => { if (content === statement.condition) { if (statement.target === 'Template') { - this._mainField.renderedDoc![statement.attribute] = statement.value; - Object.assign(this._mainField.settings.opts, {[statement.attribute]: statement.value}); + if (this._mainField.renderedDoc) { + this._mainField.renderedDoc[statement.attribute] = statement.value; + Object.assign(this._mainField.settings.opts, { [statement.attribute]: statement.value }); + } } else { - const targetField: TemplateField = this.getFieldByTitle(statement.target) as TemplateField; - if (targetField) { - targetField.renderedDoc![statement.attribute] = statement.value; - Object.assign(targetField.settings.opts, {[statement.attribute]: statement.value}); - } + const targetField = this.getFieldByTitle(statement.target); + if (targetField instanceof TemplateField && targetField.renderedDoc) { + targetField.renderedDoc[statement.attribute] = statement.value; + Object.assign(targetField.settings.opts, { [statement.attribute]: statement.value }); + } } } - }) - } + }); + }; applyConditionalLogic = (logic: Record<string, Conditional[]>) => { - const fields: (TemplateField | TemplateDataField)[] = [...this.allFields, ...this.dataFields]; - fields.forEach(field => this.applyConditionalLogicToField(field, logic)); - } + [...this.allFields, ...this._dataFields].forEach(field => this.applyConditionalLogicToField(field, logic)); + return this.getRenderedDoc(); + }; setImageAsBackground(url: string, makeTransparent: boolean = false) { const fieldSettings: FieldSettings = { @@ -120,15 +91,15 @@ export class Template { br: [1, 1], opts: {}, viewType: ViewType.IMG, - } + }; - const field: TemplateField = TemplateField.CreateField(fieldSettings, Math.random() * 100 + 100, this._mainField); + const field = TemplateField.CreateField(fieldSettings, Math.random() * 100 + 100, this._mainField); field.setContent(url); if (makeTransparent) { - this.allFields.forEach(field => { - field.updateDocSetting('backgroundColor', 'transparent'); - field.updateDocSetting('borderWidth', '0'); + this.allFields.forEach(aField => { + aField.updateDocSetting('backgroundColor', 'transparent'); + aField.updateDocSetting('borderWidth', '0'); }); } @@ -138,7 +109,7 @@ export class Template { /** * This function is just a hack for now to get around weird document icon stuff (specifically it misses the background) */ - setMatteBackground(makeTransparent: boolean = false) { + setMatteBackground(makeTransparent: boolean = false) { if (this._mainField.hasBackground) { return; } @@ -146,18 +117,17 @@ export class Template { const fieldSettings: FieldSettings = { tl: [-1, -1], br: [1, 1], - opts: {backgroundColor: String(this._mainField.renderedDoc!.backgroundColor)}, + opts: { backgroundColor: String(this._mainField.renderedDoc!.backgroundColor) }, viewType: ViewType.TEXT, - } + }; - const field: TemplateField = TemplateField.CreateField(fieldSettings, Math.random() * 100 + 100, this._mainField); + const field = TemplateField.CreateField(fieldSettings, Math.random() * 100 + 100, this._mainField); - if (makeTransparent) { - this.allFields.forEach(field => { - field.updateDocSetting('backgroundColor', 'transparent'); - field.updateDocSetting('borderWidth', '0'); + makeTransparent && + this.allFields.forEach(aField => { + aField.updateDocSetting('backgroundColor', 'transparent'); + aField.updateDocSetting('borderWidth', '0'); }); - } this._mainField.makeBackgroundField(field); } @@ -167,9 +137,7 @@ export class Template { if (cols.length !== numFields) return []; - const matches: number[][] = Array(numFields) - .fill([]) - .map(() => []); + const matches = Array<number[]>(numFields); this.contentFields.forEach((field, i) => (matches[i] = field.matches(cols))); @@ -180,8 +148,8 @@ export class Template { if (matches.length === 0) return 0; const fieldsCt = this.contentFields.length; - const used: boolean[] = Array(fieldsCt).fill(false); - const mt: number[] = Array(fieldsCt).fill(-1); + const used = Array<boolean>(fieldsCt).fill(false); + const mt = Array<number>(fieldsCt).fill(-1); const augmentingPath = (v: number): boolean => { if (!used[v]) { diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts index c100d1cce..091ef834a 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts @@ -67,13 +67,13 @@ export abstract class TemplateField { setTitle = (title: string) => { this._title = title; this.settings.title = title; - if (this._renderDoc) this._renderDoc.title = title + if (this._renderDoc) this._renderDoc.title = title; }; getTitle = () => this._title; updateDocSetting(setting: string, newVal: string) { if (this._renderDoc) this._renderDoc[setting] = newVal; - const settings: {[s: string]: string } = {[setting]: newVal} + const settings: { [s: string]: string } = { [setting]: newVal }; Object.assign(this.settings.opts, settings); } @@ -162,7 +162,7 @@ export enum ViewType { DEC = 'decoration', IMG = 'image', TEXT = 'text', - NONE = 'none' + NONE = 'none', } export type FieldDimensions = { @@ -170,4 +170,3 @@ export type FieldDimensions = { height: number; coord: { x: number; y: number }; }; - diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 30fc44f62..8ed59c6e1 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -175,6 +175,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width), height: this.layoutDoc._height }), ({ nativeSize, width, height }) => { if (!this.layoutDoc._layout_nativeDimEditable || !height || this.layoutDoc.layout_resetNativeDim) { + if (!this._props.TemplateDataDocument) this.layoutDoc._nativeWidth = this.layoutDoc._nativeHeight = undefined; this.layoutDoc.layout_resetNativeDim = undefined; // reset dimensions of templates rendered with content or if image changes. afterwards, remove this flag. this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth; } |