From 51e8786c33a1af207081212802e6da03476edf4a Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 9 Nov 2022 15:46:33 -0500 Subject: added tab highlighting when tab doc is target of hyperlink. made region annotations on images be transparent. don't show lock icon for lockedPosition documents that still get pointer events. --- src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 5 ----- 1 file changed, 5 deletions(-) (limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx') diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 260c98816..570039550 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -24,7 +24,6 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { sizeProvider?: (doc: Doc, replica: string) => { width: number; height: number } | undefined; renderCutoffProvider: (doc: Doc) => boolean; zIndex?: number; - highlight?: boolean; rotation: number; dataTransition?: string; replica: string; @@ -75,9 +74,6 @@ export class CollectionFreeFormDocumentView extends DocComponent Date: Thu, 17 Nov 2022 14:35:13 -0500 Subject: mostly changing strings to enums --- src/client/util/DictationManager.ts | 6 +- src/client/util/LinkFollower.ts | 4 +- src/client/views/LightboxView.tsx | 16 +- src/client/views/MainView.tsx | 28 +- .../views/PropertiesDocBacklinksSelector.tsx | 45 ++- src/client/views/PropertiesDocContextSelector.tsx | 6 +- src/client/views/PropertiesView.tsx | 4 +- .../views/collections/CollectionDockingView.tsx | 26 +- .../views/collections/CollectionNoteTakingView.tsx | 12 +- .../views/collections/CollectionPileView.tsx | 22 +- .../views/collections/CollectionStackingView.tsx | 4 +- .../views/collections/CollectionTimeView.tsx | 351 +++++++++------- src/client/views/collections/CollectionView.tsx | 11 +- src/client/views/collections/TabDocView.tsx | 34 +- src/client/views/collections/TreeView.tsx | 8 +- .../CollectionFreeFormLayoutEngines.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 23 +- .../CollectionMulticolumnView.tsx | 9 +- .../collectionSchema/CollectionSchemaCells.tsx | 447 +++++++++++---------- .../CollectionSchemaMovableRow.tsx | 3 +- .../collections/collectionSchema/SchemaTable.tsx | 6 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 35 +- src/client/views/nodes/KeyValueBox.tsx | 5 +- src/client/views/nodes/KeyValuePair.tsx | 65 +-- src/client/views/nodes/LinkAnchorBox.tsx | 5 +- src/client/views/nodes/LinkDocPreview.tsx | 4 +- src/client/views/nodes/VideoBox.tsx | 3 +- src/client/views/nodes/button/FontIconBox.tsx | 3 +- .../views/nodes/formattedText/DashFieldView.tsx | 3 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- .../formattedText/ProsemirrorExampleTransfer.ts | 3 +- src/client/views/nodes/trails/PresBox.tsx | 13 +- src/mobile/MobileInterface.tsx | 2 +- 34 files changed, 668 insertions(+), 550 deletions(-) (limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 0a61f3478..203d4ad62 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -11,7 +11,7 @@ import { Utils } from '../../Utils'; import { Docs } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; import { DictationOverlay } from '../views/DictationOverlay'; -import { DocumentView } from '../views/nodes/DocumentView'; +import { DocumentView, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView'; import { SelectionManager } from './SelectionManager'; import { UndoManager } from './UndoManager'; @@ -328,7 +328,7 @@ export namespace DictationManager { { action: (target: DocumentView) => { const kvp = Docs.Create.KVPDocument(target.props.Document, { _width: 300, _height: 300 }); - target.props.addDocTab(kvp, 'add:right'); + target.props.addDocTab(kvp, OpenWhere.addRight); }, }, ], @@ -345,7 +345,7 @@ export namespace DictationManager { const proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"ordered_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`; proto.data = new RichTextField(proseMirrorState); proto.backgroundColor = '#eeffff'; - target.props.addDocTab(newBox, 'add:right'); + target.props.addDocTab(newBox, OpenWhere.addRight); }, }, ], diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index 68716a207..a3eb7ed7a 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -4,7 +4,7 @@ import { BoolCast, Cast, DocCast, StrCast } from '../../fields/Types'; import { DocumentType } from '../documents/DocumentTypes'; import { DocumentDecorations } from '../views/DocumentDecorations'; import { LightboxView } from '../views/LightboxView'; -import { DocumentViewSharedProps, ViewAdjustment } from '../views/nodes/DocumentView'; +import { DocumentViewSharedProps, OpenWhere, ViewAdjustment } from '../views/nodes/DocumentView'; import { DocumentManager } from './DocumentManager'; import { LinkManager } from './LinkManager'; import { UndoManager } from './UndoManager'; @@ -32,7 +32,7 @@ export class LinkFollower { const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => { const createTabForTarget = (didFocus: boolean) => new Promise(res => { - const where = LightboxView.LightboxDoc ? 'inPlace' : StrCast(sourceDoc.followLinkLocation, followLoc); + const where = LightboxView.LightboxDoc ? OpenWhere.inPlace : (StrCast(sourceDoc.followLinkLocation, followLoc) as OpenWhere); docViewProps.addDocTab(doc, where); setTimeout(() => { const targDocView = DocumentManager.Instance.getFirstDocumentView(doc); // get first document view available within the lightbox if that's open, or anywhere otherwise. diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 5660a34e9..1f58763d1 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -18,7 +18,7 @@ import { TabDocView } from './collections/TabDocView'; import { GestureOverlay } from './GestureOverlay'; import './LightboxView.scss'; import { MainView } from './MainView'; -import { DocumentView } from './nodes/DocumentView'; +import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView'; import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider'; interface LightboxViewProps { @@ -141,11 +141,13 @@ export class LightboxView extends React.Component { this._docFilters = (f => (this._docFilters ? [this._docFilters.push(f) as any, this._docFilters][1] : [f]))(`cookies:${cookie}:provide`); } } - public static AddDocTab = (doc: Doc, location: string, layoutTemplate?: Doc, openInTabFunc?: any) => { - const inPlaceView = DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined; - if (inPlaceView) { - inPlaceView.dataDoc[Doc.LayoutFieldKey(inPlaceView.rootDoc)] = new List([doc]); - return true; + public static AddDocTab = (doc: Doc, location: OpenWhere, layoutTemplate?: Doc, openInTabFunc?: any) => { + if (location !== OpenWhere.lightbox) { + const inPlaceView = DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined; + if (inPlaceView) { + inPlaceView.dataDoc[Doc.LayoutFieldKey(inPlaceView.rootDoc)] = new List([doc]); + return true; + } } LightboxView.openInTabFunc = openInTabFunc; SelectionManager.DeselectAll(); @@ -360,7 +362,7 @@ export class LightboxView extends React.Component { title={'open in tab'} onClick={e => { e.stopPropagation(); - CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, ''); + CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, OpenWhereMod.none); //LightboxView.openInTabFunc(LightboxView._docTarget || LightboxView._doc!, "inPlace"); SelectionManager.DeselectAll(); LightboxView.SetLightboxDoc(undefined); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 392b4eeeb..c151aebcd 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -49,7 +49,7 @@ import { LinkMenu } from './linking/LinkMenu'; import './MainView.scss'; import { AudioBox } from './nodes/AudioBox'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; -import { DocumentView } from './nodes/DocumentView'; +import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView'; import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from './nodes/formattedText/RichTextMenu'; @@ -538,7 +538,7 @@ export class MainView extends React.Component { @action createNewPresentation = () => { const pres = Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true); - CollectionDockingView.AddSplit(pres, 'right'); + CollectionDockingView.AddSplit(pres, OpenWhereMod.right); Doc.MyTrails && Doc.AddDocToList(Doc.MyTrails, 'data', pres); // Doc.MyTrails should be created in createDashboard Doc.ActivePresentation = pres; }; @@ -546,7 +546,7 @@ export class MainView extends React.Component { @action openPresentation = (pres: Doc) => { if (pres.type === DocumentType.PRES) { - CollectionDockingView.AddSplit(pres, 'right'); + CollectionDockingView.AddSplit(pres, OpenWhereMod.right); Doc.MyTrails && (Doc.ActivePresentation = pres); Doc.AddDocToList(Doc.MyTrails, 'data', pres); this.closeFlyout(); @@ -683,20 +683,20 @@ export class MainView extends React.Component { sidebarScreenToLocal = () => new Transform(0, -this.topOfSidebarDoc, 1); mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0); - addDocTabFunc = (doc: Doc, location: string): boolean => { - const locationFields = doc._viewType === CollectionViewType.Docking ? ['dashboard'] : location.split(':'); - const locationParams = locationFields.length > 1 ? locationFields[1] : ''; + addDocTabFunc = (doc: Doc, location: OpenWhere): boolean => { + const whereFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':'); + const whereMods = whereFields.length > 1 ? whereFields[1] : ''; if (doc.dockingConfig) return DashboardView.openDashboard(doc); // prettier-ignore - switch (locationFields[0]) { + switch (whereFields[0]) { default: - case 'inPlace': - case 'lightbox': return LightboxView.AddDocTab(doc, location); - case 'add': return CollectionDockingView.AddSplit(doc, locationParams); - case 'dashboard': return DashboardView.openDashboard(doc); - case 'close': return CollectionDockingView.CloseSplit(doc, locationParams); - case 'fullScreen': return CollectionDockingView.OpenFullScreen(doc); - case 'toggle': return CollectionDockingView.ToggleSplit(doc, locationParams); + case OpenWhere.inPlace: + case OpenWhere.lightbox: return LightboxView.AddDocTab(doc, location); + case OpenWhere.add: return CollectionDockingView.AddSplit(doc, whereMods as OpenWhereMod); + case OpenWhere.dashboard: return DashboardView.openDashboard(doc); + case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods); + case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc); + case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods as OpenWhereMod); } }; diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx index 4ead8eaf0..25ac44078 100644 --- a/src/client/views/PropertiesDocBacklinksSelector.tsx +++ b/src/client/views/PropertiesDocBacklinksSelector.tsx @@ -1,20 +1,21 @@ -import { computed } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { Doc, DocListCast } from "../../fields/Doc"; -import { Cast } from "../../fields/Types"; -import { emptyFunction } from "../../Utils"; -import { DocumentType } from "../documents/DocumentTypes"; -import { LinkManager } from "../util/LinkManager"; -import { SelectionManager } from "../util/SelectionManager"; -import { LinkMenu } from "./linking/LinkMenu"; +import { computed } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Doc, DocListCast } from '../../fields/Doc'; +import { Cast } from '../../fields/Types'; +import { emptyFunction } from '../../Utils'; +import { DocumentType } from '../documents/DocumentTypes'; +import { LinkManager } from '../util/LinkManager'; +import { SelectionManager } from '../util/SelectionManager'; +import { LinkMenu } from './linking/LinkMenu'; +import { OpenWhere, OpenWhereMod } from './nodes/DocumentView'; import './PropertiesDocBacklinksSelector.scss'; type PropertiesDocBacklinksSelectorProps = { - Document: Doc, - Stack?: any, - hideTitle?: boolean, - addDocTab(doc: Doc, location: string): void + Document: Doc; + Stack?: any; + hideTitle?: boolean; + addDocTab(doc: Doc, location: OpenWhere): void; }; @observer @@ -40,14 +41,16 @@ export class PropertiesDocBacklinksSelector extends React.Component - {this.props.hideTitle ? (null) :

Contexts:

} - - ; + return !SelectionManager.Views().length ? null : ( +
+ {this.props.hideTitle ? null :

Contexts:

} + +
+ ); } -} \ No newline at end of file +} diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx index 9d89ee036..2c7da5931 100644 --- a/src/client/views/PropertiesDocContextSelector.tsx +++ b/src/client/views/PropertiesDocContextSelector.tsx @@ -7,14 +7,14 @@ import { Cast, NumCast, StrCast } from '../../fields/Types'; import { CollectionViewType } from '../documents/DocumentTypes'; import { DocFocusOrOpen } from '../util/DocumentManager'; import { CollectionDockingView } from './collections/CollectionDockingView'; -import { DocumentView } from './nodes/DocumentView'; +import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView'; import './PropertiesDocContextSelector.scss'; type PropertiesDocContextSelectorProps = { DocView?: DocumentView; Stack?: any; hideTitle?: boolean; - addDocTab(doc: Doc, location: string): void; + addDocTab(doc: Doc, location: OpenWhere): void; }; @observer @@ -53,7 +53,7 @@ export class PropertiesDocContextSelector extends React.Component DocFocusOrOpen(Doc.GetProto(this.props.DocView!.props.Document), col), 100); }; diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index e5ff9e267..93a3fd253 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -24,7 +24,7 @@ import { Transform } from '../util/Transform'; import { undoBatch, UndoManager } from '../util/UndoManager'; import { EditableView } from './EditableView'; import { InkStrokeProperties } from './InkStrokeProperties'; -import { DocumentView, StyleProviderFunc } from './nodes/DocumentView'; +import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView'; import { FilterBox } from './nodes/FilterBox'; import { KeyValueBox } from './nodes/KeyValueBox'; import { PresBox } from './nodes/trails'; @@ -42,7 +42,7 @@ interface PropertiesViewProps { width: number; height: number; styleProvider?: StyleProviderFunc; - addDocTab: (doc: Doc, where: string) => boolean; + addDocTab: (doc: Doc, where: OpenWhere) => boolean; } @observer diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 434466505..8cbe548c7 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -25,6 +25,7 @@ import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import { TabDocView } from './TabDocView'; import React = require('react'); +import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView'; const _global = (window /* browser */ || global) /* node */ as any; @observer @@ -142,7 +143,7 @@ export class CollectionDockingView extends CollectionSubView() { @undoBatch @action - public static ReplaceTab(document: Doc, panelName: string, stack: any, addToSplit?: boolean): boolean { + public static ReplaceTab(document: Doc, panelName: OpenWhereMod, stack: any, addToSplit?: boolean): boolean { const instance = CollectionDockingView.Instance; if (!instance) return false; const newConfig = CollectionDockingView.makeDocumentConfig(document, panelName); @@ -164,7 +165,7 @@ export class CollectionDockingView extends CollectionSubView() { } @undoBatch - public static ToggleSplit(doc: Doc, location: string, stack?: any, panelName?: string) { + public static ToggleSplit(doc: Doc, location: OpenWhereMod, stack?: any, panelName?: string) { return CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex(tab => tab.DashDoc === doc) !== -1 ? CollectionDockingView.CloseSplit(doc) : CollectionDockingView.AddSplit(doc, location, stack, panelName); @@ -175,7 +176,7 @@ export class CollectionDockingView extends CollectionSubView() { // @undoBatch @action - public static AddSplit(document: Doc, pullSide: string, stack?: any, panelName?: string) { + public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string) { if (document?._viewType === CollectionViewType.Docking) return DashboardView.openDashboard(document); if (!CollectionDockingView.Instance) return false; const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document); @@ -208,14 +209,15 @@ export class CollectionDockingView extends CollectionSubView() { // if row switch (pullSide) { default: - case 'right': + case OpenWhereMod.none: + case OpenWhereMod.right: glayRoot.contentItems[0].addChild(newContentItem()); break; - case 'left': + case OpenWhereMod.left: glayRoot.contentItems[0].addChild(newContentItem(), 0); break; - case 'top': - case 'bottom': + case OpenWhereMod.top: + case OpenWhereMod.bottom: // if not going in a row layout, must add already existing content into column const rowlayout = glayRoot.contentItems[0]; const newColumn = rowlayout.layoutManager.createContentItem({ type: 'column' }, instance._goldenLayout); @@ -496,7 +498,7 @@ export class CollectionDockingView extends CollectionSubView() { title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, }); this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd); - CollectionDockingView.AddSplit(docToAdd, '', stack); + CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } }); @@ -539,7 +541,7 @@ export class CollectionDockingView extends CollectionSubView() { title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, }); this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd); - CollectionDockingView.AddSplit(docToAdd, '', stack); + CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } }) ); @@ -568,14 +570,14 @@ export class CollectionDockingView extends CollectionSubView() { ScriptingGlobals.add( function openInLightbox(doc: any) { - LightboxView.AddDocTab(doc, 'lightbox'); + LightboxView.AddDocTab(doc, OpenWhere.lightbox); }, 'opens up document in a lightbox', '(doc: any)' ); ScriptingGlobals.add( function openOnRight(doc: any) { - return CollectionDockingView.AddSplit(doc, 'right'); + return CollectionDockingView.AddSplit(doc, OpenWhereMod.right); }, 'opens up document in tab on right side of the screen', '(doc: any)' @@ -588,5 +590,5 @@ ScriptingGlobals.add( '(doc: any)' ); ScriptingGlobals.add(function useRightSplit(doc: any, shiftKey?: boolean) { - CollectionDockingView.ReplaceTab(doc, 'right', undefined, shiftKey); + CollectionDockingView.ReplaceTab(doc, OpenWhereMod.right, undefined, shiftKey); }); diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index b0f64ed60..29670a1a7 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -19,7 +19,7 @@ import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { LightboxView } from '../LightboxView'; -import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from '../nodes/DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; @@ -180,14 +180,6 @@ export class CollectionNoteTakingView extends CollectionSubView() { return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); } - addDocTab = (doc: Doc, where: string) => { - if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) { - this.dataDoc[this.props.fieldKey] = new List([doc]); - return true; - } - return this.props.addDocTab(doc, where); - }; - scrollToBottom = () => { smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight); }; @@ -274,7 +266,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { removeDocument={this.props.removeDocument} contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)} whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} - addDocTab={this.addDocTab} + addDocTab={this.props.addDocTab} bringToFront={returnFalse} scriptContext={this.props.scriptContext} pinToPres={this.props.pinToPres} diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 38e240ac6..e95622630 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -12,6 +12,8 @@ import './CollectionPileView.scss'; import { CollectionSubView } from './CollectionSubView'; import React = require('react'); import { ScriptField } from '../../../fields/ScriptField'; +import { OpenWhere } from '../nodes/DocumentView'; +import { computePassLayout, computeStarburstLayout } from './collectionFreeForm'; @observer export class CollectionPileView extends CollectionSubView() { @@ -19,8 +21,8 @@ export class CollectionPileView extends CollectionSubView() { _disposers: { [name: string]: IReactionDisposer } = {}; componentDidMount() { - if (this.layoutEngine() !== 'pass' && this.layoutEngine() !== 'starburst') { - this.Document._pileLayoutEngine = 'pass'; + if (this.layoutEngine() !== computePassLayout.name && this.layoutEngine() !== computeStarburstLayout.name) { + this.Document._pileLayoutEngine = computePassLayout.name; } this._originalChrome = this.layoutDoc._chromeHidden; this.layoutDoc._chromeHidden = true; @@ -56,7 +58,7 @@ export class CollectionPileView extends CollectionSubView() { // returns the contents of the pileup in a CollectionFreeFormView @computed get contents() { - const isStarburst = this.layoutEngine() === 'starburst'; + const isStarburst = this.layoutEngine() === computeStarburstLayout.name; return (
{ - if (this.layoutEngine() === 'starburst') { + if (this.layoutEngine() === computeStarburstLayout.name) { const defaultSize = 110; this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2; this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2; @@ -83,12 +85,12 @@ export class CollectionPileView extends CollectionSubView() { DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false); this.layoutDoc._panX = 0; this.layoutDoc._panY = -10; - this.props.Document._pileLayoutEngine = 'pass'; + this.props.Document._pileLayoutEngine = computePassLayout.name; } else { const defaultSize = 25; !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 250); !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5); - if (this.layoutEngine() === 'pass') { + if (this.layoutEngine() === computePassLayout.name) { this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2; this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2; this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym](); @@ -96,7 +98,7 @@ export class CollectionPileView extends CollectionSubView() { } this.layoutDoc._panX = this.layoutDoc._panY = 0; this.layoutDoc._width = this.layoutDoc._height = defaultSize; - this.props.Document._pileLayoutEngine = 'starburst'; + this.props.Document._pileLayoutEngine = computeStarburstLayout.name; } }); @@ -118,7 +120,7 @@ export class CollectionPileView extends CollectionSubView() { const doc = this.childDocs[0]; doc.x = e.clientX; doc.y = e.clientY; - this.props.addDocTab(doc, 'inParent') && (this.props.removeDocument?.(doc) || false); + this.props.addDocTab(doc, OpenWhere.inParent) && (this.props.removeDocument?.(doc) || false); dist = 0; } } @@ -130,8 +132,8 @@ export class CollectionPileView extends CollectionSubView() { SnappingManager.SetIsDragging(false); }, emptyFunction, - e.shiftKey && this.layoutEngine() === 'pass', - this.layoutEngine() === 'pass' && e.shiftKey + e.shiftKey && this.layoutEngine() === computePassLayout.name, + this.layoutEngine() === computePassLayout.name && e.shiftKey ); // this sets _doubleTap }; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 175051d5c..aa4583af6 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -22,7 +22,7 @@ import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from '../EditableView'; import { LightboxView } from '../LightboxView'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; -import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from '../nodes/DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; @@ -241,7 +241,7 @@ export class CollectionStackingView extends CollectionSubView this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); } - addDocTab = (doc: Doc, where: string) => { + addDocTab = (doc: Doc, where: OpenWhere) => { if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) { this.dataDoc[this.props.fieldKey] = new List([doc]); return true; diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 3dd9d2d84..ac896a8fd 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -1,32 +1,32 @@ -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 { 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 { ViewSpecPrefix } from "../nodes/DocumentView"; -import { ViewDefBounds } from "./collectionFreeForm/CollectionFreeFormLayoutEngines"; -import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; -import { CollectionSubView } from "./CollectionSubView"; -import "./CollectionTimeView.scss"; -import React = require("react"); +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 { 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 { ViewSpecPrefix } from '../nodes/DocumentView'; +import { computePivotLayout, computeTimelineLayout, ViewDefBounds } from './collectionFreeForm/CollectionFreeFormLayoutEngines'; +import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; +import { CollectionSubView } from './CollectionSubView'; +import './CollectionTimeView.scss'; +import React = require('react'); @observer export class CollectionTimeView extends CollectionSubView() { _changing = false; - @observable _layoutEngine = "pivot"; + @observable _layoutEngine = computePivotLayout.name; @observable _collapsed: boolean = false; @observable _childClickedScript: Opt; @observable _viewDefDivClick: Opt; @@ -35,7 +35,7 @@ export class CollectionTimeView extends CollectionSubView() { getAnchor = () => { const anchor = Docs.Create.HTMLAnchorDocument([], { title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any, - annotationOn: this.rootDoc + annotationOn: this.rootDoc, }); // save view spec information for anchor @@ -43,81 +43,103 @@ export class CollectionTimeView extends CollectionSubView() { proto.pivotField = this.pivotField; proto.docFilters = ObjectField.MakeCopy(this.layoutDoc._docFilters as ObjectField) || new List([]); proto.docRangeFilters = ObjectField.MakeCopy(this.layoutDoc._docRangeFilters as ObjectField) || new List([]); - proto[ViewSpecPrefix + "_viewType"] = this.layoutDoc._viewType; + proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType; // store anchor in annotations list of document (not technically needed since these anchors are never drawn) - if (Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), null) !== undefined) { - Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), []).push(anchor); + 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]); + this.dataDoc[this.props.fieldKey + '-annotations'] = new List([anchor]); } return anchor; - } + }; 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'; alias.removeDropProperties=new List(['dropAction']); useRightSplit(alias, shiftKey); "; runInAction(() => { - this._childClickedScript = ScriptField.MakeScript("openInLightbox(self)", { this: Doc.name }); - this._viewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" }); + 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); } + get pivotField() { + return this._focusPivotField || StrCast(this.layoutDoc._pivotField); + } @action setViewSpec = (anchor: Doc, preview: boolean) => { - if (preview) { // if in preview, then override document's fields with view spec + if (preview) { + // if in preview, then override document's fields with view spec this._focusFilters = StrListCast(Doc.GetProto(anchor).docFilters); this._focusRangeFilters = StrListCast(Doc.GetProto(anchor).docRangeFilters); this._focusPivotField = StrCast(anchor.pivotField); - } else if (anchor.pivotField !== undefined) { // otherwise set document's fields based on anchor view spec + } else if (anchor.pivotField !== undefined) { + // otherwise set document's fields based on anchor view spec this.layoutDoc._prevFilterIndex = 1; this.layoutDoc._pivotField = StrCast(anchor.pivotField); this.layoutDoc._docFilters = new List(StrListCast(anchor.docFilters)); this.layoutDoc._docRangeFilters = new List(StrListCast(anchor.docRangeFilters)); } return 0; - } + }; layoutEngine = () => this._layoutEngine; - toggleVisibility = action(() => this._collapsed = !this._collapsed); + 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); - } + 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); - } + 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); - } + 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._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) => { @@ -127,37 +149,58 @@ export class CollectionTimeView extends CollectionSubView() { } else { this.layoutDoc._docFilters = new List([]); } - } + }; dontScaleFilter = (doc: Doc) => doc.type === DocumentType.RTF; @computed get contents() { - return
- -
; + return ( +
+ +
+ ); } public static SyncTimelineToPresentation(doc: Doc) { const fieldKey = Doc.LayoutFieldKey(doc); - doc[fieldKey + "-timelineCur"] = ComputedField.MakeFunction("(activePresentationItem()[this._pivotField || 'year'] || 0)"); + 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 = "timeline"; }, icon: "compress-arrows-alt" }); - layoutItems.push({ description: "Force Pivot", event: () => { doc._forceRenderEngine = "pivot"; }, 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" }); + 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" }); - } + 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))); @@ -169,37 +212,40 @@ export class CollectionTimeView extends CollectionSubView() { 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" }); + 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, ":"); - } + ContextMenu.Instance.displayMenu(x, y, ':'); + }; @computed get pivotKeyUI() { - return
- { - if (value?.length) { - this.layoutDoc._pivotField = value; - return true; - } - return false; - }} - toggle={this.toggleVisibility} - background={"#f1efeb"} // this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; - contents={":" + StrCast(this.layoutDoc._pivotField)} - showMenuOnLoad={true} - display={"inline"} - menuCallback={this.menuCallback} /> -
; + return ( +
+ { + if (value?.length) { + this.layoutDoc._pivotField = value; + return true; + } + return false; + }} + toggle={this.toggleVisibility} + background={'#f1efeb'} // this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; + contents={':' + StrCast(this.layoutDoc._pivotField)} + showMenuOnLoad={true} + display={'inline'} + menuCallback={this.menuCallback} + /> +
+ ); } render() { @@ -211,55 +257,62 @@ export class CollectionTimeView extends CollectionSubView() { } }); const forceLayout = StrCast(this.layoutDoc._forceRenderEngine); - const doTimeline = forceLayout ? (forceLayout === "timeline") : nonNumbers / this.childDocs.length < 0.1 && this.props.PanelWidth() / this.props.PanelHeight() > 6; - if (doTimeline !== (this._layoutEngine === "timeline")) { + 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 ? "timeline" : "pivot"; - this._changing = false; - }), 0); + setTimeout( + action(() => { + this._layoutEngine = doTimeline ? computeTimelineLayout.name : computePivotLayout.name; + this._changing = false; + }), + 0 + ); } } - return
- {this.pivotKeyUI} - {this.contents} - {!this.props.isSelected() || !doTimeline ? (null) : <> -
-
-
- } -
; + return ( +
+ {this.pivotKeyUI} + {this.contents} + {!this.props.isSelected() || !doTimeline ? null : ( + <> +
+
+
+ + )} +
+ ); } } ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) { - const pivotField = StrCast(pivotDoc._pivotField) || "author"; + 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['_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]; + 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; - } - })); -}); \ No newline at end of file + 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; + } + }) + ); +}); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 625d4e9e5..917d7618c 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -16,6 +16,7 @@ import { InteractionUtils } from '../../util/InteractionUtils'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; +import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import { CollectionCarousel3DView } from './CollectionCarousel3DView'; import { CollectionCarouselView } from './CollectionCarouselView'; @@ -174,7 +175,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent { const newRendition = Doc.MakeAlias(this.rootDoc); newRendition._viewType = vtype; - this.props.addDocTab(newRendition, 'add:right'); + this.props.addDocTab(newRendition, OpenWhere.addRight); return newRendition; }, false @@ -184,17 +185,17 @@ export class CollectionView extends ViewBoxAnnotatableComponent (this.rootDoc.forceActive = !this.rootDoc.forceActive), icon: 'project-diagram' }) : null; if (this.rootDoc.childLayout instanceof Doc) { - optionItems.push({ description: 'View Child Layout', event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, 'add:right'), icon: 'project-diagram' }); + optionItems.push({ description: 'View Child Layout', event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, OpenWhere.addRight), icon: 'project-diagram' }); } if (this.rootDoc.childClickedOpenTemplateView instanceof Doc) { - optionItems.push({ description: 'View Child Detailed Layout', event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, 'add:right'), icon: 'project-diagram' }); + optionItems.push({ description: 'View Child Detailed Layout', event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' }); } !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? 'Unset' : 'Set'} inPlace Container`, event: () => (this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer), icon: 'project-diagram' }); if (!Doc.noviceMode && false) { optionItems.push({ description: 'Create Branch', - event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), 'add:right'), + event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), OpenWhere.addRight), icon: 'project-diagram', }); optionItems.push({ @@ -225,7 +226,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent { const alias = Doc.MakeAlias(this.rootDoc); DocUtils.makeCustomViewClicked(alias, undefined, func.key); - this.props.addDocTab(alias, 'add:right'); + this.props.addDocTab(alias, OpenWhere.addRight); }, }) ); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index e21649648..2cc588b78 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -26,7 +26,7 @@ import { DashboardView } from '../DashboardView'; import { Colors, Shadows } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; import { MainView } from '../MainView'; -import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView'; import { DashFieldView } from '../nodes/formattedText/DashFieldView'; import { PinProps, PresBox, PresMovement } from '../nodes/trails'; import { DefaultStyleProvider, StyleProp } from '../StyleProvider'; @@ -296,7 +296,7 @@ export class TabDocView extends React.Component { ) { const docs = Cast(Doc.MyOverlayDocs.data, listSpec(Doc), []); if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1); - CollectionDockingView.AddSplit(curPres, 'right'); + CollectionDockingView.AddSplit(curPres, OpenWhereMod.right); setTimeout(() => DocumentManager.Instance.jumpToDocument(docList.lastElement(), false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things } setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs @@ -343,34 +343,30 @@ export class TabDocView extends React.Component { // "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name, // "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right // inPlace - will add the document to any collection along the path from the document to the docking view that has a field isInPlaceContainer. if none is found, inPlace adds a tab to current stack - addDocTab = (doc: Doc, location: string) => { + addDocTab = (doc: Doc, location: OpenWhere) => { SelectionManager.DeselectAll(); - const locationFields = doc._viewType === CollectionViewType.Docking ? ['dashboard'] : location.split(':'); - const locationParams = locationFields.length > 1 ? locationFields[1] : ''; + const locationFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':'); + const locationParams: OpenWhereMod = locationFields.length > 1 ? (locationFields[1] as OpenWhereMod) : OpenWhereMod.none; switch (locationFields[0]) { - case 'dashboard': + case OpenWhere.dashboard: return DashboardView.openDashboard(doc); - case 'close': + case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, locationParams); - case 'fullScreen': + case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc); - case 'replace': + case OpenWhere.replace: return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack); - // case "lightbox": { - // // TabDocView.PinDoc(doc, { hidePresBox: true }); - // return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab); - // } - case 'inPlace': + case OpenWhere.inPlace: const inPlaceView = DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined; if (inPlaceView) { inPlaceView.dataDoc[Doc.LayoutFieldKey(inPlaceView.rootDoc)] = new List([doc]); return true; - } - case 'lightbox': + } // fall through to lightbox + case OpenWhere.lightbox: return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab); - case 'toggle': + case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, locationParams, this.stack); - case 'add': + case OpenWhere.add: default: return CollectionDockingView.AddSplit(doc, locationParams, this.stack); } @@ -509,7 +505,7 @@ interface TabMinimapViewProps { document: Doc; hideMinimap: () => boolean; tabView: () => DocumentView | undefined; - addDocTab: (doc: Doc, where: string) => boolean; + addDocTab: (doc: Doc, where: OpenWhere) => boolean; PanelWidth: () => number; PanelHeight: () => number; background: () => string; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 13cf64558..bd326f917 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -22,7 +22,7 @@ import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { EditableView } from '../EditableView'; import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss'; -import { DocumentView, DocumentViewInternal, DocumentViewProps, StyleProviderFunc } from '../nodes/DocumentView'; +import { DocumentView, DocumentViewInternal, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; @@ -44,7 +44,7 @@ export interface TreeViewProps { containerCollection: Doc; renderDepth: number; dropAction: dropActionType; - addDocTab: (doc: Doc, where: string) => boolean; + addDocTab: (doc: Doc, where: OpenWhere) => boolean; panelWidth: () => number; panelHeight: () => number; addDocument: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean; @@ -236,7 +236,7 @@ export class TreeView extends React.Component { const bestAlias = docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsPrototype(docView.props.Document) ? docView.props.Document : DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail); const nextBestAlias = DocListCast(this.props.document.aliases).find(doc => doc.author === Doc.CurrentUserEmail); - this.props.addDocTab(bestAlias ?? nextBestAlias ?? Doc.MakeAlias(this.props.document), 'lightbox'); + this.props.addDocTab(bestAlias ?? nextBestAlias ?? Doc.MakeAlias(this.props.document), OpenWhere.lightbox); } }; @@ -1109,7 +1109,7 @@ export class TreeView extends React.Component { remove: undefined | ((doc: Doc | Doc[]) => boolean), move: DragManager.MoveFunction, dropAction: dropActionType, - addDocTab: (doc: Doc, where: string) => boolean, + addDocTab: (doc: Doc, where: OpenWhere) => boolean, styleProvider: undefined | StyleProviderFunc, screenToLocalXf: () => Transform, isContentActive: (outsideReaction?: boolean) => boolean, diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 54be6ba0f..7dd9cdb8b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -82,7 +82,7 @@ interface PivotColumn { filters: string[]; } -export function computerPassLayout(poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { +export function computePassLayout(poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { const docMap = new Map(); childPairs.forEach(({ layout, data }, i) => { docMap.set(layout[Id], { @@ -97,7 +97,7 @@ export function computerPassLayout(poolData: Map, pivotDoc: Do return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []); } -export function computerStarburstLayout(poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { +export function computeStarburstLayout(poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { const mustFit = pivotDoc[WidthSym]() !== panelDim[0]; // if a panel size is set that's not the same as the pivot doc's size, then assume this is in a panel for a content fitting view (like a grid) in which case everything must be scaled to stay within the panel const docMap = new Map(); const docSize = mustFit ? panelDim[0] * 0.33 : 75; // assume an icon sized at 75 diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 57cccec4a..8cabf060d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -37,7 +37,7 @@ import { GestureOverlay } from '../../GestureOverlay'; import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke'; import { LightboxView } from '../../LightboxView'; import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; -import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from '../../nodes/DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment, ViewSpecPrefix } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { PresBox } from '../../nodes/trails/PresBox'; @@ -47,7 +47,7 @@ import { StyleProp } from '../../StyleProvider'; import { CollectionSubView } from '../CollectionSubView'; import { TreeViewType } from '../CollectionTreeView'; import { TabDocView } from '../TabDocView'; -import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines'; +import { computePivotLayout, computePassLayout as computePassLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines'; import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors'; import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; @@ -1272,7 +1272,8 @@ export class CollectionFreeFormView extends CollectionSubView { const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); - const pointerEvents = this.props.isContentActive() === false ? 'none' : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === 'pass' && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.()); + const pointerEvents = + this.props.isContentActive() === false ? 'none' : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.()); return pointerEvents; }; getChildDocView(entry: PoolData) { @@ -1328,8 +1329,8 @@ export class CollectionFreeFormView extends CollectionSubView ); } - addDocTab = action((doc: Doc, where: string) => { - if (where === 'inParent') { + addDocTab = action((doc: Doc, where: OpenWhere) => { + if (where === OpenWhere.inParent) { (doc instanceof Doc ? [doc] : doc).forEach(doc => { const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y)); doc.x = pt[0]; @@ -1337,7 +1338,7 @@ export class CollectionFreeFormView extends CollectionSubView(doc as any as Doc[]); return true; } @@ -1457,10 +1458,10 @@ export class CollectionFreeFormView extends CollectionSubView(); // prettier-ignore switch (this.layoutEngine) { - case 'pass': return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) }; - case 'timeline': return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) }; - case 'pivot': return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) }; - case 'starburst': return { newPool, computedElementData: this.doEngineLayout(newPool, computerStarburstLayout) }; + case computePassLayout.name : return { newPool, computedElementData: this.doEngineLayout(newPool, computePassLayout) }; + case computeTimelineLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) }; + case computePivotLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) }; + case computeStarburstLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeStarburstLayout) }; } return { newPool, computedElementData: this.doFreeformLayout(newPool) }; } @@ -1724,7 +1725,7 @@ export class CollectionFreeFormView extends CollectionSubView ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick); - addDocTab = (doc: Doc, where: string) => { - if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) { - this.dataDoc[this.props.fieldKey] = new List([doc]); - return true; - } - return this.props.addDocTab(doc, where); - }; focusDocument = (doc: Doc, options: DocFocusOptions) => this.props.focus(this.rootDoc, options); isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive(); isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false); @@ -278,7 +271,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { moveDocument={this.props.moveDocument} removeDocument={this.props.removeDocument} whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} - addDocTab={this.addDocTab} + addDocTab={this.props.addDocTab} pinToPres={this.props.pinToPres} bringToFront={returnFalse} /> diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx index adcd9e1e3..ef75fb159 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx @@ -1,35 +1,36 @@ -import React = require("react"); -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import { extname } from "path"; -import DatePicker from "react-datepicker"; -import { CellInfo } from "react-table"; -import { DateField } from "../../../../fields/DateField"; -import { Doc, DocListCast, Field, Opt } from "../../../../fields/Doc"; -import { Id } from "../../../../fields/FieldSymbols"; -import { List } from "../../../../fields/List"; -import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; -import { ComputedField } from "../../../../fields/ScriptField"; -import { BoolCast, Cast, DateCast, FieldValue, StrCast } from "../../../../fields/Types"; -import { ImageField } from "../../../../fields/URLField"; -import { emptyFunction, Utils } from "../../../../Utils"; -import { Docs } from "../../../documents/Documents"; -import { DocumentType } from "../../../documents/DocumentTypes"; -import { DocumentManager } from "../../../util/DocumentManager"; -import { DragManager } from "../../../util/DragManager"; -import { KeyCodes } from "../../../util/KeyCodes"; -import { CompileScript } from "../../../util/Scripting"; -import { SearchUtil } from "../../../util/SearchUtil"; -import { SnappingManager } from "../../../util/SnappingManager"; -import { undoBatch } from "../../../util/UndoManager"; +import React = require('react'); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import { extname } from 'path'; +import DatePicker from 'react-datepicker'; +import { CellInfo } from 'react-table'; +import { DateField } from '../../../../fields/DateField'; +import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc'; +import { Id } from '../../../../fields/FieldSymbols'; +import { List } from '../../../../fields/List'; +import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; +import { ComputedField } from '../../../../fields/ScriptField'; +import { BoolCast, Cast, DateCast, FieldValue, StrCast } from '../../../../fields/Types'; +import { ImageField } from '../../../../fields/URLField'; +import { emptyFunction, Utils } from '../../../../Utils'; +import { Docs } from '../../../documents/Documents'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { DocumentManager } from '../../../util/DocumentManager'; +import { DragManager } from '../../../util/DragManager'; +import { KeyCodes } from '../../../util/KeyCodes'; +import { CompileScript } from '../../../util/Scripting'; +import { SearchUtil } from '../../../util/SearchUtil'; +import { SnappingManager } from '../../../util/SnappingManager'; +import { undoBatch } from '../../../util/UndoManager'; import '../../../views/DocumentDecorations.scss'; -import { EditableView } from "../../EditableView"; +import { EditableView } from '../../EditableView'; import { MAX_ROW_HEIGHT } from '../../global/globalCssVariables.scss'; -import { DocumentIconContainer } from "../../nodes/DocumentIcon"; -import { OverlayView } from "../../OverlayView"; -import { CollectionView } from "../CollectionView"; -import "./CollectionSchemaView.scss"; +import { DocumentIconContainer } from '../../nodes/DocumentIcon'; +import { OverlayView } from '../../OverlayView'; +import { CollectionView } from '../CollectionView'; +import './CollectionSchemaView.scss'; +import { OpenWhere } from '../../nodes/DocumentView'; // intialize cell properties export interface CellProps { @@ -46,10 +47,9 @@ export interface CellProps { // currently unused renderDepth: number; // called when a button is pressed on the node itself - addDocTab: (document: Doc, where: string) => boolean; + addDocTab: (document: Doc, where: OpenWhere) => boolean; pinToPres: (document: Doc) => void; - moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, - addDocument: (document: Doc | Doc[]) => boolean) => boolean; + moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; isFocused: boolean; changeFocusedCellByIndex: (row: number, col: number) => void; // set whether the cell is in the isEditing mode @@ -67,7 +67,7 @@ export class CollectionSchemaCell extends React.Component { // return a field key that is corrected for whether it COMMENT public static resolvedFieldKey(column: string, rowDoc: Doc) { const fieldKey = column; - if (fieldKey.startsWith("*")) { + if (fieldKey.startsWith('*')) { const rootKey = fieldKey.substring(1); const allKeys = [...Array.from(Object.keys(rowDoc)), ...Array.from(Object.keys(Doc.GetProto(rowDoc)))]; const matchedKeys = allKeys.filter(key => key.includes(rootKey)); @@ -82,33 +82,37 @@ export class CollectionSchemaCell extends React.Component { protected _rowDataDoc = Doc.GetProto(this.props.rowProps.original); // methods for dragging and dropping protected _dropDisposer?: DragManager.DragDropDisposer; - @observable contents: string = ""; + @observable contents: string = ''; - componentDidMount() { document.addEventListener("keydown", this.onKeyDown); } - componentWillUnmount() { document.removeEventListener("keydown", this.onKeyDown); } + componentDidMount() { + document.addEventListener('keydown', this.onKeyDown); + } + componentWillUnmount() { + document.removeEventListener('keydown', this.onKeyDown); + } @action onKeyDown = (e: KeyboardEvent): void => { // If a cell is editable and clicked, hitting enter shoudl allow the user to edit it if (this.props.isFocused && this.props.isEditable && e.keyCode === KeyCodes.ENTER) { - document.removeEventListener("keydown", this.onKeyDown); + document.removeEventListener('keydown', this.onKeyDown); this._isEditing = true; this.props.setIsEditing(true); } - } + }; @action isEditingCallback = (isEditing: boolean): void => { // a general method that takes a boolean that determines whether the cell should be in // is-editing mode // remove the event listener if it's there - document.removeEventListener("keydown", this.onKeyDown); + document.removeEventListener('keydown', this.onKeyDown); // it's not already in is-editing mode, re-add the event listener - isEditing && document.addEventListener("keydown", this.onKeyDown); + isEditing && document.addEventListener('keydown', this.onKeyDown); this._isEditing = isEditing; this.props.setIsEditing(isEditing); this.props.changeFocusedCellByIndex(this.props.row, this.props.col); - } + }; @action onPointerDown = async (e: React.PointerEvent): Promise => { @@ -119,19 +123,19 @@ export class CollectionSchemaCell extends React.Component { this.props.setPreviewDoc(this.props.rowProps.original); let url: string; - if (url = StrCast(this.props.rowProps.row.href)) { + if ((url = StrCast(this.props.rowProps.row.href))) { // opens up the the doc in a new window, blurring the old one try { new URL(url); const temp = window.open(url)!; temp.blur(); window.focus(); - } catch { } + } catch {} } const doc = Cast(this._rowDoc[this.renderFieldKey], Doc, null); doc && this.props.setPreviewDoc(doc); - } + }; @undoBatch applyToDoc = (doc: Doc, row: number, col: number, run: (args?: { [name: string]: any }) => any) => { @@ -142,7 +146,7 @@ export class CollectionSchemaCell extends React.Component { doc[this.renderFieldKey] = res.result; return true; // return whether the change was successful - } + }; private drop = (e: Event, de: DragManager.DropEvent) => { // if the drag has data at its completion @@ -151,41 +155,51 @@ export class CollectionSchemaCell extends React.Component { if (de.complete.docDragData.draggedDocuments.length === 1) { // update the renderFieldKey this._rowDataDoc[this.renderFieldKey] = de.complete.docDragData.draggedDocuments[0]; - } - else { + } else { // create schema document reflecting the new column arrangement - const coll = Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], de.complete.docDragData.draggedDocuments, {}); + const coll = Docs.Create.SchemaDocument([new SchemaHeaderField('title', '#f1efeb')], de.complete.docDragData.draggedDocuments, {}); this._rowDataDoc[this.renderFieldKey] = coll; } e.stopPropagation(); } - } + }; protected dropRef = (ele: HTMLElement | null) => { // if the drop disposer is not undefined, run its function this._dropDisposer?.(); // if ele is not null, give ele a non-undefined drop disposer ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this))); - } + }; returnHighlights(contents: string, positions?: number[]) { if (positions) { const results = []; StrCast(this.props.Document._searchString); const length = StrCast(this.props.Document._searchString).length; - const color = contents ? "black" : "grey"; + const color = contents ? 'black' : 'grey'; - results.push({contents?.slice(0, positions[0])}); + results.push( + + {contents?.slice(0, positions[0])} + + ); positions.forEach((num, cur) => { - results.push({contents?.slice(num, num + length)}); + results.push( + + {contents?.slice(num, num + length)} + + ); let end = 0; - cur === positions.length - 1 ? end = contents.length : end = positions[cur + 1]; - results.push({contents?.slice(num + length, end)}); - } - ); + cur === positions.length - 1 ? (end = contents.length) : (end = positions[cur + 1]); + results.push( + + {contents?.slice(num + length, end)} + + ); + }); return results; } - return {contents ? contents?.valueOf() : "undefined"}; + return {contents ? contents?.valueOf() : 'undefined'}; } @computed get renderFieldKey() { @@ -199,10 +213,9 @@ export class CollectionSchemaCell extends React.Component { const aliasdoc = await SearchUtil.GetAliasesOfDocument(this._rowDataDoc); const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null); // Jump to the this document - DocumentManager.Instance.jumpToDocument(this._rowDoc, false, emptyFunction, targetContext ? [targetContext] : [], - undefined, undefined, undefined, () => this.props.setPreviewDoc(this._rowDoc)); + DocumentManager.Instance.jumpToDocument(this._rowDoc, false, emptyFunction, targetContext ? [targetContext] : [], undefined, undefined, undefined, () => this.props.setPreviewDoc(this._rowDoc)); } - } + }; renderCellWithType(type: string | undefined) { const dragRef: React.RefObject = React.createRef(); @@ -214,29 +227,29 @@ export class CollectionSchemaCell extends React.Component { const onPointerEnter = (e: React.PointerEvent): void => { // e.buttons === 1 means the left moue pointer is down - if (e.buttons === 1 && SnappingManager.GetIsDragging() && (type === "document" || type === undefined)) { - dragRef.current!.className = "collectionSchemaView-cellContainer doc-drag-over"; + if (e.buttons === 1 && SnappingManager.GetIsDragging() && (type === 'document' || type === undefined)) { + dragRef.current!.className = 'collectionSchemaView-cellContainer doc-drag-over'; } }; const onPointerLeave = (e: React.PointerEvent): void => { // change the class name to indicate that the cell is no longer being dragged - dragRef.current!.className = "collectionSchemaView-cellContainer"; + dragRef.current!.className = 'collectionSchemaView-cellContainer'; }; let contents = Field.toString(field as Field); // display 2 hyphens instead of a blank box for empty cells - contents = contents === "" ? "--" : contents; + contents = contents === '' ? '--' : contents; // classname reflects the tatus of the cell - let className = "collectionSchemaView-cellWrapper"; - if (this._isEditing) className += " editing"; - if (this.props.isFocused && this.props.isEditable) className += " focused"; - if (this.props.isFocused && !this.props.isEditable) className += " inactive"; + let className = 'collectionSchemaView-cellWrapper'; + if (this._isEditing) className += ' editing'; + if (this.props.isFocused && this.props.isEditable) className += ' focused'; + if (this.props.isFocused && !this.props.isEditable) className += ' inactive'; const positions = []; - if (StrCast(this.props.Document._searchString).toLowerCase() !== "") { + if (StrCast(this.props.Document._searchString).toLowerCase() !== '') { // term is ...promise pending... if the field is a Promise, otherwise it is the cell's contents - let term = (field instanceof Promise) ? "...promise pending..." : contents.toLowerCase(); + let term = field instanceof Promise ? '...promise pending...' : contents.toLowerCase(); const search = StrCast(this.props.Document._searchString).toLowerCase(); let start = term.indexOf(search); let tally = 0; @@ -256,56 +269,60 @@ export class CollectionSchemaCell extends React.Component { positions.pop(); } } - const placeholder = type === "number" ? "0" : contents === "" ? "--" : "undefined"; + const placeholder = type === 'number' ? '0' : contents === '' ? '--' : 'undefined'; return ( -
this._isEditing = true)} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}> +
(this._isEditing = true))} + onPointerEnter={onPointerEnter} + onPointerLeave={onPointerLeave}>
-
- {!this.props.Document._searchDoc ? +
+ {!this.props.Document._searchDoc ? ( { const cfield = ComputedField.WithoutComputed(() => FieldValue(field)); const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined; - const cfinalScript = cscript?.split("return")[cscript.split("return").length - 1]; - return cscript ? (cfinalScript?.endsWith(";") ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) : - Field.IsField(cfield) ? Field.toScriptString(cfield) : ""; + const cfinalScript = cscript?.split('return')[cscript.split('return').length - 1]; + return cscript ? (cfinalScript?.endsWith(';') ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) : Field.IsField(cfield) ? Field.toScriptString(cfield) : ''; }} SetValue={action((value: string) => { // sets what is displayed after the user makes an input let retVal = false; - if (value.startsWith(":=") || value.startsWith("=:=")) { + if (value.startsWith(':=') || value.startsWith('=:=')) { // decides how to compute a value when given either of the above strings - const script = value.substring(value.startsWith("=:=") ? 3 : 2); - retVal = this.props.setComputed(script, value.startsWith(":=") ? this._rowDataDoc : this._rowDoc, this.renderFieldKey, this.props.row, this.props.col); + const script = value.substring(value.startsWith('=:=') ? 3 : 2); + retVal = this.props.setComputed(script, value.startsWith(':=') ? this._rowDataDoc : this._rowDoc, this.renderFieldKey, this.props.row, this.props.col); } else { // check if the input is a number let inputIsNum = true; for (const s of value) { - if (isNaN(parseInt(s)) && !(s === ".") && !(s === ",")) { + if (isNaN(parseInt(s)) && !(s === '.') && !(s === ',')) { inputIsNum = false; } } // check if the input is a boolean - const inputIsBool: boolean = value === "false" || value === "true"; - // what to do in the case - if (!inputIsNum && !inputIsBool && !value.startsWith("=")) { + const inputIsBool: boolean = value === 'false' || value === 'true'; + // what to do in the case + if (!inputIsNum && !inputIsBool && !value.startsWith('=')) { // if it's not a number, it's a string, and should be processed as such - // strips the string of quotes when it is edited to prevent quotes form being added to the text automatically + // strips the string of quotes when it is edited to prevent quotes form being added to the text automatically // after each edit let valueSansQuotes = value; if (this._isEditing) { const vsqLength = valueSansQuotes.length; // get rid of outer quotes - valueSansQuotes = valueSansQuotes.substring(value.startsWith("\"") ? 1 : 0, - valueSansQuotes.charAt(vsqLength - 1) === "\"" ? vsqLength - 1 : vsqLength); + valueSansQuotes = valueSansQuotes.substring(value.startsWith('"') ? 1 : 0, valueSansQuotes.charAt(vsqLength - 1) === '"' ? vsqLength - 1 : vsqLength); } let inputAsString = '"'; // escape any quotes in the string @@ -319,27 +336,27 @@ export class CollectionSchemaCell extends React.Component { // add a closing quote inputAsString += '"'; //two options here: we can strip off outer quotes or we can figure out what's going on with the script - const script = CompileScript(inputAsString, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); + const script = CompileScript(inputAsString, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } }); const changeMade = inputAsString.length !== value.length || inputAsString.length - 2 !== value.length; // change it if a change is made, otherwise, just compile using the old cell conetnts script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); // handle numbers and expressions - } else if (inputIsNum || value.startsWith("=")) { + } else if (inputIsNum || value.startsWith('=')) { //TODO: make accept numbers - const inputscript = value.substring(value.startsWith("=") ? 1 : 0); + const inputscript = value.substring(value.startsWith('=') ? 1 : 0); // if commas are not stripped, the parser only considers the numbers after the last comma - let inputSansCommas = ""; + let inputSansCommas = ''; for (const s of inputscript) { - if (!(s === ",")) { + if (!(s === ',')) { inputSansCommas += s; } } - const script = CompileScript(inputSansCommas, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); + const script = CompileScript(inputSansCommas, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } }); const changeMade = value.length - 2 !== value.length; script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); // handle booleans } else if (inputIsBool) { - const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); + const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } }); const changeMade = value.length - 2 !== value.length; script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); } @@ -352,33 +369,47 @@ export class CollectionSchemaCell extends React.Component { })} OnFillDown={async (value: string) => { // computes all of the value preceded by := - const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); - script.compiled && DocListCast(this.props.Document[this.props.fieldKey]). - forEach((doc, i) => value.startsWith(":=") ? - this.props.setComputed(value.substring(2), Doc.GetProto(doc), this.renderFieldKey, i, this.props.col) : - this.applyToDoc(Doc.GetProto(doc), i, this.props.col, script.run)); + const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } }); + script.compiled && + DocListCast(this.props.Document[this.props.fieldKey]).forEach((doc, i) => + value.startsWith(':=') ? this.props.setComputed(value.substring(2), Doc.GetProto(doc), this.renderFieldKey, i, this.props.col) : this.applyToDoc(Doc.GetProto(doc), i, this.props.col, script.run) + ); }} /> - : + ) : ( this.returnHighlights(contents, positions) - } -
+ )} +
); } - render() { return this.renderCellWithType(undefined); } + render() { + return this.renderCellWithType(undefined); + } } @observer -export class CollectionSchemaNumberCell extends CollectionSchemaCell { render() { return this.renderCellWithType("number"); } } +export class CollectionSchemaNumberCell extends CollectionSchemaCell { + render() { + return this.renderCellWithType('number'); + } +} @observer -export class CollectionSchemaBooleanCell extends CollectionSchemaCell { render() { return this.renderCellWithType("boolean"); } } +export class CollectionSchemaBooleanCell extends CollectionSchemaCell { + render() { + return this.renderCellWithType('boolean'); + } +} @observer -export class CollectionSchemaStringCell extends CollectionSchemaCell { render() { return this.renderCellWithType("string"); } } +export class CollectionSchemaStringCell extends CollectionSchemaCell { + render() { + return this.renderCellWithType('string'); + } +} @observer export class CollectionSchemaDateCell extends CollectionSchemaCell { @@ -396,24 +427,24 @@ export class CollectionSchemaDateCell extends CollectionSchemaCell { // ^ DateCast is always undefined for some reason, but that is what the field should be set to this._rowDoc[this.renderFieldKey] = new DateField(date as Date); //} - } + }; render() { - return !this.props.isFocused ? {this._date ? Field.toString(this._date as Field) : "--"} : - this.handleChange(date)} - onChange={date => this.handleChange(date)} - />; + return !this.props.isFocused ? ( + {this._date ? Field.toString(this._date as Field) : '--'} + ) : ( + this.handleChange(date)} onChange={date => this.handleChange(date)} /> + ); } } @observer export class CollectionSchemaDocCell extends CollectionSchemaCell { - _overlayDisposer?: () => void; - @computed get _doc() { return FieldValue(Cast(this._rowDoc[this.renderFieldKey], Doc)); } + @computed get _doc() { + return FieldValue(Cast(this._rowDoc[this.renderFieldKey], Doc)); + } @action onSetValue = (value: string) => { @@ -422,7 +453,7 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell { const script = CompileScript(value, { addReturn: true, typecheck: true, - transformer: DocumentIconContainer.getTransformer() + transformer: DocumentIconContainer.getTransformer(), }); // compile the script const results = script.compiled && script.run(); @@ -432,44 +463,43 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell { return true; } return false; - } + }; - componentWillUnmount() { this.onBlur(); } + componentWillUnmount() { + this.onBlur(); + } - onBlur = () => { this._overlayDisposer?.(); }; + onBlur = () => { + this._overlayDisposer?.(); + }; onFocus = () => { this.onBlur(); this._overlayDisposer = OverlayView.Instance.addElement(, { x: 0, y: 0 }); - } + }; @action isEditingCallback = (isEditing: boolean): void => { // the isEditingCallback from a general CollectionSchemaCell - document.removeEventListener("keydown", this.onKeyDown); - isEditing && document.addEventListener("keydown", this.onKeyDown); + document.removeEventListener('keydown', this.onKeyDown); + isEditing && document.addEventListener('keydown', this.onKeyDown); this._isEditing = isEditing; this.props.setIsEditing(isEditing); this.props.changeFocusedCellByIndex(this.props.row, this.props.col); - } + }; render() { // if there's a doc, render it - return !this._doc ? this.renderCellWithType("document") : -
-
+ return !this._doc ? ( + this.renderCellWithType('document') + ) : ( +
+
StrCast(this._doc?.title)} SetValue={action((value: string) => { @@ -477,33 +507,36 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell { return true; })} /> -
-
this._doc && this.props.addDocTab(this._doc, "add:right")} className="collectionSchemaView-cellContents-docButton"> +
+
this._doc && this.props.addDocTab(this._doc, OpenWhere.addRight)} className="collectionSchemaView-cellContents-docButton">
-
; +
+ ); } } @observer export class CollectionSchemaImageCell extends CollectionSchemaCell { - choosePath(url: URL) { - if (url.protocol === "data") return url.href; // if the url ises the data protocol, just return the href + if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver - if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href;//Why is this here — good question + if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; //Why is this here — good question const ext = extname(url.href); - return url.href.replace(ext, "_o" + ext); + return url.href.replace(ext, '_o' + ext); } render() { const field = Cast(this._rowDoc[this.renderFieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc - const alts = DocListCast(this._rowDoc[this.renderFieldKey + "-alternates"]); // retrieve alternate documents that may be rendered as alternate images - const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url).filter(url => url).map(url => this.choosePath(url)); // access the primary layout data of the alternate documents + const alts = DocListCast(this._rowDoc[this.renderFieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images + const altpaths = alts + .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url) + .filter(url => url) + .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths; // If there is a path, follow it; otherwise, follow a link to a default image icon - const url = paths.length ? paths : [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")]; + const url = paths.length ? paths : [Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')]; const aspect = Doc.NativeAspect(this._rowDoc); // aspect ratio let width = Math.min(75, this.props.rowProps.width); // get a with that is no smaller than 75px @@ -511,25 +544,28 @@ export class CollectionSchemaImageCell extends CollectionSchemaCell { width = height * aspect; // increase the width of the image if necessary to maintain proportionality const reference = React.createRef(); - return
-
- -
-
; + return ( +
+
+ +
+
+ ); } } - @observer export class CollectionSchemaListCell extends CollectionSchemaCell { _overlayDisposer?: () => void; - @computed get _field() { return this._rowDoc[this.renderFieldKey]; } - @computed get _optionsList() { return this._field as List; } + @computed get _field() { + return this._rowDoc[this.renderFieldKey]; + } + @computed get _optionsList() { + return this._field as List; + } @observable private _opened = false; // whether the list is opened - @observable private _text = "select an item"; + @observable private _text = 'select an item'; @observable private _selectedNum = 0; // the index of the list item selected @action @@ -538,102 +574,109 @@ export class CollectionSchemaListCell extends CollectionSchemaCell { this._optionsList[this._selectedNum] = this._text = value; (this._field as List).splice(this._selectedNum, 1, value); - } + }; @action onSelected = (element: string, index: number) => { // if an item is selected, the private variables should update to reflect this this._text = element; this._selectedNum = index; - } + }; onFocus = () => { this._overlayDisposer?.(); this._overlayDisposer = OverlayView.Instance.addElement(, { x: 0, y: 0 }); - } + }; render() { const link = false; const reference = React.createRef(); - // if the list is not opened, don't display it; otherwise, do. + // if the list is not opened, don't display it; otherwise, do. if (this._optionsList?.length) { - const options = !this._opened ? (null) : + const options = !this._opened ? null : (
{this._optionsList.map((element, index) => { const val = Field.toString(element); - return
this.onSelected(StrCast(element), index)} > - {val} -
; + return ( +
this.onSelected(StrCast(element), index)}> + {val} +
+ ); })} -
; - - const plainText =
{this._text}
; - const textarea =
- this._text} - SetValue={action((value: string) => { - // add special for params - this.onSetValue(value); - return true; - })} - /> -
; +
+ ); + + const plainText =
{this._text}
; + const textarea = ( +
+ this._text} + SetValue={action((value: string) => { + // add special for params + this.onSetValue(value); + return true; + })} + /> +
+ ); //☰ return (
-
{link ? plainText : textarea}
{options} -
+
); } - return this.renderCellWithType("list"); + return this.renderCellWithType('list'); } } - @observer export class CollectionSchemaCheckboxCell extends CollectionSchemaCell { - @computed get _isChecked() { return BoolCast(this._rowDoc[this.renderFieldKey]); } + @computed get _isChecked() { + return BoolCast(this._rowDoc[this.renderFieldKey]); + } render() { const reference = React.createRef(); return (
- this._rowDoc[this.renderFieldKey] = e.target.checked} /> + (this._rowDoc[this.renderFieldKey] = e.target.checked)} />
); } } - @observer export class CollectionSchemaButtons extends CollectionSchemaCell { // the navigation buttons for schema view when it is used for search. render() { - return !this.props.Document._searchDoc || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this._rowDoc.type) as DocumentType) ? <> : -
+ return !this.props.Document._searchDoc || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this._rowDoc.type) as DocumentType) ? ( + <> + ) : ( +
- -
; +
+ ); } } diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx index f872637e5..3cb2df7d3 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx @@ -10,6 +10,7 @@ import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; +import { OpenWhere } from '../../nodes/DocumentView'; import './CollectionSchemaView.scss'; export interface MovableRowProps { @@ -138,7 +139,7 @@ export class MovableRow extends React.Component
-
this.props.addDocTab(this.props.rowInfo.original, 'add:right')}> +
this.props.addDocTab(this.props.rowInfo.original, OpenWhere.addRight)}>
diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx index fafea5ce3..45ad4f86b 100644 --- a/src/client/views/collections/collectionSchema/SchemaTable.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx @@ -23,7 +23,7 @@ import { undoBatch } from '../../../util/UndoManager'; import '../../../views/DocumentDecorations.scss'; import { ContextMenu } from '../../ContextMenu'; import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss'; -import { DocumentView } from '../../nodes/DocumentView'; +import { DocumentView, OpenWhere } from '../../nodes/DocumentView'; import { DefaultStyleProvider } from '../../StyleProvider'; import { CollectionView } from '../CollectionView'; import { @@ -86,7 +86,7 @@ export interface SchemaTableProps { ScreenToLocalTransform: () => Transform; active: (outsideReaction: boolean | undefined) => boolean | undefined; onDrop: (e: React.DragEvent, options: DocumentOptions, completed?: (() => void) | undefined) => void; - addDocTab: (document: Doc, where: string) => boolean; + addDocTab: (document: Doc, where: OpenWhere) => boolean; pinToPres: (document: Doc) => void; isSelected: (outsideReaction?: boolean) => boolean; isFocused: (document: Doc, outsideReaction: boolean) => boolean; @@ -625,7 +625,7 @@ export class SchemaTable extends React.Component { }; onOpenClick = () => { - this._showDoc && this.props.addDocTab(this._showDoc, 'add:right'); + this._showDoc && this.props.addDocTab(this._showDoc, OpenWhere.addRight); }; getPreviewTransform = (): Transform => { diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 570039550..868822fbf 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -16,7 +16,7 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec import { DocComponent } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import './CollectionFreeFormDocumentView.scss'; -import { DocumentView, DocumentViewProps } from './DocumentView'; +import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView'; import React = require('react'); export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { @@ -199,7 +199,7 @@ export class CollectionFreeFormDocumentView extends DocComponent string; whenChildContentsActiveChanged: (isActive: boolean) => void; rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected - addDocTab: (doc: Doc, where: string) => boolean; + addDocTab: (doc: Doc, where: OpenWhere) => boolean; filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) addDocument?: (doc: Doc | Doc[]) => boolean; removeDocument?: (doc: Doc | Doc[]) => boolean; @@ -474,7 +495,7 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "map-pin", selected: -1 }); + // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), OpenWhere.addRight), icon: "map-pin", selected: -1 }); const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]); (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && RadialMenu.Instance.addItem({ @@ -485,7 +506,7 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(this.props.Document, "add:right"), icon: "trash", selected: -1 }); + // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, OpenWhere.addRight), icon: "trash", selected: -1 }); RadialMenu.Instance.addItem({ description: 'Pin', event: () => this.props.pinToPres(this.props.Document, {}), icon: 'map-pin', selected: -1 }); RadialMenu.Instance.addItem({ description: 'Open', event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: 'trash', selected: -1 }); @@ -586,7 +607,7 @@ export class DocumentViewInternal extends DocComponent (func().result?.select === true ? this.props.select(false) : ''), 'on double click'); } else if (!Doc.IsSystem(this.rootDoc) && !this.rootDoc.isLinkButton) { - UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, 'lightbox', this.props.LayoutTemplate?.(), this.props.addDocTab), 'double tap'); + UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, OpenWhere.lightbox, this.props.LayoutTemplate?.(), this.props.addDocTab), 'double tap'); SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.props.Document); } @@ -857,7 +878,7 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(templateDoc, 'add:right'), icon: 'eye' }); + !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' }); !Doc.noviceMode && appearanceItems.push({ description: 'Add a Field', @@ -957,8 +978,8 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), 'add:right'), icon: 'layer-group' }); - !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), 'add:right'), icon: 'keyboard' }); + helpItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'layer-group' }); + !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'keyboard' }); !Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.props.Document), icon: 'hand-point-right' }); !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.props.Document[DataSym]), icon: 'hand-point-right' }); diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 7d04c4b64..18c5b81ec 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -20,6 +20,7 @@ import { ContextMenuProps } from '../ContextMenuItem'; import e = require('express'); import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { ImageBox } from './ImageBox'; +import { OpenWhere } from './DocumentView'; export type KVPScript = { script: CompiledScript; @@ -259,8 +260,8 @@ export class KeyValueBox extends React.Component { openItems.push({ description: 'Default Perspective', event: () => { - this.props.addDocTab(this.props.Document, 'close'); - this.props.addDocTab(this.fieldDocToLayout, 'add:right'); + this.props.addDocTab(this.props.Document, OpenWhere.close); + this.props.addDocTab(this.fieldDocToLayout, OpenWhere.addRight); }, icon: 'image', }); diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 80def3025..e74ef4a39 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -1,18 +1,19 @@ import { action, observable } from 'mobx'; -import { observer } from "mobx-react"; +import { observer } from 'mobx-react'; import { Doc, Field, Opt } from '../../../fields/Doc'; import { emptyFunction, returnFalse, returnOne, returnZero, returnEmptyFilter, returnEmptyDoclist, emptyPath } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; -import { EditableView } from "../EditableView"; +import { EditableView } from '../EditableView'; import { FieldView, FieldViewProps } from './FieldView'; import { KeyValueBox } from './KeyValueBox'; -import "./KeyValueBox.scss"; -import "./KeyValuePair.scss"; -import React = require("react"); +import './KeyValueBox.scss'; +import './KeyValuePair.scss'; +import React = require('react'); import { DefaultStyleProvider } from '../StyleProvider'; +import { OpenWhere } from './DocumentView'; // Represents one row in a key value plane @@ -23,7 +24,7 @@ export interface KeyValuePairProps { keyWidth: number; PanelHeight: () => number; PanelWidth: () => number; - addDocTab: (doc: Doc, where: string) => boolean; + addDocTab: (doc: Doc, where: OpenWhere) => boolean; } @observer export class KeyValuePair extends React.Component { @@ -34,23 +35,23 @@ export class KeyValuePair extends React.Component { @action handleCheck = (e: React.ChangeEvent) => { this.isChecked = e.currentTarget.checked; - } + }; @action uncheck = () => { this.checkbox.current!.checked = false; this.isChecked = false; - } + }; onContextMenu = (e: React.MouseEvent) => { const value = this.props.doc[this.props.keyName]; if (value instanceof Doc) { e.stopPropagation(); e.preventDefault(); - ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" }); + ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'layer-group' }); ContextMenu.Instance.displayMenu(e.clientX, e.clientY); } - } + }; render() { const props: FieldViewProps = { @@ -68,7 +69,7 @@ export class KeyValuePair extends React.Component { isSelected: returnFalse, setHeight: returnFalse, select: emptyFunction, - dropAction: "alias", + dropAction: 'alias', bringToFront: emptyFunction, renderDepth: 1, isContentActive: returnFalse, @@ -92,30 +93,30 @@ export class KeyValuePair extends React.Component { doc = doc.proto; } const parenCount = Math.max(0, protoCount - 1); - const keyStyle = protoCount === 0 ? "black" : "blue"; + const keyStyle = protoCount === 0 ? 'black' : 'blue'; - const hover = { transition: "0.3s ease opacity", opacity: this.isPointerOver || this.isChecked ? 1 : 0 }; + const hover = { transition: '0.3s ease opacity', opacity: this.isPointerOver || this.isChecked ? 1 : 0 }; return ( - this.isPointerOver = true)} onPointerLeave={action(() => this.isPointerOver = false)}> + (this.isPointerOver = true))} onPointerLeave={action(() => (this.isPointerOver = false))}>
- - -
{"(".repeat(parenCount)}{props.fieldKey}{")".repeat(parenCount)}
+ +
+ {'('.repeat(parenCount)} + {props.fieldKey} + {')'.repeat(parenCount)} +
@@ -123,13 +124,13 @@ export class KeyValuePair extends React.Component { Field.toKeyValueString(props.Document, props.fieldKey)} - SetValue={(value: string) => - KeyValueBox.SetField(props.Document, props.fieldKey, value)} /> + SetValue={(value: string) => KeyValueBox.SetField(props.Document, props.fieldKey, value)} + />
); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index d6cf79f87..be9565452 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -17,6 +17,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import './LinkAnchorBox.scss'; import { LinkDocPreview } from './LinkDocPreview'; import React = require('react'); +import { OpenWhere } from './DocumentView'; const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -88,13 +89,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent() { }; openLinkDocOnRight = (e: React.MouseEvent) => { - this.props.addDocTab(this.rootDoc, 'add:right'); + this.props.addDocTab(this.rootDoc, OpenWhere.addRight); }; openLinkTargetOnRight = (e: React.MouseEvent) => { const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null)); alias._isLinkButton = undefined; alias.layoutKey = 'layout'; - this.props.addDocTab(alias, 'add:right'); + this.props.addDocTab(alias, OpenWhere.addRight); }; @action openLinkEditor = action((e: React.MouseEvent) => { diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index a47577701..135fbca31 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -14,7 +14,7 @@ import { LinkFollower } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; -import { DocumentView, DocumentViewSharedProps } from './DocumentView'; +import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView'; import './LinkDocPreview.scss'; import React = require('react'); import { LinkEditor } from '../linking/LinkEditor'; @@ -156,7 +156,7 @@ export class LinkDocPreview extends React.Component { LinkDocPreview.Clear(); LinkFollower.FollowLink(this._linkDoc, this._linkSrc, this.props.docProps, false); } else if (this.props.hrefs?.length) { - this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, useCors: true }), 'add:right'); + this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, useCors: true }), OpenWhere.addRight); } }; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 70ac84fa4..82d5b00f9 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -31,6 +31,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import { RecordingBox } from './RecordingBox'; import './VideoBox.scss'; import { ObjectField } from '../../../fields/ObjectField'; +import { OpenWhere } from './DocumentView'; const path = require('path'); /** @@ -273,7 +274,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent() { } showTemplate = (): void => { const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null); - dragFactory && this.props.addDocTab(dragFactory, 'add:right'); + dragFactory && this.props.addDocTab(dragFactory, OpenWhere.addRight); }; dragAsTemplate = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 1e7cb6ea5..63347015b 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -17,6 +17,7 @@ import { Tooltip } from '@material-ui/core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { NodeSelection } from 'prosemirror-state'; +import { OpenWhere } from '../DocumentView'; export class DashFieldView { dom: HTMLDivElement; // container for label and value @@ -227,7 +228,7 @@ export class DashFieldViewInternal extends React.Component c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb')); list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb')); alias._pivotField = this._fieldKey.startsWith('#') ? '#' : this._fieldKey; - this.props.tbox.props.addDocTab(alias, 'add:right'); + this.props.tbox.props.addDocTab(alias, OpenWhere.addRight); } }; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index fdd61463d..ce4639b76 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -45,7 +45,7 @@ import { LightboxView } from '../../LightboxView'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { SidebarAnnos } from '../../SidebarAnnos'; import { StyleProp } from '../../StyleProvider'; -import { DocumentViewInternal } from '../DocumentView'; +import { DocumentViewInternal, OpenWhere } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { LinkDocPreview } from '../LinkDocPreview'; import { DashDocCommentView } from './DashDocCommentView'; @@ -1428,7 +1428,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const docView = DocumentManager.Instance.getDocumentView(audiodoc); if (!docView) { - this.props.addDocTab(audiodoc, 'add:bottom'); + this.props.addDocTab(audiodoc, OpenWhere.addBottom); setTimeout(func); } else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, 'number', null)); // bcz: would be nice to find the next audio tag in the doc and play until that }; diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 3d9bd6add..68b0488a2 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -9,6 +9,7 @@ import { GetEffectiveAcl } from '../../../../fields/util'; import { Utils } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; import { SelectionManager } from '../../../util/SelectionManager'; +import { OpenWhere } from '../DocumentView'; import { liftListItem, sinkListItem } from './prosemirrorPatches.js'; const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false; @@ -135,7 +136,7 @@ export function buildKeymap>(schema: S, props: any, mapKey //Command to create a new Tab with a PDF of all the command shortcuts bind('Mod-/', (state: EditorState, dispatch: (tx: Transaction) => void) => { const newDoc = Docs.Create.PdfDocument(Utils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 }); - props.addDocTab(newDoc, 'add:right'); + props.addDocTab(newDoc, OpenWhere.addRight); }); //Commands to modify BlockType diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index e19b53f50..adfd2fda1 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -23,7 +23,7 @@ import { SelectionManager } from '../../../util/SelectionManager'; import { SettingsManager } from '../../../util/SettingsManager'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { CollectionDockingView } from '../../collections/CollectionDockingView'; -import { CollectionFreeFormView, MarqueeViewBounds } from '../../collections/collectionFreeForm'; +import { CollectionFreeFormView, computeTimelineLayout, MarqueeViewBounds } from '../../collections/collectionFreeForm'; import { CollectionView } from '../../collections/CollectionView'; import { TabDocView } from '../../collections/TabDocView'; import { ViewBoxBaseComponent } from '../../DocComponent'; @@ -35,6 +35,7 @@ import { ScriptingBox } from '../ScriptingBox'; import './PresBox.scss'; import { PresEffect, PresMovement, PresStatus } from './PresEnums'; import { map } from 'bluebird'; +import { OpenWhere, OpenWhereMod } from '../DocumentView'; const { Howl } = require('howler'); export interface PinProps { @@ -196,7 +197,7 @@ export class PresBox extends ViewBoxBaseComponent() { ); this.props.setContentView?.(this); this._unmounting = false; - this.rootDoc._forceRenderEngine = 'timeline'; + this.rootDoc._forceRenderEngine = computeTimelineLayout.name; this.layoutDoc.presStatus = PresStatus.Edit; this.layoutDoc._gridGap = 0; this.layoutDoc._yMargin = 0; @@ -563,7 +564,7 @@ export class PresBox extends ViewBoxBaseComponent() { self._eleArray.splice(0, self._eleArray.length, ...eleViewCache); }); const openInTab = (doc: Doc, finished?: () => void) => { - (collectionDocView ?? this).props.addDocTab(doc, ''); + (collectionDocView ?? this).props.addDocTab(doc, OpenWhere.add); this.layoutDoc.presCollection = targetDoc; // this still needs some fixing setTimeout(resetSelection, 500); @@ -725,7 +726,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) { this.layoutDoc.presStatus = PresStatus.Edit; Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, this.rootDoc); - CollectionDockingView.AddSplit(this.rootDoc, 'right'); + CollectionDockingView.AddSplit(this.rootDoc, OpenWhereMod.right); } else { this.layoutDoc.presStatus = PresStatus.Edit; clearTimeout(this._presTimer); @@ -1760,7 +1761,7 @@ export class PresBox extends ViewBoxBaseComponent() { TabDocView.PinDoc(doc, {}); this.gotoDocument(this.childDocs.length, this.activeItem); } else { - this.props.addDocTab(doc, 'add:right'); + this.props.addDocTab(doc, OpenWhere.addRight); } } }; @@ -2322,7 +2323,7 @@ export class PresBox extends ViewBoxBaseComponent() { static NavigateToDoc(bestTarget: Doc, activeItem: Doc) { const srcContext = Cast(bestTarget.context, Doc, null) ?? Cast(Cast(bestTarget.annotationOn, Doc, null)?.context, Doc, null); const openInTab = (doc: Doc, finished?: () => void) => { - CollectionDockingView.AddSplit(doc, 'right'); + CollectionDockingView.AddSplit(doc, OpenWhereMod.right); finished?.(); }; PresBox.NavigateToTarget(bestTarget, activeItem, openInTab, srcContext); diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 8265de445..2ae597b0b 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -700,7 +700,7 @@ export class MobileInterface extends React.Component { className="docButton" title={Doc.isDocPinned(this._activeDoc) ? 'Unpin from presentation' : 'Pin to presentation'} style={{ backgroundColor: isPinned ? 'black' : 'white', color: isPinned ? 'white' : 'black' }} - onClick={e => TabDocView.PinDoc(this._activeDoc)}> + onClick={e => TabDocView.PinDoc(this._activeDoc, {})}>
); -- cgit v1.2.3-70-g09d2 From 613daac016c367205ff1afddd81b7b9111c52d33 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 5 Dec 2022 22:45:22 -0500 Subject: cleaning up following links and pres item following so that view transitions don't interfere when clicking quickly (eg through animation frames). changed animations to animate multi-level zooming parallel. --- src/client/util/CurrentUserUtils.ts | 2 +- src/client/util/DocumentManager.ts | 145 ++++++------ src/client/util/LinkFollower.ts | 2 +- src/client/views/LightboxView.tsx | 8 +- src/client/views/PropertiesButtons.tsx | 8 +- src/client/views/PropertiesView.tsx | 5 +- .../views/collections/CollectionDockingView.tsx | 3 +- src/client/views/collections/CollectionMenu.tsx | 11 +- src/client/views/collections/TabDocView.tsx | 16 +- .../CollectionFreeFormLinkView.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 39 ++-- .../collections/collectionFreeForm/MarqueeView.tsx | 14 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 57 ++--- src/client/views/nodes/DocumentView.tsx | 54 ++++- src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 2 +- src/client/views/nodes/button/FontIconBox.tsx | 8 +- src/client/views/nodes/trails/PresBox.tsx | 253 ++++++--------------- 19 files changed, 280 insertions(+), 357 deletions(-) (limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index d13209c4f..9dfc91e3f 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -667,7 +667,7 @@ export class CurrentUserUtils { title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}}, { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "tab")'}, width: 20, scripts: { onClick: 'pinWithView(_readOnly_, altKey)'}}, { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, - { title: "Num", icon: "", toolTip: "Frame Number", btnType: ButtonType.TextButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: {}}, + { title: "Num", icon: "", toolTip: "Frame Number", btnType: ButtonType.TextButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()', scripts: { script: 'return curKeyFrame(_readOnly_)'}}, { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 1b63b615b..d2368f4f6 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,19 +1,19 @@ import { action, observable, runInAction } from 'mobx'; -import { Doc, DocListCast, DocListCastAsync, Opt } from '../../fields/Doc'; +import { Doc, Opt } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; -import { Cast, DocCast } from '../../fields/Types'; +import { listSpec } from '../../fields/Schema'; +import { Cast } from '../../fields/Types'; +import { AudioField } from '../../fields/URLField'; import { returnFalse } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; -import { LightboxView } from '../views/LightboxView'; -import { DocFocusOptions, DocumentView, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView'; -import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; import { CollectionView } from '../views/collections/CollectionView'; +import { LightboxView } from '../views/LightboxView'; +import { DocFocusOptions, DocumentView, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView'; +import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; import { ScriptingGlobals } from './ScriptingGlobals'; import { SelectionManager } from './SelectionManager'; -import { listSpec } from '../../fields/Schema'; -import { AudioField } from '../../fields/URLField'; const { Howl } = require('howler'); export class DocumentManager { @@ -31,6 +31,30 @@ export class DocumentManager { //private constructor so no other class can create a nodemanager private constructor() {} + private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => any }[] = []; + public AddViewRenderedCb = (doc: Doc, func: (dv: DocumentView) => any) => { + const dv = this.getDocumentViewById(doc[Id]); + this._viewRenderedCbs.push({ doc, func }); + if (dv) { + this.callAddViewFuncs(dv); + } + }; + callAddViewFuncs = (view: DocumentView) => { + const callFuncs = this._viewRenderedCbs.filter(vc => vc.doc === view.rootDoc); + if (callFuncs.length) { + this._viewRenderedCbs = this._viewRenderedCbs.filter(vc => callFuncs.includes(vc)); + const intTimer = setInterval( + () => { + if (!view.ComponentView?.incrementalRendering?.()) { + callFuncs.forEach(cf => cf.func(view)); + clearInterval(intTimer); + } + }, + view.ComponentView?.incrementalRendering?.() ? 0 : 100 + ); + } + }; + @action public AddView = (view: DocumentView) => { //console.log("MOUNT " + view.props.Document.title + "/" + view.props.LayoutTemplateString); @@ -50,6 +74,7 @@ export class DocumentManager { } else { this.DocumentViews.add(view); } + this.callAddViewFuncs(view); }; public RemoveView = action((view: DocumentView) => { this.LinkedDocumentViews.slice().forEach( @@ -173,7 +198,7 @@ export class DocumentManager { targetDoc: Doc, // document to display options: DocFocusOptions, // options for how to navigate to target createViewFunc = DocumentManager.addView, // how to create a view of the doc if it doesn't exist - docContext: Doc[], // context to load that should contain the target + docContextPath: Doc[], // context to load that should contain the target finished?: () => void ): void => { const originalTarget = options.originalTarget ?? targetDoc; @@ -201,29 +226,20 @@ export class DocumentManager { finished?.(); }; const annoContainerView = (!wasHidden || resolvedTarget !== annotatedDoc) && annotatedDoc && this.getFirstDocumentView(annotatedDoc); - const contextDocs = docContext.length ? DocListCast(docContext[0].data) : undefined; - const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext.lastElement() : undefined; - const targetDocContext = contextDoc || annotatedDoc; - const targetDocContextView = (targetDocContext && this.getFirstDocumentView(targetDocContext)) || (wasHidden && annoContainerView); // if we have an annotation container and the target was hidden, then try again because we just un-hid the document above - const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView; if (annoContainerView) { if (annoContainerView.props.Document.layoutKey === 'layout_icon') { - annoContainerView.iconify(() => - annoContainerView.focus(targetDoc, { - ...options, - originalTarget, - afterFocus: (didFocus: boolean) => - new Promise(res => { - focusAndFinish(true); - res(ViewAdjustment.doNothing); - }), - }) - ); - return; - } else if (!docView && targetDoc.type !== DocumentType.MARKER) { + return annoContainerView.iconify(() => DocumentManager.Instance.AddViewRenderedCb(targetDoc, () => this.jumpToDocument(resolvedTarget ?? targetDoc, { ...options, toggleTarget: false }, createViewFunc, docContextPath, finished)), 30); + } + if (!docView && targetDoc.type !== DocumentType.MARKER) { annoContainerView.focus(targetDoc, {}); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below } } + + const contextDoc = docContextPath.length ? docContextPath[0] : undefined; + const remainingDocContext = docContextPath.length ? docContextPath.slice(1) : []; + const targetDocContext = contextDoc || annotatedDoc; + const targetDocContextView = (targetDocContext && this.getFirstDocumentView(targetDocContext)) || (wasHidden && annoContainerView); // if we have an annotation container and the target was hidden, then try again because we just un-hid the document above + const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView; if (focusView) { !options.noSelect && Doc.linkFollowHighlight(focusView.rootDoc, undefined, targetDoc); //TODO:glr make this a setting in PresBox if (options.playAudio) DocumentManager.playAudioAnno(focusView.rootDoc); @@ -252,56 +268,53 @@ export class DocumentManager { // we found a context view and aren't forced to create a new one ... focus on the context first.. wasHidden = wasHidden || targetDocContextView.rootDoc.hidden; targetDocContextView.rootDoc.hidden = false; // make sure context isn't hidden - targetDocContext._viewTransition = 'transform 500ms'; + + if (targetDocContext.layoutKey === 'layout_icon') { + return targetDocContextView.iconify( + () => DocumentManager.Instance.AddViewRenderedCb(targetDoc, () => this.jumpToDocument(resolvedTarget ?? targetDoc, { ...options /* originalTarget - needed? */ }, createViewFunc, docContextPath, finished)), + 30 + ); + } + + const contextFocusTime = options.zoomTime ? options.zoomTime / 2 : 500; + const remainingFocustime = options.zoomTime ? options.zoomTime - contextFocusTime : undefined; + targetDocContextView.setViewTransition('transform', contextFocusTime); + // this makes focusing on contexts run in parallel -- jutmp to document below makes them run sequentially + this.AddViewRenderedCb(targetDoc, () => this.jumpToDocument(targetDoc, { ...options, zoomTime: remainingFocustime }, createViewFunc, remainingDocContext, finished)); targetDocContextView.props.focus(targetDocContextView.rootDoc, { ...options, + zoomTime: contextFocusTime, // originalTarget, // needed? afterFocus: async () => { - targetDocContext._viewTransition = undefined; - if (targetDocContext.layoutKey === 'layout_icon') { - targetDocContextView.iconify(() => this.jumpToDocument(resolvedTarget ?? targetDoc, { ...options /* originalTarget - needed?*/ }, createViewFunc, docContext, finished)); + // now find the target document within the context + if (targetDoc._timecodeToShow) { + // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode; + targetDocContext._currentTimecode = targetDoc.anchorTimecodeToShow; + finished?.(); + } else { + // otherwise, just look for the target document in this context view now that we've focused the context view + if (this.getFirstDocumentView(resolvedTarget)) { + // test again for the target view snce we presumably created the context above by focusing on it + this.jumpToDocument(targetDoc, { ...options, zoomTime: remainingFocustime }, createViewFunc, remainingDocContext, finished); + } else if (targetDoc.layout) { + // there will no layout for a TEXTANCHOR type document + createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target + } } return ViewAdjustment.doNothing; }, }); - - // now find the target document within the context - if (targetDoc._timecodeToShow) { - // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode; - targetDocContext._currentTimecode = targetDoc.anchorTimecodeToShow; - finished?.(); - } else { - // no timecode means we need to find the context view and focus on our target - const retryDocView = this.getFirstDocumentView(resolvedTarget); // test again for the target view snce we presumably created the context above by focusing on it - if (retryDocView) { - // we found the target in the context. - Doc.linkFollowHighlight(retryDocView.rootDoc); - retryDocView.focus(targetDoc, { - ...options, - // originalTarget -- needed? - afterFocus: (didFocus: boolean) => - new Promise(res => { - !options.noSelect && focusAndFinish(true); - res(ViewAdjustment.doNothing); - }), - }); // focus on the target in the context - } else if (targetDoc.layout) { - // there will no layout for a TEXTANCHOR type document - createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target - } - } } else { - if (docContext.length && docContext[0]?.layoutKey === 'layout_icon') { - const docContextView = this.getFirstDocumentView(docContext[0]); - if (docContextView) { - return docContextView.iconify(() => this.jumpToDocument(targetDoc, { ...options, originalTarget }, createViewFunc, docContext.slice(1, docContext.length), finished)); - } + if (docContextPath.length && docContextPath[0]?.layoutKey === 'layout_icon') { + Doc.deiconifyView(docContextPath[0]); + this.jumpToDocument(targetDoc, options, createViewFunc, docContextPath, finished); + } else { + // there's no context view so we need to create one first and try again when that finishes + createViewFunc( + targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target + () => this.jumpToDocument(targetDoc, { ...options }, (doc: Doc, finished?: () => void) => doc !== targetDocContext && createViewFunc(doc, finished), remainingDocContext, finished) + ); } - // there's no context view so we need to create one first and try again when that finishes - createViewFunc( - targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target - () => this.jumpToDocument(targetDoc, { ...options, originalTarget }, (doc: Doc, finished?: () => void) => doc !== targetDocContext && createViewFunc(doc, finished), docContext, finished) - ); } } } diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index 4f742817a..aec4db1df 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -48,7 +48,7 @@ export class LinkFollower { }); } else { finished?.(); - res(where !== 'inPlace' || BoolCast(sourceDoc.followLinkZoom) ? ViewAdjustment.resetView : ViewAdjustment.doNothing); // for 'inPlace' resetting the initial focus&zoom would negate the zoom into the target + res(where !== OpenWhere.inPlace || BoolCast(sourceDoc.followLinkZoom) ? ViewAdjustment.resetView : ViewAdjustment.doNothing); // for 'inPlace' resetting the initial focus&zoom would negate the zoom into the target } }, 100); }); diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 1f58763d1..cf0a2fcfb 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -50,7 +50,6 @@ export class LightboxView extends React.Component { this.LightboxDoc._panY = this._savedState.panY; this.LightboxDoc._scrollTop = this._savedState.scrollTop; this.LightboxDoc._viewScale = this._savedState.scale; - this.LightboxDoc._viewTransition = undefined; } if (!doc) { this._docFilters && (this._docFilters.length = 0); @@ -167,7 +166,7 @@ export class LightboxView extends React.Component { if (targetDocView && target) { const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.() || target).lastElement(); l && (Cast(l.anchor2, Doc, null).backgroundColor = 'lightgreen'); - targetDocView.focus(target, { originalTarget: target, willZoom: true, scale: 0.9 }); + targetDocView.focus(target, { originalTarget: target, willZoom: true, zoomScale: 0.9 }); if (LightboxView._history?.lastElement().target !== target) LightboxView._history?.push({ doc, target }); } else { if (!target && LightboxView.path.length) { @@ -177,7 +176,6 @@ export class LightboxView extends React.Component { LightboxView.LightboxDoc._panY = saved.panY; LightboxView.LightboxDoc._viewScale = saved.scale; LightboxView.LightboxDoc._scrollTop = saved.scrollTop; - LightboxView.LightboxDoc._viewTransition = undefined; } const pop = LightboxView.path.pop(); if (pop) { @@ -211,7 +209,7 @@ export class LightboxView extends React.Component { if (docView) { LightboxView._docTarget = target; if (!target) docView.ComponentView?.shrinkWrap?.(); - else docView.focus(target, { willZoom: true, scale: 0.9 }); + else docView.focus(target, { willZoom: true, zoomScale: 0.9 }); } else { LightboxView.SetLightboxDoc(doc, target); } @@ -290,7 +288,7 @@ export class LightboxView extends React.Component { const target = LightboxView._docTarget; const doc = LightboxView._doc; //const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target); - if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.(); + //if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.(); //else target?.focus(target, { willZoom: true, scale: 0.9, instant: true }); // bcz: why was this here? it breaks smooth navigation in lightbox using 'next' button }) ); diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 656a56a15..66c3ed439 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -14,7 +14,7 @@ import { SelectionManager } from '../util/SelectionManager'; import { undoBatch } from '../util/UndoManager'; import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; -import { DocumentView } from './nodes/DocumentView'; +import { DocumentView, OpenWhere } from './nodes/DocumentView'; import { VideoBox } from './nodes/VideoBox'; import { pasteImageBitmap } from './nodes/WebBoxRenderer'; import './PropertiesButtons.scss'; @@ -117,7 +117,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { 'In Place', 'isInPlaceContainer', on => `${on ? 'Make' : 'Remove'} in place container flag`, - on => 'window', + on => 'window-restore', onClick => { SelectionManager.Views().forEach(dv => { const containerDoc = dv.rootDoc; @@ -129,7 +129,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { containerDoc._forceActive = containerDoc._isInPlaceContainer = !containerDoc._isInPlaceContainer; - containerDoc.followLinkLocation = containerDoc._isInPlaceContainer ? 'inPlace' : undefined; + containerDoc.followLinkLocation = containerDoc._isInPlaceContainer ? OpenWhere.inPlace : undefined; containerDoc._xPadding = containerDoc._yPadding = containerDoc._isInPlaceContainer ? 10 : undefined; const menuDoc = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]).lastElement(); if (menuDoc) { @@ -139,7 +139,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { } DocListCast(menuDoc[Doc.LayoutFieldKey(menuDoc)]).forEach(menuItem => { menuItem.followLinkAudio = menuItem.followAllLinks = menuItem._isLinkButton = true; - menuItem._followLinkLocation = 'inPlace'; + menuItem._followLinkLocation = OpenWhere.inPlace; }); } }); diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index f7cc32cff..bc08e920a 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1409,7 +1409,7 @@ export class PropertiesView extends React.Component { }); @undoBatch - changeFollowBehavior = action((follow: string) => this.selectedDoc && (this.selectedDoc.followLinkLocation = follow)); + changeFollowBehavior = action((follow: string) => this.sourceAnchor && (this.sourceAnchor.followLinkLocation = follow)); @undoBatch changeAnimationBehavior = action((behavior: string) => this.sourceAnchor && (this.sourceAnchor.linkAnimEffect = behavior)); @@ -1612,7 +1612,8 @@ export class PropertiesView extends React.Component { - + + diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 8cbe548c7..ffc004df6 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -191,7 +191,7 @@ export class CollectionDockingView extends CollectionSubView() { if (!pullSide && stack) { stack.addChild(docContentConfig, undefined); - stack.setActiveContentItem(stack.contentItems[stack.contentItems.length - 1]); + setTimeout(() => stack.setActiveContentItem(stack.contentItems[stack.contentItems.length - 1])); } else { const newContentItem = () => { const newItem = glayRoot.layoutManager.createContentItem({ type: 'stack', content: [docContentConfig] }, instance._goldenLayout); @@ -389,6 +389,7 @@ export class CollectionDockingView extends CollectionSubView() { const className = typeof htmlTarget.className === 'string' ? htmlTarget.className : ''; if (!className.includes('lm_close') && !className.includes('lm_maximise')) { this._flush = UndoManager.StartBatch('golden layout edit'); + DocServer.UPDATE_SERVER_CACHE(); } } } diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index db81f28f6..e2594b6ae 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -31,7 +31,7 @@ import { Colors } from '../global/globalEnums'; import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from '../InkingStroke'; import { LightboxView } from '../LightboxView'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; -import { DocumentView } from '../nodes/DocumentView'; +import { DocumentView, OpenWhereMod } from '../nodes/DocumentView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import { DefaultStyleProvider } from '../StyleProvider'; @@ -569,7 +569,7 @@ export class CollectionViewBaseChrome extends React.Component { const doc = Docs.Create.ScreenshotDocument({ title: 'screen recording', _fitWidth: true, _width: 400, _height: 200, mediaState: 'pendingRecording' }); //Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc); - CollectionDockingView.AddSplit(doc, 'right'); + CollectionDockingView.AddSplit(doc, OpenWhereMod.right); }; @computed @@ -733,11 +733,12 @@ export class CollectionFreeFormViewChrome extends React.Component { @@ -746,7 +747,7 @@ export class CollectionFreeFormViewChrome extends React.Component { () => SelectionManager.Views().some(v => v.topMost && v.props.Document === doc), action(selected => { if (selected) this._activated = true; - const toggle = tab.element[0].children[1].children[0] as HTMLInputElement; + const toggle = tab.element[0].children[2].children[0] as HTMLInputElement; selected && tab.contentItem !== tab.header.parent.getActiveContentItem() && UndoManager.RunInBatch(() => tab.header.parent.setActiveContentItem(tab.contentItem), 'tab switch'); - // toggle.style.fontWeight = selected ? "bold" : ""; + toggle.style.fontWeight = selected ? 'bold' : ''; // toggle.style.textTransform = selected ? "uppercase" : ""; - }) + }), + { fireImmediately: true } ); // highlight the tab when the tab document is brushed in any part of the UI @@ -378,14 +379,7 @@ export class TabDocView extends React.Component { if (options?.willZoom !== false && shrinkwrap && this._document) { const focusSpeed = options.zoomTime ?? 500; shrinkwrap(); - this._document._viewTransition = `transform ${focusSpeed}ms`; - setTimeout( - action(() => { - this._document!._viewTransition = undefined; - options?.afterFocus?.(false); - }), - focusSpeed - ); + this._view?.setViewTransition('transform', focusSpeed, () => options?.afterFocus?.(false)); } else { options?.afterFocus?.(false); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index be20bf207..c57810a98 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -270,12 +270,12 @@ export class CollectionFreeFormLinkView extends React.Component - + - + @@ -286,7 +286,7 @@ export class CollectionFreeFormLinkView extends React.Component { const currentFrame = Cast(this.Document._currentFrame, 'number', null); if (currentFrame === undefined) { @@ -183,14 +185,17 @@ export class CollectionFreeFormView extends CollectionSubView (this._keyframeEditing = set); + getKeyFrameEditing = () => this._keyframeEditing; onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick); onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); @@ -712,6 +717,7 @@ export class CollectionFreeFormView extends CollectionSubView { + this.props.DocumentView?.().clearViewTransition(); const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true); this._lastX = e.clientX; @@ -1079,7 +1085,7 @@ export class CollectionFreeFormView extends CollectionSubView (this._viewTransition = 0)), + action(() => (this._panZoomTransition = 0)), nudgeTime ); return true; @@ -1127,7 +1133,7 @@ export class CollectionFreeFormView extends CollectionSubView(res => setTimeout(() => res(runInAction(() => (this._viewTransition = 0))), this._viewTransition)); // set transition to be smooth, then reset + return new Promise(res => setTimeout(() => res(runInAction(() => (this._panZoomTransition = 0))), this._panZoomTransition)); // set transition to be smooth, then reset } focusDocument = (doc: Doc, options: DocFocusOptions) => { @@ -1185,14 +1191,14 @@ export class CollectionFreeFormView extends CollectionSubView (this._viewTransition = 0)); + runInAction(() => (this._panZoomTransition = 0)); return resetView; }; const xf = !cantTransform @@ -1364,7 +1370,7 @@ export class CollectionFreeFormView extends CollectionSubView { const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; return PresBox.restoreTargetDocView( - this.rootDoc, // + this.props.DocumentView?.(), // { pinDocLayout: BoolCast(anchor.presPinDocLayout) }, anchor, focusSpeed, @@ -1869,6 +1875,8 @@ export class CollectionFreeFormView extends CollectionSubView this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])).length !== 0; + incrementalRender = action(() => { if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) { const unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])); @@ -1944,7 +1952,7 @@ export class CollectionFreeFormView extends CollectionSubView {this.children} @@ -2265,6 +2273,11 @@ ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true); }); +ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) { + const selView = SelectionManager.Views(); + if (readOnly) return selView[0].ComponentView?.getKeyFrameEditing?.() ? Colors.MEDIUM_BLUE : undefined; + runInAction(() => selView[0].ComponentView?.setKeyFrameEditing?.(!selView[0].ComponentView?.getKeyFrameEditing?.())); +}); ScriptingGlobals.add(function pinWithView(readOnly: boolean, pinDocContent: boolean) { !readOnly && SelectionManager.Views().forEach(view => TabDocView.PinDoc(view.rootDoc, { pinDocContent, pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()) })); }); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 9df3e195f..7c1137292 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -19,7 +19,6 @@ import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { OpenWhere } from '../../nodes/DocumentView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; -import { PresBox } from '../../nodes/trails/PresBox'; import { VideoBox } from '../../nodes/VideoBox'; import { pasteImageBitmap } from '../../nodes/WebBoxRenderer'; import { PreviewCursor } from '../../PreviewCursor'; @@ -244,7 +243,7 @@ export class MarqueeView extends React.Component 100 && !PresBox.startMarquee) { + if (!this._commandExecuted && Math.abs(this.Bounds.height * this.Bounds.width) > 100) { MarqueeOptionsMenu.Instance.createCollection = this.collection; MarqueeOptionsMenu.Instance.delete = this.delete; MarqueeOptionsMenu.Instance.summarize = this.summary; @@ -686,7 +678,7 @@ export class MarqueeView extends React.Component e.preventDefault()} onScroll={e => (e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0)} diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 868822fbf..bf1f13a06 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -115,45 +115,30 @@ export class CollectionFreeFormDocumentView extends DocComponent { - doc._viewTransition = doc.dataTransition = 'all 1s'; - CollectionFreeFormDocumentView.animFields.forEach(val => { - const findexed = Cast(doc[`${val.key}-indexed`], listSpec('number'), null); - findexed?.length <= timecode + 1 && findexed.push(undefined as any as number); - }); - CollectionFreeFormDocumentView.animStringFields.forEach(val => { - const findexed = Cast(doc[`${val}-indexed`], listSpec('string'), null); - findexed?.length <= timecode + 1 && findexed.push(undefined as any as string); - }); - CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => { - const findexed = Cast(doc[`${val}-indexed`], listSpec(InkField), null); - findexed?.length <= timecode + 1 && findexed.push(undefined as any); - }); - }) - ); - setTimeout( - () => - docs.forEach(doc => { - doc._viewTransition = undefined; - doc.dataTransition = 'inherit'; - }), - 1010 - ); + docs.forEach(doc => { + CollectionFreeFormDocumentView.animFields.forEach(val => { + const findexed = Cast(doc[`${val.key}-indexed`], listSpec('number'), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any as number); + }); + CollectionFreeFormDocumentView.animStringFields.forEach(val => { + const findexed = Cast(doc[`${val}-indexed`], listSpec('string'), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any as string); + }); + CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => { + const findexed = Cast(doc[`${val}-indexed`], listSpec(InkField), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any); + }); + }); + return newTimer; } - public static gotoKeyframe(docs: Doc[], duration = 1000) { - docs.forEach(doc => (doc._viewTransition = doc.dataTransition = `all ${duration}ms`)); - setTimeout( - () => - docs.forEach(doc => { - doc._viewTransition = undefined; - doc.dataTransition = 'inherit'; - }), - 1010 - ); + public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration = 1000) { + if (timer) clearTimeout(timer); + return DocumentView.SetViewTransition(docs, 'all', duration, undefined, true); } public static setupZoom(doc: Doc, targDoc: Doc) { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index fb20887cb..4abfef563 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -127,6 +127,7 @@ export interface DocComponentView { Pause?: () => void; setFocus?: () => void; componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; + incrementalRendering?: () => void; fieldKey?: string; annotationKey?: string; getTitle?: () => string; @@ -238,7 +239,7 @@ export interface DocumentViewInternalProps extends DocumentViewProps { @observer export class DocumentViewInternal extends DocComponent() { public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered. - _animateScaleTime = 300; // milliseconds; + @observable _animateScaleTime: Opt; // milliseconds for animating between views. defaults to 300 if not uset @observable _animateScalingTo = 0; @observable _pendingDoubleClick = false; private _disposers: { [name: string]: IReactionDisposer } = {}; @@ -259,6 +260,9 @@ export class DocumentViewInternal extends DocComponent {this.innards} {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ?
: null} @@ -1476,7 +1480,37 @@ export class DocumentView extends React.Component { return 'DocumentView(' + this.props.Document?.title + ')'; } // this makes mobx trace() statements more descriptive public ContentRef = React.createRef(); + public ViewTimer: NodeJS.Timeout | undefined; // timer for res private _disposers: { [name: string]: IReactionDisposer } = {}; + public clearViewTransition = () => { + this.ViewTimer && clearTimeout(this.ViewTimer); + this.rootDoc._viewTransition = undefined; + }; + public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => { + this.rootDoc._viewTransition = `${transProp} ${timeInMs}ms`; + if (dataTrans) this.rootDoc._dataTransition = `${transProp} ${timeInMs}ms`; + this.ViewTimer && clearTimeout(this.ViewTimer); + return (this.ViewTimer = setTimeout(() => { + this.rootDoc._viewTransition = undefined; + this.rootDoc._dataTransition = 'inherit'; + afterTrans?.(); + }, timeInMs + 10)); + }; + public static SetViewTransition(docs: Doc[], transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) { + docs.forEach(doc => { + doc._viewTransition = `${transProp} ${timeInMs}ms`; + dataTrans && (doc.dataTransition = `${transProp} ${timeInMs}ms`); + }); + return setTimeout( + () => + docs.forEach(doc => { + doc._viewTransition = undefined; + dataTrans && (doc.dataTransition = 'inherit'); + afterTrans?.(); + }), + timeInMs + 10 + ); + } public static showBackLinks(linkSource: Doc) { const docid = Doc.CurrentUserEmail + Doc.GetProto(linkSource)[Id] + '-pivotish'; @@ -1617,15 +1651,21 @@ export class DocumentView extends React.Component { return { left, top, right, bottom, center: this.ComponentView?.getCenter?.(xf) }; }; - public iconify(finished?: () => void) { + public iconify(finished?: () => void, animateTime?: number) { this.ComponentView?.updateIcon?.(); + const animTime = this.docView?._animateScaleTime; + runInAction(() => this.docView && animateTime !== undefined && (this.docView._animateScaleTime = animateTime)); + const finalFinished = action(() => { + finished?.(); + this.docView && (this.docView._animateScaleTime = animTime); + }); const layoutKey = Cast(this.Document.layoutKey, 'string', null); if (layoutKey !== 'layout_icon') { - this.switchViews(true, 'icon', finished); + this.switchViews(true, 'icon', finalFinished); if (layoutKey && layoutKey !== 'layout' && layoutKey !== 'layout_icon') this.Document.deiconifyLayout = layoutKey.replace('layout_', ''); } else { const deiconifyLayout = Cast(this.Document.deiconifyLayout, 'string', null); - this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finished); + this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finalFinished); this.Document.deiconifyLayout = undefined; this.props.bringToFront(this.rootDoc); } @@ -1651,10 +1691,10 @@ export class DocumentView extends React.Component { this.docView && (this.docView._animateScalingTo = 0); finished?.(); }), - this.docView!._animateScaleTime - 10 + this.docView ? Math.max(0, this.docView.animateScaleTime - 10) : 0 ); }), - this.docView!._animateScaleTime - 10 + this.docView ? Math.max(0, this.docView?.animateScaleTime - 10) : 0 ); }); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 416107859..19f5e9e29 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -65,7 +65,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent { const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; return PresBox.restoreTargetDocView( - this.rootDoc, // + this.props.DocumentView?.(), // { pinDocLayout: BoolCast(anchor.presPinDocLayout) }, anchor, focusSpeed, diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index b19c4a9e2..88aac67a7 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -209,7 +209,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent { diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 607cd6187..a8f78edd5 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -961,7 +961,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent +
(this._stackedTimeline = r))} {...this.props} diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index fe8c85e5e..e477d7ae2 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -510,8 +510,12 @@ export class FontIconBox extends DocComponent() { case ButtonType.EditableText: return this.editableText; case ButtonType.DropdownButton: button = this.dropdownButton; break; case ButtonType.ToggleButton: button = this.toggleButton; break; - case ButtonType.TextButton: button = ( -
+ case ButtonType.TextButton: + // Script for checking the outcome of the toggle + const script = ScriptCast(this.rootDoc.script); + const checkResult = script?.script.run({ _readOnly_: true }).result; + button = ( +
{this.Icon(color)} {StrCast(this.rootDoc.buttonText) ?
{StrCast(this.rootDoc.buttonText)}
: null} {label()} diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 4073677f3..169f51dac 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -5,7 +5,7 @@ import { action, computed, IReactionDisposer, observable, ObservableSet, reactio import { observer } from 'mobx-react'; import { ColorState, SketchPicker } from 'react-color'; import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal'; -import { Doc, DocListCast, FieldResult, StrListCast } from '../../../../fields/Doc'; +import { Doc, DocListCast, FieldResult, Opt, StrListCast } from '../../../../fields/Doc'; import { Copy, Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; @@ -13,9 +13,9 @@ import { ObjectField } from '../../../../fields/ObjectField'; import { listSpec } from '../../../../fields/Schema'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { AudioField } from '../../../../fields/URLField'; -import { emptyFunction, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils'; +import { returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; -import { Docs, DocumentOptions } from '../../../documents/Documents'; +import { Docs } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; @@ -30,12 +30,11 @@ import { ViewBoxBaseComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; import { LightboxView } from '../../LightboxView'; import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentView'; +import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { ScriptingBox } from '../ScriptingBox'; import './PresBox.scss'; import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums'; -import { map } from 'bluebird'; -import { DocFocusOptions, OpenWhere, OpenWhereMod } from '../DocumentView'; const { Howl } = require('howler'); export interface PinProps { @@ -97,7 +96,6 @@ export class PresBox extends ViewBoxBaseComponent() { public selectedArray = new ObservableSet(); @observable public static Instance: PresBox; - @observable static startMarquee: boolean = false; // onclick "+ new slide" in presentation mode, set as true, then when marquee selection finish, onPointerUp automatically triggers PinWithView @observable _isChildActive = false; @observable _moveOnFromAudio: boolean = true; @@ -140,7 +138,7 @@ export class PresBox extends ViewBoxBaseComponent() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); } @computed get scrollable() { - if (this.targetDoc.type === DocumentType.PDF || this.targetDoc.type === DocumentType.WEB || this.targetDoc.type === DocumentType.RTF || this.targetDoc._viewType === CollectionViewType.Stacking) return true; + if ([DocumentType.PDF, DocumentType.WEB, DocumentType.RTF].includes(this.targetDoc.type as DocumentType) || this.targetDoc._viewType === CollectionViewType.Stacking) return true; return false; } @computed get panable() { @@ -214,16 +212,6 @@ export class PresBox extends ViewBoxBaseComponent() { PresBox.Instance = this; }; - // There are still other internal frames and should go through all frames before going to next slide - nextInternalFrame = (targetDoc: Doc, activeItem: Doc) => { - const currentFrame = Cast(targetDoc?._currentFrame, 'number', null); - const childDocs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); - targetDoc._viewTransition = 'all 1s'; - setTimeout(() => (targetDoc._viewTransition = undefined), 1010); - this.nextKeyframe(targetDoc, activeItem); - targetDoc.keyFrameEditing = true; - }; - _mediaTimer!: [NodeJS.Timeout, Doc]; // 'Play on next' for audio or video therefore first navigate to the audio/video before it should be played startTempMedia = (targetDoc: Doc, activeItem: Doc) => { @@ -279,23 +267,17 @@ export class PresBox extends ViewBoxBaseComponent() { const targetDoc: Doc = this.targetDoc; const prevItem = Cast(this.childDocs[Math.max(0, this.itemIndex - 1)], Doc, null); const prevTargetDoc = Cast(prevItem.presentationTargetDoc, Doc, null); - const lastFrame = Cast(targetDoc.lastFrame, 'number', null); - const curFrame = NumCast(targetDoc._currentFrame); let prevSelected = this.itemIndex; // Functionality for group with up let didZoom = activeItem.presMovement; for (; prevSelected > 0 && this.childDocs[Math.max(0, prevSelected - 1)].groupWithUp; prevSelected--) { didZoom = didZoom === 'none' ? this.childDocs[prevSelected].presMovement : didZoom; } - if (lastFrame !== undefined && curFrame >= 1) { - // Case 1: There are still other frames and should go through all frames before going to previous slide - this.prevKeyframe(targetDoc, activeItem); - } else if (activeItem && this.childDocs[this.itemIndex - 1] !== undefined) { + if (activeItem && this.childDocs[this.itemIndex - 1] !== undefined) { // Case 2: There are no other frames so it should go to the previous slide prevSelected = Math.max(0, prevSelected - 1); this.nextSlide(prevSelected); this.rootDoc._itemIndex = prevSelected; - if (NumCast(prevTargetDoc.lastFrame) > 0) prevTargetDoc._currentFrame = NumCast(prevTargetDoc.lastFrame); } else if (this.childDocs[this.itemIndex - 1] === undefined && this.layoutDoc.presLoop) { // Case 3: Pres loop is on so it should go to the last slide this.gotoDocument(this.childDocs.length - 1, activeItem); @@ -313,14 +295,13 @@ export class PresBox extends ViewBoxBaseComponent() { this.rootDoc._itemIndex = index; const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - let focusSpeed = 500; if (activeItem.presActiveFrame !== undefined) { const transTime = NumCast(activeItem.presTransition, 500); const context = DocCast(DocCast(activeItem.presentationTargetDoc).context); if (context) { - const contextView = DocumentManager.Instance.getFirstDocumentView(context); - if (contextView?.ComponentView) { - CollectionFreeFormDocumentView.gotoKeyframe((contextView.ComponentView as CollectionFreeFormView).childDocs.slice(), transTime); + const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.ComponentView as CollectionFreeFormView; + if (ffview) { + this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, ffview.childDocs.slice(), transTime); context._currentFrame = NumCast(activeItem.presActiveFrame); } } @@ -335,9 +316,6 @@ export class PresBox extends ViewBoxBaseComponent() { if (this.layoutDoc.presStatus !== PresStatus.Edit && (targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && activeItem.mediaStart === 'auto') { this.startTempMedia(targetDoc, activeItem); } - if (targetDoc?.lastFrame !== undefined) { - targetDoc._currentFrame = 0; - } if (!group) this.clearSelectedArray(); this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); //Update selected array this.turnOffEdit(); @@ -345,14 +323,15 @@ export class PresBox extends ViewBoxBaseComponent() { this.onHideDocument(); //Handles hide after/before } }); - static pinDataTypes(target: Doc): { scrollable?: boolean; pannable?: boolean; temporal?: boolean; clippable?: boolean; dataview?: boolean; textview?: boolean; poslayoutview?: boolean; dataannos?: boolean } { - const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(target.type as any) || target._viewType === CollectionViewType.Stacking; - const pannable = [DocumentType.IMG, DocumentType.PDF].includes(target.type as any) || (target.type === DocumentType.COL && target._viewType === CollectionViewType.Freeform); - const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(target.type as any); - const clippable = [DocumentType.COMPARISON].includes(target.type as any); - const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG].includes(target.type as any) && target.activeFrame === undefined; - const poslayoutview = [DocumentType.COL].includes(target.type as any) && target.activeFrame === undefined; - const textview = [DocumentType.RTF].includes(target.type as any) && target.activeFrame === undefined; + static pinDataTypes(target?: Doc): { scrollable?: boolean; pannable?: boolean; temporal?: boolean; clippable?: boolean; dataview?: boolean; textview?: boolean; poslayoutview?: boolean; dataannos?: boolean } { + const targetType = target?.type as any; + const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetType) || target?._viewType === CollectionViewType.Stacking; + const pannable = [DocumentType.IMG, DocumentType.PDF].includes(targetType) || (targetType === DocumentType.COL && target?._viewType === CollectionViewType.Freeform); + const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(targetType); + const clippable = [DocumentType.COMPARISON].includes(targetType); + const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG].includes(targetType) && target?.activeFrame === undefined; + const poslayoutview = [DocumentType.COL].includes(targetType) && target?.activeFrame === undefined; + const textview = [DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined; const dataannos = false; return { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview, dataannos }; } @@ -360,8 +339,9 @@ export class PresBox extends ViewBoxBaseComponent() { @action playAnnotation = (anno: AudioField) => {}; @action - static restoreTargetDocView(bestTarget: Doc, pinProps: PinProps | undefined, activeItem: Doc, transTime: number, pinDataTypes = this.pinDataTypes(bestTarget)) { - const presTransitionTime = `all ${transTime}ms`; + static restoreTargetDocView(bestTargetView: Opt, pinProps: PinProps | undefined, activeItem: Doc, transTime: number, pinDataTypes = this.pinDataTypes(bestTargetView?.rootDoc)) { + if (!bestTargetView) return; + const bestTarget = bestTargetView.rootDoc; let changed = false; if (pinProps?.pinDocLayout) { if ( @@ -420,7 +400,7 @@ export class PresBox extends ViewBoxBaseComponent() { .map(str => JSON.parse(str) as { id: string; x: number; y: number; w: number; h: number }) .forEach(data => { const doc = DocServer.GetCachedRefField(data.id) as Doc; - doc._dataTransition = presTransitionTime; + doc._dataTransition = `all ${transTime}ms`; doc.x = data.x; doc.y = data.y; doc._width = data.w; @@ -456,8 +436,7 @@ export class PresBox extends ViewBoxBaseComponent() { } } if (changed) { - bestTarget._viewTransition = presTransitionTime; - return setTimeout(() => (bestTarget._viewTransition = undefined), transTime + 10); + return bestTargetView.setViewTransition('all', transTime); } } @@ -519,8 +498,6 @@ export class PresBox extends ViewBoxBaseComponent() { pinDoc.presPinViewBounds = new List([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]); } } - - static _navTimer: NodeJS.Timeout | undefined; /** * This method makes sure that cursor navigates to the element that * has the option open and last in the group. @@ -549,55 +526,59 @@ export class PresBox extends ViewBoxBaseComponent() { const selViewCache = Array.from(this.selectedArray); const dragViewCache = Array.from(this._dragArray); const eleViewCache = Array.from(this._eleArray); - const self = this; const resetSelection = action(() => { - const presDocView = DocumentManager.Instance.getDocumentView(self.rootDoc); + const presDocView = DocumentManager.Instance.getDocumentView(this.rootDoc); if (presDocView) SelectionManager.SelectView(presDocView, false); - self.rootDoc.presStatus = presStatus; - self.clearSelectedArray(); - selViewCache.forEach(doc => self.addToSelectedArray(doc)); - self._dragArray.splice(0, self._dragArray.length, ...dragViewCache); - self._eleArray.splice(0, self._eleArray.length, ...eleViewCache); + this.rootDoc.presStatus = presStatus; + this.clearSelectedArray(); + selViewCache.forEach(doc => this.addToSelectedArray(doc)); + this._eleArray.splice(0, this._eleArray.length, ...eleViewCache); }); - const openInTab = (doc: Doc, finished?: () => void) => { - (collectionDocView ?? this).props.addDocTab(doc, OpenWhere.add); + const createDocView = (doc: Doc, finished?: () => void) => { + DocumentManager.Instance.AddViewRenderedCb(doc, () => finished?.()); + (collectionDocView ?? this).props.addDocTab(doc, OpenWhere.lightbox); this.layoutDoc.presCollection = targetDoc; - // this still needs some fixing - setTimeout(resetSelection, 500); - if (doc !== targetDoc) { - setTimeout(finished ?? emptyFunction, 100); /// give it some time to create the targetDoc if we're opening up its context - } else { - finished?.(); - } }; - PresBox.NavigateToTarget(targetDoc, activeItem, openInTab, srcContext, includesDoc || tab ? undefined : resetSelection); + PresBox.NavigateToTarget(targetDoc, activeItem, createDocView, srcContext, includesDoc || tab ? undefined : resetSelection); }; - static NavigateToTarget(targetDoc: Doc, activeItem: Doc, openInTab: any, srcContext: Doc, finished?: () => void) { + static NavigateToTarget(targetDoc: Doc, activeItem: Doc, createDocView: any, srcContext: Doc, finished?: () => void) { + if (activeItem.presMovement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) { + (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.(); + return; + } + const restoreLayout = () => { + // After navigating to the document, if it is added as a presPinView then it will + // adjust the pan and scale to that of the pinView when it was added. + const pinDocLayout = (BoolCast(activeItem.presPinLayout) || BoolCast(activeItem.presPinView)) && DocCast(targetDoc.context)?._currentFrame === undefined; + if (activeItem.presPinData || activeItem.presPinView || pinDocLayout) { + // targetDoc may or may not be displayed. so get the first available document (or alias) view that matches targetDoc and use it + PresBox.restoreTargetDocView(DocumentManager.Instance.getFirstDocumentView(targetDoc), { pinDocLayout }, activeItem, NumCast(activeItem.presTransition, 500)); + } + }; // If openDocument is selected then it should open the document for the user if (activeItem.openDocument) { LightboxView.SetLightboxDoc(targetDoc); // openInTab(targetDoc); - } else if (targetDoc && activeItem.presMovement !== PresMovement.None) { - LightboxView.SetLightboxDoc(undefined); - const options: DocFocusOptions = { - willZoom: activeItem.presMovement !== PresMovement.Pan, - zoomScale: NumCast(activeItem.presZoom, 1), - zoomTime: activeItem.presMovement === PresMovement.Jump ? 0 : NumCast(activeItem.presTransition, 500), - noSelect: true, - originatingDoc: activeItem, - }; - DocumentManager.Instance.jumpToDocument(targetDoc, options, openInTab, srcContext ? [srcContext] : [], finished); - } else if (activeItem.presMovement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) { - (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.(); - } - // After navigating to the document, if it is added as a presPinView then it will - // adjust the pan and scale to that of the pinView when it was added. - const pinDocLayout = (BoolCast(activeItem.presPinLayout) || BoolCast(activeItem.presPinView)) && DocCast(targetDoc.context)?._currentFrame === undefined; - if (activeItem.presPinData || activeItem.presPinView || pinDocLayout) { - PresBox._navTimer && clearTimeout(PresBox._navTimer); - // targetDoc may or may not be displayed. this gets the first available document (or alias) view that matches targetDoc - const bestTargetView = DocumentManager.Instance.getFirstDocumentView(targetDoc); - if (bestTargetView?.props.Document) PresBox._navTimer = PresBox.restoreTargetDocView(bestTargetView?.props.Document, { pinDocLayout }, activeItem, NumCast(activeItem.presTransition, 500)); + setTimeout(restoreLayout); + } else { + if (targetDoc && activeItem.presMovement !== PresMovement.None) { + LightboxView.SetLightboxDoc(undefined); + const options: DocFocusOptions = { + willZoom: activeItem.presMovement !== PresMovement.Pan, + zoomScale: NumCast(activeItem.presZoom, 1), + zoomTime: activeItem.presMovement === PresMovement.Jump ? 0 : NumCast(activeItem.presTransition, 500), + noSelect: true, + originatingDoc: activeItem, + }; + + var containerDocContext = srcContext ? [srcContext] : []; + while (containerDocContext.length && !DocumentManager.Instance.getDocumentView(containerDocContext[0]) && containerDocContext[0].context) { + containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext]; + } + + DocumentManager.Instance.jumpToDocument(targetDoc, options, createDocView, containerDocContext, finished); + } + restoreLayout(); } } @@ -1824,32 +1805,7 @@ export class PresBox extends ViewBoxBaseComponent() { return undefined; }; - // Case in which the document has keyframes to navigate to next key frame - @action - nextKeyframe = (tagDoc: Doc, curDoc: Doc): void => { - const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]); - const currentFrame = Cast(tagDoc._currentFrame, 'number', null); - if (currentFrame === undefined) { - tagDoc._currentFrame = 0; - // CollectionFreeFormDocumentView.setupScroll(tagDoc, 0); - // CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); - } - CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, tagDoc); - tagDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1); - tagDoc.lastFrame = Math.max(NumCast(tagDoc._currentFrame), NumCast(tagDoc.lastFrame)); - }; - - @action - prevKeyframe = (tagDoc: Doc, actItem: Doc): void => { - const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]); - const currentFrame = Cast(tagDoc._currentFrame, 'number', null); - if (currentFrame === undefined) { - tagDoc._currentFrame = 0; - // CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); - } - CollectionFreeFormDocumentView.gotoKeyframe(childDocs.slice()); - tagDoc._currentFrame = Math.max(0, (currentFrame || 0) - 1); - }; + _keyTimer: NodeJS.Timeout | undefined; /** * Returns the collection type as a string for headers @@ -2022,76 +1978,6 @@ export class PresBox extends ViewBoxBaseComponent() { ); } - @action - getList = (list: any): List => list; - - @action - updateList = (list: any): List => { - const targetDoc: Doc = this.targetDoc; - const x: List = list; - x[x.length - 1] = NumCast(targetDoc._scrollY); - return x; - }; - - @action - newFrame = () => { - const activeItem: Doc = this.activeItem; - const type: string = StrCast(this.targetDoc.type); - if (!activeItem.frameList) activeItem.frameList = new List(); - switch (type) { - case DocumentType.PDF || DocumentType.RTF || DocumentType.WEB: - this.updateList(activeItem.frameList); - break; - } - }; - - @computed get frameListHeader() { - return ( -
-   Frames {this.panable ? Panable : this.scrollable ? Scrollable : null} -
- {'Add frame by example'}
}> -
{ - e.stopPropagation(); - this.newFrame(); - }}> - e.stopPropagation()} /> -
- - {'Edit in collection'}
}> -
e.stopPropagation()}> - e.stopPropagation()} /> -
- -
-
- ); - } - - @computed get frameList() { - const frameList: List = this.getList(this.activeItem.frameList); - return !frameList ? null : ( -
- {frameList.map(value => ( -
- ))} -
- ); - } - - @computed get playButtonFrames() { - const targetDoc = this.targetDoc; - return !this.targetDoc ? null : ( -
= 0 ? 'inline-flex' : 'none' }}> -
{NumCast(targetDoc._currentFrame)}
-
-
{NumCast(targetDoc.lastFrame)}
-
- ); - } - @computed get playButtons() { const presEnd: boolean = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1; const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0; @@ -2143,7 +2029,6 @@ export class PresBox extends ViewBoxBaseComponent() {
this.gotoDocument(0, this.activeItem)} style={{ display: this.props.PanelWidth() > 250 ? 'inline-flex' : 'none' }}> Slide {this.itemIndex + 1} / {this.childDocs.length} - {this.playButtonFrames}
{this.props.PanelWidth() > 250 ? ( @@ -2202,9 +2087,6 @@ export class PresBox extends ViewBoxBaseComponent() { clearTimeout(this._presTimer); }; - @action - startMarqueeCreateSlide = () => (PresBox.startMarquee = true); - AddToMap = (treeViewDoc: Doc, index: number[]): Doc[] => { var indexNum = 0; for (let i = 0; i < index.length; i++) { @@ -2271,7 +2153,6 @@ export class PresBox extends ViewBoxBaseComponent() {
Slide {this.itemIndex + 1} / {this.childDocs.length} - {this.playButtonFrames}
setupMoveUpEvents(this, e, returnFalse, returnFalse, this.exitClicked, false, false)}> -- cgit v1.2.3-70-g09d2 From 403d1c4fece9efa663e0fd7161afff9f27cf670c Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 16 Dec 2022 15:07:50 -0500 Subject: fixed problem with undo. regularized all linkfollowing anchor fields to start with followLink. added ease vs linear flag for scroll transitions in link and preselmeent navigations. added link follow to move target to specified offset from source. shifted from setting dropAction on items to setting childDropAction on collections --- src/Utils.ts | 29 +++-- src/client/documents/Documents.ts | 4 +- src/client/util/CurrentUserUtils.ts | 35 +++--- src/client/util/LinkFollower.ts | 78 ++++++++---- src/client/views/DocumentButtonBar.scss | 17 +++ src/client/views/DocumentButtonBar.tsx | 68 +++++++--- src/client/views/GestureOverlay.tsx | 1 - src/client/views/PropertiesView.tsx | 88 +++++++++---- .../views/collections/CollectionNoteTakingView.tsx | 4 +- .../views/collections/CollectionPileView.tsx | 2 +- .../views/collections/CollectionStackingView.tsx | 4 +- .../views/collections/CollectionTimeView.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 5 + .../collectionFreeForm/CollectionFreeFormView.tsx | 6 +- .../collectionLinear/CollectionLinearView.tsx | 3 +- src/client/views/linking/LinkPopup.scss | 5 +- src/client/views/linking/LinkPopup.tsx | 13 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 2 +- src/client/views/nodes/DocumentLinksButton.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 11 +- src/client/views/nodes/LabelBox.tsx | 140 ++++++++++++--------- src/client/views/nodes/WebBox.tsx | 8 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 5 +- src/client/views/nodes/trails/PresBox.tsx | 37 ++++-- src/client/views/nodes/trails/PresEnums.ts | 1 + src/client/views/pdf/AnchorMenu.tsx | 3 +- src/client/views/pdf/PDFViewer.tsx | 21 ++-- src/client/views/search/SearchBox.tsx | 29 ++++- src/fields/util.ts | 10 +- 29 files changed, 416 insertions(+), 217 deletions(-) (limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx') diff --git a/src/Utils.ts b/src/Utils.ts index 5e0514bc6..9d3b9eb2b 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -558,9 +558,13 @@ export namespace JSONUtils { } } -const easeInOutQuad = (currentTime: number, start: number, change: number, duration: number) => { - let newCurrentTime = currentTime / (duration / 2); +const easeFunc = (transition: 'ease' | 'linear' | undefined, currentTime: number, start: number, change: number, duration: number) => { + if (transition === 'linear') { + let newCurrentTime = currentTime / duration; // currentTime / (duration / 2); + return start + newCurrentTime * change; + } + let newCurrentTime = currentTime / (duration / 2); if (newCurrentTime < 1) { return (change / 2) * newCurrentTime * newCurrentTime + start; } @@ -569,23 +573,28 @@ const easeInOutQuad = (currentTime: number, start: number, change: number, durat return (-change / 2) * (newCurrentTime * (newCurrentTime - 2) - 1) + start; }; -export function smoothScroll(duration: number, element: HTMLElement | HTMLElement[], to: number) { +export function smoothScroll(duration: number, element: HTMLElement | HTMLElement[], to: number, transition: 'ease' | 'linear' | undefined, stopper?: () => void) { + stopper?.(); const elements = element instanceof HTMLElement ? [element] : element; const starts = elements.map(element => element.scrollTop); const startDate = new Date().getTime(); - + let _stop = false; + const stop = () => (_stop = true); const animateScroll = () => { const currentDate = new Date().getTime(); const currentTime = currentDate - startDate; - elements.map((element, i) => (element.scrollTop = easeInOutQuad(currentTime, starts[i], to - starts[i], duration))); + elements.map((element, i) => (element.scrollTop = easeFunc(transition, currentTime, starts[i], to - starts[i], duration))); - if (currentTime < duration) { - requestAnimationFrame(animateScroll); - } else { - elements.forEach(element => (element.scrollTop = to)); + if (!_stop) { + if (currentTime < duration) { + requestAnimationFrame(animateScroll); + } else { + elements.forEach(element => (element.scrollTop = to)); + } } }; animateScroll(); + return stop; } export function smoothScrollHorizontal(duration: number, element: HTMLElement | HTMLElement[], to: number) { @@ -596,7 +605,7 @@ export function smoothScrollHorizontal(duration: number, element: HTMLElement | const animateScroll = () => { const currentDate = new Date().getTime(); const currentTime = currentDate - startDate; - elements.map((element, i) => (element.scrollLeft = easeInOutQuad(currentTime, starts[i], to - starts[i], duration))); + elements.map((element, i) => (element.scrollLeft = easeFunc('ease', currentTime, starts[i], to - starts[i], duration))); if (currentTime < duration) { requestAnimationFrame(animateScroll); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d13d96dd3..634b14822 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -37,7 +37,7 @@ import { FontIconBox } from '../views/nodes/button/FontIconBox'; import { ColorBox } from '../views/nodes/ColorBox'; import { ComparisonBox } from '../views/nodes/ComparisonBox'; import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox'; -import { DocFocusOptions, OpenWhereMod } from '../views/nodes/DocumentView'; +import { DocFocusOptions, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView'; import { EquationBox } from '../views/nodes/EquationBox'; import { FieldViewProps } from '../views/nodes/FieldView'; import { FilterBox } from '../views/nodes/FilterBox'; @@ -1322,7 +1322,7 @@ export namespace DocUtils { return DocUtils.ActiveRecordings.map(audio => { const sourceDoc = getSourceDoc(); const link = sourceDoc && DocUtils.MakeLink({ doc: sourceDoc }, { doc: audio.getAnchor() || audio.props.Document }, 'recording annotation:linked recording', 'recording timeline'); - link && (link.followLinkLocation = 'add:right'); + link && (link.followLinkLocation = OpenWhere.addRight); return link; }); } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 635980e03..5549769aa 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -22,7 +22,7 @@ import { Colors } from "../views/global/globalEnums"; import { MainView } from "../views/MainView"; import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; import { OverlayView } from "../views/OverlayView"; -import { DragManager } from "./DragManager"; +import { DragManager, dropActionType } from "./DragManager"; import { MakeTemplate } from "./DropConverter"; import { LinkManager } from "./LinkManager"; import { ScriptingGlobals } from "./ScriptingGlobals"; @@ -306,7 +306,7 @@ export class CurrentUserUtils { const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => { const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined; const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit, - _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, _dropAction: "alias", + _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, system: true, _removeDropProperties: new List(["_stayInCollection"]), }; @@ -316,7 +316,7 @@ export class CurrentUserUtils { const reqdOpts:DocumentOptions = { title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, system: true, _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true, - childDocumentsActive: true + childDocumentsActive: true, childDropAction: 'alias' }; const reqdScripts = { dropConverter: "convertToButtons(dragData)" }; return DocUtils.AssignScripts(DocUtils.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts); @@ -352,15 +352,15 @@ export class CurrentUserUtils { const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined; const reqdBtnOpts:DocumentOptions = { title, icon, target, btnType: ButtonType.MenuButton, system: true, dontUndo: true, dontRegisterView: true, - _width: 60, _height: 60, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, _dropAction: "alias", - _removeDropProperties: new List(["dropAction", "_stayInCollection"]), + _width: 60, _height: 60, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, + _removeDropProperties: new List(["_stayInCollection"]), }; return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), scripts, funcs); }); const reqdStackOpts:DocumentOptions ={ - title: "menuItemPanel", childDropAction: "alias", backgroundColor: Colors.DARK_GRAY, boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true, - _chromeHidden: true, _gridGap: 0, _yMargin: 0, _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true + title: "menuItemPanel", childDropAction: "same", backgroundColor: Colors.DARK_GRAY, boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true, + _chromeHidden: true, _gridGap: 0, _yMargin: 0, _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true, }; return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" }); } @@ -397,14 +397,14 @@ export class CurrentUserUtils { // sets up the main document for the mobile button static mobileButton = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MulticolumnDocument(docs, { ...opts, - _removeDropProperties: new List(["dropAction"]), _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15, + _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15, borderRounding: "5px", boxShadow: "0 0", system: true }) as any as Doc // sets up the text container for the information contained within the mobile button static mobileTextContainer = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MultirowDocument(docs, { ...opts, - _removeDropProperties: new List(["dropAction"]), _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25, + _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25, backgroundColor: "rgba(0,0,0,0)", borderRounding: "0", boxShadow: "0 0", ignoreClick: true, system: true }) as any as Doc @@ -573,8 +573,8 @@ export class CurrentUserUtils { }) static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({ - btnType: ButtonType.ToolButton, _forceActive: true, _dropAction: "alias", _hideContextMenu: true, - _removeDropProperties: new List(["_dropAction", "_hideContextMenu", "stayInCollection"]), + btnType: ButtonType.ToolButton, _forceActive: true, _hideContextMenu: true, + _removeDropProperties: new List([ "_hideContextMenu", "stayInCollection"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true, ...opts, }) @@ -595,12 +595,15 @@ export class CurrentUserUtils { // { scripts: { onClick: 'nextKeyFrame(_readOnly_)'}, opts:{title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, width: 20,} }, ]; const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...desc.opts}, desc.scripts)); - const dockBtnsReqdOpts = { - title: "docked buttons", _height: 40, flexGap: 0, boxShadow: "standard", + const dockBtnsReqdOpts:DocumentOptions = { + title: "docked buttons", _height: 40, flexGap: 0, boxShadow: "standard", childDropAction: 'alias', childDontRegisterViews: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true }; reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); - reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); + reaction(() => UndoManager.undoStack.slice(), () => { + console.log(UndoManager.undoStack) + Doc.GetProto(btns.find(btn => btn.title === "undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4; + }, { fireImmediately: true }); return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns); } @@ -687,7 +690,7 @@ export class CurrentUserUtils { _nativeWidth: params.width ?? 30, _width: params.width ?? 30, _height: 30, _nativeHeight: 30, _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, - _dropAction: "alias", _removeDropProperties: new List(["dropAction", "_stayInCollection"]), + _removeDropProperties: new List([ "_stayInCollection"]), }; const reqdFuncs:{[key:string]:any} = { ...params.funcs, @@ -698,7 +701,7 @@ export class CurrentUserUtils { /// Initializes all the default buttons for the top bar context menu static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") { - const reqdCtxtOpts = { title: "context menu buttons", flexGap: 0, childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }; + const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", flexGap: 0, childDropAction: 'alias', childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }; const ctxtMenuBtnsDoc = DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined); const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => { const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find(doc => doc.title === params.title); diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index 0285803e8..94badbb44 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -1,5 +1,6 @@ import { action, runInAction } from 'mobx'; -import { Doc, DocListCast, Opt } from '../../fields/Doc'; +import { Doc, DocListCast, Opt, WidthSym } from '../../fields/Doc'; +import { listSpec } from '../../fields/Schema'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; import { DocumentType } from '../documents/DocumentTypes'; import { DocumentDecorations } from '../views/DocumentDecorations'; @@ -98,32 +99,59 @@ export class LinkFollower { : linkDoc.anchor1 ) as Doc; if (target) { - const options: DocFocusOptions = { - playAudio: BoolCast(sourceDoc.followLinkAudio), - toggleTarget: BoolCast(sourceDoc.followLinkToggle), - willPan: true, - willPanZoom: BoolCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkZoom, false), - zoomTime: NumCast(LinkManager.getOppositeAnchor(linkDoc, target)?.linkTransitionTime, 500), - zoomScale: Cast(sourceDoc.linkZoomScale, 'number', null), - effect: sourceDoc, - originatingDoc: sourceDoc, + const doFollow = (canToggle?: boolean) => { + const options: DocFocusOptions = { + playAudio: BoolCast(sourceDoc.followLinkAudio), + toggleTarget: canToggle && BoolCast(sourceDoc.followLinkToggle), + willPan: true, + willPanZoom: BoolCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkZoom, false), + zoomTime: NumCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkTransitionTime, 500), + zoomScale: Cast(sourceDoc.followLinkZoomScale, 'number', null), + easeFunc: StrCast(sourceDoc.followLinkEase, 'ease') as any, + effect: sourceDoc, + originatingDoc: sourceDoc, + }; + if (target.TourMap) { + const fieldKey = Doc.LayoutFieldKey(target); + const tour = DocListCast(target[fieldKey]).reverse(); + LightboxView.SetLightboxDoc(currentContext, undefined, tour); + setTimeout(LightboxView.Next); + allFinished(); + } else { + const containerAnnoDoc = Cast(target.annotationOn, Doc, null); + const containerDoc = containerAnnoDoc || target; + var containerDocContext = containerDoc?.context ? [Cast(containerDoc?.context, Doc, null)] : ([] as Doc[]); + while (containerDocContext.length && !DocumentManager.Instance.getDocumentView(containerDocContext[0]) && containerDocContext[0].context) { + containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext]; + } + const targetContexts = LightboxView.LightboxDoc ? [containerAnnoDoc || containerDocContext[0]].filter(a => a) : containerDocContext; + DocumentManager.Instance.jumpToDocument(target, options, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, OpenWhere.inPlace), finished), targetContexts, allFinished); + } }; - if (target.TourMap) { - const fieldKey = Doc.LayoutFieldKey(target); - const tour = DocListCast(target[fieldKey]).reverse(); - LightboxView.SetLightboxDoc(currentContext, undefined, tour); - setTimeout(LightboxView.Next); - allFinished(); - } else { - const containerAnnoDoc = Cast(target.annotationOn, Doc, null); - const containerDoc = containerAnnoDoc || target; - var containerDocContext = containerDoc?.context ? [Cast(containerDoc?.context, Doc, null)] : ([] as Doc[]); - while (containerDocContext.length && !DocumentManager.Instance.getDocumentView(containerDocContext[0]) && containerDocContext[0].context) { - containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext]; + let movedTarget = false; + if (sourceDoc.followLinkLocation === OpenWhere.inParent) { + const sourceDocParent = DocCast(sourceDoc.context); + if (target.context instanceof Doc && target.context !== sourceDocParent) { + Doc.RemoveDocFromList(target.context, Doc.LayoutFieldKey(target.context), target); + movedTarget = true; + } + if (!DocListCast(sourceDocParent[Doc.LayoutFieldKey(sourceDocParent)]).includes(target)) { + Doc.AddDocToList(sourceDocParent, Doc.LayoutFieldKey(sourceDocParent), target); + movedTarget = true; + } + target.context = sourceDocParent; + const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)]; + if (sourceDoc.followLinkXoffset !== undefined && moveTo[0] !== target.x) { + target.x = moveTo[0]; + movedTarget = true; + } + if (sourceDoc.followLinkYoffset !== undefined && moveTo[1] !== target.y) { + target.y = moveTo[1]; + movedTarget = true; } - const targetContexts = LightboxView.LightboxDoc ? [containerAnnoDoc || containerDocContext[0]].filter(a => a) : containerDocContext; - DocumentManager.Instance.jumpToDocument(target, options, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, 'inPlace'), finished), targetContexts, allFinished); - } + if (movedTarget) setTimeout(doFollow); + else doFollow(true); + } else doFollow(true); } else { allFinished(); } diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss index f9c988fdd..835d6c8bb 100644 --- a/src/client/views/DocumentButtonBar.scss +++ b/src/client/views/DocumentButtonBar.scss @@ -28,6 +28,15 @@ $linkGap: 3px; height: 20px; align-items: center; } +.documentButtonBar-linkTypes { + position: absolute; + display: none; + width: 60px; + top: -14px; + background: black; + height: 20px; + align-items: center; +} .documentButtonBar-followTypes { width: 20px; display: none; @@ -55,6 +64,14 @@ $linkGap: 3px; } } } +.documentButtonBar-link { + color: white; + &:hover { + .documentButtonBar-linkTypes { + display: flex; + } + } +} .documentButtonBar-pinIcon { &:hover { diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 5969d55e9..90c6c040c 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -1,24 +1,24 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; -import { action, computed, observable, runInAction } from 'mobx'; +import { action, computed, observable, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../fields/Doc'; import { RichTextField } from '../../fields/RichTextField'; -import { BoolCast, Cast, NumCast } from '../../fields/Types'; +import { Cast, NumCast } from '../../fields/Types'; import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; import { Docs } from '../documents/Documents'; import { DragManager } from '../util/DragManager'; import { SelectionManager } from '../util/SelectionManager'; -import { SettingsManager } from '../util/SettingsManager'; import { SharingManager } from '../util/SharingManager'; import { undoBatch, UndoManager } from '../util/UndoManager'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { TabDocView } from './collections/TabDocView'; import './DocumentButtonBar.scss'; import { Colors } from './global/globalEnums'; +import { LinkPopup } from './linking/LinkPopup'; import { MetadataEntryMenu } from './MetadataEntryMenu'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { DocumentView, DocumentViewInternal, OpenWhereMod } from './nodes/DocumentView'; @@ -26,6 +26,7 @@ import { DashFieldView } from './nodes/formattedText/DashFieldView'; import { GoogleRef } from './nodes/formattedText/FormattedTextBox'; import { TemplateMenu } from './TemplateMenu'; import React = require('react'); +import { DocumentType } from '../documents/DocumentTypes'; const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -255,6 +256,28 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV ); } + @observable subLink = ''; + @computed get linkButton() { + const targetDoc = this.view0?.props.Document; + return !targetDoc || !this.view0 ? null : ( +
+
+ search for target
}> +
+ +
+ +
+
+ +
+
+ ); + } + @observable subPin = ''; @computed get pinButton() { @@ -284,7 +307,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV .views() .filter(v => v) .map(dv => dv!.rootDoc); - TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout, pinDocContent, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout, pinDocContent, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null), currentFrame: Cast(docs.lastElement()?.currentFrame, 'number', null) }); e.stopPropagation(); }} /> @@ -319,12 +342,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV get shareButton() { const targetDoc = this.view0?.props.Document; return !targetDoc ? null : ( - -
{'Open Sharing Manager'}
- - }> + {'Open Sharing Manager'}
}>
SharingManager.Instance.open(this.view0, targetDoc)}>
@@ -347,12 +365,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV get metadataButton() { const view0 = this.view0; return !view0 ? null : ( - -
Show metadata panel
- - }> + Show metadata panel
}>
(DocumentV simulateMouseClick(child, e.clientX, e.clientY - 30, e.screenX, e.screenY - 30); }; + @observable _showLinkPopup = false; + @action + toggleLinkSearch = (e: React.PointerEvent) => { + this._showLinkPopup = !this._showLinkPopup; + e.stopPropagation(); + }; render() { if (!this.view0) return null; @@ -476,9 +495,20 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
-
- -
+ {this._showLinkPopup ? ( +
+ (link.linkDisplay = !this.props.views().lastElement()?.rootDoc.isLinkButton)} + linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.()} + linkFrom={() => this.props.views().lastElement()?.rootDoc} + /> +
+ ) : ( +
{this.linkButton}
+ )} + {(DocumentLinksButton.StartLink || Doc.UserDoc()['documentLinksButton-fullMenu']) && DocumentLinksButton.StartLink !== doc ? (
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index a29073f14..e3328fb4c 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -122,7 +122,6 @@ export class GestureOverlay extends Touchable { onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined, backgroundColor: data.backgroundColor, - _removeDropProperties: new List(['dropAction']), dragFactory: data.dragFactory, system: true, }) diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index e43b160b8..92c5708aa 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1414,7 +1414,7 @@ export class PropertiesView extends React.Component { changeFollowBehavior = action((follow: string) => this.sourceAnchor && (this.sourceAnchor.followLinkLocation = follow)); @undoBatch - changeAnimationBehavior = action((behavior: string) => this.sourceAnchor && (this.sourceAnchor.linkAnimEffect = behavior)); + changeAnimationBehavior = action((behavior: string) => this.sourceAnchor && (this.sourceAnchor.followLinkAnimEffect = behavior)); @undoBatch changeEffectDirection = action((effect: PresEffectDirection) => this.sourceAnchor && (this.sourceAnchor.linkAnimDirection = effect)); @@ -1474,8 +1474,20 @@ export class PropertiesView extends React.Component { return selAnchor ?? (LinkManager.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.currentLink, this.destinationAnchor) : LinkManager.currentLink); } - toggleAnchorProp = (e: React.PointerEvent, prop: string, anchor?: Doc) => { - anchor && setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (anchor[prop] = !anchor[prop])))); + toggleAnchorProp = (e: React.PointerEvent, prop: string, anchor?: Doc, value: any = true, ovalue: any = false, cb: (val: any) => any = val => val) => { + anchor && + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + undoBatch( + action(() => { + anchor[prop] = anchor[prop] === value ? ovalue : value; + this.selectedDoc && cb(anchor[prop]); + }) + ) + ); }; @computed @@ -1516,7 +1528,7 @@ export class PropertiesView extends React.Component { if (change) scale += change; if (scale < 0.01) scale = 0.01; if (scale > 1) scale = 1; - this.sourceAnchor && (this.sourceAnchor.linkZoomScale = scale); + this.sourceAnchor && (this.sourceAnchor.followLinkZoomScale = scale); }; /** @@ -1532,7 +1544,7 @@ export class PropertiesView extends React.Component { render() { const isNovice = Doc.noviceMode; - const zoom = Number((NumCast(this.sourceAnchor?.linkZoomScale, 1) * 100).toPrecision(3)); + const zoom = Number((NumCast(this.sourceAnchor?.followLinkZoomScale, 1) * 100).toPrecision(3)); const targZoom = this.sourceAnchor?.followLinkZoom; const indent = 30; const hasSelectedAnchor = SelectionManager.Views().some(dv => DocListCast(this.sourceAnchor?.links).includes(LinkManager.currentLink!)); @@ -1610,21 +1622,22 @@ export class PropertiesView extends React.Component {

Follow by

Animation

- this.changeAnimationBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.followLinkAnimEffect, 'default')}> {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => ( @@ -1642,9 +1655,9 @@ export class PropertiesView extends React.Component { '0.1', '0.1', '10', - NumCast(this.sourceAnchor?.linkTransitionTime) / 1000, + NumCast(this.sourceAnchor?.followLinkTransitionTime) / 1000, true, - (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.linkTransitionTime = timeInMS)), + (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.followLinkTransitionTime = timeInMS)), indent )}{' '}
{
+
+

Ease Transitions

+ +
+
+

Capture Offset to Target

+ +
+
+

Center Target (no zoom)

+ +

Zoom %

-
+
this.setZoom(String(zoom), 0.1))}> @@ -1695,18 +1741,18 @@ export class PropertiesView extends React.Component {
- {!targZoom ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom, 30)} + {!targZoom || this.sourceAnchor?.followLinkZoomScale === 0 ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom, 30)}
{ - smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight); + smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight, 'ease'); }; // let's dive in and get the actual document we want to drag/move around @@ -193,7 +193,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { const top = found.getBoundingClientRect().top; const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); if (Math.floor(localTop[1]) !== 0) { - smoothScroll((focusSpeed = options.zoomTime ?? 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop); + smoothScroll((focusSpeed = options.zoomTime ?? 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); } } const endFocus = async (moved: boolean) => (options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing); diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index e95622630..ba90ed8cd 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -120,7 +120,7 @@ export class CollectionPileView extends CollectionSubView() { const doc = this.childDocs[0]; doc.x = e.clientX; doc.y = e.clientY; - this.props.addDocTab(doc, OpenWhere.inParent) && (this.props.removeDocument?.(doc) || false); + this.props.addDocTab(doc, OpenWhere.inParentFromScreen) && (this.props.removeDocument?.(doc) || false); dist = 0; } } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 08aebc62d..acf59b5da 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -242,7 +242,7 @@ export class CollectionStackingView extends CollectionSubView { - smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight); + smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight, 'ease'); }; // let's dive in and get the actual document we want to drag/move around @@ -255,7 +255,7 @@ export class CollectionStackingView extends CollectionSubView options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing; diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index ac896a8fd..a1466bcd0 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -57,7 +57,7 @@ export class CollectionTimeView extends CollectionSubView() { 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'; alias.removeDropProperties=new List(['dropAction']); useRightSplit(alias, shiftKey); "; + ///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' }); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index a61ae680b..e45e2a1cb 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -281,6 +281,11 @@ export class TabDocView extends React.Component { pinDoc.title = doc.title + ' (move)'; pinDoc.presMovement = PresMovement.Pan; } + if (pinProps?.currentFrame !== undefined) { + pinDoc.presCurrentFrame = pinProps?.currentFrame; + pinDoc.title = doc.title + ' (move)'; + pinDoc.presMovement = PresMovement.Pan; + } if (pinDoc.isInkMask) { pinDoc.presHideAfter = true; pinDoc.presHideBefore = true; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 4818094de..6b9eb7916 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1186,7 +1186,7 @@ export class CollectionFreeFormView extends CollectionSubView { switch (where) { case OpenWhere.inParent: + return this.props.addDocument?.(doc) || false; + case OpenWhere.inParentFromScreen: return ( this.props.addDocument?.( (doc instanceof Doc ? [doc] : doc).map(doc => { @@ -1741,7 +1743,7 @@ export class CollectionFreeFormView extends CollectionSubView Doc | undefined; + linkCreateAnchor?: () => Doc | undefined; + linkCreated?: (link: Doc) => void; // groupType: string; // linkDoc: Doc; // docView: DocumentView; @@ -32,14 +35,10 @@ export class LinkPopup extends React.Component { // TODO: should check for valid URL @undoBatch - makeLinkToURL = (target: string, lcoation: string) => { - ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, 'onRadd:rightight', target, target); - }; + makeLinkToURL = (target: string, lcoation: string) => ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, OpenWhere.addRight, target, target); @action - onLinkChange = (e: React.ChangeEvent) => { - this.linkURL = e.target.value; - }; + onLinkChange = (e: React.ChangeEvent) => (this.linkURL = e.target.value); getPWidth = () => 500; getPHeight = () => 500; @@ -67,7 +66,9 @@ export class LinkPopup extends React.Component { Document={Doc.MySearcher} DataDoc={Doc.MySearcher} linkFrom={linkDoc} + linkCreateAnchor={this.props.linkCreateAnchor} linkSearch={true} + linkCreated={this.props.linkCreated} fieldKey="data" dropAction="move" isSelected={returnTrue} diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index bf1f13a06..f8ef87fb1 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -184,7 +184,7 @@ export class CollectionFreeFormDocumentView extends DocComponent - {!DocumentLinksButton.LinkEditorDocView ? this.linkButtonInner : {title}
}>{this.linkButtonInner}} + {title}
}>{this.linkButtonInner}
); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c9fbe7a98..76cc6800a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -75,6 +75,7 @@ export enum OpenWhere { inPlace = 'inPlace', lightbox = 'lightbox', add = 'add', + addLeft = 'add:left', addRight = 'add:right', addBottom = 'add:bottom', dashboard = 'dashboard', @@ -82,7 +83,10 @@ export enum OpenWhere { fullScreen = 'fullScreen', toggle = 'toggle', replace = 'replace', + replaceRight = 'replace:right', + replaceLeft = 'replace:left', inParent = 'inParent', + inParentFromScreen = 'inParentFromScreen', } export enum OpenWhereMod { none = '', @@ -108,6 +112,7 @@ export interface DocFocusOptions { playAudio?: boolean; // whether to play audio annotation on focus toggleTarget?: boolean; // whether to toggle target on and off originatingDoc?: Doc; // document that triggered the focus + easeFunc?: 'linear' | 'ease'; // transition method for scrolling } export type DocAfterFocusFunc = (notFocused: boolean) => Promise; export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => void; @@ -737,7 +742,7 @@ export class DocumentViewInternal extends DocComponent{renderDoc}; diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index b58a9affb..10897b48f 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -15,16 +15,17 @@ import { FieldView, FieldViewProps } from './FieldView'; import BigText from './LabelBigText'; import './LabelBox.scss'; - export interface LabelBoxProps { label?: string; } @observer -export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxProps)>() { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LabelBox, fieldKey); } +export class LabelBox extends ViewBoxBaseComponent() { + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(LabelBox, fieldKey); + } public static LayoutStringWithTitle(fieldStr: string, label?: string) { - return !label ? LabelBox.LayoutString(fieldStr) : ``; //e.g., "" + return !label ? LabelBox.LayoutString(fieldStr) : ``; //e.g., "" } private dropDisposer?: DragManager.DragDropDisposer; private _timeout: any; @@ -35,11 +36,12 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro this._timeout && clearTimeout(this._timeout); } + getAnchor = () => { + return this.rootDoc; + }; + getTitle() { - return this.rootDoc["title-custom"] ? StrCast(this.rootDoc.title) : - this.props.label ? this.props.label : - typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) : - StrCast(this.rootDoc.title); + return this.rootDoc['title-custom'] ? StrCast(this.rootDoc.title) : this.props.label ? this.props.label : typeof this.rootDoc[this.fieldKey] === 'string' ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title); } protected createDropTarget = (ele: HTMLDivElement) => { @@ -47,36 +49,42 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro if (ele) { this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document); } - } + }; - get paramsDoc() { return Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? this.dataDoc : this.layoutDoc; } + get paramsDoc() { + return Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? this.dataDoc : this.layoutDoc; + } specificContextMenu = (e: React.MouseEvent): void => { const funcs: ContextMenuProps[] = []; - !Doc.noviceMode && funcs.push({ - description: "Clear Script Params", event: () => { - const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []); - params?.map(p => this.paramsDoc[p] = undefined); - }, icon: "trash" - }); + !Doc.noviceMode && + funcs.push({ + description: 'Clear Script Params', + event: () => { + const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []); + params?.map(p => (this.paramsDoc[p] = undefined)); + }, + icon: 'trash', + }); - funcs.length && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: funcs, icon: "mouse-pointer" }); - } + funcs.length && ContextMenu.Instance.addItem({ description: 'OnClick...', noexpand: true, subitems: funcs, icon: 'mouse-pointer' }); + }; @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { const docDragData = de.complete.docDragData; - const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []); + const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []); const missingParams = params?.filter(p => !this.paramsDoc[p]); if (docDragData && missingParams?.includes((e.target as any).textContent)) { - this.paramsDoc[(e.target as any).textContent] = new List(docDragData.droppedDocuments.map((d, i) => - d.onDragStart ? docDragData.draggedDocuments[i] : d)); + this.paramsDoc[(e.target as any).textContent] = new List(docDragData.droppedDocuments.map((d, i) => (d.onDragStart ? docDragData.draggedDocuments[i] : d))); e.stopPropagation(); } - } + }; @observable _mouseOver = false; - @computed get hoverColor() { return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : "unset"; } + @computed get hoverColor() { + return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : 'unset'; + } fitTextToBox = (r: any): any => { const singleLine = BoolCast(this.rootDoc._singleLine, true); @@ -85,63 +93,73 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro fontSizeFactor: 1, minimumFontSize: NumCast(this.rootDoc._minFontSize, 8), maximumFontSize: NumCast(this.rootDoc._maxFontSize, 1000), - limitingDimension: "both", - horizontalAlign: "center", - verticalAlign: "center", - textAlign: "center", + limitingDimension: 'both', + horizontalAlign: 'center', + verticalAlign: 'center', + textAlign: 'center', singleLine, - whiteSpace: singleLine ? "nowrap" : "pre-wrap" + whiteSpace: singleLine ? 'nowrap' : 'pre-wrap', }; this._timeout = undefined; if (!r) return params; - if (!r.offsetHeight || !r.offsetWidth) return this._timeout = setTimeout(() => this.fitTextToBox(r)); + if (!r.offsetHeight || !r.offsetWidth) return (this._timeout = setTimeout(() => this.fitTextToBox(r))); const parent = r.parentNode; const parentStyle = parent.style; - parentStyle.display = ""; - parentStyle.alignItems = ""; - r.setAttribute("style", ""); - r.style.width = singleLine ? "" : "100%"; + parentStyle.display = ''; + parentStyle.alignItems = ''; + r.setAttribute('style', ''); + r.style.width = singleLine ? '' : '100%'; - r.style.textOverflow = "ellipsis"; - r.style.overflow = "hidden"; + r.style.textOverflow = 'ellipsis'; + r.style.overflow = 'hidden'; BigText(r, params); return params; - } + }; // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")") render() { - const boxParams = this.fitTextToBox(null);// this causes mobx to trigger re-render when data changes - const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []); + const boxParams = this.fitTextToBox(null); // this causes mobx to trigger re-render when data changes + const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []); const missingParams = params?.filter(p => !this.paramsDoc[p]); params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ... const label = this.getTitle(); return ( -
this._mouseOver = false)} - onMouseOver={action(() => this._mouseOver = true)} - ref={this.createDropTarget} onContextMenu={this.specificContextMenu} +
(this._mouseOver = false))} + onMouseOver={action(() => (this._mouseOver = true))} + ref={this.createDropTarget} + onContextMenu={this.specificContextMenu} style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow) }}> -
- this.fitTextToBox(r))}> - {label.startsWith("#") ? (null) : label.replace(/([^a-zA-Z])/g, "$1\u200b")} +
+ this.fitTextToBox(r))}> + {label.startsWith('#') ? null : label.replace(/([^a-zA-Z])/g, '$1\u200b')}
-
- {!missingParams?.length ? (null) : missingParams.map(m =>
{m}
)} +
+ {!missingParams?.length + ? null + : missingParams.map(m => ( +
+ {m} +
+ ))}
); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 64b186489..a3e83f047 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -241,7 +241,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { + goTo = (scrollTop: number, duration: number, easeFunc: 'linear' | 'ease' | undefined) => { if (this._outerRef.current) { const iframeHeight = Math.max(scrollTop, this._scrollHeight - this.panelHeight()); if (duration) { - smoothScroll(duration, [this._outerRef.current], scrollTop); + smoothScroll(duration, [this._outerRef.current], scrollTop, easeFunc); this.setDashScrollTop(scrollTop, duration); } else { this.setDashScrollTop(scrollTop); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 9e91f6c46..b895043de 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1120,7 +1120,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent void); setupEditor(config: any, fieldKey: string) { const curText = Cast(this.dataDoc[this.fieldKey], RichTextField, null) || StrCast(this.dataDoc[this.fieldKey]); const rtfField = Cast((!curText && this.layoutDoc[this.fieldKey]) || this.dataDoc[fieldKey], RichTextField); @@ -1302,7 +1303,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent() { this.rootDoc._itemIndex = index; const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - if (activeItem.presActiveFrame !== undefined) { + const activeFrame = activeItem.presActiveFrame ?? activeItem.presCurrentFrame; + if (activeFrame !== undefined) { const transTime = NumCast(activeItem.presTransition, 500); - const context = DocCast(DocCast(activeItem.presentationTargetDoc).context); + const context = activeItem.presActiveFrame ? DocCast(DocCast(activeItem.presentationTargetDoc).context) : DocCast(activeItem.presentationTargetDoc); if (context) { const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.ComponentView as CollectionFreeFormView; if (ffview) { this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, ffview.childDocs.slice(), transTime); - context._currentFrame = NumCast(activeItem.presActiveFrame); + context._currentFrame = NumCast(activeFrame); } } } @@ -547,11 +549,12 @@ export class PresBox extends ViewBoxBaseComponent() { LightboxView.SetLightboxDoc(undefined); const options: DocFocusOptions = { willPan: activeItem.presMovement !== PresMovement.None, - willPanZoom: activeItem.presMovement === PresMovement.Zoom || activeItem.presMovement === PresMovement.Jump, - zoomScale: NumCast(activeItem.presZoom, 1), + willPanZoom: activeItem.presMovement === PresMovement.Zoom || activeItem.presMovement === PresMovement.Jump || activeItem.presMovement === PresMovement.Center, + zoomScale: activeItem.presMovement === PresMovement.Center ? 0 : NumCast(activeItem.presZoom, 1), zoomTime: activeItem.presMovement === PresMovement.Jump ? 0 : NumCast(activeItem.presTransition, 500), noSelect: true, originatingDoc: activeItem, + easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any, }; var containerDocContext = srcContext ? [srcContext] : []; @@ -747,8 +750,8 @@ export class PresBox extends ViewBoxBaseComponent() { }); movementName = action((activeItem: Doc) => { - if (![PresMovement.Zoom, PresMovement.Pan, PresMovement.Jump, PresMovement.None].includes(StrCast(activeItem.presMovement) as any)) { - activeItem.presMovement = PresMovement.Zoom; + if (![PresMovement.Zoom, PresMovement.Pan, PresMovement.Center, PresMovement.Jump, PresMovement.None].includes(StrCast(activeItem.presMovement) as any)) { + return PresMovement.Zoom; } return StrCast(activeItem.presMovement); }); @@ -890,7 +893,7 @@ export class PresBox extends ViewBoxBaseComponent() { else this.regularSelect(doc, ref, drag, focus); }; - static keyEventsWrapper = (e: KeyboardEvent) => PresBox.Instance.keyEvents(e); + static keyEventsWrapper = (e: KeyboardEvent) => PresBox.Instance?.keyEvents(e); // Key for when the presentaiton is active @action @@ -1097,7 +1100,7 @@ export class PresBox extends ViewBoxBaseComponent() { let timeInMS = Number(number) * 1000; if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; - if (timeInMS > 10000) timeInMS = 10000; + if (timeInMS > 100000) timeInMS = 100000; setter(timeInMS); }; setTransitionTime = (number: String, change?: number) => { @@ -1155,6 +1158,12 @@ export class PresBox extends ViewBoxBaseComponent() { activeItem.openDocument = !activeItem.openDocument; this.selectedArray.forEach(doc => (doc.openDocument = activeItem.openDocument)); }; + @undoBatch + @action + updateEaseFunc = (activeItem: Doc) => { + activeItem.presEaseFunc = activeItem.presEaseFunc === 'linear' ? 'ease' : 'linear'; + this.selectedArray.forEach(doc => (doc.presEaseFunc = activeItem.presEaseFunc)); + }; @undoBatch @action @@ -1223,7 +1232,7 @@ export class PresBox extends ViewBoxBaseComponent() { let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 2; if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration); const effect = activeItem.presEffect ? activeItem.presEffect : PresMovement.None; - activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : PresMovement.Zoom; + // activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : PresMovement.Zoom; return (
() {
{isPresCollection || (isPresCollection && isPinWithView) ? null : presMovement(PresMovement.None)} + {presMovement(PresMovement.Center)} {presMovement(PresMovement.Zoom)} {presMovement(PresMovement.Pan)} {isPresCollection || (isPresCollection && isPinWithView) ? null : presMovement(PresMovement.Jump)} @@ -1281,7 +1291,7 @@ export class PresBox extends ViewBoxBaseComponent() {
- {PresBox.inputter('0.1', '0.1', '10', transitionSpeed, true, this.setTransitionTime)} + {PresBox.inputter('0.1', '0.1', '100', transitionSpeed, true, this.setTransitionTime)}
Fast
Medium
@@ -1317,6 +1327,11 @@ export class PresBox extends ViewBoxBaseComponent() { Lightbox
+ {'Transition movement style'}
}> +
this.updateEaseFunc(activeItem)}> + {`${StrCast(activeItem.presEaseFunc, 'ease')}`} +
+
{type === DocumentType.AUDIO || type === DocumentType.VID ? null : ( <> diff --git a/src/client/views/nodes/trails/PresEnums.ts b/src/client/views/nodes/trails/PresEnums.ts index 034f7588b..8c8b83fbf 100644 --- a/src/client/views/nodes/trails/PresEnums.ts +++ b/src/client/views/nodes/trails/PresEnums.ts @@ -1,6 +1,7 @@ export enum PresMovement { Zoom = 'zoom', Pan = 'pan', + Center = 'center', Jump = 'jump', None = 'none', } diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index ee2ae10a7..265328036 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -210,14 +210,13 @@ export class AnchorMenu extends AntimodeMenu { ), - //NOTE: link popup is currently in progress {'Find document to link to selected text'}
}> , - , + , AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? ( <> ) : ( diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 0703ca9b4..906dff377 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -76,7 +76,7 @@ export class PDFViewer extends React.Component { private _lastSearch = false; private _viewerIsSetup = false; private _ignoreScroll = false; - private _initialScroll: Opt; + private _initialScroll: { loc: Opt; easeFunc: 'linear' | 'ease' | undefined } | undefined; private _forcedScroll = true; selectionText = () => this._selectionText; @@ -164,6 +164,8 @@ export class PDFViewer extends React.Component { } }; + _scrollStopper: undefined | (() => void); + // scrolls to focus on a nested annotation document. if this is part a link preview then it will jump to the scroll location, // otherwise it will scroll smoothly. scrollFocus = (doc: Doc, scrollTop: number, options: DocFocusOptions) => { @@ -173,12 +175,12 @@ export class PDFViewer extends React.Component { const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); const scrollTo = doc.unrendered ? scrollTop : Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, NumCast(this.props.Document.scrollHeight)); if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) { - if (!this._pdfViewer) this._initialScroll = scrollTo; - else if (!options.instant) smoothScroll((focusSpeed = options.zoomTime??500), mainCont, scrollTo); + if (!this._pdfViewer) this._initialScroll = { loc: scrollTo, easeFunc: options.easeFunc }; + else if (!options.instant) this._scrollStopper = smoothScroll((focusSpeed = options.zoomTime ?? 500), mainCont, scrollTo, options.easeFunc, this._scrollStopper); else this._mainCont.current?.scrollTo({ top: Math.abs(scrollTo || 0) }); } } else { - this._initialScroll = NumCast(this.props.layoutDoc._scrollTop); + this._initialScroll = { loc: NumCast(this.props.layoutDoc._scrollTop), easeFunc: options.easeFunc }; } return focusSpeed; }; @@ -202,7 +204,7 @@ export class PDFViewer extends React.Component { this.gotoPage(NumCast(this.props.Document._curPage, 1)); } document.removeEventListener('pagesinit', this.pagesinit); - var quickScroll: string | undefined = this._initialScroll ? this._initialScroll.toString() : ''; + var quickScroll: { loc?: string; easeFunc?: 'ease' | 'linear' } | undefined = { loc: this._initialScroll ? this._initialScroll.loc?.toString() : '', easeFunc: this._initialScroll ? this._initialScroll.easeFunc : undefined }; this._disposers.scale = reaction( () => NumCast(this.props.layoutDoc._viewScale, 1), scale => (this._pdfViewer.currentScaleValue = scale), @@ -213,7 +215,7 @@ export class PDFViewer extends React.Component { pos => { if (!this._ignoreScroll) { this._showWaiting && this.setupPdfJsViewer(); - const viewTrans = quickScroll ?? StrCast(this.props.Document._viewTransition); + const viewTrans = quickScroll?.loc ?? StrCast(this.props.Document._viewTransition); const durationMiliStr = viewTrans.match(/([0-9]*)ms/); const durationSecStr = viewTrans.match(/([0-9.]*)s/); const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0; @@ -221,7 +223,7 @@ export class PDFViewer extends React.Component { if (duration) { setTimeout( () => { - this._mainCont.current && smoothScroll(duration, this._mainCont.current, pos); + this._mainCont.current && (this._scrollStopper = smoothScroll(duration, this._mainCont.current, pos, this._initialScroll?.easeFunc ?? 'ease', this._scrollStopper)); setTimeout(() => (this._forcedScroll = false), duration); }, this._mainCont.current ? 0 : 250 @@ -236,7 +238,7 @@ export class PDFViewer extends React.Component { ); quickScroll = undefined; if (this._initialScroll !== undefined && this._mainCont.current) { - this._mainCont.current?.scrollTo({ top: Math.abs(this._initialScroll || 0) }); + this._mainCont.current?.scrollTo({ top: Math.abs(this._initialScroll?.loc || 0) }); this._initialScroll = undefined; } }; @@ -295,7 +297,7 @@ export class PDFViewer extends React.Component { @action scrollToAnnotation = (scrollToAnnotation: Doc) => { if (scrollToAnnotation) { - this.scrollFocus(scrollToAnnotation, NumCast(scrollToAnnotation.y), {zoomTime: 500}); + this.scrollFocus(scrollToAnnotation, NumCast(scrollToAnnotation.y), { zoomTime: 500 }); Doc.linkFollowHighlight(scrollToAnnotation); } }; @@ -452,6 +454,7 @@ export class PDFViewer extends React.Component { }; onClick = (e: React.MouseEvent) => { + this._scrollStopper?.(); if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { this._setPreviewCursor(e.clientX, e.clientY, false, false); } diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index b78c8654f..aac488559 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -4,10 +4,12 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; -import { StrCast } from '../../../fields/Types'; +import { DocCast, StrCast } from '../../../fields/Types'; +import { StopEvent } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; +import { LinkManager } from '../../util/LinkManager'; import { CollectionDockingView } from '../collections/CollectionDockingView'; import { ViewBoxBaseComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; @@ -20,6 +22,8 @@ const ERROR = 0.03; export interface SearchBoxProps extends FieldViewProps { linkSearch: boolean; linkFrom?: (() => Doc | undefined) | undefined; + linkCreateAnchor?: () => Doc | undefined; + linkCreated?: (link: Doc) => void; } /** @@ -111,10 +115,11 @@ export class SearchBox extends ViewBoxBaseComponent() { // TODO: nda -- Change this method to change what happens when you click on the item. makeLink = action((linkTo: Doc) => { - if (this.props.linkFrom) { - const linkFrom = this.props.linkFrom(); + if (this.props.linkCreateAnchor) { + const linkFrom = this.props.linkCreateAnchor(); if (linkFrom) { - DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo }); + const link = DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo }); + link && this.props.linkCreated?.(link); } } }); @@ -380,7 +385,7 @@ export class SearchBox extends ViewBoxBaseComponent() { const query = StrCast(this._searchString); Doc.SetSearchQuery(query); - Array.from(this._results.keys()).forEach(doc => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, true)); + if (!this.props.linkSearch) Array.from(this._results.keys()).forEach(doc => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, true)); this._results.clear(); if (query) { @@ -437,6 +442,8 @@ export class SearchBox extends ViewBoxBaseComponent() { const resultsJSX = Array(); + const fromDoc = this.props.linkFrom?.(); + sortedResults.forEach(result => { var className = 'searchBox-results-scroll-view-result'; @@ -460,6 +467,13 @@ export class SearchBox extends ViewBoxBaseComponent() { e.stopPropagation(); } } + style={{ + fontWeight: DocListCast(fromDoc?.links).find( + link => Doc.AreProtosEqual(LinkManager.getOppositeAnchor(link, fromDoc!), result[0] as Doc) || Doc.AreProtosEqual(DocCast(LinkManager.getOppositeAnchor(link, fromDoc!)?.annotationOn), result[0] as Doc) + ) + ? 'bold' + : '', + }} className={className}>
{title as string}
{formattedType}
@@ -482,7 +496,10 @@ export class SearchBox extends ViewBoxBaseComponent() { defaultValue={''} autoComplete="off" onChange={this.onInputChange} - onKeyPress={e => (e.key === 'Enter' ? this.submitSearch() : null)} + onKeyPress={e => { + e.key === 'Enter' ? this.submitSearch() : null; + e.stopPropagation(); + }} type="text" placeholder="Search..." id="search-input" diff --git a/src/fields/util.ts b/src/fields/util.ts index f222e4555..7f4892bd6 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -449,7 +449,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any diff?.op === '$addToSet' ? { redo: () => { - receiver[prop].push(...diff.items.map((item: any) => (item.value ? item.value() : item))); + receiver[prop].push(...diff.items.map((item: any) => item.value ?? item)); lastValue = ObjectField.MakeCopy(receiver[prop]); }, undo: action(() => { @@ -459,7 +459,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any const ind = receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading); ind !== -1 && receiver[prop].splice(ind, 1); } else { - const ind = receiver[prop].indexOf(item.value ? item.value() : item); + const ind = receiver[prop].indexOf(item.value ?? item); ind !== -1 && receiver[prop].splice(ind, 1); } }); @@ -471,7 +471,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any ? { redo: action(() => { diff.items.forEach((item: any) => { - const ind = item instanceof SchemaHeaderField ? receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) : receiver[prop].indexOf(item.value ? item.value() : item); + const ind = item instanceof SchemaHeaderField ? receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) : receiver[prop].indexOf(item.value ?? item); ind !== -1 && receiver[prop].splice(ind, 1); }); lastValue = ObjectField.MakeCopy(receiver[prop]); @@ -483,8 +483,8 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any const ind = (prevValue as List).findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading); ind !== -1 && receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) === -1 && receiver[prop].splice(ind, 0, item); } else { - const ind = (prevValue as List).indexOf(item.value ? item.value() : item); - ind !== -1 && receiver[prop].indexOf(item.value ? item.value() : item) === -1 && receiver[prop].splice(ind, 0, item); + const ind = (prevValue as List).indexOf(item.value ?? item); + ind !== -1 && receiver[prop].indexOf(item.value ?? item) === -1 && receiver[prop].splice(ind, 0, item); } }); lastValue = ObjectField.MakeCopy(receiver[prop]); -- cgit v1.2.3-70-g09d2