import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as React from "react"; import { Doc, DocListCast } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { List } from "../../../fields/List"; import { listSpec } from "../../../fields/Schema"; import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types"; import { Utils, emptyFunction, setupMoveUpEvents } from "../../../Utils"; import { DragManager } from "../../util/DragManager"; import { undoBatch } from "../../util/UndoManager"; import { EditableView } from "../EditableView"; import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss"; import { CollectionViewType } from "./CollectionView"; import { CollectionView } from "./CollectionView"; import "./CollectionViewChromes.scss"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; const datepicker = require('js-datepicker'); interface CollectionViewChromeProps { CollectionView: CollectionView; type: CollectionViewType; collapse?: (value: boolean) => any; PanelWidth: () => number; } interface Filter { key: string; value: string; contains: boolean; } const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation(); @observer export class CollectionViewBaseChrome extends React.Component { //(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\) get target() { return this.props.CollectionView.props.Document; } _templateCommand = { params: ["target", "source"], title: "=> item view", script: "this.target.childLayout = getDocTemplate(this.source?.[0])", immediate: (source: Doc[]) => this.target.childLayout = Doc.getDocTemplate(source?.[0]), initialize: emptyFunction, }; _narrativeCommand = { params: ["target", "source"], title: "=> child click view", script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])", immediate: (source: Doc[]) => this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]), initialize: emptyFunction, }; _contentCommand = { params: ["target", "source"], title: "=> content", script: "getProto(this.target).data = copyField(this.source);", immediate: (source: Doc[]) => Doc.GetProto(this.target).data = new List(source), // Doc.aliasDocs(source), initialize: emptyFunction, }; _viewCommand = { params: ["target"], title: "=> saved view", script: "this.target._panX = this.restoredPanX; this.target._panY = this.restoredPanY; this.target.scale = this.restoredScale;", immediate: (source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target.scale = 1; }, initialize: (button: Doc) => { button.restoredPanX = this.target._panX; button.restoredPanY = this.target._panY; button.restoredScale = this.target.scale; }, }; _freeform_commands = [this._contentCommand, this._templateCommand, this._narrativeCommand, this._viewCommand]; _stacking_commands = [this._contentCommand, this._templateCommand]; _masonry_commands = [this._contentCommand, this._templateCommand]; _schema_commands = [this._templateCommand, this._narrativeCommand]; _tree_commands = []; private get _buttonizableCommands() { switch (this.props.type) { case CollectionViewType.Tree: return this._tree_commands; case CollectionViewType.Schema: return this._schema_commands; case CollectionViewType.Stacking: return this._stacking_commands; case CollectionViewType.Masonry: return this._stacking_commands; case CollectionViewType.Freeform: return this._freeform_commands; case CollectionViewType.Time: return this._freeform_commands; case CollectionViewType.Carousel: return this._freeform_commands; } return []; } private _picker: any; private _commandRef = React.createRef(); private _viewRef = React.createRef(); @observable private _currentKey: string = ""; componentDidMount = action(() => { // chrome status is one of disabled, collapsed, or visible. this determines initial state from document switch (this.props.CollectionView.props.Document._chromeStatus) { case "disabled": throw new Error("how did you get here, if chrome status is 'disabled' on a collection, a chrome shouldn't even be instantiated!"); case "collapsed": this.props.collapse?.(true); break; } }); @undoBatch viewChanged = (e: React.ChangeEvent) => { //@ts-ignore this.document._viewType = e.target.selectedOptions[0].value; } commandChanged = (e: React.ChangeEvent) => { //@ts-ignore runInAction(() => this._currentKey = e.target.selectedOptions[0].value); } @action toggleViewSpecs = (e: React.SyntheticEvent) => { this.document._facetWidth = this.document._facetWidth ? 0 : 200; e.stopPropagation(); } @action closeViewSpecs = () => { this.document._facetWidth = 0; } // @action // openDatePicker = (e: React.PointerEvent) => { // if (this._picker) { // this._picker.alwaysShow = true; // this._picker.show(); // // TODO: calendar is offset when zoomed in/out // // this._picker.calendar.style.position = "absolute"; // // let transform = this.props.CollectionView.props.ScreenToLocalTransform(); // // let x = parseInt(this._picker.calendar.style.left) / transform.Scale; // // let y = parseInt(this._picker.calendar.style.top) / transform.Scale; // // this._picker.calendar.style.left = x; // // this._picker.calendar.style.top = y; // e.stopPropagation(); // } // } // runInAction(() => this._dateValue = e.target.value)} // onPointerDown={this.openDatePicker} // placeholder="Value" /> // @action.bound // applyFilter = (e: React.MouseEvent) => { // const keyRestrictionScript = "(" + this._keyRestrictions.map(i => i[1]).filter(i => i.length > 0).join(" && ") + ")"; // const yearOffset = this._dateWithinValue[1] === 'y' ? 1 : 0; // const monthOffset = this._dateWithinValue[1] === 'm' ? parseInt(this._dateWithinValue[0]) : 0; // const weekOffset = this._dateWithinValue[1] === 'w' ? parseInt(this._dateWithinValue[0]) : 0; // const dayOffset = (this._dateWithinValue[1] === 'd' ? parseInt(this._dateWithinValue[0]) : 0) + weekOffset * 7; // let dateRestrictionScript = ""; // if (this._dateValue instanceof Date) { // const lowerBound = new Date(this._dateValue.getFullYear() - yearOffset, this._dateValue.getMonth() - monthOffset, this._dateValue.getDate() - dayOffset); // const upperBound = new Date(this._dateValue.getFullYear() + yearOffset, this._dateValue.getMonth() + monthOffset, this._dateValue.getDate() + dayOffset + 1); // dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`; // } // else { // const createdDate = new Date(this._dateValue); // if (!isNaN(createdDate.getTime())) { // const lowerBound = new Date(createdDate.getFullYear() - yearOffset, createdDate.getMonth() - monthOffset, createdDate.getDate() - dayOffset); // const upperBound = new Date(createdDate.getFullYear() + yearOffset, createdDate.getMonth() + monthOffset, createdDate.getDate() + dayOffset + 1); // dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`; // } // } // const fullScript = dateRestrictionScript.length || keyRestrictionScript.length ? dateRestrictionScript.length ? // `${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} (${keyRestrictionScript})` : // `(${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` : // "true"; // this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(fullScript, { doc: Doc.name }); // } // datePickerRef = (node: HTMLInputElement) => { // if (node) { // try { // this._picker = datepicker("#" + node.id, { // disabler: (date: Date) => date > new Date(), // onSelect: (instance: any, date: Date) => runInAction(() => {}), // this._dateValue = date), // dateSelected: new Date() // }); // } catch (e) { // console.log("date picker exception:" + e); // } // } // } @action toggleCollapse = () => { this.document._chromeStatus = this.document._chromeStatus === "enabled" ? "collapsed" : "enabled"; if (this.props.collapse) { this.props.collapse(this.props.CollectionView.props.Document._chromeStatus !== "enabled"); } } @computed get subChrome() { const collapsed = this.document._chromeStatus !== "enabled"; if (collapsed) return null; switch (this.props.type) { case CollectionViewType.Freeform: return (); case CollectionViewType.Stacking: return (); case CollectionViewType.Schema: return (); case CollectionViewType.Tree: return (); case CollectionViewType.Masonry: return (); default: return null; } } private get document() { return this.props.CollectionView.props.Document; } private dropDisposer?: DragManager.DragDropDisposer; protected createDropTarget = (ele: HTMLDivElement) => { this.dropDisposer?.(); if (ele) { this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.document); } } @undoBatch @action protected drop(e: Event, de: DragManager.DropEvent): boolean { if (de.complete.docDragData && de.complete.docDragData.draggedDocuments.length) { this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(de.complete.docDragData?.draggedDocuments || [])); e.stopPropagation(); } return true; } dragViewDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, (e, down, delta) => { const vtype = this.props.CollectionView.collectionViewType; const c = { params: ["target"], title: vtype, script: `this.target._viewType = '${StrCast(this.props.CollectionView.props.Document._viewType)}'`, immediate: (source: Doc[]) => this.props.CollectionView.props.Document._viewType = Doc.getDocTemplate(source?.[0]), initialize: emptyFunction, }; DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title), { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY); return true; }, emptyFunction, emptyFunction); } dragCommandDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, (e, down, delta) => { this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title, { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY)); return true; }, emptyFunction, emptyFunction); } @computed get templateChrome() { const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled"; return
; } @computed get viewModes() { const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled"; return
; } render() { const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled"; const scale = Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform().Scale); return (
{this.viewModes}
{this.templateChrome}
{this.subChrome}
); } } @observer export class CollectionFreeFormViewChrome extends React.Component { get Document() { return this.props.CollectionView.props.Document; } @computed get dataField() { return this.props.CollectionView.props.Document[Doc.LayoutFieldKey(this.props.CollectionView.props.Document)]; } @computed get childDocs() { return DocListCast(this.dataField); } @undoBatch @action nextKeyframe = (): void => { const currentFrame = NumCast(this.Document.currentFrame); if (currentFrame === undefined) { this.Document.currentFrame = 0; CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); } CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0); this.Document.currentFrame = Math.max(0, (currentFrame || 0) + 1); this.Document.lastFrame = Math.max(NumCast(this.Document.currentFrame), NumCast(this.Document.lastFrame)); } @undoBatch @action prevKeyframe = (): void => { const currentFrame = NumCast(this.Document.currentFrame); if (currentFrame === undefined) { this.Document.currentFrame = 0; CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); } CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice()); this.Document.currentFrame = Math.max(0, (currentFrame || 0) - 1); } render() { return this.Document.isAnnotationOverlay ? (null) :
this.Document.editing = !this.Document.editing)} > {NumCast(this.Document.currentFrame)}
; } } @observer export class CollectionStackingViewChrome extends React.Component { @observable private _currentKey: string = ""; @observable private suggestions: string[] = []; @computed private get descending() { return BoolCast(this.props.CollectionView.props.Document.stackingHeadersSortDescending); } @computed get pivotField() { return StrCast(this.props.CollectionView.props.Document._pivotField); } getKeySuggestions = async (value: string): Promise => { value = value.toLowerCase(); const docs = DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey]); if (docs instanceof Doc) { return Object.keys(docs).filter(key => key.toLowerCase().startsWith(value)); } else { const keys = new Set(); docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); return Array.from(keys).filter(key => key.toLowerCase().startsWith(value)); } } @action onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => { this._currentKey = newValue; } getSuggestionValue = (suggestion: string) => suggestion; renderSuggestion = (suggestion: string) => { return

{suggestion}

; } onSuggestionFetch = async ({ value }: { value: string }) => { const sugg = await this.getKeySuggestions(value); runInAction(() => { this.suggestions = sugg; }); } @action onSuggestionClear = () => { this.suggestions = []; } @action setValue = (value: string) => { this.props.CollectionView.props.Document._pivotField = value; return true; } @action toggleSort = () => { this.props.CollectionView.props.Document.stackingHeadersSortDescending = !this.props.CollectionView.props.Document.stackingHeadersSortDescending; }; @action resetValue = () => { this._currentKey = this.pivotField; }; render() { return (
GROUP BY:
this.pivotField} autosuggestProps={ { resetValue: this.resetValue, value: this._currentKey, onChange: this.onKeyChange, autosuggestProps: { inputProps: { value: this._currentKey, onChange: this.onKeyChange }, getSuggestionValue: this.getSuggestionValue, suggestions: this.suggestions, alwaysRenderSuggestions: true, renderSuggestion: this.renderSuggestion, onSuggestionsFetchRequested: this.onSuggestionFetch, onSuggestionsClearRequested: this.onSuggestionClear } }} oneLine SetValue={this.setValue} contents={this.pivotField ? this.pivotField : "N/A"} />
); } } @observer export class CollectionSchemaViewChrome extends React.Component { // private _textwrapAllRows: boolean = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0; @undoBatch togglePreview = () => { const dividerWidth = 4; const borderWidth = Number(COLLECTION_BORDER_WIDTH); const panelWidth = this.props.CollectionView.props.PanelWidth(); const previewWidth = NumCast(this.props.CollectionView.props.Document.schemaPreviewWidth); const tableWidth = panelWidth - 2 * borderWidth - dividerWidth - previewWidth; this.props.CollectionView.props.Document.schemaPreviewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0; } @undoBatch @action toggleTextwrap = async () => { const textwrappedRows = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []); if (textwrappedRows.length) { this.props.CollectionView.props.Document.textwrappedSchemaRows = new List([]); } else { const docs = DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey]); const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]); this.props.CollectionView.props.Document.textwrappedSchemaRows = new List(allRows); } } render() { const previewWidth = NumCast(this.props.CollectionView.props.Document.schemaPreviewWidth); const textWrapped = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0; return (
Show Preview:
{previewWidth !== 0 ? "on" : "off"}
); } } @observer export class CollectionTreeViewChrome extends React.Component { get sortAscending() { return this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"]; } set sortAscending(value) { this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"] = value; } @computed private get ascending() { return Cast(this.sortAscending, "boolean", null); } @action toggleSort = () => { if (this.sortAscending) this.sortAscending = undefined; else if (this.sortAscending === undefined) this.sortAscending = false; else this.sortAscending = true; } render() { return (
); } }