From 215f874c5f7f28964a3428909fcee0f667388bae Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 1 Jun 2025 16:07:30 -0400 Subject: a number of fixes to document creator to fix preview of templates and templates with images. --- .../DocCreatorMenu/Backend/TemplateManager.ts | 79 ++---- .../DocCreatorMenu/Backend/TemplateMenuAIUtils.ts | 34 ++- .../DataVizBox/DocCreatorMenu/DocCreatorMenu.scss | 183 +++++++------- .../DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx | 206 ++++++--------- .../DocCreatorMenu/Menu/TemplateEditingWindow.tsx | 277 ++++++++++----------- .../Menu/TemplateMenuFieldOptions.tsx | 252 +++++++++---------- .../DocCreatorMenu/Menu/TemplatePreviewBox.tsx | 113 ++++----- .../Menu/TemplateRenderPreviewWindow.tsx | 97 ++++---- .../nodes/DataVizBox/DocCreatorMenu/Template.ts | 132 ++++------ .../TemplateFieldTypes/TemplateField.ts | 7 +- src/client/views/nodes/ImageBox.tsx | 1 + 11 files changed, 620 insertions(+), 761 deletions(-) (limited to 'src/client/views/nodes') 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 = {}; + _conditionalFieldLogic: Record = {}; 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 => { try { const res = await gptImageCall(prompt); @@ -24,13 +24,10 @@ export class TemplateMenuAIUtils { public static renderGPTImageCall = async (template: Template, col: Col, fieldNumber: number): Promise => { 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 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 this._pageX = x + translation.x; this._pageY = y + translation.y; }; - - async createDocsForPreview(): Promise { 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 // }; @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 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 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 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 => { + // 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 // 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 } }; - @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 //Docs.Create.FreeformDocument([doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: ''}); }; - @computed - get templatesView() { return ( -
-
- -
-
- + @computed + get templatesView() { + return ( +
+
+ +
+
+ +
-
- )}; - - 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 {topButton('magnifying-glass', 'options', onOptionsSelected, 'middle')} {topButton('bars', 'saved', onSavedSelected, 'right')}
- +
{this.renderSelectedViewType}
@@ -642,4 +600,4 @@ export class DocCreatorMenu extends ObservableReactComponent ); } -} \ 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 { + 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 = (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 ( -
-
+
+