aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package-lock.json39
-rw-r--r--src/client/documents/Documents.ts115
-rw-r--r--src/client/util/CaptureManager.tsx23
-rw-r--r--src/client/util/CurrentUserUtils.ts5
-rw-r--r--src/client/util/DocumentManager.ts2
-rw-r--r--src/client/util/LinkFollower.ts4
-rw-r--r--src/client/util/LinkManager.ts17
-rw-r--r--src/client/util/SelectionManager.ts4
-rw-r--r--src/client/views/FilterPanel.scss189
-rw-r--r--src/client/views/FilterPanel.tsx232
-rw-r--r--src/client/views/LightboxView.tsx9
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/PropertiesButtons.tsx10
-rw-r--r--src/client/views/PropertiesView.tsx111
-rw-r--r--src/client/views/SidebarAnnos.tsx7
-rw-r--r--src/client/views/StyleProvider.tsx11
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx2
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx7
-rw-r--r--src/client/views/collections/CollectionSubView.tsx1
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx4
-rw-r--r--src/client/views/collections/CollectionView.tsx4
-rw-r--r--src/client/views/collections/TabDocView.tsx8
-rw-r--r--src/client/views/collections/TreeView.tsx99
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx55
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx2
-rw-r--r--src/client/views/linking/LinkMenu.tsx1
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx6
-rw-r--r--src/client/views/nodes/AudioBox.tsx3
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx2
-rw-r--r--src/client/views/nodes/DocumentView.tsx15
-rw-r--r--src/client/views/nodes/FilterBox.scss12
-rw-r--r--src/client/views/nodes/FilterBox.tsx588
-rw-r--r--src/client/views/nodes/ImageBox.tsx17
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx9
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx7
-rw-r--r--src/client/views/nodes/VideoBox.tsx5
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx21
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx20
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx823
-rw-r--r--src/client/views/search/SearchBox.tsx3
-rw-r--r--src/fields/Doc.ts20
-rw-r--r--src/fields/ScriptField.ts5
-rw-r--r--src/fields/Types.ts3
-rw-r--r--src/fields/util.ts23
44 files changed, 1227 insertions, 1318 deletions
diff --git a/package-lock.json b/package-lock.json
index fe75cf1a5..a30e9e6e3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5337,6 +5337,16 @@
"integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
"dev": true
},
+ "d": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
+ "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
+ "dev": true,
+ "requires": {
+ "es5-ext": "^0.10.50",
+ "type": "^1.0.1"
+ }
+ },
"d3-array": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.2.tgz",
@@ -6559,6 +6569,28 @@
"is-symbol": "^1.0.2"
}
},
+ "es5-ext": {
+ "version": "0.10.62",
+ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
+ "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
+ "dev": true,
+ "requires": {
+ "es6-iterator": "^2.0.3",
+ "es6-symbol": "^3.1.3",
+ "next-tick": "^1.1.0"
+ }
+ },
+ "es6-iterator": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
+ "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
+ "dev": true,
+ "requires": {
+ "d": "1",
+ "es5-ext": "^0.10.35",
+ "es6-symbol": "^3.1.1"
+ }
+ },
"es6-promise": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz",
@@ -6570,6 +6602,7 @@
"integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
"dev": true,
"requires": {
+ "d": "^1.0.1",
"ext": "^1.1.2"
}
},
@@ -22125,6 +22158,12 @@
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
},
+ "type": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
+ "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
+ "dev": true
+ },
"type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 65cb3317a..422ee7711 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { action, runInAction } from 'mobx';
import { basename } from 'path';
import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, DocListCastAsync, Field, Initializing, Opt, updateCachedAcls } from '../../fields/Doc';
+import { Doc, DocListCast, Field, Initializing, Opt, updateCachedAcls } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { HtmlField } from '../../fields/HtmlField';
import { InkField, PointData } from '../../fields/InkField';
@@ -39,7 +39,6 @@ import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox';
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';
import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
import { FunctionPlotBox } from '../views/nodes/FunctionPlotBox';
import { ImageBox } from '../views/nodes/ImageBox';
@@ -262,6 +261,9 @@ export class DocumentOptions {
activeFrame?: number; // the active frame of a document in a frame base collection
appearFrame?: number; // the frame in which the document appears
viewTransitionTime?: number; // transition duration for view parameters
+ presPanX?: number; // panX saved as a view spec
+ presPanY?: number; // panY saved as a view spec
+ presViewScale?: number; // viewScale saved as a view Spec
presTransition?: number; //the time taken for the transition TO a document
presDuration?: number; //the duration of the slide in presentation view
presZoomText?: boolean; // whether text anchors should shown in a larger box when following links to make them stand out
@@ -336,6 +338,7 @@ export class DocumentOptions {
strokeWidth?: number;
freezeChildren?: string; // whether children are now allowed to be added and or removed from a collection
treeViewHideTitle?: boolean; // whether to hide the top document title of a tree view
+ treeViewHideUnrendered?: boolean; // tells tree view not to display documents that have an 'unrendered' tag unless they also have a treeViewFieldKey tag (presBox)
treeViewHideHeaderIfTemplate?: boolean; // whether to hide the header for a document in a tree view only if a childLayoutTemplate is provided (presBox)
treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view
treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items.
@@ -400,7 +403,6 @@ export namespace Docs {
nativeDimModifiable: true,
nativeHeightUnfrozen: true,
forceReflow: true,
- links: '@links(self)',
},
},
],
@@ -408,42 +410,35 @@ export namespace Docs {
DocumentType.SEARCH,
{
layout: { view: SearchBox, dataField: defaultDataKey },
- options: { _width: 400, links: '@links(self)' },
- },
- ],
- [
- DocumentType.FILTER,
- {
- layout: { view: FilterBox, dataField: defaultDataKey },
- options: { _width: 400, links: '@links(self)' },
+ options: { _width: 400 },
},
],
[
DocumentType.COLOR,
{
layout: { view: ColorBox, dataField: defaultDataKey },
- options: { _nativeWidth: 220, _nativeHeight: 300, links: '@links(self)' },
+ options: { _nativeWidth: 220, _nativeHeight: 300 },
},
],
[
DocumentType.IMG,
{
layout: { view: ImageBox, dataField: defaultDataKey },
- options: { links: '@links(self)' },
+ options: {},
},
],
[
DocumentType.WEB,
{
layout: { view: WebBox, dataField: defaultDataKey },
- options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: '@links(self)' },
+ options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true },
},
],
[
DocumentType.COL,
{
layout: { view: CollectionView, dataField: defaultDataKey },
- options: { _fitWidth: true, _panX: 0, _panY: 0, _viewScale: 1, links: '@links(self)' },
+ options: { _fitWidth: true, _panX: 0, _panY: 0, _viewScale: 1 },
},
],
[
@@ -457,35 +452,35 @@ export namespace Docs {
DocumentType.VID,
{
layout: { view: VideoBox, dataField: defaultDataKey },
- options: { _currentTimecode: 0, links: '@links(self)' },
+ options: { _currentTimecode: 0 },
},
],
[
DocumentType.AUDIO,
{
layout: { view: AudioBox, dataField: defaultDataKey },
- options: { _height: 100, backgroundColor: 'lightGray', _fitWidth: true, forceReflow: true, nativeDimModifiable: true, links: '@links(self)' },
+ options: { _height: 100, forceReflow: true, nativeDimModifiable: true },
},
],
[
DocumentType.REC,
{
layout: { view: VideoBox, dataField: defaultDataKey },
- options: { _height: 100, backgroundColor: 'pink', links: '@links(self)' },
+ options: { _height: 100, backgroundColor: 'pink' },
},
],
[
DocumentType.PDF,
{
layout: { view: PDFBox, dataField: defaultDataKey },
- options: { _curPage: 1, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: '@links(self)' },
+ options: { _curPage: 1, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true },
},
],
[
DocumentType.MAP,
{
layout: { view: MapBox, dataField: defaultDataKey },
- options: { _height: 600, _width: 800, nativeDimModifiable: true, links: '@links(self)' },
+ options: { _height: 600, _width: 800, nativeDimModifiable: true },
},
],
[
@@ -506,7 +501,6 @@ export namespace Docs {
description: '',
showCaption: 'description',
backgroundColor: 'lightblue', // lightblue is default color for linking dot and link documents text comment area
- links: '@links(self)',
_removeDropProperties: new List(['isLinkButton']),
},
},
@@ -531,7 +525,7 @@ export namespace Docs {
DocumentType.SCRIPTING,
{
layout: { view: ScriptingBox, dataField: defaultDataKey },
- options: { links: '@links(self)' },
+ options: {},
},
],
[
@@ -544,56 +538,56 @@ export namespace Docs {
DocumentType.LABEL,
{
layout: { view: LabelBox, dataField: defaultDataKey },
- options: { links: '@links(self)', _singleLine: true },
+ options: { _singleLine: true },
},
],
[
DocumentType.EQUATION,
{
layout: { view: EquationBox, dataField: defaultDataKey },
- options: { links: '@links(self)', nativeDimModifiable: true, fontSize: '14px', hideResizeHandles: true, hideDecorationTitle: true },
+ options: { nativeDimModifiable: true, fontSize: '14px', hideResizeHandles: true, hideDecorationTitle: true },
},
],
[
DocumentType.FUNCPLOT,
{
layout: { view: FunctionPlotBox, dataField: defaultDataKey },
- options: { nativeDimModifiable: true, links: '@links(self)' },
+ options: { nativeDimModifiable: true },
},
],
[
DocumentType.BUTTON,
{
layout: { view: LabelBox, dataField: 'onClick' },
- options: { links: '@links(self)' },
+ options: {},
},
],
[
DocumentType.SLIDER,
{
layout: { view: SliderBox, dataField: defaultDataKey },
- options: { links: '@links(self)' },
+ options: {},
},
],
[
DocumentType.PRES,
{
layout: { view: PresBox, dataField: defaultDataKey },
- options: { links: '@links(self)' },
+ options: {},
},
],
[
DocumentType.FONTICON,
{
layout: { view: FontIconBox, dataField: defaultDataKey },
- options: { allowClickBeforeDoubleClick: true, hideLinkButton: true, _width: 40, _height: 40, borderRounding: '100%', links: '@links(self)' },
+ options: { allowClickBeforeDoubleClick: true, hideLinkButton: true, _width: 40, _height: 40, borderRounding: '100%' },
},
],
[
DocumentType.WEBCAM,
{
layout: { view: RecordingBox, dataField: defaultDataKey },
- options: { links: '@links(self)' },
+ options: {},
},
],
[
@@ -607,7 +601,7 @@ export namespace Docs {
DocumentType.MARKER,
{
layout: { view: CollectionView, dataField: defaultDataKey },
- options: { links: '@links(self)', hideLinkButton: true, pointerEvents: 'none' },
+ options: { hideLinkButton: true, pointerEvents: 'none' },
},
],
[
@@ -615,21 +609,21 @@ export namespace Docs {
{
// NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method
layout: { view: InkingStroke, dataField: defaultDataKey },
- options: { links: '@links(self)' },
+ options: {},
},
],
[
DocumentType.SCREENSHOT,
{
layout: { view: ScreenshotBox, dataField: defaultDataKey },
- options: { links: '@links(self)' },
+ options: {},
},
],
[
DocumentType.COMPARISON,
{
layout: { view: ComparisonBox, dataField: defaultDataKey },
- options: { clipWidth: 50, nativeDimModifiable: true, backgroundColor: 'gray', targetDropAction: 'alias', links: '@links(self)' },
+ options: { clipWidth: 50, nativeDimModifiable: true, backgroundColor: 'gray', targetDropAction: 'alias' },
},
],
[
@@ -644,21 +638,21 @@ export namespace Docs {
DocumentType.GROUP,
{
layout: { view: EmptyBox, dataField: defaultDataKey },
- options: { links: '@links(self)' },
+ options: {},
},
],
[
DocumentType.DATAVIZ,
{
layout: { view: DataVizBox, dataField: defaultDataKey },
- options: { _fitWidth: true, nativeDimModifiable: true, links: '@links(self)' },
+ options: { _fitWidth: true, nativeDimModifiable: true },
},
],
[
DocumentType.LOADING,
{
layout: { view: LoadingBox, dataField: '' },
- options: { _fitWidth: true, _fitHeight: true, nativeDimModifiable: true, links: '@links(self)' },
+ options: { _fitWidth: true, _fitHeight: true, nativeDimModifiable: true },
},
],
]);
@@ -885,15 +879,7 @@ export namespace Docs {
}
export function AudioDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) {
- return InstanceFromProto(
- Prototypes.get(DocumentType.AUDIO),
- new AudioField(url),
- { ...options, backgroundColor: ComputedField.MakeFunction("this._mediaState === 'playing' ? 'green':'gray'") as any },
- undefined,
- undefined,
- undefined,
- overwriteDoc
- );
+ return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(url), options, undefined, undefined, undefined, overwriteDoc);
}
export function RecordingDocument(url: string, options: DocumentOptions = {}) {
@@ -996,7 +982,6 @@ export namespace Docs {
I.creationDate = new DateField();
I['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
//I['acl-Override'] = SharingPermissions.Unset;
- I.links = ComputedField.MakeFunction('links(self)');
I[Initializing] = false;
return I;
}
@@ -1260,7 +1245,7 @@ export namespace DocUtils {
return Field.toString(d[facetKey] as Field).includes(value);
});
// if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria
- if ((parentCollection?.currentFilter as Doc)?.filterBoolean === 'OR') {
+ if (parentCollection?.filterBoolean === 'OR') {
if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true;
}
// if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria
@@ -1286,38 +1271,6 @@ export namespace DocUtils {
return rangeFilteredDocs;
}
- export function Publish(promoteDoc: Doc, targetID: string, addDoc: any, remDoc: any) {
- targetID = targetID.replace(/^-/, '').replace(/\([0-9]*\)$/, '');
- DocServer.GetRefField(targetID).then(doc => {
- if (promoteDoc !== doc) {
- let copy = doc as Doc;
- if (copy) {
- Doc.Overwrite(promoteDoc, copy, true);
- } else {
- copy = Doc.MakeCopy(promoteDoc, true, targetID);
- }
- !doc && (copy.title = undefined) && (Doc.GetProto(copy).title = targetID);
- addDoc && addDoc(copy);
- remDoc && remDoc(promoteDoc);
- if (!doc) {
- DocListCastAsync(promoteDoc.links).then(links => {
- links &&
- links.map(async link => {
- if (link) {
- const a1 = await Cast(link.anchor1, Doc);
- if (a1 && Doc.AreProtosEqual(a1, promoteDoc)) link.anchor1 = copy;
- const a2 = await Cast(link.anchor2, Doc);
- if (a2 && Doc.AreProtosEqual(a2, promoteDoc)) link.anchor2 = copy;
- LinkManager.Instance.deleteLink(link);
- LinkManager.Instance.addLink(link);
- }
- });
- });
- }
- }
- });
- }
-
export function DefaultFocus(doc: Doc, options: DocFocusOptions) {
return undefined;
}
@@ -1715,7 +1668,7 @@ export namespace DocUtils {
export function LeavePushpin(doc: Doc, annotationField: string) {
if (doc.followLinkToggle) return undefined;
const context = Cast(doc.context, Doc, null) ?? Cast(doc.annotationOn, Doc, null);
- const hasContextAnchor = DocListCast(doc.links).some(l => (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) || (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context));
+ const hasContextAnchor = LinkManager.Links(doc).some(l => (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) || (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context));
if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
const pushpin = Docs.Create.FontIconDocument({
title: 'pushpin',
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index 735b06f6d..c9fcc84a3 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -2,12 +2,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast } from '../../fields/Doc';
-import { StrCast } from '../../fields/Types';
+import { Doc } from '../../fields/Doc';
+import { DocCast, StrCast } from '../../fields/Types';
import { addStyleSheet } from '../../Utils';
import { LightboxView } from '../views/LightboxView';
import { MainViewModal } from '../views/MainViewModal';
import './CaptureManager.scss';
+import { LinkManager } from './LinkManager';
import { SelectionManager } from './SelectionManager';
@observer
@@ -48,16 +49,14 @@ export class CaptureManager extends React.Component<{}> {
const doc = this._document;
const order: JSX.Element[] = [];
if (doc) {
- DocListCast(doc.links).forEach((l, i) => {
- if (l) {
- order.push(
- <div className="list-item">
- <div className="number">{i}</div>
- {StrCast((l.anchor1 as Doc).title)}
- </div>
- );
- }
- });
+ LinkManager.Links(doc).forEach((l, i) =>
+ order.push(
+ <div className="list-item">
+ <div className="number">{i}</div>
+ {StrCast(DocCast(l.anchor1)?.title)}
+ </div>
+ )
+ );
}
return (
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index a7b2c3d04..c4fb4788c 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, dropActionType } from "./DragManager";
+import { DragManager } from "./DragManager";
import { MakeTemplate } from "./DropConverter";
import { LinkManager } from "./LinkManager";
import { ScriptingGlobals } from "./ScriptingGlobals";
@@ -269,7 +269,7 @@ export class CurrentUserUtils {
{key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, _isLinkButton: true }},
{key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
// {key: "DataViz", creator: opts => Docs.Create.DataVizDocument(opts), opts: { _width: 300, _height: 300 }},
- {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true,}},
+ {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, treeViewHideUnrendered: true}},
{key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, treeViewHideTitle: true, _fitWidth:true, _chromeHidden: true, boxShadow: "0 0" }},
{key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _fitWidth: true, _backgroundGridShow: true, }},
{key: "Slide", creator: opts => Docs.Create.TreeDocument([], opts), opts: { _width: 300, _height: 200, _viewType: CollectionViewType.Tree,
@@ -949,6 +949,5 @@ ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Do
ScriptingGlobals.add(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, "creates a new presentation when called");
ScriptingGlobals.add(function openPresentation(pres:Doc) { return MainView.Instance.openPresentation(pres); }, "creates a new presentation when called");
ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); }, "creates a new folder in myFiles when called");
-ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, "returns all the links to the document or its annotations", "(doc: any)");
ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; }); \ No newline at end of file
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index c670d0ab8..f2c554866 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -286,7 +286,7 @@ export class DocumentManager {
docView.rootDoc.layoutKey === 'layout_icon' ? await new Promise<void>(res => docView.iconify(res)) : undefined;
docView.props.focus(docView.rootDoc, options); // focus the view within its container
const { childDocView, viewSpec } = await iterator(docView);
- if (!childDocView) return { viewSpec: viewSpec ?? docView.rootDoc, docView, contextView };
+ if (!childDocView) return { viewSpec: options.anchorDoc ?? viewSpec ?? docView.rootDoc, docView, contextView };
contextView = docView;
docView = childDocView;
}
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index c9a178db7..eacbcc0e3 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -6,6 +6,7 @@ import { DocumentDecorations } from '../views/DocumentDecorations';
import { DocFocusOptions, DocumentViewSharedProps, OpenWhere } from '../views/nodes/DocumentView';
import { PresBox } from '../views/nodes/trails';
import { DocumentManager } from './DocumentManager';
+import { LinkManager } from './LinkManager';
import { SelectionManager } from './SelectionManager';
import { UndoManager } from './UndoManager';
@@ -41,7 +42,7 @@ export class LinkFollower {
};
public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, finished?: () => void, traverseBacklink?: boolean) {
- const linkDocs = link ? [link] : DocListCast(sourceDoc.links);
+ const linkDocs = link ? [link] : LinkManager.Links(sourceDoc);
const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1
const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor2
const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews((d.anchor2 as Doc).type === DocumentType.MARKER ? DocCast((d.anchor2 as Doc).annotationOn) : (d.anchor2 as Doc)).length === 0);
@@ -76,7 +77,6 @@ export class LinkFollower {
easeFunc: StrCast(sourceDoc.followLinkEase, 'ease') as any,
openLocation: StrCast(sourceDoc.followLinkLocation, OpenWhere.lightbox),
effect: sourceDoc,
- originatingDoc: sourceDoc,
zoomTextSelections: BoolCast(sourceDoc.followLinkZoomText),
};
if (target.type === DocumentType.PRES) {
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 46d44fea4..555215417 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -4,6 +4,7 @@ import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field, Opt } from '
import { List } from '../../fields/List';
import { ProxyField } from '../../fields/Proxy';
import { Cast, DocCast, PromiseValue, StrCast } from '../../fields/Types';
+import { ScriptingGlobals } from './ScriptingGlobals';
/*
* link doc:
* - anchor1: doc
@@ -24,6 +25,10 @@ export class LinkManager {
public static get Instance() {
return LinkManager._instance;
}
+
+ public static Links(doc: Doc | undefined) {
+ return doc ? LinkManager.Instance.getAllRelatedLinks(doc) : [];
+ }
public static addLinkDB = async (linkDb: any) => {
await Promise.all(
((await DocListCastAsync(linkDb.data)) ?? []).map(link =>
@@ -34,7 +39,7 @@ export class LinkManager {
LinkManager.userLinkDBs.push(linkDb);
};
public static AutoKeywords = 'keywords:Usages';
- static links: Doc[] = [];
+ static _links: Doc[] = [];
constructor() {
LinkManager._instance = this;
this.createLinkrelationshipLists();
@@ -72,7 +77,7 @@ export class LinkManager {
);
};
const watchUserLinkDB = (userLinkDBDoc: Doc) => {
- LinkManager.links.push(...DocListCast(userLinkDBDoc.data));
+ LinkManager._links.push(...DocListCast(userLinkDBDoc.data));
const toRealField = (field: Field) => (field instanceof ProxyField ? field.value : field); // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields
if (userLinkDBDoc.data) {
observe(
@@ -193,3 +198,11 @@ export class LinkManager {
if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc;
}
}
+
+ScriptingGlobals.add(
+ function links(doc: any) {
+ return new List(LinkManager.Links(doc));
+ },
+ 'returns all the links to the document or its annotations',
+ '(doc: any)'
+);
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 646942569..c0fc25376 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,6 +1,6 @@
import { action, observable, ObservableMap } from 'mobx';
import { computedFn } from 'mobx-utils';
-import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { Doc, Opt } from '../../fields/Doc';
import { DocCast } from '../../fields/Types';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { DocumentView } from '../views/nodes/DocumentView';
@@ -22,7 +22,7 @@ export namespace SelectionManager {
// if doc is not in SelectedDocuments, add it
if (!manager.SelectedViews.get(docView) && docView.props.Document.type !== DocumentType.MARKER) {
if (!ctrlPressed) {
- if (LinkManager.currentLink && !DocListCast(docView.rootDoc.links).includes(LinkManager.currentLink) && docView.rootDoc !== LinkManager.currentLink) {
+ if (LinkManager.currentLink && !LinkManager.Links(docView.rootDoc).includes(LinkManager.currentLink) && docView.rootDoc !== LinkManager.currentLink) {
LinkManager.currentLink = undefined;
}
this.DeselectAll();
diff --git a/src/client/views/FilterPanel.scss b/src/client/views/FilterPanel.scss
new file mode 100644
index 000000000..7f907c8d4
--- /dev/null
+++ b/src/client/views/FilterPanel.scss
@@ -0,0 +1,189 @@
+.filterBox-flyout {
+ display: block;
+ text-align: left;
+ font-weight: 100;
+
+ .filterBox-flyout-facet {
+ background-color: white;
+ text-align: left;
+ display: inline-block;
+ position: relative;
+ width: 100%;
+
+ .filterBox-flyout-facet-check {
+ margin-right: 6px;
+ }
+ }
+}
+
+.filter-bookmark {
+ //display: flex;
+
+ .filter-bookmark-icon {
+ float: right;
+ margin-right: 10px;
+ margin-top: 7px;
+ }
+}
+
+// .filterBox-bottom {
+// // position: fixed;
+// // bottom: 0;
+// // width: 100%;
+// }
+
+.filterBox-select {
+ // width: 90%;
+ margin-top: 5px;
+ // margin-bottom: 15px;
+}
+
+.filterBox-saveBookmark {
+ background-color: #e9e9e9;
+ border-radius: 11px;
+ padding-left: 8px;
+ padding-right: 8px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ margin: 8px;
+ display: flex;
+ font-size: 11px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: white;
+ }
+
+ .filterBox-saveBookmark-icon {
+ margin-right: 6px;
+ margin-top: 4px;
+ margin-left: 2px;
+ }
+}
+
+.filterBox-select-scope,
+.filterBox-select-bool,
+.filterBox-addWrapper,
+.filterBox-select-matched,
+.filterBox-saveWrapper {
+ font-size: 10px;
+ justify-content: center;
+ justify-items: center;
+ padding-bottom: 10px;
+ display: flex;
+}
+
+.filterBox-addWrapper {
+ font-size: 11px;
+ width: 100%;
+}
+
+.filterBox-saveWrapper {
+ width: 100%;
+}
+
+// .filterBox-top {
+// padding-bottom: 20px;
+// border-bottom: 2px solid black;
+// position: fixed;
+// top: 0;
+// width: 100%;
+// }
+
+.filterBox-select-scope {
+ padding-bottom: 20px;
+ border-bottom: 2px solid black;
+}
+
+.filterBox-title {
+ font-size: 15;
+ // border: 2px solid black;
+ width: 100%;
+ align-self: center;
+ text-align: center;
+ background-color: #d3d3d3;
+}
+
+.filterBox-select-bool {
+ margin-top: 6px;
+}
+
+.filterBox-select-text {
+ margin-right: 8px;
+ margin-left: 8px;
+ margin-top: 3px;
+}
+
+.filterBox-select-box {
+ margin-right: 2px;
+ font-size: 30px;
+ border: 0;
+ background: transparent;
+}
+
+.filterBox-selection {
+ border-radius: 6px;
+ border: none;
+ background-color: #e9e9e9;
+ padding: 2px;
+
+ &:hover {
+ background-color: white;
+ }
+}
+
+.filterBox-addFilter {
+ width: 120px;
+ background-color: #e9e9e9;
+ border-radius: 12px;
+ padding: 5px;
+ margin: 5px;
+ display: flex;
+ text-align: center;
+ justify-content: center;
+
+ &:hover {
+ background-color: white;
+ }
+}
+
+.filterBox-treeView {
+ display: flex;
+ flex-direction: column;
+ width: 200px;
+ position: absolute;
+ right: 0;
+ top: 0;
+ z-index: 1;
+ background-color: #9f9f9f;
+
+ .filterBox-tree {
+ z-index: 0;
+ }
+
+ .filterBox-addfacet {
+ display: inline-block;
+ width: 200px;
+ height: 30px;
+ text-align: left;
+
+ .filterBox-addFacetButton {
+ display: flex;
+ margin: auto;
+ cursor: pointer;
+ }
+
+ > div,
+ > div > div {
+ width: 100%;
+ height: 100%;
+ }
+ }
+
+ .filterBox-tree {
+ display: inline-block;
+ width: 100%;
+ margin-bottom: 10px;
+ //height: calc(100% - 30px);
+ }
+}
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
new file mode 100644
index 000000000..d35494f26
--- /dev/null
+++ b/src/client/views/FilterPanel.tsx
@@ -0,0 +1,232 @@
+import React = require('react');
+import { action, computed, observable, ObservableMap } from 'mobx';
+import { observer } from 'mobx-react';
+import Select from 'react-select';
+import { Doc, DocListCast, Field, StrListCast } from '../../fields/Doc';
+import { RichTextField } from '../../fields/RichTextField';
+import { StrCast } from '../../fields/Types';
+import { DocumentManager } from '../util/DocumentManager';
+import { UserOptions } from '../util/GroupManager';
+import './FilterPanel.scss';
+import { FieldView } from './nodes/FieldView';
+import { SearchBox } from './search/SearchBox';
+
+interface filterProps {
+ rootDoc: Doc;
+}
+@observer
+export class FilterPanel extends React.Component<filterProps> {
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(FilterPanel, fieldKey);
+ }
+
+ /**
+ * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
+ */
+ @computed get targetDoc() {
+ return this.props.rootDoc;
+ }
+ @computed get targetDocChildKey() {
+ const targetView = DocumentManager.Instance.getFirstDocumentView(this.targetDoc);
+ return targetView?.ComponentView?.annotationKey ?? targetView?.ComponentView?.fieldKey ?? 'data';
+ }
+ @computed get targetDocChildren() {
+ return DocListCast(this.targetDoc?.[this.targetDocChildKey] || Doc.ActiveDashboard?.data);
+ }
+
+ @computed get allDocs() {
+ const allDocs = new Set<Doc>();
+ const targetDoc = this.targetDoc;
+ if (targetDoc) {
+ SearchBox.foreachRecursiveDoc([this.targetDoc], (depth, doc) => allDocs.add(doc));
+ }
+ return Array.from(allDocs);
+ }
+
+ @computed get _allFacets() {
+ // trace();
+ const noviceReqFields = ['author', 'tags', 'text', 'type'];
+ const noviceLayoutFields: string[] = []; //["_curPage"];
+ const noviceFields = [...noviceReqFields, ...noviceLayoutFields];
+
+ const keys = new Set<string>(noviceFields);
+ this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key)));
+ return Array.from(keys.keys())
+ .filter(key => key[0])
+ .filter(key => key[0] === '#' || key.indexOf('lastModified') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('_')) || noviceFields.includes(key) || !Doc.noviceMode)
+ .sort();
+ }
+
+ /**
+ * The current attributes selected to filter based on
+ */
+ @computed get activeFilters() {
+ return StrListCast(this.targetDoc?._docFilters);
+ }
+
+ /**
+ * @returns a string array of the current attributes
+ */
+ @computed get currentFacets() {
+ return this.activeFilters.map(filter => filter.split(':')[0]);
+ }
+
+ gatherFieldValues(childDocs: Doc[], facetKey: string) {
+ const valueSet = new Set<string>();
+ let rtFields = 0;
+ let subDocs = childDocs;
+ if (subDocs.length > 0) {
+ let newarray: Doc[] = [];
+ while (subDocs.length > 0) {
+ newarray = [];
+ subDocs.forEach(t => {
+ const facetVal = t[facetKey];
+ if (facetVal instanceof RichTextField || typeof facetVal === 'string') rtFields++;
+ facetVal !== undefined && valueSet.add(Field.toString(facetVal as Field));
+ (facetVal === true || facetVal == false) && valueSet.add(Field.toString(!facetVal));
+ const fieldKey = Doc.LayoutFieldKey(t);
+ const annos = !Field.toString(Doc.LayoutField(t) as Field).includes('CollectionView');
+ DocListCast(t[annos ? fieldKey + '-annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
+ });
+ subDocs = newarray;
+ }
+ }
+ // }
+ // });
+ return { strings: Array.from(valueSet.keys()), rtFields };
+ }
+
+ public removeFilter = (filterName: string) => {
+ Doc.setDocFilter(this.targetDoc, filterName, undefined, 'remove');
+ Doc.setDocRangeFilter(this.targetDoc, filterName, undefined);
+ };
+
+ @observable _chosenFacets = new ObservableMap<string, 'text' | 'checkbox' | 'slider' | 'range'>();
+ @computed get activeFacets() {
+ const facets = new Map<string, 'text' | 'checkbox' | 'slider' | 'range'>(this._chosenFacets);
+ StrListCast(this.targetDoc?._docFilters).map(filter => facets.set(filter.split(':')[0], filter.split(':')[2] === 'match' ? 'text' : 'checkbox'));
+ setTimeout(() => StrListCast(this.targetDoc?._docFilters).map(action(filter => this._chosenFacets.set(filter.split(':')[0], filter.split(':')[2] === 'match' ? 'text' : 'checkbox'))));
+ return facets;
+ }
+ /**
+ * Responds to clicking the check box in the flyout menu
+ */
+ @action
+ facetClick = (facetHeader: string) => {
+ if (!this.targetDoc) return;
+ const allCollectionDocs = this.targetDocChildren;
+ const facetValues = this.gatherFieldValues(this.targetDocChildren, facetHeader);
+
+ let nonNumbers = 0;
+ let minVal = Number.MAX_VALUE,
+ maxVal = -Number.MAX_VALUE;
+ facetValues.strings.map(val => {
+ const num = val ? Number(val) : Number.NaN;
+ if (Number.isNaN(num)) {
+ val && nonNumbers++;
+ } else {
+ minVal = Math.min(num, minVal);
+ maxVal = Math.max(num, maxVal);
+ }
+ });
+ if (facetHeader === 'text' || (facetValues.rtFields / allCollectionDocs.length > 0.1 && facetValues.rtFields > 20)) {
+ this._chosenFacets.set(facetHeader, 'text');
+ } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) {
+ } else {
+ this._chosenFacets.set(facetHeader, 'checkbox');
+ }
+ };
+
+ facetValues = (facetHeader: string) => {
+ const allCollectionDocs = new Set<Doc>();
+ SearchBox.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
+ const set = new Set<string>();
+ if (facetHeader === 'tags')
+ allCollectionDocs.forEach(child =>
+ Field.toString(child[facetHeader] as Field)
+ .split(':')
+ .forEach(key => set.add(key))
+ );
+ else
+ allCollectionDocs.forEach(child => {
+ const fieldVal = child[facetHeader] as Field;
+ set.add(Field.toString(fieldVal));
+ (fieldVal === true || fieldVal === false) && set.add((!fieldVal).toString());
+ });
+ const facetValues = Array.from(set).filter(v => v);
+
+ let nonNumbers = 0;
+
+ facetValues.map(val => Number.isNaN(Number(val)) && nonNumbers++);
+ const facetValueDocSet = (nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue => {
+ return facetValue;
+ });
+ return facetValueDocSet;
+ };
+
+ render() {
+ const options = this._allFacets.filter(facet => this.currentFacets.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet }));
+ return (
+ <div className="filterBox-treeView" style={{ position: 'relative', width: '100%' }}>
+ <div className="filterBox-select-bool">
+ <select className="filterBox-selection" onChange={action(e => this.targetDoc && (this.targetDoc._filterBoolean = (e.target as any).value))} defaultValue={StrCast(this.targetDoc?.filterBoolean)}>
+ {['AND', 'OR'].map(bool => (
+ <option value={bool} key={bool}>
+ {bool}
+ </option>
+ ))}
+ </select>
+ <div className="filterBox-select-text">filters together</div>
+ </div>
+
+ <div className="filterBox-select">
+ <Select placeholder="Add a filter..." options={options} isMulti={false} onChange={val => this.facetClick((val as UserOptions).value)} onKeyDown={e => e.stopPropagation()} value={null} closeMenuOnSelect={true} />
+ </div>
+
+ <div className="filterBox-tree" key="tree" style={{ overflow: 'auto' }}>
+ {Array.from(this.activeFacets.keys()).map(facetHeader => (
+ <div>
+ {facetHeader}
+ {this.displayFacetValueFilterUIs(this.activeFacets.get(facetHeader), facetHeader)}
+ </div>
+ ))}
+ </div>
+ </div>
+ );
+ }
+
+ private displayFacetValueFilterUIs(type: string | undefined, facetHeader: string): React.ReactNode {
+ switch (type) {
+ case 'text':
+ return (
+ <input
+ placeholder={
+ StrListCast(this.targetDoc._docFilters)
+ .find(filter => filter.split(':')[0] === facetHeader)
+ ?.split(':')[1] ?? '-empty-'
+ }
+ onKeyDown={e => e.key === 'Enter' && Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match')}
+ />
+ );
+ case 'checkbox':
+ return this.facetValues(facetHeader).map(fval => {
+ const facetValue = fval;
+ return (
+ <div>
+ <input
+ style={{ width: 20, marginLeft: 20 }}
+ checked={
+ StrListCast(this.targetDoc._docFilters)
+ .find(filter => filter.split(':')[0] === facetHeader && filter.split(':')[1] == facetValue)
+ ?.split(':')[2] === 'check'
+ }
+ type={type}
+ onChange={e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove')}
+ />
+ {facetValue}
+ </div>
+ );
+ });
+ }
+ }
+}
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 2567d44bb..976c8763e 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -4,9 +4,8 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { InkTool } from '../../fields/InkField';
-import { List } from '../../fields/List';
-import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils';
+import { Cast, NumCast, StrCast } from '../../fields/Types';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../Utils';
import { DocUtils } from '../documents/Documents';
import { DocumentManager } from '../util/DocumentManager';
import { LinkManager } from '../util/LinkManager';
@@ -88,7 +87,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
...future
.slice()
.sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow))
- .sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length),
+ .sort((a, b) => LinkManager.Links(a).length - LinkManager.Links(b).length),
];
}
this._doc = doc;
@@ -214,7 +213,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
if (coll) {
const fieldKey = Doc.LayoutFieldKey(coll);
const contents = [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + '-annotations'])];
- const links = DocListCast(coll.links)
+ const links = LinkManager.Links(coll)
.map(link => LinkManager.getOppositeAnchor(link, coll))
.filter(doc => doc)
.map(doc => doc!);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index e1ba5943d..945cd61db 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -553,8 +553,6 @@ export class MainView extends React.Component {
Doc.MyTrails && (Doc.ActivePresentation = pres);
Doc.AddDocToList(Doc.MyTrails, 'data', pres);
this.closeFlyout();
- } else {
- PresBox.NavigateToDoc(DocCast(pres.presentationTargetDoc), pres);
}
};
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index ac1b66013..ebbe20077 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -9,8 +9,10 @@ import { RichTextField } from '../../fields/RichTextField';
import { ScriptField } from '../../fields/ScriptField';
import { BoolCast, ScriptCast, StrCast } from '../../fields/Types';
import { ImageField } from '../../fields/URLField';
+import { Utils } from '../../Utils';
import { DocUtils } from '../documents/Documents';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
+import { LinkManager } from '../util/LinkManager';
import { SelectionManager } from '../util/SelectionManager';
import { undoBatch } from '../util/UndoManager';
import { Colors } from './global/globalEnums';
@@ -19,8 +21,6 @@ import { DocumentView, OpenWhere } from './nodes/DocumentView';
import { pasteImageBitmap } from './nodes/WebBoxRenderer';
import './PropertiesButtons.scss';
import React = require('react');
-import { LinkManager } from '../util/LinkManager';
-import { Utils } from '../../Utils';
const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -139,9 +139,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]);
dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
- containerContents.forEach(doc => {
- DocListCast(doc.links).forEach(link => (link.linkDisplay = false));
- });
+ containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.linkDisplay = false)));
});
}
);
@@ -330,7 +328,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
const click = () => this.handleOptionChange(value[0]);
const linkButton = BoolCast(this.selectedDoc._isLinkButton);
const followLoc = this.selectedDoc._followLinkLocation;
- const linkedToLightboxView = () => DocListCast(this.selectedDoc.links).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox);
+ const linkedToLightboxView = () => LinkManager.Links(this.selectedDoc).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox);
let active = false;
// prettier-ignore
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index e750dc43a..03b4100a7 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -4,10 +4,10 @@ import { faAnchor, faArrowRight, faWindowMaximize } from '@fortawesome/free-soli
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Checkbox, Tooltip } from '@material-ui/core';
import { intersection } from 'lodash';
-import { action, autorun, computed, Lambda, observable } from 'mobx';
+import { action, computed, Lambda, observable } from 'mobx';
import { observer } from 'mobx-react';
import { ColorState, SketchPicker } from 'react-color';
-import { AclAdmin, AclSym, HierarchyMapping, DataSym, Doc, DocListCast, Field, HeightSym, NumListCast, Opt, StrListCast, WidthSym } from '../../fields/Doc';
+import { AclAdmin, AclSym, DataSym, Doc, Field, HeightSym, HierarchyMapping, NumListCast, Opt, StrListCast, WidthSym } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { InkField } from '../../fields/InkField';
import { List } from '../../fields/List';
@@ -23,10 +23,10 @@ import { SharingManager } from '../util/SharingManager';
import { Transform } from '../util/Transform';
import { undoBatch, UndoManager } from '../util/UndoManager';
import { EditableView } from './EditableView';
+import { FilterPanel } from './FilterPanel';
import { Colors } from './global/globalEnums';
import { InkStrokeProperties } from './InkStrokeProperties';
import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView';
-import { FilterBox } from './nodes/FilterBox';
import { KeyValueBox } from './nodes/KeyValueBox';
import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
import { PropertiesButtons } from './PropertiesButtons';
@@ -76,13 +76,13 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@observable openOptions: boolean = true;
@observable openSharing: boolean = true;
- @observable openFields: boolean = true;
+ @observable openFields: boolean = false;
@observable openLayout: boolean = false;
@observable openContexts: boolean = true;
@observable openLinks: boolean = true;
@observable openAppearance: boolean = true;
@observable openTransform: boolean = true;
- @observable openFilters: boolean = true; // should be false
+ @observable openFilters: boolean = false;
/**
* autorun to set up the filter doc of a collection if that collection has been selected and the filters panel is open
@@ -94,7 +94,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@observable layoutDocAcls: boolean = false;
//Pres Trails booleans:
- @observable openPresTransitions: boolean = false;
+ @observable openPresTransitions: boolean = true;
+ @observable openPresProgressivize: boolean = false;
+ @observable openPresVisibilityAndDuration: boolean = false;
@observable openAddSlide: boolean = false;
@observable openSlideOptions: boolean = false;
@@ -104,7 +106,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
componentDidMount() {
this.selectedDocListenerDisposer?.();
- this.selectedDocListenerDisposer = autorun(() => this.openFilters && this.selectedDoc && this.checkFilterDoc());
+ // this.selectedDocListenerDisposer = autorun(() => this.openFilters && this.selectedDoc && this.checkFilterDoc());
}
componentWillUnmount() {
@@ -1136,37 +1138,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
/**
- * Checks if a currentFilter (FilterDoc) exists on the current collection (if the Properties Panel + Filters submenu are open).
- * If it doesn't exist, it creates it.
- */
- checkFilterDoc() {
- if (!this.selectedDoc?.currentFilter) this.selectedDoc!.currentFilter = FilterBox.createFilterDoc();
- }
-
- /**
- * Creates a new currentFilter for this.filterDoc,
- */
- createNewFilterDoc = () => {
- if (this.selectedDoc) {
- const curFilterDoc = DocCast(this.selectedDoc.currentFilter);
- const currentDocFilters = this.selectedDoc._docFilters;
- const currentDocRangeFilters = this.selectedDoc._docRangeFilters;
- this.selectedDoc._docFilters = new List<string>();
- this.selectedDoc._docRangeFilters = new List<string>();
- if (DocListCast(Doc.UserDoc().savedFilters).includes(curFilterDoc)) {
- curFilterDoc._docFiltersList = currentDocFilters;
- curFilterDoc._docRangeFiltersList = currentDocRangeFilters;
- this.selectedDoc.currentFilter = FilterBox.createFilterDoc();
- } else {
- Doc.GetProto(curFilterDoc).data = undefined;
- Doc.GetProto(curFilterDoc).title = 'Unnamed Filter';
- curFilterDoc._docFiltersList = undefined;
- curFilterDoc._docRangeFiltersList = undefined;
- }
- }
- };
-
- /**
* Updates this.filterDoc's currentFilter and saves the docFilters on the currentFilter
*/
updateFilterDoc = (doc: Doc) => {
@@ -1191,7 +1162,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
};
@computed get filtersSubMenu() {
- return !(this.selectedDoc?.currentFilter instanceof Doc) ? null : (
+ return (
<div className="propertiesView-filters">
<div className="propertiesView-filters-title" onPointerDown={action(() => (this.openFilters = !this.openFilters))} style={{ backgroundColor: this.openFilters ? 'black' : '' }}>
Filters
@@ -1200,35 +1171,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
</div>
</div>
{!this.openFilters ? null : (
- <div className="propertiesView-filters-content" style={{ height: this.selectedDoc.currentFilter[HeightSym]() + 15 }}>
- <DocumentView
- Document={this.selectedDoc.currentFilter}
- DataDoc={undefined}
- addDocument={undefined}
- addDocTab={returnFalse}
- pinToPres={emptyFunction}
- rootSelected={returnTrue}
- removeDocument={returnFalse}
- ScreenToLocalTransform={this.getTransform}
- PanelWidth={() => this.props.width}
- PanelHeight={this.selectedDoc.currentFilter[HeightSym]}
- renderDepth={0}
- scriptContext={this.selectedDoc.currentFilter}
- focus={emptyFunction}
- styleProvider={DefaultStyleProvider}
- isContentActive={returnTrue}
- whenChildContentsActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- createNewFilterDoc={this.createNewFilterDoc}
- updateFilterDoc={this.updateFilterDoc}
- docViewPath={returnEmptyDoclist}
- dontCenter="y"
- />
+ <div className="propertiesView-filters-content" style={{ position: 'relative', height: 'auto' }}>
+ <FilterPanel rootDoc={this.selectedDoc ?? Doc.ActiveDashboard!} />
</div>
)}
</div>
@@ -1542,7 +1486,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
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!));
+ const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(LinkManager.currentLink!);
if (!this.selectedDoc && !this.isPres) {
return (
<div className="propertiesView" style={{ width: this.props.width }}>
@@ -1798,7 +1742,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
if (this.isPres) {
const selectedItem: boolean = PresBox.Instance?.selectedArray.size > 0;
- const type = PresBox.Instance.activeItem?.type;
+ const type = [DocumentType.AUDIO, DocumentType.VID].includes(DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType)
+ ? (DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType)
+ : PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type;
return (
<div className="propertiesView" style={{ width: this.props.width }}>
<div className="propertiesView-title" style={{ width: this.props.width }}>
@@ -1822,6 +1768,31 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
{this.openPresTransitions ? <div className="propertiesView-presTrails-content">{PresBox.Instance.transitionDropdown}</div> : null}
</div>
)}
+ {!selectedItem ? null : (
+ <div className="propertiesView-presTrails">
+ <div
+ className="propertiesView-presTrails-title"
+ onPointerDown={action(() => (this.openPresVisibilityAndDuration = !this.openPresVisibilityAndDuration))}
+ style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}>
+ &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Visibilty
+ <div className="propertiesView-presTrails-title-icon">
+ <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openPresVisibilityAndDuration ? <div className="propertiesView-presTrails-content">{PresBox.Instance.visibiltyDurationDropdown}</div> : null}
+ </div>
+ )}
+ {!selectedItem ? null : (
+ <div className="propertiesView-presTrails">
+ <div className="propertiesView-presTrails-title" onPointerDown={action(() => (this.openPresProgressivize = !this.openPresProgressivize))} style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}>
+ &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Progressivize
+ <div className="propertiesView-presTrails-title-icon">
+ <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openPresProgressivize ? <div className="propertiesView-presTrails-content">{PresBox.Instance.progressivizeDropdown}</div> : null}
+ </div>
+ )}
{!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? null : (
<div className="propertiesView-presTrails">
<div className="propertiesView-presTrails-title" onPointerDown={action(() => (this.openSlideOptions = !this.openSlideOptions))} style={{ backgroundColor: this.openSlideOptions ? 'black' : '' }}>
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 74ea624a6..7519cbb05 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -3,10 +3,12 @@ import { observer } from 'mobx-react';
import { Doc, DocListCast, Field, FieldResult, StrListCast } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
+import { RichTextField } from '../../fields/RichTextField';
import { DocCast, NumCast, StrCast } from '../../fields/Types';
import { emptyFunction, OmitKeys, returnAll, returnOne, returnTrue, returnZero } from '../../Utils';
import { Docs, DocUtils } from '../documents/Documents';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
+import { LinkManager } from '../util/LinkManager';
import { Transform } from '../util/Transform';
import { CollectionStackingView } from './collections/CollectionStackingView';
import { FieldViewProps } from './nodes/FieldView';
@@ -15,7 +17,6 @@ import { SearchBox } from './search/SearchBox';
import './SidebarAnnos.scss';
import { StyleProp } from './StyleProvider';
import React = require('react');
-import { RichTextField } from '../../fields/RichTextField';
interface ExtraProps {
fieldKey: string;
@@ -174,8 +175,8 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
showTitle = () => 'title';
setHeightCallback = (height: number) => this.props.setHeight?.(height + this.filtersHeight());
sortByLinkAnchorY = (a: Doc, b: Doc) => {
- const ay = DocListCast(a.links).length && DocCast(DocListCast(a.links)[0].anchor1).y;
- const by = DocListCast(b.links).length && DocCast(DocListCast(b.links)[0].anchor1).y;
+ const ay = LinkManager.Links(a).length && DocCast(LinkManager.Links(a)[0].anchor1).y;
+ const by = LinkManager.Links(b).length && DocCast(LinkManager.Links(b)[0].anchor1).y;
return NumCast(ay) - NumCast(by);
};
render() {
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 153c30052..ce764c7bf 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -1,12 +1,15 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Shadows } from 'browndash-components';
import { action, runInAction } from 'mobx';
import { extname } from 'path';
-import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { BoolCast, Cast, DocCast, ImageCast, NumCast, StrCast } from '../../fields/Types';
+import { Doc, Opt } from '../../fields/Doc';
+import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../fields/Types';
import { DashColor, emptyFunction, lightOrDark } from '../../Utils';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { DocFocusOrOpen } from '../util/DocumentManager';
+import { LinkManager } from '../util/LinkManager';
+import { SelectionManager } from '../util/SelectionManager';
import { ColorScheme } from '../util/SettingsManager';
import { undoBatch, UndoManager } from '../util/UndoManager';
import { TreeSort } from './collections/TreeView';
@@ -18,8 +21,6 @@ import { FieldViewProps } from './nodes/FieldView';
import { SliderBox } from './nodes/SliderBox';
import './StyleProvider.scss';
import React = require('react');
-import { Shadows } from 'browndash-components';
-import { SelectionManager } from '../util/SelectionManager';
export enum StyleProp {
TreeViewIcon = 'treeViewIcon',
@@ -261,7 +262,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
case StyleProp.BoxShadow: {
if (!doc || opacity() === 0 || doc.noShadow) return undefined; // if it's not visible, then no shadow)
if (doc.boxShadow === 'standard') return Shadows.STANDARD_SHADOW;
- if (doc?.isLinkButton && DocListCast(doc?.links).length && ![DocumentType.LINK, DocumentType.INK].includes(doc.type as any)) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
+ if (doc?.isLinkButton && LinkManager.Links(doc).length && ![DocumentType.LINK, DocumentType.INK].includes(doc.type as any)) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
switch (doc?.type) {
case DocumentType.COL:
return StrCast(
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 6035871cd..fbf7db892 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -54,7 +54,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders?.find(sh => sh.heading === 'unset'));
if (needsUnsetCategory || columnHeaders === undefined || columnHeaders.length === 0) {
setTimeout(() => {
- const columnHeaders = Array.from(Cast(this.dataDoc.columnHeaders, listSpec(SchemaHeaderField), null));
+ const columnHeaders = Array.from(Cast(this.dataDoc.columnHeaders, listSpec(SchemaHeaderField), null) ?? []);
const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders?.find(sh => sh.heading === 'unset'));
if (needsUnsetCategory || columnHeaders.length === 0) {
columnHeaders.push(new SchemaHeaderField('unset', undefined, undefined, 1));
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 572fbfec2..5bdff347c 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -2,7 +2,7 @@ import React = require('react');
import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
-import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
+import { Doc, Opt, StrListCast } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
@@ -15,6 +15,7 @@ import { DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager } from '../../util/DragManager';
import { LinkFollower } from '../../util/LinkFollower';
+import { LinkManager } from '../../util/LinkManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
@@ -740,14 +741,14 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
() => this.props.currentTimecode(),
time => {
const dictationDoc = Cast(this.props.layoutDoc['data-dictation'], Doc, null);
- const isDictation = dictationDoc && DocListCast(this.props.mark.links).some(link => Cast(link.anchor1, Doc, null)?.annotationOn === dictationDoc);
+ const isDictation = dictationDoc && LinkManager.Links(this.props.mark).some(link => Cast(link.anchor1, Doc, null)?.annotationOn === dictationDoc);
if (
!LightboxView.LightboxDoc &&
// bcz: when should links be followed? we don't want to move away from the video to follow a link but we can open it in a sidebar/etc. But we don't know that upfront.
// for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video.
/*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this.props.layoutDoc))*/
!this.props.layoutDoc.dontAutoFollowLinks &&
- DocListCast(this.props.mark.links).length &&
+ LinkManager.Links(this.props.mark).length &&
time > NumCast(this.props.mark[this.props.startTag]) &&
time < NumCast(this.props.mark[this.props.endTag]) &&
this._lastTimecode < NumCast(this.props.mark[this.props.startTag]) - 1e-5
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index c88ae314e..e46220f02 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,5 +1,4 @@
import { action, computed, observable } from 'mobx';
-import ReactLoading from 'react-loading';
import * as rp from 'request-promise';
import CursorField from '../../../fields/CursorField';
import { AclPrivate, Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 330e116d1..553967b95 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -38,8 +38,8 @@ export type collectionTreeViewProps = {
onCheckedClick?: () => ScriptField;
onChildClick?: () => ScriptField;
// TODO: [AL] add these fields
- AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
- RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+ AddToMap?: (treeViewDoc: Doc, index: number[]) => void;
+ RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
hierarchyIndex?: number[];
};
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 316f1e4e9..48e5748a0 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -64,8 +64,8 @@ interface CollectionViewProps_ extends FieldViewProps {
childClickScript?: ScriptField;
childDoubleClickScript?: ScriptField;
//TODO: [AL] add these fields
- AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
- RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+ AddToMap?: (treeViewDoc: Doc, index: number[]) => void;
+ RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views)
}
export interface CollectionViewProps extends React.PropsWithChildren<CollectionViewProps_> {}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 99283996e..9459aaf1e 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -263,9 +263,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
pinDoc.treeViewFieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
pinDoc.treeViewExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
pinDoc.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
- const presArray: Doc[] = PresBox.Instance?.sortArray();
- const size: number = PresBox.Instance?.selectedArray.size;
- const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null);
if (!pinProps?.audioRange && duration !== undefined) {
@@ -274,10 +271,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
pinDoc.presStartTime = NumCast(doc.clipStart);
pinDoc.presEndTime = NumCast(doc.clipEnd, duration);
}
- //PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, anchorDoc && anchorDoc !== doc && !anchorDoc.unrendered ? anchorDoc : doc);
- pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)');
- Doc.AddDocToList(curPres, 'data', pinDoc, presSelected);
- //save position
if (pinProps?.activeFrame !== undefined) {
pinDoc.presActiveFrame = pinProps?.activeFrame;
pinDoc.title = doc.title + ' (move)';
@@ -294,6 +287,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
pinDoc.presMovement = PresMovement.None;
}
if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
+ Doc.AddDocToList(curPres, 'data', pinDoc, PresBox.Instance?.sortArray()?.lastElement());
PresBox.Instance?.clearSelectedArray();
pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); //Update selected array
});
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 0d2968ba1..2bdcf472f 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -1,6 +1,6 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
@@ -8,13 +8,14 @@ import { List } from '../../../fields/List';
import { RichTextField } from '../../../fields/RichTextField';
import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType } from '../../util/DragManager';
+import { LinkManager } from '../../util/LinkManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
@@ -32,7 +33,6 @@ import { CollectionTreeView, TreeViewType } from './CollectionTreeView';
import { CollectionView } from './CollectionView';
import './TreeView.scss';
import React = require('react');
-import { render } from 'react-dom';
export interface TreeViewProps {
treeView: CollectionTreeView;
@@ -66,8 +66,8 @@ export interface TreeViewProps {
skipFields?: string[];
firstLevel: boolean;
// TODO: [AL] add these
- AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
- RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+ AddToMap?: (treeViewDoc: Doc, index: number[]) => void;
+ RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
hierarchyIndex?: number[];
}
@@ -169,23 +169,6 @@ export class TreeView extends React.Component<TreeViewProps> {
@computed get selected() {
return SelectionManager.IsSelected(this._docRef);
}
- // SelectionManager.Views().lastElement()?.props.Document === this.props.document; }
-
- @observable _presTimer!: NodeJS.Timeout;
- @observable _presKeyEventsActive: boolean = false;
-
- @observable _selectedArray: ObservableMap = new ObservableMap<Doc, any>();
- // the selected item's index
- @computed get itemIndex() {
- return NumCast(this.doc._itemIndex);
- }
- // the item that's active
- @computed get activeItem() {
- return this.childDocs ? Cast(this.childDocs[NumCast(this.doc._itemIndex)], Doc, null) : undefined;
- }
- @computed get targetDoc() {
- return Cast(this.activeItem?.presentationTargetDoc, Doc, null);
- }
childDocList(field: string) {
const layout = Cast(Doc.LayoutField(this.doc), Doc, null);
@@ -195,11 +178,17 @@ export class TreeView extends React.Component<TreeViewProps> {
DocListCastOrNull(this.doc[field])
); // otherwise use the document's data field
}
+ moving: boolean = false;
@undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
if (this.doc !== target && addDoc !== returnFalse) {
+ const canAdd1 = (this.props.parentTreeView as any).dropping || !(ComputedField.WithoutComputed(() => FieldValue(this.props.parentTreeView?.doc.data)) instanceof ComputedField);
+
// bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse
- if (this.props.removeDoc?.(doc) === true) {
- return addDoc(doc);
+ if (canAdd1 && this.props.removeDoc?.(doc) === true) {
+ this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.moving = true);
+ const res = addDoc(doc);
+ this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.moving = false);
+ return res;
}
}
return false;
@@ -207,6 +196,7 @@ export class TreeView extends React.Component<TreeViewProps> {
@undoBatch @action remove = (doc: Doc | Doc[], key: string) => {
this.props.treeView.props.select(false);
const ind = this.dataDoc[key].indexOf(doc);
+
const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
res && ind > 0 && DocumentManager.Instance.getDocumentView(this.dataDoc[key][ind - 1], this.props.treeView.props.CollectionView)?.select(false);
return res;
@@ -378,20 +368,32 @@ export class TreeView extends React.Component<TreeViewProps> {
const docDragData = de.complete.docDragData;
if (docDragData && pt[0] < rect.left + rect.width) {
if (docDragData.draggedDocuments[0] === this.doc) return true;
- if (this.dropDocuments(docDragData.droppedDocuments, before, inside, docDragData.dropAction, docDragData.moveDocument, docDragData.treeViewDoc === this.props.treeView.props.Document)) {
+ if (this.dropDocuments(docDragData.droppedDocuments, before, inside, docDragData.dropAction, docDragData.removeDocument, docDragData.moveDocument, docDragData.treeViewDoc === this.props.treeView.props.Document)) {
e.stopPropagation();
}
}
};
- dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) {
+ dropping: boolean = false;
+ dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, removeDocument: DragManager.RemoveFunction | undefined, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) {
const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before);
- const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes('add')) || forceAdd;
- const localAdd = (doc: Doc) => (Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) && ((doc.context = this.doc.context) || true) ? true : false);
- const addDoc = !inside ? parentAddDoc : (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc), true as boolean);
+ const localAdd = (doc: Doc | Doc[]) => {
+ const innerAdd = (doc: Doc) => {
+ const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField;
+ const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc);
+ dataIsComputed && (doc.context = this.doc.context);
+ return added;
+ };
+ return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
+ };
+ const addDoc = inside ? localAdd : parentAddDoc;
const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same') && moveDocument;
+ const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes('add')) || forceAdd;
if (canAdd) {
- return UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false));
+ this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = true);
+ const res = UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false));
+ this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = false);
+ return res;
}
return false;
}
@@ -437,12 +439,16 @@ export class TreeView extends React.Component<TreeViewProps> {
const expandedWidth = () => this.props.panelWidth() - leftOffset.width;
if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc)) {
const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key);
- const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => {
- const added = Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
- added && (doc.context = this.doc.context);
- return added;
+ const moveDoc = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(doc, target, addDoc);
+ const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => {
+ const innerAdd = (doc: Doc) => {
+ const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
+ const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
+ dataIsComputed && (doc.context = this.doc.context);
+ return added;
+ };
+ return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
};
- const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true);
contentElement = TreeView.GetChildElements(
contents instanceof Doc ? [contents] : DocListCast(contents),
this.props.treeView,
@@ -453,7 +459,7 @@ export class TreeView extends React.Component<TreeViewProps> {
this.props.prevSibling,
addDoc,
remDoc,
- this.move,
+ moveDoc,
this.props.dropAction,
this.props.addDocTab,
this.titleStyleProvider,
@@ -533,6 +539,7 @@ export class TreeView extends React.Component<TreeViewProps> {
);
const key = (expandKey === 'annotations' ? `${this.fieldKey}-` : '') + expandKey;
const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key);
+ const moveDoc = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(doc, target, addDoc);
const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => {
// if there's a sort ordering specified that can be modified on drop (eg, zorder can be modified, alphabetical can't),
// then the modification would be done here
@@ -543,8 +550,10 @@ export class TreeView extends React.Component<TreeViewProps> {
docs.push(doc);
docs.sort((a, b) => (NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1)).forEach((d, i) => (d.zIndex = i));
}
- const added = Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
- added && (doc.context = this.doc.context);
+ const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
+ const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
+ !dataIsComputed && added && (doc.context = this.doc.context);
+
return added;
};
const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true);
@@ -593,7 +602,7 @@ export class TreeView extends React.Component<TreeViewProps> {
this.props.prevSibling,
addDoc,
remDoc,
- this.move,
+ moveDoc,
StrCast(this.doc.childDropAction, this.props.dropAction) as dropActionType,
this.props.addDocTab,
this.titleStyleProvider,
@@ -700,7 +709,7 @@ export class TreeView extends React.Component<TreeViewProps> {
@computed get validExpandViewTypes() {
const annos = () => (DocListCast(this.doc[this.fieldKey + '-annotations']).length && !this.props.treeView.dashboardMode ? 'annotations' : '');
- const links = () => (DocListCast(this.doc.links).length && !this.props.treeView.dashboardMode ? 'links' : '');
+ const links = () => (LinkManager.Links(this.doc).length && !this.props.treeView.dashboardMode ? 'links' : '');
const data = () => (this.childDocs || this.props.treeView.dashboardMode ? this.fieldKey : '');
const aliases = () => (this.props.treeView.dashboardMode ? '' : 'aliases');
const fields = () => (Doc.noviceMode ? '' : 'fields');
@@ -1028,7 +1037,9 @@ export class TreeView extends React.Component<TreeViewProps> {
// renders the text version of a document as the header. This is used in the file system mode and in other vanilla tree views.
@computed get renderTitleAsHeader() {
- return (
+ return this.props.treeView.Document.treeViewHideUnrendered && this.doc.unrendered && !this.doc.treeViewFieldKey ? (
+ <div></div>
+ ) : (
<>
{this.renderBullet}
{this.renderTitle}
@@ -1062,7 +1073,7 @@ export class TreeView extends React.Component<TreeViewProps> {
const before = pt[1] < rect.top + rect.height / 2;
const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
- const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, false));
+ const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, undefined, false));
};
render() {
@@ -1146,8 +1157,8 @@ export class TreeView extends React.Component<TreeViewProps> {
unobserveHeight: (ref: any) => void,
contextMenuItems: { script: ScriptField; filter: ScriptField; label: string; icon: string }[],
// TODO: [AL] add these
- AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[],
- RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[],
+ AddToMap?: (treeViewDoc: Doc, index: number[]) => void,
+ RemFromMap?: (treeViewDoc: Doc, index: number[]) => void,
hierarchyIndex?: number[],
renderCount?: number
) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 63b566f28..15d8144fc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -74,7 +74,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable
public static ShowPresPaths = false;
- private _lastNudge: any;
+ private _panZoomTransitionTimer: any;
private _lastX: number = 0;
private _lastY: number = 0;
private _downX: number = 0;
@@ -94,9 +94,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
private _brushtimer: any;
private _brushtimer1: any;
- // private isWritingMode: boolean = true;
- // private writingModeDocs: Doc[] = [];
-
private get isAnnotationOverlay() {
return this.props.isAnnotationOverlay;
}
@@ -111,7 +108,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _panZoomTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan. set to 0 to use document's transition time or default of 0
@observable _hLines: number[] | undefined;
@observable _vLines: number[] | undefined;
- @observable _firstRender = true; // this turns off rendering of the collection's content so that there's instant feedback when a tab is switched of what content will be shown.
+ @observable _firstRender = false; // this turns off rendering of the collection's content so that there's instant feedback when a tab is switched of what content will be shown. could be used for performance improvement
@observable _showAnimTimeline = false;
@observable _clusterSets: Doc[][] = [];
@observable _deleteList: DocumentView[] = [];
@@ -139,7 +136,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
}
@computed get fitContentsToBox() {
- return (this.props.fitContentsToBox?.() || this.Document._fitContentsToBox || this.Document.isGroup) && !this.isAnnotationOverlay;
+ return (this.props.fitContentsToBox?.() || this.Document._fitContentsToBox) && !this.isAnnotationOverlay;
}
@computed get contentBounds() {
const cb = Cast(this.rootDoc.contentBounds, listSpec('number'));
@@ -181,8 +178,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return this.getContainerTransform().translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform);
}
- _keyTimer: NodeJS.Timeout | undefined;
-
public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration: number) {
if (timer) clearTimeout(timer);
return DocumentView.SetViewTransition(docs, 'all', duration, undefined, true);
@@ -208,6 +203,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return newTimer;
}
+ _keyTimer: NodeJS.Timeout | undefined; // timer for turning off transition flag when key frame change has completed. Need to clear this if you do a second navigation before first finishes, or else first timer can go off during second naviation.
changeKeyFrame = (back = false) => {
const currentFrame = Cast(this.Document._currentFrame, 'number', null);
if (currentFrame === undefined) {
@@ -520,7 +516,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._clusterSets.push([doc]);
} else if (this._clusterSets.length) {
for (let i = this._clusterSets.length; i <= doc.cluster; i++) !this._clusterSets[i] && this._clusterSets.push([]);
- this._clusterSets[doc.cluster].push(doc);
+ this._clusterSets[doc.cluster ?? 0].push(doc);
}
}
}
@@ -583,14 +579,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) &&
!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)
) {
+ // prettier-ignore
switch (Doc.ActiveTool) {
- case InkTool.Highlighter:
- break;
- // TODO: nda - this where we want to create the new "writingDoc" collection that we add strokes to
- case InkTool.Write:
- break;
- case InkTool.Pen:
- break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
+ case InkTool.Highlighter: break;
+ case InkTool.Write: break;
+ case InkTool.Pen: break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
case InkTool.Eraser:
this._batch = UndoManager.StartBatch('collectionErase');
setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, emptyFunction);
@@ -1142,7 +1135,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || DocListCast(Doc.MyOverlayDocs?.data).includes(this.Document)) {
- this._panZoomTransition = panTime;
+ this.setPanZoomTransition(panTime);
const scale = this.getLocalTransform().inverse().Scale;
const minScale = NumCast(this.rootDoc._viewScaleMin, 1);
const minPanX = NumCast(this.rootDoc._panXMin, 0);
@@ -1176,12 +1169,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
nudge = (x: number, y: number, nudgeTime: number = 500) => {
if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || this.props.ContainingCollectionDoc._panX !== undefined) {
- // bcz: this isn't ideal, but want to try it out...
- this.setPan(NumCast(this.layoutDoc._panX) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), NumCast(this.layoutDoc._panY) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(), nudgeTime, true);
- this._lastNudge && clearTimeout(this._lastNudge);
- this._lastNudge = setTimeout(
- action(() => (this._panZoomTransition = 0)),
- nudgeTime
+ this.setPan(
+ NumCast(this.layoutDoc._panX) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
+ NumCast(this.layoutDoc._panY) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(),
+ nudgeTime,
+ true
);
return true;
}
@@ -1207,9 +1199,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
@action
+ setPanZoomTransition = (transitionTime: number) => {
+ this._panZoomTransition = transitionTime;
+ this._panZoomTransitionTimer && clearTimeout(this._panZoomTransitionTimer);
+ this._panZoomTransitionTimer = setTimeout(
+ action(() => (this._panZoomTransition = 0)),
+ transitionTime
+ );
+ };
+
+ @action
zoomSmoothlyAboutPt(docpt: number[], scale: number, transitionTime = 500) {
if (this.Document._isGroup) return;
- this._panZoomTransition = transitionTime;
+ this.setPanZoomTransition(transitionTime);
const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
this.layoutDoc[this.scaleFieldKey] = scale;
const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
@@ -1217,7 +1219,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const newpan = this.getTransform().transformDirection(scrDelta.x, scrDelta.y);
this.layoutDoc._panX = NumCast(this.layoutDoc._panX) - newpan[0];
this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1];
- return new Promise<number>(res => setTimeout(() => res(runInAction(() => (this._panZoomTransition = 0))), this._panZoomTransition)); // set transition to be smooth, then reset
}
calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
@@ -1553,7 +1554,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
// create an anchor that saves information about the current state of the freeform view (pan, zoom, view type)
const anchor = Docs.Create.CollectionAnchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), unrendered: true, presTransition: 500, annotationOn: this.rootDoc });
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: true, viewType: true, filters: true } }, this.rootDoc);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document._isGroup, viewType: true, filters: true } }, this.rootDoc);
if (addAsAnnotation) {
if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) {
@@ -1574,7 +1575,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._firstRender = false;
this._disposers.groupBounds = reaction(
() => {
- if (this.props.Document._isGroup && this.childDocs.length === this.childDocList?.length) {
+ if (this.Document._isGroup && this.childDocs.length === this.childDocList?.length) {
const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[WidthSym](), height: cd[HeightSym]() }));
return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding));
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index a73627165..0714bffbc 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -396,7 +396,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
newCollection.forceActive = makeGroup;
newCollection.x = this.Bounds.left;
newCollection.y = this.Bounds.top;
- newCollection.fitwidth = true;
+ newCollection.fitWidth = true;
selected.forEach(d => (d.context = newCollection));
this.hideMarquee();
return newCollection;
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index c9112eec3..3f6369898 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -1,7 +1,6 @@
import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../../fields/Doc';
-import { DocCast } from '../../../fields/Types';
import { LinkManager } from '../../util/LinkManager';
import { DocumentView } from '../nodes/DocumentView';
import { LinkDocPreview } from '../nodes/LinkDocPreview';
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index cdac91a62..4741fc6f2 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { Doc } from '../../../fields/Doc';
import { Cast, DocCast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
@@ -15,11 +15,11 @@ import { LinkManager } from '../../util/LinkManager';
import { SelectionManager } from '../../util/SelectionManager';
import { SettingsManager } from '../../util/SettingsManager';
import { undoBatch } from '../../util/UndoManager';
+import { MainView } from '../MainView';
import { DocumentView, OpenWhere } from '../nodes/DocumentView';
import { LinkDocPreview } from '../nodes/LinkDocPreview';
import './LinkMenuItem.scss';
import React = require('react');
-import { MainView } from '../MainView';
interface LinkMenuItemProps {
groupType: string;
@@ -34,7 +34,7 @@ interface LinkMenuItemProps {
// drag links and drop link targets (aliasing them if needed)
export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: DocumentView, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) {
- const draggedDocs = (specificLinks ? specificLinks : DocListCast(sourceDoc.links)).map(link => LinkManager.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
+ const draggedDocs = (specificLinks ? specificLinks : LinkManager.Links(sourceDoc)).map(link => LinkManager.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
if (draggedDocs.length) {
const moddrag: Doc[] = [];
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index d4854eb49..5e4315833 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -11,6 +11,7 @@ import { emptyFunction, formatTime, OmitKeys, returnFalse, setupMoveUpEvents } f
import { DocUtils } from '../../documents/Documents';
import { Networking } from '../../Network';
import { DragManager } from '../../util/DragManager';
+import { LinkManager } from '../../util/LinkManager';
import { undoBatch } from '../../util/UndoManager';
import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline';
import { ContextMenu } from '../ContextMenu';
@@ -85,7 +86,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return this.props.PanelHeight() < 50;
} // used to collapse timeline when node is shrunk
@computed get links() {
- return DocListCast(this.dataDoc.links);
+ return LinkManager.Links(this.dataDoc);
}
@computed get mediaState() {
return this.dataDoc.mediaState as media_state;
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 459554ebe..105f7e151 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -24,7 +24,6 @@ import { DocumentViewProps } from './DocumentView';
import './DocumentView.scss';
import { EquationBox } from './EquationBox';
import { FieldView, FieldViewProps } from './FieldView';
-import { FilterBox } from './FilterBox';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { FunctionPlotBox } from './FunctionPlotBox';
import { ImageBox } from './ImageBox';
@@ -255,7 +254,6 @@ export class DocumentContentsView extends React.Component<
YoutubeBox,
PresElementBox,
SearchBox,
- FilterBox,
FunctionPlotBox,
ColorBox,
DashWebRTCVideo,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8be188209..c4c14c0ab 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -49,7 +49,6 @@ import './DocumentView.scss';
import { FieldViewProps } from './FieldView';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { LinkAnchorBox } from './LinkAnchorBox';
-import { LinkDocPreview } from './LinkDocPreview';
import { RadialMenu } from './RadialMenu';
import { ScriptingBox } from './ScriptingBox';
import { PresEffect, PresEffectDirection } from './trails';
@@ -104,7 +103,7 @@ export interface DocFocusOptions {
openLocation?: string; // where to open a missing document
zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections
toggleTarget?: boolean; // whether to toggle target on and off
- originatingDoc?: Doc; // document that triggered the focus
+ anchorDoc?: Doc; // doc containing anchor info to apply at end of focus to target doc
easeFunc?: 'linear' | 'ease'; // transition method for scrolling
}
export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => Opt<number>;
@@ -931,7 +930,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
},
icon: 'eye',
});
- DocListCast(this.Document.links).length &&
+ LinkManager.Links(this.Document).length &&
appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? 'Show' : 'Hide'} Link Button`, event: action(() => (this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton)), icon: 'eye' });
!appearance && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' });
@@ -971,7 +970,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
onClicks.push({ description: (this.Document.followLinkToggle ? 'Remove' : 'Make') + ' Target Visibility Toggle', event: () => this.toggleFollowLink(false, true), icon: 'map-pin' });
onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
!existingOnClick && cm.addItem({ description: 'OnClick...', addDivider: true, noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
- } else if (DocListCast(this.Document.links).length) {
+ } else if (LinkManager.Links(this.Document).length) {
onClicks.push({ description: 'Select on Click', event: () => this.selectOnClick(), icon: 'link' });
onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(undefined, false), icon: 'link' });
onClicks.push({ description: 'Toggle Link Target on Click', event: () => this.toggleTargetOnClick(), icon: 'map-pin' });
@@ -1542,11 +1541,17 @@ export class DocumentView extends React.Component<DocumentViewProps> {
} // this makes mobx trace() statements more descriptive
public ContentRef = React.createRef<HTMLDivElement>();
public ViewTimer: NodeJS.Timeout | undefined; // timer for res
+ public AnimEffectTimer: NodeJS.Timeout | undefined; // timer for res
private _disposers: { [name: string]: IReactionDisposer } = {};
public clearViewTransition = () => {
this.ViewTimer && clearTimeout(this.ViewTimer);
this.rootDoc._viewTransition = undefined;
};
+ public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => {
+ this.AnimEffectTimer && clearTimeout(this.AnimEffectTimer);
+ this.rootDoc[AnimationSym] = presEffect;
+ this.AnimEffectTimer = setTimeout(() => (this.rootDoc[AnimationSym] = undefined), timeInMs);
+ };
public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => {
this.rootDoc._viewTransition = `${transProp} ${timeInMs}ms`;
if (dataTrans) this.rootDoc._dataTransition = `${transProp} ${timeInMs}ms`;
@@ -1903,7 +1908,7 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc) {
const linkSource = Cast(linkCollection.linkSource, Doc, null);
const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data);
let wid = linkSource[WidthSym]();
- const links = DocListCast(linkSource.links);
+ const links = LinkManager.Links(linkSource);
links.forEach(link => {
const other = LinkManager.getOppositeAnchor(link, linkSource);
const otherdoc = !other ? undefined : other.annotationOn ? Cast(other.annotationOn, Doc, null) : other;
diff --git a/src/client/views/nodes/FilterBox.scss b/src/client/views/nodes/FilterBox.scss
index 107ad2e36..7f907c8d4 100644
--- a/src/client/views/nodes/FilterBox.scss
+++ b/src/client/views/nodes/FilterBox.scss
@@ -16,7 +16,6 @@
}
}
-
.filter-bookmark {
//display: flex;
@@ -39,7 +38,6 @@
// margin-bottom: 15px;
}
-
.filterBox-saveBookmark {
background-color: #e9e9e9;
border-radius: 11px;
@@ -61,7 +59,6 @@
margin-top: 4px;
margin-left: 2px;
}
-
}
.filterBox-select-scope,
@@ -154,12 +151,11 @@
display: flex;
flex-direction: column;
width: 200px;
- height: 100%;
position: absolute;
right: 0;
top: 0;
z-index: 1;
- background-color: #9F9F9F;
+ background-color: #9f9f9f;
.filterBox-tree {
z-index: 0;
@@ -177,8 +173,8 @@
cursor: pointer;
}
- >div,
- >div>div {
+ > div,
+ > div > div {
width: 100%;
height: 100%;
}
@@ -190,4 +186,4 @@
margin-bottom: 10px;
//height: calc(100% - 30px);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index d41a4bb82..e69de29bb 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -1,588 +0,0 @@
-import React = require('react');
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, reaction, runInAction } from 'mobx';
-import { observer } from 'mobx-react';
-import Select from 'react-select';
-import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt } from '../../../fields/Doc';
-import { List } from '../../../fields/List';
-import { RichTextField } from '../../../fields/RichTextField';
-import { listSpec } from '../../../fields/Schema';
-import { ComputedField, ScriptField } from '../../../fields/ScriptField';
-import { Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
-import { Docs, DocumentOptions } from '../../documents/Documents';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { UserOptions } from '../../util/GroupManager';
-import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { SelectionManager } from '../../util/SelectionManager';
-import { CollectionTreeView } from '../collections/CollectionTreeView';
-import { CollectionView } from '../collections/CollectionView';
-import { ViewBoxBaseComponent } from '../DocComponent';
-import { EditableView } from '../EditableView';
-import { SearchBox } from '../search/SearchBox';
-import { DashboardToggleButton, DefaultStyleProvider, StyleProp } from '../StyleProvider';
-import { DocumentViewProps } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
-import './FilterBox.scss';
-const higflyout = require('@hig/flyout');
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
-
-@observer
-export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
- constructor(props: Readonly<FieldViewProps>) {
- super(props);
- const targetDoc = FilterBox.targetDoc;
- if (targetDoc && !targetDoc.currentFilter) runInAction(() => (targetDoc.currentFilter = FilterBox.createFilterDoc()));
- }
-
- /// creates a new, empty filter doc
- static createFilterDoc() {
- const clearAll = `getProto(self).data = new List([])`;
- const reqdOpts: DocumentOptions = {
- _lockedPosition: true,
- _autoHeight: true,
- _fitWidth: true,
- _height: 150,
- _xPadding: 5,
- _yPadding: 5,
- _gridGap: 5,
- _forceActive: true,
- title: 'Unnamed Filter',
- filterBoolean: 'AND',
- boxShadow: '0 0',
- childDontRegisterViews: true,
- targetDropAction: 'same',
- ignoreClick: true,
- system: true,
- childDropAction: 'none',
- treeViewHideTitle: true,
- treeViewTruncateTitleWidth: 150,
- childContextMenuLabels: new List<string>(['Clear All']),
- childContextMenuScripts: new List<ScriptField>([ScriptField.MakeFunction(clearAll)!]),
- };
- return Docs.Create.FilterDocument(reqdOpts);
- }
- public static LayoutString(fieldKey: string) {
- return FieldView.LayoutString(FilterBox, fieldKey);
- }
-
- public _filterSelected = false;
- public _filterMatch = 'matched';
-
- @computed static get currentContainingCollectionDoc() {
- let docView: any = SelectionManager.Views()[0];
- let doc = docView.Document;
-
- while (docView && doc && doc.type !== 'collection') {
- doc = docView.props.ContainingCollectionDoc;
- docView = docView.props.ContainingCollectionView;
- }
-
- return doc;
- }
-
- // /**
- // * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
- // */
-
- // trying to get this to work rather than the version below this
-
- // @computed static get targetDoc() {
- // console.log(FilterBox.currentContainingCollectionDoc.type);
- // if (FilterBox._filterScope === "Current Collection") {
- // return FilterBox.currentContainingCollectionDoc;
- // }
- // else return CollectionDockingView.Instance.props.Document;
- // // return FilterBox._filterScope === "Current Collection" ? SelectionManager.Views()[0].Document || CollectionDockingView.Instance.props.Document : CollectionDockingView.Instance.props.Document;
- // }
-
- /**
- * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
- */
- @computed static get targetDoc() {
- return SelectionManager.Docs().length ? SelectionManager.Docs()[0] : Doc.ActiveDashboard;
- }
- @computed static get targetDocChildKey() {
- if (SelectionManager.Views().length) {
- return SelectionManager.Views()[0].ComponentView?.annotationKey || SelectionManager.Views()[0].ComponentView?.fieldKey || 'data';
- }
- return 'data';
- }
- @computed static get targetDocChildren() {
- return DocListCast(FilterBox.targetDoc?.[FilterBox.targetDocChildKey] || Doc.ActiveDashboard?.data);
- }
-
- @observable _loaded = false;
- componentDidMount() {
- reaction(
- () => DocListCastAsync(this.layoutDoc[this.fieldKey]),
- async activeTabsAsync => {
- const activeTabs = await activeTabsAsync;
- activeTabs && (await SearchBox.foreachRecursiveDocAsync(activeTabs, emptyFunction));
- runInAction(() => (this._loaded = true));
- },
- { fireImmediately: true }
- );
- }
-
- @computed get allDocs() {
- // trace();
- const allDocs = new Set<Doc>();
- const targetDoc = FilterBox.targetDoc;
- if (this._loaded && targetDoc) {
- SearchBox.foreachRecursiveDoc(FilterBox.targetDocChildren, (depth, doc) => allDocs.add(doc));
- }
- return Array.from(allDocs);
- }
-
- @computed get _allFacets() {
- // trace();
- const noviceReqFields = ['author', 'tags', 'text', 'type'];
- const noviceLayoutFields: string[] = []; //["_curPage"];
- const noviceFields = [...noviceReqFields, ...noviceLayoutFields];
-
- const keys = new Set<string>(noviceFields);
- this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key)));
- return Array.from(keys.keys())
- .filter(key => key[0])
- .filter(key => key[0] === '#' || key.indexOf('lastModified') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('_')) || noviceFields.includes(key) || !Doc.noviceMode)
- .sort();
- }
-
- /**
- * The current attributes selected to filter based on
- */
- @computed get activeAttributes() {
- return DocListCast(this.dataDoc[this.props.fieldKey]);
- }
-
- /**
- * @returns a string array of the current attributes
- */
- @computed get currentFacets() {
- return this.activeAttributes.map(attribute => StrCast(attribute.title));
- }
-
- gatherFieldValues(childDocs: Doc[], facetKey: string) {
- const valueSet = new Set<string>();
- let rtFields = 0;
- childDocs.forEach(d => {
- const facetVal = d[facetKey];
- if (facetVal instanceof RichTextField) rtFields++;
- valueSet.add(Field.toString(facetVal as Field));
- const fieldKey = Doc.LayoutFieldKey(d);
- const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView');
- const data = d[annos ? fieldKey + '-annotations' : fieldKey];
- if (data !== undefined) {
- let subDocs = DocListCast(data);
- if (subDocs.length > 0) {
- let newarray: Doc[] = [];
- while (subDocs.length > 0) {
- newarray = [];
- subDocs.forEach(t => {
- const facetVal = t[facetKey];
- if (facetVal instanceof RichTextField) rtFields++;
- facetVal !== undefined && valueSet.add(Field.toString(facetVal as Field));
- const fieldKey = Doc.LayoutFieldKey(t);
- const annos = !Field.toString(Doc.LayoutField(t) as Field).includes('CollectionView');
- DocListCast(t[annos ? fieldKey + '-annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
- });
- subDocs = newarray;
- }
- }
- }
- });
- return { strings: Array.from(valueSet.keys()), rtFields };
- }
- removeFilterDoc = (doc: Doc | Doc[]) => ((doc instanceof Doc ? [doc] : doc).map(doc => this.removeFilter(StrCast(doc.title))).length ? true : false);
- public removeFilter = (filterName: string) => {
- const targetDoc = FilterBox.targetDoc;
- if (targetDoc) {
- const filterDoc = targetDoc.currentFilter as Doc;
- const attributes = DocListCast(filterDoc.data);
- const found = attributes.findIndex(doc => doc.title === filterName);
- if (found !== -1) {
- (filterDoc.data as List<Doc>).splice(found, 1);
- const docFilter = Cast(targetDoc._docFilters, listSpec('string'));
- if (docFilter) {
- let index: number;
- while ((index = docFilter.findIndex(item => item.split(':')[0] === filterName)) !== -1) {
- docFilter.splice(index, 1);
- }
- }
- const docRangeFilters = Cast(targetDoc._docRangeFilters, listSpec('string'));
- if (docRangeFilters) {
- let index: number;
- while ((index = docRangeFilters.findIndex(item => item.split(':')[0] === filterName)) !== -1) {
- docRangeFilters.splice(index, 3);
- }
- }
- }
- }
- return true;
- };
- /**
- * Responds to clicking the check box in the flyout menu
- */
- facetClick = (facetHeader: string) => {
- const { targetDoc, targetDocChildren } = FilterBox;
- if (!targetDoc) return;
- const found = this.activeAttributes.findIndex(doc => doc.title === facetHeader);
- if (found !== -1) {
- this.removeFilter(facetHeader);
- } else {
- const allCollectionDocs = targetDocChildren;
- const facetValues = this.gatherFieldValues(targetDocChildren, facetHeader);
-
- let nonNumbers = 0;
- let minVal = Number.MAX_VALUE,
- maxVal = -Number.MAX_VALUE;
- facetValues.strings.map(val => {
- const num = val ? Number(val) : Number.NaN;
- if (Number.isNaN(num)) {
- val && nonNumbers++;
- } else {
- minVal = Math.min(num, minVal);
- maxVal = Math.max(num, maxVal);
- }
- });
- let newFacet: Opt<Doc>;
- if (facetHeader === 'text' || facetValues.rtFields / allCollectionDocs.length > 0.1) {
- newFacet = Docs.Create.TextDocument('', {
- title: facetHeader,
- system: true,
- target: targetDoc,
- _width: 100,
- _height: 25,
- _stayInCollection: true,
- treeViewExpandedView: 'layout',
- _treeViewOpen: true,
- _forceActive: true,
- ignoreClick: true,
- });
- Doc.GetProto(newFacet).forceActive = true; // required for FormattedTextBox to be able to gain focus since it will never be 'selected'
- Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
- newFacet._textBoxPaddingX = newFacet._textBoxPaddingY = 4;
- const scriptText = `setDocFilter(this?.target, "${facetHeader}", text, "match")`;
- newFacet.onTextChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, text: 'string' });
- } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) {
- newFacet = Docs.Create.SliderDocument({
- title: facetHeader,
- system: true,
- target: targetDoc,
- _fitWidth: true,
- _height: 40,
- _stayInCollection: true,
- treeViewExpandedView: 'layout',
- _treeViewOpen: true,
- _forceActive: true,
- _overflow: 'visible',
- });
- const newFacetField = Doc.LayoutFieldKey(newFacet);
- const ranged = Doc.readDocRangeFilter(targetDoc, facetHeader);
- Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
- const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1));
- const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05)));
- newFacet[newFacetField + '-min'] = ranged === undefined ? extendedMinVal : ranged[0];
- newFacet[newFacetField + '-max'] = ranged === undefined ? extendedMaxVal : ranged[1];
- Doc.GetProto(newFacet)[newFacetField + '-minThumb'] = extendedMinVal;
- Doc.GetProto(newFacet)[newFacetField + '-maxThumb'] = extendedMaxVal;
- const scriptText = `setDocRangeFilter(this?.target, "${facetHeader}", range)`;
- newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: 'number' });
- newFacet.data = ComputedField.MakeFunction(`readNumFacetData(self.target, self, "${FilterBox.targetDocChildKey}", "${facetHeader}")`);
- } else {
- newFacet = new Doc();
- newFacet.system = true;
- newFacet.title = facetHeader;
- newFacet.treeViewOpen = true;
- newFacet.layout = CollectionView.LayoutString('data');
- newFacet.layoutKey = 'layout';
- newFacet.type = DocumentType.COL;
- newFacet.target = targetDoc;
- newFacet.data = ComputedField.MakeFunction(`readFacetData(self.target, "${FilterBox.targetDocChildKey}", "${facetHeader}")`);
- }
- newFacet.hideContextMenu = true;
- Doc.AddDocToList(this.dataDoc, this.props.fieldKey, newFacet);
- }
- };
-
- @computed get scriptField() {
- const scriptText = 'setDocFilter(this?.target, heading, this.title, checked)';
- const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: 'string', checked: 'string', containingTreeView: Doc.name });
- return script ? () => script : undefined;
- }
-
- /**
- * Sets whether filters are ANDed or ORed together
- */
- @action
- changeBool = (e: any) => {
- FilterBox.targetDoc && (DocCast(FilterBox.targetDoc.currentFilter).filterBoolean = e.currentTarget.value);
- };
-
- /**
- * Changes whether to select matched or unmatched documents
- */
- @action
- changeMatch = (e: any) => {
- this._filterMatch = e.currentTarget.value;
- };
-
- @action
- changeSelected = () => {
- if (this._filterSelected) {
- this._filterSelected = false;
- SelectionManager.DeselectAll();
- } else {
- this._filterSelected = true;
- // helper method to select specified docs
- }
- };
-
- FilteringStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) {
- switch (property.split(':')[0]) {
- case StyleProp.Decorations:
- if (doc && !doc.treeViewHideHeaderFields) {
- return (
- <>
- <div style={{ marginRight: '5px', fontSize: '10px' }}>
- <select className="filterBox-selection">
- <option value="Is" key="Is">
- Is
- </option>
- <option value="Is Not" key="Is Not">
- Is Not
- </option>
- </select>
- </div>
- <div className="filterBox-treeView-close" onClick={e => this.removeFilter(StrCast(doc.title))}>
- <FontAwesomeIcon icon={'times'} size="sm" />
- </div>
- </>
- );
- }
- }
- return DefaultStyleProvider(doc, props, property);
- }
-
- suppressChildClick = () => ScriptField.MakeScript('')!;
-
- /**
- * Adds a filterDoc to the list of saved filters
- */
- saveFilter = () => {
- Doc.AddDocToList(Doc.UserDoc(), 'savedFilters', this.props.Document);
- };
-
- /**
- * Changes the title of the filterDoc
- */
- onTitleValueChange = (val: string) => {
- Doc.GetProto(this.props.Document).title = val || `FilterDoc for ${FilterBox.targetDoc?.title}`;
- return true;
- };
-
- /**
- * The flyout from which you can select a saved filter to apply
- */
- @computed get flyoutPanel() {
- return DocListCast(Doc.UserDoc().savedFilters).map(doc => {
- return (
- <div className="filterBox-tempFlyout" onWheel={e => e.stopPropagation()} style={{ height: 20, border: '2px' }} onPointerDown={() => this.props.updateFilterDoc?.(doc)}>
- {StrCast(doc.title)}
- </div>
- );
- });
- }
- setTreeHeight = (hgt: number) => {
- this.layoutDoc._height = NumCast(this.layoutDoc._autoHeightMargins) + 150; // need to add all the border sizes together.
- };
-
- /**
- * add lock and hide button decorations for the "Dashboards" flyout TreeView
- */
- FilterStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) => {
- if (property.split(':')[0] === StyleProp.Decorations) {
- return !doc || doc.treeViewHideHeaderFields ? null : DashboardToggleButton(doc, 'hidden', 'trash', 'trash', () => this.removeFilter(StrCast(doc.title)));
- }
- return this.props.styleProvider?.(doc, props, property);
- };
-
- layoutHeight = () => this.layoutDoc[HeightSym]();
- render() {
- const facetCollection = this.props.Document;
-
- const options = this._allFacets.filter(facet => this.currentFacets.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet }));
- return this.props.dontRegisterView ? null : (
- <div className="filterBox-treeView" style={{ width: '100%' }}>
- <div className="filterBox-title">
- <EditableView key="editableView" contents={this.props.Document.title} height={24} fontSize={15} GetValue={() => StrCast(this.props.Document.title)} SetValue={this.onTitleValueChange} />
- </div>
-
- <div className="filterBox-select-bool">
- <select className="filterBox-selection" onChange={this.changeBool} defaultValue={StrCast((FilterBox.targetDoc?.currentFilter as Doc)?.filterBoolean)}>
- <option value="AND" key="AND">
- AND
- </option>
- <option value="OR" key="OR">
- OR
- </option>
- </select>
- <div className="filterBox-select-text">filters together</div>
- </div>
-
- <div className="filterBox-select">
- <Select placeholder="Add a filter..." options={options} isMulti={false} onChange={val => this.facetClick((val as UserOptions).value)} onKeyDown={e => e.stopPropagation()} value={null} closeMenuOnSelect={true} />
- </div>
-
- <div className="filterBox-tree" key="tree">
- <CollectionTreeView
- Document={facetCollection}
- DataDoc={Doc.GetProto(facetCollection)}
- fieldKey={this.props.fieldKey}
- CollectionView={undefined}
- disableDocBrushing={true}
- setHeight={this.setTreeHeight} // if the tree view can trigger the height of the filter box to change, then this needs to be filled in.
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- childDocumentsActive={returnTrue}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}
- ContainingCollectionView={this.props.ContainingCollectionView}
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.layoutHeight}
- rootSelected={this.props.rootSelected}
- renderDepth={1}
- dropAction={this.props.dropAction}
- ScreenToLocalTransform={this.props.ScreenToLocalTransform}
- isAnyChildContentActive={returnFalse}
- addDocTab={returnFalse}
- pinToPres={returnFalse}
- isSelected={returnFalse}
- select={returnFalse}
- bringToFront={emptyFunction}
- isContentActive={returnTrue}
- whenChildContentsActiveChanged={returnFalse}
- treeViewHideTitle={true}
- focus={emptyFunction}
- onCheckedClick={this.scriptField}
- treeViewHideHeaderFields={false}
- dontRegisterView={true}
- styleProvider={this.FilterStyleProvider}
- docViewPath={this.props.docViewPath}
- scriptContext={this.props.scriptContext}
- moveDocument={returnFalse}
- removeDocument={this.removeFilterDoc}
- addDocument={returnFalse}
- />
- </div>
- <div className="filterBox-bottom">
- {/* <div className="filterBox-select-matched">
- <input className="filterBox-select-box" type="checkbox"
- onChange={this.changeSelected} />
- <div className="filterBox-select-text">select</div>
- <select className="filterBox-selection" onChange={e => this.changeMatch(e)}>
- <option value="matched" key="matched">matched</option>
- <option value="unmatched" key="unmatched">unmatched</option>
- </select>
- <div className="filterBox-select-text">documents</div>
- </div> */}
-
- <div style={{ display: 'flex' }}>
- <div className="filterBox-saveWrapper">
- <div className="filterBox-saveBookmark" onPointerDown={this.saveFilter}>
- <div>SAVE</div>
- </div>
- </div>
- <div className="filterBox-saveWrapper">
- <div className="filterBox-saveBookmark">
- <Flyout className="myFilters-flyout" anchorPoint={anchorPoints.TOP} content={this.flyoutPanel}>
- <div>FILTERS</div>
- </Flyout>
- </div>
- </div>
- <div className="filterBox-saveWrapper">
- <div className="filterBox-saveBookmark" onPointerDown={this.props.createNewFilterDoc}>
- <div>NEW</div>
- </div>
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
-
-ScriptingGlobals.add(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) {
- const docFilters = Cast(layoutDoc._docFilters, listSpec('string'), []);
- for (const filter of docFilters) {
- const fields = filter.split(':'); // split into key:value:modifiers
- if (fields[0] === facetHeader && fields[1] === facetValue) {
- return fields[2];
- }
- }
- return undefined;
-});
-ScriptingGlobals.add(function readNumFacetData(layoutDoc: Doc, facetDoc: Doc, childKey: string, facetHeader: string) {
- const allCollectionDocs = new Set<Doc>();
- const activeTabs = DocListCast(layoutDoc[childKey]);
- SearchBox.foreachRecursiveDoc(activeTabs, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
- const set = new Set<string>();
- if (facetHeader === 'tags')
- allCollectionDocs.forEach(child =>
- Field.toString(child[facetHeader] as Field)
- .split(':')
- .forEach(key => set.add(key))
- );
- else allCollectionDocs.forEach(child => set.add(Field.toString(child[facetHeader] as Field)));
- const facetValues = Array.from(set).filter(v => v);
-
- let minVal = Number.MAX_VALUE,
- maxVal = -Number.MAX_VALUE;
- facetValues.map(val => {
- const num = val ? Number(val) : Number.NaN;
- if (!Number.isNaN(num)) {
- minVal = Math.min(num, minVal);
- maxVal = Math.max(num, maxVal);
- }
- });
- const newFacetField = Doc.LayoutFieldKey(facetDoc);
- const ranged = Doc.readDocRangeFilter(layoutDoc, facetHeader);
- const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1));
- const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05)));
- facetDoc[newFacetField + '-min'] = ranged === undefined ? extendedMinVal : ranged[0];
- facetDoc[newFacetField + '-max'] = ranged === undefined ? extendedMaxVal : ranged[1];
- Doc.GetProto(facetDoc)[newFacetField + '-minThumb'] = extendedMinVal;
- Doc.GetProto(facetDoc)[newFacetField + '-maxThumb'] = extendedMaxVal;
-});
-ScriptingGlobals.add(function readFacetData(layoutDoc: Doc, childKey: string, facetHeader: string) {
- const allCollectionDocs = new Set<Doc>();
- const activeTabs = DocListCast(layoutDoc[childKey]);
- SearchBox.foreachRecursiveDoc(activeTabs, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
- const set = new Set<string>();
- if (facetHeader === 'tags')
- allCollectionDocs.forEach(child =>
- Field.toString(child[facetHeader] as Field)
- .split(':')
- .forEach(key => set.add(key))
- );
- else allCollectionDocs.forEach(child => set.add(Field.toString(child[facetHeader] as Field)));
- const facetValues = Array.from(set).filter(v => v);
-
- let nonNumbers = 0;
-
- facetValues.map(val => Number.isNaN(Number(val)) && nonNumbers++);
- const facetValueDocSet = (nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue => {
- const doc = new Doc();
- doc.system = true;
- doc.title = facetValue.toString();
- doc.target = layoutDoc;
- doc.facetHeader = facetHeader;
- doc.facetValue = facetValue;
- doc.treeViewHideHeaderFields = true;
- doc.treeViewChecked = ComputedField.MakeFunction('determineCheckedState(self.target, self.facetHeader, self.facetValue)');
- return doc;
- });
- return new List<Doc>(facetValueDocSet);
-});
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index c6d4cb694..e60a03190 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -27,7 +27,7 @@ import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComp
import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { AnchorMenu } from '../pdf/AnchorMenu';
import { StyleProp } from '../StyleProvider';
-import { OpenWhere } from './DocumentView';
+import { DocFocusOptions, OpenWhere } from './DocumentView';
import { FaceRectangles } from './FaceRectangles';
import { FieldView, FieldViewProps } from './FieldView';
import './ImageBox.scss';
@@ -73,7 +73,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
const anchor =
this._getAnchor?.(this._savedAnnotations, false) ?? // use marquee anchor, otherwise, save zoom/pan as anchor
- Docs.Create.ImageanchorDocument({ title: 'ImgAnchor:' + this.rootDoc.title, presTransition: 1000, unrendered: true, annotationOn: this.rootDoc });
+ Docs.Create.ImageanchorDocument({
+ title: 'ImgAnchor:' + this.rootDoc.title,
+ presPanX: NumCast(this.layoutDoc._panX),
+ presPanY: NumCast(this.layoutDoc._panY),
+ presViewScale: Cast(this.layoutDoc._viewScale, 'number', null),
+ presTransition: 1000,
+ unrendered: true,
+ annotationOn: this.rootDoc,
+ });
if (anchor) {
if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
/* addAsAnnotation &&*/ this.addDocument(anchor);
@@ -433,6 +441,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this._marqueeing = undefined;
this.props.select(false);
};
+ focus = (anchor: Doc, options: DocFocusOptions) => this._ffref.current?.focus(anchor, options);
+
+ _ffref = React.createRef<CollectionFreeFormView>();
savedAnnotations = () => this._savedAnnotations;
render() {
TraceMobx();
@@ -460,6 +471,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
overflow: this.layoutDoc.fitWidth || this.props.fitWidth?.(this.rootDoc) ? 'auto' : undefined,
}}>
<CollectionFreeFormView
+ ref={this._ffref}
{...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
renderDepth={this.props.renderDepth + 1}
fieldKey={this.annotationKey}
@@ -471,6 +483,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
PanelHeight={this.props.PanelHeight}
ScreenToLocalTransform={this.screenToLocalTransform}
select={emptyFunction}
+ focus={this.focus}
getScrollHeight={this.getScrollHeight}
NativeDimScaling={returnOne}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index bbe9b4323..16b352e4f 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -3,7 +3,7 @@ import { Tooltip } from '@material-ui/core';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import wiki from 'wikijs';
-import { Doc, DocCastAsync, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { Doc, DocCastAsync, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types';
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
import { DocServer } from '../../DocServer';
@@ -14,11 +14,10 @@ import { LinkFollower } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
import { SettingsManager } from '../../util/SettingsManager';
import { Transform } from '../../util/Transform';
+import { SearchBox } from '../search/SearchBox';
import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView';
import './LinkDocPreview.scss';
import React = require('react');
-import { SearchUtil } from '../../util/SearchUtil';
-import { SearchBox } from '../search/SearchBox';
interface LinkDocPreviewProps {
linkDoc?: Doc;
@@ -110,8 +109,8 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
const anchorDoc = anchorDocId ? PromiseValue(DocCast(DocServer.GetCachedRefField(anchorDocId) ?? DocServer.GetRefField(anchorDocId))) : undefined;
anchorDoc?.then?.(
action(anchor => {
- if (anchor instanceof Doc && DocListCast(anchor.links).length) {
- this._linkDoc = this._linkDoc ?? DocListCast(anchor.links)[0];
+ if (anchor instanceof Doc && LinkManager.Links(anchor).length) {
+ this._linkDoc = this._linkDoc ?? LinkManager.Links(anchor)[0];
const automaticLink = this._linkDoc.linkRelationship === LinkManager.AutoKeywords;
if (automaticLink) {
// automatic links specify the target in the link info, not the source
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index c182dfd19..0064a2022 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -21,6 +21,7 @@ import { AnchorMenu } from '../../pdf/AnchorMenu';
import { Annotation } from '../../pdf/Annotation';
import { SidebarAnnos } from '../../SidebarAnnos';
import { FieldView, FieldViewProps } from '../FieldView';
+import { PinProps } from '../trails';
import './MapBox.scss';
import { MapBoxInfoWindow } from './MapBoxInfoWindow';
@@ -133,8 +134,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
private _sidebarRef = React.createRef<SidebarAnnos>();
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
- constructor(props: any) {
- super(props);
+ componentDidMount() {
+ this.props.setContentView?.(this);
}
@action
@@ -525,7 +526,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
);
}
- getAnchor = (addAsAnnotation: boolean) => AnchorMenu.Instance?.GetAnchor(this._savedAnnotations, addAsAnnotation) ?? this.rootDoc;
+ getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => AnchorMenu.Instance?.GetAnchor(this._savedAnnotations, addAsAnnotation) ?? this.rootDoc;
/**
* render contents in allMapMarkers (e.g. images with exifData) into google maps as map marker
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 7df3d5f24..dc6bf6f9e 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, untracked } from 'mobx';
import { observer } from 'mobx-react';
import { basename } from 'path';
-import { Doc, DocListCast, HeightSym, WidthSym } from '../../../fields/Doc';
+import { Doc, HeightSym, WidthSym } from '../../../fields/Doc';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
@@ -14,6 +14,7 @@ import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { Networking } from '../../Network';
import { DocumentManager } from '../../util/DocumentManager';
+import { LinkManager } from '../../util/LinkManager';
import { ReplayMovements } from '../../util/ReplayMovements';
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
@@ -85,7 +86,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@observable _scrubbing: boolean = false;
@computed get links() {
- return DocListCast(this.dataDoc.links);
+ return LinkManager.Links(this.dataDoc);
}
@computed get heightPercent() {
return NumCast(this.layoutDoc._timelineHeightPercent, 100);
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index b33529aeb..a89e8b4ed 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,5 +1,8 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
import { action, computed, IReactionDisposer, observable } from 'mobx';
import { observer } from 'mobx-react';
+import { NodeSelection } from 'prosemirror-state';
import * as ReactDOM from 'react-dom/client';
import { DataSym, Doc, DocListCast, Field } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
@@ -7,18 +10,14 @@ import { listSpec } from '../../../../fields/Schema';
import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
import { ComputedField } from '../../../../fields/ScriptField';
import { Cast, StrCast } from '../../../../fields/Types';
+import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
+import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { OpenWhere } from '../DocumentView';
import './DashFieldView.scss';
import { FormattedTextBox } from './FormattedTextBox';
import React = require('react');
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
-import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
-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';
-import { FormattedTextBoxComment } from './FormattedTextBoxComment';
export class DashFieldView {
dom: HTMLDivElement; // container for label and value
@@ -102,7 +101,11 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
this._textBoxDoc = this.props.tbox.props.Document;
if (this.props.docid) {
- DocServer.GetRefField(this.props.docid).then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
+ DocServer.GetRefField(this.props.docid).then(
+ action(async dashDoc => {
+ dashDoc instanceof Doc && (this._dashDoc = dashDoc);
+ })
+ );
} else {
this._dashDoc = this.props.tbox.rootDoc;
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d857b150c..a76f9623a 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,7 +1,7 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { isEqual } from 'lodash';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { baseKeymap, selectAll } from 'prosemirror-commands';
import { history } from 'prosemirror-history';
@@ -11,7 +11,7 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, Doc, DocListCast, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
import { PrefetchProxy } from '../../../../fields/Proxy';
@@ -48,6 +48,7 @@ import { StyleProp } from '../../StyleProvider';
import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
import { LinkDocPreview } from '../LinkDocPreview';
+import { PinProps, PresBox } from '../trails';
import { DashDocCommentView } from './DashDocCommentView';
import { DashDocView } from './DashDocView';
import { DashFieldView } from './DashFieldView';
@@ -63,7 +64,6 @@ import { schema } from './schema_rts';
import { SummaryView } from './SummaryView';
import applyDevTools = require('prosemirror-dev-tools');
import React = require('react');
-import { PinProps, PresBox } from '../trails';
const translateGoogleApi = require('translate-google-api');
export const GoogleRef = 'googleDocId';
@@ -363,7 +363,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
let linkTime;
let linkAnchor;
let link;
- DocListCast(this.dataDoc.links).forEach((l, i) => {
+ LinkManager.Links(this.dataDoc).forEach((l, i) => {
const anchor = (l.anchor1 as Doc).annotationOn ? (l.anchor1 as Doc) : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined;
if (anchor && (anchor.annotationOn as Doc).mediaState === 'recording') {
linkTime = NumCast(anchor._timecodeToShow /* audioStart */);
@@ -400,7 +400,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
autoLink = () => {
const newAutoLinks = new Set<Doc>();
- const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship === LinkManager.AutoKeywords);
+ const oldAutoLinks = LinkManager.Links(this.props.Document).filter(link => link.linkRelationship === LinkManager.AutoKeywords);
if (this._editorView?.state.doc.textContent) {
const isNodeSel = this._editorView.state.selection instanceof NodeSelection;
const f = this._editorView.state.selection.from;
@@ -453,7 +453,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
alink =
alink ??
- (DocListCast(this.Document.links).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) ||
+ (LinkManager.Links(this.Document).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) ||
DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, LinkManager.AutoKeywords)!);
newAutoLinks.add(alink);
const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }];
@@ -687,7 +687,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
@undoBatch
deleteAnnotation = (anchor: Doc) => {
- LinkManager.Instance.deleteLink(DocListCast(anchor.links)[0]);
+ LinkManager.Instance.deleteLink(LinkManager.Links(anchor)[0]);
// const docAnnotations = DocListCast(this.props.dataDoc[this.fieldKey]);
// this.props.dataDoc[this.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion));
// AnchorMenu.Instance.fadeOut(true);
@@ -1014,7 +1014,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
componentDidMount() {
!this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
- this._cachedLinks = DocListCast(this.Document.links);
+ this._cachedLinks = LinkManager.Links(this.Document);
this._disposers.breakupDictation = reaction(() => DocumentManager.Instance.RecordingEvent, this.breakupDictation);
this._disposers.autoHeight = reaction(
() => this.autoHeight,
@@ -1041,7 +1041,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
{ fireImmediately: true }
);
this._disposers.links = reaction(
- () => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
+ () => LinkManager.Links(this.dataDoc), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
newLinks => {
this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l));
this._cachedLinks = newLinks;
@@ -1435,7 +1435,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
} else (e.nativeEvent as any).handledByInnerReactInstance = true;
if (this.Document.forceActive) e.stopPropagation();
- this.tryUpdateScrollHeight(); // if a doc a fitwidth doc is being viewed in different context (eg freeform & lightbox), then it will have conflicting heights. so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view.
+ this.tryUpdateScrollHeight(); // if a doc a fitWidth doc is being viewed in different context (eg freeform & lightbox), then it will have conflicting heights. so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view.
if ((e.target as any).tagName === 'AUDIOTAG') {
e.preventDefault();
e.stopPropagation();
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 94962650a..be40b3592 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -3,13 +3,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
-import { ColorState, SketchPicker } from 'react-color';
-import { AnimationSym, Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../../fields/Doc';
+import { AnimationSym, Doc, DocListCast, FieldResult, Opt, StrListCast } from '../../../../fields/Doc';
import { Copy, Id } from '../../../../fields/FieldSymbols';
import { InkField, InkTool } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
import { ObjectField } from '../../../../fields/ObjectField';
import { listSpec } from '../../../../fields/Schema';
+import { ComputedField, ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { AudioField } from '../../../../fields/URLField';
import { emptyFunction, emptyPath, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils';
@@ -19,6 +19,7 @@ import { CollectionViewType, DocumentType } from '../../../documents/DocumentTyp
import { DocumentManager } from '../../../util/DocumentManager';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
import { SelectionManager } from '../../../util/SelectionManager';
+import { SerializationHelper } from '../../../util/SerializationHelper';
import { SettingsManager } from '../../../util/SettingsManager';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { CollectionDockingView } from '../../collections/CollectionDockingView';
@@ -34,7 +35,6 @@ import { FieldView, FieldViewProps } from '../FieldView';
import { ScriptingBox } from '../ScriptingBox';
import './PresBox.scss';
import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
-import { SerializationHelper } from '../../../util/SerializationHelper';
const { Howl } = require('howler');
export interface pinDataTypes {
@@ -69,9 +69,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(PresBox, fieldKey);
}
+ static navigateToDocScript: ScriptField;
+
+ constructor(props: any) {
+ super(props);
+ if (!PresBox.navigateToDocScript) {
+ PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)')!;
+ }
+ }
private _disposers: { [name: string]: IReactionDisposer } = {};
public selectedArray = new ObservableSet<Doc>();
+ _batch: UndoManager.Batch | undefined = undefined; // undo batch for dragging sliders which generate multiple scene edit events as the cursor moves
+ _keyTimer: NodeJS.Timeout | undefined; // timer for turning off transition flag when key frame change has completed. Need to clear this if you do a second navigation before first finishes, or else first timer can go off during second naviation.
+ _unmounting = false; // flag that view is unmounting used to block RemFromMap from deleting things
@observable public static Instance: PresBox;
@@ -87,6 +98,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@observable _newDocumentTools: boolean = false;
@observable _openMovementDropdown: boolean = false;
@observable _openEffectDropdown: boolean = false;
+ @observable _openBulletEffectDropdown: boolean = false;
@observable _presentTools: boolean = false;
@observable _treeViewMap: Map<Doc, number> = new Map();
@observable _presKeyEvents: boolean = false;
@@ -115,6 +127,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed get targetDoc() {
return Cast(this.activeItem?.presentationTargetDoc, Doc, null);
}
+ public static targetRenderedDoc = (doc: Doc) => {
+ const targetDoc = Cast(doc?.presentationTargetDoc, Doc, null);
+ return targetDoc?.unrendered ? DocCast(targetDoc.annotationOn) : targetDoc;
+ };
@computed get scrollable() {
if ([DocumentType.PDF, DocumentType.WEB, DocumentType.RTF].includes(this.targetDoc.type as DocumentType) || this.targetDoc._viewType === CollectionViewType.Stacking) return true;
return false;
@@ -138,14 +154,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
addToSelectedArray = action((doc: Doc) => this.selectedArray.add(doc));
removeFromSelectedArray = action((doc: Doc) => this.selectedArray.delete(doc));
- _unmounting = false;
@action
componentWillUnmount() {
this._unmounting = true;
if (this._presTimer) clearTimeout(this._presTimer);
document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
this.resetPresentation();
- // Turn of progressivize editors
this.turnOffEdit(true);
Object.values(this._disposers).forEach(disposer => disposer?.());
}
@@ -185,6 +199,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
() => SelectionManager.Views(),
views => views.some(view => view.props.Document === this.rootDoc) && this.updateCurrentPresentation()
);
+ this._disposers.editing = reaction(
+ () => this.layoutDoc.presStatus === PresStatus.Edit,
+ editing => {
+ if (editing) {
+ this.childDocs.forEach(doc => {
+ if (doc.presIndexed !== undefined) {
+ this.progressivizedItems(doc)?.forEach(indexedDoc => (indexedDoc.opacity = undefined));
+ doc.presIndexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, 1);
+ }
+ });
+ }
+ }
+ );
}
@action
@@ -204,7 +231,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
stopTempMedia = (targetDocField: FieldResult) => {
- const targetDoc = Cast(targetDocField, Doc, null);
+ const targetDoc = DocCast(DocCast(targetDocField).annotationOn) ?? DocCast(targetDocField);
if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) {
const targMedia = DocumentManager.Instance.getDocumentView(targetDoc);
targMedia?.ComponentView?.Pause?.();
@@ -217,7 +244,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
nextSlide = (slideNum?: number) => {
const nextSlideInd = slideNum ?? this.itemIndex + 1;
let curSlideInd = nextSlideInd;
- CollectionStackedTimeline.CurrentlyPlaying?.map(clipView => clipView?.ComponentView?.Pause?.());
+ //CollectionStackedTimeline.CurrentlyPlaying?.map(clipView => clipView?.ComponentView?.Pause?.());
this.clearSelectedArray();
const doGroupWithUp =
(nextSelected: number, force = false) =>
@@ -243,18 +270,57 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
doGroupWithUp(curSlideInd, true)();
};
+ // docs within a slide target that will be progressively revealed
+ progressivizedItems = (doc: Doc) => {
+ const targetList = PresBox.targetRenderedDoc(doc);
+ if (doc.presIndexed !== undefined && targetList) {
+ const listItems = (Cast(targetList[Doc.LayoutFieldKey(targetList)], listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[]) ?? DocListCast(targetList[Doc.LayoutFieldKey(targetList) + '-annotations']);
+ return listItems.filter(doc => !doc.unrendered);
+ }
+ };
// Called when the user activates 'next' - to move to the next part of the pres. trail
@action
next = () => {
+ const progressiveReveal = (first: boolean) => {
+ const presIndexed = Cast(this.activeItem?.presIndexed, 'number', null);
+ if (presIndexed !== undefined) {
+ const targetRenderedDoc = PresBox.targetRenderedDoc(this.activeItem);
+ targetRenderedDoc._dataTransition = 'all 1s';
+ targetRenderedDoc.opacity = 1;
+ setTimeout(() => (targetRenderedDoc._dataTransition = 'inherit'), 1000);
+ const listItems = this.progressivizedItems(this.activeItem);
+ if (listItems && presIndexed < listItems.length) {
+ if (!first) {
+ const listItemDoc = listItems[presIndexed];
+ const targetView = listItems && DocumentManager.Instance.getFirstDocumentView(listItemDoc);
+ Doc.linkFollowUnhighlight();
+ Doc.HighlightDoc(listItemDoc);
+ listItemDoc.presEffect = this.activeItem.presBulletEffect;
+ listItemDoc.presTransition = 500;
+ targetView?.setAnimEffect(listItemDoc, 500);
+ if (targetView?.docView && this.activeItem.presBulletExpand) {
+ targetView.docView._animateScalingTo = 1.1;
+ Doc.AddUnHighlightWatcher(() => (targetView!.docView!._animateScalingTo = 0));
+ }
+ listItemDoc.opacity = undefined;
+ this.activeItem.presIndexed = presIndexed + 1;
+ }
+ return true;
+ }
+ }
+ };
+ if (progressiveReveal(false)) return true;
if (this.childDocs[this.itemIndex + 1] !== undefined) {
// Case 1: No more frames in current doc and next slide is defined, therefore move to next slide
const slides = DocListCast(this.rootDoc[StrCast(this.presFieldKey, 'data')]);
const curLast = this.selectedArray.size ? Math.max(...Array.from(this.selectedArray).map(d => slides.indexOf(DocCast(d)))) : this.itemIndex;
this.nextSlide(curLast + 1 === this.childDocs.length ? (this.layoutDoc.presLoop ? 0 : curLast) : curLast + 1);
+ progressiveReveal(true); // shows first progressive document, but without a transition effect
} else {
if (this.childDocs[this.itemIndex + 1] === undefined && (this.layoutDoc.presLoop || this.layoutDoc.presStatus === PresStatus.Edit)) {
// Case 2: Last slide and presLoop is toggled ON or it is in Edit mode
this.nextSlide(0);
+ progressiveReveal(true); // shows first progressive document, but without a transition effect
}
return 0;
}
@@ -291,21 +357,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
Doc.UnBrushAllDocs();
if (index >= 0 && index < this.childDocs.length) {
this.rootDoc._itemIndex = index;
- const activeItem: Doc = this.activeItem;
- const targetDoc: Doc = this.targetDoc;
- const activeFrame = activeItem.presActiveFrame ?? activeItem.presCurrentFrame;
- if (activeFrame !== undefined) {
- const transTime = NumCast(activeItem.presTransition, 500);
- const acontext = activeItem.presActiveFrame !== undefined ? DocCast(DocCast(activeItem.presentationTargetDoc).context) : DocCast(activeItem.presentationTargetDoc);
- const context = DocCast(acontext)?.annotationOn ? DocCast(DocCast(acontext).annotationOn) : acontext;
- if (context) {
- const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.ComponentView as CollectionFreeFormView;
- if (ffview?.childDocs) {
- this._keyTimer = CollectionFreeFormView.gotoKeyframe(this._keyTimer, ffview.childDocs, transTime);
- context._currentFrame = NumCast(activeFrame);
- }
- }
- }
if (from?.mediaStopTriggerList && this.layoutDoc.presStatus !== PresStatus.Edit) {
DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia);
}
@@ -313,14 +364,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.stopTempMedia(from.presentationTargetDoc);
}
// If next slide is audio / video 'Play automatically' then the next slide should be played
- if (this.layoutDoc.presStatus !== PresStatus.Edit && (targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && activeItem.mediaStart === 'auto') {
- this.startTempMedia(targetDoc, activeItem);
+ if (this.layoutDoc.presStatus !== PresStatus.Edit && (this.targetDoc.type === DocumentType.AUDIO || this.targetDoc.type === DocumentType.VID) && this.activeItem.mediaStart === 'auto') {
+ this.startTempMedia(this.targetDoc, this.activeItem);
}
if (!group) this.clearSelectedArray();
this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); //Update selected array
this.turnOffEdit();
this.navigateToActiveItem(finished); //Handles movement to element only when presTrail is list
- this.onHideDocument(); //Handles hide after/before
+ this.doHideBeforeAfter(); //Handles hide after/before
}
});
static pinDataTypes(target?: Doc): pinDataTypes {
@@ -338,7 +389,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const filters = true;
const pivot = true;
const dataannos = false;
- return { scrollable, pannable, inkable, viewType, pivot, filters, temporal, clippable, dataview, textview, poslayoutview, dataannos };
+ return { scrollable, pannable, inkable, viewType, pivot, filters, temporal, clippable, dataview, datarange, textview, poslayoutview, dataannos };
}
@action
@@ -366,6 +417,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
changed = true;
}
}
+
+ const activeFrame = activeItem.presActiveFrame ?? activeItem.presCurrentFrame;
+ if (activeFrame !== undefined) {
+ const transTime = NumCast(activeItem.presTransition, 500);
+ const acontext = activeItem.presActiveFrame !== undefined ? DocCast(DocCast(activeItem.presentationTargetDoc).context) : DocCast(activeItem.presentationTargetDoc);
+ const context = DocCast(acontext)?.annotationOn ? DocCast(DocCast(acontext).annotationOn) : acontext;
+ if (context) {
+ const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+ if (ffview?.childDocs) {
+ PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, transTime);
+ ffview.rootDoc._currentFrame = NumCast(activeFrame);
+ }
+ }
+ }
if (pinDataTypes?.datarange || (!pinDataTypes && activeItem.presXRange !== undefined)) {
if (bestTarget.xRange !== activeItem.presXRange) {
bestTarget.xRange = (activeItem.presXRange as ObjectField)?.[Copy]();
@@ -491,7 +556,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
setTimeout(() => Array.from(transitioned).forEach(action(doc => (doc._dataTransition = undefined))), transTime + 10);
}
- if (pinDataTypes?.pannable || (!pinDataTypes && (activeItem.presPinViewBounds !== undefined || activeItem.presPanX !== undefined || activeItem.presViewScale !== undefined))) {
+ if ((pinDataTypes?.pannable || (!pinDataTypes && (activeItem.presPinViewBounds !== undefined || activeItem.presPanX !== undefined || activeItem.presViewScale !== undefined))) && !bestTarget._isGroup) {
const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number'));
if (contentBounds) {
const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] };
@@ -652,7 +717,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
zoomTime: activeItem.presMovement === PresMovement.Jump ? 0 : Math.min(Math.max(effect ? 750 : 500, (effect ? 0.2 : 1) * presTime), presTime),
effect: activeItem,
noSelect: true,
- originatingDoc: activeItem,
+ anchorDoc: activeItem,
easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any,
zoomTextSelections: BoolCast(activeItem.presZoomText),
playAudio: BoolCast(activeItem.presPlayAudio),
@@ -681,22 +746,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
* they are hidden each time the presentation is updated.
*/
@action
- onHideDocument = () => {
+ doHideBeforeAfter = () => {
this.childDocs.forEach((doc, index) => {
const curDoc = Cast(doc, Doc, null);
- const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null);
+ const tagDoc = PresBox.targetRenderedDoc(curDoc);
const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, tagDoc);
+ let opacity: Opt<number> = index === this.itemIndex ? 1 : undefined;
if (curDoc.presHide) {
if (index !== this.itemIndex) {
- tagDoc.opacity = 1;
+ opacity = 1;
}
}
const hidingIndBef = itemIndexes.find(item => item >= this.itemIndex) ?? itemIndexes.slice().reverse().lastElement();
if (curDoc.presHideBefore && index === hidingIndBef) {
if (index > this.itemIndex) {
- tagDoc.opacity = 0;
+ opacity = 0;
} else if (index === this.itemIndex || !curDoc.presHideAfter) {
- tagDoc.opacity = 1;
+ opacity = 1;
+ setTimeout(() => (tagDoc._dataTransition = undefined), 1000);
}
}
const hidingIndAft =
@@ -706,17 +773,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
.find(item => item <= this.itemIndex) ?? itemIndexes.lastElement();
if (curDoc.presHideAfter && index === hidingIndAft) {
if (index < this.itemIndex) {
- tagDoc.opacity = 0;
+ opacity = 0;
} else if (index === this.itemIndex || !curDoc.presHideBefore) {
- tagDoc.opacity = 1;
+ opacity = 1;
}
}
const hidingInd = itemIndexes.find(item => item === this.itemIndex);
if (curDoc.presHide && index === hidingInd) {
if (index === this.itemIndex) {
- tagDoc.opacity = 0;
+ opacity = 0;
}
}
+ opacity !== undefined && (tagDoc.opacity = opacity);
});
};
@@ -780,7 +848,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
//stops the presentaton.
resetPresentation = () => {
this.childDocs
- .map(doc => Cast(doc.presentationTargetDoc, Doc, null))
+ .map(doc => PresBox.targetRenderedDoc(doc))
.filter(doc => doc instanceof Doc)
.forEach(doc => {
try {
@@ -806,6 +874,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
};
+ initializePresState = (startIndex: number) => {
+ this.childDocs.forEach((doc, index) => {
+ const tagDoc = PresBox.targetRenderedDoc(doc);
+ if (doc.presHideBefore && index > startIndex) tagDoc.opacity = 0;
+ if (doc.presHideAfter && index < startIndex) tagDoc.opacity = 0;
+ if (doc.presIndexed !== undefined && index >= startIndex) {
+ const startInd = NumCast(doc.presIndexedStart);
+ this.progressivizedItems(doc)
+ ?.slice(startInd)
+ .forEach(indexedDoc => (indexedDoc.opacity = 0));
+ doc.presIndexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, startInd);
+ }
+ // if (doc.presHide && this.childDocs.indexOf(doc) === startIndex) tagDoc.opacity = 0;
+ });
+ };
+
/**
* The function that starts the presentation at the given index, also checking if actions should be applied
* directly at start.
@@ -817,12 +901,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
clearTimeout(this._presTimer);
if (this.childDocs.length) {
this.layoutDoc.presStatus = PresStatus.Autoplay;
- this.childDocs.forEach(doc => {
- const tagDoc = doc.presentationTargetDoc as Doc;
- if (doc.presHideBefore && this.childDocs.indexOf(doc) > startIndex) tagDoc.opacity = 0;
- if (doc.presHideAfter && this.childDocs.indexOf(doc) < startIndex) tagDoc.opacity = 0;
- // if (doc.presHide && this.childDocs.indexOf(doc) === startIndex) tagDoc.opacity = 0;
- });
+ this.initializePresState(startIndex);
const func = () => {
const delay = NumCast(this.activeItem.presDuration, this.activeItem.type === DocumentType.SCRIPTING ? 0 : 2500) + NumCast(this.activeItem.presTransition);
this._presTimer = setTimeout(() => {
@@ -859,6 +938,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
doc._height = 30;
doc._width = PresBox.minimizedWidth;
Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc);
+ PresBox.Instance.initializePresState(PresBox.Instance.itemIndex);
return (doc.presStatus = PresStatus.Manual);
}
@@ -1267,12 +1347,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (timeInMS > 100000) timeInMS = 100000;
setter(timeInMS);
};
- setTransitionTime = (number: String, change?: number) => {
+
+ @undoBatch
+ updateTransitionTime = (number: String, change?: number) => {
PresBox.SetTransitionTime(number, (timeInMS: number) => this.selectedArray.forEach(doc => (doc.presTransition = timeInMS)), change);
};
// Converts seconds to ms and updates presTransition
- setZoom = (number: String, change?: number) => {
+ @undoBatch
+ updateZoom = (number: String, change?: number) => {
let scale = Number(number) / 100;
if (change) scale += change;
if (scale < 0.01) scale = 0.01;
@@ -1280,8 +1363,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.selectedArray.forEach(doc => (doc.presZoom = scale));
};
- // Converts seconds to ms and updates presDuration
- setDurationTime = (number: String, change?: number) => {
+ /*
+ * Converts seconds to ms and updates presDuration
+ */
+ @undoBatch
+ updateDurationTime = (number: String, change?: number) => {
let timeInMS = Number(number) * 1000;
if (change) timeInMS += change;
if (timeInMS < 100) timeInMS = 100;
@@ -1289,9 +1375,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.selectedArray.forEach(doc => (doc.presDuration = timeInMS));
};
- /**
- * When the movement dropdown is changes
- */
@undoBatch
updateMovement = action((movement: PresMovement, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presMovement = movement)));
@@ -1322,6 +1405,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
activeItem.presOpenInLightbox = !activeItem.presOpenInLightbox;
this.selectedArray.forEach(doc => (doc.presOpenInLightbox = activeItem.presOpenInLightbox));
};
+
@undoBatch
@action
updateEaseFunc = (activeItem: Doc) => {
@@ -1335,12 +1419,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@undoBatch
@action
- updateEffect = (effect: PresEffect, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presEffect = effect));
-
- _batch: UndoManager.Batch | undefined = undefined;
+ updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (bullet ? (doc.presBulletEffect = effect) : (doc.presEffect = effect)));
+ static _sliderBatch: any;
public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?: number) => {
- let batch: any;
return (
<input
type="range"
@@ -1351,10 +1433,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)` }}
className={`toolbar-slider ${active ? '' : 'none'}`}
onPointerDown={e => {
- batch = UndoManager.StartBatch('pres slider');
+ PresBox._sliderBatch = UndoManager.StartBatch('pres slider');
e.stopPropagation();
}}
- onPointerUp={() => batch?.end()}
+ onPointerUp={() => PresBox._sliderBatch.end()}
onChange={e => {
e.stopPropagation();
change(e.target.value);
@@ -1362,11 +1444,161 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
/>
);
};
+
+ @undoBatch
+ @action
+ applyTo = (array: Doc[]) => {
+ this.updateMovement(this.activeItem.presMovement as PresMovement, true);
+ this.updateEffect(this.activeItem.presEffect as PresEffect, false, true);
+ this.updateEffect(this.activeItem.presBulletEffect as PresEffect, true, true);
+ this.updateEffectDirection(this.activeItem.presEffectDirection as PresEffectDirection, true);
+ const { presTransition, presDuration, presHideBefore, presHideAfter } = this.activeItem;
+ array.forEach(curDoc => {
+ curDoc.presTransition = presTransition;
+ curDoc.presDuration = presDuration;
+ curDoc.presHideBefore = presHideBefore;
+ curDoc.presHideAfter = presHideAfter;
+ });
+ };
+
+ @computed get visibiltyDurationDropdown() {
+ const activeItem = this.activeItem;
+ if (activeItem && this.targetDoc) {
+ const targetType = this.targetDoc.type;
+ let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 0;
+ if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration);
+ return (
+ <div className="presBox-ribbon">
+ <div className="ribbon-doubleButton">
+ <Tooltip title={<div className="dash-tooltip">{'Hide before presented'}</div>}>
+ <div className={`ribbon-toggle ${activeItem.presHideBefore ? 'active' : ''}`} onClick={() => this.updateHideBefore(activeItem)}>
+ Hide before
+ </div>
+ </Tooltip>
+ <Tooltip title={<div className="dash-tooltip">{'Hide while presented'}</div>}>
+ <div className={`ribbon-toggle ${activeItem.presHide ? 'active' : ''}`} onClick={() => this.updateHide(activeItem)}>
+ Hide
+ </div>
+ </Tooltip>
+
+ <Tooltip title={<div className="dash-tooltip">{'Hide after presented'}</div>}>
+ <div className={`ribbon-toggle ${activeItem.presHideAfter ? 'active' : ''}`} onClick={() => this.updateHideAfter(activeItem)}>
+ Hide after
+ </div>
+ </Tooltip>
+
+ <Tooltip title={<div className="dash-tooltip">{'Open in lightbox view'}</div>}>
+ <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presOpenInLightbox ? Colors.LIGHT_BLUE : '' }} onClick={() => this.updateOpenDoc(activeItem)}>
+ Lightbox
+ </div>
+ </Tooltip>
+ <Tooltip title={<div className="dash-tooltip">{'Transition movement style'}</div>}>
+ <div className="ribbon-toggle" onClick={() => this.updateEaseFunc(activeItem)}>
+ {`${StrCast(activeItem.presEaseFunc, 'ease')}`}
+ </div>
+ </Tooltip>
+ </div>
+ {[DocumentType.AUDIO, DocumentType.VID].includes(targetType as any as DocumentType) ? null : (
+ <>
+ <div className="ribbon-doubleButton">
+ <div className="presBox-subheading">Slide Duration</div>
+ <div className="ribbon-property">
+ <input className="presBox-input" type="number" value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s
+ </div>
+ <div className="ribbon-propertyUpDown">
+ <div className="ribbon-propertyUpDownItem" onClick={() => this.updateDurationTime(String(duration), 1000)}>
+ <FontAwesomeIcon icon={'caret-up'} />
+ </div>
+ <div className="ribbon-propertyUpDownItem" onClick={() => this.updateDurationTime(String(duration), -1000)}>
+ <FontAwesomeIcon icon={'caret-down'} />
+ </div>
+ </div>
+ </div>
+ {PresBox.inputter('0.1', '0.1', '20', duration, targetType !== DocumentType.AUDIO, this.updateDurationTime)}
+ <div className={'slider-headers'} style={{ display: targetType === DocumentType.AUDIO ? 'none' : 'grid' }}>
+ <div className="slider-text">Short</div>
+ <div className="slider-text">Medium</div>
+ <div className="slider-text">Long</div>
+ </div>
+ </>
+ )}
+ </div>
+ );
+ }
+ }
+ @computed get progressivizeDropdown() {
+ const activeItem = this.activeItem;
+ if (activeItem && this.targetDoc) {
+ const effect = activeItem.presBulletEffect ? activeItem.presBulletEffect : PresMovement.None;
+ const bulletEffect = (effect: PresEffect) => (
+ <div className={`presBox-dropdownOption ${activeItem.presEffect === effect || (effect === PresEffect.None && !activeItem.presEffect) ? 'active' : ''}`} onPointerDown={StopEvent} onClick={() => this.updateEffect(effect, true)}>
+ {effect}
+ </div>
+ );
+ return (
+ <div className="presBox-ribbon">
+ <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
+ <div className="presBox-subheading">Progressivize Collection</div>
+ <input
+ className="presBox-checkbox"
+ style={{ margin: 10 }}
+ type="checkbox"
+ onChange={() => {
+ activeItem.presIndexed = activeItem.presIndexed === undefined ? 0 : undefined;
+ activeItem.presHideBefore = activeItem.presIndexed !== undefined;
+ const tagDoc = PresBox.targetRenderedDoc(this.activeItem);
+ const type = DocCast(tagDoc?.annotationOn)?.type ?? tagDoc.type;
+ activeItem.presIndexedStart = type === DocumentType.COL ? 1 : 0;
+ // a progressivized slide doesn't have sub-slides, but rather iterates over the data list of the target being progressivized.
+ // to avoid creating a new slide to correspond to each of the target's data list, we create a computedField to refernce the target's data list.
+ let dataField = Doc.LayoutFieldKey(tagDoc);
+ if (Cast(tagDoc[dataField], listSpec(Doc), null)?.filter(d => d instanceof Doc) === undefined) dataField = dataField + '-annotations';
+
+ if (DocCast(activeItem.presentationTargetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`self.presentationTargetDoc.annotationOn["${dataField}"]`);
+ else activeItem.data = ComputedField.MakeFunction(`self.presentationTargetDoc["${dataField}"]`);
+ }}
+ checked={Cast(activeItem.presIndexed, 'number', null) !== undefined ? true : false}
+ />
+ </div>
+ <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
+ <div className="presBox-subheading">Progressivize First Bullet</div>
+ <input className="presBox-checkbox" style={{ margin: 10 }} type="checkbox" onChange={() => (activeItem.presIndexedStart = activeItem.presIndexedStart ? 0 : 1)} checked={!NumCast(activeItem.presIndexedStart)} />
+ </div>
+ <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
+ <div className="presBox-subheading">Expand Current Bullet</div>
+ <input className="presBox-checkbox" style={{ margin: 10 }} type="checkbox" onChange={() => (activeItem.presBulletExpand = !activeItem.presBulletExpand)} checked={BoolCast(activeItem.presBulletExpand)} />
+ </div>
+
+ <div className="ribbon-box">
+ Bullet Effect
+ <div
+ className="presBox-dropdown"
+ onClick={action(e => {
+ e.stopPropagation();
+ this._openBulletEffectDropdown = !this._openBulletEffectDropdown;
+ })}
+ style={{ borderBottomLeftRadius: this._openBulletEffectDropdown ? 0 : 5, border: this._openBulletEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
+ {effect?.toString()}
+ <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openBulletEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
+ <div className={'presBox-dropdownOptions'} style={{ display: this._openBulletEffectDropdown ? 'grid' : 'none' }} onPointerDown={e => e.stopPropagation()}>
+ {bulletEffect(PresEffect.None)}
+ {bulletEffect(PresEffect.Fade)}
+ {bulletEffect(PresEffect.Flip)}
+ {bulletEffect(PresEffect.Rotate)}
+ {bulletEffect(PresEffect.Bounce)}
+ {bulletEffect(PresEffect.Roll)}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ return null;
+ }
@computed get transitionDropdown() {
- const activeItem: Doc = this.activeItem;
- const targetDoc: Doc = this.targetDoc;
+ const activeItem = this.activeItem;
const presEffect = (effect: PresEffect) => (
- <div className={`presBox-dropdownOption ${activeItem.presEffect === effect || (effect === PresEffect.None && !activeItem.presEffect) ? 'active' : ''}`} onPointerDown={StopEvent} onClick={() => this.updateEffect(effect)}>
+ <div className={`presBox-dropdownOption ${activeItem.presEffect === effect || (effect === PresEffect.None && !activeItem.presEffect) ? 'active' : ''}`} onPointerDown={StopEvent} onClick={() => this.updateEffect(effect, false)}>
{effect}
</div>
);
@@ -1387,14 +1619,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</Tooltip>
);
};
- if (activeItem && targetDoc) {
- const type = targetDoc.type;
+ if (activeItem && this.targetDoc) {
const transitionSpeed = activeItem.presTransition ? NumCast(activeItem.presTransition) / 1000 : 0.5;
const zoom = NumCast(activeItem.presZoom, 1) * 100;
- let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 0;
- 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;
return (
<div
className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presStatus === PresStatus.Edit ? 'active' : ''}`}
@@ -1404,6 +1632,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
e.stopPropagation();
this._openMovementDropdown = false;
this._openEffectDropdown = false;
+ this._openBulletEffectDropdown = false;
})}>
<div className="ribbon-box">
Movement
@@ -1427,33 +1656,33 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Zoom ? 'inline-flex' : 'none' }}>
<div className="presBox-subheading">Zoom (% screen filled)</div>
<div className="ribbon-property">
- <input className="presBox-input" type="number" value={zoom} onChange={action(e => this.setZoom(e.target.value))} />%
+ <input className="presBox-input" type="number" value={zoom} onChange={e => this.updateZoom(e.target.value)} />%
</div>
<div className="ribbon-propertyUpDown">
- <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), 0.1))}>
+ <div className="ribbon-propertyUpDownItem" onClick={() => this.updateZoom(String(zoom), 0.1)}>
<FontAwesomeIcon icon={'caret-up'} />
</div>
- <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), -0.1))}>
+ <div className="ribbon-propertyUpDownItem" onClick={() => this.updateZoom(String(zoom), -0.1)}>
<FontAwesomeIcon icon={'caret-down'} />
</div>
</div>
</div>
- {PresBox.inputter('0', '1', '100', zoom, activeItem.presMovement === PresMovement.Zoom, this.setZoom)}
+ {PresBox.inputter('0', '1', '100', zoom, activeItem.presMovement === PresMovement.Zoom, this.updateZoom)}
<div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
- <div className="presBox-subheading">Transition Speed</div>
+ <div className="presBox-subheading">Transition Time</div>
<div className="ribbon-property">
- <input className="presBox-input" type="number" value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.setTransitionTime(e.target.value))} /> s
+ <input className="presBox-input" type="number" value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s
</div>
<div className="ribbon-propertyUpDown">
- <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setTransitionTime(String(transitionSpeed), 1000))}>
+ <div className="ribbon-propertyUpDownItem" onClick={() => this.updateTransitionTime(String(transitionSpeed), 1000)}>
<FontAwesomeIcon icon={'caret-up'} />
</div>
- <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setTransitionTime(String(transitionSpeed), -1000))}>
+ <div className="ribbon-propertyUpDownItem" onClick={() => this.updateTransitionTime(String(transitionSpeed), -1000)}>
<FontAwesomeIcon icon={'caret-down'} />
</div>
</div>
</div>
- {PresBox.inputter('0.1', '0.1', '100', transitionSpeed, true, this.setTransitionTime)}
+ {PresBox.inputter('0.1', '0.1', '100', transitionSpeed, true, this.updateTransitionTime)}
<div className={'slider-headers'}>
<div className="slider-text">Fast</div>
<div className="slider-text">Medium</div>
@@ -1461,62 +1690,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
</div>
<div className="ribbon-box">
- Visibility {'&'} Duration
- <div className="ribbon-doubleButton">
- <Tooltip title={<div className="dash-tooltip">{'Hide before presented'}</div>}>
- <div className={`ribbon-toggle ${activeItem.presHideBefore ? 'active' : ''}`} onClick={() => this.updateHideBefore(activeItem)}>
- Hide before
- </div>
- </Tooltip>
- <Tooltip title={<div className="dash-tooltip">{'Hide while presented'}</div>}>
- <div className={`ribbon-toggle ${activeItem.presHide ? 'active' : ''}`} onClick={() => this.updateHide(activeItem)}>
- Hide
- </div>
- </Tooltip>
-
- <Tooltip title={<div className="dash-tooltip">{'Hide after presented'}</div>}>
- <div className={`ribbon-toggle ${activeItem.presHideAfter ? 'active' : ''}`} onClick={() => this.updateHideAfter(activeItem)}>
- Hide after
- </div>
- </Tooltip>
-
- <Tooltip title={<div className="dash-tooltip">{'Open in lightbox view'}</div>}>
- <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presOpenInLightbox ? Colors.LIGHT_BLUE : '' }} onClick={() => this.updateOpenDoc(activeItem)}>
- Lightbox
- </div>
- </Tooltip>
- <Tooltip title={<div className="dash-tooltip">{'Transition movement style'}</div>}>
- <div className="ribbon-toggle" onClick={() => this.updateEaseFunc(activeItem)}>
- {`${StrCast(activeItem.presEaseFunc, 'ease')}`}
- </div>
- </Tooltip>
- </div>
- {type === DocumentType.AUDIO || type === DocumentType.VID ? null : (
- <>
- <div className="ribbon-doubleButton">
- <div className="presBox-subheading">Slide Duration</div>
- <div className="ribbon-property">
- <input className="presBox-input" type="number" value={duration} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.setDurationTime(e.target.value))} /> s
- </div>
- <div className="ribbon-propertyUpDown">
- <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), 1000))}>
- <FontAwesomeIcon icon={'caret-up'} />
- </div>
- <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), -1000))}>
- <FontAwesomeIcon icon={'caret-down'} />
- </div>
- </div>
- </div>
- {PresBox.inputter('0.1', '0.1', '20', duration, targetDoc.type !== DocumentType.AUDIO, this.setDurationTime)}
- <div className={'slider-headers'} style={{ display: targetDoc.type === DocumentType.AUDIO ? 'none' : 'grid' }}>
- <div className="slider-text">Short</div>
- <div className="slider-text">Medium</div>
- <div className="slider-text">Long</div>
- </div>
- </>
- )}
- </div>
- <div className="ribbon-box">
Effects
<div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
<div className="presBox-subheading">Play Audio Annotation</div>
@@ -1565,173 +1738,154 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
}
}
-
- @undoBatch
- @action
- applyTo = (array: Doc[]) => {
- this.updateMovement(this.activeItem.presMovement as PresMovement, true);
- this.updateEffect(this.activeItem.presEffect as PresEffect, true);
- this.updateEffectDirection(this.activeItem.presEffectDirection as PresEffectDirection, true);
- const { presTransition, presDuration, presHideBefore, presHideAfter } = this.activeItem;
- array.forEach(curDoc => {
- curDoc.presTransition = presTransition;
- curDoc.presDuration = presDuration;
- curDoc.presHideBefore = presHideBefore;
- curDoc.presHideAfter = presHideAfter;
- });
- };
-
@computed get mediaOptionsDropdown() {
- const activeItem: Doc = this.activeItem;
- const targetDoc: Doc = this.targetDoc;
- const clipStart: number = NumCast(activeItem.clipStart);
- const clipEnd: number = NumCast(activeItem.clipEnd, NumCast(activeItem[Doc.LayoutFieldKey(activeItem) + '-duration']));
- const mediaStopDocInd: number = NumCast(activeItem.mediaStopDoc);
- if (activeItem && targetDoc) {
+ const activeItem = this.activeItem;
+ if (activeItem && this.targetDoc) {
+ const clipStart = NumCast(activeItem.clipStart);
+ const clipEnd = NumCast(activeItem.clipEnd, NumCast(activeItem[Doc.LayoutFieldKey(activeItem) + '-duration']));
return (
- <div>
- <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
- <div>
- <div className="ribbon-box">
- Start {'&'} End Time
- <div className={'slider-headers'}>
- <div className="slider-block">
- <div className="slider-text" style={{ fontWeight: 500 }}>
- Start time (s)
- </div>
- <div id={'startTime'} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}>
- <input
- className="presBox-input"
- style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
- type="number"
- value={NumCast(activeItem.presStartTime).toFixed(2)}
- onKeyDown={e => e.stopPropagation()}
- onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
- activeItem.presStartTime = Number(e.target.value);
- })}
- />
- </div>
- </div>
- <div className="slider-block">
- <div className="slider-text" style={{ fontWeight: 500 }}>
- Duration (s)
- </div>
- <div className="slider-number" style={{ backgroundColor: Colors.LIGHT_BLUE }}>
- {Math.round((NumCast(activeItem.presEndTime) - NumCast(activeItem.presStartTime)) * 10) / 10}
- </div>
+ <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+ <div>
+ <div className="ribbon-box">
+ Start {'&'} End Time
+ <div className={'slider-headers'}>
+ <div className="slider-block">
+ <div className="slider-text" style={{ fontWeight: 500 }}>
+ Start time (s)
</div>
- <div className="slider-block">
- <div className="slider-text" style={{ fontWeight: 500 }}>
- End time (s)
- </div>
- <div id={'endTime'} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}>
- <input
- className="presBox-input"
- onKeyDown={e => e.stopPropagation()}
- style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
- type="number"
- value={NumCast(activeItem.presEndTime).toFixed(2)}
- onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
- activeItem.presEndTime = Number(e.target.value);
- })}
- />
- </div>
+ <div id={'startTime'} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}>
+ <input
+ className="presBox-input"
+ style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
+ type="number"
+ value={NumCast(activeItem.presStartTime).toFixed(2)}
+ onKeyDown={e => e.stopPropagation()}
+ onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
+ activeItem.presStartTime = Number(e.target.value);
+ })}
+ />
</div>
</div>
- <div className="multiThumb-slider">
- <input
- type="range"
- step="0.1"
- min={clipStart}
- max={clipEnd}
- value={NumCast(activeItem.presEndTime)}
- style={{ gridColumn: 1, gridRow: 1 }}
- className={`toolbar-slider ${'end'}`}
- id="toolbar-slider"
- onPointerDown={e => {
- this._batch = UndoManager.StartBatch('presEndTime');
- const endBlock = document.getElementById('endTime');
- if (endBlock) {
- endBlock.style.color = Colors.LIGHT_GRAY;
- endBlock.style.backgroundColor = Colors.MEDIUM_BLUE;
- }
- e.stopPropagation();
- }}
- onPointerUp={() => {
- this._batch?.end();
- const endBlock = document.getElementById('endTime');
- if (endBlock) {
- endBlock.style.color = Colors.BLACK;
- endBlock.style.backgroundColor = Colors.LIGHT_GRAY;
- }
- }}
- onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
- e.stopPropagation();
- activeItem.presEndTime = Number(e.target.value);
- }}
- />
- <input
- type="range"
- step="0.1"
- min={clipStart}
- max={clipEnd}
- value={NumCast(activeItem.presStartTime)}
- style={{ gridColumn: 1, gridRow: 1 }}
- className={`toolbar-slider ${'start'}`}
- id="toolbar-slider"
- onPointerDown={e => {
- this._batch = UndoManager.StartBatch('presStartTime');
- const startBlock = document.getElementById('startTime');
- if (startBlock) {
- startBlock.style.color = Colors.LIGHT_GRAY;
- startBlock.style.backgroundColor = Colors.MEDIUM_BLUE;
- }
- e.stopPropagation();
- }}
- onPointerUp={() => {
- this._batch?.end();
- const startBlock = document.getElementById('startTime');
- if (startBlock) {
- startBlock.style.color = Colors.BLACK;
- startBlock.style.backgroundColor = Colors.LIGHT_GRAY;
- }
- }}
- onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
- e.stopPropagation();
- activeItem.presStartTime = Number(e.target.value);
- }}
- />
- </div>
- <div className="slider-headers">
- <div className="slider-text">{clipStart.toFixed(2)} s</div>
- <div className="slider-text"></div>
- <div className="slider-text">{clipEnd.toFixed(2)} s</div>
- </div>
- </div>
- <div className="ribbon-final-box">
- Playback
- <div className="presBox-subheading">Start playing:</div>
- <div className="presBox-radioButtons">
- <div className="checkbox-container">
- <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStart = 'manual')} checked={activeItem.mediaStart === 'manual'} />
- <div>On click</div>
+ <div className="slider-block">
+ <div className="slider-text" style={{ fontWeight: 500 }}>
+ Duration (s)
</div>
- <div className="checkbox-container">
- <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStart = 'auto')} checked={activeItem.mediaStart === 'auto'} />
- <div>Automatically</div>
+ <div className="slider-number" style={{ backgroundColor: Colors.LIGHT_BLUE }}>
+ {Math.round((NumCast(activeItem.presEndTime) - NumCast(activeItem.presStartTime)) * 10) / 10}
</div>
</div>
- <div className="presBox-subheading">Stop playing:</div>
- <div className="presBox-radioButtons">
- <div className="checkbox-container">
- <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStop = 'manual')} checked={activeItem.mediaStop === 'manual'} />
- <div>At audio end time</div>
+ <div className="slider-block">
+ <div className="slider-text" style={{ fontWeight: 500 }}>
+ End time (s)
</div>
- <div className="checkbox-container">
- <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStop = 'auto')} checked={activeItem.mediaStop === 'auto'} />
- <div>On slide change</div>
+ <div id={'endTime'} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}>
+ <input
+ className="presBox-input"
+ onKeyDown={e => e.stopPropagation()}
+ style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
+ type="number"
+ value={NumCast(activeItem.presEndTime).toFixed(2)}
+ onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
+ activeItem.presEndTime = Number(e.target.value);
+ })}
+ />
</div>
- {/* <div className="checkbox-container">
+ </div>
+ </div>
+ <div className="multiThumb-slider">
+ <input
+ type="range"
+ step="0.1"
+ min={clipStart}
+ max={clipEnd}
+ value={NumCast(activeItem.presEndTime)}
+ style={{ gridColumn: 1, gridRow: 1 }}
+ className={`toolbar-slider ${'end'}`}
+ id="toolbar-slider"
+ onPointerDown={e => {
+ this._batch = UndoManager.StartBatch('presEndTime');
+ const endBlock = document.getElementById('endTime');
+ if (endBlock) {
+ endBlock.style.color = Colors.LIGHT_GRAY;
+ endBlock.style.backgroundColor = Colors.MEDIUM_BLUE;
+ }
+ e.stopPropagation();
+ }}
+ onPointerUp={() => {
+ this._batch?.end();
+ const endBlock = document.getElementById('endTime');
+ if (endBlock) {
+ endBlock.style.color = Colors.BLACK;
+ endBlock.style.backgroundColor = Colors.LIGHT_GRAY;
+ }
+ }}
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
+ e.stopPropagation();
+ activeItem.presEndTime = Number(e.target.value);
+ }}
+ />
+ <input
+ type="range"
+ step="0.1"
+ min={clipStart}
+ max={clipEnd}
+ value={NumCast(activeItem.presStartTime)}
+ style={{ gridColumn: 1, gridRow: 1 }}
+ className={`toolbar-slider ${'start'}`}
+ id="toolbar-slider"
+ onPointerDown={e => {
+ this._batch = UndoManager.StartBatch('presStartTime');
+ const startBlock = document.getElementById('startTime');
+ if (startBlock) {
+ startBlock.style.color = Colors.LIGHT_GRAY;
+ startBlock.style.backgroundColor = Colors.MEDIUM_BLUE;
+ }
+ e.stopPropagation();
+ }}
+ onPointerUp={() => {
+ this._batch?.end();
+ const startBlock = document.getElementById('startTime');
+ if (startBlock) {
+ startBlock.style.color = Colors.BLACK;
+ startBlock.style.backgroundColor = Colors.LIGHT_GRAY;
+ }
+ }}
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
+ e.stopPropagation();
+ activeItem.presStartTime = Number(e.target.value);
+ }}
+ />
+ </div>
+ <div className="slider-headers">
+ <div className="slider-text">{clipStart.toFixed(2)} s</div>
+ <div className="slider-text"></div>
+ <div className="slider-text">{clipEnd.toFixed(2)} s</div>
+ </div>
+ </div>
+ <div className="ribbon-final-box">
+ Playback
+ <div className="presBox-subheading">Start playing:</div>
+ <div className="presBox-radioButtons">
+ <div className="checkbox-container">
+ <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStart = 'manual')} checked={activeItem.mediaStart === 'manual'} />
+ <div>On click</div>
+ </div>
+ <div className="checkbox-container">
+ <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStart = 'auto')} checked={activeItem.mediaStart === 'auto'} />
+ <div>Automatically</div>
+ </div>
+ </div>
+ <div className="presBox-subheading">Stop playing:</div>
+ <div className="presBox-radioButtons">
+ <div className="checkbox-container">
+ <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStop = 'manual')} checked={activeItem.mediaStop === 'manual'} />
+ <div>At audio end time</div>
+ </div>
+ <div className="checkbox-container">
+ <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStop = 'auto')} checked={activeItem.mediaStop === 'auto'} />
+ <div>On slide change</div>
+ </div>
+ {/* <div className="checkbox-container">
<input className="presBox-checkbox"
type="checkbox"
onChange={() => activeItem.mediaStop = "afterSlide"}
@@ -1748,7 +1902,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</select>
</div>
</div> */}
- </div>
</div>
</div>
</div>
@@ -1756,7 +1909,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
}
}
-
@computed get newDocumentToolbarDropdown() {
return (
<div
@@ -1951,6 +2103,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
onClick={undoBatch(
action(() => {
this.layoutDoc.presStatus = 'manual';
+ this.initializePresState(this.itemIndex);
this.turnOffEdit(true);
this.gotoDocument(this.itemIndex, this.activeItem);
})
@@ -1961,65 +2114,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
}
- _keyTimer: NodeJS.Timeout | undefined;
-
- /**
- * Returns the collection type as a string for headers
- */
- @computed get stringType() {
- if (this.activeItem) {
- // prettier-ignore
- switch (this.targetDoc.type) {
- case DocumentType.PDF: return 'PDF';
- case DocumentType.RTF: return 'Text node';
- case DocumentType.COL: return 'Collection';
- case DocumentType.AUDIO: return 'Audio';
- case DocumentType.VID: return 'Video';
- case DocumentType.IMG: return 'Image';
- case DocumentType.WEB: return 'Web page';
- case DocumentType.MAP: return 'Map';
- default: return 'Other node';
- }
- }
- return '';
- }
-
- @observable private openActiveColorPicker: boolean = false;
- @observable private openViewedColorPicker: boolean = false;
-
- @undoBatch
- @action
- switchActive = (color: ColorState) => {
- this.targetDoc['pres-text-color'] = String(color.hex);
- return true;
- };
- @undoBatch
- @action
- switchPresented = (color: ColorState) => {
- this.targetDoc['pres-text-viewed-color'] = String(color.hex);
- return true;
- };
-
- @computed get activeColorPicker() {
- return !this.openActiveColorPicker ? null : (
- <SketchPicker
- onChange={this.switchActive}
- presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
- color={StrCast(this.targetDoc['pres-text-color'])}
- />
- );
- }
-
- @computed get viewedColorPicker() {
- return !this.openViewedColorPicker ? null : (
- <SketchPicker
- onChange={this.switchPresented}
- presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
- color={StrCast(this.targetDoc['pres-text-viewed-color'])}
- />
- );
- }
-
@action
turnOffEdit = (paths?: boolean) => paths && this.togglePath(true); // Turn off paths
@@ -2056,14 +2150,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
{isMini ? null : (
<>
<div className="toolbar-divider" />
- {/* <Tooltip title={<><div className="dash-tooltip">{this._expandBoolean ? "Minimize all" : "Expand all"}</div></>}>
- <div className={"toolbar-button"}
- style={{ color: this._expandBoolean ? Colors.MEDIUM_BLUE : 'white' }}
- onClick={this.toggleExpandMode}>
- <FontAwesomeIcon icon={"eye"} />
- </div>
- </Tooltip>
- <div className="toolbar-divider" /> */}
<Tooltip title={<div className="dash-tooltip">{this._presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}</div>}>
<div className="toolbar-button" style={{ cursor: this._presKeyEvents ? 'default' : 'pointer', position: 'absolute', right: 30, fontSize: 16 }}>
<FontAwesomeIcon className={'toolbar-thumbtack'} icon={'keyboard'} style={{ color: this._presKeyEvents ? activeColor : inactiveColor }} />
@@ -2113,6 +2199,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
onClick={undoBatch(() => {
if (this.childDocs.length) {
this.layoutDoc.presStatus = 'manual';
+ this.initializePresState(this.itemIndex);
this.gotoDocument(this.itemIndex, this.activeItem);
}
})}>
@@ -2137,7 +2224,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
@computed get playButtons() {
- const presEnd: boolean = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1;
+ const presEnd = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1 && (this.activeItem.presIndexed === undefined || NumCast(this.activeItem.presIndexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0;
const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc);
// Case 1: There are still other frames and should go through all frames before going to next slide
@@ -2225,7 +2312,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
</Tooltip>
<div className="presPanel-button-text" onClick={() => this.gotoDocument(0, this.activeItem)} style={{ display: inOverlay || this.props.PanelWidth() > 250 ? 'inline-flex' : 'none' }}>
- {`${inOverlay ? '' : 'Slide'} ${this.itemIndex + 1} / ${this.childDocs.length}`}
+ {inOverlay ? '' : 'Slide'} {this.itemIndex + 1}
+ {this.activeItem?.presIndexed !== undefined ? `(${this.activeItem.presIndexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length}
</div>
<div className="presPanel-divider"></div>
{this.props.PanelWidth() > 250 ? (
@@ -2272,6 +2360,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.layoutDoc.presStatus = PresStatus.Manual;
}
};
+
@undoBatch
@action
exitClicked = () => {
@@ -2279,7 +2368,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
clearTimeout(this._presTimer);
};
- AddToMap = (treeViewDoc: Doc, index: number[]): Doc[] => {
+ AddToMap = (treeViewDoc: Doc, index: number[]) => {
+ if (!treeViewDoc.presentationTargetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element.
var indexNum = 0;
for (let i = 0; i < index.length; i++) {
indexNum += index[i] * 10 ** -i;
@@ -2292,25 +2382,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.dataDoc[this.presFieldKey] = new List<Doc>(sorted); // this is a flat array of Docs
}
}
- return this.childDocs;
};
- RemFromMap = (treeViewDoc: Doc, index: number[]): Doc[] => {
+ RemFromMap = (treeViewDoc: Doc, index: number[]) => {
+ if (!treeViewDoc.presentationTargetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element.
if (!this._unmounting && this.isTree) {
this._treeViewMap.delete(treeViewDoc);
this.dataDoc[this.presFieldKey] = new List<Doc>(this.sort(this._treeViewMap));
}
- return this.childDocs;
};
- // TODO: [AL] implement sort function for an array of numbers (e.g. arr[1,2,4] v arr[1,2,1])
sort = (treeViewMap: Map<Doc, number>) => [...treeViewMap.entries()].sort((a: [Doc, number], b: [Doc, number]) => (a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0)).map(kv => kv[0]);
render() {
// needed to ensure that the childDocs are loaded for looking up fields
this.childDocs.slice();
const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
- const presEnd = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1;
+ const presEnd = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1 && (this.activeItem.presIndexed === undefined || NumCast(this.activeItem.presIndexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
const presStart = !this.layoutDoc.presLoop && this.itemIndex === 0;
const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc);
return this.props.addDocTab === returnFalse ? ( // bcz: hack!! - addDocTab === returnFalse only when this is being rendered by the OverlayView which means the doc is a mini player
@@ -2345,7 +2433,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
</Tooltip>
<div className="presPanel-button-text">
- Slide {this.itemIndex + 1} / {this.childDocs.length}
+ Slide {this.itemIndex + 1}
+ {this.activeItem.presIndexed !== undefined ? `(${this.activeItem.presIndexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length}
</div>
<div className="presPanel-divider" />
<div className="presPanel-button-text" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.exitClicked, false, false)}>
@@ -2372,6 +2461,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
//childFitWidth={returnTrue}
childOpacity={returnOne}
//childLayoutString={PresElementBox.LayoutString('data')}
+ childClickScript={PresBox.navigateToDocScript}
childLayoutTemplate={this.childLayoutTemplate}
childXPadding={Doc.IsComicStyle(this.rootDoc) ? 20 : undefined}
filterAddDocument={this.addDocumentFilter}
@@ -2399,11 +2489,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
);
}
- static NavigateToDoc(bestTarget: Doc, activeItem: Doc) {
- PresBox.NavigateToTarget(bestTarget, activeItem);
- }
}
ScriptingGlobals.add(function navigateToDoc(bestTarget: Doc, activeItem: Doc) {
- PresBox.NavigateToDoc(bestTarget, activeItem);
+ PresBox.NavigateToTarget(bestTarget, activeItem);
});
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 3ba60edd7..fe9b13fbe 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -5,7 +5,6 @@ import * as React from 'react';
import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
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';
@@ -470,7 +469,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
}
}
style={{
- fontWeight: DocListCast(fromDoc?.links).find(
+ fontWeight: LinkManager.Links(fromDoc).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'
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index ed5eaa756..ee164ab31 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1304,6 +1304,7 @@ export namespace Doc {
}
export function linkFollowUnhighlight() {
+ clearTimeout(UnhighlightTimer);
UnhighlightWatchers.forEach(watcher => watcher());
UnhighlightWatchers.length = 0;
highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc));
@@ -1345,12 +1346,15 @@ export namespace Doc {
}
});
}
- export function UnHighlightDoc(doc: Doc) {
+ /// if doc is defined, then it is unhighlighted, otherwise all highlighted docs are unhighlighted
+ export function UnHighlightDoc(doc?: Doc) {
runInAction(() => {
- highlightedDocs.delete(doc);
- highlightedDocs.delete(Doc.GetProto(doc));
- doc[HighlightSym] = Doc.GetProto(doc)[HighlightSym] = false;
- doc[AnimationSym] = undefined;
+ (doc ? [doc] : Array.from(highlightedDocs)).forEach(doc => {
+ highlightedDocs.delete(doc);
+ highlightedDocs.delete(Doc.GetProto(doc));
+ doc[HighlightSym] = Doc.GetProto(doc)[HighlightSym] = false;
+ doc[AnimationSym] = undefined;
+ });
});
}
export function UnBrushAllDocs() {
@@ -1420,14 +1424,14 @@ export namespace Doc {
// filters document in a container collection:
// all documents with the specified value for the specified key are included/excluded
// based on the modifiers :"check", "x", undefined
- export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldSuffix?: string, append: boolean = true) {
+ export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldPrefix?: string, append: boolean = true) {
if (!container) return;
- const filterField = '_' + (fieldSuffix ? fieldSuffix + '-' : '') + 'docFilters';
+ const filterField = '_' + (fieldPrefix ? fieldPrefix + '-' : '') + 'docFilters';
const docFilters = Cast(container[filterField], listSpec('string'), []);
runInAction(() => {
for (let i = 0; i < docFilters.length; i++) {
const fields = docFilters[i].split(':'); // split key:value:modifier
- if (fields[0] === key && (fields[1] === value || modifiers === 'match')) {
+ if (fields[0] === key && (fields[1] === value || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) {
if (fields[2] === modifiers && modifiers && fields[1] === value) {
if (toggle) modifiers = 'remove';
else return;
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 16da0f9e2..b5eca78dd 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -212,6 +212,7 @@ export class ComputedField extends ScriptField {
return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
}
public static MakeInterpolatedDataField(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number) {
+ if (doc[`${fieldKey}`] instanceof List) return;
if (!doc[`${fieldKey}-indexed`]) {
const flist = new List<Field>(numberRange(curTimecode + 1).map(i => undefined) as any as Field[]);
flist[curTimecode] = Field.Copy(doc[fieldKey]);
@@ -219,12 +220,12 @@ export class ComputedField extends ScriptField {
}
const getField = ScriptField.CompileScript(`getIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey})`, {}, true, {});
const setField = ScriptField.CompileScript(
- `{setIndexVal (self['${fieldKey}-indexed'], self.${interpolatorKey}, value); console.log(self["data-indexed"][self.${interpolatorKey}],self.data,self["data-indexed"]))}`,
+ `{setIndexVal (self['${fieldKey}-indexed'], self.${interpolatorKey}, value); console.log(self["${fieldKey}-indexed"][self.${interpolatorKey}],self.data,self["${fieldKey}-indexed"]))}`,
{ value: 'any' },
false,
{}
);
- return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
+ return (doc[`${fieldKey}`] = getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined);
}
}
export namespace ComputedField {
diff --git a/src/fields/Types.ts b/src/fields/Types.ts
index 3ef7cb1de..4cf286a32 100644
--- a/src/fields/Types.ts
+++ b/src/fields/Types.ts
@@ -79,7 +79,8 @@ export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal
}
export function DocCast(field: FieldResult, defaultVal?: Doc) {
- return Cast(field, Doc, null) ?? defaultVal;
+ const doc = Cast(field, Doc, null);
+ return doc && !(doc instanceof Promise) ? doc : (defaultVal as Doc);
}
export function NumCast(field: FieldResult, defaultVal: number | null = 0) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index e517e7604..6ff13d5d3 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,8 +1,8 @@
-import { forEach } from 'lodash';
import { $mobx, action, observable, runInAction, trace } from 'mobx';
import { computedFn } from 'mobx-utils';
import { DocServer } from '../client/DocServer';
import { CollectionViewType } from '../client/documents/DocumentTypes';
+import { LinkManager } from '../client/util/LinkManager';
import { SerializationHelper } from '../client/util/SerializationHelper';
import { UndoManager } from '../client/util/UndoManager';
import { returnZero } from '../Utils';
@@ -18,7 +18,6 @@ import {
Doc,
DocListCast,
DocListCastAsync,
- FieldResult,
ForceServerWrite,
HeightSym,
HierarchyMapping,
@@ -259,7 +258,7 @@ function getEffectiveAcl(target: any, user?: string): symbol {
*/
export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean, visited?: Doc[], isDashboard?: boolean) {
if (!visited) visited = [] as Doc[];
- if (visited.includes(target)) return;
+ if (!target || visited.includes(target)) return;
if ((target._viewType === CollectionViewType.Docking && visited.length > 1) || Doc.GetProto(visited[0]) !== Doc.GetProto(target)) {
target[key] = acl;
@@ -293,26 +292,18 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
}
// maps over the links of the document
- DocListCast(dataDoc.links).forEach(link => distributeAcls(key, acl, link, inheritingFromCollection, visited));
+ LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, inheritingFromCollection, visited));
// maps over the children of the document
- DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + (isDashboard ? '-all' : '')]).map(d => {
+ DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).forEach(d => {
distributeAcls(key, acl, d, inheritingFromCollection, visited);
- // }
- const data = d[DataSym];
- if (data) {
- distributeAcls(key, acl, data, inheritingFromCollection, visited);
- }
+ distributeAcls(key, acl, d[DataSym], inheritingFromCollection, visited);
});
// maps over the annotations of the document
- DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '-annotations']).map(d => {
+ DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '-annotations']).forEach(d => {
distributeAcls(key, acl, d, inheritingFromCollection, visited);
- // }
- const data = d[DataSym];
- if (data) {
- distributeAcls(key, acl, data, inheritingFromCollection, visited);
- }
+ distributeAcls(key, acl, d[DataSym], inheritingFromCollection, visited);
});
}