aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts99
-rw-r--r--src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuFireflyManager.ts0
-rw-r--r--src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuGPTManager.ts0
-rw-r--r--src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx434
-rw-r--r--src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/ExpandedTemplatePreview.tsx0
-rw-r--r--src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/FieldOptionsScreen.tsx0
-rw-r--r--src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/SuggestedTemplatesWindow.tsx81
-rw-r--r--src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingScreen.tsx0
-rw-r--r--src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx48
-rw-r--r--src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts40
-rw-r--r--src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts5
-rw-r--r--src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts13
-rw-r--r--src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateManager.ts24
13 files changed, 307 insertions, 437 deletions
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts
new file mode 100644
index 000000000..6d63078a8
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts
@@ -0,0 +1,99 @@
+import { action, makeAutoObservable } from 'mobx';
+import { Col } from '../DocCreatorMenu';
+import { FieldSettings } from '../TemplateFieldTypes/TemplateField';
+import { Template } from '../Template';
+import { NumListCast } from '../../../../../../fields/Doc';
+import { DataVizBox } from '../../DataVizBox';
+import { TemplateFieldType } from '../TemplateBackend';
+import { TemplateMenuGPTManager } from './TemplateMenuGPTManager';
+
+export type Conditional = {
+ field: string;
+ operator: '=' | '>' | '<';
+ condition: string;
+ target: 'self' | 'template';
+ attribute: string;
+ value: string;
+}
+
+export class TemplateManager {
+
+ templates: Template[] = [];
+
+ fieldConditions: Record<string, Conditional[]> = {};
+
+ constructor(templateSettings: FieldSettings[]) {
+ makeAutoObservable(this);
+ this.templates = this.initializeTemplates(templateSettings);
+ }
+
+ initializeTemplates = (templateSettings: FieldSettings[]) => templateSettings.map(settings => {
+ return new Template(settings, this.fieldConditions)});
+
+ getValidTemplates = (cols: Col[]) => this.templates.filter(template => template.isValidTemplate(cols));
+
+ addTemplate = (newTemplate: Template) => this.templates.push(newTemplate);
+
+ removeTemplate = (template: Template) => {
+ this.templates.splice(this.templates.indexOf(template), 1);
+ template.cleanup();
+ };
+
+ addFieldCondition = (fieldTitle: string, condition: Conditional) => {
+ if (this.fieldConditions[fieldTitle] === undefined) {
+ this.fieldConditions[fieldTitle] = [condition];
+ } else {
+ this.fieldConditions[fieldTitle].push(condition);
+ }
+ }
+
+ removeFieldCondition = (fieldTitle: string, condition: Conditional) => {
+ if (this.fieldConditions[fieldTitle]) {
+ this.fieldConditions[fieldTitle] = this.fieldConditions[fieldTitle].filter(cond => cond !== condition);
+ }
+ }
+
+ createDocsFromTemplate = action((dv: DataVizBox, template: Template, csvColumns: Col[], GPTManager: TemplateMenuGPTManager, debug: boolean = false) => {
+ const fields = Array.from(Object.keys(dv.records[0]));
+
+ const processContent = (content: { [title: string]: string }) => {
+ const templateCopy = template.cloneBase();
+
+ fields
+ .filter(title => title)
+ .forEach(title => {
+ const field = templateCopy.getFieldByTitle(title);
+ field && field.setContent(content[title], field.viewType);
+ });
+
+ const gptFunc = (type: TemplateFieldType) => (type === TemplateFieldType.VISUAL ? GPTManager.renderGPTImageCall : GPTManager.renderGPTTextCall);
+ const gptPromises = csvColumns
+ .filter(field => field.type !== TemplateFieldType.UNSET && field.AIGenerated)
+ .map(field => {
+ const templateField = templateCopy.getFieldByTitle(field.title);
+ if (templateField !== undefined) {
+ return gptFunc(field.type)(templateCopy, field, templateField.getID);
+ }
+ });
+
+ return Promise.all(gptPromises)
+ };
+
+ const rowContents = debug
+ ? [{}, {}, {}, {}]
+ : NumListCast(dv.layoutDoc.dataViz_selectedRows).map(row =>
+ fields.reduce(
+ (values, col) => {
+ values[col] = dv.records[row][col];
+ return values;
+ },
+ {} as { [title: string]: string }
+ )
+ );
+ return Promise.all(rowContents.map(processContent)).then(
+ action(renderedDocs => {
+ return renderedDocs;
+ })
+ );
+ });
+}
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuFireflyManager.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuFireflyManager.ts
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuFireflyManager.ts
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuGPTManager.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuGPTManager.ts
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuGPTManager.ts
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
index de345a335..ed2e20843 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
@@ -30,10 +30,12 @@ import './DocCreatorMenu.scss';
import { TemplateField, ViewType } from './TemplateFieldTypes/TemplateField';
import { Template } from './Template';
import { TemplateFieldSize, TemplateFieldType, TemplateLayouts } from './TemplateBackend';
-import { TemplateManager } from './TemplateManager';
+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';
export enum LayoutType {
FREEFORM = 'Freeform',
@@ -64,6 +66,7 @@ export type Col = {
title: string;
type: TemplateFieldType;
defaultContent?: string;
+ AIGenerated?: boolean;
};
export type Conditional = {
@@ -84,10 +87,11 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
// eslint-disable-next-line no-use-before-define
static Instance: DocCreatorMenu;
- private DEBUG_MODE: boolean = false;
+ private DEBUG_MODE: boolean = true;
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
@@ -148,6 +152,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
makeObservable(this);
DocCreatorMenu.Instance = this;
this.templateManager = new TemplateManager(TemplateLayouts.allTemplates);
+ this.GPTManager = new TemplateMenuGPTManager();
}
setContainerRef: React.LegacyRef<HTMLDivElement> = (node) => {
@@ -162,12 +167,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
this._suggestedTemplates = [];
this._userCreatedFields = [];
};
- @action addUserTemplate = (template: Template) => {
- this._userTemplates.push(template);
- };
- @action removeUserTemplate = (template: Template) => {
- this._userTemplates.splice(this._userTemplates.indexOf(template), 1);
- };
@action setSuggestedTemplates = (templates: Template[]) => {
this._suggestedTemplates = templates; //prettier-ignore
};
@@ -238,7 +237,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
return bounds;
}
- setUpButtonClick = (e: React.PointerEvent, func: () => void) => {
+ setUpButtonClick = (e: React.PointerEvent, func: (...args: any) => void) => {
setupMoveUpEvents(
this,
e,
@@ -518,6 +517,31 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
this.forceUpdate();
};
+ generatePresetTemplates = async (debug: boolean) => {
+ const templates: Template[] = [];
+
+ if (debug) {
+ 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 renderedTemplatePromises = assignments.map(([template, assgns]) => this.GPTManager.applyGPTContentToTemplate(template, assgns));
+
+ await Promise.all(renderedTemplatePromises);
+ }
+
+ setTimeout(
+ action(() => {
+ this.setSuggestedTemplates(templates);
+ this._GPTLoading = false;
+ })
+ );
+ };
+
@action setVariationTab = (open: boolean) => {
this._variationsTab = open;
if (this._previewWindow && open) {
@@ -554,292 +578,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
doc.y = 10000;
}
- 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);
- }
- };
-
- /**
- * 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
- */
- applyGPTContentToTemplate = async (template: Template, assignments: { [field: string]: Col }): Promise<Template | undefined> => {
- const GPTTextCalls = Object.entries(assignments).filter(([, col]) => col.type === TemplateFieldType.TEXT && this._userCreatedFields.includes(col));
- const GPTIMGCalls = Object.entries(assignments).filter(([, col]) => col.type === TemplateFieldType.VISUAL && this._userCreatedFields.includes(col));
-
- if (GPTTextCalls.length) {
- const promises = GPTTextCalls.map(([str, col]) => {
- return this.renderGPTTextCall(template, col, Number(str));
- });
-
- await Promise.all(promises);
- }
-
- if (GPTIMGCalls.length) {
- const promises = GPTIMGCalls.map(async ([fieldNum, col]) => {
- return this.renderGPTImageCall(template, col, Number(fieldNum));
- });
-
- await Promise.all(promises);
- }
-
- return template;
- };
-
- 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);
-
- ++this._callCount;
- const origCount = this._callCount;
-
- const prompt: string = `(${origCount}) ${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);
- this._conditions.filter(c => c.field === field.getTitle()).forEach(conditional => {
- if (field.getContent() === conditional.condition){
- if (conditional.target === 'self'){
- field.renderedDoc![conditional.attribute] = conditional.value;
- (field.settings.opts as any)[conditional.attribute] = conditional.value;
- } else if (conditional.target === 'template'){
- template._mainField!.renderedDoc![conditional.attribute] = conditional.value;
- (template._mainField!.settings.opts as any)[conditional.attribute] = conditional.value;
- }
- }
- })
- } 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 (this.DEBUG_MODE) {
- templates.push(...this.templateManager.templates);
- } else {
- this._dataViz?.updateColDefaults();
-
- templates.push(...this.templateManager.getValidTemplates(this.fieldsInfos));
-
- const assignments = await this.assignColsToFields(templates, this.fieldsInfos);
-
- const renderedTemplatePromises = assignments.map(([template, assgns]) => this.applyGPTContentToTemplate(template, assgns));
-
- await Promise.all(renderedTemplatePromises);
- }
-
- setTimeout(
- action(() => {
- this.setSuggestedTemplates(templates);
- this._GPTLoading = false;
- })
- );
- };
-
- 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;
- };
-
- 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(`${++this._callCount}: ${prompt}`, GPTCallType.FILL);
-
- // console.log('prompt: ', prompt, ' response: ', res);
-
- if (res) {
- const assignments: { [title: string]: { number: string; content: string } } = JSON.parse(res);
- Object.entries(assignments).forEach(([, /* title */ info]) => {
- const field: TemplateField = template.getFieldByID(Number(info.number));
- // const column = this.getColByTitle(title);
-
- field.setContent(info.content ?? '', ViewType.TEXT);
- field.setTitle(col.title);
- });
- }
- } catch (err) {
- console.log(err);
- }
-
- return true;
- };
-
- createDocsFromTemplate = action((dv: DataVizBox, template: Template) => {
- this._docsRendering = true;
- const fields = Array.from(Object.keys(dv.records[0]));
-
- const processContent = (content: { [title: string]: string }) => {
- const templateCopy = template.cloneBase();
-
- fields
- .filter(title => title)
- .forEach(title => {
- const field = templateCopy.getFieldByTitle(title);
- if (field !== undefined) {
- field.setContent(content[title], field.viewType);
- console.log('content set')
- this._conditions.filter(c => c.field === title).forEach(conditional => {
- console.log('in conditional')
- if (content[title] === conditional.condition){
- if (conditional.target === 'self'){
- field.renderedDoc![conditional.attribute] = conditional.value;
- (field.settings.opts as any)[conditional.attribute] = conditional.value;
- } else if (conditional.target === 'template'){
- console.log('setting', conditional.attribute, 'to: ', conditional.value)
- templateCopy._mainField!.renderedDoc![conditional.attribute] = conditional.value;
- (templateCopy._mainField!.settings.opts as any)[conditional.attribute] = conditional.value;
- }
- }
- })
- }
- });
-
- const gptFunc = (type: TemplateFieldType) => (type === TemplateFieldType.VISUAL ? this.renderGPTImageCall : this.renderGPTTextCall);
- const gptPromises = this._userCreatedFields
- .filter(field => field.type !== TemplateFieldType.UNSET)
- .map(field => {
- const templateField = templateCopy.getFieldByTitle(field.title);
- if (templateField !== undefined) {
- return gptFunc(field.type)(templateCopy, field, templateField.getID);
- }
- });
-
- return Promise.all(gptPromises).then(() => (this._DOCCC = templateCopy._mainField?.renderedDoc));
- };
-
- const rowContents = this.DEBUG_MODE
- ? [{}, {}, {}, {}]
- : NumListCast(dv.layoutDoc.dataViz_selectedRows).map(row =>
- fields.reduce(
- (values, col) => {
- values[col] = dv.records[row][col];
- return values;
- },
- {} as { [title: string]: string }
- )
- );
- return Promise.all(rowContents.map(processContent)).then(
- action(renderedDocs => {
- this._docsRendering = false; // removes loading indicator
- return renderedDocs;
- })
- );
- });
-
addRenderedCollectionToMainview = () => {
const collection = this._renderedDocCollection;
if (collection) {
@@ -978,110 +716,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
/>
);
- get templatesPreviewContents() {
- const GPTOptions = <div></div>;
-
- //<img className='docCreatorMenu-preview-image expanded' src={this._expandedPreview.icon!.url.href.replace(".png", "_o.png")} />
-
- return (
- <div className={`docCreatorMenu-templates-view`}>
- {this._expandedPreview ? (
- this.editingWindow
- ) : (
- <div className="docCreatorMenu-templates-displays">
- <div className="docCreatorMenu-section">
- <div className="docCreatorMenu-section-topbar">
- <div className="docCreatorMenu-section-title">Suggested Templates</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" style={{ justifyContent: this._GPTLoading || this._menuDimensions.width > 400 ? 'center' : '' }}>
- {this._GPTLoading ? (
- <div className="loading-spinner">
- <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
- </div>
- ) : (
- this._suggestedTemplates.map(template => (
- <div
- key={template.title}
- className="docCreatorMenu-preview-window"
- style={{
- border: this._selectedTemplate === template ? `solid 3px ${Colors.MEDIUM_BLUE}` : '',
- boxShadow: this._selectedTemplate === template ? `0 0 15px rgba(68, 118, 247, .8)` : '',
- }}
- onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(template)))}>
- <button
- className="option-button left"
- onPointerDown={e =>
- this.setUpButtonClick(e, () => {
- this.setExpandedView(template);
- this.forceUpdate();
- })
- }>
- <FontAwesomeIcon icon="magnifying-glass" color="white" />
- </button>
- <button className="option-button right" onPointerDown={e => this.setUpButtonClick(e, () => this.addUserTemplate(template))}>
- <FontAwesomeIcon icon="plus" color="white" />
- </button>
- {this.docPreview(template.getRenderedDoc())}
- </div>
- ))
- )}
- </div>
- <div className="docCreatorMenu-GPT-options">
- <div className="docCreatorMenu-GPT-options-container">
- <button className="docCreatorMenu-menu-button" onPointerDown={e => this.setUpButtonClick(e, () => this.generatePresetTemplates())}>
- <FontAwesomeIcon icon="arrows-rotate" />
- </button>
- </div>
- {this._GPTOpt ? GPTOptions : null}
- </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" onPointerDown={e => this.setUpButtonClick(e, () => (this._GPTOpt = !this._GPTOpt))}>
- <FontAwesomeIcon icon="gear" />
- </button>
- </div>
- <div className="docCreatorMenu-templates-preview-window" style={{ justifyContent: this._menuDimensions.width > 400 ? 'center' : '' }}>
- <div className="docCreatorMenu-preview-window empty">
- <FontAwesomeIcon icon="plus" color="rgb(160, 160, 160)" />
- </div>
- {this._userTemplates.map(template => (
- <div
- key={template.toString()}
- className="docCreatorMenu-preview-window"
- style={{
- border: this._selectedTemplate === template ? `solid 3px ${Colors.MEDIUM_BLUE}` : '',
- boxShadow: this._selectedTemplate === template ? `0 0 15px rgba(68, 118, 247, .8)` : '',
- }}
- onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(template)))}>
- <button
- className="option-button left"
- onPointerDown={e =>
- this.setUpButtonClick(e, () => {
- this.setExpandedView(template);
- })
- }>
- <FontAwesomeIcon icon="magnifying-glass" color="white" />
- </button>
- <button className="option-button right" onPointerDown={e => this.setUpButtonClick(e, () => this.removeUserTemplate(template))}>
- <FontAwesomeIcon icon="minus" color="white" />
- </button>
- {this.docPreview(template.getRenderedDoc())}
- </div>
- ))}
- </div>
- </div>
- </div>
- )}
- </div>
- );
- }
-
@action updateXMargin = (input: string) => {
this._layout.xMargin = Number(input);
setTimeout(() => {
@@ -1503,7 +1137,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
get renderSelectedViewType() {
switch (this._menuContent) {
- case 'templates': return this.templatesPreviewContents;
+ case 'templates': return <SuggestedTemplatesWindow menu={this} setupButtonClick={this.setUpButtonClick}/>;
case 'options': return this.optionsMenuContents;
case 'dashboard': return this.dashboardContents;
} // prettier-ignore
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/ExpandedTemplatePreview.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/ExpandedTemplatePreview.tsx
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/ExpandedTemplatePreview.tsx
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/FieldOptionsScreen.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/FieldOptionsScreen.tsx
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/FieldOptionsScreen.tsx
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/SuggestedTemplatesWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/SuggestedTemplatesWindow.tsx
new file mode 100644
index 000000000..517a998d9
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/SuggestedTemplatesWindow.tsx
@@ -0,0 +1,81 @@
+import { Colors } from "@dash/components/src";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, observable, runInAction } from "mobx";
+import React from "react";
+import ReactLoading from "react-loading";
+import { Doc } from "../../../../../../fields/Doc";
+import { StrCast } from "../../../../../../fields/Types";
+import { ObservableReactComponent } from "../../../../ObservableReactComponent";
+import { Template } from "../Template";
+import { observer } from "mobx-react";
+import { DocCreatorMenu } from "../DocCreatorMenu";
+import { TemplatePreviewBox } from "./TemplatePreviewBox";
+
+export interface SuggestedTemplatesProps {
+ menu: DocCreatorMenu;
+ setupButtonClick: (e: React.PointerEvent, func: () => void) => void;
+}
+
+@observer
+export class SuggestedTemplatesWindow 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'>
+ <div className="docCreatorMenu-templates-displays">
+ <div className="docCreatorMenu-section">
+ <div className="docCreatorMenu-section-topbar">
+ <div className="docCreatorMenu-section-title">Suggested Templates</div>
+ <button className="docCreatorMenu-menu-button section-reveal-options" onPointerDown={e => this.props.setupButtonClick(e, () => runInAction(() => (this.props.menu._menuContent = 'dashboard')))}>
+ <FontAwesomeIcon icon="gear" />
+ </button>
+ </div>
+ <div className="docCreatorMenu-templates-preview-window" style={{ justifyContent: this.props.menu._menuDimensions.width > 400 ? 'center' : '' }}>
+ {this._suggestedTemplates.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)]}
+ />
+ ))}
+ </div>
+ <div className="docCreatorMenu-GPT-options">
+ <div className="docCreatorMenu-GPT-options-container">
+ <button className="docCreatorMenu-menu-button" onPointerDown={e => this.props.setupButtonClick(e, () => this.props.menu.generatePresetTemplates())}>
+ <FontAwesomeIcon icon="arrows-rotate" />
+ </button>
+ </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>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingScreen.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingScreen.tsx
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingScreen.tsx
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
new file mode 100644
index 000000000..e40192fa8
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
@@ -0,0 +1,48 @@
+import { Colors } from "@dash/components/src";
+import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
+import { Template } from "../Template";
+import { runInAction } from "mobx";
+import React from "react";
+import { ObservableReactComponent } from "../../../../ObservableReactComponent";
+import { DocCreatorMenu } from "../DocCreatorMenu";
+import { IconProp } from "@fortawesome/fontawesome-svg-core";
+
+export interface TemplatePreviewBoxProps {
+ template: Template;
+ menu: DocCreatorMenu;
+ leftButtonOpts?: [icon: IconProp, func: (template: Template) => void]
+ rightButtonOpts?: [icon: IconProp, func: (template: Template) => void]
+}
+
+export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreviewBoxProps> {
+
+ render() {
+ const template = this.props.template;
+
+ return (
+ <div
+ key={template.title}
+ className="docCreatorMenu-preview-window"
+ 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, () => runInAction(() => this.props.menu._selectedTemplate = template))}>
+ { this.props.leftButtonOpts ?
+ <button
+ className="option-button left"
+ onPointerDown={e =>
+ this.props.menu.setUpButtonClick(e, () => this.props.leftButtonOpts![1](template))
+ }>
+ <FontAwesomeIcon icon={this.props.leftButtonOpts![0]} color="white" />
+ </button> : null
+ }
+ { this.props.rightButtonOpts ?
+ <button className="option-button right" onPointerDown={e => this.props.menu.setUpButtonClick(e, () => this.props.rightButtonOpts![1](template))}>
+ <FontAwesomeIcon icon={this.props.rightButtonOpts![0]} color="white" />
+ </button> : null }
+ {this.props.menu.docPreview(template.getRenderedDoc())}
+ </div>
+ )
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
index 2fe146b1c..1a4384bc1 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
@@ -1,19 +1,23 @@
import { makeAutoObservable } from 'mobx';
import { Col } from './DocCreatorMenu';
-import { TemplateLayouts } from './TemplateBackend';
+import { TemplateFieldType, TemplateLayouts } from './TemplateBackend';
import { DynamicField } from './TemplateFieldTypes/DynamicField';
-import { FieldSettings, TemplateField } from './TemplateFieldTypes/TemplateField';
+import { FieldSettings, TemplateField, ViewType } from './TemplateFieldTypes/TemplateField';
+import { Conditional } from './Backend/TemplateManager';
export class Template {
- _mainField: DynamicField | undefined;
+ _mainField: DynamicField;
+
+ 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) {
+ constructor(definition: FieldSettings | DynamicField, conditionalLogic: Record<string, Conditional[]>) {
makeAutoObservable(this);
this._mainField = definition instanceof DynamicField ? definition : this.setupMainField(definition);
+ this.conditionalLogic = conditionalLogic;
}
get childFields(): TemplateField[] {
@@ -52,7 +56,7 @@ export class Template {
//dispose each subfields disposers, etc.
};
- cloneBase = () => new Template(this._mainField?.makeClone(undefined) ?? TemplateLayouts.BasicSettings);
+ cloneBase = () => new Template(this._mainField?.makeClone(undefined) ?? TemplateLayouts.BasicSettings, this.conditionalLogic);
getRenderedDoc = () => this.doc;
@@ -68,11 +72,37 @@ export class Template {
});
};
+ 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);
+ }
+
isValidTemplate = (cols: Col[]) => {
const maxMatches = this.maxMatches(this.getMatches(cols));
return maxMatches === this.contentFields.length;
};
+ applyConditionalLogicToField = (field: TemplateField) => {
+ if (field instanceof DynamicField) return;
+ const logic: Conditional[] = this.conditionalLogic[field.getTitle()];
+ const content = field.getContent()
+ logic && logic.forEach(statement => {
+ if (content === statement.condition) {
+ if (statement.target === 'template') {
+ this._mainField.renderedDoc![statement.attribute] = statement.value;
+ } else {
+ field.renderedDoc![statement.attribute] = statement.value;
+ }
+ }
+ })
+ }
+
+ applyConditionalLogic = () => {
+ const fields: TemplateField[] = [this._mainField, ...this.allFields];
+ fields.forEach(this.applyConditionalLogicToField);
+ }
+
getMatches = (cols: Col[]): number[][] => {
const numFields = this.contentFields.length;
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts
index 56db33ed5..377f5acb2 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts
@@ -19,8 +19,6 @@ export class DynamicField extends TemplateField {
return this.getSubfields.flatMap(field => [field, ...((field as DynamicField).getAllSubfields ?? [])]);
}
- setSubFields = (fields: TemplateField[]) => (this._subfields = fields);
-
handleFieldUpdate = (newDocsList: Doc[]) => {
const currRenderedDocs = new Set(this.getSubfields.filter(field => field.renderedDoc).map(field => field.renderedDoc!));
newDocsList.forEach(doc => !currRenderedDocs.has(doc) && this.addFieldFromDoc(doc));
@@ -89,10 +87,9 @@ export class DynamicField extends TemplateField {
initRenderDoc = (settings: FieldSettings) => {
this._disposers.fieldList = reaction(() => DocListCast(this._renderDoc?.[Doc.LayoutFieldKey(this._renderDoc)]), this.handleFieldUpdate);
- this._subfields = settings.subfields?.map((fieldSettings, index) => TemplateField.CreateField(fieldSettings, index, this)) || [];
+ this._subfields = settings.subfields?.map((fieldSettings, index) => {fieldSettings.template = this.settings.template; return TemplateField.CreateField(fieldSettings, index, this)}) || [];
const renderedSubfields = this._subfields.filter(field => field.renderedDoc).map(field => field.renderedDoc!);
settings.opts.title = settings.title;
- console.log('initializing dynamicfield with color: ', settings.opts.backgroundColor)
this._renderDoc = (() => { switch (settings.viewType) {
case ViewType.CAROUSEL3D: return Docs.Create.Carousel3DDocument(renderedSubfields, settings.opts);
case ViewType.FREEFORM:
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
index ecb4f6f24..0ad1accc1 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
@@ -1,7 +1,9 @@
/* eslint-disable no-use-before-define */
import { Doc } from '../../../../../../fields/Doc';
import { DocumentOptions } from '../../../../../documents/Documents';
+import { Conditional } from '../Backend/TemplateManager';
import { Col } from '../DocCreatorMenu';
+import { Template } from '../Template';
import { TemplateFieldSize, TemplateFieldType } from '../TemplateBackend';
export abstract class TemplateField {
@@ -16,7 +18,7 @@ export abstract class TemplateField {
* @param sameId -
* @returns TemplateField
*/
- static CreateField: (settings: FieldSettings, index: number, parent: TemplateField | undefined, sameId?: boolean) => TemplateField;
+ static CreateField: (settings: FieldSettings, index: number, parent: TemplateField | undefined, template: Template, sameId?: boolean) => TemplateField;
protected _parent?: TemplateField;
protected _id: number;
@@ -24,13 +26,15 @@ export abstract class TemplateField {
protected _settings: FieldSettings;
protected _renderDoc: Doc | undefined;
protected _dimensions: FieldDimensions | undefined;
+ public template: Template;
- constructor(settings: FieldSettings, id: number = 1, parent?: TemplateField) {
+ constructor(settings: FieldSettings, id: number = 1, template: Template, 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;
}
@@ -71,7 +75,7 @@ export abstract class TemplateField {
makeClone(parent?: TemplateField) {
const settings: FieldSettings = structuredClone(this._settings);
- const cloned = TemplateField.CreateField(settings, this._id, parent, true); // create a value for this.Document/subfields that we want to ignore
+ const cloned = TemplateField.CreateField(settings, this._id, parent, this.template, 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;
@@ -85,7 +89,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, true);
+ const newField = TemplateField.CreateField(newSettings, this._id, this._parent, this.template, true);
this._parent?.exchangeFields(newField, this);
return newField;
};
@@ -139,6 +143,7 @@ export type FieldSettings = {
types?: TemplateFieldType[];
sizes?: TemplateFieldSize[];
description?: string;
+ template?: Template;
};
export enum ViewType {
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateManager.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateManager.ts
deleted file mode 100644
index 0978444e3..000000000
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateManager.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { makeAutoObservable } from 'mobx';
-import { Col } from './DocCreatorMenu';
-import { FieldSettings } from './TemplateFieldTypes/TemplateField';
-import { Template } from './Template';
-
-export class TemplateManager {
- templates: Template[] = [];
-
- constructor(templateSettings: FieldSettings[]) {
- makeAutoObservable(this);
- this.templates = this.initializeTemplates(templateSettings);
- }
-
- initializeTemplates = (templateSettings: FieldSettings[]) => templateSettings.map(settings => new Template(settings));
-
- getValidTemplates = (cols: Col[]) => this.templates.filter(template => template.isValidTemplate(cols));
-
- addTemplate = (newTemplate: Template) => this.templates.push(newTemplate);
-
- removeTemplate = (template: Template) => {
- this.templates.splice(this.templates.indexOf(template), 1);
- template.cleanup();
- };
-}