import { toUpper } from 'lodash'; import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Opt, StrListCast } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { RichTextField } from '../../../fields/RichTextField'; import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from '../EditableView'; import { computePivotLayout, computeTimelineLayout, ViewDefBounds } from './collectionFreeForm/CollectionFreeFormLayoutEngines'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import { CollectionSubView } from './CollectionSubView'; import './CollectionTimeView.scss'; import React = require('react'); import { DocFocusOptions } from '../nodes/DocumentView'; import { PresBox } from '../nodes/trails'; @observer export class CollectionTimeView extends CollectionSubView() { _changing = false; @observable _layoutEngine = computePivotLayout.name; @observable _collapsed: boolean = false; @observable _childClickedScript: Opt; @observable _viewDefDivClick: Opt; @observable _focusPivotField: Opt; async componentDidMount() { this.props.setContentView?.(this); //const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.rootDoc.type), ""); ///const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; useRightSplit(alias, shiftKey); "; runInAction(() => { this._childClickedScript = ScriptField.MakeScript('openInLightbox(self)', { this: Doc.name }); this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' }); }); } get pivotField() { return this._focusPivotField || StrCast(this.layoutDoc._pivotField); } getAnchor = (addAsAnnotation: boolean) => { const anchor = Docs.Create.HTMLAnchorDocument([], { title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any, annotationOn: this.rootDoc, }); anchor.presPinPivotField = this.pivotField; // should be captured in pinDocView below PresBox.pinDocView(anchor, { pinData: { viewType: true, filters: true } }, this.rootDoc); if (addAsAnnotation) { // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) { Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), []).push(anchor); } else { this.dataDoc[this.props.fieldKey + '-annotations'] = new List([anchor]); } } return anchor; }; @action scrollFocus = (anchor: Doc, options: DocFocusOptions) => { if (options.preview) { // if in preview, then override document's fields with view spec this._focusFilters = StrListCast(anchor.presPinDocFilters); this._focusRangeFilters = StrListCast(anchor.presPinDocRangeFilters); this._focusPivotField = StrCast(anchor.presPinPivotField); return undefined; } const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; // should be part of restoreTargetDocView this.layoutDoc._prevFilterIndex = 1; this.layoutDoc._pivotField = anchor.presPinPivotField; return PresBox.restoreTargetDocView( this.props.DocumentView?.(), // { pinDocLayout: BoolCast(anchor.presPinLayout) }, anchor, focusSpeed, { viewType: anchor.presPinViewType ? true : false, filters: anchor.presPinDocFilters || anchor.presPinDocRangeFilters ? true : false, } ) ? focusSpeed : undefined; }; layoutEngine = () => this._layoutEngine; toggleVisibility = action(() => (this._collapsed = !this._collapsed)); onMinDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, e, action((e: PointerEvent, down: number[], delta: number[]) => { const minReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMinReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMin'], 0)); const maxReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMaxReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMax'], 10)); this.props.Document[this.props.fieldKey + '-timelineMinReq'] = minReq + ((maxReq - minReq) * delta[0]) / this.props.PanelWidth(); this.props.Document[this.props.fieldKey + '-timelineSpan'] = undefined; return false; }), returnFalse, emptyFunction ); }; onMaxDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, e, action((e: PointerEvent, down: number[], delta: number[]) => { const minReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMinReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMin'], 0)); const maxReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMaxReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMax'], 10)); this.props.Document[this.props.fieldKey + '-timelineMaxReq'] = maxReq + ((maxReq - minReq) * delta[0]) / this.props.PanelWidth(); return false; }), returnFalse, emptyFunction ); }; onMidDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, e, action((e: PointerEvent, down: number[], delta: number[]) => { const minReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMinReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMin'], 0)); const maxReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMaxReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMax'], 10)); this.props.Document[this.props.fieldKey + '-timelineMinReq'] = minReq - ((maxReq - minReq) * delta[0]) / this.props.PanelWidth(); this.props.Document[this.props.fieldKey + '-timelineMaxReq'] = maxReq - ((maxReq - minReq) * delta[0]) / this.props.PanelWidth(); return false; }), returnFalse, emptyFunction ); }; goTo = (prevFilterIndex: number) => { this.layoutDoc._pivotField = this.layoutDoc['_prevPivotFields' + prevFilterIndex]; this.layoutDoc._docFilters = ObjectField.MakeCopy(this.layoutDoc['_prevDocFilter' + prevFilterIndex] as ObjectField); this.layoutDoc._docRangeFilters = ObjectField.MakeCopy(this.layoutDoc['_prevDocRangeFilters' + prevFilterIndex] as ObjectField); this.layoutDoc._prevFilterIndex = prevFilterIndex; }; @action contentsDown = (e: React.MouseEvent) => { const prevFilterIndex = NumCast(this.layoutDoc._prevFilterIndex); if (prevFilterIndex > 0) { this.goTo(prevFilterIndex - 1); } else { this.layoutDoc._docFilters = new List([]); } }; dontScaleFilter = (doc: Doc) => doc.type === DocumentType.RTF; @computed get contents() { return (
); } public static SyncTimelineToPresentation(doc: Doc) { const fieldKey = Doc.LayoutFieldKey(doc); doc[fieldKey + '-timelineCur'] = ComputedField.MakeFunction("(activePresentationItem()[this._pivotField || 'year'] || 0)"); } specificMenu = (e: React.MouseEvent) => { const layoutItems: ContextMenuProps[] = []; const doc = this.layoutDoc; layoutItems.push({ description: 'Force Timeline', event: () => { doc._forceRenderEngine = computeTimelineLayout.name; }, icon: 'compress-arrows-alt', }); layoutItems.push({ description: 'Force Pivot', event: () => { doc._forceRenderEngine = computePivotLayout.name; }, icon: 'compress-arrows-alt', }); layoutItems.push({ description: 'Auto Time/Pivot layout', event: () => { doc._forceRenderEngine = undefined; }, icon: 'compress-arrows-alt', }); layoutItems.push({ description: 'Sync with presentation', event: () => CollectionTimeView.SyncTimelineToPresentation(doc), icon: 'compress-arrows-alt' }); ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' }); }; @computed get _allFacets() { const facets = new Set(); this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key))); Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key))); return Array.from(facets); } menuCallback = (x: number, y: number) => { ContextMenu.Instance.clearItems(); const docItems: ContextMenuProps[] = []; const keySet: Set = new Set(); this.childLayoutPairs.map(pair => this._allFacets .filter(fieldKey => pair.layout[fieldKey] instanceof RichTextField || typeof pair.layout[fieldKey] === 'number' || typeof pair.layout[fieldKey] === 'boolean' || typeof pair.layout[fieldKey] === 'string') .filter(fieldKey => fieldKey[0] !== '_' && (fieldKey[0] !== '#' || fieldKey === '#') && (fieldKey === 'tags' || fieldKey[0] === toUpper(fieldKey)[0])) .map(fieldKey => keySet.add(fieldKey)) ); Array.from(keySet).map(fieldKey => docItems.push({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' })); docItems.push({ description: ':default', event: () => (this.layoutDoc._pivotField = undefined), icon: 'compress-arrows-alt' }); ContextMenu.Instance.addItem({ description: 'Pivot Fields ...', subitems: docItems, icon: 'eye' }); const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y); ContextMenu.Instance.displayMenu(x, y, ':'); }; @computed get pivotKeyUI() { return (
{ if (value?.length) { this.layoutDoc._pivotField = value; return true; } return false; }} background={'#f1efeb'} // this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; contents={':' + StrCast(this.layoutDoc._pivotField)} showMenuOnLoad={true} display={'inline'} menuCallback={this.menuCallback} />
); } render() { let nonNumbers = 0; this.childDocs.map(doc => { const num = NumCast(doc[this.pivotField], Number(StrCast(doc[this.pivotField]))); if (Number.isNaN(num)) { nonNumbers++; } }); const forceLayout = StrCast(this.layoutDoc._forceRenderEngine); const doTimeline = forceLayout ? forceLayout === computeTimelineLayout.name : nonNumbers / this.childDocs.length < 0.1 && this.props.PanelWidth() / this.props.PanelHeight() > 6; if (doTimeline !== (this._layoutEngine === computeTimelineLayout.name)) { if (!this._changing) { this._changing = true; setTimeout( action(() => { this._layoutEngine = doTimeline ? computeTimelineLayout.name : computePivotLayout.name; this._changing = false; }), 0 ); } } return (
{this.pivotKeyUI} {this.contents} {!this.props.isSelected() || !doTimeline ? null : ( <>
)}
); } } ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) { const pivotField = StrCast(pivotDoc._pivotField) || 'author'; let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex); const originalFilter = StrListCast(ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField)); pivotDoc['_prevDocFilter' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField); pivotDoc['_prevDocRangeFilters' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField); pivotDoc['_prevPivotFields' + prevFilterIndex] = pivotField; pivotDoc._prevFilterIndex = ++prevFilterIndex; pivotDoc._docFilters = new List(); setTimeout( action(() => { const filterVals = bounds.payload as string[]; filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, pivotField, filterVal, 'check')); const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc); if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) { if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) { pivotDoc._pivotField = filterVals[0]; } } const newFilters = StrListCast(pivotDoc._docFilters); if (newFilters.length && originalFilter.length && newFilters.lastElement() === originalFilter.lastElement()) { pivotDoc._prevFilterIndex = --prevFilterIndex; pivotDoc['_prevDocFilter' + prevFilterIndex] = undefined; pivotDoc['_prevDocRangeFilters' + prevFilterIndex] = undefined; pivotDoc['_prevPivotFields' + prevFilterIndex] = undefined; } }) ); });