import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Colors } from 'browndash-components'; import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { IDisposer } from 'mobx-utils'; import * as React from 'react'; import ReactLoading from 'react-loading'; import { ClientUtils, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils'; import { emptyFunction } from '../../../../Utils'; import { Doc, NumListCast, StrListCast, returnEmptyDoclist } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { Cast, DocCast, ImageCast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; import { Networking } from '../../../Network'; import { GPTCallType, gptAPICall, gptImageCall } from '../../../apis/gpt/GPT'; import { Docs } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; import { MakeTemplate } from '../../../util/DropConverter'; import { SnappingManager } from '../../../util/SnappingManager'; import { UndoManager, undoable } from '../../../util/UndoManager'; import { LightboxView } from '../../LightboxView'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView'; import { DocumentView, DocumentViewInternal } from '../DocumentView'; import { FieldViewProps } from '../FieldView'; import { OpenWhere } from '../OpenWhere'; import { DataVizBox } from './DataVizBox'; import './DocCreatorMenu.scss'; import { DefaultStyleProvider, returnEmptyDocViewList } from '../../StyleProvider'; import { Transform } from '../../../util/Transform'; import { IconProp } from '@fortawesome/fontawesome-svg-core'; export enum LayoutType { Stacked = 'stacked', Grid = 'grid', Row = 'row', Column = 'column', Custom = 'custom', } export interface DataVizTemplateInfo { doc: Doc; layout: { type: LayoutType; xMargin: number; yMargin: number; repeat: number }; columns: number; referencePos: { x: number; y: number }; } export interface DataVizTemplateLayout { template: Doc; docsNumList: number[]; layout: { type: LayoutType; xMargin: number; yMargin: number; repeat: number }; columns: number; rows: number; } export enum TemplateFieldType { TEXT = 'text', VISUAL = 'visual', UNSET = 'unset', } export enum TemplateFieldSize { TINY = 'tiny', SMALL = 'small', MEDIUM = 'medium', LARGE = 'large', HUGE = 'huge', } export type Col = { sizes: TemplateFieldSize[]; desc: string; title: string; type: TemplateFieldType; defaultContent?: string; }; export interface FieldOpts { backgroundColor?: string; color?: string; cornerRounding?: number; borderWidth?: string; borderColor?: string; contentXCentering?: 'h-left' | 'h-center' | 'h-right'; contentYCentering?: 'top' | 'center' | 'bottom'; opacity?: number; rotation?: number; //animation?: boolean; fontBold?: boolean; fontTransform?: 'uppercase' | 'lowercase'; fieldViewType?: 'freeform' | 'stacked'; } type Field = { tl: [number, number]; br: [number, number]; opts: FieldOpts; subfields?: Field[]; types?: TemplateFieldType[]; sizes?: TemplateFieldSize[]; isDecoration?: boolean; description?: string; }; // class ContentField implements Field { // tl: [number, number]; // br: [number, number]; // opts: FieldOpts; // subfields?: Field[]; // types?: TemplateFieldType[]; // sizes?: TemplateFieldSize[]; // description?: string; // constructor( tl: [number, number], br: [number, number], // opts: FieldOpts, subfields?: Field[], // types?: TemplateFieldType[], // sizes?: TemplateFieldSize[], // description?: string) { // this.tl = tl; // this.br = br; // this.opts = opts; // this.subfields = subfields; // this.types = types; // this.sizes = sizes; // this.description = description; // } // render = (content: any): Doc => { // return new Doc; // } // } type DecorationField = Field; type InkDecoration = {}; type TemplateDecorations = Field | InkDecoration; interface TemplateOpts extends FieldOpts {} export interface TemplateDocInfos { title: string; height: number; width: number; opts: TemplateOpts; fields: Field[]; decorations: Field[]; } export class TemplateLayouts { public static get allTemplates(): TemplateDocInfos[] { return Object.values(TemplateLayouts).filter(value => typeof value === 'object' && value !== null && 'title' in value) as TemplateDocInfos[]; } public static getTemplateByTitle = (title: string): TemplateDocInfos | undefined => { switch (title) { case 'fourfield1': return TemplateLayouts.FourField001; case 'fourfield2': return TemplateLayouts.FourField002; // case 'fourfield3': // return TemplateLayouts.FourField003; case 'fourfield4': return TemplateLayouts.FourField004; case 'threefield1': return TemplateLayouts.ThreeField001; case 'threefield2': return TemplateLayouts.ThreeField002; default: break; } return undefined; }; public static FourField001: TemplateDocInfos = { title: 'fourfield1', width: 416, height: 700, opts: { backgroundColor: '#C0B887', cornerRounding: 20, borderColor: '#6B461F', borderWidth: '12', }, fields: [ { tl: [-0.95, -1], br: [0.95, -0.85], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.TINY], description: 'A title field for very short text that contextualizes the content.', opts: { backgroundColor: 'transparent', color: '#F1F0E9', contentXCentering: 'h-center', fontBold: true, }, }, { tl: [-0.87, -0.83], br: [0.87, 0.2], types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], description: 'The main focus of the template; could be an image, long text, etc.', opts: { cornerRounding: 20, borderColor: '#8F5B25', borderWidth: '6', backgroundColor: '#CECAB9', }, }, { tl: [-0.8, 0.2], br: [0.8, 0.3], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], description: 'A caption for field #2, very short to short text that contextualizes the content of field #2', opts: { backgroundColor: 'transparent', contentXCentering: 'h-center', color: '#F1F0E9', }, }, { tl: [-0.87, 0.37], br: [0.87, 0.96], types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], description: 'A medium-sized field for medium/long text.', opts: { cornerRounding: 15, borderColor: '#8F5B25', borderWidth: '6', backgroundColor: '#CECAB9', }, }, ], decorations: [], }; public static FourField002: TemplateDocInfos = { title: 'fourfield2', width: 425, height: 778, opts: { backgroundColor: '#242425', }, fields: [ { tl: [-0.83, -0.95], br: [0.83, -0.2], types: [TemplateFieldType.VISUAL, TemplateFieldType.TEXT], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE], description: 'A medium to large-sized field suitable for an image or longer text that should be the main focus.', opts: { borderWidth: '8', borderColor: '#F8E71C', }, }, { tl: [-0.65, -0.2], br: [0.65, -0.02], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.TINY], description: 'A tiny field for just a word or two of plain text.', opts: { backgroundColor: 'transparent', color: 'white', contentXCentering: 'h-center', fontTransform: 'uppercase', }, }, { tl: [-0.65, 0], br: [0.65, 0.18], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.TINY], description: 'A tiny field for just a word or two of plain text.', opts: { backgroundColor: 'transparent', color: 'white', contentXCentering: 'h-center', fontTransform: 'uppercase', }, }, { tl: [-0.83, 0.2], br: [0.83, 0.95], types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], description: 'A medium to large-sized field suitable for an image or longer text that should be the main focus, or share focus with field 1.', opts: { borderWidth: '8', borderColor: '#F8E71C', color: 'white', backgroundColor: '#242425', }, }, ], decorations: [ { tl: [-0.8, -0.075], br: [-0.525, 0.075], opts: { backgroundColor: '#F8E71C', rotation: 45, }, }, { tl: [-0.3075, -0.0245], br: [-0.2175, 0.0245], opts: { backgroundColor: '#F8E71C', rotation: 45, }, }, { tl: [-0.045, -0.0245], br: [0.045, 0.0245], opts: { backgroundColor: '#F8E71C', rotation: 45, }, }, { tl: [0.2175, -0.0245], br: [0.3075, 0.0245], opts: { backgroundColor: '#F8E71C', rotation: 45, }, }, { tl: [0.525, -0.075], br: [0.8, 0.075], opts: { backgroundColor: '#F8E71C', rotation: 45, }, }, ], }; // public static FourField003: TemplateDocInfos = { // title: 'fourfield3', // width: 477, // height: 662, // opts: { // backgroundColor: '#9E9C95' // }, // fields: [{ // tl: [-.875, -.9], // br: [.875, .7], // types: [TemplateFieldType.VISUAL], // sizes: [TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], // description: '', // opts: { // borderWidth: '15', // borderColor: '#E0E0DA', // } // }, { // tl: [-.95, .8], // br: [-.1, .95], // types: [TemplateFieldType.TEXT], // sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], // description: '', // opts: { // backgroundColor: 'transparent', // color: 'white', // contentXCentering: 'h-right', // } // }, { // tl: [.1, .8], // br: [.95, .95], // types: [TemplateFieldType.TEXT], // sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], // description: '', // opts: { // backgroundColor: 'transparent', // color: 'red', // fontTransform: 'uppercase', // contentXCentering: 'h-left' // } // }, { // tl: [0, -.9], // br: [.85, -.66], // types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL], // sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], // description: '', // opts: { // backgroundColor: 'transparent', // contentXCentering: 'h-right' // } // }], // decorations: [{ // tl: [-.025, .8], // br: [.025, .95], // opts: { // backgroundColor: '#E0E0DA', // } // }] // }; public static FourField004: TemplateDocInfos = { title: 'fourfield4', width: 414, height: 583, opts: { backgroundColor: '#6CCAF0', borderColor: '#1088C3', borderWidth: '10', }, fields: [ { tl: [-0.86, -0.92], br: [-0.075, -0.77], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.TINY], description: 'A tiny field for just a word or two of plain text.', opts: { backgroundColor: '#E2B4F5', borderWidth: '9', borderColor: '#9222F1', contentXCentering: 'h-center', }, }, { tl: [0.075, -0.92], br: [0.86, -0.77], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.TINY], description: 'A tiny field for just a word or two of plain text.', opts: { backgroundColor: '#F5B4DD', borderWidth: '9', borderColor: '#E260F3', contentXCentering: 'h-center', }, }, { tl: [-0.81, -0.64], br: [0.81, 0.48], types: [TemplateFieldType.VISUAL], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], description: 'A large to huge field for visual content that is the main content of the template.', opts: { borderWidth: '16', borderColor: '#A2BD77', }, }, { tl: [-0.86, 0.6], br: [0.86, 0.92], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE], description: 'A medium to large field for text that describes the visual content above', opts: { borderWidth: '9', borderColor: '#F0D601', backgroundColor: '#F3F57D', }, }, ], decorations: [ { tl: [-0.852, -0.67], br: [0.852, 0.51], opts: { backgroundColor: 'transparent', borderColor: '#007C0C', borderWidth: '10', }, }, ], }; public static ThreeField001: TemplateDocInfos = { title: 'threefield1', width: 575, height: 770, opts: { backgroundColor: '#DDD3A9', }, fields: [ { tl: [-0.66, -0.747], br: [0.66, 0.247], types: [TemplateFieldType.VISUAL], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], description: 'A medium to large field for visual content that is the central focus.', opts: { borderColor: 'yellow', borderWidth: '8', rotation: 45, }, }, { tl: [-0.7, 0.2], br: [0.7, 0.46], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], description: 'A very small text field for one to a few words. A good caption for the image.', opts: { backgroundColor: 'transparent', contentXCentering: 'h-center', }, }, { tl: [-0.95, 0.5], br: [0.95, 0.95], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE], description: 'A medium to large text field for a thorough description of the image. ', opts: { backgroundColor: 'transparent', color: 'white', }, }, ], decorations: [ { tl: [0.2, -1.32], br: [1.8, -0.66], opts: { backgroundColor: '#CEB155', rotation: 45, }, }, { tl: [-1.8, -1.32], br: [-0.2, -0.66], opts: { backgroundColor: '#CEB155', rotation: 135, }, }, { tl: [0.33, 0.75], br: [1.66, 1.25], opts: { backgroundColor: '#CEB155', rotation: 135, }, }, { tl: [-1.66, 0.75], br: [-0.33, 1.25], opts: { backgroundColor: '#CEB155', rotation: 45, }, }, ], }; public static ThreeField002: TemplateDocInfos = { title: 'threefield2', width: 477, height: 662, opts: { backgroundColor: '#9E9C95', }, fields: [ { tl: [-0.875, -0.9], br: [0.875, 0.7], types: [TemplateFieldType.VISUAL], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], description: 'A medium to large visual field for the main content of the template', opts: { borderWidth: '15', borderColor: '#E0E0DA', }, }, { tl: [0.1, 0.775], br: [0.95, 0.975], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], description: 'A very small text field for one to a few words. The content should represent a general categorization of the image.', opts: { backgroundColor: 'transparent', color: '#AF0D0D', fontTransform: 'uppercase', fontBold: true, contentXCentering: 'h-left', }, }, { tl: [-0.95, 0.775], br: [-0.1, 0.975], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], description: 'A very small text field for one to a few words. The content should contextualize field 2.', opts: { backgroundColor: 'transparent', color: 'black', contentXCentering: 'h-right', }, }, ], decorations: [ { tl: [-0.025, 0.8], br: [0.025, 0.95], opts: { backgroundColor: '#E0E0DA', }, }, ], }; } export class FieldUtils { public static contentFields = (fields: Field[]) => { let toRet: Field[] = []; fields.forEach(field => { if (!field.isDecoration) { toRet.push(field); } toRet = toRet.concat(FieldUtils.contentFields(field.subfields ?? [])); }); return toRet; }; public static calculateFontSize = (contWidth: number, contHeight: number, text: string, uppercase: boolean): number => { const words: string[] = text.split(/\s+/).filter(Boolean); let currFontSize = 1; let rowsCount = 1; let currTextHeight = currFontSize * rowsCount * 2; while (currTextHeight <= contHeight) { let wordIndex = 0; let currentRowWidth = 0; let wordsInCurrRow = 0; rowsCount = 1; while (wordIndex < words.length) { const word = words[wordIndex]; const wordWidth = word.length * currFontSize * 0.5; //console.log(wordWidth) if (currentRowWidth + wordWidth <= contWidth) { currentRowWidth += wordWidth; ++wordsInCurrRow; } else { if (words.length !== 1 && words.length > wordsInCurrRow) { rowsCount++; currentRowWidth = wordWidth; wordsInCurrRow = 1; } else { break; } } wordIndex++; } currTextHeight = rowsCount * currFontSize * 2; //console.log(rowsCount, currFontSize, currTextHeight) currFontSize += 1; } return currFontSize - 1; }; private static getDimensions = (coords: { tl: [number, number]; br: [number, number] }, parentWidth: number, parentHeight: number): { width: number; height: number; coord: { x: number; y: number } } => { const l = (coords.tl[0] * parentHeight) / 2; const t = coords.tl[1] * parentWidth / 2; //prettier-ignore const r = (coords.br[0] * parentHeight) / 2; const b = coords.br[1] * parentWidth / 2; //prettier-ignore const width = r - l; const height = b - t; const coord = { x: l, y: t }; //console.log(coords, parentWidth, parentHeight, height); return { width, height, coord }; }; public static FreeformField = (coords: { tl: [number, number]; br: [number, number] }, parentWidth: number, parentHeight: number, title: string, content: string, opts: FieldOpts) => { const { width, height, coord } = FieldUtils.getDimensions(coords, parentWidth, parentHeight); const docWithBasicOpts = Docs.Create.FreeformDocument([], { isDefaultTemplateDoc: true, _height: height, _width: width, title: title, x: coord.x, y: coord.y, backgroundColor: opts.backgroundColor ?? '', _layout_borderRounding: `${opts.cornerRounding ?? 0}px`, borderColor: opts.borderColor, borderWidth: opts.borderWidth, opacity: opts.opacity, hCentering: opts.contentXCentering, _rotation: opts.rotation, }); return docWithBasicOpts; }; public static TextField = (coords: { tl: [number, number]; br: [number, number] }, parentWidth: number, parentHeight: number, title: string, content: string, opts: FieldOpts) => { const { width, height, coord } = FieldUtils.getDimensions(coords, parentWidth, parentHeight); const docWithBasicOpts = Docs.Create.TextDocument(content, { isDefaultTemplateDoc: true, _height: height, _width: width, title: title, x: coord.x, y: coord.y, _text_fontSize: `${FieldUtils.calculateFontSize(width, height, content, true)}`, backgroundColor: opts.backgroundColor ?? '', text_fontColor: opts.color, contentBold: opts.fontBold, textTransform: opts.fontTransform, color: opts.color, _layout_borderRounding: `${opts.cornerRounding ?? 0}px`, borderColor: opts.borderColor, borderWidth: opts.borderWidth, opacity: opts.opacity, hCentering: opts.contentXCentering, _rotation: opts.rotation, }); docWithBasicOpts._layout_hideScroll = true; return docWithBasicOpts; }; public static ImageField = (coords: { tl: [number, number]; br: [number, number] }, parentWidth: number, parentHeight: number, title: string, content: string, opts: FieldOpts) => { const { width, height, coord } = FieldUtils.getDimensions(coords, parentWidth, parentHeight); const doc = Docs.Create.ImageDocument(content, { isDefaultTemplateDoc: true, _height: height, _width: width, title: title, x: coord.x, y: coord.y, _layout_fitWidth: false, backgroundColor: opts.backgroundColor ?? '', _layout_borderRounding: `${opts.cornerRounding ?? 0}px`, borderColor: opts.borderColor, borderWidth: opts.borderWidth, opacity: opts.opacity, _rotation: opts.rotation, }); //setTimeout(() => {doc._height = height; doc._width = width}, 10); return doc; }; public static CarouselField = (coords: { tl: [number, number]; br: [number, number] }, parentWidth: number, parentHeight: number, title: string, fields: Doc[]) => { const { width, height, coord } = FieldUtils.getDimensions(coords, parentWidth, parentHeight); const doc = Docs.Create.Carousel3DDocument(fields, { _height: height, _width: width, title: title, x: coord.x, y: coord.y, _text_fontSize: `${height / 2}` }); return doc; }; } // public static FourField002: TemplateDocInfos = { // width: 450, // height: 600, // fields: [{ // tl: [-.6, -.9], // br: [.6, -.8], // types: [FieldType.TEXT], // sizes: [FieldSize.TINY] // }, { // tl: [-.9, -.7], // br: [.9, .2], // types: [FieldType.TEXT, FieldType.VISUAL], // sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE] // }, { // tl: [-.9, .3], // br: [-.05, .9], // types: [FieldType.TEXT], // sizes: [FieldSize.TINY] // }, { // tl: [.05, .3], // br: [.9, .9], // types: [FieldType.TEXT, FieldType.VISUAL], // sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE] // }] // }; // public static TwoFieldPlusCarousel: TemplateDocInfos = { // width: 500, // height: 600, // fields: [{ // tl: [-.9, -.99], // br: [.9, -.7], // types: [FieldType.TEXT], // sizes: [FieldSize.TINY] // }, { // tl: [-.9, -.65], // br: [.9, .35], // types: [], // sizes: [] // }, { // tl: [-.9, .4], // br: [.9, .95], // types: [FieldType.TEXT], // sizes: [FieldSize.TINY] // }] // }; // } @observer export class DocCreatorMenu extends ObservableReactComponent { // eslint-disable-next-line no-use-before-define 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?: boolean; @observable _snapPt: { x: number; y: number } = { x: 0, y: 0 }; @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: FieldViewProps) { 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 ?? '', sizes: fieldInfo?.sizes ?? [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: React.PointerEvent, func: () => void) => { 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 => this.updateIcons(docs) ); this._disposers.gpt = reaction( () => this._suggestedTemplates.slice(), docs => this.updateIcons(docs) ); //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[]) => { console.log('called'); 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 Element).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: PointerEvent): 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: PointerEvent): 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 }) => { 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 = []; 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 = async () => { this.updateIcons(this._suggestedTemplates.slice()); this.forceUpdate(); // try { // const res = await gptImageCall('Image of panda eating a cookie'); // if (res) { // const result = await Networking.PostToServer('/uploadRemoteImage', { sources: res }); // console.log(result); // } // } catch (e) { // console.log(e); // } }; @action addField = () => { const newFields: Col[] = this._columns.concat([{ title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [] }]); 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); } } }; @action setColTitle = (column: Col, title: string) => { if (this.selectedFields.includes(column.title)) { this._dataViz?.setColumnTitle(column.title, title); } else { column.title = title; } this.forceUpdate(); }; @action setColType = (column: Col, type: TemplateFieldType) => { if (this.selectedFields.includes(column.title)) { this._dataViz?.setColumnType(column.title, type); } else { column.type = type; } this.forceUpdate(); }; modifyColSizes = (column: Col, size: TemplateFieldSize, valid: boolean) => { if (this.selectedFields.includes(column.title)) { this._dataViz?.modifyColumnSizes(column.title, size, valid); } else { if (!valid && column.sizes.includes(size)) { column.sizes.splice(column.sizes.indexOf(size), 1); } else if (valid && !column.sizes.includes(size)) { column.sizes.push(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(); }; generateGPTImage = async (prompt: string): Promise => { console.log(prompt); try { const res = await gptImageCall(prompt); if (res) { const result = await Networking.PostToServer('/uploadRemoteImage', { sources: res }); const source = ClientUtils.prepend(result[0].accessPaths.agnostic.client); return source; } } catch (e) { console.log(e); } }; matchesForTemplate = (template: TemplateDocInfos, cols: Col[]): number[][] => { const colMatchesField = (col: Col, field: Field) => { return field.sizes?.some(size => col.sizes?.includes(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); const augmentingPath = (v: number): boolean => { if (used[v]) return false; used[v] = true; for (const to of matches[v]) { 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[]) => { const validTemplates: string[] = []; 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); } }); return validTemplates .map(title => TemplateLayouts.getTemplateByTitle(title)) .filter(t => t) .map(t => t!); }; // 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; // } /** * 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 */ fillPresetTemplate = async (template: TemplateDocInfos, assignments: { [field: string]: Col }): Promise => { const fields: Doc[] = []; 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; } // prettier-ignore }; const stringifyGPTInfo = (calls: [string, Col][]): string => { let string: string = '*** COLUMN INFO:'; calls.forEach(([fieldNum, col]) => { string += `--- title: ${col.title}, prompt: ${col.desc}, word limit: ${wordLimit(col.sizes[0])} words, assigned field: ${fieldNum} ---`; }); return (string += ' ***'); }; 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 GPTTextCalls = GPTAssignments.filter(([str, col]) => col.type === TemplateFieldType.TEXT); const GPTIMGCalls = GPTAssignments.filter(([str, col]) => col.type === TemplateFieldType.VISUAL); const GPTTextAssignment = stringifyGPTInfo(GPTTextCalls); let fieldContent: string = ''; const renderTextCalls = async (): Promise => { const rendered: Doc[] = []; if (GPTTextCalls.length) { try { const prompt = fieldContent + GPTTextAssignment; const res = await gptAPICall(prompt, GPTCallType.FILL); if (res) { const asngs: { [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(asngs).forEach(([title, info]) => { const field: Field = template.fields[Number(info.number)]; const col = this.getColByTitle(title); const doc = FieldUtils.TextField( { tl: field.tl, br: field.br, }, template.height, template.width, col.title, info.content ?? '', field.opts ); rendered.push(doc); }); } } catch (err) { console.log(err); } } return rendered; }; const createGeneratedImage = async (fieldNum: string, col: Col, prompt: string) => { const url = await this.generateGPTImage(prompt); const field: Field = template.fields[Number(fieldNum)]; const doc = FieldUtils.ImageField( { tl: field.tl, br: field.br, }, template.height, template.width, col.title, url ?? '', field.opts ); return doc; }; const renderImageCalls = async (): Promise => { const rendered: Doc[] = []; const calls = GPTIMGCalls; if (calls.length) { try { const renderedImages: Doc[] = await Promise.all( calls.map(async ([fieldNum, col]) => { 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); console.log(sysPrompt, prompt); return createGeneratedImage(fieldNum, col, prompt); }) ); const renderedTemplates: Doc[] = await Promise.all(renderedImages); renderedTemplates.forEach(doc => rendered.push(doc)); } catch (e) { console.log(e); } } return rendered; }; Object.entries(nonGPTAssignments).forEach(([f, strCol]) => { const field: Field = template.fields[Number(f)]; const col = strCol[1]; const doc = (col.type === TemplateFieldType.VISUAL ? FieldUtils.ImageField : FieldUtils.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 = FieldUtils.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 ?? 0}px`, borderWidth: template.opts.borderWidth, borderColor: template.opts.borderColor, x: 40000, y: 40000, }); const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView; mainCollection.addDocument(main); return main; }; const textCalls = await renderTextCalls(); const imageCalls = await renderImageCalls(); textCalls.forEach(doc => { fields.push(doc); }); imageCalls.forEach(doc => { fields.push(doc); }); return createMainDoc(); }; 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}, 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: 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); ++this._callCount; const origCount = this._callCount; const prompt: string = `(${origCount}) ${inputText}`; this._GPTLoading = true; try { const res = await gptAPICall(prompt, GPTCallType.TEMPLATE); if (res && this._callCount === origCount) { 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.getTemplateByTitle(tempTitle); if (!template) return; 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, asgns]) => this.fillPresetTemplate(template, asgns)); const renderedTemplates: Doc[] = await Promise.all(renderedTemplatePromises); setTimeout(() => { this.setGSuggestedTemplates(renderedTemplates); this._GPTLoading = false; }); }; @action setExpandedView = (info: { icon: ImageField; doc: Doc } | undefined) => { if (info) { const doc = info.doc; const wrapper: Doc = Docs.Create.FreeformDocument([info.doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: '' }); const newInfo = { icon: new ImageField(''), doc: wrapper }; this._expandedPreview = newInfo; } else { this._expandedPreview = info; } }; get editingWindow() { const doc = this._expandedPreview?.doc ?? new Doc(); const rendered = (
Cast(doc.childLayoutTemplate, Doc, null)} isContentActive={emptyFunction} isAnyChildContentActive={() => true} select={emptyFunction} isSelected={returnFalse} fieldKey={Doc.LayoutFieldKey(doc)} addDocument={returnFalse} moveDocument={returnFalse} removeDocument={returnFalse} PanelWidth={() => this._menuDimensions.width - 10} PanelHeight={() => this._menuDimensions.height - 60} ScreenToLocalTransform={Transform.Identity} renderDepth={5} whenChildContentsActiveChanged={emptyFunction} focus={emptyFunction} styleProvider={DefaultStyleProvider} addDocTab={this._props.addDocTab} pinToPres={emptyFunction} childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} fitContentsToBox={returnTrue} xPadding={0} yPadding={0} />
); return (
{rendered}
); } get templatesPreviewContents() { const renderedTemplates: Doc[] = []; const GPTOptions =
; // return (
{this._expandedPreview ? ( this.editingWindow ) : (
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: (arg: string) => void, 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?: object, specialFunc?: () => void) => { 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 * 0.75) : null} {selectionBox( 60, 20, 'repeat', undefined, repeatOptions.map(num => ) )}
); } get dashboardContents() { const sizes: string[] = ['tiny', 'small', 'medium', 'large', 'huge']; const fieldPanel = (field: Col) => { return (
{`${field.title} Field`}
Title