aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx')
-rw-r--r--src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx694
1 files changed, 694 insertions, 0 deletions
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
new file mode 100644
index 000000000..ef53dde8e
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -0,0 +1,694 @@
+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, setupMoveUpEvents } from '../../../../ClientUtils';
+import { Doc, NumListCast } from '../../../../fields/Doc';
+import { DocCast, ImageCast } 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 } 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 { threadId } from 'worker_threads';
+import { ideahub } from 'googleapis/build/src/apis/ideahub';
+import { DragManager } from '../../../util/DragManager';
+import { DateField } from '../../../../fields/DateField';
+
+export enum LayoutType {
+ Stacked = 'stacked',
+ Grid = 'grid',
+ Row = 'row',
+ Column = 'column',
+ Custom = 'custom'
+}
+
+@observer
+export class DocCreatorMenu extends ObservableReactComponent<{}> {
+
+ static Instance: DocCreatorMenu;
+
+ private _ref: HTMLDivElement | null = null;
+
+ @observable _templateDocs: Doc[] = [];
+ @observable _selectedTemplate: Doc | undefined = 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 _pageX: number = 0;
+ @observable _pageY: number = 0;
+ @observable _indicatorX: number | undefined = undefined;
+ @observable _indicatorY: number | undefined = undefined;
+ @observable _display: boolean = false;
+
+ @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' = '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: 300, height: 400};
+
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ DocCreatorMenu.Instance = this;
+ }
+
+ @action setDataViz = (dataViz: DataVizBox) => { this._dataViz = dataViz };
+ @action setTemplateDocs = (docs: Doc[]) => {this._templateDocs = docs.map(doc => doc.annotationOn ? DocCast(doc.annotationOn):doc)};
+
+ @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 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();
+ 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;
+ }
+ };
+
+ _disposer: IReactionDisposer | undefined;
+ componentDidMount() {
+ document.addEventListener('pointerdown', this.onPointerDown, true);
+ document.addEventListener('pointerup', this.onPointerUp);
+ this._disposer = reaction(() => this._templateDocs.slice(), (docs) => docs.map(this.getIcon));
+ }
+
+ componentWillUnmount() {
+ this._disposer?.();
+ 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 = () => {
+ const wasOpen = this._display;
+ this._display = false;
+ this._shouldDisplay = false;
+ return wasOpen;
+ };
+
+ @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<any>(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(100, scale.x * width);
+ this._menuDimensions.height = Math.max(100, 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<ImageField | undefined>(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;
+ }
+
+ get templatesPreviewContents(){
+ const renderedTemplates: Doc[] = [];
+ return (
+ <div className='docCreatorMenu-preview-container'>
+ {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 (<div
+ className='docCreatorMenu-preview-window'
+ style={{
+ border: this._selectedTemplate === info.doc ? `solid 3px ${Colors.MEDIUM_BLUE}` : '',
+ boxShadow: this._selectedTemplate === info.doc ? `0 0 15px rgba(68, 118, 247, .8)` : ''
+ }}
+ onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(info.doc)))}>
+ <img className='docCreatorMenu-preview-image' src={info.icon!.url.href.replace(".png", "_o.png")} />
+ </div>
+ )})}
+ <div className='docCreatorMenu-preview-window empty'>
+ <FontAwesomeIcon icon='plus' color='rgb(160, 160, 160)'/>
+ </div>
+ </div>
+ );
+ }
+
+ get savedLayoutsPreviewContents(){
+ return (
+ <div className='docCreatorMenu-preview-container'>
+ {this._savedLayouts.map((layout, index) =>
+ <div
+ className='docCreatorMenu-preview-window'
+ style={{
+ border: this.isSelectedLayout(layout) ? `solid 3px ${Colors.MEDIUM_BLUE}` : '',
+ boxShadow: this.isSelectedLayout(layout) ? `0 0 15px rgba(68, 118, 247, .8)` : ''
+ }}
+ onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedSavedLayout(layout)))}
+ >
+ {this.layoutPreviewContents(87, layout, true, index)}
+ </div>
+ )}
+ </div>
+ );
+ }
+
+ @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 (
+ <div className='docCreatorMenu-option-container small no-margin' key={key} style={{marginTop: noMargin ? '0px' : ''}}
+ >
+ <div className='docCreatorMenu-option-title config layout-config'>
+ <FontAwesomeIcon icon={icon as any}/>
+ </div>
+ <input defaultValue={def} onInput={(e) => func(e.currentTarget.value)} className='docCreatorMenu-input config layout-config'/>
+ </div>
+ );
+ }
+
+ switch (this._layout.type) {
+ case LayoutType.Row:
+ return (
+ <div className='docCreatorMenu-configuration-bar'>
+ {optionInput('arrows-left-right', this.updateXMargin, this._layout.xMargin, '0')}
+ </div>
+ );
+ case LayoutType.Column:
+ return (
+ <div className='docCreatorMenu-configuration-bar'>
+ {optionInput('arrows-up-down', this.updateYMargin, this._layout.yMargin, '1')}
+ </div>
+ );
+ case LayoutType.Grid:
+ return (
+ <div className='docCreatorMenu-configuration-bar'>
+ {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)}
+ </div>
+ );
+ case LayoutType.Stacked:
+ return null;
+ default:
+ break;
+ }
+ }
+
+ 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 (
+ <div className='docCreatorMenu-layout-preview-window-wrapper' id={String(id) ?? undefined}>
+ <div className='docCreatorMenu-zoom-button-container'>
+ <button
+ className='docCreatorMenu-zoom-button'
+ onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this._layoutPreviewScale *= 1.25))}>
+ <FontAwesomeIcon icon={'minus'}/>
+ </button>
+ <button
+ className='docCreatorMenu-zoom-button zoom-in'
+ onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this._layoutPreviewScale *= .75))}>
+ <FontAwesomeIcon icon={'plus'}/>
+ </button>
+ {altLayout ? <button
+ className='docCreatorMenu-zoom-button trash'
+ onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this._savedLayouts.splice(this._savedLayouts.indexOf(altLayout), 1)))}>
+ <FontAwesomeIcon icon={'trash'}/>
+ </button> : null}
+ </div>
+ <div
+ id={String(id) ?? undefined}
+ className={`docCreatorMenu-layout-preview-window ${small ? 'small' : ''}`}
+ style={{
+ gridTemplateColumns: `repeat(${altLayout ? altLayout.columns : this.columnsCount}, ${scaledDown(docWidth)}px`,
+ gridTemplateRows: `${scaledDown(docHeight)}px`,
+ gridAutoRows: `${scaledDown(docHeight)}px`,
+ rowGap: `${scaledDown(layout.yMargin)}px`,
+ columnGap: `${scaledDown(layout.xMargin)}px`
+ }}>
+ {this._layout.type === LayoutType.Stacked ?
+ <div
+ className='docCreatorMenu-layout-preview-item'
+ style={{
+ width: scaledDown(docWidth),
+ height: scaledDown(docHeight),
+ fontSize: fontSize,
+ }}
+ >
+ All
+ </div> :
+ this.docsToRender.map(num =>
+ <div
+ onMouseEnter={() => this._dataViz?.setSpecialHighlightedRow(num)}
+ onMouseLeave={() => this._dataViz?.setSpecialHighlightedRow(undefined)}
+ className='docCreatorMenu-layout-preview-item'
+ style={{
+ width: scaledDown(docWidth),
+ height: scaledDown(docHeight),
+ fontSize: fontSize,
+ }}
+ >
+ {num}
+ </div>
+ )}
+
+ </div>
+ </div>
+ );
+ }
+
+ get optionsMenuContents(){
+ const layoutEquals = (layout: DataVizTemplateLayout) => {
+
+ } //TODO: ADD LATER
+
+ const layoutOption = (option: LayoutType, optStyle?: {}, specialFunc?: Function) => {
+ return (
+ <div
+ className="docCreatorMenu-dropdown-option"
+ style={optStyle}
+ onPointerDown={e => this.setUpButtonClick(e, () => {specialFunc?.(); runInAction(() => this._layout.type = option)})}>
+ {option}
+ </div>
+ );
+ }
+
+ const selectionBox = (width: number, height: number, icon: string, specClass?: string, options?: JSX.Element[], manual?: boolean): JSX.Element => {
+ return (<div className='docCreatorMenu-option-container'>
+ <div className={`docCreatorMenu-option-title config ${specClass}`} style={{width: width * .4, height: height}}>
+ <FontAwesomeIcon icon={icon as any}/>
+ </div>
+ {manual ? <input className={`docCreatorMenu-input config ${specClass}`} style={{width: width * .6, height: height}}/> :
+ <select className={`docCreatorMenu-input config ${specClass}`} style={{width: width * .6, height: height}}>
+ {options}
+ </select>
+ }
+ </div>);
+ }
+
+ const repeatOptions = [0, 1, 2, 3, 4, 5];
+
+ return (
+ <div className='docCreatorMenu-menu-container'>
+ <div className='docCreatorMenu-option-container layout'>
+ <div className='docCreatorMenu-dropdown-hoverable'>
+ <div className="docCreatorMenu-option-title">{this._layout.type ? this._layout.type.toUpperCase() : 'Choose Layout'}</div>
+ <div className="docCreatorMenu-dropdown-content">
+ {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`})}
+ </div>
+ </div>
+ <button
+ className='docCreatorMenu-menu-button preview-toggle'
+ onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this._layoutPreview = !this._layoutPreview))}>
+ <FontAwesomeIcon icon={this._layoutPreview ? 'minus' : 'magnifying-glass'}/>
+ </button>
+ </div>
+ {this._layout.type ? this.layoutConfigOptions: null}
+ {this._layoutPreview ? this.layoutPreviewContents(225) : null}
+ {selectionBox(60, 20, 'repeat', undefined, repeatOptions.map(num => <option onPointerDown={e => this._layout.repeat = num}>{`${num}x`}</option>))}
+ <hr className='docCreatorMenu-option-divider'/>
+ <div className='docCreatorMenu-general-options-container'>
+ <button
+ className='docCreatorMenu-save-layout-button'
+ onPointerDown={e => setupMoveUpEvents( this, e, returnFalse, emptyFunction,
+ undoable(clickEv => {
+ clickEv.stopPropagation();
+ if (!this._selectedTemplate) return;
+ const layout: DataVizTemplateLayout = {template: this._selectedTemplate, layout: {type: this._layout.type, xMargin: this._layout.xMargin, yMargin:this._layout.yMargin, repeat: 0}, columns: this.columnsCount, rows: this.rowsCount, docsNumList: this.docsToRender};
+ if (!this._savedLayouts.includes(layout)) { this._savedLayouts.push(layout) };
+ }, 'make docs')
+ )
+ }>
+ <FontAwesomeIcon icon='floppy-disk'/>
+ </button>
+ <button
+ className='docCreatorMenu-create-docs-button'
+ style={{backgroundColor: this.canMakeDocs ? '' : 'rgb(155, 155, 155)', border: this.canMakeDocs ? '' : 'solid 2px rgb(180, 180, 180)'}}
+ onPointerDown={e => setupMoveUpEvents( this, e, returnFalse, emptyFunction,
+ undoable(clickEv => {
+ clickEv.stopPropagation();
+ if (!this._selectedTemplate) return;
+ const templateInfo: DataVizTemplateInfo = {doc: this._selectedTemplate, layout: this._layout, referencePos: {x: this._pageX + 450, y: this._pageY}, columns: this.columnsCount};
+ this._dataViz?.createDocsFromTemplate(templateInfo);
+ }, 'make docs')
+ )
+ }>
+ <FontAwesomeIcon icon='plus'/>
+ </button>
+ </div>
+ </div>
+ );
+ }
+
+ get renderSelectedViewType(){
+ switch (this._menuContent){
+ case 'templates':
+ return this.templatesPreviewContents;
+ case 'options':
+ return this.optionsMenuContents;
+ case 'saved':
+ return this.savedLayoutsPreviewContents;
+ default:
+ return undefined;
+ }
+ }
+
+ get resizePanes(){
+ const ref = this._ref?.getBoundingClientRect();
+ const height: number = ref?.height ?? 0;
+ const width: number = ref?.width ?? 0;
+
+ return [
+ <div className='docCreatorMenu-resizer top' onPointerDown={this.onResizePointerDown} style={{width: width, left: 0, top: -7}}/>,
+ <div className='docCreatorMenu-resizer right' onPointerDown={this.onResizePointerDown} style={{height: height, left: width - 3, top: 0}}/>,
+ <div className='docCreatorMenu-resizer bottom' onPointerDown={this.onResizePointerDown} style={{width: width, left: 0, top: height - 3}}/>,
+ <div className='docCreatorMenu-resizer left' onPointerDown={this.onResizePointerDown} style={{height: height, left: -7, top: 0}}/>,
+ <div className='docCreatorMenu-resizer topRight' onPointerDown={this.onResizePointerDown} style={{left: width - 5, top: -10, cursor: 'nesw-resize'}}/>,
+ <div className='docCreatorMenu-resizer topLeft' onPointerDown={this.onResizePointerDown} style={{left: -10, top: -10, cursor: 'nwse-resize'}}/>,
+ <div className='docCreatorMenu-resizer bottomRight' onPointerDown={this.onResizePointerDown} style={{left: width - 5, top: height - 5, cursor: 'nwse-resize'}}/>,
+ <div className='docCreatorMenu-resizer bottomLeft' onPointerDown={this.onResizePointerDown} style={{left: -10, top: height - 5, cursor: 'nesw-resize'}}/>
+ ]; //prettier-ignore
+ }
+
+ render() {
+ const topButton = (icon: string, opt: string, func: Function, tag: string) => {
+ return (
+ <div className={`top-button-container ${tag} ${opt === this._menuContent ? 'selected' : ''}`}>
+ <div
+ className="top-button-content"
+ onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => {func()}))}>
+ <FontAwesomeIcon icon={icon as any}/>
+ </div>
+ </div>
+ );
+ }
+
+ const onPreviewSelected = () => {this._menuContent = 'templates'}
+ const onSavedSelected = () => {this._menuContent = 'saved'}
+ const onOptionsSelected = () => {
+ this._menuContent = 'options';
+ if (!this._layout.columns) this._layout.columns = Math.ceil(Math.sqrt(this.docsToRender.length));
+ }
+
+
+ return (
+ <div className='docCreatorMenu'>
+ {!this._shouldDisplay ? undefined :
+ <>
+ {/* <div className='docCreatorMenu-placement-indicator'
+ style={{
+ display: '',
+ left: this._indicatorX ?? this._pageX + 300,
+ top: this._indicatorY ?? this._pageY,
+ }}
+ onPointerMove={e => this.onPointerMove(e)}
+ onPointerDown={e =>
+ setupMoveUpEvents(
+ this,
+ e,
+ (e) => {
+ this._draggingIndicator = true;
+ this._startPos = {x: 0, y: 0};
+ this._startPos.x = e.pageX - (this._ref?.getBoundingClientRect().left ?? 0);
+ this._startPos.y = e.pageY - (this._ref?.getBoundingClientRect().top ?? 0);
+ return true;
+ },
+ emptyFunction,
+ undoable(clickEv => {
+ clickEv.stopPropagation();
+ }, 'drag menu')
+ )
+ }/> */}
+ <div
+ className="docCreatorMenu-cont"
+ ref={r => this._ref = r}
+ style={{
+ display: '',
+ left: this._pageX,
+ top: this._pageY,
+ width: this._menuDimensions.width,
+ height: this._menuDimensions.height,
+ background: SnappingManager.userBackgroundColor,
+ color: SnappingManager.userColor,
+ }}>
+ {this.resizePanes}
+ <div
+ className='docCreatorMenu-menu'
+ onPointerDown={e =>
+ setupMoveUpEvents(
+ this,
+ e,
+ (e) => {
+ this._dragging = true;
+ this._startPos = {x: 0, y: 0};
+ this._startPos.x = e.pageX - (this._ref?.getBoundingClientRect().left ?? 0);
+ this._startPos.y = e.pageY - (this._ref?.getBoundingClientRect().top ?? 0);
+ document.addEventListener('pointermove', this.onDrag);
+ return true;
+ },
+ emptyFunction,
+ undoable(clickEv => {
+ clickEv.stopPropagation();
+ }, 'drag menu')
+ )
+ }
+ >
+ <div className='docCreatorMenu-top-buttons-container'>
+ {topButton('table-cells', 'templates', onPreviewSelected, 'left')}
+ {topButton('bars', 'options', onOptionsSelected, 'middle')}
+ {topButton('floppy-disk', 'saved', onSavedSelected, 'right')}
+ </div>
+ <button
+ className='docCreatorMenu-menu-button close-menu'
+ onPointerDown={e => this.setUpButtonClick(e, this.closeMenu)}>
+ <FontAwesomeIcon icon={'minus'}/>
+ </button>
+ </div>
+ {this.renderSelectedViewType}
+ </div>
+ </>
+ }
+ </div>
+ )
+ }
+}
+
+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;
+} \ No newline at end of file