diff options
Diffstat (limited to 'src')
10 files changed, 423 insertions, 213 deletions
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts index 6d63078a8..ef7dbc7ab 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts @@ -2,10 +2,10 @@ import { action, makeAutoObservable } from 'mobx'; import { Col } from '../DocCreatorMenu'; import { FieldSettings } from '../TemplateFieldTypes/TemplateField'; import { Template } from '../Template'; -import { NumListCast } from '../../../../../../fields/Doc'; +import { Doc, NumListCast } from '../../../../../../fields/Doc'; import { DataVizBox } from '../../DataVizBox'; import { TemplateFieldType } from '../TemplateBackend'; -import { TemplateMenuGPTManager } from './TemplateMenuGPTManager'; +import { TemplateMenuAIUtils } from './TemplateMenuAIUtils'; export type Conditional = { field: string; @@ -28,7 +28,7 @@ export class TemplateManager { } initializeTemplates = (templateSettings: FieldSettings[]) => templateSettings.map(settings => { - return new Template(settings, this.fieldConditions)}); + return new Template(settings)}); getValidTemplates = (cols: Col[]) => this.templates.filter(template => template.isValidTemplate(cols)); @@ -53,7 +53,7 @@ export class TemplateManager { } } - createDocsFromTemplate = action((dv: DataVizBox, template: Template, csvColumns: Col[], GPTManager: TemplateMenuGPTManager, debug: boolean = false) => { + createDocsFromTemplate = action((dv: DataVizBox, template: Template, csvColumns: Col[], debug: boolean = false) => { const fields = Array.from(Object.keys(dv.records[0])); const processContent = (content: { [title: string]: string }) => { @@ -66,7 +66,7 @@ export class TemplateManager { field && field.setContent(content[title], field.viewType); }); - const gptFunc = (type: TemplateFieldType) => (type === TemplateFieldType.VISUAL ? GPTManager.renderGPTImageCall : GPTManager.renderGPTTextCall); + const gptFunc = (type: TemplateFieldType) => (type === TemplateFieldType.VISUAL ? TemplateMenuAIUtils.renderGPTImageCall : TemplateMenuAIUtils.renderGPTTextCall); const gptPromises = csvColumns .filter(field => field.type !== TemplateFieldType.UNSET && field.AIGenerated) .map(field => { @@ -76,7 +76,7 @@ export class TemplateManager { } }); - return Promise.all(gptPromises) + return templateCopy.getRenderedDoc(); }; const rowContents = debug diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts new file mode 100644 index 000000000..446fe3442 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts @@ -0,0 +1,129 @@ +import { action } from "mobx"; +import { Upload } from "openai/resources"; +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 { Doc } from "../../../../../../fields/Doc"; +import { DrawingFillHandler } from "../../../../smartdraw/DrawingFillHandler"; +import { CollectionFreeFormView } from "../../../../collections/collectionFreeForm"; + +export class TemplateMenuAIUtils { + + public static generateGPTImage = async (prompt: string): Promise<string | undefined> => { + try { + const res = await gptImageCall(prompt); + + if (res) { + const result = (await Networking.PostToServer('/uploadRemoteImage', { sources: res })) as Upload.FileInformation[]; + const source = ClientUtils.prepend(result[0].accessPaths.agnostic.client); + return source; + } + } catch (e) { + console.log(e); + } + }; + + public static renderGPTImageCall = async (template: Template, col: Col, fieldNumber: number | undefined): Promise<boolean> => { + const generateAndLoadImage = async (fieldNum: string, column: Col, prompt: string) => { + const url = await this.generateGPTImage(prompt); + const field: TemplateField = template.getFieldByID(Number(fieldNum)); + + field.setContent(url ?? '', ViewType.IMG); + field.setTitle(col.title); + }; + + const fieldContent: string = template.compiledContent; + + try { + const sysPrompt = + 'Your job is to create a prompt for an AI image generator to help it generate an image based on existing content in a template and a user prompt. Your prompt should focus heavily on visual elements to help the image generator; avoid unecessary info that might distract it. ONLY INCLUDE THE PROMPT, NO OTHER TEXT OR EXPLANATION. The existing content is as follows: ' + + fieldContent + + ' **** The user prompt is: ' + + col.desc; + + const prompt = await gptAPICall(sysPrompt, GPTCallType.COMPLETEPROMPT); + + await generateAndLoadImage(String(fieldNumber), col, prompt); + } catch (e) { + console.log(e); + } + return true; + }; + + public static renderGPTTextCall = async (template: Template, col: Col, fieldNum: number | undefined): Promise<boolean> => { + const wordLimit = (size: TemplateFieldSize) => { + switch (size) { + case TemplateFieldSize.TINY: + return 2; + case TemplateFieldSize.SMALL: + return 5; + case TemplateFieldSize.MEDIUM: + return 20; + case TemplateFieldSize.LARGE: + return 50; + case TemplateFieldSize.HUGE: + return 100; + default: + return 10; + } + }; + + const textAssignment = `--- title: ${col.title}, prompt: ${col.desc}, word limit: ${wordLimit(col.sizes[0])} words, assigned field: ${fieldNum} ---`; + + const fieldContent: string = template.compiledContent; + + try { + const prompt = fieldContent + textAssignment; + + const res = await gptAPICall(`${Math.random() * 100000}: ${prompt}`, GPTCallType.FILL); + + 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)); + + field.setContent(info.content ?? '', ViewType.TEXT); + field.setTitle(col.title); + }); + } + } catch (err) { + console.log(err); + } + + return true; + }; + + /** + * Populates a preset template framework with content from a datavizbox or any AI-generated content. + * @param template the preloaded template framework being filled in + * @param assignments a list of template field numbers (from top to bottom) and their assigned columns from the linked dataviz + * @returns a doc containing the fully rendered template + */ + public static applyGPTContentToTemplate = async (template: Template, assignments: { [field: string]: Col }): Promise<Template | undefined> => { + const GPTTextCalls = Object.entries(assignments).filter(([, col]) => col.type === TemplateFieldType.TEXT && !col.AIGenerated); + const GPTIMGCalls = Object.entries(assignments).filter(([, col]) => col.type === TemplateFieldType.VISUAL && !col.AIGenerated); + + if (GPTTextCalls.length) { + const promises = GPTTextCalls.map(([str, col]) => { + return TemplateMenuAIUtils.renderGPTTextCall(template, col, Number(str)); + }); + + await Promise.all(promises); + } + + if (GPTIMGCalls.length) { + const promises = GPTIMGCalls.map(async ([fieldNum, col]) => { + return TemplateMenuAIUtils.renderGPTImageCall(template, col, Number(fieldNum)); + }); + + await Promise.all(promises); + } + + return template; + }; + +}
\ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuFireflyManager.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuFireflyManager.ts deleted file mode 100644 index e69de29bb..000000000 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuFireflyManager.ts +++ /dev/null diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuGPTManager.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuGPTManager.ts deleted file mode 100644 index e69de29bb..000000000 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuGPTManager.ts +++ /dev/null diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx index ed2e20843..48fea91e2 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx @@ -34,8 +34,9 @@ import { TemplateManager } from './Backend/TemplateManager'; import { DrawingFillHandler } from '../../../smartdraw/DrawingFillHandler'; import { CgPathIntersect } from 'react-icons/cg'; import { StaticContentField } from './TemplateFieldTypes/StaticContentField'; -import { SuggestedTemplatesWindow } from './Menu/SuggestedTemplatesWindow'; -import { TemplateMenuGPTManager } from './Backend/TemplateMenuGPTManager'; +import { TemplateMenuAIUtils } from './Backend/TemplateMenuAIUtils' +import { TemplateSidescrollView } from './Menu/TemplatesSidescrollDisplay'; +import { TemplateEditingWindow } from './Menu/TemplateEditingWindow'; export enum LayoutType { FREEFORM = 'Freeform', @@ -91,7 +92,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> private _disposers: { [name: string]: IDisposer } = {}; private _ref: HTMLDivElement | null = null; private templateManager: TemplateManager; - private GPTManager: TemplateMenuGPTManager; @observable _fullyRenderedDocs: Doc[] = []; // collection of templates filled in with content @observable _renderedDocCollection: Doc | undefined = undefined; // fullyRenderedDocs in a parent collection @@ -102,27 +102,16 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> @observable _currEditingTemplate: Template | undefined = undefined; @observable _userCreatedFields: Col[] = []; - @observable _selectedCols: { title: string; type: string; desc: string }[] | undefined = []; @observable _collapsedCols: String[] = []; //any columns whose options panels are hidden @observable _conditions: Conditional[] = []; @observable _currEditingConditional: Conditional = {} as Conditional; @observable _layout: { type: LayoutType; yMargin: number; xMargin: number; columns?: number; repeat: number } = { type: LayoutType.FREEFORM, yMargin: 10, xMargin: 10, columns: 3, repeat: 0 }; @observable _savedLayouts: DataVizTemplateLayout[] = []; - @observable _expandedPreview: Doc | undefined = undefined; - @observable _variationsTab: boolean = false; - @observable _numVarsToGenerate: number = 3; @observable _loadingVariants: boolean = false; - @observable _currentVariations: Doc[] = []; - @observable _variationPrompt: string = 'Use this template to generate an empty baseball card template.'; - _previewWindow: HTMLDivElement | null = null; @observable _suggestedTemplates: Template[] = []; - @observable _suggestedTemplatePreviews: { doc: Doc; template: Template }[] = []; - @observable _GPTOpt: boolean = false; - @observable _callCount: number = 0; @observable _GPTLoading: boolean = false; - @observable _DOCCC: Doc | undefined; @observable _pageX: number = 0; @observable _pageY: number = 0; @@ -133,9 +122,8 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> @observable _startPos?: { x: number; y: number }; @observable _shouldDisplay: boolean = false; - @observable _menuContent: 'templates' | 'options' | 'saved' | 'dashboard' = 'templates'; + @observable _menuContent: 'templates' | 'options' | 'saved' | 'dashboard' | 'templateEditing' = 'templates'; @observable _dragging: boolean = false; - @observable _draggingIndicator: boolean = false; @observable _dataViz?: DataVizBox; @observable _interactionLock: boolean | undefined; @observable _snapPt: { x: number; y: number } = { x: 0, y: 0 }; @@ -145,20 +133,14 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> @observable _resizeUndo: UndoManager.Batch | undefined = undefined; @observable _initDimensions: { width: number; height: number; x?: number; y?: number } = { width: 300, height: 400, x: undefined, y: undefined }; @observable _menuDimensions: { width: number; height: number } = { width: 400, height: 400 }; - @observable _editing: boolean = false; constructor(props: DocCreateMenuProps) { super(props); makeObservable(this); DocCreatorMenu.Instance = this; this.templateManager = new TemplateManager(TemplateLayouts.allTemplates); - this.GPTManager = new TemplateMenuGPTManager(); } - setContainerRef: React.LegacyRef<HTMLDivElement> = (node) => { - this._previewWindow = node; - }; - @action setDataViz = (dataViz: DataVizBox) => { this._dataViz = dataViz; this._selectedTemplate = undefined; @@ -392,7 +374,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> } @action updateRenderedPreviewCollection = async (template: Template) => { - this._fullyRenderedDocs = this._dataViz ? ((await this.createDocsFromTemplate(this._dataViz, template)).filter(doc => doc).map(doc => doc!) ?? []) : []; + this._fullyRenderedDocs = this._dataViz ? ((await this.templateManager.createDocsFromTemplate(this._dataViz, template, this.fieldsInfos, this.DEBUG_MODE)).filter(doc => doc).map(doc => doc!) ?? []) as unknown as Doc[] : []; this.updateRenderedDocCollection(); }; @@ -517,19 +499,88 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> this.forceUpdate(); }; - generatePresetTemplates = async (debug: boolean) => { + 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}} `)); + + return descriptions; + }; + + getColByTitle = (title: string) => { + return 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 inputText = fieldDescriptions.concat(colDescriptions); + + const prompt: string = `(${Math.random() * 100000}) ${inputText}`; + + this._GPTLoading = true; + + try { + const res = await gptAPICall(prompt, GPTCallType.TEMPLATE); + + if (res) { + const assignments: { [templateTitle: string]: { [fieldID: string]: string } } = JSON.parse(res); + const brokenDownAssignments: [Template, { [fieldID: number]: Col }][] = []; + + 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 (!this._userCreatedFields.includes(col)) { + // do the following for any fields not added by the user; will change in the future, for now only GPT content works with user-added field + 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)] = this.getColByTitle(colTitle); + } + return a; + }, + {} as { [field: number]: Col } + ); + brokenDownAssignments.push([template, toObj]); + }); + + return brokenDownAssignments; + } + } catch (err) { + console.error(err); + } + + return []; + }; + + generatePresetTemplates = async () => { const templates: Template[] = []; - if (debug) { + if (this.DEBUG_MODE) { templates.push(...this.templateManager.templates); } else { this._dataViz?.updateColDefaults(); templates.push(...this.templateManager.getValidTemplates(this.fieldsInfos)); - const assignments = await this.GPTManager.assignColsToFields(templates, this.fieldsInfos); + const assignments = await this.assignColsToFields(templates, this.fieldsInfos); - const renderedTemplatePromises = assignments.map(([template, assgns]) => this.GPTManager.applyGPTContentToTemplate(template, assgns)); + const renderedTemplatePromises = assignments.map(([template, assgns]) => TemplateMenuAIUtils.applyGPTContentToTemplate(template, assgns)); await Promise.all(renderedTemplatePromises); } @@ -542,16 +593,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> ); }; - @action setVariationTab = (open: boolean) => { - this._variationsTab = 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); - } - } - - generateVariations = async (onDoc: Doc): Promise<Doc[]> => { + generateVariations = async (onDoc: Doc, prompt: string): Promise<Doc[]> => { this._loadingVariants = true; this.variationDocss = []; const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView; @@ -561,7 +603,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> clone.x = 10000; clone.y = 10000; - await DrawingFillHandler.drawingToImage(clone, 100, this._variationPrompt, undefined, this) + await DrawingFillHandler.drawingToImage(clone, 100, prompt, undefined, this) this._loadingVariants = false; @@ -591,130 +633,15 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> @action setExpandedView = (template: Template | undefined) => { this._currEditingTemplate = template; - this._expandedPreview = template?.doc; //Docs.Create.FreeformDocument([doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: ''}); - }; - - @action setVariationPrompt = (prompt: string) => { - this._variationPrompt = prompt; - } - - get editingWindow() { - const rendered = !this._expandedPreview ? null : ( - <> - <div className="docCreatorMenu-expanded-template-preview" ref={this.setContainerRef}> - { this._previewWindow ? <DocumentView - Document={this._expandedPreview} - isContentActive={emptyFunction} - addDocument={returnFalse} - moveDocument={returnFalse} - removeDocument={returnFalse} - PanelWidth={() => this._previewWindow?.clientWidth ?? 1000 - 10} - PanelHeight={() => this._previewWindow?.clientHeight ?? 1000 - 60} - ScreenToLocalTransform={() => new Transform(-this._pageX - 5, -this._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> - { this._variationsTab ? - <div className="docCreatorMenu-section"> - <div className="docCreatorMenu-section-topbar"> - <div className="docCreatorMenu-section-title" style={{color: 'white'}}>Variations</div> - <button className="docCreatorMenu-menu-button section-reveal-options" onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => (this._menuContent = 'dashboard')))}> - <FontAwesomeIcon icon="gear" /> - </button> - </div> - <div className="docCreatorMenu-templates-preview-window"> - {this._currentVariations.map(variant => - <div className="docCreatorMenu-preview-window"> - {this.docPreview(variant)} - </div> - )} - </div> - <div className="docCreatorMenu-variation-prompt-input"> - <textarea - className="docCreatorMenu-variation-prompt-input-textbox" - onChange={e => this.setVariationPrompt(e.target.value)} - defaultValue={''} - placeholder={'Enter a custom prompt here (optional)'} - /> - <button className="docCreatorMenu-menu-button" - onPointerDown={e => this.setUpButtonClick(e, async () => { - this._currentVariations = await this.generateVariations(this._currEditingTemplate?.getRenderedDoc()!); - }) - }> - <FontAwesomeIcon icon="arrows-rotate" /> - </button> - </div> - </div> - : null - } - </> - ); - return ( - <div className="docCreatorMenu-expanded-template-preview"> - <div className="top-panel" /> - {rendered} - <div className="right-buttons-panel"> - <button - className="docCreatorMenu-menu-button section-reveal-options top-right" - onPointerDown={e => - this.setUpButtonClick(e, () => { - if (!this._currEditingTemplate) return; - if (this._currEditingTemplate === this._selectedTemplate) { - this.updateRenderedPreviewCollection(this._currEditingTemplate); - } - this.setExpandedView(undefined); - }) - }> - <FontAwesomeIcon icon="minimize" /> - </button> - <button className="docCreatorMenu-menu-button section-reveal-options top-right" onPointerDown={e => this.setUpButtonClick(e, async () => { - if (!this._currEditingTemplate) return; - this.setVariationTab(!this._variationsTab); - })}> - <FontAwesomeIcon icon="lightbulb" /> - </button> - </div> - </div> - ); - } + if (template) { + this._menuContent = 'templateEditing'; + } else { + this._menuContent = 'templates'; + } - docPreview = (doc: Doc | undefined) => - !doc ? null : ( - <DocumentView - Document={doc} - isContentActive={emptyFunction} // !!! should be return false - addDocument={returnFalse} - moveDocument={returnFalse} - removeDocument={returnFalse} - PanelWidth={() => this._menuDimensions.height * .3} - PanelHeight={() => this._menuDimensions.height * .3} - ScreenToLocalTransform={() => new Transform(-this._pageX - 5, -this._pageY - 35, 1)} - renderDepth={1} - whenChildContentsActiveChanged={emptyFunction} - focus={emptyFunction} - styleProvider={DefaultStyleProvider} - addDocTab={this._props.addDocTab} - pinToPres={() => undefined} - childFilters={returnEmptyFilter} - childFiltersByRanges={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - fitContentsToBox={returnFalse} - fitWidth={returnFalse} - hideDecorations={true} - /> - ); + //Docs.Create.FreeformDocument([doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: ''}); + }; @action updateXMargin = (input: string) => { this._layout.xMargin = Number(input); @@ -1137,7 +1064,19 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> get renderSelectedViewType() { switch (this._menuContent) { - case 'templates': return <SuggestedTemplatesWindow menu={this} setupButtonClick={this.setUpButtonClick}/>; + case 'templates': + return <TemplateSidescrollView + title={'Suggested Templates'} + menu={this} + setupButtonClick={this.setUpButtonClick} + templates={this._suggestedTemplates} + /> + case 'templateEditing': + return <TemplateEditingWindow + setupButtonClick={this.setUpButtonClick} + template={this._currEditingTemplate as Template} + menu={this} + /> case 'options': return this.optionsMenuContents; case 'dashboard': return this.dashboardContents; } // prettier-ignore diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx new file mode 100644 index 000000000..fe7822087 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx @@ -0,0 +1,134 @@ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, observable, 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 { TemplateSidescrollView } from "./TemplatesSidescrollDisplay"; +import { observer } from "mobx-react"; +import { Transform } from "../../../../../util/Transform"; +import { Template } from "../Template"; +import { TemplateMenuAIUtils } from "../Backend/TemplateMenuAIUtils"; +import { ObservableReactComponent } from "../../../../ObservableReactComponent"; + +interface TemplateEditingWindowProps { + menu: DocCreatorMenu; + template: Template; + setupButtonClick: (e: React.PointerEvent, func: () => void) => void; +} + +@observer +export class TemplateEditingWindow extends ObservableReactComponent<TemplateEditingWindowProps> { + + private fireflyPrompt: string = 'Use this template to generate an empty baseball card template.'; + private previewWindow: HTMLDivElement | null = null; + + @observable _variationsTabOpen: boolean = false; + @observable _variations: Doc[] = []; + + 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); + } + } + + get fireflyVariationsTab() { + + return ( + <> + <TemplateSidescrollView + menu={this._props.menu} + title={'Generate Variations'} + templates={[]} + setupButtonClick={this._props.setupButtonClick} + /> + <div className="docCreatorMenu-section"> + <div className="docCreatorMenu-variation-prompt-input"> + <textarea + className="docCreatorMenu-variation-prompt-input-textbox" + onChange={e => this.fireflyPrompt = e.target.value} + defaultValue={''} + placeholder={'Enter a custom prompt here (optional)'} + /> + <button className="docCreatorMenu-menu-button" + onPointerDown={e => this._props.setupButtonClick(e, async () => { + this._variations = await this._props.menu.generateVariations(this._props.template.getRenderedDoc()!, this.fireflyPrompt); + }) + }> + <FontAwesomeIcon icon="arrows-rotate" /> + </button> + </div> + </div> + </> + ) + } + + get renderedDocPreview(){ + const doc: Doc = this._props.template.getRenderedDoc() as unknown as Doc; + + return ( + <div className="docCreatorMenu-expanded-template-preview" ref={this.setContainerRef}> + <DocumentView + Document={doc} + isContentActive={emptyFunction} + addDocument={returnFalse} + moveDocument={returnFalse} + removeDocument={returnFalse} + PanelWidth={() => this.previewWindow?.clientWidth ?? 1000 - 10} + PanelHeight={() => this.previewWindow?.clientHeight ?? 1000 - 60} + 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} + /> + </div> + ) + } + + render() { + return ( + <div className="docCreatorMenu-expanded-template-preview"> + <div className="top-panel" /> + {this.renderedDocPreview} + {this.fireflyVariationsTab} + <div className="right-buttons-panel"> + <button + className="docCreatorMenu-menu-button section-reveal-options top-right" + onPointerDown={e => + this._props.setupButtonClick(e, () => { + if (this._props.template === this._props.menu._selectedTemplate) { + this._props.menu.updateRenderedPreviewCollection(this._props.template); + } + this._props.menu.setExpandedView(undefined); + }) + }> + <FontAwesomeIcon icon="minimize" /> + </button> + <button className="docCreatorMenu-menu-button section-reveal-options top-right" onPointerDown={e => this._props.setupButtonClick(e, async () => { + this.setVariationTab(!this._variationsTabOpen); + })}> + <FontAwesomeIcon icon="lightbulb" /> + </button> + </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 e40192fa8..4759a4158 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx @@ -6,6 +6,12 @@ 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 { returnEmptyDoclist } from "../../../../../../fields/Doc"; export interface TemplatePreviewBoxProps { template: Template; @@ -27,7 +33,7 @@ export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreview 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, () => runInAction(() => this.props.menu._selectedTemplate = template))}> + onPointerDown={e => this.props.menu.setUpButtonClick(e, () => runInAction(() => {console.log('setting'); this.props.menu._selectedTemplate = template}))}> { this.props.leftButtonOpts ? <button className="option-button left" @@ -41,7 +47,28 @@ export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreview <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 } - {this.props.menu.docPreview(template.getRenderedDoc())} + <DocumentView + Document={this.props.template.getRenderedDoc()!} //!!! bad + isContentActive={emptyFunction} // !!! should be return false + addDocument={returnFalse} + moveDocument={returnFalse} + removeDocument={returnFalse} + PanelWidth={() => this.props.menu._menuDimensions.height * .3} + PanelHeight={() => 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} + /> </div> ) } diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/SuggestedTemplatesWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatesSidescrollDisplay.tsx index 517a998d9..5035227d1 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/SuggestedTemplatesWindow.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatesSidescrollDisplay.tsx @@ -13,20 +13,17 @@ import { TemplatePreviewBox } from "./TemplatePreviewBox"; export interface SuggestedTemplatesProps { menu: DocCreatorMenu; + templates: Template[]; + previewBoxLeftButtonOpts?: [string, () => any]; + previewBoxRightButtonOpts?: [string, () => any]; setupButtonClick: (e: React.PointerEvent, func: () => void) => void; } @observer -export class SuggestedTemplatesWindow extends ObservableReactComponent<SuggestedTemplatesProps> { +export class TemplateSidescrollView extends ObservableReactComponent<SuggestedTemplatesProps> { @observable _GPTLoading: boolean = false; - @observable _suggestedTemplates: Template[] = []; - @observable _userTemplates: Template[] = []; - - @action addUserTemplate = (template: Template) => { this._userTemplates.push(template) }; - @action removeUserTemplate = (template: Template) => { this._userTemplates.splice(this._userTemplates.indexOf(template), 1) }; - render() { return ( <div className='docCreatorMenu-templates-view'> @@ -39,12 +36,12 @@ export class SuggestedTemplatesWindow extends ObservableReactComponent<Suggested </button> </div> <div className="docCreatorMenu-templates-preview-window" style={{ justifyContent: this.props.menu._menuDimensions.width > 400 ? 'center' : '' }}> - {this._suggestedTemplates.map(template => ( + {this.props.templates.map(template => ( <TemplatePreviewBox template={template} menu={this.props.menu} leftButtonOpts={["magnifying-glass", (template: Template) => { this.props.menu.setExpandedView(template); this.forceUpdate(); }]} - rightButtonOpts={["plus", (template: Template) => this.addUserTemplate(template)]} + rightButtonOpts={["plus", (template: Template) => {}]} /> ))} </div> @@ -56,24 +53,6 @@ export class SuggestedTemplatesWindow extends ObservableReactComponent<Suggested </div> </div> </div> - <hr className="docCreatorMenu-option-divider full no-margin" /> - <div className="docCreatorMenu-section"> - <div className="docCreatorMenu-section-topbar"> - <div className="docCreatorMenu-section-title">Your Templates</div> - <button className="docCreatorMenu-menu-button section-reveal-options"> - <FontAwesomeIcon icon="gear" /> - </button> - </div> - <div className="docCreatorMenu-templates-preview-window" style={{ justifyContent: this.props.menu._menuDimensions.width > 400 ? 'center' : '' }}> - {this._userTemplates.map(template => ( - <TemplatePreviewBox - template={template} - menu={this.props.menu} - - /> - ))} - </div> - </div> </div> </div> ); diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts index 1a4384bc1..2bf9f8de5 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts @@ -8,16 +8,15 @@ import { Conditional } from './Backend/TemplateManager'; export class Template { _mainField: DynamicField; - private conditionalLogic: Record<string, Conditional[]>; + private conditionalLogic: Record<string, Conditional[]> = {}; /** * A Template can be created from a description of its fields (FieldSettings) or from a DynamicField * @param definition definition of template as settings or DynamicField */ - constructor(definition: FieldSettings | DynamicField, conditionalLogic: Record<string, Conditional[]>) { + constructor(definition: FieldSettings | DynamicField) { makeAutoObservable(this); this._mainField = definition instanceof DynamicField ? definition : this.setupMainField(definition); - this.conditionalLogic = conditionalLogic; } get childFields(): TemplateField[] { @@ -56,7 +55,7 @@ export class Template { //dispose each subfields disposers, etc. }; - cloneBase = () => new Template(this._mainField?.makeClone(undefined) ?? TemplateLayouts.BasicSettings, this.conditionalLogic); + cloneBase = () => new Template(this._mainField?.makeClone(undefined) ?? TemplateLayouts.BasicSettings); getRenderedDoc = () => this.doc; @@ -103,6 +102,14 @@ export class Template { fields.forEach(this.applyConditionalLogicToField); } + addConditionalStatement = (field: string, statement: Conditional) => { + !this.conditionalLogic[field] ? this.conditionalLogic[field] = [statement] : this.conditionalLogic[field].push(statement); + } + + removeConditionalStatement = (field: string, statement: Conditional) => { + this.conditionalLogic[field] = this.conditionalLogic[field]?.filter(cond => cond !== statement); + } + getMatches = (cols: Col[]): number[][] => { const numFields = this.contentFields.length; diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts index 0ad1accc1..b4ba3fe7d 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts @@ -18,7 +18,7 @@ export abstract class TemplateField { * @param sameId - * @returns TemplateField */ - static CreateField: (settings: FieldSettings, index: number, parent: TemplateField | undefined, template: Template, sameId?: boolean) => TemplateField; + static CreateField: (settings: FieldSettings, index: number, parent: TemplateField | undefined, sameId?: boolean) => TemplateField; protected _parent?: TemplateField; protected _id: number; @@ -26,15 +26,13 @@ export abstract class TemplateField { protected _settings: FieldSettings; protected _renderDoc: Doc | undefined; protected _dimensions: FieldDimensions | undefined; - public template: Template; - constructor(settings: FieldSettings, id: number = 1, template: Template, parent?: TemplateField) { + constructor(settings: FieldSettings, id: number = 1, parent?: TemplateField) { this._id = id; this._parent = parent; this._settings = settings; this._title = settings.title ?? ''; this._dimensions = this.getLocalDimensions(this._settings, this._parent?.getDimensions); - this.template = template; this.applyBasicOpts(this._dimensions, settings); return this; } @@ -75,7 +73,7 @@ export abstract class TemplateField { makeClone(parent?: TemplateField) { const settings: FieldSettings = structuredClone(this._settings); - const cloned = TemplateField.CreateField(settings, this._id, parent, this.template, true); // create a value for this.Document/subfields that we want to ignore + const cloned = TemplateField.CreateField(settings, this._id, parent, true); // create a value for this.Document/subfields that we want to ignore this._renderDoc && Doc.MakeClone(this._renderDoc).then(({ clone }) => (cloned._renderDoc = clone)); cloned._title = this._title; cloned._dimensions = this._dimensions; @@ -89,7 +87,7 @@ export abstract class TemplateField { changeFieldType = (newType: ViewType): TemplateField => { const newSettings = this._settings; newSettings.viewType = newType; - const newField = TemplateField.CreateField(newSettings, this._id, this._parent, this.template, true); + const newField = TemplateField.CreateField(newSettings, this._id, this._parent, true); this._parent?.exchangeFields(newField, this); return newField; }; @@ -137,13 +135,13 @@ export type FieldSettings = { tl: [number, number]; br: [number, number]; opts: DocumentOptions; - viewType: ViewType; - title?: string; - subfields?: FieldSettings[]; types?: TemplateFieldType[]; sizes?: TemplateFieldSize[]; - description?: string; + title?: string; + viewType: ViewType; template?: Template; + subfields?: FieldSettings[]; + description?: string; }; export enum ViewType { @@ -161,6 +159,3 @@ export type FieldDimensions = { coord: { x: number; y: number }; }; -export type FieldTree = { - node: { field: TemplateField; subfields: FieldTree[] }; -}; |