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 { 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'; 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; 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; } 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 (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 onPointerMove = (e: any) => { // if (this._draggingIndicator) { // this._indicatorX = e.pageX - (this._startPos?.x ?? 0) + this._pageX; // this._indicatorY = e.pageY - (this._startPos?.y ?? 0) + this._pageY; // } else if (this._dragging){ this._pageX = e.pageX - (this._startPos?.x ?? 0); this._pageY = e.pageY - (this._startPos?.y ?? 0); } } 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; } get templatesPreviewContents(){ const renderedTemplates: Doc[] = []; return (
{this._templateDocs.map(doc => ({icon: ImageCast(doc.icon), doc})).filter(info => info.icon && info.doc).map(info => { if (renderedTemplates.includes(info.doc)) return undefined; renderedTemplates.push(info.doc); return (
this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(info.doc)))}>
)})}
); } get savedLayoutsPreviewContents(){ return (
{this._savedLayouts.map((layout, index) =>
this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedSavedLayout(layout)))} > {this.layoutPreviewContents(87, layout, true, index)}
)}
); } @action updateXMargin = (input: string) => { this._layout.xMargin = Number(input) }; @action updateYMargin = (input: string) => { this._layout.yMargin = Number(input) }; @action updateColumns = (input: string) => { this._layout.columns = Number(input) }; get layoutConfigOptions() { const optionInput = (icon: string, func: Function, def?: number, key?: string, noMargin?: boolean) => { return (
func(e.currentTarget.value)} className='docCreatorMenu-input config layout-config'/>
); } switch (this._layout.type) { case LayoutType.Row: return (
{optionInput('arrows-left-right', this.updateXMargin, this._layout.xMargin, '0')}
); case LayoutType.Column: return (
{optionInput('arrows-up-down', this.updateYMargin, this._layout.yMargin, '1')}
); case LayoutType.Grid: return (
{optionInput('arrows-up-down', this.updateYMargin, this._layout.xMargin, '2')} {optionInput('arrows-left-right', this.updateXMargin, this._layout.xMargin, '3')} {optionInput('table-columns', this.updateColumns, this._layout.columns, '4', true)}
); case LayoutType.Stacked: return null; default: break; } } 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 (
{altLayout ? : null}
{this._layout.type === LayoutType.Stacked ?
All
: this.docsToRender.map(num =>
this._dataViz?.setSpecialHighlightedRow(num)} onMouseLeave={() => this._dataViz?.setSpecialHighlightedRow(undefined)} className='docCreatorMenu-layout-preview-item' style={{ width: scaledDown(docWidth), height: scaledDown(docHeight), fontSize: fontSize, }} > {num}
)}
); } get optionsMenuContents(){ const layoutEquals = (layout: DataVizTemplateLayout) => { } //TODO: ADD LATER const layoutOption = (option: LayoutType, optStyle?: {}, specialFunc?: Function) => { return (
this.setUpButtonClick(e, () => {specialFunc?.(); runInAction(() => this._layout.type = option)})}> {option}
); } const selectionBox = (width: number, height: number, icon: string, specClass?: string, options?: JSX.Element[], manual?: boolean): JSX.Element => { return (
{manual ? : }
); } const repeatOptions = [0, 1, 2, 3, 4, 5]; return (
{this._layout.type ? this._layout.type.toUpperCase() : 'Choose Layout'}
{layoutOption(LayoutType.Stacked)} {layoutOption(LayoutType.Grid, undefined, () => {if (!this._layout.columns) this._layout.columns = Math.ceil(Math.sqrt(this.docsToRender.length))})} {layoutOption(LayoutType.Row)} {layoutOption(LayoutType.Column)} {layoutOption(LayoutType.Custom, {borderBottom: `0px`})}
{this._layout.type ? this.layoutConfigOptions: null} {this._layoutPreview ? this.layoutPreviewContents(225) : null} {selectionBox(60, 20, 'repeat', undefined, repeatOptions.map(num => ))}
); } get renderSelectedViewType(){ switch (this._menuContent){ case 'templates': return this.templatesPreviewContents; case 'options': return this.optionsMenuContents; case 'saved': return this.savedLayoutsPreviewContents; default: return undefined; } } render() { const topButton = (icon: string, opt: string, func: Function, tag: string) => { return (
this.setUpButtonClick(e, () => runInAction(() => {func()}))}>
); } 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 (
{!this._shouldDisplay ? undefined : <> {/*
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') ) }/> */}
this._ref = r} style={{ display: '', left: this._pageX, top: this._pageY, width: 300, height: 400, background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor, }}>
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); return true; }, emptyFunction, undoable(clickEv => { clickEv.stopPropagation(); }, 'drag menu') ) } onPointerMove={e => this.onPointerMove(e)} onPointerUp={() => this._dragging = false} >
{topButton('table-cells', 'templates', onPreviewSelected, 'left')} {topButton('bars', 'options', onOptionsSelected, 'middle')} {topButton('floppy-disk', 'saved', onSavedSelected, 'right')}
{this.renderSelectedViewType}
}
) } } 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; }