import { Colors } from '@dash/components'; import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, makeObservable, observable, 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, setupMoveUpEvents } from '../../../../../ClientUtils'; import { emptyFunction } from '../../../../../Utils'; import { Doc, NumListCast, StrListCast, returnEmptyDoclist } from '../../../../../fields/Doc'; import { Id } from '../../../../../fields/FieldSymbols'; import { ImageCast, StrCast } from '../../../../../fields/Types'; import { ImageField } from '../../../../../fields/URLField'; import { Upload } from '../../../../../server/SharedMediaTypes'; import { Networking } from '../../../../Network'; import { GPTCallType, gptAPICall, gptImageCall } from '../../../../apis/gpt/GPT'; import { Docs, DocumentOptions } from '../../../../documents/Documents'; import { DragManager } from '../../../../util/DragManager'; import { SnappingManager } from '../../../../util/SnappingManager'; import { Transform } from '../../../../util/Transform'; import { UndoManager, undoable } from '../../../../util/UndoManager'; import { ObservableReactComponent } from '../../../ObservableReactComponent'; import { DefaultStyleProvider } from '../../../StyleProvider'; import { CollectionFreeFormView } from '../../../collections/collectionFreeForm/CollectionFreeFormView'; import { DocumentView, DocumentViewInternal } from '../../DocumentView'; import { OpenWhere } from '../../OpenWhere'; import { DataVizBox } from '../DataVizBox'; import './DocCreatorMenu.scss'; import { TemplateField, ViewType } from './TemplateFieldTypes/TemplateField'; import { Template } from './Template'; import { TemplateFieldSize, TemplateFieldType, TemplateLayouts } from './TemplateBackend'; import { Conditional, TemplateManager } from './Backend/TemplateManager'; import { DrawingFillHandler } from '../../../smartdraw/DrawingFillHandler'; import { CgPathIntersect } from 'react-icons/cg'; import { StaticContentField } from './TemplateFieldTypes/StaticContentField'; import { TemplateMenuAIUtils } from './Backend/TemplateMenuAIUtils' import { TemplatePreviewGrid } from './Menu/TemplatePreviewGrid'; import { FireflyStructureOptions, TemplateEditingWindow } from './Menu/TemplateEditingWindow'; import { DocCreatorMenuButton } from './Menu/DocCreatorMenuButton'; export enum LayoutType { FREEFORM = 'Freeform', CAROUSEL = 'Carousel', CAROUSEL3D = '3D Carousel', MASONRY = 'Masonry', CARD = 'Card View', } 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 type Col = { sizes: TemplateFieldSize[]; desc: string; title: string; type: TemplateFieldType; defaultContent?: string; AIGenerated?: boolean; }; interface DocCreateMenuProps { addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean; } @observer export class DocCreatorMenu extends ObservableReactComponent { // eslint-disable-next-line no-use-before-define static Instance: DocCreatorMenu; private DEBUG_MODE: boolean = true; private _ref: HTMLDivElement | null = null; private templateManager: TemplateManager; @observable _fullyRenderedDocs: Doc[] = []; // collection of templates filled in with content @observable _renderedDocCollection: Doc | undefined = undefined; // fullyRenderedDocs in a parent collection @observable _docsRendering: boolean = false; // dictates loading symbol @observable _userTemplates: Template[] = []; @observable _selectedTemplate: Template | undefined = undefined; @observable _currEditingTemplate: Template | undefined = undefined; @observable _userCreatedFields: Col[] = []; @observable _collapsedCols: String[] = []; //any columns whose options panels are hidden @observable _currEditingConditional: Conditional = {} as Conditional; @observable _layout: { type: LayoutType; yMargin: number; xMargin: number; columns?: number; repeat: number } = { type: LayoutType.FREEFORM, yMargin: 10, xMargin: 10, columns: 3, repeat: 0 }; @observable _savedLayouts: DataVizTemplateLayout[] = []; @observable _loadingVariants: boolean = false; @observable _suggestedTemplates: Template[] = []; @observable _GPTLoading: boolean = false; @observable _pageX: number = 0; @observable _pageY: number = 0; @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' | 'templateEditing' = 'templates'; @observable _dragging: boolean = false; @observable _dataViz?: DataVizBox; @observable _interactionLock: boolean | undefined; @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 _variations: Template[] = []; constructor(props: DocCreateMenuProps) { super(props); makeObservable(this); DocCreatorMenu.Instance = this; this.templateManager = new TemplateManager(TemplateLayouts.allTemplates); } @action setDataViz = (dataViz: DataVizBox) => { this._dataViz = dataViz; this._selectedTemplate = undefined; this._renderedDocCollection = undefined; this._fullyRenderedDocs = []; this._suggestedTemplates = []; this._userCreatedFields = []; }; @action setSuggestedTemplates = (templates: Template[]) => { this._suggestedTemplates = templates; //prettier-ignore }; @computed get docsToRender() { if (this.DEBUG_MODE) { return [1, 2, 3, 4]; } else { return this._selectedTemplate ? NumListCast(this._dataViz?.layoutDoc.dataViz_selectedRows) : []; } } @computed get rowsCount() { switch (this._layout.type) { case LayoutType.FREEFORM: return Math.ceil(this.docsToRender.length / (this._layout.columns ?? 1)) ?? 0; case LayoutType.CAROUSEL3D: return 1.8; default: return 1; } } @computed get columnsCount() { switch (this._layout.type) { case LayoutType.FREEFORM: return this._layout.columns ?? 0; case LayoutType.CAROUSEL3D: return 3; default: return 1; } } @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._userCreatedFields); } @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: (...args: any) => 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); } componentWillUnmount() { document.removeEventListener('pointerdown', this.onPointerDown, true); document.removeEventListener('pointerup', this.onPointerUp); } @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 = () => { this._shouldDisplay = true; }; @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 HTMLElement).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; }; @action updateRenderedPreviewCollection = async (template: Template) => { this._docsRendering = true; this._fullyRenderedDocs = this._dataViz ? ((await this.templateManager.createDocsFromTemplate(this._dataViz, template, this.fieldsInfos, this.DEBUG_MODE)).filter(doc => doc).map(doc => doc!) ?? []) as unknown as Doc[] : []; this.updateRenderedDocCollection(); }; @action updateSelectedTemplate = async (template: Template) => { if (this._selectedTemplate === template) { this._selectedTemplate = undefined; return; } else { this._selectedTemplate = template; this.updateRenderedPreviewCollection(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; }; editTemplate = (doc: Doc) => { DocumentViewInternal.addDocTabFunc(doc, OpenWhere.addRight); DocumentView.DeselectAll(); Doc.UnBrushDoc(doc); }; testTemplate = async () => { this._suggestedTemplates = this.templateManager.templates; //prettier-ignore //console.log(this.templateManager.templates) // const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView; // this.templateManager.templates.forEach(template => { // const doc = template.mainField.renderedDoc(); // mainCollection.addDocument(doc); // }) // 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._userCreatedFields.concat([{ title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [], AIGenerated: true }]); this._userCreatedFields = 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._userCreatedFields.filter(f => f === field); if (!toRemove) return; if (toRemove.length > 1) { while (toRemove.length > 1) { toRemove.pop(); } } if (this._userCreatedFields.length === 1) { this._userCreatedFields = []; } else { this._userCreatedFields.splice(this._userCreatedFields.indexOf(toRemove[0]), 1); } } }; @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 (type === TemplateFieldType.DATA) { this.templateManager.addDataField(column.title); } else if (column.type === TemplateFieldType.DATA) { this.templateManager.removeDataField(column.title); } 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(); }; compileFieldDescriptions = (templates: Template[]): string => { let descriptions: string = ''; templates.forEach(template => { descriptions += `---------- NEW TEMPLATE TO INCLUDE: The title is: ${template.title}. Its fields are: `; descriptions += template.descriptionSummary; }); return descriptions; }; compileColDescriptions = (cols: Col[]): string => { let descriptions: string = ' ------------- COL DESCRIPTIONS START HERE:'; cols.forEach(col => (descriptions += `{title: ${col.title}, sizes: ${String(col.sizes)}, type: ${col.type}, descreiption: ${col.desc}} `)); return descriptions; }; getColByTitle = (title: string) => { return this.fieldsInfos.filter(col => col.title === title)[0]; }; @action assignColsToFields = async (templates: Template[], cols: Col[]): Promise<[Template, { [field: number]: Col }][]> => { const fieldDescriptions: string = this.compileFieldDescriptions(templates); const colDescriptions: string = this.compileColDescriptions(cols); const inputText = fieldDescriptions.concat(colDescriptions); const prompt: string = `(${Math.random() * 100000}) ${inputText}`; this._GPTLoading = true; try { const res = await gptAPICall(prompt, GPTCallType.TEMPLATE); if (res) { const assignments: { [templateTitle: string]: { [fieldID: string]: string } } = JSON.parse(res); const brokenDownAssignments: [Template, { [fieldID: number]: Col }][] = []; Object.entries(assignments).forEach(([tempTitle, assignment]) => { const template = templates.filter(temp => temp.title === tempTitle)[0]; if (!template) return; const toObj = Object.entries(assignment).reduce( (a, [fieldID, colTitle]) => { const col = this.getColByTitle(colTitle); if (!col.AIGenerated) { var field = template.getFieldByID(Number(fieldID)); field.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? ViewType.IMG : ViewType.TEXT); field = template.getFieldByID(Number(fieldID)); field.setTitle(col.title); } else { a[Number(fieldID)] = this.getColByTitle(colTitle); } return a; }, {} as { [field: number]: Col } ); brokenDownAssignments.push([template, toObj]); }); 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(); const contentFields = this.fieldsInfos.filter(field => field.type !== TemplateFieldType.DATA); templates.push(...this.templateManager.getValidTemplates(contentFields)); const assignments = await this.assignColsToFields(templates, contentFields); const renderedTemplatePromises = assignments.map(([template, assgns]) => TemplateMenuAIUtils.applyGPTContentToTemplate(template, assgns)); await Promise.all(renderedTemplatePromises); } setTimeout( action(() => { this.setSuggestedTemplates(templates); this._GPTLoading = false; }) ); }; generateVariations = async (onDoc: Doc, prompt: string, options: FireflyStructureOptions): Promise => { const { numVariations, temperature, useStyleRef } = options; this.variations = []; const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView; console.log('styleref: ', useStyleRef); const clone: Doc = (await Doc.MakeClone(onDoc)).clone; mainCollection.addDocument(clone); clone.x = 10000; clone.y = 10000; await DrawingFillHandler.drawingToImage(clone, 100 - temperature, prompt, useStyleRef ? clone : undefined, this, numVariations) return this.variations; } variations: string[] = [] @action addVariation = (url: string) => { this.variations.push(url); } addRenderedCollectionToMainview = () => { const collection = this._renderedDocCollection; if (collection) { const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView; collection.x = this._pageX - this._menuDimensions.width; collection.y = this._pageY - this._menuDimensions.height; mainCollection?.addDocument(collection); this.closeMenu(); } }; @action setExpandedView = (template: Template | undefined) => { this._currEditingTemplate = template; if (template) { this._menuContent = 'templateEditing'; } else { this._menuContent = 'templates'; } //Docs.Create.FreeformDocument([doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: ''}); }; @action updateXMargin = (input: string) => { this._layout.xMargin = Number(input); setTimeout(() => { if (!this._renderedDocCollection || !this._fullyRenderedDocs) return; this.applyLayout(this._renderedDocCollection, this._fullyRenderedDocs); }); }; @action updateYMargin = (input: string) => { this._layout.yMargin = Number(input); setTimeout(() => { if (!this._renderedDocCollection || !this._fullyRenderedDocs) return; this.applyLayout(this._renderedDocCollection, this._fullyRenderedDocs); }); }; @action updateColumns = (input: string) => { this._layout.columns = Number(input); this.updateRenderedDocCollection(); }; get layoutConfigOptions() { const optionInput = (icon: string, func: (input: 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.FREEFORM: 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)}
); default: break; } } applyLayout = (collection: Doc, docs: Doc[]) => { const { horizontalSpan, verticalSpan } = this.previewInfo; collection._height = verticalSpan; collection._width = horizontalSpan; const columns: number = this._layout.columns ?? this.columnsCount; const xGap: number = this._layout.xMargin; const yGap: number = this._layout.yMargin; const startX: number = -Number(collection._width) / 2; const startY: number = -Number(collection._height) / 2; const docHeight: number = Number(docs[0]._height); const docWidth: number = Number(docs[0]._width); if (columns === 0 || docs.length === 0) { return; } let i: number = 0; let docsChanged: number = 0; let curX: number = startX; let curY: number = startY; while (docsChanged < docs.length) { while (i < columns && docsChanged < docs.length) { docs[docsChanged].x = curX; docs[docsChanged].y = curY; curX += docWidth + xGap; ++docsChanged; ++i; } i = 0; curX = startX; curY += docHeight + yGap; } }; @computed get previewInfo() { const docHeight: number = Number(this._fullyRenderedDocs[0]._height); const docWidth: number = Number(this._fullyRenderedDocs[0]._width); const layout = this._layout; return { docHeight: docHeight, docWidth: docWidth, horizontalSpan: (docWidth + layout.xMargin) * this.columnsCount - layout.xMargin, verticalSpan: (docHeight + layout.yMargin) * this.rowsCount - layout.yMargin, }; } /** * Updates the preview that shows how all docs will be rendered in the chosen collection type. @type the type of collection the docs should render to (ie. freeform, carousel, card) */ updateRenderedDocCollection = () => { if (!this._fullyRenderedDocs) return; const collectionFactory = (): ((docs: Doc[], options: DocumentOptions) => Doc) => { switch (this._layout.type) { case LayoutType.CAROUSEL3D: return Docs.Create.Carousel3DDocument; case LayoutType.FREEFORM: return Docs.Create.FreeformDocument; case LayoutType.CARD: return Docs.Create.CardDeckDocument; case LayoutType.MASONRY: return Docs.Create.MasonryDocument; case LayoutType.CAROUSEL: return Docs.Create.CarouselDocument; default: return Docs.Create.FreeformDocument; } // prettier-ignore }; const collection = collectionFactory()(this._fullyRenderedDocs, { isDefaultTemplateDoc: true, title: 'title', backgroundColor: 'gray', x: 200, y: 200, _width: 4000, _height: 4000, }); this.applyLayout(collection, this._fullyRenderedDocs); this._renderedDocCollection = collection; this._docsRendering = false; this.forceUpdate(); }; layoutPreviewContents = action(() => { return this._docsRendering ? (
) : !this._renderedDocCollection ? null : (
this._menuDimensions.width - 80} PanelHeight={() => this._menuDimensions.height - 105} ScreenToLocalTransform={() => new Transform(-this._pageX - 5, -this._pageY - 35, 1)} renderDepth={5} whenChildContentsActiveChanged={emptyFunction} focus={emptyFunction} styleProvider={DefaultStyleProvider} addDocTab={this._props.addDocTab} pinToPres={() => undefined} childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} fitContentsToBox={returnFalse} fitWidth={returnFalse} hideDecorations={true} />
); }); get optionsMenuContents() { const layoutOption = (option: LayoutType, optStyle?: object, specialFunc?: () => void) => { return (
this.setUpButtonClick(e, () => { specialFunc?.(); runInAction(() => { this._layout.type = option; this.updateRenderedDocCollection(); }); }) }> {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.FREEFORM, undefined, () => { if (!this._layout.columns) this._layout.columns = Math.ceil(Math.sqrt(this.docsToRender.length)); })} {layoutOption(LayoutType.CAROUSEL)} {layoutOption(LayoutType.CAROUSEL3D)} {layoutOption(LayoutType.MASONRY)}
{this._layout.type ? this.layoutConfigOptions : null} {this.layoutPreviewContents()} {selectionBox( 60, 20, 'repeat', undefined, repeatOptions.map(num => ) )}
); } get dashboardContents() { const contentFieldTitles = this.fieldsInfos.filter(field => field.type !== TemplateFieldType.DATA).map(field => field.title).concat('Template'); const conditionForm = (title: string, parameters?: Conditional, empty: boolean = false) => { const params: Conditional = parameters ?? this._currEditingConditional; return (
If
{title}
{params.operator ?? '='}
{params.operator = '='}}>{'='}
{params.operator = '>'}}>{'>'}
{params.operator = '<'}}>{'<'}
{params.operator = 'contains'}}>{'has'}