import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { returnAll, returnFalse, returnNone, returnOne, returnZero, setupMoveUpEvents } from '../../../../ClientUtils'; import { Doc, NumListCast, StrListCast } from '../../../../fields/Doc'; import { DocCast, ImageCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; import { emptyFunction } from '../../../../Utils'; import { SnappingManager } from '../../../util/SnappingManager'; import { UndoManager, undoable } from '../../../util/UndoManager'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DocumentView, DocumentViewInternal } from '../DocumentView'; import { DataVizBox } from './DataVizBox'; import './DocCreatorMenu.scss'; import { Id } from '../../../../fields/FieldSymbols'; import { Colors, IconButton, Size } from 'browndash-components'; import { MakeTemplate } from '../../../util/DropConverter'; import { DragManager } from '../../../util/DragManager'; import { GPTCallType, gptAPICall } from '../../../apis/gpt/GPT'; import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView'; import { Docs } from '../../../documents/Documents'; import { OpenWhere } from '../OpenWhere'; import { IDisposer } from 'mobx-utils'; import { LightboxView } from '../../LightboxView'; import ReactLoading from 'react-loading'; import { CollectionStackingView } from '../../collections/CollectionStackingView'; import { FieldViewProps } from '../FieldView'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { dropActionType } from '../../../util/DropActionTypes'; import { ImageBox } from '../ImageBox'; import { a } from '@react-spring/web'; import { RichTextMenu } from '../formattedText/RichTextMenu'; import e from 'cors'; export enum LayoutType { Stacked = 'stacked', Grid = 'grid', Row = 'row', Column = 'column', Custom = 'custom' } @observer export class DocCreatorMenu extends ObservableReactComponent { static Instance: DocCreatorMenu; private _disposers: { [name: string]: IDisposer } = {}; private _ref: HTMLDivElement | null = null; @observable _templateDocs: Doc[] = []; @observable _selectedTemplate: Doc | undefined = undefined; @observable _columns: Col[] = []; @observable _selectedCols: {title: string, type: string, desc: string}[] | undefined = []; @observable _layout: {type: LayoutType, yMargin: number, xMargin: number, columns?: number, repeat: number} = {type: LayoutType.Grid, yMargin: 0, xMargin: 0, repeat: 0}; @observable _layoutPreview: boolean = true; @observable _layoutPreviewScale: number = 1; @observable _savedLayouts: DataVizTemplateLayout[] = []; @observable _expandedPreview: {icon: ImageField, doc: Doc} | undefined = undefined; @observable _suggestedTemplates: Doc[] = []; @observable _GPTOpt: boolean = false; @observable _userPrompt: string = ''; @observable _callCount: number = 0; @observable _GPTLoading: boolean = false; @observable _pageX: number = 0; @observable _pageY: number = 0; @observable _indicatorX: number | undefined = undefined; @observable _indicatorY: number | undefined = undefined; @observable _hoveredLayoutPreview: number | undefined = undefined; @observable _mouseX: number = -1; @observable _mouseY: number = -1; @observable _startPos?: {x: number, y: number}; @observable _shouldDisplay: boolean = false; @observable _menuContent: 'templates' | 'options' | 'saved' | 'dashboard' = 'templates'; @observable _dragging: boolean = false; @observable _draggingIndicator: boolean = false; @observable _dataViz?: DataVizBox; @observable _interactionLock: any; @observable _snapPt: any; @observable _resizeHdlId: string = ''; @observable _resizing: boolean = false; @observable _offset: {x: number, y: number} = {x: 0, y: 0}; @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: any) { super(props); makeObservable(this); DocCreatorMenu.Instance = this; //setTimeout(() => this.generateTemplates('')); } @action setDataViz = (dataViz: DataVizBox) => { this._dataViz = dataViz }; @action setTemplateDocs = (docs: Doc[]) => {this._templateDocs = docs.map(doc => doc.annotationOn ? DocCast(doc.annotationOn):doc)}; @action setGSuggestedTemplates = (docs: Doc[]) => {this._suggestedTemplates = docs}; @computed get docsToRender() { return this._selectedTemplate ? NumListCast(this._dataViz?.layoutDoc.dataViz_selectedRows) : []; } @computed get rowsCount(){ switch (this._layout.type) { case LayoutType.Row: case LayoutType.Stacked: return 1; case LayoutType.Column: return this.docsToRender.length; case LayoutType.Grid: return Math.ceil(this.docsToRender.length / (this._layout.columns ?? 1)) ?? 0; default: return 0; } } @computed get columnsCount(){ switch (this._layout.type) { case LayoutType.Row: return this.docsToRender.length; case LayoutType.Column: case LayoutType.Stacked: return 1; case LayoutType.Grid: return this._layout.columns ?? 0; default: return 0; } } @computed get selectedFields() { return StrListCast(this._dataViz?.layoutDoc._dataViz_axes); } @computed get fieldsInfos(): Col[] { const colInfo = this._dataViz?.colsInfo; return this.selectedFields.map(field => { const fieldInfo = colInfo?.get(field); const col: Col = { title: field, type: fieldInfo?.type ?? TemplateFieldType.UNSET, desc: fieldInfo?.desc ?? '', size: fieldInfo?.size ?? TemplateFieldSize.MEDIUM }; if (fieldInfo?.defaultContent !== undefined) { col.defaultContent = fieldInfo.defaultContent; } return col; }).concat(this._columns); } @computed get canMakeDocs(){ return this._selectedTemplate !== undefined && this._layout !== undefined; } get bounds(): {t: number, b: number, l: number, r: number} { const rect = this._ref?.getBoundingClientRect(); const bounds = {t: rect?.top ?? 0, b: rect?.bottom ?? 0, l: rect?.left ?? 0, r: rect?.right ?? 0}; return bounds; } setUpButtonClick = (e: any, func: Function) => { setupMoveUpEvents( this, e, returnFalse, emptyFunction, undoable(clickEv => { clickEv.stopPropagation(); clickEv.preventDefault(); func(); }, 'create docs') ) } @action onPointerDown = (e: PointerEvent) => { this._mouseX = e.clientX; this._mouseY = e.clientY; }; @action onPointerUp = (e: PointerEvent) => { if (this._resizing) { this._initDimensions.width = this._menuDimensions.width; this._initDimensions.height = this._menuDimensions.height; this._initDimensions.x = this._pageX; this._initDimensions.y = this._pageY; document.removeEventListener('pointermove', this.onResize); SnappingManager.SetIsResizing(undefined); this._resizing = false; } if (this._dragging) { document.removeEventListener('pointermove', this.onDrag); this._dragging = false; } if (e.button !== 2 && !e.ctrlKey) return; const curX = e.clientX; const curY = e.clientY; if (Math.abs(this._mouseX - curX) > 1 || Math.abs(this._mouseY - curY) > 1) { this._shouldDisplay = false; } }; componentDidMount() { document.addEventListener('pointerdown', this.onPointerDown, true); document.addEventListener('pointerup', this.onPointerUp); this._disposers.templates = reaction(() => this._templateDocs.slice(), (docs) => docs.map(this.getIcon)); this._disposers.gpt = reaction(() => this._suggestedTemplates.slice(), (docs) => docs.map(this.getIcon)); //this._disposers.columns = reaction(() => this._dataViz?.layoutDoc._dataViz_axes, () => {this.generateTemplates('')}) this._disposers.lightbox = reaction(() => LightboxView.LightboxDoc(), doc => { doc ? this._shouldDisplay && this.closeMenu() : !this._shouldDisplay && this.openMenu()}); //this._disposers.fields = reaction(() => this._dataViz?.axes, cols => this._selectedCols = cols?.map(col => { return {title: col, type: '', desc: ''}})) } componentWillUnmount() { Object.values(this._disposers).forEach(disposer => disposer?.()); document.removeEventListener('pointerdown', this.onPointerDown, true); document.removeEventListener('pointerup', this.onPointerUp); } updateIcons = (docs: Doc[]) => { docs.map(this.getIcon) } @action updateSelectedCols = (cols: string[]) => { this._selectedCols } @action toggleDisplay = (x: number, y: number) => { if (this._shouldDisplay) { this._shouldDisplay = false; } else { this._pageX = x; this._pageY = y; this._shouldDisplay = true; } }; @action closeMenu = () => { this._shouldDisplay = false }; @action openMenu = () => { const allTemplates = this._templateDocs.concat(this._suggestedTemplates); this._shouldDisplay = true; this.updateIcons(allTemplates); }; @action onResizePointerDown = (e: React.PointerEvent): void => { this._resizing = true; document.addEventListener('pointermove', this.onResize); SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them e.stopPropagation(); const id = (this._resizeHdlId = e.currentTarget.className); const pad = id.includes('Left') || id.includes('Right') ? Number(getComputedStyle(e.target as any).width.replace('px', '')) / 2 : 0; const bounds = e.currentTarget.getBoundingClientRect(); this._offset = { x: id.toLowerCase().includes('left') ? bounds.right - e.clientX - pad : bounds.left - e.clientX + pad, // y: id.toLowerCase().includes('top') ? bounds.bottom - e.clientY - pad : bounds.top - e.clientY + pad, }; this._resizeUndo = UndoManager.StartBatch('drag resizing'); this._snapPt = { x: e.pageX, y: e.pageY }; }; @action onResize = (e: any): boolean => { const dragHdl = this._resizeHdlId.split(' ')[1]; const thisPt = DragManager.snapDrag(e, -this._offset.x, -this._offset.y, this._offset.x, this._offset.y); const { scale, refPt, transl } = this.getResizeVals(thisPt, dragHdl); !this._interactionLock && runInAction(async () => { // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate) this._interactionLock = true; const scaleAspect = {x: scale.x, y: scale.y}; this.resizeView(refPt, scaleAspect, transl); // prettier-ignore await new Promise(res => { setTimeout(() => { res(this._interactionLock = undefined)})}); }); // prettier-ignore return true; } @action onDrag = (e: any): boolean => { this._pageX = e.pageX - (this._startPos?.x ?? 0); this._pageY = e.pageY - (this._startPos?.y ?? 0); this._initDimensions.x = this._pageX; this._initDimensions.y = this._pageY; return true; } getResizeVals = (thisPt: { x: number; y: number }, dragHdl: string) => { const [w, h] = [this._initDimensions.width, this._initDimensions.height]; const [moveX, moveY] = [thisPt.x - this._snapPt.x, thisPt.y - this._snapPt.y]; let vals: {scale: {x: number, y: number}, refPt: [number, number], transl: {x: number, y: number}}; switch (dragHdl) { case 'topLeft': vals = { scale: { x: 1 - moveX / w, y: 1 -moveY / h }, refPt: [this.bounds.r, this.bounds.b], transl: {x: moveX, y: moveY } }; break; case 'topRight': vals = { scale: { x: 1 + moveX / w, y: 1 -moveY / h }, refPt: [this.bounds.l, this.bounds.b], transl: {x: 0, y: moveY } }; break; case 'top': vals = { scale: { x: 1, y: 1 -moveY / h }, refPt: [this.bounds.l, this.bounds.b], transl: {x: 0, y: moveY } }; break; case 'left': vals = { scale: { x: 1 - moveX / w, y: 1 }, refPt: [this.bounds.r, this.bounds.t], transl: {x: moveX, y: 0 } }; break; case 'bottomLeft': vals = { scale: { x: 1 - moveX / w, y: 1 + moveY / h }, refPt: [this.bounds.r, this.bounds.t], transl: {x: moveX, y: 0 } }; break; case 'right': vals = { scale: { x: 1 + moveX / w, y: 1 }, refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break; case 'bottomRight':vals = { scale: { x: 1 + moveX / w, y: 1 + moveY / h }, refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break; case 'bottom': vals = { scale: { x: 1, y: 1 + moveY / h }, refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break; default: vals = { scale: { x: 1, y: 1 }, refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break; } // prettier-ignore return vals; }; resizeView = (refPt: number[], scale: { x: number; y: number }, translation: {x: number, y: number}) => { const refCent = [refPt[0], refPt[1]] // fixed reference point for resize (ie, a point that doesn't move) if (this._initDimensions.x === undefined) this._initDimensions.x = this._pageX; if (this._initDimensions.y === undefined) this._initDimensions.y = this._pageY; const {height, width, x, y} = this._initDimensions; this._menuDimensions.width = Math.max(300, scale.x * width); this._menuDimensions.height = Math.max(200, scale.y * height); this._pageX = x + translation.x; this._pageY = y + translation.y; }; async getIcon(doc: Doc) { const docView = DocumentView.getDocumentView(doc); if (docView) { docView.ComponentView?.updateIcon?.(); return new Promise(res => setTimeout(() => res(ImageCast(docView.Document.icon)), 500));; } return undefined; }; @action updateSelectedTemplate = (template: Doc) => { if (this._selectedTemplate === template) { this._selectedTemplate = undefined; return; } else { this._selectedTemplate = template; MakeTemplate(template); } }; @action updateSelectedSavedLayout = (layout: DataVizTemplateLayout) => { this._layout.xMargin = layout.layout.xMargin; this._layout.yMargin = layout.layout.yMargin; this._layout.type = layout.layout.type; this._layout.columns = layout.columns; }; isSelectedLayout = (layout: DataVizTemplateLayout) => { return this._layout.xMargin === layout.layout.xMargin && this._layout.yMargin === layout.layout.yMargin && this._layout.type === layout.layout.type && this._layout.columns === layout.columns; }; @action generateTemplates = async (inputText: string) => { ++this._callCount; const origCount = this._callCount; let prompt: string = `(#${origCount}) Please generate for the fields:`; this.selectedFields?.forEach(field => prompt += ` ${field},`) prompt += ` (-----NOT A FIELD-----) Additional prompt: ${inputText}`; this._GPTLoading = true; try { const res = await gptAPICall(prompt, GPTCallType.TEMPLATE); if (res && this._callCount === origCount) { this._suggestedTemplates = []; this._GPTLoading = false; const templates: {template_type: string, fieldVals: {title: string, tlx: string, tly: string, brx: string, bry: string}[]}[] = JSON.parse(res); this.createGeneratedTemplates(templates, 500, 500); } } catch (err) { console.error(err); } }; @action createGeneratedTemplates = (layouts: {template_type: string, fieldVals: {title: string, tlx: string, tly: string, brx: string, bry: string}[]}[], tempWidth: number, tempHeight: number) => { const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView; const GPTTemplates: Doc[] = []; layouts.forEach(layout => { const fields: Doc[] = layout.fieldVals.map(field => { const left: number = Number(field.tlx) * tempWidth / 2; const top: number = Number(field.tly) * tempHeight / 2; //prettier-ignore const right: number = Number(field.brx) * tempWidth / 2; const bottom: number = Number(field.bry) * tempHeight / 2; //prettier-ignore const height = bottom - top; const width = right - left; const doc = !field.title.includes('$$') ? Docs.Create.TextDocument('', { _height: height, _width: width, title: field.title, x: left, y: top, _text_fontSize: `${height/2}` }) : Docs.Create.ImageDocument('', { _height: height, _width: width, title: field.title.replace(/\$\$/g, ''), x: left, y: top }); return doc; }); const template = Docs.Create.FreeformDocument(fields, { _height: tempHeight, _width: tempWidth, title: layout.template_type, x: 400000, y: 400000 }); mainCollection.addDocument(template); GPTTemplates.push(template); }); setTimeout(() => {this.setGSuggestedTemplates(GPTTemplates); /*GPTTemplates.forEach(template => mainCollection.removeDocument(template))*/}, 100); this.forceUpdate(); }; editTemplate = (doc: Doc) => { //this.closeMenu(); DocumentViewInternal.addDocTabFunc(doc, OpenWhere.addRight); DocumentView.DeselectAll(); Doc.UnBrushDoc(doc); }; removeTemplate = (doc: Doc) => { this._templateDocs.splice(this._templateDocs.indexOf(doc), 1); }; testTemplate = () => { // const temp = TemplateLayouts.FourField001; // const title: Doc = FieldFuncs.TextField({tl: temp.fields[0].tl, br: temp.fields[0].br}, temp.height, temp.width, 'title', 'Title', {backgroundColor: 'transparent'}); // const image: Doc = FieldFuncs.ImageField({tl: temp.fields[1].tl, br: temp.fields[1].br}, temp.height, temp.width, 'title', '', {borderColor: '#159fe4', borderWidth: '10', cornerRounding: 10, rotation: 40}); // const caption: Doc = FieldFuncs.TextField({tl: temp.fields[2].tl, br: temp.fields[2].br}, temp.height, temp.width, 'title', 'Caption', {backgroundColor: 'transparent'}); // const desc: Doc = FieldFuncs.TextField({tl: temp.fields[3].tl, br: temp.fields[3].br}, temp.height, temp.width, 'title', '', {backgroundColor: 'lightblue', borderColor: '#159fe4', borderWidth: '10', cornerRounding: 10}); // const doc = Docs.Create.FreeformDocument([title, image, caption, desc], { _height: temp.height, _width: temp.width, title: 'hey', x: 400, y: 400 }); // const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView; // mainCollection.addDocument(doc); // const temp = TemplateLayouts.FourField001; // const img: Col = {type: TemplateFieldType.TEXT, title: 'Type', desc: 'description whpoo', size: TemplateFieldSize.LARGE, defaultContent: ''}; // const capt1: Col = {type: TemplateFieldType.TEXT, title: 'Image', desc: 'description hey', size: TemplateFieldSize.TINY}; // const capt2: Col = {type: TemplateFieldType.TEXT, title: 'Locality', desc: '', size: TemplateFieldSize.TINY, defaultContent: ''}; // const desc: Col = {type: TemplateFieldType.TEXT, title: 'Description', desc: '', size: TemplateFieldSize.LARGE, defaultContent: 'This is a description of a rock. It is kind of long. It is very long. It is gratuitous. This description should be shorter. Oh well. This is a description of a rock. It is kind of long. It is very long. It is gratuitous. This description should be shorter. Oh well. This is a description of a rock. It is kind of long. It is very long. It is gratuitous. This description should be shorter. Oh well.'}; // const assignments = {'0': img, '1': capt1, '2': capt2, '3': desc} // this.createEmptyTemplate(temp, assignments); // console.log(this.findValidTemplates(this.fieldsInfos, TemplateLayouts.allTemplates)); // console.log(this._dataViz?.colsInfo.get("IMG")?.size, this._dataViz?.colsInfo.get("IMG")?.type) // console.log(this.fieldsInfos) }; @action addField = () => { const newFields: Col[] = this._columns.concat([{title: '', type: TemplateFieldType.UNSET, desc: '', size: TemplateFieldSize.MEDIUM}]) this._columns = newFields; }; @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._columns.filter(f => f === field); if (!toRemove) return; if (toRemove.length > 1) { while (toRemove.length > 1) { toRemove.pop(); } } if (this._columns.length === 1) { this._columns = [] } else { this._columns.splice(this._columns.indexOf(toRemove[0]), 1); } } }; setColTitle = (column: Col, title: string) => { if (this.selectedFields.includes(column.title)) { this._dataViz?.setColumnTitle(column.title, title); } else { column.title = title; } this.forceUpdate(); }; setColType = (column: Col, type: TemplateFieldType) => { if (this.selectedFields.includes(column.title)) { this._dataViz?.setColumnType(column.title, type); } else { column.type = type; } this.forceUpdate(); }; setColSize = (column: Col, size: TemplateFieldSize) => { if (this.selectedFields.includes(column.title)) { this._dataViz?.setColumnSize(column.title, size); } else { column.size = size; console.log(column.size) } this.forceUpdate(); }; setColDesc = (column: Col, desc: string) => { if (this.selectedFields.includes(column.title)) { this._dataViz?.setColumnDesc(column.title, desc); } else { column.desc = desc; } this.forceUpdate(); }; matchesForTemplate = (template: TemplateDocInfos, cols: Col[]): number[][] => { const colMatchesField = (col: Col, field : Field) => { return field.sizes?.includes(col.size) && field.types?.includes(col.type) }; const matches: number[][] = Array(template.fields.length).fill([]).map(() => []); template.fields.forEach((field, i) => { cols.forEach((col, v) => { if (colMatchesField(col, field)) { matches[i].push(v); } }); }); return matches; }; maxMatches = (fieldsCt: number, matches: number[][]) => { const used: boolean[] = Array(fieldsCt).fill(false); const mt: number[] = Array(fieldsCt).fill(-1); console.log(fieldsCt, matches) const augmentingPath = (v: number): boolean => { if (used[v]) return false; used[v] = true; for (const to of matches[v]) { console.log(mt[to]); if (mt[to] === -1 || augmentingPath(mt[to])) { mt[to] = v; return true; } } return false; } for (let v = 0; v < fieldsCt; ++v) { used.fill(false); augmentingPath(v); } let count: number = 0; for (let i = 0; i < fieldsCt; ++i) { if (mt[i] !== -1) ++count; } return count; }; findValidTemplates = (cols: Col[], templates: TemplateDocInfos[]) => { let validTemplates: any[] = []; templates.forEach(template => { const numFields = template.fields.length; if (!(numFields === cols.length)) return; const matches = this.matchesForTemplate(template, cols); if (this.maxMatches(numFields, matches) === numFields) { validTemplates.push(template.title); } }) validTemplates = validTemplates.map(title => TemplateLayouts.fieldByTitle(title)); console.log(validTemplates); return validTemplates; }; // createColumnField = (template: TemplateDocInfos, field: Field, column: Col): Doc => { // if (field.subfields) { // const doc = FieldFuncs.FreeformField({ // tl: field.tl, // br: field.br }, // template.height, // template.width, // column.title, // '', // field.opts // ); // field.subfields[1].forEach(f => { // const fDoc = () // }) // } // return new Doc; // } fillPresetTemplate = async(template: TemplateDocInfos, assignments: {[field: string]: Col}): Promise => { 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 fields: Doc[] = []; const GPTAssignments = Object.entries(assignments).filter(([f, col]) => this._columns.includes(col)); const nonGPTAssignments: [string, Col][] = Object.entries(assignments).filter(a => !GPTAssignments.includes(a)); const stringifyGPTInfo = (): string => { let string: string = '*** COLUMN INFO:'; GPTAssignments.forEach(([fieldNum, col]) => { string += `--- title: ${col.title}, prompt: ${col.desc}, word limit: ${wordLimit(col.size)} words, assigned field: ${fieldNum} ---` }); return string += ' ***'; }; const GPTAssignmentString = stringifyGPTInfo(); let fieldContent: string = ''; Object.entries(nonGPTAssignments).forEach(([f, strCol]) => { const field: Field = template.fields[Number(f)]; const col = strCol[1]; const doc = (col.type === TemplateFieldType.VISUAL ? FieldFuncs.ImageField : FieldFuncs.TextField)({ tl: field.tl, br: field.br }, template.height, template.width, col.title, col.defaultContent ?? '', field.opts ); fieldContent += `--- Field #${f} (title: ${col.title}): ${col.defaultContent ?? ''} ---` fields.push(doc); }); template.decorations.forEach(dec => { const doc = FieldFuncs.FreeformField({ tl: dec.tl, br: dec.br }, template.height, template.width, '', '', dec.opts, ); fields.push(doc); }); const createMainDoc = (): Doc => { const main = Docs.Create.FreeformDocument(fields, { _height: template.height, _width: template.width, title: template.title, backgroundColor: template.opts.backgroundColor, _layout_borderRounding: `${template.opts.cornerRounding}px` ?? '0px', borderWidth: template.opts.borderWidth, borderColor: template.opts.borderColor, x: 400, y: 400, }); const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView; mainCollection.addDocument(main); return main; } if (GPTAssignments.length) { try { const prompt = fieldContent + GPTAssignmentString; const res = await gptAPICall(prompt, GPTCallType.FILL); if (res){ console.log('response', res); const assignments: {[title: string]: {number: string, content: string}} = JSON.parse(res); console.log('assignments', GPTAssignments, 'assignment string', GPTAssignmentString, 'field content', fieldContent, 'response', res, 'assignments', assignments); Object.entries(assignments).forEach(([title, info]) => { const field: Field = template.fields[Number(info.number)]; const col = this.getColByTitle(title); const doc = (col.type === TemplateFieldType.VISUAL ? FieldFuncs.ImageField : FieldFuncs.TextField)({ tl: field.tl, br: field.br }, template.height, template.width, col.title, info.content ?? '', field.opts ); fields.push(doc); }); return createMainDoc(); } } catch(err) { console.log(err); } } else { return createMainDoc(); } return new Doc; }; compileFieldDescriptions = (templates: TemplateDocInfos[]): string => { let descriptions: string = ''; templates.forEach(template => { descriptions += `---------- NEW TEMPLATE TO INCLUDE: Description of template ${template.title}'s fields: ` template.fields.forEach((field, index) => { descriptions += `{Field #${index}: ${field.description}} ` }); }); return descriptions; }; compileColDescriptions = (cols: Col[]): string => { let descriptions: string = ' ------------- COL DESCRIPTIONS START HERE:'; cols.forEach(col => descriptions += `{title: ${col.title}, size: ${col.size}, type: ${col.type}, descreiption: ${col.desc}} `); return descriptions; }; getColByTitle = (title: string) => { return this.fieldsInfos.filter(col => col.title === title)[0]; }; @action assignColsToFields = async(templates: TemplateDocInfos[], cols: Col[]): Promise<[TemplateDocInfos, {[field: number]: Col}][]> => { const fieldDescriptions: string = this.compileFieldDescriptions(templates); const colDescriptions: string = this.compileColDescriptions(cols); const inputText = fieldDescriptions.concat(colDescriptions); console.log(inputText); ++this._callCount; const origCount = this._callCount; let prompt: string = `(${origCount}) ${inputText}`; this._GPTLoading = true; try { const res = await gptAPICall(prompt, GPTCallType.TEMPLATE); if (res && this._callCount === origCount) { this._GPTLoading = false; console.log(res); const assignments: {[templateTitle: string]: {[field: string]: string}} = JSON.parse(res); const brokenDownAssignments: [TemplateDocInfos, {[field: number]: Col}][] = []; Object.entries(assignments).forEach(([tempTitle, assignment]) => { const template = TemplateLayouts.fieldByTitle(tempTitle); if (!template) return; console.log(assignments) const toObj = Object.entries(assignment).reduce((a, [fieldNum, colTitle]) => { a[Number(fieldNum)] = this.getColByTitle(colTitle); return a; }, {} as { [field: number]: Col }); brokenDownAssignments.push([template, toObj]) }) return brokenDownAssignments; } } catch (err) { console.error(err); } return []; }; generatePresetTemplates = async () => { this._dataViz?.updateColDefaults(); const cols = this.fieldsInfos; const templates = this.findValidTemplates(cols, TemplateLayouts.allTemplates); const assignments: [TemplateDocInfos, { [field: number]: Col }][] = await this.assignColsToFields(templates, cols); const renderedTemplatePromises: Promise[] = assignments.map(([template, assignments]) => this.fillPresetTemplate(template, assignments) ); const renderedTemplates: Doc[] = await Promise.all(renderedTemplatePromises); setTimeout(() => { this.setGSuggestedTemplates(renderedTemplates) }); }; @action setExpandedView = (info: {icon: ImageField, doc: Doc} | undefined) => { this._expandedPreview = info; } get templatesPreviewContents(){ const renderedTemplates: Doc[] = []; const GPTOptions =
return (
{this._expandedPreview ?
:
Suggested Templates
400 ? 'center' : ''}}> {this._GPTLoading ? (
) : ( this._suggestedTemplates?.map(doc => ({icon: ImageCast(doc.icon), doc})).filter(info => info.icon && info.doc).map(info =>
this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(info.doc)))}>
))}
{this._GPTOpt ? GPTOptions : null }

Your Templates
400 ? 'center' : ''}}>
this.testTemplate()}>
{this._templateDocs.map(doc => ({icon: ImageCast(doc.icon), doc})).filter(info => info.icon && info.doc).map(info => { if (renderedTemplates.includes(info.doc)) return undefined; renderedTemplates.push(info.doc); return (
this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(info.doc)))}>
)})}
}
); } get savedLayoutsPreviewContents(){ return (
{this._savedLayouts.map((layout, index) =>
this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedSavedLayout(layout)))} > {this.layoutPreviewContents(87, layout, true, index)}
)}
); } @action updateXMargin = (input: string) => { this._layout.xMargin = Number(input) }; @action updateYMargin = (input: string) => { this._layout.yMargin = Number(input) }; @action updateColumns = (input: string) => { this._layout.columns = Number(input) }; get layoutConfigOptions() { const optionInput = (icon: string, func: Function, def?: number, key?: string, noMargin?: boolean) => { return (
func(e.currentTarget.value)} className='docCreatorMenu-input config layout-config'/>
); } switch (this._layout.type) { case LayoutType.Row: return (
{optionInput('arrows-left-right', this.updateXMargin, this._layout.xMargin, '0')}
); case LayoutType.Column: return (
{optionInput('arrows-up-down', this.updateYMargin, this._layout.yMargin, '1')}
); case LayoutType.Grid: return (
{optionInput('arrows-up-down', this.updateYMargin, this._layout.xMargin, '2')} {optionInput('arrows-left-right', this.updateXMargin, this._layout.xMargin, '3')} {optionInput('table-columns', this.updateColumns, this._layout.columns, '4', true)}
); case LayoutType.Stacked: return null; default: break; } } // doc = () => { // return Docs.Create.FreeformDocument([], { _height: 200, _width: 200, title: 'title'}); // } screenToLocalTransform = () => this._props .ScreenToLocalTransform(); layoutPreviewContents = (outerSpan: number, altLayout?: DataVizTemplateLayout, small: boolean = false, id?: number) => { const doc: Doc | undefined = altLayout ? altLayout.template : this._selectedTemplate; if (!doc) return; const layout = altLayout ? altLayout.layout : this._layout; const docWidth: number = Number(doc._width); const docHeight: number = Number(doc._height); const horizontalSpan: number = (docWidth + layout.xMargin) * (altLayout ? altLayout.columns : this.columnsCount) - layout.xMargin;; const verticalSpan: number = (docHeight + layout.yMargin) * (altLayout ? altLayout.rows : this.rowsCount) - layout.yMargin; const largerSpan: number = horizontalSpan > verticalSpan ? horizontalSpan : verticalSpan; const scaledDown = (input: number) => {return input / (largerSpan / outerSpan * this._layoutPreviewScale)} const fontSize = Math.min(scaledDown(docWidth / 3), scaledDown(docHeight / 3)); return ( //
// 100} // NativeHeight={() => 100} // pointerEvents={SnappingManager.IsDragging ? returnAll : returnNone} // isAnnotationOverlay // isAnnotationOverlayScrollable // childDocumentsActive={returnFalse} // fieldKey={this._props.fieldKey + '_annotations'} // dropAction={dropActionType.move} // select={emptyFunction} // addDocument={returnFalse} // removeDocument={returnFalse} // moveDocument={returnFalse} // renderDepth={this._props.renderDepth + 1}> // {null} // //
{altLayout ? : null}
{
{this._layout.type === LayoutType.Stacked ?
All
: this.docsToRender.map(num =>
this._dataViz?.setSpecialHighlightedRow(num)} onMouseLeave={() => this._dataViz?.setSpecialHighlightedRow(undefined)} className='docCreatorMenu-layout-preview-item' style={{ width: scaledDown(docWidth), height: scaledDown(docHeight), fontSize: fontSize, }} > {num}
)}
}
); } get optionsMenuContents(){ const layoutEquals = (layout: DataVizTemplateLayout) => { } //TODO: ADD LATER const layoutOption = (option: LayoutType, optStyle?: {}, specialFunc?: Function) => { return (
this.setUpButtonClick(e, () => {specialFunc?.(); runInAction(() => this._layout.type = option)})}> {option}
); } const selectionBox = (width: number, height: number, icon: string, specClass?: string, options?: JSX.Element[], manual?: boolean): JSX.Element => { return (
{manual ? : }
); } const repeatOptions = [0, 1, 2, 3, 4, 5]; return (
{this._layout.type ? this._layout.type.toUpperCase() : 'Choose Layout'}
{layoutOption(LayoutType.Stacked)} {layoutOption(LayoutType.Grid, undefined, () => {if (!this._layout.columns) this._layout.columns = Math.ceil(Math.sqrt(this.docsToRender.length))})} {layoutOption(LayoutType.Row)} {layoutOption(LayoutType.Column)} {layoutOption(LayoutType.Custom, {borderBottom: `0px`})}
{this._layout.type ? this.layoutConfigOptions: null} {this._layoutPreview ? this.layoutPreviewContents(this._menuDimensions.width * .75) : null} {selectionBox(60, 20, 'repeat', undefined, repeatOptions.map(num => ))}
); } get dashboardContents(){ return (
{this.fieldsInfos.map((field, index) =>
this.setColTitle(field, e.target.value)}/>
{field.type === TemplateFieldType.UNSET ? Select media type : null} {field.type === TemplateFieldType.TEXT ? 'Text Field' : field.type === TemplateFieldType.VISUAL ? 'File Field' : ''}
{this.setColType(field, TemplateFieldType.TEXT)}}/>
Text
{this.setColType(field, TemplateFieldType.VISUAL)}}/>
File
this.setColSize(field, e.target.value as TemplateFieldSize)}/>