diff options
Diffstat (limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx')
-rw-r--r-- | src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx | 694 |
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 |