aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2023-12-10 20:19:27 -0500
committerbobzel <zzzman@gmail.com>2023-12-10 20:19:27 -0500
commit380ee1acac1c0b7972d7d423cf804af146dc0edf (patch)
tree1d77244a600e6eb1fb6d56356b3ce01ca6add89d /src
parentb7b7105fac83ec11480204c5c7ac0ae6579774e1 (diff)
massive changes to use mobx 6 which means not accessing props directly in @computed functions.
Diffstat (limited to 'src')
-rw-r--r--src/.babelrc3
-rw-r--r--src/Utils.ts7
-rw-r--r--src/client/DocServer.ts2
-rw-r--r--src/client/documents/DocumentTypes.ts1
-rw-r--r--src/client/documents/Documents.ts13
-rw-r--r--src/client/util/DocumentManager.ts11
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx3
-rw-r--r--src/client/util/LinkManager.ts14
-rw-r--r--src/client/util/RTFMarkup.tsx2
-rw-r--r--src/client/util/SelectionManager.ts20
-rw-r--r--src/client/util/ServerStats.tsx6
-rw-r--r--src/client/util/SettingsManager.tsx8
-rw-r--r--src/client/util/SharingManager.tsx4
-rw-r--r--src/client/util/SnappingManager.ts2
-rw-r--r--src/client/views/AntimodeMenu.tsx19
-rw-r--r--src/client/views/AudioWaveform.tsx117
-rw-r--r--src/client/views/ContextMenu.tsx99
-rw-r--r--src/client/views/ContextMenuItem.tsx20
-rw-r--r--src/client/views/DocComponent.tsx61
-rw-r--r--src/client/views/DocumentButtonBar.tsx61
-rw-r--r--src/client/views/DocumentDecorations.tsx74
-rw-r--r--src/client/views/EditableView.tsx111
-rw-r--r--src/client/views/InkStrokeProperties.ts2
-rw-r--r--src/client/views/InkingStroke.tsx4
-rw-r--r--src/client/views/MainView.tsx19
-rw-r--r--src/client/views/MainViewModal.tsx2
-rw-r--r--src/client/views/MarqueeAnnotator.tsx19
-rw-r--r--src/client/views/PreviewCursor.tsx135
-rw-r--r--src/client/views/PropertiesView.tsx53
-rw-r--r--src/client/views/StyleProvider.tsx4
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx45
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx101
-rw-r--r--src/client/views/collections/CollectionMenu.tsx45
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx1
-rw-r--r--src/client/views/collections/CollectionPileView.tsx2
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx217
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx187
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx139
-rw-r--r--src/client/views/collections/CollectionSubView.tsx92
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx192
-rw-r--r--src/client/views/collections/CollectionView.tsx53
-rw-r--r--src/client/views/collections/TabDocView.tsx91
-rw-r--r--src/client/views/collections/TreeView.tsx505
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx16
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx38
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx376
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx142
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx2
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.tsx44
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx1
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx1
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx10
-rw-r--r--src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx2
-rw-r--r--src/client/views/collections/collectionSchema/SchemaRowBox.tsx4
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTableCell.tsx141
-rw-r--r--src/client/views/global/globalScripts.ts8
-rw-r--r--src/client/views/linking/LinkMenu.tsx8
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx96
-rw-r--r--src/client/views/nodes/AudioBox.tsx75
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx109
-rw-r--r--src/client/views/nodes/ColorBox.scss22
-rw-r--r--src/client/views/nodes/ColorBox.tsx88
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.tsx4
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx60
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx106
-rw-r--r--src/client/views/nodes/DocumentView.tsx419
-rw-r--r--src/client/views/nodes/EquationBox.tsx34
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.tsx32
-rw-r--r--src/client/views/nodes/FunctionPlotBox.tsx37
-rw-r--r--src/client/views/nodes/ImageBox.tsx198
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx38
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx37
-rw-r--r--src/client/views/nodes/LabelBox.tsx29
-rw-r--r--src/client/views/nodes/LinkBox.tsx2
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx4
-rw-r--r--src/client/views/nodes/LoadingBox.tsx14
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx84
-rw-r--r--src/client/views/nodes/MapBox/MapBox2.tsx2
-rw-r--r--src/client/views/nodes/MapBox/MapPushpinBox.tsx14
-rw-r--r--src/client/views/nodes/PDFBox.tsx95
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx2
-rw-r--r--src/client/views/nodes/VideoBox.tsx123
-rw-r--r--src/client/views/nodes/WebBox.tsx4
-rw-r--r--src/client/views/nodes/audio/AudioWaveform.scss (renamed from src/client/views/AudioWaveform.scss)0
-rw-r--r--src/client/views/nodes/audio/AudioWaveform.tsx127
-rw-r--r--src/client/views/nodes/audio/WaveCanvas.tsx100
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx2
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx14
-rw-r--r--src/client/views/nodes/formattedText/EquationEditor.scss468
-rw-r--r--src/client/views/nodes/formattedText/EquationEditor.tsx8
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx213
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx48
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx61
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx64
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx24
-rw-r--r--src/client/views/pdf/PDFViewer.tsx151
-rw-r--r--src/client/views/topbar/TopBar.tsx1
-rw-r--r--src/fields/Doc.ts40
-rw-r--r--src/fields/List.ts5
-rw-r--r--src/fields/util.ts2
-rw-r--r--src/server/ApiManagers/GooglePhotosManager.ts155
-rw-r--r--src/server/DashUploadUtils.ts27
103 files changed, 3735 insertions, 2866 deletions
diff --git a/src/.babelrc b/src/.babelrc
new file mode 100644
index 000000000..86ef21087
--- /dev/null
+++ b/src/.babelrc
@@ -0,0 +1,3 @@
+{
+ "presets": ["@babel/preset-env", "@babel/preset-react"]
+}
diff --git a/src/Utils.ts b/src/Utils.ts
index 26514622c..d54760100 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -7,6 +7,7 @@ import { DocumentType } from './client/documents/DocumentTypes';
import { Colors } from './client/views/global/globalEnums';
import { Message } from './server/Message';
import * as Color from 'color';
+import { action, makeObservable } from 'mobx';
export namespace Utils {
export let CLICK_TIME = 300;
@@ -439,6 +440,12 @@ export namespace Utils {
socket.on(message, (room: any) => handler(socket, room));
}
}
+export function copyProps(thing: { _prevProps: any; props: any; _props: any }) {
+ Object.keys(thing._prevProps).forEach(action(pkey =>
+ (thing._prevProps as any)[pkey] !== (thing.props as any)[pkey] &&
+ ((thing._props as any)[pkey] = (thing.props as any)[pkey]))); // prettier-ignore
+ thing._prevProps = thing.props;
+}
export function OmitKeys(obj: any, keys: string[], pattern?: string, addKeyFunc?: (dup: any) => void): { omit: any; extract: any } {
const omit: any = { ...obj };
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index df92fe50e..6217cf04b 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -57,7 +57,7 @@ export namespace DocServer {
Doc.AddDocToList(Doc.MyRecentlyClosed, undefined, link);
}
});
- LinkManager.userLinkDBs.forEach(linkDb => Doc.FindReferences(linkDb, references, undefined));
+ LinkManager.Instance.userLinkDBs.forEach(linkDb => Doc.FindReferences(linkDb, references, undefined));
const filtered = Array.from(references);
const newCacheUpdate = filtered.map(doc => doc[Id]).join(';');
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 87010f770..427b11751 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -32,7 +32,6 @@ export enum DocumentType {
IMPORT = 'import',
PRES = 'presentation',
PRESELEMENT = 'preselement',
- COLOR = 'color',
YOUTUBE = 'youtube',
COMPARISON = 'comparison',
GROUP = 'group',
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 4ec100728..37196187b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -32,7 +32,6 @@ import { ContextMenu } from '../views/ContextMenu';
import { ContextMenuProps } from '../views/ContextMenuItem';
import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke';
import { AudioBox, media_state } from '../views/nodes/AudioBox';
-import { ColorBox } from '../views/nodes/ColorBox';
import { ComparisonBox } from '../views/nodes/ComparisonBox';
import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox';
import { EquationBox } from '../views/nodes/EquationBox';
@@ -496,13 +495,6 @@ export namespace Docs {
},
],
[
- DocumentType.COLOR,
- {
- layout: { view: ColorBox, dataField: defaultDataKey },
- options: { _nativeWidth: 220, _nativeHeight: 300 },
- },
- ],
- [
DocumentType.IMG,
{
layout: { view: ImageBox, dataField: defaultDataKey },
@@ -1018,9 +1010,6 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.SEARCH), new List<Doc>([]), options);
}
- export function ColorDocument(options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.COLOR), '', options);
- }
export function LoadingDocument(file: File | string, options: DocumentOptions) {
return InstanceFromProto(Prototypes.get(DocumentType.LOADING), undefined, { _height: 150, _width: 200, title: typeof file == 'string' ? file : file.name, ...options }, undefined, '');
}
@@ -1638,7 +1627,7 @@ export namespace DocUtils {
options = { ...options, _width: 400, _height: 512, title: path };
}
- return ctor ? ctor(path, options, overwriteDoc) : undefined;
+ return ctor ? ctor(path, overwriteDoc ? { ...options, title: StrCast(overwriteDoc.title, path) } : options, overwriteDoc) : undefined;
}
export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string): void {
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index ad5a1654d..7f82ff70a 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,4 +1,4 @@
-import { action, computed, observable, ObservableSet, observe } from 'mobx';
+import { action, computed, makeObservable, observable, ObservableSet, observe } from 'mobx';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { AclAdmin, AclEdit, Animation } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
@@ -22,8 +22,8 @@ const { Howl } = require('howler');
export class DocumentManager {
//global holds all of the nodes (regardless of which collection they're in)
@observable _documentViews = new Set<DocumentView>();
- @observable public LinkAnchorBoxViews: DocumentView[] = [];
- @observable public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = [];
+ @observable public LinkAnchorBoxViews: DocumentView[] = observable([]);
+ @observable public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = observable([]);
@computed public get DocumentViews() {
return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(view.docViewPath)));
}
@@ -38,11 +38,12 @@ export class DocumentManager {
public static get Instance(): DocumentManager {
return this._instance || (this._instance = new this());
}
+ @observable public CurrentlyLoading: Doc[] = observable([]); // this assignment doesn't work. the actual assignment happens in DocumentManager's constructor
//private constructor so no other class can create a nodemanager
private constructor() {
- if (!LoadingBox.CurrentlyLoading) LoadingBox.CurrentlyLoading = [];
- observe(LoadingBox.CurrentlyLoading, change => {
+ makeObservable(this);
+ observe(this.CurrentlyLoading, change => {
// watch CurrentlyLoading-- when something is loaded, it's removed from the list and we have to update its icon if it were iconified since LoadingBox icons are different than the media they become
switch (change.type as any) {
case 'update':
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 30e448797..55aac5eb0 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx
@@ -21,7 +21,6 @@ import { DocumentManager } from '../DocumentManager';
import './DirectoryImportBox.scss';
import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from './ImportMetadataEntry';
import * as React from 'react';
-import * as _ from 'lodash';
const unsupported = ['text/html', 'text/plain'];
@@ -157,7 +156,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
x: NumCast(doc.x),
y: NumCast(doc.y) + offset,
};
- const parent = this.props.DocumentView?.().props.docViewPath().lastElement();
+ const parent = this.props.DocumentView?.()._props.docViewPath().lastElement();
if (parent?.Document.type === DocumentType.COL) {
let importContainer: Doc;
if (docs.length < 50) {
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 3e98ea379..8346949ba 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,4 +1,4 @@
-import { action, observable, observe, runInAction } from 'mobx';
+import { action, makeObservable, observable, observe, runInAction } from 'mobx';
import { computedFn } from 'mobx-utils';
import { Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc';
import { DirectLinks } from '../../fields/DocSymbols';
@@ -22,7 +22,7 @@ import { ScriptingGlobals } from './ScriptingGlobals';
*/
export class LinkManager {
@observable static _instance: LinkManager;
- @observable static userLinkDBs: Doc[] = [];
+ @observable userLinkDBs: Doc[] = observable([]);
@observable public static currentLink: Opt<Doc>;
@observable public static currentLinkAnchor: Opt<Doc>;
public static get Instance() {
@@ -32,21 +32,21 @@ export class LinkManager {
public static Links(doc: Doc | undefined) {
return doc ? LinkManager.Instance.getAllRelatedLinks(doc) : [];
}
- public static addLinkDB = async (linkDb: any) => {
+ public addLinkDB = async (linkDb: any) => {
await Promise.all(
((await DocListCastAsync(linkDb.data)) ?? []).map(link =>
// makes sure link anchors are loaded to avoid incremental updates to computedFns in LinkManager
[PromiseValue(link?.link_anchor_1), PromiseValue(link?.link_anchor_2)]
)
);
- LinkManager.userLinkDBs.push(linkDb);
+ this.userLinkDBs.push(linkDb);
};
public static AutoKeywords = 'keywords:Usages';
static _links: Doc[] = [];
constructor() {
+ makeObservable(this);
LinkManager._instance = this;
this.createlink_relationshipLists();
- LinkManager.userLinkDBs = [];
// since this is an action, not a reaction, we get only one shot to add this link to the Anchor docs
// Thus make sure all promised values are resolved from link -> link.proto -> link.link_anchor_[1,2] -> link.link_anchor_[1,2].proto
// Then add the link to the anchor protos.
@@ -124,7 +124,7 @@ export class LinkManager {
}
};
observe(
- LinkManager.userLinkDBs,
+ this.userLinkDBs,
change => {
switch (change.type as any) {
case 'splice':
@@ -136,7 +136,7 @@ export class LinkManager {
true
);
runInAction(() => (FieldLoader.ServerLoadStatus.message = 'links'));
- LinkManager.addLinkDB(Doc.LinkDBDoc());
+ this.addLinkDB(Doc.LinkDBDoc());
}
public createlink_relationshipLists = () => {
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index c8940194c..495af6abd 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -26,7 +26,7 @@ export class RTFMarkup extends React.Component<{}> {
RTFMarkup.Instance = this;
}
- @observable _stats: { [key: string]: any } | undefined;
+ @observable _stats: { [key: string]: any } | undefined = undefined;
/**
* @returns the main interface of the SharingManager.
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 25f158f40..d36a360a1 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,4 +1,4 @@
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { Doc, Opt } from '../../fields/Doc';
import { DocViews } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
@@ -12,9 +12,13 @@ import { UndoManager } from './UndoManager';
export namespace SelectionManager {
class Manager {
- @observable SelectedViews: DocumentView[] = [];
+ @observable SelectedViews: DocumentView[] = observable([]);
@observable IsDragging: boolean = false;
- @observable SelectedSchemaDocument: Doc | undefined;
+ @observable SelectedSchemaDocument: Doc | undefined = undefined;
+
+ constructor() {
+ makeObservable(this);
+ }
@action
SelectSchemaViewDoc(doc: Opt<Doc>) {
@@ -26,7 +30,7 @@ export namespace SelectionManager {
if (!extendSelection) this.DeselectAll();
manager.SelectedViews.push(docView);
docView.SELECTED = true;
- docView.props.whenChildContentsActiveChanged(true);
+ docView._props.whenChildContentsActiveChanged(true);
}
}
@action
@@ -34,7 +38,7 @@ export namespace SelectionManager {
if (docView && manager.SelectedViews.includes(docView)) {
docView.SELECTED = false;
manager.SelectedViews.splice(manager.SelectedViews.indexOf(docView), 1);
- docView.props.whenChildContentsActiveChanged(false);
+ docView._props.whenChildContentsActiveChanged(false);
}
}
@action
@@ -44,7 +48,7 @@ export namespace SelectionManager {
manager.SelectedSchemaDocument = undefined;
manager.SelectedViews.forEach(dv => {
dv.SELECTED = false;
- dv.props.whenChildContentsActiveChanged(false);
+ dv._props.whenChildContentsActiveChanged(false);
});
manager.SelectedViews.length = 0;
}
@@ -87,7 +91,7 @@ export namespace SelectionManager {
ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, expertMode: boolean, checkContext?: boolean) {
if (Doc.noviceMode && expertMode) return false;
if (type === 'tab') {
- return SelectionManager.Views().lastElement()?.props.renderDepth === 0;
+ return SelectionManager.Views().lastElement()?._props.renderDepth === 0;
}
let selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
return selected?.type === type || selected?.type_collection === type || !type;
@@ -115,7 +119,7 @@ ScriptingGlobals.add(function redo() {
});
ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) {
const docs = SelectionManager.Views()
- .map(dv => dv.props.Document)
+ .map(dv => dv.Document)
.filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null)));
return docs.length ? new List(docs) : prevValue;
});
diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx
index 08dbaac5d..7d5e0458d 100644
--- a/src/client/util/ServerStats.tsx
+++ b/src/client/util/ServerStats.tsx
@@ -36,7 +36,7 @@ export class ServerStats extends React.Component<{}> {
ServerStats.Instance = this;
}
- @observable _stats: { [key: string]: any } | undefined;
+ @observable _stats: { [key: string]: any } | undefined = undefined;
/**
* @returns the main interface of the SharingManager.
@@ -56,9 +56,7 @@ export class ServerStats extends React.Component<{}> {
<br />
<span>Active users:{this._stats?.socketMap.length}</span>
- {this._stats?.socketMap.map((user: any) => (
- <p>{user.username}</p>
- ))}
+ {this._stats?.socketMap.map((user: any) => <p>{user.username}</p>)}
</div>
</div>
);
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 39c471970..ccf6fb820 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, ColorPicker, Dropdown, DropdownType, EditableText, Group, NumberDropdown, Size, Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { BsGoogle } from 'react-icons/bs';
@@ -45,11 +45,12 @@ export class SettingsManager extends React.Component<{}> {
@observable private new_confirm = '';
@observable activeTab = 'Accounts';
- @observable public static propertiesWidth: number = 0;
- @observable public static headerBarHeight: number = 0;
+ @observable public propertiesWidth: number = 0;
+ @observable public headerBarHeight: number = 0;
constructor(props: {}) {
super(props);
+ makeObservable(this);
SettingsManager.Instance = this;
this.matchSystem();
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
@@ -116,7 +117,6 @@ export class SettingsManager extends React.Component<{}> {
});
@undoBatch
- @action
changeColorScheme = action((scheme: string) => {
Doc.UserDoc().userTheme = scheme;
switch (scheme) {
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 403f4e090..a46c6363e 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -67,8 +67,8 @@ export class SharingManager extends React.Component<{}> {
public static Instance: SharingManager;
@observable private isOpen = false; // whether the SharingManager modal is open or not
@observable public users: ValidatedUser[] = []; // the list of users with sharing docs
- @observable private targetDoc: Doc | undefined; // the document being shared
- @observable private targetDocView: DocumentView | undefined; // the DocumentView of the document being shared
+ @observable private targetDoc: Doc | undefined = undefined; // the document being shared
+ @observable private targetDocView: DocumentView | undefined = undefined; // the DocumentView of the document being shared
// @observable private copied = false;
@observable private dialogueBoxOpacity = 1; // for the modal
@observable private overlayOpacity = 0.4; // for the modal
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index fce43eef6..715eb021f 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -6,7 +6,7 @@ export namespace SnappingManager {
@observable ShiftKey = false;
@observable CtrlKey = false;
@observable IsDragging: boolean = false;
- @observable IsResizing: Doc | undefined;
+ @observable IsResizing: Doc | undefined = undefined;
@observable CanEmbed: boolean = false;
@observable public horizSnapLines: number[] = [];
@observable public vertSnapLines: number[] = [];
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index 412b8ba6e..f89624941 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -1,9 +1,8 @@
+import { action, makeObservable, observable, runInAction } from 'mobx';
import * as React from 'react';
-import { observable, action, runInAction } from 'mobx';
-import './AntimodeMenu.scss';
-import { StrCast } from '../../fields/Types';
-import { Doc } from '../../fields/Doc';
import { SettingsManager } from '../util/SettingsManager';
+import { copyProps } from '../../Utils';
+import './AntimodeMenu.scss';
export interface AntimodeMenuProps {}
/**
@@ -16,6 +15,18 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends React.Co
protected _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
protected _dragging: boolean = false;
+ _prevProps: React.PropsWithChildren<T>;
+ @observable _props: React.PropsWithChildren<T>;
+ constructor(props: React.PropsWithChildren<T>) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
@observable protected _top: number = -300;
@observable protected _left: number = -300;
@observable protected _opacity: number = 0;
diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx
deleted file mode 100644
index 1fd2fedc3..000000000
--- a/src/client/views/AudioWaveform.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import * as React from 'react';
-import axios from 'axios';
-import { action, computed, IReactionDisposer, reaction } from 'mobx';
-import { observer } from 'mobx-react';
-import Waveform from 'react-audio-waveform';
-import { Doc, NumListCast } from '../../fields/Doc';
-import { List } from '../../fields/List';
-import { listSpec } from '../../fields/Schema';
-import { Cast } from '../../fields/Types';
-import { numberRange } from '../../Utils';
-import './AudioWaveform.scss';
-import { Colors } from './global/globalEnums';
-
-/**
- * AudioWaveform
- *
- * Used in CollectionStackedTimeline to render a canvas with a visual of an audio waveform for AudioBox and VideoBox documents.
- * Uses react-audio-waveform package.
- * Bins the audio data into audioBuckets which are passed to package to render the lines.
- * Calculates new buckets each time a new zoom factor or new set of trim bounds is created and stores it in a field on the layout doc with a title indicating the bounds and zoom for that list (see audioBucketField)
- */
-
-export interface AudioWaveformProps {
- duration: number; // length of media clip
- rawDuration: number; // length of underlying media data
- mediaPath: string;
- layoutDoc: Doc;
- clipStart: number;
- clipEnd: number;
- zoomFactor: number;
- PanelHeight: number;
- PanelWidth: number;
- fieldKey: string;
-}
-
-@observer
-export class AudioWaveform extends React.Component<AudioWaveformProps> {
- public static NUMBER_OF_BUCKETS = 100; // number of buckets data is divided into to draw waveform lines
-
- _disposer: IReactionDisposer | undefined;
-
- @computed get waveHeight() {
- return Math.max(50, this.props.PanelHeight);
- }
-
- @computed get clipStart() {
- return this.props.clipStart;
- }
- @computed get clipEnd() {
- return this.props.clipEnd;
- }
- @computed get zoomFactor() {
- return this.props.zoomFactor;
- }
-
- @computed get audioBuckets() {
- return NumListCast(this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)]);
- }
- audioBucketField = (start: number, end: number, zoomFactor: number) => this.props.fieldKey + '_audioBuckets/' + '/' + start.toFixed(2).replace('.', '_') + '/' + end.toFixed(2).replace('.', '_') + '/' + zoomFactor * 10;
-
- componentWillUnmount() {
- this._disposer?.();
- }
-
- componentDidMount() {
- this._disposer = reaction(
- () => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor), zoomFactor: this.props.zoomFactor }),
- ({ clipStart, clipEnd, fieldKey, zoomFactor }) => {
- if (!this.props.layoutDoc[fieldKey]) {
- // setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time.
- const waveform = Cast(this.props.layoutDoc[this.audioBucketField(0, this.props.rawDuration, 1)], listSpec('number'));
- this.props.layoutDoc[fieldKey] = waveform && new List<number>(waveform.slice((clipStart / this.props.rawDuration) * waveform.length, (clipEnd / this.props.rawDuration) * waveform.length));
- setTimeout(() => this.createWaveformBuckets(fieldKey, clipStart, clipEnd, zoomFactor));
- }
- },
- { fireImmediately: true }
- );
- }
-
- // decodes the audio file into peaks for generating the waveform
- createWaveformBuckets = async (fieldKey: string, clipStart: number, clipEnd: number, zoomFactor: number) => {
- axios({ url: this.props.mediaPath, responseType: 'arraybuffer' }).then(response => {
- const context = new window.AudioContext();
- context.decodeAudioData(
- response.data,
- action(buffer => {
- const rawDecodedAudioData = buffer.getChannelData(0);
- const startInd = clipStart / this.props.rawDuration;
- const endInd = clipEnd / this.props.rawDuration;
- const decodedAudioData = rawDecodedAudioData.slice(Math.floor(startInd * rawDecodedAudioData.length), Math.floor(endInd * rawDecodedAudioData.length));
- const numBuckets = Math.floor(AudioWaveform.NUMBER_OF_BUCKETS * zoomFactor);
-
- const bucketDataSize = Math.floor(decodedAudioData.length / numBuckets);
- const brange = Array.from(Array(bucketDataSize));
- const bucketList = numberRange(numBuckets).map((i: number) => brange.reduce((p, x, j) => Math.abs(Math.max(p, decodedAudioData[i * bucketDataSize + j])), 0) / 2);
- this.props.layoutDoc[fieldKey] = new List<number>(bucketList);
- })
- );
- });
- };
-
- render() {
- return (
- <div className="audioWaveform">
- <Waveform
- color={Colors.MEDIUM_BLUE_ALT}
- height={this.waveHeight}
- barWidth={200 / this.audioBuckets.length}
- pos={this.props.duration}
- duration={this.props.duration}
- peaks={Array.from(this.audioBuckets)}
- progressColor={Colors.MEDIUM_BLUE_ALT}
- />
- </div>
- );
- }
-}
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 57bc11385..249e2c3a3 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -1,40 +1,42 @@
-import * as React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
-import './ContextMenu.scss';
-import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from './ContextMenuItem';
-import { Utils } from '../../Utils';
+import * as React from 'react';
import { StrCast } from '../../fields/Types';
-import { Doc } from '../../fields/Doc';
import { SettingsManager } from '../util/SettingsManager';
+import './ContextMenu.scss';
+import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from './ContextMenuItem';
@observer
export class ContextMenu extends React.Component {
static Instance: ContextMenu;
- @observable private _items: Array<ContextMenuProps> = [];
- @observable private _pageX: number = 0;
- @observable private _pageY: number = 0;
- @observable private _display: boolean = false;
- @observable private _searchString: string = '';
- @observable private _showSearch: boolean = false;
+ private _ignoreUp = false;
+ private _reactionDisposer?: IReactionDisposer;
+ private _defaultPrefix: string = '';
+ private _defaultItem: ((name: string) => void) | undefined;
+ private _onDisplay?: () => void = undefined;
+
+ @observable _items: ContextMenuProps[] = observable([]);
+ @observable _pageX: number = 0;
+ @observable _pageY: number = 0;
+ @observable _display: boolean = false;
+ @observable _searchString: string = '';
+ @observable _showSearch: boolean = false;
// afaik displaymenu can be called before all the items are added to the menu, so can't determine in displayMenu what the height of the menu will be
- @observable private _yRelativeToTop: boolean = true;
- @observable selectedIndex = -1;
+ @observable _yRelativeToTop: boolean = true;
+ @observable _selectedIndex = -1;
- @observable private _width: number = 0;
- @observable private _height: number = 0;
+ @observable _width: number = 0;
+ @observable _height: number = 0;
- @observable private _mouseX: number = -1;
- @observable private _mouseY: number = -1;
- @observable private _shouldDisplay: boolean = false;
- private _ignoreUp = false;
- private _reactionDisposer?: IReactionDisposer;
+ @observable _mouseX: number = -1;
+ @observable _mouseY: number = -1;
+ @observable _shouldDisplay: boolean = false;
- constructor(props: Readonly<{}>) {
+ constructor(props: any) {
super(props);
-
+ makeObservable(this);
ContextMenu.Instance = this;
}
@@ -74,36 +76,27 @@ export class ContextMenu extends React.Component {
this._reactionDisposer?.();
}
- @action
- componentDidMount = () => {
+ componentDidMount() {
document.addEventListener('pointerdown', this.onPointerDown, true);
document.addEventListener('pointerup', this.onPointerUp);
- };
+ }
@action
clearItems() {
- this._items = [];
+ this._items.length = 0;
this._defaultPrefix = '';
this._defaultItem = undefined;
}
- _defaultPrefix: string = '';
- _defaultItem: ((name: string) => void) | undefined;
-
- findByDescription = (target: string, toLowerCase = false) => {
- return this._items.find(menuItem => {
- let reference = menuItem.description;
- toLowerCase && (reference = reference.toLowerCase());
- return reference === target;
- });
- };
+ findByDescription = (target: string, toLowerCase = false) =>
+ this._items.find(menuItem =>
+ (toLowerCase ? menuItem.description.toLowerCase() : menuItem.description) === target); // prettier-ignore
@action
addItem(item: ContextMenuProps) {
- if (this._items.indexOf(item) === -1) {
- this._items.push(item);
- }
+ !this._items.includes(item) && this._items.push(item);
}
+
@action
moveAfter(item: ContextMenuProps, after?: ContextMenuProps) {
const curInd = this._items.findIndex(i => i.description === item.description);
@@ -111,6 +104,7 @@ export class ContextMenu extends React.Component {
const afterInd = after && this.findByDescription(after.description) ? this._items.findIndex(i => i.description === after.description) : this._items.length;
this._items.splice(afterInd, 0, item);
}
+
@action
setDefaultItem(prefix: string, item: (name: string) => void) {
this._defaultPrefix = prefix;
@@ -126,7 +120,6 @@ export class ContextMenu extends React.Component {
return this._pageY + this._height > window.innerHeight - ContextMenu.buffer ? window.innerHeight - ContextMenu.buffer - this._height : Math.max(0, this._pageY);
}
- _onDisplay?: () => void = undefined;
@action
displayMenu = (x: number, y: number, initSearch = '', showSearch = false, onDisplay?: () => void) => {
//maxX and maxY will change if the UI/font size changes, but will work for any amount
@@ -201,7 +194,7 @@ export class ContextMenu extends React.Component {
<div className="contextMenu-description">{value.join(' -> ')}</div>
</div>
) : (
- <ContextMenuItem {...value} key={index + value.description} closeMenu={this.closeMenu} selected={index === this.selectedIndex} />
+ <ContextMenuItem {...value} key={index + value.description} closeMenu={this.closeMenu} selected={index === this._selectedIndex} />
)
);
}
@@ -211,7 +204,8 @@ export class ContextMenu extends React.Component {
}
render() {
- return !this._display ? null : (
+ console.log('DISPLAY = ' + this._display);
+ return (
<div
className="contextMenu-cont"
ref={action((r: any) => {
@@ -221,6 +215,7 @@ export class ContextMenu extends React.Component {
}
})}
style={{
+ display: this._display ? '' : 'none',
left: this.pageX,
...(this._yRelativeToTop ? { top: this.pageY } : { bottom: this.pageY }),
background: SettingsManager.userBackgroundColor,
@@ -242,17 +237,17 @@ export class ContextMenu extends React.Component {
@action
onKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'ArrowDown') {
- if (this.selectedIndex < this.flatItems.length - 1) {
- this.selectedIndex++;
+ if (this._selectedIndex < this.flatItems.length - 1) {
+ this._selectedIndex++;
}
e.preventDefault();
} else if (e.key === 'ArrowUp') {
- if (this.selectedIndex > 0) {
- this.selectedIndex--;
+ if (this._selectedIndex > 0) {
+ this._selectedIndex--;
}
e.preventDefault();
} else if (e.key === 'Enter' || e.key === 'Tab') {
- const item = this.flatItems[this.selectedIndex];
+ const item = this.flatItems[this._selectedIndex];
if (item) {
item.event({ x: this.pageX, y: this.pageY });
} else if (this._searchString.startsWith(this._defaultPrefix)) {
@@ -268,12 +263,12 @@ export class ContextMenu extends React.Component {
onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this._searchString = e.target.value;
if (!this._searchString) {
- this.selectedIndex = -1;
+ this._selectedIndex = -1;
} else {
- if (this.selectedIndex === -1) {
- this.selectedIndex = 0;
+ if (this._selectedIndex === -1) {
+ this._selectedIndex = 0;
} else {
- this.selectedIndex = Math.min(this.flatItems.length - 1, this.selectedIndex);
+ this._selectedIndex = Math.min(this.flatItems.length - 1, this._selectedIndex);
}
}
};
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 6d97d965e..59b223c14 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -1,12 +1,11 @@
import * as React from 'react';
-import { observable, action, runInAction } from 'mobx';
+import { observable, action, runInAction, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { UndoManager } from '../util/UndoManager';
-import { Doc } from '../../fields/Doc';
-import { StrCast } from '../../fields/Types';
import { SettingsManager } from '../util/SettingsManager';
+import { copyProps } from '../../Utils';
export interface OriginalMenuProps {
description: string;
@@ -32,9 +31,20 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
@observable private _items: Array<ContextMenuProps> = [];
@observable private overItem = false;
- @action
+ _prevProps: ContextMenuProps & { selected?: boolean };
+ @observable _props: ContextMenuProps & { selected?: boolean };
+ constructor(props: ContextMenuProps & { selected?: boolean }) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
componentDidMount() {
- this._items.length = 0;
+ runInAction(() => this._items.length = 0);
if ((this.props as SubmenuProps)?.subitems) {
(this.props as SubmenuProps).subitems?.forEach(i => runInAction(() => this._items.push(i)));
}
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index d104eb90c..2ce0c085a 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction, untracked } from 'mobx';
import { DateField } from '../../fields/DateField';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols';
@@ -21,17 +21,26 @@ export interface DocComponentProps {
}
export function DocComponent<P extends DocComponentProps>() {
class Component extends React.Component<React.PropsWithChildren<P>> {
+ @observable _props!: React.PropsWithChildren<P>;
+ constructor(props: React.PropsWithChildren<P>) {
+ super(props);
+ this._props = props;
+ makeObservable(this);
+ }
+ componentDidUpdate() {
+ // untracked(() => (this._props = this.props));
+ }
//TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
@computed get Document() {
- return this.props.Document;
+ return this._props.Document;
}
// This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
@computed get layoutDoc() {
- return this.props.LayoutTemplateString ? this.props.Document : Doc.Layout(this.props.Document, this.props.LayoutTemplate?.());
+ return this._props.LayoutTemplateString ? this.Document : Doc.Layout(this.Document, this._props.LayoutTemplate?.());
}
// This is the data part of a document -- ie, the data that is constant across all views of the document
@computed get dataDoc() {
- return this.props.Document[DocData] as Doc;
+ return this._props.Document[DocData] as Doc;
}
}
return Component;
@@ -49,22 +58,28 @@ interface ViewBoxBaseProps {
}
export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
class Component extends React.Component<React.PropsWithChildren<P>> {
+ @observable _props: React.PropsWithChildren<P>;
+ constructor(props: React.PropsWithChildren<P>) {
+ super(props);
+ this._props = props;
+ makeObservable(this);
+ }
//TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
//@computed get Document(): T { return schemaCtor(this.props.Document); }
- @computed get Document() {
- return this.props.Document;
+ get Document() {
+ return this._props.Document;
}
// This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
- @computed get layoutDoc() {
- return Doc.Layout(this.props.Document);
+ get layoutDoc() {
+ return Doc.Layout(this.Document);
}
// This is the data part of a document -- ie, the data that is constant across all views of the document
- @computed get dataDoc() {
- return this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc ? this.props.TemplateDataDocument ?? this.props.Document[DocData] : this.props.Document[DocData];
+ get dataDoc() {
+ return this.Document.isTemplateForField || this.Document.isTemplateDoc ? this._props.TemplateDataDocument ?? this.Document[DocData] : this.Document[DocData];
}
// key where data is stored
- @computed get fieldKey() {
- return this.props.fieldKey;
+ get fieldKey() {
+ return this._props.fieldKey;
}
}
return Component;
@@ -85,24 +100,30 @@ export interface ViewBoxAnnotatableProps {
}
export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>() {
class Component extends React.Component<React.PropsWithChildren<P>> {
+ @observable _props: React.PropsWithChildren<P>;
+ constructor(props: React.PropsWithChildren<P>) {
+ super(props);
+ this._props = props;
+ makeObservable(this);
+ }
@observable _annotationKeySuffix = () => 'annotations';
@observable _isAnyChildContentActive = false;
//TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
@computed get Document() {
- return this.props.Document;
+ return this._props.Document;
}
// This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
@computed get layoutDoc() {
- return Doc.Layout(this.props.Document);
+ return Doc.Layout(this.Document);
}
// This is the data part of a document -- ie, the data that is constant across all views of the document
@computed get dataDoc() {
- return this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc ? this.props.TemplateDataDocument ?? this.props.Document[DocData] : this.props.Document[DocData];
+ return this.Document.isTemplateForField || this.Document.isTemplateDoc ? this._props.TemplateDataDocument ?? this.Document[DocData] : this.Document[DocData];
}
// key where data is stored
@computed get fieldKey() {
- return this.props.fieldKey;
+ return this._props.fieldKey;
}
isAnyChildContentActive = () => this._isAnyChildContentActive;
@@ -136,7 +157,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
if (targetDataDoc.isGroup && DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]).length < 2) {
(DocumentManager.Instance.getFirstDocumentView(targetDataDoc)?.ComponentView as CollectionFreeFormView)?.promoteCollection();
} else {
- this.isAnyChildContentActive() && this.props.select(false);
+ this.isAnyChildContentActive() && this._props.select(false);
}
return true;
}
@@ -149,7 +170,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
// moving it into the target.
@action.bound
moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => {
- if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
+ if (Doc.AreProtosEqual(this._props.Document, targetCollection)) {
return true;
}
const first = doc instanceof Doc ? doc : doc[0];
@@ -161,7 +182,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
@action.bound
addDocument = (doc: Doc | Doc[], annotationKey?: string): boolean => {
const docs = doc instanceof Doc ? [doc] : doc;
- if (this.props.filterAddDocument?.(docs) === false || docs.find(doc => Doc.AreProtosEqual(doc, this.props.Document) && Doc.LayoutField(doc) === Doc.LayoutField(this.props.Document))) {
+ if (this._props.filterAddDocument?.(docs) === false || docs.find(doc => Doc.AreProtosEqual(doc, this.Document) && Doc.LayoutField(doc) === Doc.LayoutField(this.Document))) {
return false;
}
const targetDataDoc = this.dataDoc;
@@ -190,7 +211,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
return true;
};
- whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
+ whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
}
return Component;
}
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index a64722a0b..47ec0f1b4 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,7 +1,7 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../fields/Doc';
import { RichTextField } from '../../fields/RichTextField';
@@ -60,8 +60,11 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
public static hasPushedHack = false;
public static hasPulledHack = false;
+ @observable _props: { views: () => (DocumentView | undefined)[] };
constructor(props: { views: () => (DocumentView | undefined)[] }) {
super(props);
+ this._props = props;
+ makeObservable(this);
runInAction(() => (DocumentButtonBar.Instance = this));
}
@@ -111,12 +114,12 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
});
get view0() {
- return this.props.views()?.[0];
+ return this._props.views()?.[0];
}
@computed
get considerGoogleDocsPush() {
- const targetDoc = this.view0?.props.Document;
+ const targetDoc = this.view0?.Document;
const published = targetDoc && Doc.GetProto(targetDoc)[GoogleRef] !== undefined;
const animation = this.isAnimatingPulse ? 'shadow-pulse 1s linear infinite' : 'none';
return !targetDoc ? null : (
@@ -143,7 +146,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
@computed
get considerGoogleDocsPull() {
- const targetDoc = this.view0?.props.Document;
+ const targetDoc = this.view0?.Document;
const dataDoc = targetDoc && Doc.GetProto(targetDoc);
const animation = this.isAnimatingFetch ? 'spin 0.5s linear infinite' : 'none';
@@ -214,7 +217,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
@observable subFollow = '';
@computed
get followLinkButton() {
- const targetDoc = this.view0?.props.Document;
+ const targetDoc = this.view0?.Document;
const followBtn = (allDocs: boolean, click: (doc: Doc) => void, isSet: (doc?: Doc) => boolean, icon: IconProp) => {
const tooltip = `Follow ${this.subPin}documents`;
return !tooltip ? null : (
@@ -229,7 +232,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
onPointerEnter={action(e => (this.subPin = allDocs ? 'All ' : ''))}
onPointerLeave={action(e => (this.subPin = ''))}
onClick={e => {
- this.props.views().forEach(dv => click(dv!.Document));
+ this._props.views().forEach(dv => click(dv!.Document));
e.stopPropagation();
}}
/>
@@ -243,7 +246,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
<div
className="documentButtonBar-icon documentButtonBar-follow"
style={{ backgroundColor: followLink ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: followLink ? Colors.BLACK : Colors.WHITE }}
- onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, false)))}>
+ onClick={undoBatch(e => this._props.views().map(view => view?.docView?.toggleFollowLink(undefined, false)))}>
<div className="documentButtonBar-followTypes">
{followBtn(
true,
@@ -260,7 +263,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
@observable subLink = '';
@computed get linkButton() {
- const targetDoc = this.view0?.props.Document;
+ const targetDoc = this.view0?.Document;
return !targetDoc || !this.view0 ? null : (
<div className="documentButtonBar-icon documentButtonBar-link">
<div className="documentButtonBar-linkTypes">
@@ -304,7 +307,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
onPointerLeave={action(e => (this.subEndLink = ''))}
onClick={e => {
this.view0 &&
- DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.view0.props.Document, true, this.view0, {
+ DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.view0.Document, true, this.view0, {
pinDocLayout: pinLayout,
pinData: !pinContent ? {} : { poslayoutview: true, dataannos: true, dataview: pinContent },
} as PinProps);
@@ -331,7 +334,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
@observable subPin = '';
@computed
get pinButton() {
- const targetDoc = this.view0?.props.Document;
+ const targetDoc = this.view0?.Document;
const pinBtn = (pinLayoutView: boolean, pinContentView: boolean, icon: IconProp) => {
const tooltip = `Pin Document and Save ${this.subPin} to trail`;
return !tooltip ? null : (
@@ -353,7 +356,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
)}
onPointerLeave={action(e => (this.subPin = ''))}
onClick={e => {
- const docs = this.props
+ const docs = this._props
.views()
.filter(v => v)
.map(dv => dv!.Document);
@@ -376,7 +379,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
<div
className="documentButtonBar-icon documentButtonBar-pin"
onClick={e => {
- const docs = this.props
+ const docs = this._props
.views()
.filter(v => v)
.map(dv => dv!.Document);
@@ -396,7 +399,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
@computed
get shareButton() {
- const targetDoc = this.view0?.props.Document;
+ const targetDoc = this.view0?.Document;
return !targetDoc ? null : (
<Tooltip title={<div className="dash-tooltip">{'Open Sharing Manager'}</div>}>
<div className="documentButtonBar-icon" style={{ color: 'white' }} onClick={e => SharingManager.Instance.open(this.view0, targetDoc)}>
@@ -408,10 +411,10 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
@computed
get menuButton() {
- const targetDoc = this.view0?.props.Document;
+ const targetDoc = this.view0?.Document;
return !targetDoc ? null : (
<Tooltip title={<div className="dash-tooltip">{`Open Context Menu`}</div>}>
- <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onClick={this.openContextMenu}>
+ <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => this.openContextMenu(e))}>
<FontAwesomeIcon className="documentdecorations-icon" icon="bars" />
</div>
</Tooltip>
@@ -427,10 +430,10 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
anchorPoint={anchorPoints.LEFT_TOP}
content={
<MetadataEntryMenu
- docs={this.props
+ docs={this._props
.views()
.filter(dv => dv)
- .map(dv => dv!.props.Document)}
+ .map(dv => dv!.Document)}
suggestWithFunction
/> // tfs: @bcz This might need to be the data document?
}>
@@ -447,7 +450,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
_stopFunc: () => void = emptyFunction;
@computed
get recordButton() {
- const targetDoc = this.view0?.props.Document;
+ const targetDoc = this.view0?.Document;
return !targetDoc ? null : (
<Tooltip title={<div className="dash-tooltip">Press to record audio annotation</div>}>
<div
@@ -455,7 +458,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
style={{ backgroundColor: this._isRecording ? Colors.ERROR_RED : Colors.DARK_GRAY, color: Colors.WHITE }}
onPointerDown={action((e: React.PointerEvent) => {
this._isRecording = true;
- this.props.views().map(view => view && DocumentViewInternal.recordAudioAnnotation(view.dataDoc, view.LayoutFieldKey, stopFunc => (this._stopFunc = stopFunc), emptyFunction));
+ this._props.views().map(view => view && DocumentViewInternal.recordAudioAnnotation(view.dataDoc, view.LayoutFieldKey, stopFunc => (this._stopFunc = stopFunc), emptyFunction));
const b = UndoManager.StartBatch('Recording');
setupMoveUpEvents(
this,
@@ -482,8 +485,8 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
onEmbedButtonMoved = () => {
if (this._dragRef.current) {
const dragDocView = this.view0!;
- const dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
- const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ const dragData = new DragManager.DocumentDragData([dragDocView.Document]);
+ const [left, top] = dragDocView._props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
dragData.defaultDropAction = 'embed';
dragData.canEmbed = true;
DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, { hideSource: false });
@@ -497,7 +500,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
@computed
get templateButton() {
const view0 = this.view0;
- const views = this.props.views();
+ const views = this._props.views();
return !view0 ? null : (
<Tooltip title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embedding</div>} open={this._tooltipOpen} onClose={action(() => (this._tooltipOpen = false))} placement="bottom">
<div className="documentButtonBar-linkFlyout" ref={this._dragRef} onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}>
@@ -522,7 +525,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
);
}
- openContextMenu = (e: React.MouseEvent) => {
+ openContextMenu = (e: PointerEvent) => {
let child = SelectionManager.Views()[0].ContentDiv!.children[0];
while (child.children.length) {
const next = Array.from(child.children).find(c => c.className?.toString().includes('SVGAnimatedString') || typeof c.className === 'string');
@@ -559,7 +562,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
@action
toggleTrail = (e: React.PointerEvent) => {
- const rootView = this.props.views()[0];
+ const rootView = this._props.views()[0];
const doc = rootView?.Document;
if (doc) {
const anchor = rootView.ComponentView?.getAnchor?.(true) ?? doc;
@@ -569,12 +572,12 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
anchor.presentationTrail = trail;
}
Doc.ActivePresentation = trail;
- this.props.views().lastElement()?.props.addDocTab(trail, OpenWhere.replaceRight);
+ this._props.views().lastElement()?._props.addDocTab(trail, OpenWhere.replaceRight);
}
e.stopPropagation();
};
render() {
- const doc = this.view0?.props.Document;
+ const doc = this.view0?.Document;
if (!doc || !this.view0) return null;
const isText = () => doc[this.view0!.LayoutFieldKey] instanceof RichTextField;
@@ -589,9 +592,9 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
<div style={{ position: 'absolute', zIndex: 1000 }}>
<LinkPopup
key="popup"
- linkCreated={link => (link.link_displayLine = !IsFollowLinkScript(this.props.views().lastElement()?.Document.onClick))}
- linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.(true)}
- linkFrom={() => this.props.views().lastElement()?.Document}
+ linkCreated={link => (link.link_displayLine = !IsFollowLinkScript(this._props.views().lastElement()?.Document.onClick))}
+ linkCreateAnchor={() => this._props.views().lastElement()?.ComponentView?.getAnchor?.(true)}
+ linkFrom={() => this._props.views().lastElement()?.Document}
/>
</div>
) : (
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index b4c19df2d..6ef3fcc66 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,7 +1,7 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { IconButton } from 'browndash-components';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { FaUndo } from 'react-icons/fa';
import { DateField } from '../../fields/DateField';
@@ -34,10 +34,15 @@ import { DocumentView, OpenWhereMod } from './nodes/DocumentView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { ImageBox } from './nodes/ImageBox';
import * as React from 'react';
-import * as _ from 'lodash';
+interface DocumentDecorationsProps {
+ PanelWidth: number;
+ PanelHeight: number;
+ boundsLeft: number;
+ boundsTop: number;
+}
@observer
-export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> {
+export class DocumentDecorations extends React.Component<DocumentDecorationsProps> {
static Instance: DocumentDecorations;
private _resizeHdlId = '';
private _keyinput = React.createRef<HTMLInputElement>();
@@ -61,8 +66,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
@observable private _showRotCenter = false; // whether to show a draggable green dot that represents the center of rotation
@observable private _rotCenter = [0, 0]; // the center of rotation in object coordinates (0,0) = object center (not top left!)
- constructor(props: any) {
+ @observable _props: React.PropsWithChildren<DocumentDecorationsProps>;
+ constructor(props: React.PropsWithChildren<DocumentDecorationsProps>) {
super(props);
+ this._props = props;
+ makeObservable(this);
DocumentDecorations.Instance = this;
document.addEventListener('pointermove', // show decorations whenever pointer moves outside of selection bounds.
action(e => {
@@ -78,8 +86,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
@computed get ClippedBounds() {
const bounds = this.Bounds;
- const leftBounds = this.props.boundsLeft;
- const topBounds = LightboxView.LightboxDoc ? 0 : this.props.boundsTop;
+ const leftBounds = this._props.boundsLeft;
+ const topBounds = LightboxView.LightboxDoc ? 0 : this._props.boundsTop;
bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
bounds.y = Math.max(topBounds, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight;
const borderRadiusDraggerWidth = 15;
@@ -92,7 +100,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
return (LinkFollower.IsFollowing || DocumentView.ExploreMode) ?
{ x: 0, y: 0, r: 0, b: 0 }
: SelectionManager.Views()
- .filter(dv => dv.props.renderDepth > 0)
+ .filter(dv => dv._props.renderDepth > 0)
.map(dv => dv.getBounds())
.reduce((bounds, rect) => !rect ? bounds
: { x: Math.min(rect.left, bounds.x),
@@ -198,12 +206,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
if (containers.size > 1) return false;
const { left, top } = dragDocView.getBounds() || { left: 0, top: 0 };
const dragData = new DragManager.DocumentDragData(
- SelectionManager.Views().map(dv => dv.props.Document),
- dragDocView.props.dropAction
+ SelectionManager.Views().map(dv => dv.Document),
+ dragDocView._props.dropAction
);
- dragData.offset = dragDocView.props.ScreenToLocalTransform().transformDirection(e.x - left, e.y - top);
- dragData.moveDocument = dragDocView.props.moveDocument;
- dragData.removeDocument = dragDocView.props.removeDocument;
+ dragData.offset = dragDocView._props.ScreenToLocalTransform().transformDirection(e.x - left, e.y - top);
+ dragData.moveDocument = dragDocView._props.moveDocument;
+ dragData.removeDocument = dragDocView._props.removeDocument;
dragData.isDocDecorationMove = true;
dragData.canEmbed = dragTitle;
this._hidden = true;
@@ -223,7 +231,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
_deleteAfterIconify = false;
_iconifyBatch: UndoManager.Batch | undefined;
onCloseClick = (forceDeleteOrIconify: boolean | undefined) => {
- const views = SelectionManager.Views().filter(v => v && v.props.renderDepth > 0);
+ const views = SelectionManager.Views().filter(v => v && v._props.renderDepth > 0);
if (forceDeleteOrIconify === false && this._iconifyBatch) return;
this._deleteAfterIconify = forceDeleteOrIconify || this._iconifyBatch ? true : false;
var iconifyingCount = views.length;
@@ -231,11 +239,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
if ((force || --iconifyingCount === 0) && this._iconifyBatch) {
if (this._deleteAfterIconify) {
views.forEach(iconView => {
- Doc.setNativeView(iconView.props.Document);
- if (iconView.props.Document.activeFrame) {
- iconView.props.Document.opacity = 0; // bcz: hacky ... allows inkMasks and other documents to be "turned off" without removing them from the animated collection which allows them to function properly in a presenation.
+ Doc.setNativeView(iconView.Document);
+ if (iconView.Document.activeFrame) {
+ iconView.Document.opacity = 0; // bcz: hacky ... allows inkMasks and other documents to be "turned off" without removing them from the animated collection which allows them to function properly in a presenation.
} else {
- iconView.props.removeDocument?.(iconView.props.Document);
+ iconView._props.removeDocument?.(iconView.Document);
}
});
views.forEach(SelectionManager.DeselectView);
@@ -296,7 +304,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
SelectionManager.DeselectAll();
};
- onSelectContainerDocClick = () => SelectionManager.Views()?.[0]?.props.docViewPath?.().lastElement()?.select(false);
+ onSelectContainerDocClick = () => SelectionManager.Views()?.[0]?._props.docViewPath?.().lastElement()?.select(false);
/**
* sets up events when user clicks on the border radius editor
*/
@@ -344,7 +352,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
};
setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => {
- const newloccentern = seldocview.props.ScreenToLocalTransform().transformPoint(rotCenter[0], rotCenter[1]);
+ const newloccentern = seldocview._props.ScreenToLocalTransform().transformPoint(rotCenter[0], rotCenter[1]);
const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2];
const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.Document._rotation) / 180) * Math.PI);
seldocview.Document.rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
@@ -376,7 +384,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
const seldocview = SelectionManager.Views()[0];
SelectionManager.Views().forEach(dv => {
const accumRot = (NumCast(dv.Document._rotation) / 180) * Math.PI;
- const localRotCtr = dv.props.ScreenToLocalTransform().transformPoint(rcScreen.X, rcScreen.Y);
+ const localRotCtr = dv._props.ScreenToLocalTransform().transformPoint(rcScreen.X, rcScreen.Y);
const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.Document.width) / 2, localRotCtr[1] - NumCast(dv.Document.height) / 2];
const startRotCtr = Utils.rotPt(localRotCtrOffset[0], localRotCtrOffset[1], -accumRot);
const unrotatedDocPos = { x: NumCast(dv.Document.x) + localRotCtrOffset[0] - startRotCtr.x, y: NumCast(dv.Document.y) + localRotCtrOffset[1] - startRotCtr.y };
@@ -454,7 +462,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
dot = (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
return [a[0] + atob[0] * t, a[1] + atob[1] * t];
};
- const tl = docView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ const tl = docView._props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
return project([e.clientX + this._offset.x, e.clientY + this._offset.y], tl, [tl[0] + fixedAspect, tl[1] + 1]);
};
onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
@@ -475,7 +483,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
!this._interactionLock && runInAction(async () => { // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate)
this._interactionLock = true;
this._snapPt = thisPt;
- e.ctrlKey && (SelectionManager.Views().forEach(docView => !Doc.NativeHeight(docView.props.Document) && docView.toggleNativeDimensions()));
+ e.ctrlKey && (SelectionManager.Views().forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions()));
const fixedAspect = SelectionManager.Docs().some(this.hasFixedAspect);
const scaleAspect = {x:scale.x === 1 && fixedAspect ? scale.y : scale.x, y: scale.x !== 1 && fixedAspect ? scale.x : scale.y};
SelectionManager.Views().forEach(docView =>
@@ -522,7 +530,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
doc.xPadding = NumCast(doc.xPadding) * scale.x;
doc.yPadding = NumCast(doc.yPadding) * scale.y;
} else {
- const refCent = docView.props.ScreenToLocalTransform().transformPoint(refPt[0], refPt[1]); // fixed reference point for resize (ie, a point that doesn't move)
+ const refCent = docView._props.ScreenToLocalTransform().transformPoint(refPt[0], refPt[1]); // fixed reference point for resize (ie, a point that doesn't move)
const [nwidth, nheight] = [docView.nativeWidth, docView.nativeHeight];
const [initWidth, initHeight] = [NumCast(doc._width, 1), NumCast(doc._height)];
@@ -615,7 +623,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.Document, this: selected.layoutDoc }, console.log).result?.toString() || '';
}
if (this._titleControlString.startsWith('#')) {
- return Field.toString(selected.props.Document[this._titleControlString.substring(1)] as Field) || '-unset-';
+ return Field.toString(selected.Document[this._titleControlString.substring(1)] as Field) || '-unset-';
}
return this._accumulatedTitle;
}
@@ -625,7 +633,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
@computed get rotCenter() {
const lastView = SelectionManager.Views().lastElement();
if (lastView) {
- const invXf = lastView.props.ScreenToLocalTransform().inverse();
+ const invXf = lastView._props.ScreenToLocalTransform().inverse();
const seldoc = lastView.layoutDoc;
const loccenter = Utils.rotPt(NumCast(seldoc.rotation_centerX) * NumCast(seldoc._width), NumCast(seldoc.rotation_centerY) * NumCast(seldoc._height), invXf.Rotate);
return invXf.transformPoint(loccenter.x + NumCast(seldoc._width) / 2, loccenter.y + NumCast(seldoc._height) / 2);
@@ -648,16 +656,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
var shareSymbolIcon = ReverseHierarchyMap.get(shareMode)?.image;
// hide the decorations if the parent chooses to hide it or if the document itself hides it
- const hideDecorations = SnappingManager.GetIsResizing() || seldocview.props.hideDecorations || seldocview.Document.layout_hideDecorations;
+ const hideDecorations = SnappingManager.GetIsResizing() || seldocview._props.hideDecorations || seldocview.Document.layout_hideDecorations;
const hideResizers =
- ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveAcl(seldocview.Document)) || hideDecorations || seldocview.props.hideResizeHandles || seldocview.Document.layout_hideResizeHandles || this._isRounding || this._isRotating;
- const hideTitle = this._showNothing || hideDecorations || seldocview.props.hideDecorationTitle || seldocview.Document.layout_hideDecorationTitle || this._isRounding || this._isRotating;
- const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.Document.layout_hideDocumentButtonBar || this._isRounding || this._isRotating;
+ ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveAcl(seldocview.Document)) || hideDecorations || seldocview._props.hideResizeHandles || seldocview.Document.layout_hideResizeHandles || this._isRounding || this._isRotating;
+ const hideTitle = this._showNothing || hideDecorations || seldocview._props.hideDecorationTitle || seldocview.Document.layout_hideDecorationTitle || this._isRounding || this._isRotating;
+ const hideDocumentButtonBar = hideDecorations || seldocview._props.hideDocumentButtonBar || seldocview.Document.layout_hideDocumentButtonBar || this._isRounding || this._isRotating;
// if multiple documents have been opened at the same time, then don't show open button
const hideOpenButton =
this._showNothing ||
hideDecorations ||
- seldocview.props.hideOpenButton ||
+ seldocview._props.hideOpenButton ||
seldocview.Document.layout_hideOpenButton ||
SelectionManager.Views().some(docView => docView.Document._dragOnlyWithinContainer || docView.Document.isGroup || docView.Document.layout_hideOpenButton) ||
this._isRounding ||
@@ -667,10 +675,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
hideDecorations ||
this._isRounding ||
this._isRotating ||
- seldocview.props.hideDeleteButton ||
+ seldocview._props.hideDeleteButton ||
seldocview.Document.hideDeleteButton ||
SelectionManager.Views().some(docView => {
- const collectionAcl = docView.props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView.props.docViewPath().lastElement().dataDoc) : AclEdit;
+ const collectionAcl = docView._props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView._props.docViewPath().lastElement().dataDoc) : AclEdit;
return collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.Document) !== AclAdmin;
});
const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
@@ -798,7 +806,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
<div key="br" className="documentDecorations-bottomRightResizer" onPointerDown={this.onPointerDown} />
</>
)}
- {seldocview.props.renderDepth <= 1 || !seldocview.props.docViewPath().lastElement() ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectContainerDocClick, 'tap to select containing document')}
+ {seldocview._props.renderDepth <= 1 || !seldocview._props.docViewPath().lastElement() ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectContainerDocClick, 'tap to select containing document')}
{useRounding && (
<div
className="documentDecorations-borderRadius"
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 6bc0e5424..3db77be71 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -1,11 +1,13 @@
import * as React from 'react';
-import { action, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as Autosuggest from 'react-autosuggest';
import { ObjectField } from '../../fields/ObjectField';
import './EditableView.scss';
import { DocumentIconContainer } from './nodes/DocumentIcon';
import { OverlayView } from './OverlayView';
+import { copyProps } from '../../Utils';
+import { FieldView, FieldViewProps } from './nodes/FieldView';
export interface EditableProps {
/**
@@ -26,6 +28,7 @@ export interface EditableProps {
* The contents to render when not editing
*/
contents: any;
+ fieldContents?: FieldViewProps;
fontStyle?: string;
fontSize?: number;
height?: number | 'auto';
@@ -64,9 +67,13 @@ export class EditableView extends React.Component<EditableProps> {
_editingDisposer?: IReactionDisposer;
@observable _editing: boolean = false;
+ _prevProps: EditableProps;
+ @observable _props: EditableProps;
constructor(props: EditableProps) {
super(props);
- this._editing = this.props.editing ? true : false;
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ this._editing = this._props.editing ? true : false;
}
componentDidMount(): void {
@@ -89,12 +96,12 @@ export class EditableView extends React.Component<EditableProps> {
);
}
- @action
componentDidUpdate() {
- if (this._editing && this.props.editing === false) {
+ copyProps(this);
+ if (this._editing && this._props.editing === false) {
this._inputref?.value && this.finalizeEdit(this._inputref.value, false, true, false);
- } else if (this.props.editing !== undefined) {
- this._editing = this.props.editing;
+ } else if (this._props.editing !== undefined) {
+ this._editing = this._props.editing;
}
}
@@ -120,28 +127,28 @@ export class EditableView extends React.Component<EditableProps> {
case 'Tab':
e.stopPropagation();
this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, false);
- this.props.OnTab?.(e.shiftKey);
+ this._props.OnTab?.(e.shiftKey);
break;
case 'Backspace':
e.stopPropagation();
- if (!e.currentTarget.value) this.props.OnEmpty?.();
+ if (!e.currentTarget.value) this._props.OnEmpty?.();
break;
case 'Enter':
- if (this.props.allowCRs !== true) {
+ if (this._props.allowCRs !== true) {
e.stopPropagation();
if (!e.ctrlKey) {
this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, true);
- } else if (this.props.OnFillDown) {
- this.props.OnFillDown(e.currentTarget.value);
+ } else if (this._props.OnFillDown) {
+ this._props.OnFillDown(e.currentTarget.value);
this._editing = false;
- this.props.isEditingCallback?.(false);
+ this._props.isEditingCallback?.(false);
}
}
break;
case 'Escape':
e.stopPropagation();
this._editing = false;
- this.props.isEditingCallback?.(false);
+ this._props.isEditingCallback?.(false);
break;
case 'ArrowUp':
case 'ArrowDown':
@@ -155,30 +162,30 @@ export class EditableView extends React.Component<EditableProps> {
case 'Control':
break;
case ':':
- if (this.props.menuCallback) {
+ if (this._props.menuCallback) {
e.stopPropagation();
- this.props.menuCallback(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y);
+ this._props.menuCallback(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y);
break;
}
default:
- if (this.props.textCallback?.(e.key)) {
+ if (this._props.textCallback?.(e.key)) {
e.stopPropagation();
this._editing = false;
- this.props.isEditingCallback?.(false);
+ this._props.isEditingCallback?.(false);
}
}
};
@action
onClick = (e: React.MouseEvent) => {
- if (this.props.editing !== false) {
+ if (this._props.editing !== false) {
e.nativeEvent.stopPropagation();
- if (this._ref.current && this.props.showMenuOnLoad) {
- this.props.menuCallback?.(this._ref.current.getBoundingClientRect().x, this._ref.current.getBoundingClientRect().y);
+ if (this._ref.current && this._props.showMenuOnLoad) {
+ this._props.menuCallback?.(this._ref.current.getBoundingClientRect().x, this._ref.current.getBoundingClientRect().y);
} else {
this._editing = true;
- this.props.isEditingCallback?.(true);
+ this._props.isEditingCallback?.(true);
}
// e.stopPropagation();
}
@@ -186,17 +193,17 @@ export class EditableView extends React.Component<EditableProps> {
@action
finalizeEdit(value: string, shiftDown: boolean, lostFocus: boolean, enterKey: boolean) {
- if (this.props.SetValue(value, shiftDown, enterKey)) {
+ if (this._props.SetValue(value, shiftDown, enterKey)) {
this._editing = false;
- this.props.isEditingCallback?.(false);
+ this._props.isEditingCallback?.(false);
} else {
this._editing = false;
- this.props.isEditingCallback?.(false);
+ this._props.isEditingCallback?.(false);
!lostFocus &&
setTimeout(
action(() => {
this._editing = true;
- this.props.isEditingCallback?.(true);
+ this._props.isEditingCallback?.(true);
}),
0
);
@@ -215,9 +222,9 @@ export class EditableView extends React.Component<EditableProps> {
};
renderEditor() {
- return this.props.autosuggestProps ? (
+ return this._props.autosuggestProps ? (
<Autosuggest
- {...this.props.autosuggestProps.autosuggestProps}
+ {...this._props.autosuggestProps.autosuggestProps}
inputProps={{
className: 'editableView-input',
onKeyDown: this.onKeyDown,
@@ -228,19 +235,19 @@ export class EditableView extends React.Component<EditableProps> {
onClick: this.stopPropagation,
onPointerUp: this.stopPropagation,
onKeyPress: this.stopPropagation,
- value: this.props.autosuggestProps.value,
+ value: this._props.autosuggestProps.value,
// @ts-ignore
- onChange: this.props.autosuggestProps.onChange,
+ onChange: this._props.autosuggestProps.onChange,
}}
/>
- ) : this.props.oneLine !== false && this.props.GetValue()?.toString().indexOf('\n') === -1 ? (
+ ) : this._props.oneLine !== false && this._props.GetValue()?.toString().indexOf('\n') === -1 ? (
<input
className="editableView-input"
ref={r => (this._inputref = r)}
- style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minWidth: 20, background: this.props.background }}
- placeholder={this.props.placeholder}
+ style={{ display: this._props.display, overflow: 'auto', fontSize: this._props.fontSize, minWidth: 20, background: this._props.background }}
+ placeholder={this._props.placeholder}
onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)}
- defaultValue={this.props.GetValue()}
+ defaultValue={this._props.GetValue()}
autoFocus={true}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
@@ -253,10 +260,10 @@ export class EditableView extends React.Component<EditableProps> {
<textarea
className="editableView-input"
ref={r => (this._inputref = r)}
- style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minHeight: `min(100%, ${(this.props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20, background: this.props.background }}
- placeholder={this.props.placeholder}
+ style={{ display: this._props.display, overflow: 'auto', fontSize: this._props.fontSize, minHeight: `min(100%, ${(this._props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20, background: this._props.background }}
+ placeholder={this._props.placeholder}
onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)}
- defaultValue={this.props.GetValue()}
+ defaultValue={this._props.GetValue()}
autoFocus={true}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
@@ -269,9 +276,9 @@ export class EditableView extends React.Component<EditableProps> {
}
render() {
- const gval = this.props.GetValue()?.replace(/\n/g, '\\r\\n');
+ const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n');
if (this._editing && gval !== undefined) {
- return this.props.sizeToContent ? (
+ return this._props.sizeToContent ? (
<div style={{ display: 'grid', minWidth: 100 }}>
<div style={{ display: 'inline-block', position: 'relative', height: 0, width: '100%', overflow: 'hidden' }}>{gval}</div>
{this.renderEditor()}
@@ -280,30 +287,30 @@ export class EditableView extends React.Component<EditableProps> {
this.renderEditor()
);
}
- setTimeout(() => this.props.autosuggestProps?.resetValue());
- return this.props.contents instanceof ObjectField ? null : (
+ setTimeout(() => this._props.autosuggestProps?.resetValue());
+ return this._props.contents instanceof ObjectField ? null : (
<div
- className={`editableView-container-editing${this.props.oneLine ? '-oneLine' : ''}`}
+ className={`editableView-container-editing${this._props.oneLine ? '-oneLine' : ''}`}
ref={this._ref}
style={{
- display: this.props.display, //
- textOverflow: this.props.overflow,
+ display: this._props.display, //
+ textOverflow: this._props.overflow,
minHeight: '10px',
- whiteSpace: this.props.oneLine ? 'nowrap' : 'pre-line',
- height: this.props.height,
- maxHeight: this.props.maxHeight,
- fontStyle: this.props.fontStyle,
- fontSize: this.props.fontSize,
+ whiteSpace: this._props.oneLine ? 'nowrap' : 'pre-line',
+ height: this._props.height,
+ maxHeight: this._props.maxHeight,
+ fontStyle: this._props.fontStyle,
+ fontSize: this._props.fontSize,
}}
//onPointerDown={this.stopPropagation}
onClick={this.onClick}
- placeholder={this.props.placeholder}>
+ placeholder={this._props.placeholder}>
<span
style={{
- fontStyle: this.props.fontStyle,
- fontSize: this.props.fontSize,
+ fontStyle: this._props.fontStyle,
+ fontSize: this._props.fontSize,
}}>
- {this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}
+ {this._props.fieldContents ? <FieldView {...this._props.fieldContents} /> : this.props.contents ? this._props.contents?.valueOf() : this._props.placeholder?.valueOf()}
</span>
</div>
);
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 9efa7baac..884c74f9b 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -12,7 +12,7 @@ import { DocumentManager } from '../util/DocumentManager';
import { undoBatch } from '../util/UndoManager';
import { InkingStroke } from './InkingStroke';
import { DocumentView } from './nodes/DocumentView';
-import _ = require('lodash');
+import * as _ from 'lodash';
export class InkStrokeProperties {
static _Instance: InkStrokeProperties | undefined;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 41a2507f9..d34df8742 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -141,7 +141,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
public static toggleMask = action((inkDoc: Doc) => {
inkDoc.stroke_isInkMask = !inkDoc.stroke_isInkMask;
});
- @observable controlUndo: UndoManager.Batch | undefined;
+ @observable controlUndo: UndoManager.Batch | undefined = undefined;
/**
* Drags the a simple bezier segment of the stroke.
* Also adds a control point when double clicking on the stroke.
@@ -466,7 +466,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
<svg
className="inkStroke"
style={{
- transform: isInkMask ? `rotate(-${NumCast(this.props.CollectionFreeFormDocumentView?.().props.w_Rotation?.() ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+ transform: isInkMask ? `rotate(-${NumCast(this.props.CollectionFreeFormDocumentView?.()._props.w_Rotation?.() ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
// mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
cursor: this.props.isSelected() ? 'default' : undefined,
}}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 50feccce2..dae2490c9 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -3,11 +3,11 @@ import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons';
import * as far from '@fortawesome/free-regular-svg-icons';
import * as fa from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import '../../../node_modules/browndash-components/dist/styles/global.min.css';
-import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
+import { action, computed, configure, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import 'normalize.css';
import * as React from 'react';
+import '../../../node_modules/browndash-components/dist/styles/global.min.css';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { DocCast, StrCast } from '../../fields/Types';
import { emptyFunction, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../Utils';
@@ -48,7 +48,7 @@ import { LightboxView } from './LightboxView';
import { LinkMenu } from './linking/LinkMenu';
import './MainView.scss';
import { AudioBox } from './nodes/AudioBox';
-import { DocumentLinksButton } from './nodes/DocumentLinksButton';
+import { DocButtonState } from './nodes/DocumentLinksButton';
import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
@@ -131,14 +131,14 @@ export class MainView extends React.Component {
}
headerBarDocWidth = () => this.mainDocViewWidth();
- headerBarDocHeight = () => (this._hideUI ? 0 : SettingsManager.headerBarHeight ?? 0);
+ headerBarDocHeight = () => (this._hideUI ? 0 : SettingsManager.Instance?.headerBarHeight ?? 0);
topMenuHeight = () => (this._hideUI ? 0 : 35);
topMenuWidth = returnZero; // value is ignored ...
leftMenuWidth = () => (this._hideUI ? 0 : Number(LEFT_MENU_WIDTH.replace('px', '')));
leftMenuHeight = () => this._dashUIHeight;
leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth;
leftMenuFlyoutHeight = () => this._dashUIHeight;
- propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, SettingsManager.propertiesWidth || 0));
+ propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, SettingsManager.Instance?.propertiesWidth || 0));
propertiesHeight = () => this._dashUIHeight;
mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth() - this.leftMenuFlyoutWidth();
mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight();
@@ -232,6 +232,7 @@ export class MainView extends React.Component {
constructor(props: Readonly<{}>) {
super(props);
+ makeObservable(this);
DocumentViewInternal.addDocTabFunc = MainView.addDocTabFunc_impl;
MainView.Instance = this;
DashboardView._urlState = HistoryUtil.parseUrl(window.location) || ({} as any);
@@ -683,9 +684,9 @@ export class MainView extends React.Component {
setupMoveUpEvents(
this,
e,
- action(e => ((SettingsManager.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false)),
- action(() => SettingsManager.propertiesWidth < 5 && (SettingsManager.propertiesWidth = 0)),
- action(() => (SettingsManager.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0)),
+ action(e => ((SettingsManager.Instance.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false)),
+ action(() => SettingsManager.Instance.propertiesWidth < 5 && (SettingsManager.Instance.propertiesWidth = 0)),
+ action(() => (SettingsManager.Instance.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0)),
false
);
};
@@ -1037,7 +1038,7 @@ export class MainView extends React.Component {
<ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
{this._hideUI ? null : <TopBar />}
{LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
- {DocumentLinksButton.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocumentLinksButton.LinkEditorDocView = undefined))} docView={DocumentLinksButton.LinkEditorDocView} /> : null}
+ {DocButtonState.Instance.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocButtonState.Instance.LinkEditorDocView = undefined))} docView={DocButtonState.Instance.LinkEditorDocView} /> : null}
{this.linkDocPreview}
{((page: string) => {
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index 67b722e7a..af7f38937 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -1,8 +1,6 @@
import { isDark } from 'browndash-components';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc } from '../../fields/Doc';
-import { StrCast } from '../../fields/Types';
import { SettingsManager } from '../util/SettingsManager';
import './MainViewModal.scss';
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index a241e3a2b..3f0db4258 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -1,12 +1,12 @@
-import { action, computed, observable, ObservableMap } from 'mobx';
+import { action, computed, makeObservable, observable, ObservableMap } from 'mobx';
import { observer } from 'mobx-react';
+import * as React from 'react';
import { Doc, Opt } from '../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocData } from '../../fields/DocSymbols';
-import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { NumCast } from '../../fields/Types';
import { GetEffectiveAcl } from '../../fields/util';
-import { unimplementedFunction, Utils } from '../../Utils';
+import { copyProps, unimplementedFunction, Utils } from '../../Utils';
import { Docs, DocUtils } from '../documents/Documents';
import { DragManager } from '../util/DragManager';
import { FollowLinkScript } from '../util/LinkFollower';
@@ -15,7 +15,6 @@ import './MarqueeAnnotator.scss';
import { DocumentView } from './nodes/DocumentView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { AnchorMenu } from './pdf/AnchorMenu';
-import * as React from 'react';
const _global = (window /* browser */ || global) /* node */ as any;
export interface MarqueeAnnotatorProps {
@@ -46,6 +45,17 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
@computed get top() { return Math.min(this._start.y, this._start.y + this._height); } // prettier-ignore
@computed get left() { return Math.min(this._start.x, this._start.x + this._width);} // prettier-ignore
+ _prevProps: React.PropsWithChildren<MarqueeAnnotatorProps>;
+ @observable _props: React.PropsWithChildren<MarqueeAnnotatorProps>;
+ constructor(props: React.PropsWithChildren<MarqueeAnnotatorProps>) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+ componentDidMount() {
+ copyProps(this);
+ }
+
@action
static clearAnnotations(savedAnnotations: ObservableMap<number, HTMLDivElement[]>) {
AnchorMenu.Instance.Status = 'marquee';
@@ -56,7 +66,6 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
}
@undoBatch
- @action
makeAnnotationDocument = (color: string, isLinkButton?: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>): Opt<Doc> => {
const savedAnnoMap = savedAnnotations?.values() && Array.from(savedAnnotations?.values()).length ? savedAnnotations : this.props.savedAnnotations();
if (savedAnnoMap.size === 0) return undefined;
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index d1bd0500b..6e1a2cfb1 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -1,4 +1,4 @@
-import { action, observable, runInAction } from 'mobx';
+import { action, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, Opt } from '../../fields/Doc';
@@ -12,30 +12,38 @@ import './PreviewCursor.scss';
@observer
export class PreviewCursor extends React.Component<{}> {
- static _onKeyPress?: (e: KeyboardEvent) => void;
- static _getTransform: () => Transform;
- static _addDocument: (doc: Doc | Doc[]) => boolean;
- static _addLiveTextDoc: (doc: Doc) => void;
- static _nudge?: undefined | ((x: number, y: number) => boolean);
- static _slowLoadDocuments?: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>;
- @observable static _clickPoint = [0, 0];
- @observable public static Visible = false;
- public static Doc: Opt<Doc>;
+ static _instance: PreviewCursor;
+ public static get Instance() {
+ return PreviewCursor._instance;
+ }
+
+ _onKeyPress?: (e: KeyboardEvent) => void;
+ _getTransform?: () => Transform;
+ _addDocument?: (doc: Doc | Doc[]) => boolean;
+ _addLiveTextDoc?: (doc: Doc) => void;
+ _nudge?: undefined | ((x: number, y: number) => boolean);
+ _slowLoadDocuments?: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>;
+ @observable _clickPoint: number[];
+ @observable public Visible = false;
+ public Doc: Opt<Doc>;
constructor(props: any) {
super(props);
+ PreviewCursor._instance = this;
+ makeObservable(this);
+ this._clickPoint = observable([0, 0]);
document.addEventListener('keydown', this.onKeyPress);
document.addEventListener('paste', this.paste, true);
}
paste = async (e: ClipboardEvent) => {
- if (PreviewCursor.Visible && e.clipboardData) {
- const newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]);
- runInAction(() => (PreviewCursor.Visible = false));
+ if (this.Visible && e.clipboardData) {
+ const newPoint = this._getTransform?.().transformPoint(this._clickPoint[0], this._clickPoint[1]);
+ runInAction(() => (this.Visible = false));
// tests for URL and makes web document
const re: any = /^https?:\/\//g;
const plain = e.clipboardData.getData('text/plain');
- if (plain) {
+ if (plain && newPoint) {
// tests for youtube and makes video document
if (plain.indexOf('www.youtube.com/watch') !== -1) {
const batch = UndoManager.StartBatch('youtube upload');
@@ -47,21 +55,22 @@ export class PreviewCursor extends React.Component<{}> {
x: newPoint[0],
y: newPoint[1],
};
- PreviewCursor._slowLoadDocuments?.(plain.split('v=')[1].split('&')[0], options, generatedDocuments, '', undefined, PreviewCursor._addDocument).then(batch.end);
+ this._slowLoadDocuments?.(plain.split('v=')[1].split('&')[0], options, generatedDocuments, '', undefined, this._addDocument ?? returnFalse).then(batch.end);
} else if (re.test(plain)) {
const url = plain;
if (!url.startsWith(window.location.href)) {
- undoBatch(() =>
- PreviewCursor._addDocument(
- Docs.Create.WebDocument(url, {
- title: url,
- _width: 500,
- _height: 300,
- data_useCors: true,
- x: newPoint[0],
- y: newPoint[1],
- })
- )
+ undoBatch(
+ () =>
+ this._addDocument?.(
+ Docs.Create.WebDocument(url, {
+ title: url,
+ _width: 500,
+ _height: 300,
+ data_useCors: true,
+ x: newPoint[0],
+ y: newPoint[1],
+ })
+ )
)();
} else alert('cannot paste dash into itself');
} else if (plain.startsWith('__DashDocId(') || plain.startsWith('__DashCloneId(')) {
@@ -70,13 +79,13 @@ export class PreviewCursor extends React.Component<{}> {
const strs = docids[0].split(','); // hack! docids[0] is the top left of the selection rectangle
const ptx = Number(strs[0].substring((clone ? '__DashCloneId(' : '__DashDocId(').length));
const pty = Number(strs[1].substring(0, strs[1].length - 1));
- Doc.Paste(docids.slice(1), clone, PreviewCursor._addDocument, ptx, pty, newPoint);
+ this._addDocument && Doc.Paste(docids.slice(1), clone, this._addDocument, ptx, pty, newPoint);
e.stopPropagation();
} else {
FormattedTextBox.PasteOnLoad = e;
if (e.clipboardData.getData('dash/pdfAnchor')) e.preventDefault();
- UndoManager.RunInBatch(() => PreviewCursor._addLiveTextDoc(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined, undefined, 750)), 'paste');
+ UndoManager.RunInBatch(() => this._addLiveTextDoc?.(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined, undefined, 750)), 'paste');
}
}
//pasting in images
@@ -84,17 +93,19 @@ export class PreviewCursor extends React.Component<{}> {
const re: any = /<img src="(.*?)"/g;
const arr: any[] = re.exec(e.clipboardData.getData('text/html'));
- undoBatch(() => {
- const doc = Docs.Create.ImageDocument(arr[1], {
- _width: 300,
- title: arr[1],
- x: newPoint[0],
- y: newPoint[1],
- });
- ImageUtils.ExtractExif(doc);
- PreviewCursor._addDocument(doc);
- })();
- } else if (e.clipboardData.items.length) {
+ if (newPoint) {
+ undoBatch(() => {
+ const doc = Docs.Create.ImageDocument(arr[1], {
+ _width: 300,
+ title: arr[1],
+ x: newPoint[0],
+ y: newPoint[1],
+ });
+ ImageUtils.ExtractExif(doc);
+ this._addDocument?.(doc);
+ })();
+ }
+ } else if (e.clipboardData.items.length && newPoint) {
const batch = UndoManager.StartBatch('collection view drop');
const files: File[] = [];
Array.from(e.clipboardData.items).forEach(item => {
@@ -102,7 +113,7 @@ export class PreviewCursor extends React.Component<{}> {
file && files.push(file);
});
const generatedDocuments = await DocUtils.uploadFilesToDocs(files, { x: newPoint[0], y: newPoint[1] });
- generatedDocuments.forEach(PreviewCursor._addDocument);
+ this._addDocument && generatedDocuments.forEach(this._addDocument);
batch.end();
}
}
@@ -135,25 +146,25 @@ export class PreviewCursor extends React.Component<{}> {
) {
if ((!e.metaKey && !e.ctrlKey) || (e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 65 && e.keyCode <= 90)) {
// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
- PreviewCursor.Visible && PreviewCursor._onKeyPress?.(e);
- ((!e.ctrlKey && !e.metaKey) || e.key !== 'v') && (PreviewCursor.Visible = false);
+ this.Visible && this._onKeyPress?.(e);
+ ((!e.ctrlKey && !e.metaKey) || e.key !== 'v') && (this.Visible = false);
}
- } else if (PreviewCursor.Visible) {
+ } else if (this.Visible) {
if (e.key === 'ArrowRight') {
- PreviewCursor._nudge?.(1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
+ this._nudge?.(1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
} else if (e.key === 'ArrowLeft') {
- PreviewCursor._nudge?.(-1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
+ this._nudge?.(-1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
} else if (e.key === 'ArrowUp') {
- PreviewCursor._nudge?.(0, 1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
+ this._nudge?.(0, 1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
} else if (e.key === 'ArrowDown') {
- PreviewCursor._nudge?.(0, -1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
+ this._nudge?.(0, -1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
}
}
};
//when focus is lost, this will remove the preview cursor
@action onBlur = (): void => {
- PreviewCursor.Visible = false;
+ this.Visible = false;
};
@action
@@ -167,23 +178,21 @@ export class PreviewCursor extends React.Component<{}> {
nudge: undefined | ((nudgeX: number, nudgeY: number) => boolean),
slowLoadDocuments: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>
) {
- this._clickPoint = [x, y];
- this._onKeyPress = onKeyPress;
- this._addLiveTextDoc = addLiveText;
- this._getTransform = getTransform;
- this._addDocument = addDocument || returnFalse;
- this._nudge = nudge;
- this._slowLoadDocuments = slowLoadDocuments;
- this.Visible = true;
+ const self = PreviewCursor.Instance;
+ if (self) {
+ self._clickPoint = [x, y];
+ self._onKeyPress = onKeyPress;
+ self._addLiveTextDoc = addLiveText;
+ self._getTransform = getTransform;
+ self._addDocument = addDocument || returnFalse;
+ self._nudge = nudge;
+ self._slowLoadDocuments = slowLoadDocuments;
+ self.Visible = true;
+ }
}
render() {
- return !PreviewCursor._clickPoint || !PreviewCursor.Visible ? null : (
- <div
- className="previewCursor"
- onBlur={this.onBlur}
- tabIndex={0}
- ref={e => e?.focus()}
- style={{ color: lightOrDark(PreviewCursor.Doc?.backgroundColor ?? 'white'), transform: `translate(${PreviewCursor._clickPoint[0]}px, ${PreviewCursor._clickPoint[1]}px)` }}>
+ return !this._clickPoint || !this.Visible ? null : (
+ <div className="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={e => e?.focus()} style={{ color: lightOrDark(this.Doc?.backgroundColor ?? 'white'), transform: `translate(${this._clickPoint[0]}px, ${this._clickPoint[1]}px)` }}>
I
</div>
);
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 2c3cd8eac..b4c591b89 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -5,7 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Checkbox, Tooltip } from '@mui/material';
import { Colors, EditableText, IconButton, NumberInput, Size, Slider, Type } from 'browndash-components';
import { concat } from 'lodash';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { ColorResult, SketchPicker } from 'react-color';
import * as Icons from 'react-icons/bs'; //{BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs"
@@ -17,7 +17,7 @@ import { List } from '../../fields/List';
import { ComputedField } from '../../fields/ScriptField';
import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
import { GetEffectiveAcl, normalizeEmail, SharingPermissions } from '../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../Utils';
+import { copyProps, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../Utils';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { DocumentManager } from '../util/DocumentManager';
import { GroupManager } from '../util/GroupManager';
@@ -53,8 +53,12 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
private _widthUndo?: UndoManager.Batch;
public static Instance: PropertiesView | undefined;
- constructor(props: any) {
+ _prevProps: React.PropsWithChildren<PropertiesViewProps>;
+ @observable _props: React.PropsWithChildren<PropertiesViewProps>;
+ constructor(props: React.PropsWithChildren<PropertiesViewProps>) {
super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
PropertiesView.Instance = this;
}
@@ -116,6 +120,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
);
}
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
componentWillUnmount() {
Object.values(this._disposers).forEach(disposer => disposer?.());
}
@@ -130,7 +138,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
return [CollectionViewType.Stacking, CollectionViewType.NoteTaking].includes(this.selectedDoc?.type_collection as any);
}
- rtfWidth = () => (!this.selectedLayoutDoc ? 0 : Math.min(NumCast(this.selectedLayoutDoc?._width), this.props.width - 20));
+ rtfWidth = () => (!this.selectedLayoutDoc ? 0 : Math.min(NumCast(this.selectedLayoutDoc?._width), this._props.width - 20));
rtfHeight = () => (!this.selectedLayoutDoc ? 0 : this.rtfWidth() <= NumCast(this.selectedLayoutDoc?._width) ? Math.min(NumCast(this.selectedLayoutDoc?._height), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT);
@action
@@ -138,8 +146,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const layoutDoc = this.selectedLayoutDoc;
if (layoutDoc) {
const aspect = Doc.NativeAspect(layoutDoc, undefined, !layoutDoc._layout_fitWidth);
- if (aspect) return Math.min(NumCast(layoutDoc._width), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.width - 20));
- return Doc.NativeWidth(layoutDoc) ? Math.min(NumCast(layoutDoc._width), this.props.width - 20) : this.props.width - 20;
+ if (aspect) return Math.min(NumCast(layoutDoc._width), Math.min(this.MAX_EMBED_HEIGHT * aspect, this._props.width - 20));
+ return Doc.NativeWidth(layoutDoc) ? Math.min(NumCast(layoutDoc._width), this._props.width - 20) : this._props.width - 20;
}
return 0;
};
@@ -155,10 +163,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
Doc.NativeAspect(layoutDoc, undefined, true)
? this.docWidth() / Doc.NativeAspect(layoutDoc, undefined, true)
: layoutDoc._layout_fitWidth
- ? !Doc.NativeHeight(this.dataDoc)
- ? NumCast(this.props.height)
- : Math.min((this.docWidth() * Doc.NativeHeight(layoutDoc)) / Doc.NativeWidth(layoutDoc) || NumCast(this.props.height))
- : NumCast(layoutDoc._height) || 50
+ ? !Doc.NativeHeight(this.dataDoc)
+ ? NumCast(this._props.height)
+ : Math.min((this.docWidth() * Doc.NativeHeight(layoutDoc)) / Doc.NativeWidth(layoutDoc) || NumCast(this._props.height))
+ : NumCast(layoutDoc._height) || 50
)
);
}
@@ -254,12 +262,12 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
};
@computed get contexts() {
- return !this.selectedDoc ? null : <PropertiesDocContextSelector DocView={this.selectedDocumentView} hideTitle={true} addDocTab={this.props.addDocTab} />;
+ return !this.selectedDoc ? null : <PropertiesDocContextSelector DocView={this.selectedDocumentView} hideTitle={true} addDocTab={this._props.addDocTab} />;
}
@computed get contextCount() {
if (this.selectedDocumentView) {
- const target = this.selectedDocumentView.props.Document;
+ const target = this.selectedDocumentView.Document;
const embeddings = DocListCast(target.proto_embeddings);
return embeddings.length - 1;
} else {
@@ -269,7 +277,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@computed get links() {
const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor ?? this.selectedDoc;
- return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle={true} addDocTab={this.props.addDocTab} />;
+ return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle={true} addDocTab={this._props.addDocTab} />;
}
@computed get linkCount() {
@@ -377,7 +385,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
color={SettingsManager.userColor}
onClick={action(() => {
if (this.selectedDocumentView || this.selectedDoc) {
- SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDoc ? this.selectedDocumentView : undefined, this.selectedDoc);
+ SharingManager.Instance.open(this.selectedDocumentView?.Document === this.selectedDoc ? this.selectedDocumentView : undefined, this.selectedDoc);
}
})}
/>
@@ -619,7 +627,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
@undoBatch
- @action
setTitle = (value: string | number) => {
if (SelectionManager.Views().length > 1) {
SelectionManager.Views().map(dv => Doc.SetInPlace(dv.Document, 'title', value, true));
@@ -630,7 +637,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
};
@undoBatch
- @action
rotate = (angle: number) => {
const _centerPoints: { X: number; Y: number }[] = [];
const doc = this.selectedDoc;
@@ -1057,7 +1063,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
@undoBatch
- @action
changeDash = () => {
this.dashdStk = this.dashdStk === '2' ? '0' : '2';
};
@@ -1676,8 +1681,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(LinkManager.currentLink!);
if (!this.selectedDoc && !this.isPres) {
return (
- <div className="propertiesView" style={{ width: this.props.width }}>
- <div className="propertiesView-title" style={{ width: this.props.width }}>
+ <div className="propertiesView" style={{ width: this._props.width }}>
+ <div className="propertiesView-title" style={{ width: this._props.width }}>
No Document Selected
</div>
</div>
@@ -1690,11 +1695,11 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
style={{
background: SettingsManager.userBackgroundColor,
color: SettingsManager.userColor,
- width: this.props.width,
- minWidth: this.props.width,
+ width: this._props.width,
+ minWidth: this._props.width,
}}>
<div className="propertiesView-propAndInfoGrouping">
- <div className="propertiesView-title" style={{ width: this.props.width }}>
+ <div className="propertiesView-title" style={{ width: this._props.width }}>
Properties
<div className="propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties')}>
<IconButton icon={<FontAwesomeIcon icon="info-circle" />} color={SettingsManager.userColor} />
@@ -1722,8 +1727,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
? (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-sectionTitle" style={{ width: this.props.width }}>
+ <div className="propertiesView" style={{ width: this._props.width }}>
+ <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}>
Presentation
</div>
<div className="propertiesView-name" style={{ borderBottom: 0 }}>
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 4a08081a1..f3d515364 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -265,7 +265,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
}
case StyleProp.PointerEvents:
if (StrCast(doc?.pointerEvents) && !docProps?.LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc
- if (docProps?.DocumentView?.().props.LayoutTemplateString?.includes(KeyValueBox.name)) return 'all';
+ if (docProps?.DocumentView?.()._props.LayoutTemplateString?.includes(KeyValueBox.name)) return 'all';
if (DocumentView.ExploreMode || doc?.layout_unrendered) return isInk() ? 'visiblePainted' : 'all';
if (props?.pointerEvents?.() === 'none') return 'none';
if (opacity() === 0) return 'none';
@@ -302,7 +302,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
setSelectedVal={
action((dv) => {
(dv as any).select(false);
- (SettingsManager.propertiesWidth = 250);
+ (SettingsManager.Instance.propertiesWidth = 250);
setTimeout(action(() => {
if (PropertiesView.Instance) {
PropertiesView.Instance.CloseAll();
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 858090c05..4ddf9e69b 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,4 +1,4 @@
-import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, IReactionDisposer, makeObservable, observable, override, reaction, runInAction, untracked } from 'mobx';
import { observer } from 'mobx-react';
import * as ReactDOM from 'react-dom/client';
import * as GoldenLayout from '../../../client/goldenLayout';
@@ -10,7 +10,7 @@ import { List } from '../../../fields/List';
import { ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { GetEffectiveAcl, inheritParentAcls } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, incrementTitleCopy } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, copyProps, emptyFunction, incrementTitleCopy } from '../../../Utils';
import { DocServer } from '../../DocServer';
import { Docs } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
@@ -36,7 +36,7 @@ const _global = (window /* browser */ || global) /* node */ as any;
@observer
export class CollectionDockingView extends CollectionSubView() {
- @observable public static Instance: CollectionDockingView | undefined;
+ @observable public static Instance: CollectionDockingView | undefined = undefined;
public static makeDocumentConfig(document: Doc, panelName?: string, width?: number, keyValue?: boolean) {
return {
type: 'react-component',
@@ -62,9 +62,14 @@ export class CollectionDockingView extends CollectionSubView() {
}
private _goldenLayout: any = null;
static _highlightStyleSheet: any = addStyleSheet();
+
+ _prevProps: SubCollectionViewProps;
+ @override _props: SubCollectionViewProps;
constructor(props: SubCollectionViewProps) {
super(props);
- if (this.props.renderDepth < 0) runInAction(() => (CollectionDockingView.Instance = this));
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ if (this._props.renderDepth < 0) runInAction(() => (CollectionDockingView.Instance = this));
//Why is this here?
(window as any).React = React;
(window as any).ReactDOM = ReactDOM;
@@ -72,6 +77,10 @@ export class CollectionDockingView extends CollectionSubView() {
this.Document.myTrails; // this is equivalent to having a prefetchProxy for myTrails which is needed for the My Trails button in the UI which assumes that Doc.ActiveDashboard.myTrails is legit...
}
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
/**
* Switches from dragging a document around a freeform canvas to dragging it as a tab to be docked.
*
@@ -274,8 +283,8 @@ export class CollectionDockingView extends CollectionSubView() {
return true;
}
setupGoldenLayout = async () => {
- //const config = StrCast(this.props.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.props.Document)));
- const config = StrCast(this.props.Document.dockingConfig);
+ //const config = StrCast(this._props.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this._props.Document)));
+ const config = StrCast(this._props.Document.dockingConfig);
if (config) {
const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g);
const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) ?? [];
@@ -318,7 +327,7 @@ export class CollectionDockingView extends CollectionSubView() {
);
new _global.ResizeObserver(this.onResize).observe(this._containerRef.current);
this._reactionDisposer = reaction(
- () => StrCast(this.props.Document.dockingConfig),
+ () => StrCast(this._props.Document.dockingConfig),
config => {
if (!this._goldenLayout || this._ignoreStateChange !== config) {
// bcz: TODO! really need to diff config with ignoreStateChange and modify the current goldenLayout instead of building a new one.
@@ -328,7 +337,7 @@ export class CollectionDockingView extends CollectionSubView() {
}
);
reaction(
- () => this.props.PanelWidth(),
+ () => this._props.PanelWidth(),
width => !this._goldenLayout && width > 20 && setTimeout(() => this.setupGoldenLayout()), // need to wait for the collectiondockingview-container to have it's width/height since golden layout reads that to configure its windows
{ fireImmediately: true }
);
@@ -375,7 +384,7 @@ export class CollectionDockingView extends CollectionSubView() {
.map(id => DocServer.GetCachedRefField(id))
.filter(f => f)
.map(f => f as Doc);
- const changesMade = this.props.Document.dockingConfig !== json;
+ const changesMade = this._props.Document.dockingConfig !== json;
if (changesMade) {
if (![AclAdmin, AclEdit].includes(GetEffectiveAcl(this.dataDoc))) {
this.layoutDoc.dockingConfig = json;
@@ -425,7 +434,7 @@ export class CollectionDockingView extends CollectionSubView() {
};
public CaptureThumbnail() {
- const content = this.props.DocumentView?.()?.ContentDiv;
+ const content = this._props.DocumentView?.()?.ContentDiv;
if (content) {
const _width = Number(getComputedStyle(content).width.replace('px', ''));
const _height = Number(getComputedStyle(content).height.replace('px', ''));
@@ -472,7 +481,7 @@ export class CollectionDockingView extends CollectionSubView() {
stateChanged = () => {
this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
const json = JSON.stringify(this._goldenLayout.toConfig());
- const changesMade = this.props.Document.dockingConfig !== json;
+ const changesMade = this._props.Document.dockingConfig !== json;
return changesMade;
};
@@ -506,8 +515,8 @@ export class CollectionDockingView extends CollectionSubView() {
if (dashboard && e.target === stack.header?.element[0] && e.button === 2) {
dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1;
const docToAdd = Docs.Create.FreeformDocument([], {
- _width: this.props.PanelWidth(),
- _height: this.props.PanelHeight(),
+ _width: this._props.PanelWidth(),
+ _height: this._props.PanelHeight(),
_freeform_backgroundGrid: true,
_layout_fitWidth: true,
title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
@@ -524,8 +533,8 @@ export class CollectionDockingView extends CollectionSubView() {
if (dashboard) {
dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1;
const docToAdd = Docs.Create.FreeformDocument([], {
- _width: this.props.PanelWidth(),
- _height: this.props.PanelHeight(),
+ _width: this._props.PanelWidth(),
+ _height: this._props.PanelHeight(),
_layout_fitWidth: true,
_freeform_backgroundGrid: true,
title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
@@ -573,14 +582,14 @@ export class CollectionDockingView extends CollectionSubView() {
render() {
const href = ImageCast(this.Document.thumb)?.url?.href;
- return this.props.renderDepth > -1 ? (
+ return this._props.renderDepth > -1 ? (
<div>
{href ? (
<img
style={{ background: 'white', top: 0, position: 'absolute' }}
src={href} // + '?d=' + (new Date()).getTime()}
- width={this.props.PanelWidth()}
- height={this.props.PanelHeight()}
+ width={this._props.PanelWidth()}
+ height={this._props.PanelHeight()}
/>
) : (
<p>nested dashboards has no thumbnail</p>
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 61a5738ec..1c6f97c14 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -1,10 +1,9 @@
import * as React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, DocListCast } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
-import { Id } from '../../../fields/FieldSymbols';
import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { ScriptField } from '../../../fields/ScriptField';
import { emptyFunction, numberRange, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../Utils';
@@ -49,14 +48,20 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@observable private color: string = '#f1efeb';
@observable private collapsed: boolean = false;
@observable private _paletteOn = false;
+ @observable _props: React.PropsWithChildren<CMVFieldRowProps>;
+ constructor(props: React.PropsWithChildren<CMVFieldRowProps>) {
+ super(props);
+ this._props = props;
+ makeObservable(this);
+ }
private set _heading(value: string) {
- runInAction(() => this.props.headingObject && (this.props.headingObject.heading = this.heading = value));
+ runInAction(() => this._props.headingObject && (this._props.headingObject.heading = this.heading = value));
}
private set _color(value: string) {
- runInAction(() => this.props.headingObject && (this.props.headingObject.color = this.color = value));
+ runInAction(() => this._props.headingObject && (this._props.headingObject.color = this.color = value));
}
private set _collapsed(value: boolean) {
- runInAction(() => this.props.headingObject && (this.props.headingObject.collapsed = this.collapsed = value));
+ runInAction(() => this._props.headingObject && (this._props.headingObject.collapsed = this.collapsed = value));
}
private _dropDisposer?: DragManager.DragDropDisposer;
@@ -68,28 +73,28 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
this._dropDisposer?.();
if (ele) {
this._ele = ele;
- this.props.observeHeight(ele);
+ this._props.observeHeight(ele);
this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
}
};
@action
componentDidMount() {
- this.heading = this.props.headingObject?.heading || '';
- this.color = this.props.headingObject?.color || '#f1efeb';
- this.collapsed = this.props.headingObject?.collapsed || false;
+ this.heading = this._props.headingObject?.heading || '';
+ this.color = this._props.headingObject?.color || '#f1efeb';
+ this.collapsed = this._props.headingObject?.collapsed || false;
}
componentWillUnmount() {
- this.props.unobserveHeight(this._ele);
+ this._props.unobserveHeight(this._ele);
}
getTrueHeight = () => {
if (this.collapsed) {
- this.props.setDocHeight(this.heading, 20);
+ this._props.setDocHeight(this.heading, 20);
} else {
const rawHeight = this._contRef.current!.getBoundingClientRect().height + 15; //+ 15 accounts for the group header
- const transformScale = this.props.screenToLocalTransform().Scale;
+ const transformScale = this._props.screenToLocalTransform().Scale;
const trueHeight = rawHeight * transformScale;
- this.props.setDocHeight(this.heading, trueHeight);
+ this._props.setDocHeight(this.heading, trueHeight);
}
};
@@ -97,10 +102,10 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
rowDrop = action((e: Event, de: DragManager.DropEvent) => {
this._createEmbeddingSelected = false;
if (de.complete.docDragData) {
- const key = this.props.pivotField;
+ const key = this._props.pivotField;
const castedValue = this.getValue(this.heading);
const onLayoutDoc = this.onLayoutDoc(key);
- if (this.props.parent.onInternalDrop(e, de)) {
+ if (this._props.parent.onInternalDrop(e, de)) {
de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !onLayoutDoc));
}
return true;
@@ -119,15 +124,15 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@action
headingChanged = (value: string, shiftDown?: boolean) => {
this._createEmbeddingSelected = false;
- const key = this.props.pivotField;
+ const key = this._props.pivotField;
const castedValue = this.getValue(value);
if (castedValue) {
- if (this.props.parent.colHeaderData) {
- if (this.props.parent.colHeaderData.map(i => i.heading).indexOf(castedValue.toString()) > -1) {
+ if (this._props.parent.colHeaderData) {
+ if (this._props.parent.colHeaderData.map(i => i.heading).indexOf(castedValue.toString()) > -1) {
return false;
}
}
- this.props.docList.forEach(d => Doc.SetInPlace(d, key, castedValue, true));
+ this._props.docList.forEach(d => Doc.SetInPlace(d, key, castedValue, true));
this._heading = castedValue.toString();
return true;
}
@@ -152,24 +157,24 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
addDocument = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
if (!value && !forceEmptyNote) return false;
this._createEmbeddingSelected = false;
- const key = this.props.pivotField;
+ const key = this._props.pivotField;
const newDoc = Docs.Create.TextDocument('', { _layout_autoHeight: true, _width: 200, _layout_fitWidth: true, title: value });
const onLayoutDoc = this.onLayoutDoc(key);
FormattedTextBox.SetSelectOnLoad(newDoc);
FormattedTextBox.SelectOnLoadChar = value;
- (onLayoutDoc ? newDoc : newDoc[DocData])[key] = this.getValue(this.props.heading);
- const docs = this.props.parent.childDocList;
- return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this.props.parent.props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list)
+ (onLayoutDoc ? newDoc : newDoc[DocData])[key] = this.getValue(this._props.heading);
+ const docs = this._props.parent.childDocList;
+ return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list)
};
deleteRow = undoBatch(
action(() => {
this._createEmbeddingSelected = false;
- const key = this.props.pivotField;
- this.props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true));
- if (this.props.parent.colHeaderData && this.props.headingObject) {
- const index = this.props.parent.colHeaderData.indexOf(this.props.headingObject);
- this.props.parent.colHeaderData.splice(index, 1);
+ const key = this._props.pivotField;
+ this._props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true));
+ if (this._props.parent.colHeaderData && this._props.headingObject) {
+ const index = this._props.parent.colHeaderData.indexOf(this._props.headingObject);
+ this._props.parent.colHeaderData.splice(index, 1);
}
})
);
@@ -182,8 +187,8 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
};
headerMove = (e: PointerEvent) => {
- const embedding = Doc.MakeEmbedding(this.props.Document);
- const key = this.props.pivotField;
+ const embedding = Doc.MakeEmbedding(this._props.Document);
+ const key = this._props.pivotField;
let value = this.getValue(this.heading);
value = typeof value === 'string' ? `"${value}"` : value;
const script = `return doc.${key} === ${value}`;
@@ -198,7 +203,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@action
headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
if (e.button === 0 && !e.ctrlKey) {
- setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => !this.props.chromeHidden && this.collapseSection(e));
+ setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => !this._props.chromeHidden && this.collapseSection(e));
this._createEmbeddingSelected = false;
}
};
@@ -207,7 +212,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
* Returns true if a key is on the layout doc of the documents in the collection.
*/
onLayoutDoc = (key: string): boolean => {
- DocListCast(this.props.parent.Document.data).forEach(doc => {
+ DocListCast(this._props.parent.Document.data).forEach(doc => {
if (Doc.Get(doc, key, true)) return true;
});
return false;
@@ -264,19 +269,19 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
};
@computed get contentLayout() {
- const rows = Math.max(1, Math.min(this.props.docList.length, Math.floor((this.props.parent.props.PanelWidth() - 2 * this.props.parent.xMargin) / (this.props.parent.columnWidth + this.props.parent.gridGap))));
- const showChrome = !this.props.chromeHidden;
- const stackPad = showChrome ? `0px ${this.props.parent.xMargin}px` : `${this.props.parent.yMargin}px ${this.props.parent.xMargin}px 0px ${this.props.parent.xMargin}px `;
+ const rows = Math.max(1, Math.min(this._props.docList.length, Math.floor((this._props.parent._props.PanelWidth() - 2 * this._props.parent.xMargin) / (this._props.parent.columnWidth + this._props.parent.gridGap))));
+ const showChrome = !this._props.chromeHidden;
+ const stackPad = showChrome ? `0px ${this._props.parent.xMargin}px` : `${this._props.parent.yMargin}px ${this._props.parent.xMargin}px 0px ${this._props.parent.xMargin}px `;
return this.collapsed ? null : (
<div style={{ position: 'relative' }}>
- {this.props.showHandle && this.props.parent.props.isContentActive() ? this.props.parent.columnDragger : null}
+ {this._props.showHandle && this._props.parent._props.isContentActive() ? this._props.parent.columnDragger : null}
{showChrome ? (
<div
className="collectionStackingView-addDocumentButton"
style={
{
//width: style.columnWidth / style.numGroupColumns,
- //padding: `${NumCast(this.props.parent.layoutDoc._yPadding, this.props.parent.yMargin)}px 0px 0px 0px`,
+ //padding: `${NumCast(this._props.parent.layoutDoc._yPadding, this._props.parent.yMargin)}px 0px 0px 0px`,
}
}>
<EditableView GetValue={returnEmptyString} SetValue={this.addDocument} textCallback={this.textCallback} contents={'+ NEW'} />
@@ -287,25 +292,25 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
ref={this._contRef}
style={{
padding: stackPad,
- minHeight: this.props.showHandle && this.props.parent.props.isContentActive() ? '10px' : undefined,
- width: this.props.parent.NodeWidth,
- gridGap: this.props.parent.gridGap,
- gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this.props.parent.columnWidth}px`, ''),
+ minHeight: this._props.showHandle && this._props.parent._props.isContentActive() ? '10px' : undefined,
+ width: this._props.parent.NodeWidth,
+ gridGap: this._props.parent.gridGap,
+ gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this._props.parent.columnWidth}px`, ''),
}}>
- {this.props.parent.children(this.props.docList)}
+ {this._props.parent.children(this._props.docList)}
</div>
</div>
);
}
@computed get headingView() {
- const noChrome = this.props.chromeHidden;
- const key = this.props.pivotField;
- const evContents = this.heading ? this.heading : this.props.type && this.props.type === 'number' ? '0' : `NO ${key.toUpperCase()} VALUE`;
+ const noChrome = this._props.chromeHidden;
+ const key = this._props.pivotField;
+ const evContents = this.heading ? this.heading : this._props.type && this._props.type === 'number' ? '0' : `NO ${key.toUpperCase()} VALUE`;
const editableHeaderView = <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} />;
- return this.props.Document.miniHeaders ? (
+ return this._props.Document.miniHeaders ? (
<div className="collectionStackingView-miniHeader">{editableHeaderView}</div>
- ) : !this.props.headingObject ? null : (
+ ) : !this._props.headingObject ? null : (
<div className="collectionStackingView-sectionHeader" ref={this._headerRef}>
<div
className="collectionStackingView-sectionHeader-subCont"
@@ -352,7 +357,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
render() {
const background = this._background;
return (
- <div className="collectionStackingView-masonrySection" style={{ width: this.props.parent.NodeWidth, background }} ref={this.createRowDropRef} onPointerEnter={this.pointerEnteredRow} onPointerLeave={this.pointerLeaveRow}>
+ <div className="collectionStackingView-masonrySection" style={{ width: this._props.parent.NodeWidth, background }} ref={this.createRowDropRef} onPointerEnter={this.pointerEnteredRow} onPointerLeave={this.pointerLeaveRow}>
{this.headingView}
{this.contentLayout}
</div>
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 3edf4135f..f8e80ed87 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -33,7 +33,7 @@ interface CollectionMenuProps {
export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
@observable static Instance: CollectionMenu;
- @observable SelectedCollection: DocumentView | undefined;
+ @observable SelectedCollection: DocumentView | undefined = undefined;
@observable FieldKey: string;
private _docBtnRef = React.createRef<HTMLDivElement>();
@@ -41,7 +41,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
constructor(props: any) {
super(props);
this.FieldKey = '';
- runInAction(() => (CollectionMenu.Instance = this));
+ CollectionMenu.Instance = this;
this._canFade = false; // don't let the inking menu fade away
runInAction(() => (this.Pinned = Cast(Doc.UserDoc()['menuCollections-pinned'], 'boolean', true)));
this.jumpTo(300, 300);
@@ -69,19 +69,19 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
@action
toggleTopBar = () => {
- if (SettingsManager.headerBarHeight > 0) {
- SettingsManager.headerBarHeight = 0;
+ if (SettingsManager.Instance.headerBarHeight > 0) {
+ SettingsManager.Instance.headerBarHeight = 0;
} else {
- SettingsManager.headerBarHeight = 60;
+ SettingsManager.Instance.headerBarHeight = 60;
}
};
@action
toggleProperties = () => {
if (MainView.Instance.propertiesWidth() > 0) {
- SettingsManager.propertiesWidth = 0;
+ SettingsManager.Instance.propertiesWidth = 0;
} else {
- SettingsManager.propertiesWidth = 300;
+ SettingsManager.Instance.propertiesWidth = 300;
}
};
@@ -94,7 +94,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
@computed get contMenuButtons() {
const selDoc = Doc.MyContextMenuBtns;
return !(selDoc instanceof Doc) ? null : (
- <div className="collectionMenu-contMenuButtons" ref={this._docBtnRef} style={{ height: this.props.panelHeight() }}>
+ <div className="collectionMenu-contMenuButtons" ref={this._docBtnRef} style={{ height: this._props.panelHeight() }}>
<CollectionLinearView
Document={selDoc}
fieldKey="data"
@@ -113,8 +113,8 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
pinToPres={emptyFunction}
removeDocument={returnFalse}
ScreenToLocalTransform={this.buttonBarXf}
- PanelWidth={this.props.panelWidth}
- PanelHeight={this.props.panelHeight}
+ PanelWidth={this._props.panelWidth}
+ PanelHeight={this._props.panelHeight}
renderDepth={0}
focus={emptyFunction}
whenChildContentsActiveChanged={emptyFunction}
@@ -127,10 +127,10 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
}
render() {
- const headerIcon = SettingsManager.headerBarHeight > 0 ? 'angle-double-up' : 'angle-double-down';
- const headerTitle = SettingsManager.headerBarHeight > 0 ? 'Close Header Bar' : 'Open Header Bar';
- const propIcon = SettingsManager.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
- const propTitle = SettingsManager.propertiesWidth > 0 ? 'Close Properties' : 'Open Properties';
+ const headerIcon = SettingsManager.Instance.headerBarHeight > 0 ? 'angle-double-up' : 'angle-double-down';
+ const headerTitle = SettingsManager.Instance.headerBarHeight > 0 ? 'Close Header Bar' : 'Open Header Bar';
+ const propIcon = SettingsManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
+ const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Properties' : 'Open Properties';
const hardCodedButtons = (
<div className={`hardCodedButtons`}>
@@ -139,7 +139,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
type={Type.PRIM}
color={SettingsManager.userColor}
onClick={this.toggleTopBar}
- toggleStatus={SettingsManager.headerBarHeight > 0}
+ toggleStatus={SettingsManager.Instance.headerBarHeight > 0}
icon={<FontAwesomeIcon icon={headerIcon} size="lg" />}
tooltip={headerTitle}
/>
@@ -148,7 +148,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
type={Type.PRIM}
color={SettingsManager.userColor}
onClick={this.toggleProperties}
- toggleStatus={SettingsManager.propertiesWidth > 0}
+ toggleStatus={SettingsManager.Instance.propertiesWidth > 0}
icon={<FontAwesomeIcon icon={propIcon} size="lg" />}
tooltip={propTitle}
/>
@@ -185,7 +185,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
//(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
get document() {
- return this.props.docView?.props.Document;
+ return this.props.docView?.Document;
}
get target() {
return this.document;
@@ -577,13 +577,18 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
return this.props.docView.props.Document;
}
+ @computed get panelWidth() {
+ return this.props.docView.props.PanelWidth();
+ }
+
componentDidMount() {
runInAction(() => (this.resize = this.props.docView.props.PanelWidth() < 700));
// listener to reduce text on chrome resize (panel resize)
- this.resizeListenerDisposer = computed(() => this.props.docView.props.PanelWidth()).observe(({ newValue }) => {
- runInAction(() => (this.resize = newValue < 700));
- });
+ this.resizeListenerDisposer = reaction(
+ () => this.panelWidth,
+ newValue => (this.resize = newValue < 700)
+ );
}
componentWillUnmount() {
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index ba00c4fa0..f9d99f1e5 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -416,7 +416,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// onInternalDrop is used when dragging and dropping a document within the view, such as dragging
// a document to a new column or changing its order within the column.
@undoBatch
- @action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData) {
if (super.onInternalDrop(e, de)) {
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index c5677e2ba..a0f8b9c89 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -43,7 +43,7 @@ export class CollectionPileView extends CollectionSubView() {
removePileDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
(doc instanceof Doc ? [doc] : doc).forEach(d => Doc.deiconifyView(d));
const ret = this.props.moveDocument?.(doc, targetCollection, addDoc) || false;
- if (ret && !DocListCast(this.dataDoc[this.fieldKey ?? 'data']).length) this.props.DocumentView?.().props.removeDocument?.(this.Document);
+ if (ret && !DocListCast(this.dataDoc[this.fieldKey ?? 'data']).length) this.props.DocumentView?.()._props.removeDocument?.(this.Document);
return ret;
};
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 92b5470ae..28d15be71 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -1,7 +1,7 @@
-import * as React from 'react';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
+import * as React from 'react';
import { Doc, Opt } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
@@ -9,7 +9,7 @@ import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { Cast, NumCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
-import { emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
+import { copyProps, emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
@@ -20,9 +20,9 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from '../../util/UndoManager';
-import { AudioWaveform } from '../AudioWaveform';
-import { CollectionSubView } from '../collections/CollectionSubView';
+import { CollectionSubView, SubCollectionViewProps } from '../collections/CollectionSubView';
import { LightboxView } from '../LightboxView';
+import { AudioWaveform } from '../nodes/audio/AudioWaveform';
import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../nodes/DocumentView';
import { LabelBox } from '../nodes/LabelBox';
import { VideoBox } from '../nodes/VideoBox';
@@ -53,17 +53,28 @@ export enum TrimScope {
}
@observer
-export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() {
+export class CollectionStackedTimeline extends CollectionSubView<SubCollectionViewProps & CollectionStackedTimelineProps>() {
@observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined;
@observable public static CurrentlyPlaying: DocumentView[];
+ _prevProps: React.PropsWithChildren<SubCollectionViewProps & CollectionStackedTimelineProps>;
+ @override _props: React.PropsWithChildren<SubCollectionViewProps & CollectionStackedTimelineProps>;
+ constructor(props: React.PropsWithChildren<SubCollectionViewProps & CollectionStackedTimelineProps>) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
static LabelScript: ScriptField;
static LabelPlayScript: ScriptField;
private _timeline: HTMLDivElement | null = null; // ref to actual timeline div
private _timelineWrapper: HTMLDivElement | null = null; // ref to timeline wrapper div for zooming and scrolling
private _markerStart: number = 0;
- @observable _markerEnd: number | undefined;
+ @observable _markerEnd: number | undefined = undefined;
@observable _trimming: number = TrimScope.None;
@observable _trimStart: number = 0; // trim controls start pos
@observable _trimEnd: number = 0; // trim controls end pos
@@ -73,14 +84,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
@observable _hoverTime: number = 0;
- @observable _thumbnail: string | undefined;
+ @observable _thumbnail: string | undefined = undefined;
// ensures that clip doesn't get trimmed so small that controls cannot be adjusted anymore
get minTrimLength() {
return Math.max(this._timeline?.getBoundingClientRect() ? 0.05 * this.clipDuration : 0, 0.5);
}
@computed get thumbnails() {
- return this.props.thumbnails?.();
+ return this._props.thumbnails?.();
}
@computed get trimStart() {
@@ -100,7 +111,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
return this.clipEnd - this.clipStart;
}
@computed get clipEnd() {
- return this.IsTrimming === TrimScope.All ? this.props.rawDuration : NumCast(this.layoutDoc.clipEnd, this.props.rawDuration);
+ return this.IsTrimming === TrimScope.All ? this._props.rawDuration : NumCast(this.layoutDoc.clipEnd, this._props.rawDuration);
}
@computed get currentTime() {
@@ -115,11 +126,10 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
document.addEventListener('keydown', this.keyEvents, true);
}
- @action
componentWillUnmount() {
document.removeEventListener('keydown', this.keyEvents, true);
if (CollectionStackedTimeline.SelectingRegion === this) {
- CollectionStackedTimeline.SelectingRegion = undefined;
+ runInAction(() => (CollectionStackedTimeline.SelectingRegion = undefined));
}
}
@@ -149,8 +159,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this._zoomFactor = zoom;
}
- anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag]));
- anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this.props.endTag], val) ?? null);
+ anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this._props.startTag]));
+ anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this._props.endTag], val) ?? null);
// converts screen pixel offset to time
toTimeline = (screen_delta: number, width: number) => {
@@ -177,13 +187,13 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
if (
// need to include range inputs because after dragging video time slider it becomes target element
!(e.target instanceof HTMLInputElement && !(e.target.type === 'range')) &&
- this.props.isContentActive()
+ this._props.isContentActive()
) {
// if shift pressed scrub 1 second otherwise 1/10th
const jump = e.shiftKey ? 1 : 0.1;
switch (e.key) {
case ' ':
- this.props.playing() ? this.props.Pause() : this.props.Play();
+ this._props.playing() ? this._props.Pause() : this._props.Play();
break;
case '^':
if (!CollectionStackedTimeline.SelectingRegion) {
@@ -191,7 +201,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
CollectionStackedTimeline.SelectingRegion = this;
} else {
this._markerEnd = this.currentTime;
- CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
+ CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
this._markerEnd = undefined;
CollectionStackedTimeline.SelectingRegion = undefined;
}
@@ -204,11 +214,11 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this._trimming = TrimScope.None;
break;
case 'ArrowLeft':
- this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime - jump), this.clipEnd));
+ this._props.setTime(Math.min(Math.max(this.clipStart, this.currentTime - jump), this.clipEnd));
e.stopPropagation();
break;
case 'ArrowRight':
- this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd));
+ this._props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd));
e.stopPropagation();
break;
}
@@ -218,7 +228,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
getLinkData(l: Doc) {
let la1 = l.link_anchor_1 as Doc;
let la2 = l.link_anchor_2 as Doc;
- const linkTime = NumCast(la2[this.props.startTag], NumCast(la1[this.props.startTag]));
+ const linkTime = NumCast(la2[this._props.startTag], NumCast(la1[this._props.startTag]));
if (Doc.AreProtosEqual(la1, this.dataDoc)) {
la1 = l.link_anchor_2 as Doc;
la2 = l.link_anchor_1 as Doc;
@@ -233,9 +243,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
const clientX = e.clientX;
const diff = rect ? clientX - rect?.x : null;
const shiftKey = e.shiftKey;
- if (rect && this.props.isContentActive()) {
- const wasPlaying = this.props.playing();
- if (wasPlaying) this.props.Pause();
+ if (rect && this._props.isContentActive()) {
+ const wasPlaying = this._props.playing();
+ if (wasPlaying) this._props.Pause();
var wasSelecting = this._markerEnd !== undefined;
setupMoveUpEvents(
this,
@@ -257,7 +267,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this._markerEnd = tmp;
}
if (!isClick && Math.abs(movement[0]) > 15 && !this.IsTrimming) {
- const anchor = CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
+ const anchor = CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false));
}
(!isClick || !wasSelecting) && (this._markerEnd = undefined);
@@ -265,17 +275,17 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
}),
(e, doubleTap) => {
if (e.button !== 2) {
- this.props.select(false);
- !wasPlaying && doubleTap && this.props.Play();
+ this._props.select(false);
+ !wasPlaying && doubleTap && this._props.Play();
}
},
- this.props.isSelected() || this.props.isContentActive(),
+ this._props.isSelected() || this._props.isContentActive(),
undefined,
() => {
if (shiftKey) {
- CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.props.fieldKey, this.currentTime, undefined, undefined, true);
+ CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this.currentTime, undefined, undefined, true);
} else {
- !wasPlaying && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
+ !wasPlaying && this._props.setTime(this.toTimeline(clientX - rect.x, rect.width));
}
}
);
@@ -290,7 +300,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
if (rect) {
this._hoverTime = this.toTimeline(clientX - rect.x, rect.width);
if (this.thumbnails) {
- const nearest = Math.floor((this._hoverTime / this.props.rawDuration) * VideoBox.numThumbnails);
+ const nearest = Math.floor((this._hoverTime / this._props.rawDuration) * VideoBox.numThumbnails);
const imgField = this.thumbnails.length > 0 ? new ImageField(this.thumbnails[nearest]) : undefined;
this._thumbnail = imgField?.url?.href ? imgField.url.href.replace('.png', '_m.png') : undefined;
}
@@ -305,7 +315,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this,
e,
action((e, [], []) => {
- if (rect && this.props.isContentActive()) {
+ if (rect && this._props.isContentActive()) {
this._trimStart = Math.min(Math.max(this.trimStart + (e.movementX / rect.width) * this.clipDuration, this.clipStart), this.trimEnd - this.minTrimLength);
}
return false;
@@ -325,7 +335,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this,
e,
action((e, [], []) => {
- if (rect && this.props.isContentActive()) {
+ if (rect && this._props.isContentActive()) {
this._trimEnd = Math.max(Math.min(this.trimEnd + (e.movementX / rect.width) * this.clipDuration, this.clipEnd), this.trimStart + this.minTrimLength);
}
return false;
@@ -348,8 +358,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
@action
scrollToTime = (time: number) => {
if (this._timelineWrapper) {
- if (time > this.toTimeline(this._scroll + this.props.PanelWidth(), this.timelineContentWidth)) {
- this._scroll = Math.min(this._scroll + this.props.PanelWidth(), this.timelineContentWidth - this.props.PanelWidth());
+ if (time > this.toTimeline(this._scroll + this._props.PanelWidth(), this.timelineContentWidth)) {
+ this._scroll = Math.min(this._scroll + this._props.PanelWidth(), this.timelineContentWidth - this._props.PanelWidth());
smoothScrollHorizontal(200, this._timelineWrapper, this._scroll);
} else if (time < this.toTimeline(this._scroll, this.timelineContentWidth)) {
this._scroll = (time / this.timelineContentWidth) * this.clipDuration;
@@ -363,15 +373,15 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number) {
if (super.onInternalDrop(e, de)) {
// determine x coordinate of drop and assign it to the documents being dragged --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view
- const localPt = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+ const localPt = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y);
const x = localPt[0] - docDragData.offset[0];
const timelinePt = this.toTimeline(x + this._scroll, this.timelineContentWidth);
docDragData.droppedDocuments.forEach(drop => {
const anchorEnd = this.anchorEnd(drop);
if (anchorEnd !== undefined) {
- Doc.SetInPlace(drop, drop._timecodeToHide === undefined ? this.props.endTag : 'timecodeToHide', timelinePt + anchorEnd - this.anchorStart(drop), false);
+ Doc.SetInPlace(drop, drop._timecodeToHide === undefined ? this._props.endTag : 'timecodeToHide', timelinePt + anchorEnd - this.anchorStart(drop), false);
}
- Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this.props.startTag : 'timecodeToShow', timelinePt, false);
+ Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this._props.startTag : 'timecodeToShow', timelinePt, false);
});
return true;
@@ -386,7 +396,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
// creates marker on timeline
@undoBatch
- @action
static createAnchor(doc: Doc, dataDoc: Doc, fieldKey: string, anchorStartTime: Opt<number>, anchorEndTime: Opt<number>, docAnchor: Opt<Doc>, addAsAnnotation: boolean) {
if (anchorStartTime === undefined) return doc;
const startTag = '_timecodeToShow';
@@ -422,20 +431,20 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05;
const endTime = this.anchorEnd(anchorDoc);
if (this.layoutDoc.autoPlayAnchors) {
- if (this.props.playing()) this.props.Pause();
+ if (this._props.playing()) this._props.Pause();
else {
- this.props.playFrom(seekTimeInSeconds, endTime);
+ this._props.playFrom(seekTimeInSeconds, endTime);
this.scrollToTime(seekTimeInSeconds);
}
} else {
if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) && endTime > NumCast(this.layoutDoc._layout_currentTimecode)) {
- if (!this.layoutDoc.autoPlayAnchors && this.props.playing()) {
- this.props.Pause();
+ if (!this.layoutDoc.autoPlayAnchors && this._props.playing()) {
+ this._props.Pause();
} else {
- this.props.Play();
+ this._props.Play();
}
} else {
- this.props.playFrom(seekTimeInSeconds, endTime);
+ this._props.playFrom(seekTimeInSeconds, endTime);
this.scrollToTime(seekTimeInSeconds);
}
}
@@ -450,17 +459,17 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05;
const endTime = this.anchorEnd(anchorDoc);
if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._layout_currentTimecode) - 1e-4) {
- if (this.props.playing()) this.props.Pause();
- else if (this.layoutDoc.autoPlayAnchors) this.props.Play();
+ if (this._props.playing()) this._props.Pause();
+ else if (this.layoutDoc.autoPlayAnchors) this._props.Play();
else if (!this.layoutDoc.autoPlayAnchors) {
const rect = this._timeline?.getBoundingClientRect();
- rect && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
+ rect && this._props.setTime(this.toTimeline(clientX - rect.x, rect.width));
}
} else {
if (this.layoutDoc.autoPlayAnchors) {
- this.props.playFrom(seekTimeInSeconds, endTime);
+ this._props.playFrom(seekTimeInSeconds, endTime);
} else {
- this.props.setTime(seekTimeInSeconds);
+ this._props.setTime(seekTimeInSeconds);
}
}
return { select: true };
@@ -490,18 +499,18 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
};
dictationHeightPercent = 50;
- dictationHeight = () => (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100;
+ dictationHeight = () => (this._props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100;
@computed get timelineContentHeight() {
- return (this.props.PanelHeight() * this.dictationHeightPercent) / 100;
+ return (this._props.PanelHeight() * this.dictationHeightPercent) / 100;
}
@computed get timelineContentWidth() {
- return this.props.PanelWidth() * this.zoomFactor;
+ return this._props.PanelWidth() * this.zoomFactor;
} // subtract size of container border
- dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight);
+ dictationScreenToLocalTransform = () => this._props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight);
- isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+ isContentActive = () => this._props.isSelected() || this._props.isContentActive();
currentTimecode = () => this.currentTime;
@@ -520,7 +529,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
}
@computed get timelineEvents() {
- return this.props.isContentActive() ? 'all' : this.props.isContentActive() === false ? 'none' : undefined;
+ return this._props.isContentActive() ? 'all' : this._props.isContentActive() === false ? 'none' : undefined;
}
render() {
const overlaps: {
@@ -537,7 +546,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
<div ref={this.createDashEventsTarget} style={{ pointerEvents: this.timelineEvents }}>
<div
className="collectionStackedTimeline-timelineContainer"
- style={{ width: this.props.PanelWidth(), cursor: SnappingManager.GetIsDragging() ? 'grab' : '' }}
+ style={{ width: this._props.PanelWidth(), cursor: SnappingManager.GetIsDragging() ? 'grab' : '' }}
onWheel={e => this.isContentActive() && e.stopPropagation()}
onScroll={this.setScroll}
onMouseMove={e => this.isContentActive() && this.onHover(e)}
@@ -553,11 +562,11 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
const end = this.anchorEnd(d.anchor, start + (10 / this.timelineContentWidth) * this.clipDuration);
if (end < this.clipStart || start > this.clipEnd) return null;
const left = Math.max(((start - this.clipStart) / this.clipDuration) * this.timelineContentWidth, 0);
- const top = (d.level / maxLevel) * this.props.PanelHeight();
+ const top = (d.level / maxLevel) * this._props.PanelHeight();
const timespan = Math.max(0, Math.min(end - this.clipStart, this.clipEnd)) - Math.max(0, start - this.clipStart);
const width = (timespan / this.clipDuration) * this.timelineContentWidth;
- const height = this.props.PanelHeight() / maxLevel;
- return this.props.Document.hideAnchors ? null : (
+ const height = this._props.PanelHeight() / maxLevel;
+ return this._props.Document.hideAnchors ? null : (
<div
className={'collectionStackedTimeline-marker-timeline'}
key={d.anchor[Id]}
@@ -569,7 +578,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
pointerEvents: 'none',
}}>
<StackedTimelineAnchor
- {...this.props}
+ {...this._props}
mark={d.anchor}
rangeClickScript={this.rangeClickScript}
rangePlayScript={this.rangePlayScript}
@@ -590,18 +599,19 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
);
})}
{!this.IsTrimming && this.selectionContainer}
- {!this.props.PanelHeight() ? null : (
+ {!this._props.PanelHeight() ? null : (
<AudioWaveform
- rawDuration={this.props.rawDuration}
- fieldKey={this.props.dataFieldKey}
+ rawDuration={this._props.rawDuration}
+ fieldKey={this._props.dataFieldKey}
duration={this.clipDuration}
- mediaPath={this.props.mediaPath}
+ mediaPath={this._props.mediaPath}
layoutDoc={this.layoutDoc}
clipStart={this.clipStart}
clipEnd={this.clipEnd}
zoomFactor={this.zoomFactor}
PanelHeight={this.timelineContentHeight}
PanelWidth={this.timelineContentWidth}
+ progress={(this.currentTime - this.clipStart) / this.clipDuration}
/>
)}
@@ -691,37 +701,44 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
_lastTimecode: number;
_disposer: IReactionDisposer | undefined;
- constructor(props: any) {
+ _prevProps: React.PropsWithChildren<StackedTimelineAnchorProps>;
+ @observable _props: React.PropsWithChildren<StackedTimelineAnchorProps>;
+ constructor(props: React.PropsWithChildren<StackedTimelineAnchorProps>) {
super(props);
- this._lastTimecode = this.props.currentTimecode();
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ this._lastTimecode = this._props.currentTimecode();
+ }
+ componentDidUpdate() {
+ copyProps(this);
}
// updates marker document title to reflect correct timecodes
computeTitle = () => {
- if (this.props.mark.type !== DocumentType.LABEL) return undefined;
- const start = Math.max(NumCast(this.props.mark[this.props.startTag]), this.props.trimStart) - this.props.trimStart;
- const end = Math.min(NumCast(this.props.mark[this.props.endTag]), this.props.trimEnd) - this.props.trimStart;
+ if (this._props.mark.type !== DocumentType.LABEL) return undefined;
+ const start = Math.max(NumCast(this._props.mark[this._props.startTag]), this._props.trimStart) - this._props.trimStart;
+ const end = Math.min(NumCast(this._props.mark[this._props.endTag]), this._props.trimEnd) - this._props.trimStart;
return `#${formatTime(start)}-${formatTime(end)}`;
};
componentDidMount() {
this._disposer = reaction(
- () => this.props.currentTimecode(),
+ () => this._props.currentTimecode(),
time => {
- const dictationDoc = Cast(this.props.layoutDoc.data_dictation, Doc, null);
- const isDictation = dictationDoc && LinkManager.Links(this.props.mark).some(link => Cast(link.link_anchor_1, Doc, null)?.annotationOn === dictationDoc);
+ const dictationDoc = Cast(this._props.layoutDoc.data_dictation, Doc, null);
+ const isDictation = dictationDoc && LinkManager.Links(this._props.mark).some(link => Cast(link.link_anchor_1, 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 &&
- 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
+ /*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this._props.layoutDoc))*/
+ !this._props.layoutDoc.dontAutoFollowLinks &&
+ 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
) {
- LinkFollower.FollowLink(undefined, this.props.mark, false);
+ LinkFollower.FollowLink(undefined, this._props.mark, false);
}
this._lastTimecode = time;
}
@@ -736,16 +753,16 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
// starting the drag event for anchor resizing
@action
onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => {
- //this.props._timeline?.setPointerCapture(e.pointerId);
+ //this._props._timeline?.setPointerCapture(e.pointerId);
const newTime = (e: PointerEvent) => {
const rect = (e.target as any).getBoundingClientRect();
- return this.props.toTimeline(e.clientX - rect.x, rect.width);
+ return this._props.toTimeline(e.clientX - rect.x, rect.width);
};
const changeAnchor = (anchor: Doc, left: boolean, time: number | undefined) => {
- const timelineOnly = Cast(anchor[this.props.startTag], 'number', null) !== undefined;
+ const timelineOnly = Cast(anchor[this._props.startTag], 'number', null) !== undefined;
if (timelineOnly) {
- if (!left && time !== undefined && time <= NumCast(anchor[this.props.startTag])) time = undefined;
- Doc.SetInPlace(anchor, left ? this.props.startTag : this.props.endTag, time, true);
+ if (!left && time !== undefined && time <= NumCast(anchor[this._props.startTag])) time = undefined;
+ Doc.SetInPlace(anchor, left ? this._props.startTag : this._props.endTag, time, true);
if (!left) Doc.SetInPlace(anchor, 'layout_borderRounding', time !== undefined ? undefined : '100%', true);
} else {
anchor[left ? '_timecodeToShow' : '_timecodeToHide'] = time;
@@ -759,11 +776,11 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
e,
e => {
if (!undo) undo = UndoManager.StartBatch('drag anchor');
- this.props.setTime(newTime(e));
+ this._props.setTime(newTime(e));
return changeAnchor(anchor, left, newTime(e));
},
action(e => {
- this.props.setTime(newTime(e));
+ this._props.setTime(newTime(e));
undo?.end();
this.noEvents = false;
}),
@@ -774,7 +791,9 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
// context menu
contextMenuItems = () => {
const resetTitle = {
- script: ScriptField.MakeFunction(`this.title = this["${this.props.endTag}"] ? "#" + formatToTime(this["${this.props.startTag}"]) + "-" + formatToTime(this["${this.props.endTag}"]) : "#" + formatToTime(this["${this.props.startTag}"])`)!,
+ script: ScriptField.MakeFunction(
+ `this.title = this["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"])`
+ )!,
icon: 'folder-plus',
label: 'Reset Title',
};
@@ -785,7 +804,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) {
const anchor = observable({ view: undefined as Opt<DocumentView> | null });
const focusFunc = (doc: Doc, options: DocFocusOptions): number | undefined => {
- this.props.playLink(mark, options);
+ this._props.playLink(mark, options);
return undefined;
};
return {
@@ -793,7 +812,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
view: (
<DocumentView
key="view"
- {...this.props}
+ {...this._props}
NativeWidth={returnZero}
NativeHeight={returnZero}
ref={action((r: DocumentView | null) => (anchor.view = r))}
@@ -801,11 +820,11 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
TemplateDataDocument={undefined}
docViewPath={returnEmptyDoclist}
pointerEvents={this.noEvents ? returnNone : undefined}
- styleProvider={this.props.styleProvider}
- renderDepth={this.props.renderDepth + 1}
+ styleProvider={this._props.styleProvider}
+ renderDepth={this._props.renderDepth + 1}
LayoutTemplate={undefined}
LayoutTemplateString={LabelBox.LayoutStringWithTitle('data', this.computeTitle())}
- isDocumentActive={this.props.isDocumentActive}
+ isDocumentActive={this._props.isDocumentActive}
PanelWidth={width}
PanelHeight={height}
layout_fitWidth={returnTrue}
@@ -817,7 +836,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
onClick={script}
- onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
+ onDoubleClick={this._props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
ignoreAutoHeight={false}
hideResizeHandles={true}
bringToFront={emptyFunction}
@@ -827,19 +846,19 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
};
});
- anchorScreenToLocalXf = () => this.props.ScreenToLocalTransform().translate(-this.props.left, -this.props.top);
- width = () => this.props.width;
- height = () => this.props.height;
+ anchorScreenToLocalXf = () => this._props.ScreenToLocalTransform().translate(-this._props.left, -this._props.top);
+ width = () => this._props.width;
+ height = () => this._props.height;
render() {
- const inner = this.renderInner(this.props.mark, this.props.rangeClickScript, this.props.rangePlayScript, this.anchorScreenToLocalXf, this.width, this.height);
+ const inner = this.renderInner(this._props.mark, this._props.rangeClickScript, this._props.rangePlayScript, this.anchorScreenToLocalXf, this.width, this.height);
return (
<div style={{ pointerEvents: this.noEvents ? 'none' : undefined }}>
{inner.view}
{!inner.anchor.view || !inner.anchor.view.SELECTED ? null : (
<>
- <div key="left" className="collectionStackedTimeline-left-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this.props.mark, true)} />
- <div key="right" className="collectionStackedTimeline-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this.props.mark, false)} />
+ <div key="left" className="collectionStackedTimeline-left-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this._props.mark, true)} />
+ <div key="right" className="collectionStackedTimeline-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this._props.mark, false)} />
</>
)}
</div>
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index bc428c22f..984d6eedd 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import * as CSS from 'csstype';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction, untracked } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
@@ -11,7 +11,7 @@ import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { copyProps, emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { CollectionViewType } from '../../documents/DocumentTypes';
import { DragManager, dropActionType } from '../../util/DragManager';
@@ -31,7 +31,7 @@ import { StyleProp } from '../StyleProvider';
import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow';
import './CollectionStackingView.scss';
import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn';
-import { CollectionSubView } from './CollectionSubView';
+import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
const _global = (window /* browser */ || global) /* node */ as any;
export type collectionStackingViewProps = {
@@ -64,7 +64,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
@observable _scroll = 0; // used to force the document decoration to update when scrolling
// does this mean whether the browser is hidden? Or is chrome something else entirely?
@computed get chromeHidden() {
- return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden);
+ return this._props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden);
}
// it looks like this gets the column headers that Mehek was showing just now
@computed get colHeaderData() {
@@ -77,18 +77,18 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// filteredChildren is what you want to work with. It's the list of things that you're currently displaying
@computed get filteredChildren() {
const children = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout);
- if (this.props.sortFunc) children.sort(this.props.sortFunc);
+ if (this._props.sortFunc) children.sort(this._props.sortFunc);
return children;
}
// how much margin we give the header
@computed get headerMargin() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin);
}
@computed get xMargin() {
- return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this.props.PanelWidth()));
+ return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this._props.PanelWidth()));
}
@computed get yMargin() {
- return this.props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this.props.PanelWidth()));
+ return this._props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth()));
}
@computed get gridGap() {
@@ -96,7 +96,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
// are we stacking or masonry?
@computed get isStackingView() {
- return (this.props.type_collection ?? this.layoutDoc._type_collection) === CollectionViewType.Stacking;
+ return (this._props.type_collection ?? this.layoutDoc._type_collection) === CollectionViewType.Stacking;
}
// this is the number of StackingViewFieldColumns that we have
@computed get numGroupColumns() {
@@ -108,15 +108,19 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
// columnWidth handles the margin on the left and right side of the documents
@computed get columnWidth() {
- return Math.min(this.props.PanelWidth() - 2 * this.xMargin, this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250));
+ return Math.min(this._props.PanelWidth() - 2 * this.xMargin, this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this._props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250));
}
@computed get NodeWidth() {
- return this.props.PanelWidth() - this.gridGap;
+ return this._props.PanelWidth() - this.gridGap;
}
+ _prevProps: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionStackingViewProps>>;
+ _props: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionStackingViewProps>>;
constructor(props: any) {
super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
if (this.colHeaderData === undefined) {
// TODO: what is a layout doc? Is it literally how this document is supposed to be layed out?
@@ -124,6 +128,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>();
}
}
+ componentDidUpdate() {
+ copyProps(this);
+ }
// TODO: plj - these are the children
children = (docs: Doc[]) => {
@@ -203,7 +210,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
componentDidMount() {
super.componentDidMount?.();
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
// reset section headers when a new filter is inputted
this._pivotFieldDisposer = reaction(
@@ -214,7 +221,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
() => this.layoutDoc._layout_autoHeight,
layout_autoHeight =>
layout_autoHeight &&
- this.props.setHeight?.(
+ this._props.setHeight?.(
Math.min(
NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
this.headerMargin + (this.isStackingView ? Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))) : this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0))
@@ -229,20 +236,19 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
this._layout_autoHeightDisposer?.();
}
- isAnyChildContentActive = () => this.props.isAnyChildContentActive();
+ isAnyChildContentActive = () => this._props.isAnyChildContentActive();
- @action
moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
- return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
+ return this._props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
};
createRef = (ele: HTMLDivElement | null) => {
this._masonryGridRef = ele;
this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
};
- onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
+ onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
@computed get onChildDoubleClickHandler() {
- return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
+ return () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
}
scrollToBottom = () => {
@@ -256,7 +262,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
if (found) {
const top = found.getBoundingClientRect().top;
- const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
+ const localTop = this._props.ScreenToLocalTransform().transformPoint(0, top);
if (Math.floor(localTop[1]) !== 0) {
let focusSpeed = options.zoomTime ?? 500;
smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
@@ -268,17 +274,16 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
if (property === StyleProp.Opacity && doc) {
- if (this.props.childOpacity) {
- return this.props.childOpacity();
+ if (this._props.childOpacity) {
+ return this._props.childOpacity();
}
if (this.Document._currentFrame !== undefined) {
return CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document._currentFrame))?.opacity;
}
}
- return this.props.styleProvider?.(doc, props, property);
+ return this._props.styleProvider?.(doc, props, property);
};
@undoBatch
- @action
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
const docView = fieldProps.DocumentView?.();
if (docView && ['Enter'].includes(e.key) && e.ctrlKey) {
@@ -296,26 +301,26 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
return this.addDocument?.(newDoc);
}
};
- isContentActive = () => (this.props.isContentActive() ? true : this.props.isSelected() === false || this.props.isContentActive() === false ? false : undefined);
+ isContentActive = () => (this._props.isContentActive() ? true : this._props.isSelected() === false || this._props.isContentActive() === false ? false : undefined);
@observable _renderCount = 5;
isChildContentActive = () =>
- this.props.isContentActive?.() === false
+ this._props.isContentActive?.() === false
? false
- : this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive))
- ? true
- : this.props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false
- ? false
- : undefined;
- isChildButtonContentActive = () => (this.props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false ? false : undefined);
+ : this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive))
+ ? true
+ : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false
+ ? false
+ : undefined;
+ isChildButtonContentActive = () => (this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false ? false : undefined);
@observable docRefs = new ObservableMap<Doc, DocumentView>();
- childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this.props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null));
+ childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null));
// this is what renders the document that you see on the screen
// called in Children: this actually adds a document to our children list
getDisplayDoc(doc: Doc, width: () => number, count: number) {
- const dataDoc = doc.isTemplateDoc || doc.isTemplateForField ? this.props.TemplateDataDocument : undefined;
+ const dataDoc = doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined;
const height = () => this.getDocHeight(doc);
- const panelHeight = () => (this.isStackingView ? height() : Math.min(height(), this.props.PanelHeight()));
+ const panelHeight = () => (this.isStackingView ? height() : Math.min(height(), this._props.PanelHeight()));
const panelWidth = () => (this.isStackingView ? width() : this.columnWidth);
const stackedDocTransform = () => this.getDocTransform(doc);
this._docXfs.push({ stackedDocTransform, width, height });
@@ -324,45 +329,45 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(doc, r))}
Document={doc}
TemplateDataDocument={dataDoc ?? (Doc.AreProtosEqual(doc[DocData], doc) ? undefined : doc[DocData])}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
PanelWidth={panelWidth}
PanelHeight={panelHeight}
- pointerEvents={this.props.DocumentView?.().props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
+ pointerEvents={this._props.DocumentView?.()._props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
styleProvider={this.styleProvider}
- docViewPath={this.props.docViewPath}
+ docViewPath={this._props.docViewPath}
layout_fitWidth={this.childFitWidth}
isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive}
onKey={this.onKeyDown}
- onBrowseClick={this.props.onBrowseClick}
+ onBrowseClick={this._props.onBrowseClick}
isDocumentActive={this.isContentActive}
- LayoutTemplate={this.props.childLayoutTemplate}
- LayoutTemplateString={this.props.childLayoutString}
- NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
- NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeHeight(doc)) ? height : undefined}
- dontCenter={this.props.childIgnoreNativeSize ? 'xy' : (StrCast(this.layoutDoc.layout_dontCenter) as any)}
- dontRegisterView={BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)} // used to be true if DataDoc existed, but template textboxes won't layout_autoHeight resize if dontRegisterView is set, but they need to.
+ LayoutTemplate={this._props.childLayoutTemplate}
+ LayoutTemplateString={this._props.childLayoutString}
+ NativeWidth={this._props.childIgnoreNativeSize ? returnZero : this._props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
+ NativeHeight={this._props.childIgnoreNativeSize ? returnZero : this._props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeHeight(doc)) ? height : undefined}
+ dontCenter={this._props.childIgnoreNativeSize ? 'xy' : (StrCast(this.layoutDoc.layout_dontCenter) as any)}
+ dontRegisterView={BoolCast(this.layoutDoc.childDontRegisterViews, this._props.dontRegisterView)} // used to be true if DataDoc existed, but template textboxes won't layout_autoHeight resize if dontRegisterView is set, but they need to.
rootSelected={this.rootSelected}
- layout_showTitle={this.props.childlayout_showTitle}
- dragAction={(this.layoutDoc.childDragAction ?? this.props.childDragAction) as dropActionType}
+ layout_showTitle={this._props.childlayout_showTitle}
+ dragAction={(this.layoutDoc.childDragAction ?? this._props.childDragAction) as dropActionType}
onClick={this.onChildClickHandler}
onDoubleClick={this.onChildDoubleClickHandler}
ScreenToLocalTransform={stackedDocTransform}
focus={this.focusDocument}
childFilters={this.childDocFilters}
- hideDecorationTitle={this.props.childHideDecorationTitle}
- hideResizeHandles={this.props.childHideResizeHandles}
+ hideDecorationTitle={this._props.childHideDecorationTitle}
+ hideResizeHandles={this._props.childHideResizeHandles}
childFiltersByRanges={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
- xPadding={NumCast(this.layoutDoc._childXPadding, this.props.childXPadding)}
- yPadding={NumCast(this.layoutDoc._childYPadding, this.props.childYPadding)}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- removeDocument={this.props.removeDocument}
+ xPadding={NumCast(this.layoutDoc._childXPadding, this._props.childXPadding)}
+ yPadding={NumCast(this.layoutDoc._childYPadding, this._props.childYPadding)}
+ addDocument={this._props.addDocument}
+ moveDocument={this._props.moveDocument}
+ removeDocument={this._props.removeDocument}
contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents) as any}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- addDocTab={this.props.addDocTab}
+ whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+ addDocTab={this._props.addDocTab}
bringToFront={returnFalse}
- pinToPres={this.props.pinToPres}
+ pinToPres={this._props.pinToPres}
/>
);
}
@@ -372,11 +377,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
this._scroll; // must be referenced for document decorations to update when the text box container is scrolled
const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv);
// the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off
- return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale);
+ return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this._props.ScreenToLocalTransform().Scale);
}
getDocWidth(d?: Doc) {
if (!d) return 0;
- const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
+ const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.());
const maxWidth = this.columnWidth / this.numGroupColumns;
if (!this.layoutDoc._columnsFill && !this.childFitWidth(childLayoutDoc)) {
return Math.min(NumCast(d._width), maxWidth);
@@ -385,9 +390,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
getDocHeight(d?: Doc) {
if (!d || d.hidden) return 0;
- const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
- const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.TemplateDataDocument;
- const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
+ const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.());
+ const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this._props.TemplateDataDocument;
+ const maxHeight = (lim => (lim === 0 ? this._props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!this.childFitWidth(childLayoutDoc) ? NumCast(d._width) : 0);
const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!this.childFitWidth(childLayoutDoc) ? NumCast(d._height) : 0);
if (nw && nh) {
@@ -396,7 +401,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
return Math.min(maxHeight, (docWid * nh) / nw);
}
const childHeight = NumCast(childLayoutDoc._height);
- const panelHeight = this.childFitWidth(childLayoutDoc) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin;
+ const panelHeight = this.childFitWidth(childLayoutDoc) ? Number.MAX_SAFE_INTEGER : this._props.PanelHeight() - 2 * this.yMargin;
return Math.min(childHeight, maxHeight, panelHeight);
}
@@ -434,7 +439,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
@undoBatch
- @action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
// Fairly confident that this is where the swapping of nodes in the various arrays happens
const where = [de.x, de.y];
@@ -472,9 +476,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
return true;
}
- } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
+ } else if (de.complete.linkDragData?.dragDocument.embedContainer === this._props.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' });
- if (!this.props.addDocument?.(source)) e.preventDefault();
+ if (!this._props.addDocument?.(source)) e.preventDefault();
de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: 'doc annotation' }); // TODODO this is where in text links get passed
e.stopPropagation();
return true;
@@ -497,7 +501,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
/// an item from outside of Dash is being dropped onto this stacking view (e.g, a document from the file system)
@undoBatch
- @action
onExternalDrop = async (e: React.DragEvent): Promise<void> => {
const where = [e.clientX, e.clientY];
let targInd = -1;
@@ -544,8 +547,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
action((entries: any) => {
if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))));
- if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) {
- this.props.setHeight?.(height);
+ if (!LightboxView.IsLightboxDocView(this._props.docViewPath())) {
+ this._props.setHeight?.(height);
}
}
})
@@ -556,8 +559,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
addDocument={this.addDocument}
chromeHidden={this.chromeHidden}
colHeaderData={this.colHeaderData}
- Document={this.props.Document}
- TemplateDataDocument={this.props.TemplateDataDocument}
+ Document={this._props.Document}
+ TemplateDataDocument={this._props.TemplateDataDocument}
renderChildren={this.children}
columnWidth={this.columnWidth}
numGroupColumns={this.numGroupColumns}
@@ -571,7 +574,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
yMargin={this.yMargin}
type={type}
createDropTarget={this.createDashEventsTarget}
- screenToLocalTransform={this.props.ScreenToLocalTransform}
+ screenToLocalTransform={this._props.ScreenToLocalTransform}
/>
);
};
@@ -584,11 +587,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
type = types[0];
}
- const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))));
+ const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this._props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))));
return (
<CollectionMasonryViewFieldRow
showHandle={first}
- Document={this.props.Document}
+ Document={this._props.Document}
chromeHidden={this.chromeHidden}
pivotField={this.pivotField}
unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)}
@@ -599,7 +602,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
action((entries: any) => {
if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0);
- this.props.setHeight?.(2 * this.headerMargin + height); // bcz: added 2x for header to fix problem with scrollbars appearing in Tools panel
+ this._props.setHeight?.(2 * this.headerMargin + height); // bcz: added 2x for header to fix problem with scrollbars appearing in Tools panel
}
})
);
@@ -615,7 +618,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
parent={this}
type={type}
createDropTarget={this.createDashEventsTarget}
- screenToLocalTransform={this.props.ScreenToLocalTransform}
+ screenToLocalTransform={this._props.ScreenToLocalTransform}
setDocHeight={this.setDocHeight}
/>
);
@@ -672,43 +675,43 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
Document={menuDoc}
isContentActive={this.isContentActive}
isDocumentActive={this.isContentActive}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- addDocTab={this.props.addDocTab}
- onBrowseClick={this.props.onBrowseClick}
+ addDocument={this._props.addDocument}
+ moveDocument={this._props.moveDocument}
+ addDocTab={this._props.addDocTab}
+ onBrowseClick={this._props.onBrowseClick}
pinToPres={emptyFunction}
rootSelected={this.rootSelected}
- removeDocument={this.props.removeDocument}
+ removeDocument={this._props.removeDocument}
ScreenToLocalTransform={Transform.Identity}
PanelWidth={this.return35}
PanelHeight={this.return35}
- renderDepth={this.props.renderDepth}
+ renderDepth={this._props.renderDepth}
focus={emptyFunction}
- styleProvider={this.props.styleProvider}
+ styleProvider={this._props.styleProvider}
docViewPath={returnEmptyDoclist}
whenChildContentsActiveChanged={emptyFunction}
bringToFront={emptyFunction}
- childFilters={this.props.childFilters}
- childFiltersByRanges={this.props.childFiltersByRanges}
- searchFilterDocs={this.props.searchFilterDocs}
+ childFilters={this._props.childFilters}
+ childFiltersByRanges={this._props.childFiltersByRanges}
+ searchFilterDocs={this._props.searchFilterDocs}
/>
</div>
);
}
@computed get nativeWidth() {
- return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc);
+ return this._props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc);
}
@computed get nativeHeight() {
- return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc);
+ return this._props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc);
}
@computed get scaling() {
- return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight;
+ return !this.nativeWidth ? 1 : this._props.PanelHeight() / this.nativeHeight;
}
@computed get backgroundEvents() {
- return this.props.isContentActive() === false ? 'none' : undefined;
+ return this._props.isContentActive() === false ? 'none' : undefined;
}
observer: any;
render() {
@@ -734,8 +737,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
ref={this.createRef}
style={{
overflowY: this.isContentActive() ? 'auto' : 'hidden',
- background: this.props.styleProvider?.(this.Document, this.props, StyleProp.BackgroundColor),
- pointerEvents: (this.props.pointerEvents?.() as any) ?? this.backgroundEvents,
+ background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor),
+ pointerEvents: (this._props.pointerEvents?.() as any) ?? this.backgroundEvents,
}}
onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
onDrop={this.onExternalDrop.bind(this)}
@@ -743,7 +746,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
onWheel={e => this.isContentActive() && e.stopPropagation()}>
{this.renderedSections}
{!this.showAddAGroup ? null : (
- <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" style={{ width: !this.isStackingView ? '100%' : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
+ <div key={`${this._props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" style={{ width: !this.isStackingView ? '100%' : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
<EditableView {...editableViewProps} />
</div>
)}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 3282eb4b4..b8dcd6248 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction, untracked } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { RichTextField } from '../../../fields/RichTextField';
@@ -50,15 +50,22 @@ interface CSVFieldColumnProps {
@observer
export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldColumnProps> {
- @observable private _background = 'inherit';
-
private dropDisposer?: DragManager.DragDropDisposer;
private _disposers: { [name: string]: IReactionDisposer } = {};
private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();
-
+ @observable private _background = 'inherit';
@observable _paletteOn = false;
- @observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading;
- @observable _color = this.props.headingObject ? this.props.headingObject.color : '#f1efeb';
+ @observable _heading = '';
+ @observable _color = '';
+
+ @observable _props!: CSVFieldColumnProps;
+ constructor(props: CSVFieldColumnProps) {
+ super(props);
+ this._props = props;
+ makeObservable(this);
+ this._heading = this._props.headingObject ? this._props.headingObject.heading : this._props.heading;
+ this._color = this._props.headingObject ? this._props.headingObject.color : '#f1efeb';
+ }
_ele: HTMLElement | null = null;
@@ -68,29 +75,33 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
this.dropDisposer?.();
if (ele) {
this._ele = ele;
- this.props.observeHeight(ele);
+ this._props.observeHeight(ele);
this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this));
}
};
+ componentDidUpdate() {
+ // untracked(() => (this._props = this.props));
+ }
+
@action
componentDidMount() {
this._disposers.collapser = reaction(
- () => this.props.headingObject?.collapsed,
+ () => this._props.headingObject?.collapsed,
collapsed => (this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false),
{ fireImmediately: true }
);
}
componentWillUnmount() {
this._disposers.collapser?.();
- this.props.unobserveHeight(this._ele);
+ this._props.unobserveHeight(this._ele);
}
//TODO: what is scripting? I found it in SetInPlace def but don't know what that is
@undoBatch
columnDrop = action((e: Event, de: DragManager.DropEvent) => {
const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) };
- this.props.pivotField && drop.docs?.forEach(d => Doc.SetInPlace(d, this.props.pivotField, drop.val, false));
+ this._props.pivotField && drop.docs?.forEach(d => Doc.SetInPlace(d, this._props.pivotField, drop.val, false));
return true;
});
getValue = (value: string): any => {
@@ -105,13 +116,13 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
headingChanged = (value: string, shiftDown?: boolean) => {
const castedValue = this.getValue(value);
if (castedValue) {
- if (this.props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
+ if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
return false;
}
- this.props.docList.forEach(d => (d[this.props.pivotField] = castedValue));
- if (this.props.headingObject) {
- this.props.headingObject.setHeading(castedValue.toString());
- this._heading = this.props.headingObject.heading;
+ this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue));
+ if (this._props.headingObject) {
+ this._props.headingObject.setHeading(castedValue.toString());
+ this._heading = this._props.headingObject.heading;
}
return true;
}
@@ -120,7 +131,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@action
changeColumnColor = (color: string) => {
- this.props.headingObject?.setColor(color);
+ this._props.headingObject?.setColor(color);
this._color = color;
};
@@ -131,30 +142,30 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@action
addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
if (!value && !forceEmptyNote) return false;
- const key = this.props.pivotField;
+ const key = this._props.pivotField;
const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true });
- newDoc[key] = this.getValue(this.props.heading);
- const maxHeading = this.props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0);
- const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
+ newDoc[key] = this.getValue(this._props.heading);
+ const maxHeading = this._props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0);
+ const heading = maxHeading === 0 || this._props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
newDoc.heading = heading;
FormattedTextBox.SetSelectOnLoad(newDoc);
FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
- return this.props.addDocument?.(newDoc) || false;
+ return this._props.addDocument?.(newDoc) || false;
};
@action
deleteColumn = () => {
- this.props.docList.forEach(d => (d[this.props.pivotField] = undefined));
- if (this.props.colHeaderData && this.props.headingObject) {
- const index = this.props.colHeaderData.indexOf(this.props.headingObject);
- this.props.colHeaderData.splice(index, 1);
+ this._props.docList.forEach(d => (d[this._props.pivotField] = undefined));
+ if (this._props.colHeaderData && this._props.headingObject) {
+ const index = this._props.colHeaderData.indexOf(this._props.headingObject);
+ this._props.colHeaderData.splice(index, 1);
}
};
@action
collapseSection = () => {
- this.props.headingObject?.setCollapsed(!this.props.headingObject.collapsed);
- this.collapsed = BoolCast(this.props.headingObject?.collapsed);
+ this._props.headingObject?.setCollapsed(!this._props.headingObject.collapsed);
+ this.collapsed = BoolCast(this._props.headingObject?.collapsed);
};
headerDown = (e: React.PointerEvent<HTMLDivElement>) => setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);
@@ -162,12 +173,12 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
//TODO: I think this is where I'm supposed to edit stuff
startDrag = (e: PointerEvent, down: number[], delta: number[]) => {
// is MakeEmbedding a way to make a copy of a doc without rendering it?
- const embedding = Doc.MakeEmbedding(this.props.Document);
- embedding._width = this.props.columnWidth / (this.props.colHeaderData?.length || 1);
+ const embedding = Doc.MakeEmbedding(this._props.Document);
+ embedding._width = this._props.columnWidth / (this._props.colHeaderData?.length || 1);
embedding._pivotField = undefined;
let value = this.getValue(this._heading);
value = typeof value === 'string' ? `"${value}"` : value;
- embedding.viewSpecScript = ScriptField.MakeFunction(`doc.${this.props.pivotField} === ${value}`, { doc: Doc.name });
+ embedding.viewSpecScript = ScriptField.MakeFunction(`doc.${this._props.pivotField} === ${value}`, { doc: Doc.name });
if (embedding.viewSpecScript) {
DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([embedding]), e.clientX, e.clientY);
return true;
@@ -177,7 +188,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
renderColorPicker = () => {
const gray = '#f1efeb';
- const selected = this.props.headingObject ? this.props.headingObject.color : gray;
+ const selected = this._props.headingObject ? this._props.headingObject.color : gray;
const colors = ['pink2', 'purple4', 'bluegreen1', 'yellow4', 'gray', 'red2', 'bluegreen7', 'bluegreen5', 'orange1'];
return (
<div className="collectionStackingView-colorPicker">
@@ -211,15 +222,15 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
ContextMenu.Instance.clearItems();
const layoutItems: ContextMenuProps[] = [];
const docItems: ContextMenuProps[] = [];
- const dataDoc = this.props.TemplateDataDocument || this.props.Document;
+ const dataDoc = this._props.TemplateDataDocument || this._props.Document;
const width = this._ele ? Number(getComputedStyle(this._ele).width.replace('px', '')) : 0;
const height = this._ele ? Number(getComputedStyle(this._ele).height.replace('px', '')) : 0;
DocUtils.addDocumentCreatorMenuItems(
doc => {
FormattedTextBox.SetSelectOnLoad(doc);
- return this.props.addDocument?.(doc);
+ return this._props.addDocument?.(doc);
},
- this.props.addDocument,
+ this._props.addDocument,
0,
0,
true
@@ -231,12 +242,12 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
docItems.push({
description: ':' + fieldKey,
event: () => {
- const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document));
+ const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document));
if (created) {
- if (this.props.Document.isTemplateDoc) {
- Doc.MakeMetadataFieldTemplate(created, this.props.Document);
+ if (this._props.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this._props.Document);
}
- return this.props.addDocument?.(created);
+ return this._props.addDocument?.(created);
}
},
icon: 'compress-arrows-alt',
@@ -250,12 +261,12 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
event: () => {
const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
if (created) {
- const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document;
+ const container = this._props.Document.resolvedDataDoc ? Doc.GetProto(this._props.Document) : this._props.Document;
if (container.isTemplateDoc) {
Doc.MakeMetadataFieldTemplate(created, container);
return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created);
}
- return this.props.addDocument?.(created) || false;
+ return this._props.addDocument?.(created) || false;
}
},
icon: 'compress-arrows-alt',
@@ -264,16 +275,16 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
!Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' });
!Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' });
ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
- Doc.GetProto(this.props.Document)[name] = '';
+ Doc.GetProto(this._props.Document)[name] = '';
const created = Docs.Create.TextDocument('', { title: name, _width: 250, _layout_autoHeight: true });
if (created) {
- if (this.props.Document.isTemplateDoc) {
- Doc.MakeMetadataFieldTemplate(created, this.props.Document);
+ if (this._props.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this._props.Document);
}
- this.props.addDocument?.(created);
+ this._props.addDocument?.(created);
}
});
- const pt = this.props
+ const pt = this._props
.screenToLocalTransform()
.inverse()
.transformPoint(width - 30, height);
@@ -282,21 +293,21 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@computed get innards() {
TraceMobx();
- const key = this.props.pivotField;
- const headings = this.props.headings();
+ const key = this._props.pivotField;
+ const headings = this._props.headings();
const heading = this._heading;
- const columnYMargin = this.props.headingObject ? 0 : this.props.yMargin;
+ const columnYMargin = this._props.headingObject ? 0 : this._props.yMargin;
const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
const noValueHeader = `NO ${key.toUpperCase()} VALUE`;
- const evContents = heading ? heading : this.props?.type === 'number' ? '0' : noValueHeader;
- const headingView = this.props.headingObject ? (
+ const evContents = heading ? heading : this._props?.type === 'number' ? '0' : noValueHeader;
+ const headingView = this._props.headingObject ? (
<div
key={heading}
className="collectionStackingView-sectionHeader"
ref={this._headerRef}
style={{
- marginTop: this.props.yMargin,
- width: this.props.columnWidth / (uniqueHeadings.length + (this.props.chromeHidden ? 0 : 1) || 1),
+ marginTop: this._props.yMargin,
+ width: this._props.columnWidth / (uniqueHeadings.length + (this._props.chromeHidden ? 0 : 1) || 1),
}}>
{/* the default bucket (no key value) has a tooltip that describes what it is.
Further, it does not have a color and cannot be deleted. */}
@@ -326,35 +337,35 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
)} */}
</div>
<div
- className={'collectionStackingView-collapseBar' + (this.props.headingObject.collapsed === true ? ' active' : '')}
- style={{ display: this.props.headingObject.collapsed === true ? 'block' : undefined }}
+ className={'collectionStackingView-collapseBar' + (this._props.headingObject.collapsed === true ? ' active' : '')}
+ style={{ display: this._props.headingObject.collapsed === true ? 'block' : undefined }}
onClick={this.collapseSection}
/>
</div>
) : null;
- const templatecols = `${this.props.columnWidth / this.props.numGroupColumns}px `;
- const type = this.props.Document.type;
+ const templatecols = `${this._props.columnWidth / this._props.numGroupColumns}px `;
+ const type = this._props.Document.type;
return (
<>
- {this.props.Document._columnsHideIfEmpty ? null : headingView}
+ {this._props.Document._columnsHideIfEmpty ? null : headingView}
{this.collapsed ? null : (
<div>
<div
key={`${heading}-stack`}
className={`collectionStackingView-masonrySingle`}
style={{
- padding: `${columnYMargin}px ${0}px ${this.props.yMargin}px ${0}px`,
+ padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`,
margin: 'auto',
width: 'max-content', //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
height: 'max-content',
position: 'relative',
- gridGap: this.props.gridGap,
+ gridGap: this._props.gridGap,
gridTemplateColumns: templatecols,
gridAutoRows: '0px',
}}>
- {this.props.renderChildren(this.props.docList)}
+ {this._props.renderChildren(this._props.docList)}
</div>
- {!this.props.chromeHidden && type !== DocumentType.PRES ? (
+ {!this._props.chromeHidden && type !== DocumentType.PRES ? (
// TODO: this is the "new" button: see what you can work with here
// change cursor to pointer for this, and update dragging cursor
//TODO: there is a bug that occurs when adding a freeform document and trying to move it around
@@ -365,7 +376,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
key={`${heading}-add-document`}
onKeyDown={e => e.stopPropagation()}
className="collectionStackingView-addDocumentButton"
- style={{ width: 'calc(100% - 25px)', maxWidth: this.props.columnWidth / this.props.numGroupColumns - 25, marginBottom: 10 }}>
+ style={{ width: 'calc(100% - 25px)', maxWidth: this._props.columnWidth / this._props.numGroupColumns - 25, marginBottom: 10 }}>
<EditableView
GetValue={returnEmptyString}
SetValue={this.addNewTextDoc}
@@ -384,7 +395,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
render() {
TraceMobx();
- const headings = this.props.headings();
+ const headings = this._props.headings();
const heading = this._heading;
const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
return (
@@ -392,7 +403,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
className={'collectionStackingViewFieldColumn' + (SnappingManager.GetIsDragging() ? 'Dragging' : '')}
key={heading}
style={{
- width: `${100 / (uniqueHeadings.length + (this.props.chromeHidden ? 0 : 1) || 1)}%`,
+ width: `${100 / (uniqueHeadings.length + (this._props.chromeHidden ? 0 : 1) || 1)}%`,
height: undefined, // DraggingManager.GetIsDragging() ? "100%" : undefined,
background: this._background,
}}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 40b2f9644..fd53e12da 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable, override, runInAction, untracked } from 'mobx';
import * as rp from 'request-promise';
import CursorField from '../../../fields/CursorField';
import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
@@ -35,6 +35,17 @@ export function CollectionSubView<X>(moreProps?: X) {
private dropDisposer?: DragManager.DragDropDisposer;
private gestureDisposer?: GestureUtils.GestureEventDisposer;
protected _mainCont?: HTMLDivElement;
+ @override _props: X & SubCollectionViewProps;
+
+ constructor(props: any) {
+ super(props);
+ this._props = props;
+ makeObservable(this);
+ }
+ componentDidUpdate() {
+ // untracked(() => (this._props = this.props));
+ }
+
@observable _focusFilters: Opt<string[]>; // childFilters that are overridden when previewing a link to an anchor which has childFilters set on it
@observable _focusRangeFilters: Opt<string[]>; // childFiltersByRanges that are overridden when previewing a link to an anchor which has childFiltersByRanges set on it
protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
@@ -55,29 +66,29 @@ export function CollectionSubView<X>(moreProps?: X) {
this.gestureDisposer?.();
}
- @computed get dataDoc() {
- return this.props.TemplateDataDocument instanceof Doc && this.props.Document.isTemplateForField
- ? Doc.GetProto(this.props.TemplateDataDocument)
- : this.props.Document.resolvedDataDoc
- ? this.props.Document
- : Doc.GetProto(this.props.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
+ get dataDoc() {
+ return this._props.TemplateDataDocument instanceof Doc && this._props.Document.isTemplateForField
+ ? Doc.GetProto(this._props.TemplateDataDocument)
+ : this._props.Document.resolvedDataDoc
+ ? this._props.Document
+ : Doc.GetProto(this._props.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
}
// this returns whether either the collection is selected, or the template that it is part of is selected
- rootSelected = () => this.props.isSelected() || BoolCast(this.props.TemplateDataDocument && this.props.rootSelected?.());
+ rootSelected = () => this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.());
- // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.TemplateDataDocument.
+ // The data field for rendering this collection will be on the this._props.Document unless we're rendering a template in which case we try to use props.TemplateDataDocument.
// When a document has a TemplateDataDoc but it's not a template, then it contains its own rendering data, but needs to pass the TemplateDataDoc through
// to its children which may be templates.
// If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
@computed get dataField() {
- return this.dataDoc[this.props.fieldKey]; // this used to be 'layoutDoc', but then template fields will get ignored since the template is not a proto of the layout. hopefully nothing depending on the previous code.
+ return this.dataDoc[this._props.fieldKey]; // this used to be 'layoutDoc', but then template fields will get ignored since the template is not a proto of the layout. hopefully nothing depending on the previous code.
}
@computed get childLayoutPairs(): { layout: Doc; data: Doc }[] {
- const { Document, TemplateDataDocument } = this.props;
+ const { Document, TemplateDataDocument } = this._props;
const validPairs = this.childDocs
- .map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc))
+ .map(doc => Doc.GetLayoutDataDocPair(Document, !this._props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc))
.filter(pair => {
// filter out any documents that have a proto that we don't have permissions to
return !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate));
@@ -87,14 +98,14 @@ export function CollectionSubView<X>(moreProps?: X) {
@computed get childDocList() {
return Cast(this.dataField, listSpec(Doc));
}
- collectionFilters = () => this._focusFilters ?? StrListCast(this.props.Document._childFilters);
- collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.props.Document._childFiltersByRanges, listSpec('string'), []);
+ collectionFilters = () => this._focusFilters ?? StrListCast(this._props.Document._childFilters);
+ collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this._props.Document._childFiltersByRanges, listSpec('string'), []);
// child filters apply to the descendants of the documents in this collection
- childDocFilters = () => [...(this.props.childFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()];
+ childDocFilters = () => [...(this._props.childFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()];
// unrecursive filters apply to the documents in the collection, but no their children. See Utils.noRecursionHack
- unrecursiveDocFilters = () => [...(this.props.childFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])];
- childDocRangeFilters = () => [...(this.props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()];
- searchFilterDocs = () => this.props.searchFilterDocs?.() ?? DocListCast(this.props.Document._searchFilterDocs);
+ unrecursiveDocFilters = () => [...(this._props.childFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])];
+ childDocRangeFilters = () => [...(this._props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()];
+ searchFilterDocs = () => this._props.searchFilterDocs?.() ?? DocListCast(this._props.Document._searchFilterDocs);
@computed.struct get childDocs() {
TraceMobx();
let rawdocs: (Doc | Promise<Doc>)[] = [];
@@ -108,27 +119,27 @@ export function CollectionSubView<X>(moreProps?: X) {
// Finally, if it's not a doc or a list and the document is a template, we try to render the root doc.
// For example, if an image doc is rendered with a slide template, the template will try to render the data field as a collection.
// Since the data field is actually an image, we set the list of documents to the singleton of root document's proto which will be an image.
- const templateRoot = this.props.TemplateDataDocument;
- rawdocs = templateRoot && !this.props.isAnnotationOverlay ? [Doc.GetProto(templateRoot)] : [];
+ const templateRoot = this._props.TemplateDataDocument;
+ rawdocs = templateRoot && !this._props.isAnnotationOverlay ? [Doc.GetProto(templateRoot)] : [];
}
- const childDocs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this.props.ignoreUnrendered || !d.layout_unrendered)).map(d => d as Doc);
+ const childDocs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this._props.ignoreUnrendered || !d.layout_unrendered)).map(d => d as Doc);
const childDocFilters = this.childDocFilters();
const childFiltersByRanges = this.childDocRangeFilters();
const searchDocs = this.searchFilterDocs();
- if (this.props.Document.dontRegisterView || (!childDocFilters.length && !this.unrecursiveDocFilters().length && !childFiltersByRanges.length && !searchDocs.length)) {
+ if (this._props.Document.dontRegisterView || (!childDocFilters.length && !this.unrecursiveDocFilters().length && !childFiltersByRanges.length && !searchDocs.length)) {
return childDocs.filter(cd => !cd.cookies); // remove any documents that require a cookie if there are no filters to provide one
}
const docsforFilter: Doc[] = [];
childDocs.forEach(d => {
// dragging facets
- const dragged = this.props.childFilters?.().some(f => f.includes(Utils.noDragDocsFilter));
+ const dragged = this._props.childFilters?.().some(f => f.includes(Utils.noDragDocsFilter));
if (dragged && SnappingManager.GetCanEmbed() && DragManager.docsBeingDragged.includes(d)) return false;
- let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.props.Document).length > 0;
+ let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this._props.Document).length > 0;
if (notFiltered) {
- notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.props.Document).length > 0;
+ notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this._props.Document).length > 0;
const fieldKey = Doc.LayoutFieldKey(d);
const annos = !Field.toString(Doc.LayoutField(d) as Field).includes(CollectionView.name);
const data = d[annos ? fieldKey + '_annotations' : fieldKey];
@@ -161,7 +172,7 @@ export function CollectionSubView<X>(moreProps?: X) {
@action
protected async setCursorPosition(position: [number, number]) {
let ind;
- const doc = this.props.Document;
+ const doc = this._props.Document;
const id = Doc.UserDoc()[Id];
const email = Doc.CurrentUserEmail;
const pos = { x: position[0], y: position[1] };
@@ -197,23 +208,23 @@ export function CollectionSubView<X>(moreProps?: X) {
const dropAction = this.layoutDoc.dropAction as dropActionType;
// if the dropEvent's dragAction is, say 'embed', but we're just dragging within a collection, we may not actually want to make an embedding.
// so we check if our collection has a dropAction set on it and if so, we use that instead.
- if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this.props.Document && this.childDocs.includes(d))) {
+ if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this._props.Document && this.childDocs.includes(d))) {
de.complete.docDragData.dropAction = dropAction;
}
e.stopPropagation();
}
}
- addDocument = (doc: Doc | Doc[], annotationKey?: string) => this.props.addDocument?.(doc, annotationKey) || false;
- removeDocument = (doc: Doc | Doc[], annotationKey?: string) => this.props.removeDocument?.(doc, annotationKey) || false;
- moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this.props.moveDocument?.(doc, targetCollection, addDocument);
- @action
+ addDocument = (doc: Doc | Doc[], annotationKey?: string) => this._props.addDocument?.(doc, annotationKey) || false;
+ removeDocument = (doc: Doc | Doc[], annotationKey?: string) => this._props.removeDocument?.(doc, annotationKey) || false;
+ moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this._props.moveDocument?.(doc, targetCollection, addDocument);
+
protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
const docDragData = de.complete.docDragData;
if (docDragData) {
let added = undefined;
const dropAction = docDragData.dropAction || docDragData.userDropAction;
- const targetDocments = DocListCast(this.dataDoc[this.props.fieldKey]);
+ const targetDocments = DocListCast(this.dataDoc[this._props.fieldKey]);
const someMoved = !dropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag));
if (someMoved) docDragData.droppedDocuments = docDragData.droppedDocuments.map((drop, i) => (targetDocments.includes(docDragData.draggedDocuments[i]) ? docDragData.draggedDocuments[i] : drop));
if ((!dropAction || dropAction === 'inSame' || dropAction === 'same' || dropAction === 'move' || someMoved) && docDragData.moveDocument) {
@@ -236,13 +247,13 @@ export function CollectionSubView<X>(moreProps?: X) {
added = this.addDocument(docDragData.droppedDocuments);
!added && alert('You cannot perform this move');
}
- added === false && !this.props.isAnnotationOverlay && e.preventDefault();
+ added === false && !this._props.isAnnotationOverlay && e.preventDefault();
added === true && e.stopPropagation();
return added ? true : false;
} else if (de.complete.annoDragData) {
const dropCreator = de.complete.annoDragData.dropDocCreator;
de.complete.annoDragData.dropDocCreator = () => {
- const dropped = dropCreator(this.props.isAnnotationOverlay ? this.Document : undefined);
+ const dropped = dropCreator(this._props.isAnnotationOverlay ? this.Document : undefined);
this.addDocument(dropped);
return dropped;
};
@@ -252,7 +263,6 @@ export function CollectionSubView<X>(moreProps?: X) {
}
@undoBatch
- @action
protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions, completed?: (docs: Doc[]) => void) {
if (e.ctrlKey) {
e.stopPropagation(); // bcz: this is a hack to stop propagation when dropping an image on a text document with shift+ctrl
@@ -463,15 +473,15 @@ export function CollectionSubView<X>(moreProps?: X) {
}
if (generatedDocuments.length) {
// Creating a dash document
- const isFreeformView = this.props.Document._type_collection === CollectionViewType.Freeform;
+ const isFreeformView = this._props.Document._type_collection === CollectionViewType.Freeform;
const set = !isFreeformView
? generatedDocuments
: generatedDocuments.length > 1
- ? generatedDocuments.map(d => {
- DocUtils.iconify(d);
- return d;
- })
- : [];
+ ? generatedDocuments.map(d => {
+ DocUtils.iconify(d);
+ return d;
+ })
+ : [];
if (completed) completed(set);
else {
if (isFreeformView && generatedDocuments.length > 1) {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 5c165fe70..938af002f 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
@@ -7,7 +7,7 @@ import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../Utils';
+import { copyProps, emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType } from '../../util/DragManager';
@@ -23,7 +23,7 @@ import { FieldViewProps } from '../nodes/FieldView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { StyleProp } from '../StyleProvider';
import { CollectionFreeFormView } from './collectionFreeForm';
-import { CollectionSubView } from './CollectionSubView';
+import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
import './CollectionTreeView.scss';
import { TreeView } from './TreeView';
import * as React from 'react';
@@ -59,27 +59,35 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
private refList: Set<any> = new Set(); // list of tree view items to monitor for height changes
private observer: any; // observer for monitoring tree view items.
- @computed get doc() {
- return this.props.Document;
+ _prevProps: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionTreeViewProps>>;
+ @override _props: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionTreeViewProps>>;
+ constructor(props: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionTreeViewProps>>) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
}
- @computed get dataDoc() {
- return this.props.TemplateDataDocument || this.doc;
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
+ get dataDoc() {
+ return this._props.TemplateDataDocument || this.Document;
}
@computed get treeViewtruncateTitleWidth() {
- return NumCast(this.doc.treeView_TruncateTitleWidth, this.panelWidth());
+ return NumCast(this.Document.treeView_TruncateTitleWidth, this.panelWidth());
}
@computed get treeChildren() {
TraceMobx();
- return this.props.childDocuments || this.childDocs;
+ return this._props.childDocuments || this.childDocs;
}
@computed get outlineMode() {
- return this.doc.treeView_Type === TreeViewType.outline;
+ return this.Document.treeView_Type === TreeViewType.outline;
}
@computed get fileSysMode() {
- return this.doc.treeView_Type === TreeViewType.fileSystem;
+ return this.Document.treeView_Type === TreeViewType.fileSystem;
}
@computed get dashboardMode() {
- return this.doc === Doc.MyDashboards;
+ return this.Document === Doc.MyDashboards;
}
@observable _titleHeight = 0; // height of the title bar
@@ -88,8 +96,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
// these should stay in synch with counterparts in DocComponent.ts ViewBoxAnnotatableComponent
@observable _isAnyChildContentActive = false;
- whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
- isContentActive = (outsideReaction?: boolean) => (this._isAnyChildContentActive ? true : this.props.isContentActive() ? true : false);
+ whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
+ isContentActive = (outsideReaction?: boolean) => (this._isAnyChildContentActive ? true : this._props.isContentActive() ? true : false);
componentWillUnmount() {
this._isDisposing = true;
@@ -99,7 +107,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
}
componentDidMount() {
- //this.props.setContentView?.(this);
+ //this._props.setContentView?.(this);
this._disposers.autoheight = reaction(
() => this.layoutDoc.layout_autoHeight,
auto => auto && this.computeHeight(),
@@ -112,7 +120,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace('px', ''));
const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), this.marginBot()) + 6;
this.layoutDoc._layout_autoHeightMargins = bodyHeight;
- !this.props.dontRegisterView && this.props.setHeight?.(bodyHeight + titleHeight);
+ !this._props.dontRegisterView && this._props.setHeight?.(bodyHeight + titleHeight);
}
};
unobserveHeight = (ref: any) => {
@@ -135,7 +143,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
};
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this._treedropDisposer?.();
- if ((this._mainEle = ele)) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this));
+ if ((this._mainEle = ele)) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.Document, this.onInternalPreDrop.bind(this));
};
protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent) => {
@@ -143,7 +151,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
const dragData = de.complete.docDragData;
if (dragData) {
const sameTree = Doc.AreProtosEqual(dragData.treeViewDoc, this.Document) ? true : false;
- const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.doc && this.childDocs.includes(d));
+ const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.Document && this.childDocs.includes(d));
if (isAlreadyInTree() !== sameTree) {
console.log('WHAAAT');
}
@@ -152,22 +160,22 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
}
};
- screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this._headerHeight);
+ screenToLocalTransform = () => this._props.ScreenToLocalTransform().translate(0, -this._headerHeight);
@action
remove = (doc: Doc | Doc[]): boolean => {
const docs = doc instanceof Doc ? [doc] : doc;
- const targetDataDoc = this.doc[DocData];
- const value = DocListCast(targetDataDoc[this.props.fieldKey]);
+ const targetDataDoc = this.Document[DocData];
+ const value = DocListCast(targetDataDoc[this._props.fieldKey]);
const result = value.filter(v => !docs.includes(v));
if ((doc instanceof Doc ? [doc] : doc).some(doc => SelectionManager.Views().some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll();
if (result.length !== value.length && doc instanceof Doc) {
- const ind = DocListCast(targetDataDoc[this.props.fieldKey]).indexOf(doc);
- const prev = ind && DocListCast(targetDataDoc[this.props.fieldKey])[ind - 1];
- this.props.removeDocument?.(doc);
+ const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(doc);
+ const prev = ind && DocListCast(targetDataDoc[this._props.fieldKey])[ind - 1];
+ this._props.removeDocument?.(doc);
if (ind > 0 && prev) {
FormattedTextBox.SetSelectOnLoad(prev);
- DocumentManager.Instance.getDocumentView(prev, this.props.DocumentView?.())?.select(false);
+ DocumentManager.Instance.getDocumentView(prev, this._props.DocumentView?.())?.select(false);
}
return true;
}
@@ -178,24 +186,28 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
addDoc = (docs: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => {
const doAddDoc = (doc: Doc | Doc[]) =>
(doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => {
- const res = flg && Doc.AddDocToList(this.doc[DocData], this.props.fieldKey, doc, relativeTo, before);
- res && Doc.SetContainer(doc, this.props.Document);
+ const res = flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before);
+ res && Doc.SetContainer(doc, this.Document);
return res;
}, true);
- if (this.doc.resolvedDataDoc instanceof Promise) return false;
- return relativeTo === undefined ? this.props.addDocument?.(docs) || false : doAddDoc(docs);
+ if (this.Document.resolvedDataDoc instanceof Promise) return false;
+ return relativeTo === undefined ? this._props.addDocument?.(docs) || false : doAddDoc(docs);
};
onContextMenu = (e: React.MouseEvent): void => {
// need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
if (!Doc.noviceMode) {
const layoutItems: ContextMenuProps[] = [];
- layoutItems.push({ description: 'Make tree state ' + (this.doc.treeView_OpenIsTransient ? 'persistent' : 'transient'), event: () => (this.doc.treeView_OpenIsTransient = !this.doc.treeView_OpenIsTransient), icon: 'paint-brush' });
- layoutItems.push({ description: (this.doc.treeView_HideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => (this.doc.treeView_HideHeaderFields = !this.doc.treeView_HideHeaderFields), icon: 'paint-brush' });
- layoutItems.push({ description: (this.doc.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.doc.treeView_HideTitle = !this.doc.treeView_HideTitle), icon: 'paint-brush' });
+ layoutItems.push({
+ description: 'Make tree state ' + (this.Document.treeView_OpenIsTransient ? 'persistent' : 'transient'),
+ event: () => (this.Document.treeView_OpenIsTransient = !this.Document.treeView_OpenIsTransient),
+ icon: 'paint-brush',
+ });
+ layoutItems.push({ description: (this.Document.treeView_HideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => (this.Document.treeView_HideHeaderFields = !this.Document.treeView_HideHeaderFields), icon: 'paint-brush' });
+ layoutItems.push({ description: (this.Document.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.Document.treeView_HideTitle = !this.Document.treeView_HideTitle), icon: 'paint-brush' });
ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' });
const existingOnClick = ContextMenu.Instance.findByDescription('OnClick...');
const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
- onClicks.push({ description: 'Edit onChecked Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.doc, undefined, 'onCheckedClick'), 'edit onCheckedClick'), icon: 'edit' });
+ onClicks.push({ description: 'Edit onChecked Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onCheckedClick'), 'edit onCheckedClick'), icon: 'edit' });
!existingOnClick && ContextMenu.Instance.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
}
};
@@ -213,7 +225,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
height={'auto'}
GetValue={() => StrCast(this.dataDoc.title)}
SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => {
- if (enter && this.props.Document.treeView_Type === TreeViewType.outline) this.makeTextCollection(this.treeChildren);
+ if (enter && this.Document.treeView_Type === TreeViewType.outline) this.makeTextCollection(this.treeChildren);
this.dataDoc.title = value;
return true;
})}
@@ -231,9 +243,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
get documentTitle() {
return (
<FormattedTextBox
- {...this.props}
+ {...this._props}
fieldKey="text"
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
isContentActive={this.isContentActive}
isDocumentActive={this.isContentActive}
forceAutoHeight={true} // needed to make the title resize even if the rest of the tree view is not layout_autoHeight
@@ -253,52 +265,52 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
);
}
childContextMenuItems = () => {
- const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []);
- const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
- const icons = StrListCast(this.doc.childContextMenuIcons);
- return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
+ const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), []);
+ const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), []);
+ const icons = StrListCast(this.Document.childContextMenuIcons);
+ return StrListCast(this.Document.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
};
- headerFields = () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeView_HideHeaderFields);
+ headerFields = () => this._props.treeViewHideHeaderFields || BoolCast(this.Document.treeView_HideHeaderFields);
@observable _renderCount = 1;
@computed get treeViewElements() {
TraceMobx();
- const dragAction = StrCast(this.doc.childDragAction) as dropActionType;
+ const dragAction = StrCast(this.Document.childDragAction) as dropActionType;
const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
- const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument?.(d, target, addDoc) || false;
+ const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this._props.moveDocument?.(d, target, addDoc) || false;
if (this._renderCount < this.treeChildren.length) setTimeout(action(() => (this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20))));
return TreeView.GetChildElements(
this.treeChildren,
this,
this,
- this.doc,
- this.props.TemplateDataDocument,
+ this.Document,
+ this._props.TemplateDataDocument,
undefined,
undefined,
addDoc,
this.remove,
moveDoc,
dragAction,
- this.props.addDocTab,
- this.props.styleProvider,
+ this._props.addDocTab,
+ this._props.styleProvider,
this.screenToLocalTransform,
this.isContentActive,
this.panelWidth,
- this.props.renderDepth,
+ this._props.renderDepth,
this.headerFields,
[],
- this.props.onCheckedClick,
+ this._props.onCheckedClick,
this.onChildClick,
- this.props.treeViewSkipFields,
+ this._props.treeViewSkipFields,
true,
this.whenChildContentsActiveChanged,
- this.props.dontRegisterView || Cast(this.props.Document.childDontRegisterViews, 'boolean', null),
+ this._props.dontRegisterView || Cast(this.Document.childDontRegisterViews, 'boolean', null),
this.observeHeight,
this.unobserveHeight,
this.childContextMenuItems(),
//TODO: [AL] add these
- this.props.AddToMap,
- this.props.RemFromMap,
- this.props.hierarchyIndex,
+ this._props.AddToMap,
+ this._props.RemFromMap,
+ this._props.hierarchyIndex,
this._renderCount
);
}
@@ -306,8 +318,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
return this.dataDoc === null ? null : (
<div
className="collectionTreeView-titleBar"
- ref={action((r: any) => (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this.props.ScreenToLocalTransform().Scale))}
- key={this.doc[Id]}
+ ref={action((r: any) => (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this._props.ScreenToLocalTransform().Scale))}
+ key={this.Document[Id]}
style={!this.outlineMode ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}}>
{this.outlineMode ? this.documentTitle : this.editableTitle}
</div>
@@ -327,26 +339,26 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
<DocumentView
Document={menuDoc}
TemplateDataDocument={menuDoc}
- isContentActive={this.props.isContentActive}
+ isContentActive={this._props.isContentActive}
isDocumentActive={returnTrue}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- removeDocument={this.props.removeDocument}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
+ addDocument={this._props.addDocument}
+ moveDocument={this._props.moveDocument}
+ removeDocument={this._props.removeDocument}
+ addDocTab={this._props.addDocTab}
+ pinToPres={this._props.pinToPres}
rootSelected={this.rootSelected}
ScreenToLocalTransform={Transform.Identity}
PanelWidth={this.return35}
PanelHeight={this.return35}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
focus={emptyFunction}
- styleProvider={this.props.styleProvider}
+ styleProvider={this._props.styleProvider}
docViewPath={returnEmptyDoclist}
whenChildContentsActiveChanged={emptyFunction}
bringToFront={emptyFunction}
- childFilters={this.props.childFilters}
- childFiltersByRanges={this.props.childFiltersByRanges}
- searchFilterDocs={this.props.searchFilterDocs}
+ childFilters={this._props.childFilters}
+ childFiltersByRanges={this._props.childFiltersByRanges}
+ searchFilterDocs={this._props.searchFilterDocs}
/>
</div>
);
@@ -363,30 +375,30 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
@computed get nativeDimScaling() {
const nw = this.nativeWidth;
const nh = this.nativeHeight;
- const hscale = nh ? this.props.PanelHeight() / nh : 1;
- const wscale = nw ? this.props.PanelWidth() / nw : 1;
+ const hscale = nh ? this._props.PanelHeight() / nh : 1;
+ const wscale = nw ? this._props.PanelWidth() / nw : 1;
return wscale < hscale ? wscale : hscale;
}
- marginX = () => NumCast(this.doc._xMargin);
- marginTop = () => NumCast(this.doc._yMargin);
- marginBot = () => NumCast(this.doc._yMargin);
+ marginX = () => NumCast(this.Document._xMargin);
+ marginTop = () => NumCast(this.Document._yMargin);
+ marginBot = () => NumCast(this.Document._yMargin);
documentTitleWidth = () => Math.min(NumCast(this.layoutDoc?._width), this.panelWidth());
documentTitleHeight = () => NumCast(this.layoutDoc?._height) - NumCast(this.layoutDoc.layout_autoHeightMargins);
truncateTitleWidth = () => this.treeViewtruncateTitleWidth;
- onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
- panelWidth = () => Math.max(0, this.props.PanelWidth() - 2 * this.marginX() * (this.props.NativeDimScaling?.() || 1));
+ onChildClick = () => this._props.onChildClick?.() || ScriptCast(this.Document.onChildClick);
+ panelWidth = () => Math.max(0, this._props.PanelWidth() - 2 * this.marginX() * (this._props.NativeDimScaling?.() || 1));
- addAnnotationDocument = (doc: Doc | Doc[]) => this.addDocument(doc, `${this.props.fieldKey}_annotations`) || false;
- remAnnotationDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, `${this.props.fieldKey}_annotations`) || false;
+ addAnnotationDocument = (doc: Doc | Doc[]) => this.addDocument(doc, `${this._props.fieldKey}_annotations`) || false;
+ remAnnotationDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, `${this._props.fieldKey}_annotations`) || false;
moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) =>
- this.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}_annotations`) || false;
+ this.moveDocument(doc, targetCollection, addDocument, `${this._props.fieldKey}_annotations`) || false;
@observable _headerHeight = 0;
@computed get content() {
- const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor);
- const color = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.Color);
- const pointerEvents = () => (this.props.isContentActive() === false ? 'none' : undefined);
- const titleBar = this.props.treeViewHideTitle || this.doc.treeView_HideTitle ? null : this.titleBar;
+ const background = () => this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor);
+ const color = () => this._props.styleProvider?.(this.Document, this._props, StyleProp.Color);
+ const pointerEvents = () => (this._props.isContentActive() === false ? 'none' : undefined);
+ const titleBar = this._props.treeViewHideTitle || this.Document.treeView_HideTitle ? null : this.titleBar;
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', pointerEvents: 'all' }}>
{!this.buttonMenu && !this.noviceExplainer ? null : (
@@ -398,7 +410,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
<div
className="collectionTreeView-contents"
key="tree"
- ref={r => !this.doc.treeView_HasOverlay && r && this.createTreeDropTarget(r)}
+ ref={r => !this.Document.treeView_HasOverlay && r && this.createTreeDropTarget(r)}
style={{
...(!titleBar ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}),
color: color(),
@@ -423,7 +435,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
minHeight: '100%',
}}
onWheel={e => e.stopPropagation()}
- onClick={e => (!this.layoutDoc.forceActive ? this.props.select(false) : SelectionManager.DeselectAll())}
+ onClick={e => (!this.layoutDoc.forceActive ? this._props.select(false) : SelectionManager.DeselectAll())}
onDrop={this.onTreeDrop}>
<ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul>
</div>
@@ -435,27 +447,27 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
render() {
TraceMobx();
- const scale = this.props.NativeDimScaling?.() || 1;
+ const scale = this._props.NativeDimScaling?.() || 1;
return (
<div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
- {!(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeView_HasOverlay ? (
+ {!(this.Document instanceof Doc) || !this.treeChildren ? null : this.Document.treeView_HasOverlay ? (
<CollectionFreeFormView
- {...this.props}
+ {...this._props}
setContentView={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
- pointerEvents={this.props.isContentActive() && SnappingManager.GetIsDragging() ? returnAll : returnNone}
+ pointerEvents={this._props.isContentActive() && SnappingManager.GetIsDragging() ? returnAll : returnNone}
isAnnotationOverlay={true}
isAnnotationOverlayScrollable={true}
- childDocumentsActive={this.props.isDocumentActive}
- fieldKey={this.props.fieldKey + '_annotations'}
+ childDocumentsActive={this._props.isDocumentActive}
+ fieldKey={this._props.fieldKey + '_annotations'}
dropAction="move"
select={emptyFunction}
addDocument={this.addAnnotationDocument}
removeDocument={this.remAnnotationDocument}
moveDocument={this.moveAnnotationDocument}
bringToFront={emptyFunction}
- renderDepth={this.props.renderDepth + 1}>
+ renderDepth={this._props.renderDepth + 1}>
{this.content}
</CollectionFreeFormView>
) : (
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 2e4c5af6b..e84c12e02 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,4 +1,4 @@
-import { IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { IReactionDisposer, makeObservable, observable, override, reaction, runInAction, untracked } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
@@ -6,7 +6,7 @@ import { ObjectField } from '../../../fields/ObjectField';
import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { returnEmptyString } from '../../../Utils';
+import { copyProps, returnEmptyString } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
import { CollectionViewType } from '../../documents/DocumentTypes';
import { dropActionType } from '../../util/DragManager';
@@ -76,12 +76,19 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
this._safeMode = safeMode;
}
private reactionDisposer: IReactionDisposer | undefined;
- @observable _isContentActive: boolean | undefined;
+ @observable _isContentActive: boolean | undefined = undefined;
+ _prevProps: React.PropsWithChildren<ViewBoxAnnotatableProps & CollectionViewProps>;
+ @override _props: React.PropsWithChildren<ViewBoxAnnotatableProps & CollectionViewProps>;
constructor(props: any) {
super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
runInAction(() => (this._annotationKeySuffix = returnEmptyString));
}
+ componentDidUpdate() {
+ copyProps(this);
+ }
componentDidMount() {
// we use a reaction/observable instead of a computed value to reduce invalidations.
@@ -89,7 +96,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
// will cause downstream invalidations even if the computed value doesn't change. By making
// this a reaction, downstream invalidations only occur when the reaction value actually changes.
this.reactionDisposer = reaction(
- () => (this.isAnyChildContentActive() ? true : this.props.isContentActive()),
+ () => (this.isAnyChildContentActive() ? true : this._props.isContentActive()),
active => (this._isContentActive = active),
{ fireImmediately: true }
);
@@ -112,7 +119,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
return viewField as any as CollectionViewType;
}
- screenToLocalTransform = () => (this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth()));
+ screenToLocalTransform = () => (this._props.renderDepth ? this._props.ScreenToLocalTransform() : this._props.ScreenToLocalTransform().scale(this._props.PanelWidth() / this.bodyPanelWidth()));
// prettier-ignore
private renderSubView = (type: CollectionViewType | undefined, props: SubCollectionViewProps) => {
TraceMobx();
@@ -172,7 +179,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
this.setupViewTypes('Appearance...', vtype => {
const newRendition = Doc.MakeEmbedding(this.Document);
newRendition._type_collection = vtype;
- this.props.addDocTab(newRendition, OpenWhere.addRight);
+ this._props.addDocTab(newRendition, OpenWhere.addRight);
return newRendition;
});
@@ -180,10 +187,10 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
const optionItems = options && 'subitems' in options ? options.subitems : [];
!Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.Document.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => (this.Document.forceActive = !this.Document.forceActive), icon: 'project-diagram' }) : null;
if (this.Document.childLayout instanceof Doc) {
- optionItems.push({ description: 'View Child Layout', event: () => this.props.addDocTab(this.Document.childLayout as Doc, OpenWhere.addRight), icon: 'project-diagram' });
+ optionItems.push({ description: 'View Child Layout', event: () => this._props.addDocTab(this.Document.childLayout as Doc, OpenWhere.addRight), icon: 'project-diagram' });
}
if (this.Document.childClickedOpenTemplateView instanceof Doc) {
- optionItems.push({ description: 'View Child Detailed Layout', event: () => this.props.addDocTab(this.Document.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' });
+ optionItems.push({ description: 'View Child Detailed Layout', event: () => this._props.addDocTab(this.Document.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' });
}
!Doc.noviceMode && optionItems.push({ description: `${this.layoutDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => (this.layoutDoc._isLightbox = !this.layoutDoc._isLightbox), icon: 'project-diagram' });
@@ -203,7 +210,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
event: (obj: any) => {
const embedding = Doc.MakeEmbedding(this.Document);
DocUtils.makeCustomViewClicked(embedding, undefined, func.key);
- this.props.addDocTab(embedding, OpenWhere.addRight);
+ this._props.addDocTab(embedding, OpenWhere.addRight);
},
})
);
@@ -226,34 +233,40 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
}
};
- bodyPanelWidth = () => this.props.PanelWidth();
+ bodyPanelWidth = () => this._props.PanelWidth();
- childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.Document.childLayoutTemplate, Doc, null);
+ childLayoutTemplate = () => this._props.childLayoutTemplate?.() || Cast(this.Document.childLayoutTemplate, Doc, null);
isContentActive = (outsideReaction?: boolean) => this._isContentActive;
+ pointerEvents = () => {
+ const viewPath = this._props.DocumentView?.()?._props.docViewPath();
+ return (
+ this.layoutDoc._lockedPosition && //
+ viewPath?.lastElement()?.Document?._type_collection === CollectionViewType.Freeform
+ );
+ };
+
render() {
TraceMobx();
+ const pointerEvents = this.pointerEvents() ? 'none' : undefined;
const props: SubCollectionViewProps = {
- ...this.props,
+ ...this._props,
addDocument: this.addDocument,
moveDocument: this.moveDocument,
removeDocument: this.removeDocument,
isContentActive: this.isContentActive,
isAnyChildContentActive: this.isAnyChildContentActive,
PanelWidth: this.bodyPanelWidth,
- PanelHeight: this.props.PanelHeight,
+ PanelHeight: this._props.PanelHeight,
ScreenToLocalTransform: this.screenToLocalTransform,
childLayoutTemplate: this.childLayoutTemplate,
whenChildContentsActiveChanged: this.whenChildContentsActiveChanged,
- childLayoutString: StrCast(this.Document.childLayoutString, this.props.childLayoutString),
- childHideResizeHandles: this.props.childHideResizeHandles ?? BoolCast(this.Document.childHideResizeHandles),
- childHideDecorationTitle: this.props.childHideDecorationTitle ?? BoolCast(this.Document.childHideDecorationTitle),
+ childLayoutString: StrCast(this.Document.childLayoutString, this._props.childLayoutString),
+ childHideResizeHandles: this._props.childHideResizeHandles ?? BoolCast(this.Document.childHideResizeHandles),
+ childHideDecorationTitle: this._props.childHideDecorationTitle ?? BoolCast(this.Document.childHideDecorationTitle),
};
return (
- <div
- className="collectionView"
- onContextMenu={this.onContextMenu}
- style={{ pointerEvents: this.props.DocumentView?.()?.props.docViewPath().lastElement()?.Document?._type_collection === CollectionViewType.Freeform && this.layoutDoc._lockedPosition ? 'none' : undefined }}>
+ <div className="collectionView" onContextMenu={this.onContextMenu} style={{ pointerEvents }}>
{this.renderSubView(this.collectionViewType, props)}
</div>
);
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 4a1d702b8..dc7ee206c 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Popup, Type } from 'browndash-components';
import { clamp } from 'lodash';
-import { action, computed, IReactionDisposer, observable, ObservableSet, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as ReactDOM from 'react-dom/client';
import { Doc, Opt } from '../../../fields/Doc';
@@ -11,7 +11,7 @@ import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { FieldId } from '../../../fields/RefField';
import { Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { DashColor, emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils';
+import { copyProps, DashColor, emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils';
import { DocServer } from '../../DocServer';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
@@ -48,6 +48,15 @@ export class TabDocView extends React.Component<TabDocViewProps> {
static _allTabs = new ObservableSet<TabDocView>();
_mainCont: HTMLDivElement | null = null;
_tabReaction: IReactionDisposer | undefined;
+
+ _prevProps: React.PropsWithChildren<TabDocViewProps>;
+ @observable _props: React.PropsWithChildren<TabDocViewProps>;
+ constructor(props: React.PropsWithChildren<TabDocViewProps>) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+
@observable _activated: boolean = false;
@observable _panelWidth = 0;
@observable _panelHeight = 0;
@@ -57,21 +66,21 @@ export class TabDocView extends React.Component<TabDocViewProps> {
@computed get _isUserActivated() {
return SelectionManager.IsSelected(this._document) || this._isAnyChildContentActive;
}
- @computed get _isContentActive() {
+ get _isContentActive() {
return this._isUserActivated || this._hovering;
}
- @observable _document: Doc | undefined;
- @observable _view: DocumentView | undefined;
+ @observable _document: Doc | undefined = undefined;
+ @observable _view: DocumentView | undefined = undefined;
@computed get layoutDoc() {
return this._document && Doc.Layout(this._document);
}
get stack() {
- return (this.props as any).glContainer.parent.parent;
+ return this._props.glContainer.parent.parent;
}
get tab() {
- return (this.props as any).glContainer.tab;
+ return this._props.glContainer.tab;
}
get view() {
return this._view;
@@ -162,12 +171,12 @@ export class TabDocView extends React.Component<TabDocViewProps> {
this._isUserActivated
? 0
: this._hovering
- ? 0.25
- : degree === Doc.DocBrushStatus.selfBrushed
- ? 0.5
- : degree === Doc.DocBrushStatus.protoBrushed //
- ? 0.7
- : 0.9
+ ? 0.25
+ : degree === Doc.DocBrushStatus.selfBrushed
+ ? 0.5
+ : degree === Doc.DocBrushStatus.protoBrushed //
+ ? 0.7
+ : 0.9
)
.rgb()
.toString()
@@ -316,7 +325,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs
}
- @action
componentDidMount() {
new _global.ResizeObserver(
action((entries: any) => {
@@ -325,25 +333,25 @@ export class TabDocView extends React.Component<TabDocViewProps> {
this._panelHeight = entry.contentRect.height;
}
})
- ).observe(this.props.glContainer._element[0]);
- this.props.glContainer.layoutManager.on('activeContentItemChanged', this.onActiveContentItemChanged);
- this.props.glContainer.tab?.isActive && this.onActiveContentItemChanged(undefined);
+ ).observe(this._props.glContainer._element[0]);
+ this._props.glContainer.layoutManager.on('activeContentItemChanged', this.onActiveContentItemChanged);
+ this._props.glContainer.tab?.isActive && this.onActiveContentItemChanged(undefined);
// this._tabReaction = reaction(() => ({ selected: this.active(), title: this.tab?.titleElement[0] }),
// ({ selected, title }) => title && (title.style.backgroundColor = selected ? "white" : ""),
// { fireImmediately: true });
- TabDocView._allTabs.add(this);
+ runInAction(() => TabDocView._allTabs.add(this));
}
componentDidUpdate() {
this._view && DocumentManager.Instance.AddView(this._view);
+ copyProps(this);
}
- @action
componentWillUnmount() {
this._tabReaction?.();
this._view && DocumentManager.Instance.RemoveView(this._view);
- TabDocView._allTabs.delete(this);
+ runInAction(() => TabDocView._allTabs.delete(this));
- this.props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged);
+ this._props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged);
}
// Flag indicating that when a tab is activated, it should not select it's document.
@@ -420,7 +428,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
ScreenToLocalTransform = () => {
this._forceInvalidateScreenToLocal;
const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
- return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY) ?? Transform.Identity();
+ return CollectionDockingView.Instance?._props.ScreenToLocalTransform().translate(-translateX, -translateY) ?? Transform.Identity();
};
PanelWidth = () => this._panelWidth;
PanelHeight = () => this._panelHeight;
@@ -441,8 +449,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
this._lastView = this._view;
})}
renderDepth={0}
- LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString() : undefined}
- hideTitle={this.props.keyValue}
+ LayoutTemplateString={this._props.keyValue ? KeyValueBox.LayoutString() : undefined}
+ hideTitle={this._props.keyValue}
Document={this._document}
TemplateDataDocument={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined}
onBrowseClick={DocumentView.exploreMode}
@@ -489,7 +497,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
}
this._lastTab = this.tab;
(this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document);
- DocServer.GetRefField(this.props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document)));
+ DocServer.GetRefField(this._props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document)));
new _global.ResizeObserver(action((entries: any) => this._forceInvalidateScreenToLocal++)).observe(ref);
}
}}>
@@ -547,8 +555,15 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
}
}
};
+
+ @observable _props: React.PropsWithChildren<TabMinimapViewProps>;
+ constructor(props: React.PropsWithChildren<TabMinimapViewProps>) {
+ super(props);
+ this._props = props;
+ makeObservable(this);
+ }
@computed get renderBounds() {
- const compView = this.props.tabView()?.ComponentView as CollectionFreeFormView;
+ const compView = this._props.tabView()?.ComponentView as CollectionFreeFormView;
const bounds = compView?.freeformData?.(true)?.bounds;
if (!bounds) return undefined;
const xbounds = bounds.r - bounds.x;
@@ -556,10 +571,10 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
const dim = Math.max(xbounds, ybounds);
return { l: bounds.x + xbounds / 2 - dim / 2, t: bounds.y + ybounds / 2 - dim / 2, cx: bounds.x + xbounds / 2, cy: bounds.y + ybounds / 2, dim };
}
- childLayoutTemplate = () => Cast(this.props.document.childLayoutTemplate, Doc, null);
- returnMiniSize = () => NumCast(this.props.document._miniMapSize, 150);
+ childLayoutTemplate = () => Cast(this._props.document.childLayoutTemplate, Doc, null);
+ returnMiniSize = () => NumCast(this._props.document._miniMapSize, 150);
miniDown = (e: React.PointerEvent) => {
- const doc = this.props.document;
+ const doc = this._props.document;
const miniSize = this.returnMiniSize();
doc &&
setupMoveUpEvents(
@@ -578,15 +593,15 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
popup = () => {
if (!this.renderBounds) return <></>;
const renderBounds = this.renderBounds;
- const miniWidth = () => (this.props.PanelWidth() / NumCast(this.props.document._freeform_scale, 1) / renderBounds.dim) * 100;
- const miniHeight = () => (this.props.PanelHeight() / NumCast(this.props.document._freeform_scale, 1) / renderBounds.dim) * 100;
- const miniLeft = () => 50 + ((NumCast(this.props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2;
- const miniTop = () => 50 + ((NumCast(this.props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2;
+ const miniWidth = () => (this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
+ const miniHeight = () => (this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
+ const miniLeft = () => 50 + ((NumCast(this._props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2;
+ const miniTop = () => 50 + ((NumCast(this._props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2;
const miniSize = this.returnMiniSize();
return (
- <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this.props.background() }}>
+ <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}>
<CollectionFreeFormView
- Document={this.props.document}
+ Document={this._props.document}
docViewPath={returnEmptyDoclist}
childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
noOverlay={true} // don't render overlay Docs since they won't scale
@@ -596,7 +611,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
select={emptyFunction}
isSelected={returnFalse}
dontRegisterView={true}
- fieldKey={Doc.LayoutFieldKey(this.props.document)}
+ fieldKey={Doc.LayoutFieldKey(this._props.document)}
bringToFront={emptyFunction}
addDocument={returnFalse}
moveDocument={returnFalse}
@@ -608,7 +623,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
whenChildContentsActiveChanged={emptyFunction}
focus={emptyFunction}
styleProvider={TabMinimapView.miniStyleProvider}
- addDocTab={this.props.addDocTab}
+ addDocTab={this._props.addDocTab}
pinToPres={TabDocView.PinDoc}
childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
@@ -622,7 +637,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
);
};
render() {
- return this.props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this.props.document)) || this.props.document?._type_collection !== CollectionViewType.Freeform ? null : (
+ return this._props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._props.document)) || this._props.document?._type_collection !== CollectionViewType.Freeform ? null : (
<div className="miniMap-hidden">
<Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SettingsManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement={'top-end'} popup={this.popup} />
</div>
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index ac79e4fef..27ff7166d 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -1,7 +1,7 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { IconButton, Size } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
@@ -12,7 +12,7 @@ import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils';
+import { copyProps, emptyFunction, lightOrDark, return18, 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';
@@ -44,7 +44,7 @@ export interface TreeViewProps {
observeHeight: (ref: any) => void;
unobserveHeight: (ref: any) => void;
prevSibling?: Doc;
- document: Doc;
+ Document: Doc;
dataDoc?: Doc;
treeViewParent: Doc;
renderDepth: number;
@@ -100,57 +100,61 @@ export class TreeView extends React.Component<TreeViewProps> {
private _treedropDisposer?: DragManager.DragDropDisposer;
get treeViewOpenIsTransient() {
- return this.props.treeView.doc.treeView_OpenIsTransient || Doc.IsDataProto(this.doc);
+ return this.treeView.Document.treeView_OpenIsTransient || Doc.IsDataProto(this.Document);
}
set treeViewOpen(c: boolean) {
if (this.treeViewOpenIsTransient) this._transientOpenState = c;
else {
- this.doc.treeView_Open = c;
+ this.Document.treeView_Open = c;
this._transientOpenState = false;
}
}
@observable _transientOpenState = false; // override of the treeView_Open field allowing the display state to be independent of the document's state
@observable _editTitle: boolean = false;
- @observable _dref: DocumentView | undefined | null;
+ @observable _dref: DocumentView | undefined | null = undefined;
get displayName() {
- return 'TreeView(' + this.props.document.title + ')';
+ return 'TreeView(' + this.Document.title + ')';
} // this makes mobx trace() statements more descriptive
get defaultExpandedView() {
- return this.doc._type_collection === CollectionViewType.Docking
+ return this.Document._type_collection === CollectionViewType.Docking
? this.fieldKey
- : this.props.treeView.dashboardMode
+ : this.treeView.dashboardMode
? this.fieldKey
- : this.props.treeView.fileSysMode
- ? this.doc.isFolder
+ : this.treeView.fileSysMode
+ ? this.Document.isFolder
? this.fieldKey
: 'data' // file system folders display their contents (data). used to be they displayed their embeddings but now its a tree structure and not a flat list
- : this.props.treeView.outlineMode || this.childDocs
+ : this.treeView.outlineMode || this.childDocs
? this.fieldKey
: Doc.noviceMode
? 'layout'
- : StrCast(this.props.treeView.doc.treeView_ExpandedView, 'fields');
+ : StrCast(this.treeView.Document.treeView_ExpandedView, 'fields');
}
- @computed get doc() {
- return this.props.document;
+ @computed get treeView() {
+ return this._props.treeView;
+ }
+
+ @computed get Document() {
+ return this._props.Document;
}
@computed get treeViewOpen() {
- return (!this.treeViewOpenIsTransient && Doc.GetT(this.doc, 'treeView_Open', 'boolean', true)) || this._transientOpenState;
+ return (!this.treeViewOpenIsTransient && Doc.GetT(this.Document, 'treeView_Open', 'boolean', true)) || this._transientOpenState;
}
@computed get treeViewExpandedView() {
- return this.validExpandViewTypes.includes(StrCast(this.doc.treeView_ExpandedView)) ? StrCast(this.doc.treeView_ExpandedView) : this.defaultExpandedView;
+ return this.validExpandViewTypes.includes(StrCast(this.Document.treeView_ExpandedView)) ? StrCast(this.Document.treeView_ExpandedView) : this.defaultExpandedView;
}
@computed get MAX_EMBED_HEIGHT() {
- return NumCast(this.props.treeViewParent.maxEmbedHeight, 200);
+ return NumCast(this._props.treeViewParent.maxEmbedHeight, 200);
}
@computed get dataDoc() {
- return this.doc[DocData];
+ return this.Document[DocData];
}
@computed get layoutDoc() {
- return Doc.Layout(this.doc);
+ return Doc.Layout(this.Document);
}
@computed get fieldKey() {
- return StrCast(this.doc._treeView_FieldKey, Doc.LayoutFieldKey(this.doc));
+ return StrCast(this.Document._treeView_FieldKey, Doc.LayoutFieldKey(this.Document));
}
@computed get childDocs() {
return this.childDocList(this.fieldKey);
@@ -169,30 +173,30 @@ export class TreeView extends React.Component<TreeViewProps> {
}
childDocList(field: string) {
- const layout = Cast(Doc.LayoutField(this.doc), Doc, null);
- return DocListCast(this.props.dataDoc?.[field], DocListCast(layout?.[field], DocListCast(this.doc[field])));
+ const layout = Cast(Doc.LayoutField(this.Document), Doc, null);
+ return DocListCast(this._props.dataDoc?.[field], DocListCast(layout?.[field], DocListCast(this.Document[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);
+ if (this.Document !== target && addDoc !== returnFalse) {
+ const canAdd1 = (this._props.parentTreeView as any).dropping || !(ComputedField.WithoutComputed(() => FieldValue(this._props.parentTreeView?.Document.data)) instanceof ComputedField);
// bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse
- if (canAdd1 && this.props.removeDoc?.(doc) === true) {
- this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.moving = true);
+ 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);
+ this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.moving = false);
return res;
}
}
return false;
};
- @undoBatch @action remove = (doc: Doc | Doc[], key: string) => {
- this.props.treeView.props.select(false);
+ @undoBatch remove = (doc: Doc | Doc[], key: string) => {
+ this.treeView._props.select(false);
const ind = DocListCast(this.dataDoc[key]).indexOf(doc instanceof Doc ? doc : doc.lastElement());
const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
- res && ind > 0 && DocumentManager.Instance.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.props.treeView.props.DocumentView?.())?.select(false);
+ res && ind > 0 && DocumentManager.Instance.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.treeView._props.DocumentView?.())?.select(false);
return res;
};
@@ -212,17 +216,16 @@ export class TreeView extends React.Component<TreeViewProps> {
};
@action
openLevel = (docView: DocumentView) => {
- if (this.props.document.isFolder || Doc.IsSystem(this.props.document)) {
+ if (this.Document.isFolder || Doc.IsSystem(this.Document)) {
this.treeViewOpen = !this.treeViewOpen;
} else {
// choose an appropriate embedding or make one. --- choose the first embedding that (1) user owns, (2) has no context field ... otherwise make a new embedding
- const bestEmbedding = docView.Document.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document) ? docView.Document : Doc.BestEmbedding(docView.Document);
- this.props.addDocTab(bestEmbedding, OpenWhere.lightbox);
+ const bestEmbedding = docView.Document.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.Document) ? docView.Document : Doc.BestEmbedding(docView.Document);
+ this._props.addDocTab(bestEmbedding, OpenWhere.lightbox);
}
};
@undoBatch
- @action
recurToggle = (childList: Doc[]) => {
if (childList.length > 0) {
childList.forEach(child => {
@@ -233,7 +236,6 @@ export class TreeView extends React.Component<TreeViewProps> {
};
@undoBatch
- @action
getRunningChildren = (childList: Doc[]) => {
if (childList.length === 0) {
return [];
@@ -253,23 +255,27 @@ export class TreeView extends React.Component<TreeViewProps> {
static GetRunningChildren = new Map<Doc, any>();
static ToggleChildrenRun = new Map<Doc, () => void>();
- constructor(props: any) {
+ _prevProps: TreeViewProps;
+ @observable _props: TreeViewProps;
+ constructor(props: TreeViewProps) {
super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
if (!TreeView._openLevelScript) {
TreeView._openTitleScript = ScriptField.MakeScript('scriptContext.setEditTitle(documentView)', { scriptContext: 'any', documentView: 'any' });
TreeView._openLevelScript = ScriptField.MakeScript(`scriptContext.openLevel(documentView)`, { scriptContext: 'any', documentView: 'any' });
}
- this._openScript = Doc.IsSystem(this.props.document) ? undefined : () => TreeView._openLevelScript!;
- this._editTitleScript = Doc.IsSystem(this.props.document) ? () => TreeView._openLevelScript! : () => TreeView._openTitleScript!;
+ this._openScript = Doc.IsSystem(this.Document) ? undefined : () => TreeView._openLevelScript!;
+ this._editTitleScript = Doc.IsSystem(this.Document) ? () => TreeView._openLevelScript! : () => TreeView._openTitleScript!;
// set for child processing highligting
this.dataDoc.hasChildren = this.childDocs.length > 0;
// this.dataDoc.children = this.childDocs;
- TreeView.ToggleChildrenRun.set(this.doc, () => {
+ TreeView.ToggleChildrenRun.set(this.Document, () => {
this.recurToggle(this.childDocs);
});
- TreeView.GetRunningChildren.set(this.doc, () => {
+ TreeView.GetRunningChildren.set(this.Document, () => {
return this.getRunningChildren(this.childDocs);
});
}
@@ -277,31 +283,32 @@ export class TreeView extends React.Component<TreeViewProps> {
_treeEle: any;
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this._treedropDisposer?.();
- ele && ((this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this))), this.doc);
- if (this._treeEle) this.props.unobserveHeight(this._treeEle);
- this.props.observeHeight((this._treeEle = ele));
+ ele && ((this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this))), this.Document);
+ if (this._treeEle) this._props.unobserveHeight(this._treeEle);
+ this._props.observeHeight((this._treeEle = ele));
};
componentWillUnmount() {
this._renderTimer && clearTimeout(this._renderTimer);
Object.values(this._disposers).forEach(disposer => disposer?.());
- this._treeEle && this.props.unobserveHeight(this._treeEle);
+ this._treeEle && this._props.unobserveHeight(this._treeEle);
document.removeEventListener('pointermove', this.onDragMove, true);
document.removeEventListener('pointermove', this.onDragUp, true);
// TODO: [AL] add these
- this.props.hierarchyIndex !== undefined && this.props.RemFromMap?.(this.doc, this.props.hierarchyIndex);
+ this._props.hierarchyIndex !== undefined && this._props.RemFromMap?.(this.Document, this._props.hierarchyIndex);
}
componentDidUpdate() {
+ copyProps(this);
this._disposers.opening = reaction(
() => this.treeViewOpen,
open => !open && (this._renderCount = 20)
);
- this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex);
+ this._props.hierarchyIndex !== undefined && this._props.AddToMap?.(this.Document, this._props.hierarchyIndex);
}
componentDidMount() {
- this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex);
+ this._props.hierarchyIndex !== undefined && this._props.AddToMap?.(this.Document, this._props.hierarchyIndex);
}
onDragUp = (e: PointerEvent) => {
@@ -309,8 +316,8 @@ export class TreeView extends React.Component<TreeViewProps> {
document.removeEventListener('pointermove', this.onDragMove, true);
};
onPointerEnter = (e: React.PointerEvent): void => {
- this.props.isContentActive(true) && Doc.BrushDoc(this.dataDoc);
- if (e.buttons === 1 && SnappingManager.GetIsDragging() && this.props.isContentActive()) {
+ this._props.isContentActive(true) && Doc.BrushDoc(this.dataDoc);
+ if (e.buttons === 1 && SnappingManager.GetIsDragging() && this._props.isContentActive()) {
this._header.current!.className = 'treeView-header';
document.removeEventListener('pointermove', this.onDragMove, true);
document.removeEventListener('pointerup', this.onDragUp, true);
@@ -333,7 +340,7 @@ export class TreeView extends React.Component<TreeViewProps> {
const before = pt[1] < rect.top + rect.height / 2;
const inside = pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length);
this._header.current!.className = 'treeView-header';
- if (!this.props.treeView.outlineMode || DragManager.DocDragData?.treeViewDoc === this.props.treeView.Document) {
+ if (!this.treeView.outlineMode || DragManager.DocDragData?.treeViewDoc === this.treeView.Document) {
if (inside) this._header.current!.className += ' treeView-header-inside';
else if (before) this._header.current!.className += ' treeView-header-above';
else if (!before) this._header.current!.className += ' treeView-header-below';
@@ -371,18 +378,18 @@ export class TreeView extends React.Component<TreeViewProps> {
makeTextCollection = () => {
const bullet = TreeView.makeTextBullet();
TreeView._editTitleOnLoad = { id: bullet[Id], parent: this };
- return this.props.addDocument(bullet);
+ return this._props.addDocument(bullet);
};
makeFolder = () => {
const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true });
- TreeView._editTitleOnLoad = { id: folder[Id], parent: this.props.parentTreeView };
- return this.props.addDocument(folder);
+ TreeView._editTitleOnLoad = { id: folder[Id], parent: this._props.parentTreeView };
+ return this._props.addDocument(folder);
};
preTreeDrop = (e: Event, de: DragManager.DropEvent) => {
const dragData = de.complete.docDragData;
- dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? 'same' : dragData.dropAction);
+ dragData && (dragData.dropAction = this.treeView.Document === dragData.treeViewDoc ? 'same' : dragData.dropAction);
};
@undoBatch
@@ -391,17 +398,17 @@ export class TreeView extends React.Component<TreeViewProps> {
if (!this._header.current) return false;
const rect = this._header.current.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
- const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
+ const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
if (de.complete.linkDragData) {
const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor();
- const destDoc = this.doc;
+ const destDoc = this.Document;
DocUtils.MakeLink(sourceDoc, destDoc, { link_relationship: 'tree link' });
e.stopPropagation();
return true;
}
const docDragData = de.complete.docDragData;
if (docDragData && pt[0] < rect.left + rect.width) {
- if (docDragData.draggedDocuments[0] === this.doc) return true;
+ if (docDragData.draggedDocuments[0] === this.Document) return true;
const added = this.dropDocuments(
docDragData.droppedDocuments, //
before,
@@ -409,7 +416,7 @@ export class TreeView extends React.Component<TreeViewProps> {
docDragData.dropAction,
docDragData.removeDocument,
docDragData.moveDocument,
- docDragData.treeViewDoc === this.props.treeView.props.Document
+ docDragData.treeViewDoc === this.treeView.Document
);
e.stopPropagation();
!added && e.preventDefault();
@@ -420,40 +427,40 @@ export class TreeView extends React.Component<TreeViewProps> {
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, undefined, before);
+ const parentAddDoc = (doc: Doc | Doc[]) => this._props.addDocument(doc, undefined, undefined, before);
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.SetContainer(doc, DocCast(this.doc.embedContainer));
+ dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer));
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' || dropAction === 'inSame') && moveDocument;
- const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.treeViewParent)?.treeView_FreezeChildren).includes('add')) || forceAdd;
- if (canAdd && (dropAction !== 'inSame' || droppedDocuments.every(d => d.embedContainer === this.props.parentTreeView?.doc))) {
- this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = true);
+ const canAdd = (!this.treeView.outlineMode && !StrCast((inside ? this.Document : this._props.treeViewParent)?.treeView_FreezeChildren).includes('add')) || forceAdd;
+ if (canAdd && (dropAction !== 'inSame' || droppedDocuments.every(d => d.embedContainer === this._props.parentTreeView?.Document))) {
+ this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = true);
const res = 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);
+ this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = false);
return res;
}
return false;
}
refTransform = (ref: HTMLDivElement | undefined | null) => {
- if (!ref) return this.props.ScreenToLocalTransform();
+ if (!ref) return this._props.ScreenToLocalTransform();
const { scale, translateX, translateY } = Utils.GetScreenTransform(ref);
- const outerXf = Utils.GetScreenTransform(this.props.treeView.MainEle());
- const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
- return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]);
+ const outerXf = Utils.GetScreenTransform(this.treeView.MainEle());
+ const offset = this._props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
+ return this._props.ScreenToLocalTransform().translate(offset[0], offset[1]);
};
docTransform = () => this.refTransform(this._dref?.ContentRef?.current);
getTransform = () => this.refTransform(this._tref.current);
- embeddedPanelWidth = () => this.props.panelWidth() / (this.props.treeView.props.NativeDimScaling?.() || 1);
+ embeddedPanelWidth = () => this._props.panelWidth() / (this.treeView._props.NativeDimScaling?.() || 1);
embeddedPanelHeight = () => {
- const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
+ const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.Document))(this.treeView._props.childLayoutTemplate?.()) || this.layoutDoc;
return Math.min(
NumCast(layoutDoc._height),
this.MAX_EMBED_HEIGHT,
@@ -463,7 +470,7 @@ export class TreeView extends React.Component<TreeViewProps> {
return layoutDoc._layout_fitWidth
? !Doc.NativeHeight(layoutDoc)
? NumCast(layoutDoc._height)
- : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.treeViewParent._height)))
+ : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this._props.treeViewParent._height)))
: (this.embeddedPanelWidth() * NumCast(layoutDoc._height)) / NumCast(layoutDoc._width);
})()
);
@@ -471,16 +478,16 @@ export class TreeView extends React.Component<TreeViewProps> {
@computed get expandedField() {
const ids: { [key: string]: string } = {};
const rows: JSX.Element[] = [];
- const doc = this.doc;
+ const doc = this.Document;
doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key));
for (const key of Object.keys(ids).slice().sort()) {
- if (this.props.skipFields?.includes(key) || key === 'title' || key === 'treeView_Open') continue;
+ if (this._props.skipFields?.includes(key) || key === 'title' || key === 'treeView_Open') continue;
const contents = doc[key];
let contentElement: (JSX.Element | null)[] | JSX.Element = [];
let leftOffset = observable({ width: 0 });
- const expandedWidth = () => this.props.panelWidth() - leftOffset.width;
+ 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 moveDoc = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(doc, target, addDoc);
@@ -488,44 +495,44 @@ export class TreeView extends React.Component<TreeViewProps> {
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.SetContainer(doc, DocCast(this.doc.embedContainer));
+ dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer));
return added;
};
return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
};
contentElement = TreeView.GetChildElements(
contents instanceof Doc ? [contents] : DocListCast(contents),
- this.props.treeView,
+ this.treeView,
this,
doc,
undefined,
- this.props.treeViewParent,
- this.props.prevSibling,
+ this._props.treeViewParent,
+ this._props.prevSibling,
addDoc,
remDoc,
moveDoc,
- this.props.dragAction,
- this.props.addDocTab,
+ this._props.dragAction,
+ this._props.addDocTab,
this.titleStyleProvider,
- this.props.ScreenToLocalTransform,
- this.props.isContentActive,
+ this._props.ScreenToLocalTransform,
+ this._props.isContentActive,
expandedWidth,
- this.props.renderDepth,
- this.props.treeViewHideHeaderFields,
- [...this.props.renderedIds, doc[Id]],
- this.props.onCheckedClick,
- this.props.onChildClick,
- this.props.skipFields,
+ this._props.renderDepth,
+ this._props.treeViewHideHeaderFields,
+ [...this._props.renderedIds, doc[Id]],
+ this._props.onCheckedClick,
+ this._props.onChildClick,
+ this._props.skipFields,
false,
- this.props.whenChildContentsActiveChanged,
- this.props.dontRegisterView,
+ this._props.whenChildContentsActiveChanged,
+ this._props.dontRegisterView,
emptyFunction,
emptyFunction,
this.childContextMenuItems(),
// TODO: [AL] Add these
- this.props.AddToMap,
- this.props.RemFromMap,
- this.props.hierarchyIndex
+ this._props.AddToMap,
+ this._props.RemFromMap,
+ this._props.hierarchyIndex
);
} else {
contentElement = (
@@ -573,9 +580,9 @@ export class TreeView extends React.Component<TreeViewProps> {
@computed get renderContent() {
TraceMobx();
const expandKey = this.treeViewExpandedView;
- const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; icon: JSX.Element | string } }) ?? {};
+ const sortings = (this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; icon: JSX.Element | string } }) ?? {};
if (['links', 'annotations', 'embeddings', this.fieldKey].includes(expandKey)) {
- const sorting = StrCast(this.doc.treeView_SortCriterion, TreeSort.WhenAdded);
+ const sorting = StrCast(this.Document.treeView_SortCriterion, TreeSort.WhenAdded);
const sortKeys = Object.keys(sortings);
const curSortIndex = Math.max(
0,
@@ -587,7 +594,7 @@ export class TreeView extends React.Component<TreeViewProps> {
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
- const ordering = StrCast(this.doc.treeView_SortCriterion);
+ const ordering = StrCast(this.Document.treeView_SortCriterion);
if (ordering === TreeSort.Zindex) {
const docs = TreeView.sortDocs(this.childDocs || ([] as Doc[]), ordering);
doc.zIndex = addBefore ? NumCast(addBefore.zIndex) + (before ? -0.5 : 0.5) : 1000;
@@ -596,7 +603,7 @@ export class TreeView extends React.Component<TreeViewProps> {
}
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.SetContainer(doc, this.doc);
+ !dataIsComputed && added && Doc.SetContainer(doc, this.Document);
return added;
};
@@ -614,12 +621,12 @@ export class TreeView extends React.Component<TreeViewProps> {
}
return (
<div>
- {!docs?.length || this.props.AddToMap /* hack to identify pres box trees */ ? null : (
+ {!docs?.length || this._props.AddToMap /* hack to identify pres box trees */ ? null : (
<div className="treeView-sorting">
<IconButton
color={sortings[sorting]?.color}
size={Size.XSMALL}
- tooltip={`Sorted by : ${this.doc.treeView_SortCriterion}. click to cycle`}
+ tooltip={`Sorted by : ${this.Document.treeView_SortCriterion}. click to cycle`}
icon={sortings[sorting]?.icon}
onPointerDown={e => {
downX = e.clientX;
@@ -627,8 +634,8 @@ export class TreeView extends React.Component<TreeViewProps> {
e.stopPropagation();
}}
onClick={undoable(e => {
- if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) {
- !this.props.treeView.outlineMode && (this.doc.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]);
+ if (this._props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) {
+ !this.treeView.outlineMode && (this.Document.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]);
e.stopPropagation();
}
}, 'sort order')}
@@ -638,7 +645,7 @@ export class TreeView extends React.Component<TreeViewProps> {
<ul
style={{ cursor: 'inherit' }}
key={expandKey + 'more'}
- title={`Sorted by : ${this.doc.treeView_SortCriterion}. click to cycle`}
+ title={`Sorted by : ${this.Document.treeView_SortCriterion}. click to cycle`}
className="" //this.doc.treeView_HideTitle ? 'no-indent' : ''}
onPointerDown={e => {
downX = e.clientX;
@@ -646,8 +653,8 @@ export class TreeView extends React.Component<TreeViewProps> {
e.stopPropagation();
}}
onClick={undoable(e => {
- if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) {
- !this.props.treeView.outlineMode && (this.doc.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]);
+ if (this._props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) {
+ !this.treeView.outlineMode && (this.Document.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]);
e.stopPropagation();
}
}, 'sort order')}>
@@ -655,37 +662,37 @@ export class TreeView extends React.Component<TreeViewProps> {
? null
: TreeView.GetChildElements(
docs,
- this.props.treeView,
+ this.treeView,
this,
this.layoutDoc,
this.dataDoc,
- this.props.treeViewParent,
- this.props.prevSibling,
+ this._props.treeViewParent,
+ this._props.prevSibling,
addDoc,
remDoc,
moveDoc,
- StrCast(this.doc.childDragAction, this.props.dragAction) as dropActionType,
- this.props.addDocTab,
+ StrCast(this.Document.childDragAction, this._props.dragAction) as dropActionType,
+ this._props.addDocTab,
this.titleStyleProvider,
- this.props.ScreenToLocalTransform,
- this.props.isContentActive,
- this.props.panelWidth,
- this.props.renderDepth,
- this.props.treeViewHideHeaderFields,
- [...this.props.renderedIds, this.doc[Id]],
- this.props.onCheckedClick,
- this.props.onChildClick,
- this.props.skipFields,
+ this._props.ScreenToLocalTransform,
+ this._props.isContentActive,
+ this._props.panelWidth,
+ this._props.renderDepth,
+ this._props.treeViewHideHeaderFields,
+ [...this._props.renderedIds, this.Document[Id]],
+ this._props.onCheckedClick,
+ this._props.onChildClick,
+ this._props.skipFields,
false,
- this.props.whenChildContentsActiveChanged,
- this.props.dontRegisterView,
+ this._props.whenChildContentsActiveChanged,
+ this._props.dontRegisterView,
emptyFunction,
emptyFunction,
this.childContextMenuItems(),
// TODO: [AL] add these
- this.props.AddToMap,
- this.props.RemFromMap,
- this.props.hierarchyIndex,
+ this._props.AddToMap,
+ this._props.RemFromMap,
+ this._props.hierarchyIndex,
this._renderCount
)}
</ul>
@@ -693,7 +700,7 @@ export class TreeView extends React.Component<TreeViewProps> {
);
} else if (this.treeViewExpandedView === 'fields') {
return (
- <ul key={this.doc[Id] + this.doc.title} style={{ cursor: 'inherit' }}>
+ <ul key={this.Document[Id] + this.Document.title} style={{ cursor: 'inherit' }}>
<div>{this.expandedField}</div>
</ul>
);
@@ -705,13 +712,13 @@ export class TreeView extends React.Component<TreeViewProps> {
e.preventDefault();
e.stopPropagation();
}}>
- {this.renderEmbeddedDocument(false, this.props.treeView.props.childDocumentsActive ?? returnFalse)}
+ {this.renderEmbeddedDocument(false, this.treeView._props.childDocumentsActive ?? returnFalse)}
</ul>
); // "layout"
}
get onCheckedClick() {
- return this.doc.type === DocumentType.COL ? undefined : this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick);
+ return this.Document.type === DocumentType.COL ? undefined : this._props.onCheckedClick?.() ?? ScriptCast(this.Document.onCheckedClick);
}
@action
@@ -719,10 +726,10 @@ export class TreeView extends React.Component<TreeViewProps> {
if (this.onCheckedClick) {
this.onCheckedClick?.script.run(
{
- this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc,
- heading: this.props.treeViewParent.title,
- checked: this.doc.treeView_Checked === 'check' ? 'x' : this.doc.treeView_Checked === 'x' ? 'remove' : 'check',
- containingTreeView: this.props.treeView.props.Document,
+ this: this.Document.isTemplateForField && this._props.dataDoc ? this._props.dataDoc : this.Document,
+ heading: this._props.treeViewParent.title,
+ checked: this.Document.treeView_Checked === 'check' ? 'x' : this.Document.treeView_Checked === 'x' ? 'remove' : 'check',
+ containingTreeView: this.treeView.Document,
},
console.log
);
@@ -734,28 +741,28 @@ export class TreeView extends React.Component<TreeViewProps> {
@computed get renderBullet() {
TraceMobx();
- const iconType = this.props.treeView.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':open' : !this.childDocs.length ? ':empty' : '')) || 'question';
+ const iconType = this.treeView._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':open' : !this.childDocs.length ? ':empty' : '')) || 'question';
const color = SettingsManager.userColor;
- const checked = this.onCheckedClick ? this.doc.treeView_Checked ?? 'unchecked' : undefined;
+ const checked = this.onCheckedClick ? this.Document.treeView_Checked ?? 'unchecked' : undefined;
return (
<div
- className={`bullet${this.props.treeView.outlineMode ? '-outline' : ''}`}
+ className={`bullet${this.treeView.outlineMode ? '-outline' : ''}`}
key="bullet"
- title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : `view ${this.props.document.type} content`}
+ title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : `view ${this.Document.type} content`}
onClick={this.bulletClick}
style={
- this.props.treeView.outlineMode
+ this.treeView.outlineMode
? {
- opacity: this.titleStyleProvider?.(this.doc, this.props.treeView.props, StyleProp.Opacity),
+ opacity: this.titleStyleProvider?.(this.Document, this.treeView._props, StyleProp.Opacity),
}
: {
- pointerEvents: this.props.isContentActive() ? 'all' : undefined,
+ pointerEvents: this._props.isContentActive() ? 'all' : undefined,
opacity: checked === 'unchecked' || typeof iconType !== 'string' ? undefined : 0.4,
color: checked === 'unchecked' ? SettingsManager.userColor : 'inherit',
}
}>
- {this.props.treeView.outlineMode ? (
- !(this.doc.text as RichTextField)?.Text ? null : (
+ {this.treeView.outlineMode ? (
+ !(this.Document.text as RichTextField)?.Text ? null : (
<IconButton color={color} icon={<FontAwesomeIcon icon={[this.childDocs?.length && !this.treeViewOpen ? 'fas' : 'far', 'circle']} />} size={Size.XSMALL} />
)
) : (
@@ -775,28 +782,28 @@ 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 = () => (LinkManager.Links(this.doc).length && !this.props.treeView.dashboardMode ? 'links' : '');
- const data = () => (this.childDocs || this.props.treeView.dashboardMode ? this.fieldKey : '');
- const embeddings = () => (this.props.treeView.dashboardMode ? '' : 'embeddings');
+ const annos = () => (DocListCast(this.Document[this.fieldKey + '_annotations']).length && !this.treeView.dashboardMode ? 'annotations' : '');
+ const links = () => (LinkManager.Links(this.Document).length && !this.treeView.dashboardMode ? 'links' : '');
+ const data = () => (this.childDocs || this.treeView.dashboardMode ? this.fieldKey : '');
+ const embeddings = () => (this.treeView.dashboardMode ? '' : 'embeddings');
const fields = () => (Doc.noviceMode ? '' : 'fields');
- const layout = Doc.noviceMode || this.doc._type_collection === CollectionViewType.Docking ? [] : ['layout'];
- return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [embeddings(), links(), annos()] : []), fields()].filter(m => m);
+ const layout = Doc.noviceMode || this.Document._type_collection === CollectionViewType.Docking ? [] : ['layout'];
+ return [data(), ...layout, ...(this.treeView.fileSysMode ? [embeddings(), links(), annos()] : []), fields()].filter(m => m);
}
@action
expandNextviewType = () => {
- if (this.treeViewOpen && !this.doc.isFolder && !this.props.treeView.outlineMode && !this.doc.treeView_ExpandedViewLock) {
+ if (this.treeViewOpen && !this.Document.isFolder && !this.treeView.outlineMode && !this.Document.treeView_ExpandedViewLock) {
const next = (modes: any[]) => modes[(modes.indexOf(StrCast(this.treeViewExpandedView)) + 1) % modes.length];
- this.doc.treeView_ExpandedView = next(this.validExpandViewTypes);
+ this.Document.treeView_ExpandedView = next(this.validExpandViewTypes);
}
this.treeViewOpen = true;
};
@observable headerEleWidth = 0;
@computed get titleButtons() {
- const customHeaderButtons = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations);
+ const customHeaderButtons = this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.Decorations);
const color = SettingsManager.userColor;
- return this.props.treeViewHideHeaderFields() || this.doc.treeView_HideHeaderFields ? null : (
+ return this._props.treeViewHideHeaderFields() || this.Document.treeView_HideHeaderFields ? null : (
<>
{customHeaderButtons} {/* e.g.,. hide button is set by dashboardStyleProvider */}
<IconButton
@@ -808,7 +815,7 @@ export class TreeView extends React.Component<TreeViewProps> {
e.stopPropagation();
}}
/>
- {Doc.noviceMode ? null : this.doc.treeView_ExpandedViewLock || Doc.IsSystem(this.doc) ? null : (
+ {Doc.noviceMode ? null : this.Document.treeView_ExpandedViewLock || Doc.IsSystem(this.Document) ? null : (
<span className="collectionTreeView-keyHeader" title="type of expanded data" key={this.treeViewExpandedView} onPointerDown={this.expandNextviewType}>
{this.treeViewExpandedView}
</span>
@@ -829,50 +836,50 @@ export class TreeView extends React.Component<TreeViewProps> {
const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!, icon: 'eye', label: 'Focus or Open' };
const reopenDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!, icon: 'eye', label: 'Reopen' };
return [
- ...(this.props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)),
- ...(this.doc.isFolder
+ ...(this._props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.Document })?.result)),
+ ...(this.Document.isFolder
? folderOp
- : Doc.IsSystem(this.doc)
+ : Doc.IsSystem(this.Document)
? []
- : this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc)
+ : this.treeView.fileSysMode && this.Document === Doc.GetProto(this.Document)
? [openEmbedding, makeFolder]
- : this.doc._type_collection === CollectionViewType.Docking
+ : this.Document._type_collection === CollectionViewType.Docking
? []
- : this.props.treeView.Document === Doc.MyRecentlyClosed
+ : this.treeView.Document === Doc.MyRecentlyClosed
? [reopenDoc]
: [openEmbedding, focusDoc]),
];
};
childContextMenuItems = () => {
- const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []);
- const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
- const icons = StrListCast(this.doc.childContextMenuIcons);
- return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
+ const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), []);
+ const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), []);
+ const icons = StrListCast(this.Document.childContextMenuIcons);
+ return StrListCast(this.Document.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
};
- onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!);
+ onChildClick = () => this._props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!);
- onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeView_ChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null);
+ onChildDoubleClick = () => ScriptCast(this.treeView.Document.treeView_ChildDoubleClick, !this.treeView.outlineMode ? this._openScript?.() : null);
- refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document, {});
+ refocus = () => this.treeView._props.focus(this.treeView.Document, {});
ignoreEvent = (e: any) => {
- if (this.props.isContentActive(true)) {
+ if (this._props.isContentActive(true)) {
e.stopPropagation();
e.preventDefault();
}
};
titleStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
- if (!doc || doc !== this.doc) return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
+ if (!doc || doc !== this.Document) return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
- const treeView = this.props.treeView;
+ const treeView = this.treeView;
// prettier-ignore
switch (property.split(':')[0]) {
- case StyleProp.Opacity: return this.props.treeView.outlineMode ? undefined : 1;
+ case StyleProp.Opacity: return this.treeView.outlineMode ? undefined : 1;
case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
- case StyleProp.Highlighting: if (this.props.treeView.outlineMode) return undefined;
+ case StyleProp.Highlighting: if (this.treeView.outlineMode) return undefined;
case StyleProp.BoxShadow: return undefined;
case StyleProp.DocContents:
- const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.GetBrushHighlightStatus(doc);
+ const highlightIndex = this.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.GetBrushHighlightStatus(doc);
const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
return treeView.outlineMode ? null : (
<div
@@ -882,32 +889,32 @@ export class TreeView extends React.Component<TreeViewProps> {
maxWidth: props?.PanelWidth() || undefined,
background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor),
outline: SnappingManager.GetIsDragging() ? undefined: `solid ${highlightColor} ${highlightIndex}px`,
- paddingLeft: NumCast(treeView.Document.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
- paddingRight: NumCast(treeView.Document.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
- paddingTop: treeView.props.childYPadding,
- paddingBottom: treeView.props.childYPadding,
+ paddingLeft: NumCast(treeView.Document.childXPadding, NumCast(treeView._props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
+ paddingRight: NumCast(treeView.Document.childXPadding, NumCast(treeView._props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
+ paddingTop: treeView._props.childYPadding,
+ paddingBottom: treeView._props.childYPadding,
}}>
{StrCast(doc?.title)}
</div>
);
}
- return treeView.props.styleProvider?.(doc, props, property);
+ return treeView._props.styleProvider?.(doc, props, property);
};
embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
if (property.startsWith(StyleProp.Decorations)) return null;
- return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
+ return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
};
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
- if (this.doc.treeView_HideHeader || (this.doc.treeView_HideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode) {
+ if (this.Document.treeView_HideHeader || (this.Document.treeView_HideHeaderIfTemplate && this.treeView._props.childLayoutTemplate?.()) || this.treeView.outlineMode) {
switch (e.key) {
case 'Tab':
e.stopPropagation?.();
e.preventDefault?.();
setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150);
- UndoManager.RunInBatch(() => (e.shiftKey ? this.props.outdentDocument?.(true) : this.props.indentDocument?.(true)), 'tab');
+ UndoManager.RunInBatch(() => (e.shiftKey ? this._props.outdentDocument?.(true) : this._props.indentDocument?.(true)), 'tab');
return true;
case 'Backspace':
- if (!(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc)) {
+ if (!(this.Document.text as RichTextField)?.Text && this._props.removeDoc?.(this.Document)) {
e.stopPropagation?.();
e.preventDefault?.();
return true;
@@ -921,7 +928,7 @@ export class TreeView extends React.Component<TreeViewProps> {
}
return false;
};
- titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth())) / (this.props.treeView.props.NativeDimScaling?.() || 1) - this.headerEleWidth - treeBulletWidth();
+ titleWidth = () => Math.max(20, Math.min(this.treeView.truncateTitleWidth(), this._props.panelWidth())) / (this.treeView._props.NativeDimScaling?.() || 1) - this.headerEleWidth - treeBulletWidth();
/**
* Renders the EditableView title element for placement into the tree.
@@ -936,21 +943,21 @@ export class TreeView extends React.Component<TreeViewProps> {
display={'inline-block'}
editing={this._editTitle}
background={'#7089bb'}
- contents={StrCast(this.doc.title)}
+ contents={StrCast(this.Document.title)}
height={12}
sizeToContent={true}
fontSize={12}
isEditingCallback={action(e => (this._editTitle = e))}
- GetValue={() => StrCast(this.doc.title)}
+ GetValue={() => StrCast(this.Document.title)}
OnTab={undoBatch((shift?: boolean) => {
- if (!shift) this.props.indentDocument?.(true);
- else this.props.outdentDocument?.(true);
+ if (!shift) this._props.indentDocument?.(true);
+ else this._props.outdentDocument?.(true);
})}
- OnEmpty={undoBatch(() => this.props.treeView.outlineMode && this.props.removeDoc?.(this.doc))}
- OnFillDown={val => this.props.treeView.fileSysMode && this.makeFolder()}
+ OnEmpty={undoBatch(() => this.treeView.outlineMode && this._props.removeDoc?.(this.Document))}
+ OnFillDown={val => this.treeView.fileSysMode && this.makeFolder()}
SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => {
- Doc.SetInPlace(this.doc, 'title', value, false);
- this.props.treeView.outlineMode && enterKey && this.makeTextCollection();
+ Doc.SetInPlace(this.Document, 'title', value, false);
+ this.treeView.outlineMode && enterKey && this.makeTextCollection();
})}
/>
) : (
@@ -958,29 +965,29 @@ export class TreeView extends React.Component<TreeViewProps> {
key="title"
ref={action((r: any) => {
this._docRef = r ? r : undefined;
- if (this._docRef && TreeView._editTitleOnLoad?.id === this.props.document[Id] && TreeView._editTitleOnLoad.parent === this.props.parentTreeView) {
+ if (this._docRef && TreeView._editTitleOnLoad?.id === this.Document[Id] && TreeView._editTitleOnLoad.parent === this._props.parentTreeView) {
this._docRef.select(false);
this.setEditTitle(this._docRef);
TreeView._editTitleOnLoad = undefined;
}
})}
- Document={this.doc}
+ Document={this.Document}
layout_fitWidth={returnTrue}
scriptContext={this}
- hideDecorationTitle={this.props.treeView.outlineMode}
- hideResizeHandles={this.props.treeView.outlineMode}
+ hideDecorationTitle={this.treeView.outlineMode}
+ hideResizeHandles={this.treeView.outlineMode}
styleProvider={this.titleStyleProvider}
onClickScriptDisable="never" // tree docViews have a script to show fields, etc.
- docViewPath={this.props.treeView.props.docViewPath}
- treeViewDoc={this.props.treeView.props.Document}
+ docViewPath={this.treeView._props.docViewPath}
+ treeViewDoc={this.treeView.Document}
addDocument={undefined}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.treeView.props.pinToPres}
+ addDocTab={this._props.addDocTab}
+ pinToPres={this.treeView._props.pinToPres}
onClick={this.onChildClick}
onDoubleClick={this.onChildDoubleClick}
- dragAction={this.props.dragAction}
+ dragAction={this._props.dragAction}
moveDocument={this.move}
- removeDocument={this.props.removeDoc}
+ removeDocument={this._props.removeDoc}
ScreenToLocalTransform={this.getTransform}
NativeHeight={returnZero}
NativeWidth={returnZero}
@@ -988,16 +995,16 @@ export class TreeView extends React.Component<TreeViewProps> {
PanelHeight={return18}
contextMenuItems={this.contextMenuItems}
renderDepth={1}
- isContentActive={emptyFunction} //this.props.isContentActive}
- isDocumentActive={this.props.isContentActive}
+ isContentActive={emptyFunction} //this._props.isContentActive}
+ isDocumentActive={this._props.isContentActive}
focus={this.refocus}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+ whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
bringToFront={emptyFunction}
- disableBrushing={this.props.treeView.props.disableBrushing}
- hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)}
- dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)}
- xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)}
- yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)}
+ disableBrushing={this.treeView._props.disableBrushing}
+ hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)}
+ dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)}
+ xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)}
+ yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)}
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
@@ -1006,16 +1013,16 @@ export class TreeView extends React.Component<TreeViewProps> {
return (
<>
<div
- className={`docContainer${Doc.IsSystem(this.props.document) || this.props.document.isFolder ? '-system' : ''}`}
+ className={`docContainer${Doc.IsSystem(this.Document) || this.Document.isFolder ? '-system' : ''}`}
ref={this._tref}
title="click to edit title. Double Click or Drag to Open"
style={{
- backgroundColor: Doc.IsSystem(this.props.document) || this.props.document.isFolder ? SettingsManager.userVariantColor : undefined,
- color: Doc.IsSystem(this.props.document) || this.props.document.isFolder ? lightOrDark(SettingsManager.userVariantColor) : undefined,
- fontWeight: Doc.IsSearchMatch(this.doc) !== undefined ? 'bold' : undefined,
- textDecoration: Doc.GetT(this.doc, 'title', 'string', true) ? 'underline' : undefined,
- outline: this.doc === Doc.ActiveDashboard ? 'dashed 1px #06123232' : undefined,
- pointerEvents: !this.props.isContentActive() ? 'none' : undefined,
+ backgroundColor: Doc.IsSystem(this.Document) || this.Document.isFolder ? SettingsManager.userVariantColor : undefined,
+ color: Doc.IsSystem(this.Document) || this.Document.isFolder ? lightOrDark(SettingsManager.userVariantColor) : undefined,
+ fontWeight: Doc.IsSearchMatch(this.Document) !== undefined ? 'bold' : undefined,
+ textDecoration: Doc.GetT(this.Document, 'title', 'string', true) ? 'underline' : undefined,
+ outline: this.Document === Doc.ActiveDashboard ? 'dashed 1px #06123232' : undefined,
+ pointerEvents: !this._props.isContentActive() ? 'none' : undefined,
}}>
{view}
</div>
@@ -1055,42 +1062,42 @@ export class TreeView extends React.Component<TreeViewProps> {
return (
<div style={{ height: this.embeddedPanelHeight(), width: this.embeddedPanelWidth() }}>
<DocumentView
- key={this.doc[Id]}
+ key={this.Document[Id]}
ref={action((r: DocumentView | null) => (this._dref = r))}
- Document={this.doc}
+ Document={this.Document}
layout_fitWidth={this.fitWidthFilter}
PanelWidth={this.embeddedPanelWidth}
PanelHeight={this.embeddedPanelHeight}
LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined}
- LayoutTemplate={this.props.treeView.props.childLayoutTemplate}
+ LayoutTemplate={this.treeView._props.childLayoutTemplate}
isContentActive={isActive}
isDocumentActive={isActive}
styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
hideTitle={asText}
fitContentsToBox={returnTrue}
- hideDecorationTitle={this.props.treeView.outlineMode}
- hideResizeHandles={this.props.treeView.outlineMode}
+ hideDecorationTitle={this.treeView.outlineMode}
+ hideResizeHandles={this.treeView.outlineMode}
onClick={this.onChildClick}
focus={this.refocus}
onKey={this.onKeyDown}
- hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)}
- dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)}
+ hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)}
+ dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)}
ScreenToLocalTransform={this.docTransform}
- renderDepth={this.props.renderDepth + 1}
- treeViewDoc={this.props.treeView?.props.Document}
- docViewPath={this.props.treeView.props.docViewPath}
+ renderDepth={this._props.renderDepth + 1}
+ treeViewDoc={this.treeView?.Document}
+ docViewPath={this.treeView._props.docViewPath}
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
- addDocument={this.props.addDocument}
+ addDocument={this._props.addDocument}
moveDocument={this.move}
- removeDocument={this.props.removeDoc}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)}
- yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.treeView.props.pinToPres}
- disableBrushing={this.props.treeView.props.disableBrushing}
+ removeDocument={this._props.removeDoc}
+ whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+ xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)}
+ yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)}
+ addDocTab={this._props.addDocTab}
+ pinToPres={this.treeView._props.pinToPres}
+ disableBrushing={this.treeView._props.disableBrushing}
bringToFront={returnFalse}
scriptContext={this}
/>
@@ -1100,7 +1107,7 @@ 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 this.props.treeView.Document.treeView_HideUnrendered && this.doc.layout_unrendered && !this.doc.treeView_FieldKey ? (
+ return this.treeView.Document.treeView_HideUnrendered && this.Document.layout_unrendered && !this.Document.treeView_FieldKey ? (
<div></div>
) : (
<>
@@ -1115,16 +1122,16 @@ export class TreeView extends React.Component<TreeViewProps> {
return (
<>
{this.renderBullet}
- {this.renderEmbeddedDocument(asText, this.props.isContentActive)}
+ {this.renderEmbeddedDocument(asText, this._props.isContentActive)}
</>
);
};
@computed get renderBorder() {
- const sorting = StrCast(this.doc.treeView_SortCriterion, TreeSort.WhenAdded);
- const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) ?? {}) as { [key: string]: { color: string; label: string } };
+ const sorting = StrCast(this.Document.treeView_SortCriterion, TreeSort.WhenAdded);
+ const sortings = (this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewSortings) ?? {}) as { [key: string]: { color: string; label: string } };
return (
- <div className={`treeView-border${this.props.treeView.outlineMode ? TreeViewType.outline : ''}`} style={{ borderColor: sortings[sorting]?.color }}>
+ <div className={`treeView-border${this.treeView.outlineMode ? TreeViewType.outline : ''}`} style={{ borderColor: sortings[sorting]?.color }}>
{!this.treeViewOpen ? null : this.renderContent}
</div>
);
@@ -1134,28 +1141,28 @@ export class TreeView extends React.Component<TreeViewProps> {
const pt = [de.clientX, de.clientY];
const rect = this._header.current!.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
- const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
+ const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
- const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, undefined, false));
+ const docs = this.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, undefined, false));
};
render() {
TraceMobx();
- const hideTitle = this.doc.treeView_HideHeader || (this.doc.treeView_HideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode;
- return this.props.renderedIds?.indexOf(this.doc[Id]) !== -1 ? (
- '<' + this.doc.title + '>' // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles
+ const hideTitle = this.Document.treeView_HideHeader || (this.Document.treeView_HideHeaderIfTemplate && this.treeView._props.childLayoutTemplate?.()) || this.treeView.outlineMode;
+ return this._props.renderedIds?.indexOf(this.Document[Id]) !== -1 ? (
+ '<' + this.Document.title + '>' // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles
) : (
<div
- className={`treeView-container${this.props.isContentActive() ? '-active' : ''}`}
+ className={`treeView-container${this._props.isContentActive() ? '-active' : ''}`}
ref={this.createTreeDropTarget}
onDrop={this.onTreeDrop}
- //onPointerDown={e => this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document
+ //onPointerDown={e => this._props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document
// onKeyDown={this.onKeyDown}
>
<li className="collection-child">
- {hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeView_RenderAsBulletHeader // should test for prop 'treeView_RenderDocWithBulletAsHeader"
+ {hideTitle && this.Document.type !== DocumentType.RTF && !this.Document.treeView_RenderAsBulletHeader // should test for prop 'treeView_RenderDocWithBulletAsHeader"
? this.renderEmbeddedDocument(false, returnFalse)
- : this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.doc.treeView_RenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)}
+ : this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.Document.treeView_RenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)}
</li>
</div>
);
@@ -1231,7 +1238,7 @@ export class TreeView extends React.Component<TreeViewProps> {
}
const docs = TreeView.sortDocs(childDocs, StrCast(treeView_Parent.treeView_SortCriterion, TreeSort.WhenAdded));
- const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView.props.NativeDimScaling?.() || 1);
+ const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView._props.NativeDimScaling?.() || 1);
const treeView_Refs = new Map<Doc, TreeView | undefined>();
return docs
.filter(child => child instanceof Doc)
@@ -1243,7 +1250,7 @@ export class TreeView extends React.Component<TreeViewProps> {
}
const dentDoc = (editTitle: boolean, newParent: Doc, addAfter: Doc | undefined, parent: TreeView | CollectionTreeView | undefined) => {
- if (parent instanceof TreeView && parent.props.treeView.fileSysMode && !newParent.isFolder) return;
+ if (parent instanceof TreeView && parent._props.treeView.fileSysMode && !newParent.isFolder) return;
const fieldKey = Doc.LayoutFieldKey(newParent);
if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) {
remove(child);
@@ -1255,7 +1262,7 @@ export class TreeView extends React.Component<TreeViewProps> {
}
};
const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeView_Refs.get(docs[i - 1]));
- const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined);
+ const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView._props.parentTreeView : undefined);
const addDocument = (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false);
const childLayout = Doc.Layout(pair.layout);
const rowHeight = () => {
@@ -1266,7 +1273,7 @@ export class TreeView extends React.Component<TreeViewProps> {
<TreeView
key={child[Id]}
ref={r => treeView_Refs.set(child, r ? r : undefined)}
- document={pair.layout}
+ Document={pair.layout}
dataDoc={pair.data}
treeViewParent={treeView_Parent}
prevSibling={docs[i]}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
index 3ba7aedef..dd7e12e41 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
@@ -1,6 +1,7 @@
-import { IReactionDisposer, reaction } from 'mobx';
+import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { copyProps } from '../../../../Utils';
import './CollectionFreeFormView.scss';
/**
@@ -49,8 +50,16 @@ export interface CollectionFreeFormInfoStateProps {
export class CollectionFreeFormInfoState extends React.Component<CollectionFreeFormInfoStateProps> {
_disposers: IReactionDisposer[] = [];
+ _prevProps: React.PropsWithChildren<CollectionFreeFormInfoStateProps>;
+ @observable _props: React.PropsWithChildren<CollectionFreeFormInfoStateProps>;
+ constructor(props: React.PropsWithChildren<CollectionFreeFormInfoStateProps>) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+
get State() {
- return this.props.infoState;
+ return this._props.infoState;
}
get Arcs() {
return Object.keys(this.State).map(key => this.State[key]);
@@ -65,7 +74,7 @@ export class CollectionFreeFormInfoState extends React.Component<CollectionFreeF
res => {
if (res) {
const next = arc.act(res);
- this.props.next(next);
+ this._props.next(next);
}
},
{ fireImmediately: true }
@@ -76,6 +85,7 @@ export class CollectionFreeFormInfoState extends React.Component<CollectionFreeF
this.initState();
}
componentDidUpdate() {
+ copyProps(this);
this.clearState();
this.initState();
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
index 1265dc2de..f0a052c1d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -1,4 +1,4 @@
-import { IReactionDisposer, computed, observable, reaction, action, runInAction } from 'mobx';
+import { IReactionDisposer, computed, observable, reaction, action, runInAction, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../../../fields/Doc';
import { ScriptField } from '../../../../fields/ScriptField';
@@ -10,11 +10,12 @@ import { NumCast } from '../../../../fields/Types';
import { LinkManager } from '../../../util/LinkManager';
import { InkTool } from '../../../../fields/InkField';
import { LinkDocPreview } from '../../nodes/LinkDocPreview';
-import { DocumentLinksButton } from '../../nodes/DocumentLinksButton';
+import { DocumentLinksButton, DocButtonState } from '../../nodes/DocumentLinksButton';
import { DocumentManager } from '../../../util/DocumentManager';
import { CollectionFreeFormInfoState, infoState, StateMessage, infoArc, StateEntryFunc, InfoState } from './CollectionFreeFormInfoState';
import { string32 } from 'pdfjs-dist/types/src/shared/util';
import { any } from 'bluebird';
+import { copyProps } from '../../../../Utils';
export interface CollectionFreeFormInfoUIProps {
Document: Doc;
@@ -25,10 +26,23 @@ export interface CollectionFreeFormInfoUIProps {
export class CollectionFreeFormInfoUI extends React.Component<CollectionFreeFormInfoUIProps> {
private _disposers: { [name: string]: IReactionDisposer } = {};
- @observable currState!: infoState;
- constructor(props: any) {
+ @observable _currState: infoState | undefined = undefined;
+ get currState() {
+ return this._currState!;
+ }
+ set currState(val) {
+ this._currState = val;
+ }
+ _prevProps: React.PropsWithChildren<CollectionFreeFormInfoUIProps>;
+ @observable _props: React.PropsWithChildren<CollectionFreeFormInfoUIProps>;
+ constructor(props: React.PropsWithChildren<CollectionFreeFormInfoUIProps>) {
super(props);
- this.setCurrState(this.setupStates());
+ this._props = this._prevProps = this.props;
+ makeObservable(this);
+ this.currState = this.setupStates();
+ }
+ componentDidUpdate() {
+ copyProps(this);
}
setCurrState = (state: infoState) => {
@@ -40,12 +54,12 @@ export class CollectionFreeFormInfoUI extends React.Component<CollectionFreeForm
setupStates = () => {
// state entry functions
- const setBackground = (col: string) => () => (this.props.Freeform.layoutDoc.backgroundColor = col);
+ const setBackground = (col: string) => () => (this._props.Freeform.layoutDoc.backgroundColor = col);
// arc transition trigger conditions
- const firstDoc = () => this.props.Freeform.childDocs.lastElement();
- const numDocs = () => this.props.Freeform.childDocs.length;
+ const firstDoc = () => this._props.Freeform.childDocs.lastElement();
+ const numDocs = () => this._props.Freeform.childDocs.length;
const numDocLinks = () => LinkManager.Instance.getAllDirectLinks(firstDoc())?.length;
- const linkMenuOpen = () => DocumentLinksButton.LinkEditorDocView;
+ const linkMenuOpen = () => DocButtonState.Instance.LinkEditorDocView;
// set of states.
const start = InfoState('Click to create Object', {
@@ -78,7 +92,7 @@ export class CollectionFreeFormInfoUI extends React.Component<CollectionFreeForm
/*
componentDidMount(): void {
this._disposers.reaction1 = reaction(
- () => this.props.Freeform.childDocs.slice(),
+ () => this._props.Freeform.childDocs.slice(),
docs => {
if (docs.length === 1) {
this.firstDoc = docs[0];
@@ -162,8 +176,8 @@ export class CollectionFreeFormInfoUI extends React.Component<CollectionFreeForm
// this._disposers.reaction1();
@observable message = 'Click anywhere and begin typing to create your first document!';
- @observable firstDoc: Doc | undefined;
- @observable secondDoc: Doc | undefined;
+ @observable firstDoc: Doc | undefined = undefined;
+ @observable secondDoc: Doc | undefined = undefined;
*/
firstDocPos = { x: 0, y: 0 };
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 4995925c8..5cbea4783 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -171,8 +171,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
@action
toggleProperties = () => {
- if ((SettingsManager.propertiesWidth ?? 0) < 100) {
- SettingsManager.propertiesWidth = 250;
+ if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
+ SettingsManager.Instance.propertiesWidth = 250;
}
};
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 03d302f39..154c914ad 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
import { Bezier } from 'bezier-js';
import { Colors } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction, toJS } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import * as React from 'react';
@@ -16,7 +16,7 @@ import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../
import { ImageField } from '../../../../fields/URLField';
import { TraceMobx } from '../../../../fields/util';
import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, copyProps, DashColor, emptyFunction, intersectRect, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
import { Docs, DocUtils } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -43,7 +43,7 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { PinProps, PresBox } from '../../nodes/trails/PresBox';
import { CreateImage } from '../../nodes/WebBoxRenderer';
import { StyleProp } from '../../StyleProvider';
-import { CollectionSubView } from '../CollectionSubView';
+import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView';
import { TreeViewType } from '../CollectionTreeView';
import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid';
import { CollectionFreeFormInfoUI } from './CollectionFreeFormInfoUI';
@@ -69,9 +69,20 @@ export type collectionFreeformViewProps = {
@observer
export class CollectionFreeFormView extends CollectionSubView<Partial<collectionFreeformViewProps>>() {
public get displayName() {
- return 'CollectionFreeFormView(' + this.props.Document.title?.toString() + ')';
+ return 'CollectionFreeFormView(' + this._props.Document.title?.toString() + ')';
} // this makes mobx trace() statements more descriptive
+ _prevProps: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionFreeformViewProps>>;
+ @override _props: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionFreeformViewProps>>;
+ constructor(props: any) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
@observable
public static ShowPresPaths = false;
@@ -90,19 +101,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
private _brushtimer1: any;
public get isAnnotationOverlay() {
- return this.props.isAnnotationOverlay;
+ return this._props.isAnnotationOverlay;
}
public get scaleFieldKey() {
- return (this.props.viewField ?? '') + '_freeform_scale';
+ return (this._props.viewField ?? '') + '_freeform_scale';
}
private get panXFieldKey() {
- return (this.props.viewField ?? '') + '_freeform_panX';
+ return (this._props.viewField ?? '') + '_freeform_panX';
}
private get panYFieldKey() {
- return (this.props.viewField ?? '') + '_freeform_panY';
+ return (this._props.viewField ?? '') + '_freeform_panY';
}
private get autoResetFieldKey() {
- return (this.props.viewField ?? '') + '_freeform_autoReset';
+ return (this._props.viewField ?? '') + '_freeform_autoReset';
}
@observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
@@ -113,7 +124,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _deleteList: DocumentView[] = [];
@observable _timelineRef = React.createRef<Timeline>();
@observable _marqueeViewRef = React.createRef<MarqueeView>();
- @observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined; // highlighted region of freeform canvas used by presentations to indicate a region
+ @observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined = undefined; // highlighted region of freeform canvas used by presentations to indicate a region
@observable GroupChildDrag: boolean = false; // child document view being dragged. needed to update drop areas of groups when a group item is dragged.
@computed get contentViews() {
@@ -130,11 +141,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
scale:
(!this.childDocs.length || !Number.isFinite(hgt) || !Number.isFinite(wid)
? 1 //
- : Math.min(this.props.PanelHeight() / hgt, this.props.PanelWidth() / wid)) / (this.props.NativeDimScaling?.() || 1),
+ : Math.min(this._props.PanelHeight() / hgt, this._props.PanelWidth() / wid)) / (this._props.NativeDimScaling?.() || 1),
};
}
@computed get fitContentsToBox() {
- return (this.props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox) && !this.isAnnotationOverlay;
+ return (this._props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox) && !this.isAnnotationOverlay;
}
@computed get contentBounds() {
const cb = Cast(this.dataDoc.contentBounds, listSpec('number'));
@@ -147,30 +158,30 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
);
}
@computed get nativeWidth() {
- return this.props.NativeWidth?.() || Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+ return this._props.NativeWidth?.() || Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
}
@computed get nativeHeight() {
- return this.props.NativeHeight?.() || Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+ return this._props.NativeHeight?.() || Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
}
@computed get cachedCenteringShiftX(): number {
const scaling = !this.nativeDimScaling ? 1 : this.nativeDimScaling;
- return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+ return this._props.isAnnotationOverlay || this._props.originTopLeft ? 0 : this._props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
}
@computed get cachedCenteringShiftY(): number {
- const dv = this.props.DocumentView?.();
- const fitWidth = this.props.layout_fitWidth?.(this.Document) ?? dv?.layoutDoc.layout_fitWidth;
+ const dv = this._props.DocumentView?.();
+ const fitWidth = this._props.layout_fitWidth?.(this.Document) ?? dv?.layoutDoc.layout_fitWidth;
const scaling = !this.nativeDimScaling ? 1 : this.nativeDimScaling;
// if freeform has a native aspect, then the panel height needs to be adjusted to match it
- const aspect = dv?.nativeWidth && dv?.nativeHeight && !fitWidth ? dv.nativeHeight / dv.nativeWidth : this.props.PanelHeight() / this.props.PanelWidth();
- return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : (aspect * this.props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+ const aspect = dv?.nativeWidth && dv?.nativeHeight && !fitWidth ? dv.nativeHeight / dv.nativeWidth : this._props.PanelHeight() / this._props.PanelWidth();
+ return this._props.isAnnotationOverlay || this._props.originTopLeft ? 0 : (aspect * this._props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
}
@computed get panZoomXf() {
return new Transform(this.panX(), this.panY(), 1 / this.zoomScaling());
}
@computed get screenToLocalXf() {
- return this.props
+ return this._props
.ScreenToLocalTransform()
- .scale(this.props.isAnnotationOverlay ? 1 : 1)
+ .scale(this._props.isAnnotationOverlay ? 1 : 1)
.translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY)
.transform(this.panZoomXf);
}
@@ -219,9 +230,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _keyframeEditing = false;
@action setKeyFrameEditing = (set: boolean) => (this._keyframeEditing = set);
getKeyFrameEditing = () => this._keyframeEditing;
- onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick);
- onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
- onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
+ onBrowseClickHandler = () => this._props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick);
+ onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
+ onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
elementFunc = () => this._layoutElements;
fitContentOnce = () => {
const vals = this.fitToContentVals;
@@ -236,27 +247,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1));
zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1));
PanZoomCenterXf = () =>
- this.props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
+ this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
ScreenToLocalXf = () => this.screenToLocalXf.copy();
getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
- isAnyChildContentActive = () => this.props.isAnyChildContentActive();
+ isAnyChildContentActive = () => this._props.isAnyChildContentActive();
addLiveTextBox = (newDoc: Doc) => {
FormattedTextBox.SetSelectOnLoad(newDoc); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
this.addDocument(newDoc);
};
selectDocuments = (docs: Doc[]) => {
SelectionManager.DeselectAll();
- docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())).forEach(dv => dv && SelectionManager.SelectView(dv, true));
+ docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this._props.DocumentView?.())).forEach(dv => dv && SelectionManager.SelectView(dv, true));
};
addDocument = (newBox: Doc | Doc[]) => {
let retVal = false;
if (newBox instanceof Doc) {
- if ((retVal = this.props.addDocument?.(newBox) || false)) {
+ if ((retVal = this._props.addDocument?.(newBox) || false)) {
this.bringToFront(newBox);
this.updateCluster(newBox);
}
} else {
- retVal = this.props.addDocument?.(newBox) || false;
+ retVal = this._props.addDocument?.(newBox) || false;
// bcz: deal with clusters
}
if (retVal) {
@@ -270,7 +281,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
CollectionFreeFormDocumentView.animFields.forEach((field, i) => field.key !== 'opacity' && (newBox[field.key] = vals[i]));
}
}
- if (this.Document._currentFrame !== undefined && !this.props.isAnnotationOverlay) {
+ if (this.Document._currentFrame !== undefined && !this._props.isAnnotationOverlay) {
CollectionFreeFormDocumentView.setupKeyframes(newBoxes, NumCast(this.Document._currentFrame), true);
}
}
@@ -286,7 +297,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
groupFocus = (anchor: Doc, options: DocFocusOptions) => {
options.docTransform = new Transform(-NumCast(this.layoutDoc[this.panXFieldKey]) + NumCast(anchor.x), -NumCast(this.layoutDoc[this.panYFieldKey]) + NumCast(anchor.y), 1);
- const res = this.props.focus(this.Document, options);
+ const res = this._props.focus(this.Document, options);
options.docTransform = undefined;
return res;
};
@@ -324,11 +335,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
findDoc(dv => res(dv));
});
- @action
internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) {
if (!super.onInternalDrop(e, de)) return false;
const refDoc = docDragData.droppedDocuments[0];
- const [xpo, ypo] = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+ const [xpo, ypo] = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y);
const z = NumCast(refDoc.z);
const x = (z ? xpo : xp) - docDragData.offset[0];
const y = (z ? ypo : yp) - docDragData.offset[1];
@@ -343,7 +353,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
const d = docDragData.droppedDocuments[i];
const layoutDoc = Doc.Layout(d);
- const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], this.props.ScreenToLocalTransform().Rotate);
+ const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], this._props.ScreenToLocalTransform().Rotate);
if (this.Document._currentFrame !== undefined) {
CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false);
const pvals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); // get filled in values (uses defaults when not value is specified) for position
@@ -405,7 +415,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@undoBatch
internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) {
- if (linkDragData.linkDragView.props.docViewPath().includes(this.props.docViewPath().lastElement())) {
+ if (linkDragData.linkDragView.props.docViewPath().includes(this._props.docViewPath().lastElement())) {
let added = false;
// do nothing if link is dropped into any freeform view parent of dragged document
const source =
@@ -426,7 +436,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
_xPadding: 0,
onClick: FollowLinkScript(),
});
- added = this.props.addDocument?.(source) ? true : false;
+ added = this._props.addDocument?.(source) ? true : false;
de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
e.stopPropagation();
@@ -463,7 +473,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return this.childLayoutPairs
.map(pair => pair.layout)
.reduce((cluster, cd) => {
- const grouping = this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster, -1) : NumCast(cd.group, -1);
+ const grouping = this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster, -1) : NumCast(cd.group, -1);
if (grouping !== -1) {
const layoutDoc = Doc.Layout(cd);
const cx = NumCast(cd.x) - this._clusterDistance / 2;
@@ -480,11 +490,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (cluster !== -1) {
const ptsParent = e;
if (ptsParent) {
- const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
- const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.DocumentView?.())!);
+ const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
+ const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this._props.DocumentView?.())!);
const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'embed' : undefined);
- de.moveDocument = this.props.moveDocument;
+ de.moveDocument = this._props.moveDocument;
de.offset = this.screenToLocalXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
DragManager.StartDocumentDrag(
clusterDocs.map(v => v.ContentDiv!),
@@ -502,7 +512,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
updateClusters(_freeform_useClusters: boolean) {
- this.props.Document._freeform_useClusters = _freeform_useClusters;
+ this._props.Document._freeform_useClusters = _freeform_useClusters;
this._clusterSets.length = 0;
this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c));
}
@@ -510,7 +520,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
updateClusterDocs(docs: Doc[]) {
const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
- if (this.props.Document._freeform_useClusters) {
+ if (this._props.Document._freeform_useClusters) {
const docFirst = docs[0];
docs.map(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)));
const preferredInd = NumCast(docFirst.layout_cluster);
@@ -553,7 +563,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
updateCluster = (doc: Doc) => {
const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
- if (this.props.Document._freeform_useClusters) {
+ if (this._props.Document._freeform_useClusters) {
this._clusterSets.forEach(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
const preferredInd = NumCast(doc.layout_cluster);
doc.layout_cluster = -1;
@@ -583,7 +593,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
clusterStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => {
- let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1
+ let styleProp = this._props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1
if (doc && this.childDocList?.includes(doc))
switch (property) {
case StyleProp.BackgroundColor:
@@ -612,7 +622,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
trySelectCluster = (addToSel: boolean) => {
if (this._hitCluster !== -1) {
!addToSel && SelectionManager.DeselectAll();
- const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
+ const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
this.selectDocuments(eles);
return true;
}
@@ -625,7 +635,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._downY = this._lastY = e.pageY;
this._downTime = Date.now();
const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode;
- if (e.button === 0 && (!(e.ctrlKey && !e.metaKey) || scrollMode !== freeformScrollMode.Pan) && this.props.isContentActive(true)) {
+ if (e.button === 0 && (!(e.ctrlKey && !e.metaKey) || scrollMode !== freeformScrollMode.Pan) && this._props.isContentActive(true)) {
if (!this.Document.isGroup) {
// group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag
// prettier-ignore
@@ -638,7 +648,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, emptyFunction);
break;
case InkTool.None:
- if (!(this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
+ if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
this._hitCluster = this.pickCluster(this.screenToLocalXf.transformPoint(e.clientX, e.clientY));
setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false, false);
}
@@ -661,7 +671,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
case GestureUtils.Gestures.Stroke:
const points = ge.points;
const B = this.screenToLocalXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
- const inkWidth = ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale;
+ const inkWidth = ActiveInkWidth() * this._props.ScreenToLocalTransform().Scale;
const inkDoc = Docs.Create.InkDocument(
points,
{ title: ge.gesture.toString(),
@@ -710,7 +720,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (this._lightboxDoc) this._lightboxDoc = undefined;
if (Utils.isClick(e.pageX, e.pageY, this._downX, this._downY, this._downTime)) {
if (this.onBrowseClickHandler()) {
- this.onBrowseClickHandler().script.run({ documentView: this.props.DocumentView?.(), clientX: e.clientX, clientY: e.clientY });
+ this.onBrowseClickHandler().script.run({ documentView: this._props.DocumentView?.(), clientX: e.clientX, clientY: e.clientY });
e.stopPropagation();
e.preventDefault();
} else if (this.isContentActive() && e.shiftKey) {
@@ -733,9 +743,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const ctrlKey = e.ctrlKey && !e.shiftKey;
const shiftKey = e.shiftKey && !e.ctrlKey;
PresBox.Instance?.pauseAutoPres();
- this.props.DocumentView?.().clearViewTransition();
+ this._props.DocumentView?.().clearViewTransition();
const [dxi, dyi] = this.screenToLocalXf.transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
- const { x: dx, y: dy } = Utils.rotPt(dxi, dyi, this.props.ScreenToLocalTransform().Rotate);
+ const { x: dx, y: dy } = Utils.rotPt(dxi, dyi, this._props.ScreenToLocalTransform().Rotate);
this.setPan(NumCast(this.Document[this.panXFieldKey]) - (ctrlKey ? 0 : dx), NumCast(this.Document[this.panYFieldKey]) - (shiftKey ? 0 : dy), 0, true);
this._lastX = e.clientX;
this._lastY = e.clientY;
@@ -788,7 +798,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return true;
}
// pan the view if this is a regular collection, or it's an overlay and the overlay is zoomed (otherwise, there's nothing to pan)
- if (!this.props.isAnnotationOverlay || 1 - NumCast(this.layoutDoc._freeform_scale_min, 1) / this.zoomScaling()) {
+ if (!this._props.isAnnotationOverlay || 1 - NumCast(this.layoutDoc._freeform_scale_min, 1) / this.zoomScaling()) {
this.pan(e);
e.stopPropagation(); // if we are actually panning, stop propagation -- this will preven things like the overlayView from dragging the document while we're panning
}
@@ -804,7 +814,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) };
return this.childDocs
- .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.()))
+ .map(doc => DocumentManager.Instance.getDocumentView(doc, this._props.DocumentView?.()))
.filter(inkView => inkView?.ComponentView instanceof InkingStroke)
.map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
.filter(
@@ -901,7 +911,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.childDocs
.filter(doc => doc.type === DocumentType.INK && !doc.dontIntersect)
.forEach(doc => {
- const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())?.ComponentView as InkingStroke;
+ const otherInk = DocumentManager.Instance.getDocumentView(doc, this._props.DocumentView?.())?.ComponentView as InkingStroke;
const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point));
const otherCtrlPts = otherScreenPts.map(spt => (ink.ComponentView as InkingStroke).ptFromScreen(spt));
@@ -933,7 +943,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
zoom = (pointX: number, pointY: number, deltaY: number): void => {
- if (this.Document.isGroup || this.Document[(this.props.viewField ?? '_') + 'freeform_noZoom']) return;
+ if (this.Document.isGroup || this.Document[(this._props.viewField ?? '_') + 'freeform_noZoom']) return;
let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05;
if (deltaScale < 0) deltaScale = -deltaScale;
const [x, y] = this.screenToLocalXf.transformPoint(pointX, pointY);
@@ -955,8 +965,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const localTransform = invTransform.scaleAbout(deltaScale, x, y);
if (localTransform.Scale >= 0.05 || localTransform.Scale > this.zoomScaling()) {
const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20);
- this.props.Document[this.scaleFieldKey] = Math.abs(safeScale);
- this.setPan(-localTransform.TranslateX / safeScale, (this.props.originTopLeft ? undefined : NumCast(this.props.Document.layout_scrollTop) * safeScale) || -localTransform.TranslateY / safeScale);
+ this._props.Document[this.scaleFieldKey] = Math.abs(safeScale);
+ this.setPan(-localTransform.TranslateX / safeScale, (this._props.originTopLeft ? undefined : NumCast(this._props.Document.layout_scrollTop) * safeScale) || -localTransform.TranslateY / safeScale);
}
};
@@ -964,10 +974,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onPointerWheel = (e: React.WheelEvent): void => {
if (this.Document.isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
PresBox.Instance?.pauseAutoPres();
- if (this.layoutDoc._Transform || this.props.Document.treeView_OutlineMode === TreeViewType.outline) return;
+ if (this.layoutDoc._Transform || this._props.Document.treeView_OutlineMode === TreeViewType.outline) return;
e.stopPropagation();
const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight);
- const scrollable = this.isAnnotationOverlay && NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling + 1e-4;
+ const scrollable = this.isAnnotationOverlay && NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling + 1e-4;
switch (
!e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey ?//
Doc.UserDoc().freeformScrollMode : // no modifiers, do assigned mode
@@ -975,7 +985,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
freeformScrollMode.Zoom : freeformScrollMode.Pan // prettier-ignore
) {
case freeformScrollMode.Pan:
- if (((!e.metaKey && !e.altKey) || Doc.UserDoc().freeformScrollMode === freeformScrollMode.Zoom) && this.props.isContentActive(true)) {
+ if (((!e.metaKey && !e.altKey) || Doc.UserDoc().freeformScrollMode === freeformScrollMode.Zoom) && this._props.isContentActive(true)) {
const deltaX = e.shiftKey ? e.deltaX : e.ctrlKey ? 0 : e.deltaX;
const deltaY = e.shiftKey ? 0 : e.ctrlKey ? e.deltaY : e.deltaY;
this.scrollPan({ deltaX: -deltaX * this.screenToLocalXf.Scale, deltaY: e.shiftKey ? 0 : -deltaY * this.screenToLocalXf.Scale });
@@ -983,8 +993,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
default:
case freeformScrollMode.Zoom:
- if ((e.ctrlKey || !scrollable) && this.props.isContentActive(true)) {
- this.zoom(e.clientX, e.clientY, Math.max(-1, Math.min(1, e.deltaY))); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
+ if ((e.ctrlKey || !scrollable) && this._props.isContentActive(true)) {
+ this.zoom(e.clientX, e.clientY, Math.max(-1, Math.min(1, e.deltaY))); // if (!this._props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
e.preventDefault();
}
break;
@@ -1013,19 +1023,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) },
}),
{
- xrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
- yrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
+ xrange: { min: this._props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
+ yrange: { min: this._props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
}
);
- const scaling = this.zoomScaling() * (this.props.NativeDimScaling?.() || 1);
- const panelWidMax = (this.props.PanelWidth() / scaling) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
- const panelWidMin = (this.props.PanelWidth() / scaling) * (this.props.originTopLeft ? 0 : 1);
- const panelHgtMax = (this.props.PanelHeight() / scaling) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
- const panelHgtMin = (this.props.PanelHeight() / scaling) * (this.props.originTopLeft ? 0 : 1);
- if (ranges.xrange.min >= panX + panelWidMax / 2) panX = ranges.xrange.max + (this.props.originTopLeft ? 0 : panelWidMax / 2);
- else if (ranges.xrange.max <= panX - panelWidMin / 2) panX = ranges.xrange.min - (this.props.originTopLeft ? panelWidMax / 2 : panelWidMin / 2);
- if (ranges.yrange.min >= panY + panelHgtMax / 2) panY = ranges.yrange.max + (this.props.originTopLeft ? 0 : panelHgtMax / 2);
- else if (ranges.yrange.max <= panY - panelHgtMin / 2) panY = ranges.yrange.min - (this.props.originTopLeft ? panelHgtMax / 2 : panelHgtMin / 2);
+ const scaling = this.zoomScaling() * (this._props.NativeDimScaling?.() || 1);
+ const panelWidMax = (this._props.PanelWidth() / scaling) * (this._props.originTopLeft ? 2 / this.nativeDimScaling : 1);
+ const panelWidMin = (this._props.PanelWidth() / scaling) * (this._props.originTopLeft ? 0 : 1);
+ const panelHgtMax = (this._props.PanelHeight() / scaling) * (this._props.originTopLeft ? 2 / this.nativeDimScaling : 1);
+ const panelHgtMin = (this._props.PanelHeight() / scaling) * (this._props.originTopLeft ? 0 : 1);
+ if (ranges.xrange.min >= panX + panelWidMax / 2) panX = ranges.xrange.max + (this._props.originTopLeft ? 0 : panelWidMax / 2);
+ else if (ranges.xrange.max <= panX - panelWidMin / 2) panX = ranges.xrange.min - (this._props.originTopLeft ? panelWidMax / 2 : panelWidMin / 2);
+ if (ranges.yrange.min >= panY + panelHgtMax / 2) panY = ranges.yrange.max + (this._props.originTopLeft ? 0 : panelHgtMax / 2);
+ else if (ranges.yrange.max <= panY - panelHgtMin / 2) panY = ranges.yrange.min - (this._props.originTopLeft ? panelHgtMax / 2 : panelHgtMin / 2);
}
}
if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc) {
@@ -1036,13 +1046,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const minPanY = NumCast(this.dataDoc._freeform_panY_min, 0);
const maxPanX = NumCast(this.dataDoc._freeform_panX_max, this.nativeWidth);
const newPanX = Math.min(minPanX + scale * maxPanX, Math.max(minPanX, panX));
- const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this.props.PanelWidth() - this.props.PanelHeight()) * this.props.ScreenToLocalTransform().Scale) / minScale;
- const nativeHeight = (this.props.PanelHeight() / this.props.PanelWidth() / (this.nativeHeight / this.nativeWidth)) * this.nativeHeight;
- const maxScrollTop = this.nativeHeight / this.props.ScreenToLocalTransform().Scale - this.props.PanelHeight();
+ const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this._props.PanelWidth() - this._props.PanelHeight()) * this._props.ScreenToLocalTransform().Scale) / minScale;
+ const nativeHeight = (this._props.PanelHeight() / this._props.PanelWidth() / (this.nativeHeight / this.nativeWidth)) * this.nativeHeight;
+ const maxScrollTop = this.nativeHeight / this._props.ScreenToLocalTransform().Scale - this._props.PanelHeight();
const maxPanY =
minPanY + // minPanY + scrolling introduced by view scaling + scrolling introduced by layout_fitWidth
scale * NumCast(this.dataDoc._panY_max, nativeHeight) +
- (!this.props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning
+ (!this._props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning
let newPanY = Math.max(minPanY, Math.min(maxPanY, panY));
if (false && NumCast(this.layoutDoc.layout_scrollTop) && NumCast(this.layoutDoc._freeform_scale, minScale) !== minScale) {
const relTop = NumCast(this.layoutDoc.layout_scrollTop) / maxScrollTop;
@@ -1061,11 +1071,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
nudge = (x: number, y: number, nudgeTime: number = 500) => {
- const collectionDoc = this.props.docViewPath().lastElement().Document;
+ const collectionDoc = this._props.docViewPath().lastElement().Document;
if (collectionDoc?._type_collection !== CollectionViewType.Freeform) {
this.setPan(
- NumCast(this.layoutDoc[this.panXFieldKey]) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
- NumCast(this.layoutDoc[this.panYFieldKey]) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(),
+ NumCast(this.layoutDoc[this.panXFieldKey]) + ((this._props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
+ NumCast(this.layoutDoc[this.panYFieldKey]) + ((this._props.PanelHeight() / 2) * -y) / this.zoomScaling(),
nudgeTime,
true
);
@@ -1132,20 +1142,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const newScale =
scale === 0
? NumCast(this.layoutDoc[this.scaleFieldKey])
- : Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
+ : Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this._props.PanelWidth() / Math.abs(bounds.width), this._props.PanelHeight() / Math.abs(bounds.height)));
return {
- panX: this.props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2,
- panY: this.props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2,
+ panX: this._props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2,
+ panY: this._props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2,
scale: newScale,
};
}
- const panelWidth = this.props.isAnnotationOverlay ? this.nativeWidth : this.props.PanelWidth();
- const panelHeight = this.props.isAnnotationOverlay ? this.nativeHeight : this.props.PanelHeight();
+ const panelWidth = this._props.isAnnotationOverlay ? this.nativeWidth : this._props.PanelWidth();
+ const panelHeight = this._props.isAnnotationOverlay ? this.nativeHeight : this._props.PanelHeight();
const pw = panelWidth / NumCast(this.layoutDoc._freeform_scale, 1);
const ph = panelHeight / NumCast(this.layoutDoc._freeform_scale, 1);
- const cx = NumCast(this.layoutDoc[this.panXFieldKey]) + (this.props.isAnnotationOverlay ? pw / 2 : 0);
- const cy = NumCast(this.layoutDoc[this.panYFieldKey]) + (this.props.isAnnotationOverlay ? ph / 2 : 0);
+ const cx = NumCast(this.layoutDoc[this.panXFieldKey]) + (this._props.isAnnotationOverlay ? pw / 2 : 0);
+ const cy = NumCast(this.layoutDoc[this.panYFieldKey]) + (this._props.isAnnotationOverlay ? ph / 2 : 0);
const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
const maxYShift = Math.max(0, screen.bot - screen.top - (bounds.bot - bounds.top));
const phborder = bounds.top < screen.top || bounds.bot > screen.bot ? Math.min(ph / 10, maxYShift / 2) : 0;
@@ -1153,19 +1163,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return {
panX: (bounds.left + bounds.right) / 2,
panY: (bounds.top + bounds.bot) / 2,
- scale: Math.min(this.props.PanelHeight() / (bounds.bot - bounds.top), this.props.PanelWidth() / (bounds.right - bounds.left)) / 1.1,
+ scale: Math.min(this._props.PanelHeight() / (bounds.bot - bounds.top), this._props.PanelWidth() / (bounds.right - bounds.left)) / 1.1,
};
}
return {
- panX: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panXFieldKey]) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
- panY: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panYFieldKey]) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot),
+ panX: (this._props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panXFieldKey]) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
+ panY: (this._props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panYFieldKey]) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot),
};
};
- isContentActive = () => this.props.isContentActive();
+ isContentActive = () => this._props.isContentActive();
@undoBatch
- @action
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
const docView = fieldProps.DocumentView?.();
if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
@@ -1186,21 +1195,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
};
@computed get childPointerEvents() {
- const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
+ const engine = this._props.layoutEngine?.() || StrCast(this._props.Document._layoutEngine);
const pointerevents = DocumentView.Interacting
? 'none'
- : this.props.childPointerEvents?.() ??
- (this.props.viewDefDivClick || //
- (engine === computePassLayout.name && !this.props.isSelected()) ||
+ : this._props.childPointerEvents?.() ??
+ (this._props.viewDefDivClick || //
+ (engine === computePassLayout.name && !this._props.isSelected()) ||
this.isContentActive() === false
? 'none'
- : this.props.pointerEvents?.());
+ : this._props.pointerEvents?.());
return pointerevents;
}
- @observable _childPointerEvents: 'none' | 'all' | 'visiblepainted' | undefined;
+ @observable _childPointerEvents: 'none' | 'all' | 'visiblepainted' | undefined = undefined;
childPointerEventsFunc = () => this._childPointerEvents;
- childContentsActive = () => (this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)();
+ childContentsActive = () => (this._props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)();
getChildDocView(entry: PoolData) {
const childLayout = entry.pair.layout;
const childData = entry.pair.data;
@@ -1212,50 +1221,50 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
TemplateDataDocument={childData}
dragStarting={this.dragStarting}
dragEnding={this.dragEnding}
- isGroupActive={this.props.isGroupActive}
- renderDepth={this.props.renderDepth + 1}
+ isGroupActive={this._props.isGroupActive}
+ renderDepth={this._props.renderDepth + 1}
hideDecorations={BoolCast(childLayout._layout_isSvg && childLayout.type === DocumentType.LINK)}
suppressSetHeight={this.layoutEngine ? true : false}
RenderCutoffProvider={this.renderCutoffProvider}
CollectionFreeFormView={this}
- LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate}
- LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString}
+ LayoutTemplate={childLayout.z ? undefined : this._props.childLayoutTemplate}
+ LayoutTemplateString={childLayout.z ? undefined : this._props.childLayoutString}
rootSelected={childData ? this.rootSelected : returnFalse}
- waitForDoubleClickToClick={this.props.waitForDoubleClickToClick}
+ waitForDoubleClickToClick={this._props.waitForDoubleClickToClick}
onClick={this.onChildClickHandler}
onKey={this.onKeyDown}
onDoubleClick={this.onChildDoubleClickHandler}
onBrowseClick={this.onBrowseClickHandler}
- ScreenToLocalTransform={childLayout.z ? this.props.ScreenToLocalTransform : this.ScreenToLocalXf}
+ ScreenToLocalTransform={childLayout.z ? this._props.ScreenToLocalTransform : this.ScreenToLocalXf}
PanelWidth={childLayout[Width]}
PanelHeight={childLayout[Height]}
childFilters={this.childDocFilters}
childFiltersByRanges={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
- isDocumentActive={childLayout.pointerEvents === 'none' ? returnFalse : this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
+ isDocumentActive={childLayout.pointerEvents === 'none' ? returnFalse : this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this.isContentActive}
isContentActive={this.childContentsActive}
- focus={this.Document.isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus}
+ focus={this.Document.isGroup ? this.groupFocus : this.isAnnotationOverlay ? this._props.focus : this.focus}
addDocTab={this.addDocTab}
- addDocument={this.props.addDocument}
- removeDocument={this.props.removeDocument}
- moveDocument={this.props.moveDocument}
- pinToPres={this.props.pinToPres}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- docViewPath={this.props.docViewPath}
+ addDocument={this._props.addDocument}
+ removeDocument={this._props.removeDocument}
+ moveDocument={this._props.moveDocument}
+ pinToPres={this._props.pinToPres}
+ whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+ docViewPath={this._props.docViewPath}
styleProvider={this.clusterStyleProvider}
- dragAction={(this.Document.childDragAction ?? this.props.childDragAction) as dropActionType}
+ dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
bringToFront={this.bringToFront}
- layout_showTitle={this.props.childlayout_showTitle}
- dontRegisterView={this.props.dontRegisterView}
+ layout_showTitle={this._props.childlayout_showTitle}
+ dontRegisterView={this._props.dontRegisterView}
pointerEvents={this.childPointerEventsFunc}
/>
);
}
addDocTab = action((doc: Doc, where: OpenWhere) => {
- if (this.props.isAnnotationOverlay) return this.props.addDocTab(doc, where);
+ if (this._props.isAnnotationOverlay) return this._props.addDocTab(doc, where);
switch (where) {
case OpenWhere.inParent:
- return this.props.addDocument?.(doc) || false;
+ return this._props.addDocument?.(doc) || false;
case OpenWhere.inParentFromScreen:
const docContext = DocCast((doc instanceof Doc ? doc : doc?.[0])?.embedContainer);
return (
@@ -1267,7 +1276,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return doc;
})
) &&
- (!docContext || this.props.removeDocument?.(docContext))) ||
+ (!docContext || this._props.removeDocument?.(docContext))) ||
false
);
case undefined:
@@ -1281,9 +1290,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return true;
}
}
- return this.props.addDocTab(doc, where);
+ return this._props.addDocTab(doc, where);
});
- @observable _lightboxDoc: Opt<Doc>;
+ @observable _lightboxDoc: Opt<Doc> = undefined;
getCalculatedPositions(pair: { layout: Doc; data?: Doc }): PoolData {
const random = (min: number, max: number, x: number, y: number) => /* min should not be equal to max */ min + (((Math.abs(x * y) * 9301 + 49297) % 233280) / 233280) * (max - min);
@@ -1295,7 +1304,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const { backgroundColor, color } = contentFrameNumber === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, contentFrameNumber);
const { x, y, autoDim, _width, _height, opacity, _rotation } =
layoutFrameNumber === undefined // -1 for width/height means width/height should be PanelWidth/PanelHeight (prevents collectionfreeformdocumentview width/height from getting out of synch with panelWIdth/Height which causes detailView to re-render and lose focus because HTMLtag scaling gets set to a bad intermediate value)
- ? { autoDim: 1, _width: Cast(childDoc._width, 'number'), _height: Cast(childDoc._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() }
+ ? { autoDim: 1, _width: Cast(childDoc._width, 'number'), _height: Cast(childDoc._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this._props.childOpacity?.() }
: CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber);
// prettier-ignore
const rotation = Cast(_rotation,'number',
@@ -1307,9 +1316,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
z: Cast(z, 'number'),
autoDim,
rotation,
- color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color),
- backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, this.props, StyleProp.BackgroundColor),
- opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
+ color: Cast(color, 'string') ? StrCast(color) : this._props.styleProvider?.(childDoc, this._props, StyleProp.Color),
+ backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, this._props, StyleProp.BackgroundColor),
+ opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this._props.styleProvider?.(childDoc, this._props, StyleProp.Opacity),
zIndex: Cast(zIndex, 'number'),
width: _width,
height: _height,
@@ -1321,7 +1330,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
onViewDefDivClick = (e: React.MouseEvent, payload: any) => {
- (this.props.viewDefDivClick || ScriptCast(this.props.Document.onViewDefDivClick))?.script.run({ this: this.props.Document, payload });
+ (this._props.viewDefDivClick || ScriptCast(this._props.Document.onViewDefDivClick))?.script.run({ this: this._props.Document, payload });
e.stopPropagation();
};
@@ -1376,7 +1385,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
poolData: Map<string, PoolData>,
engine: (poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) => ViewDefResult[]
) {
- return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX, this.props.engineProps);
+ return engine(poolData, this._props.Document, this.childLayoutPairs, [this._props.PanelWidth(), this._props.PanelHeight()], this.viewDefsToJSX, this._props.engineProps);
}
doFreeformLayout(poolData: Map<string, PoolData>) {
@@ -1385,7 +1394,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
@computed get layoutEngine() {
- return this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine);
+ return this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine);
}
@computed get doInternalLayoutComputation() {
TraceMobx();
@@ -1423,10 +1432,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document.isGroup, type_collection: true, filters: true } }, this.Document);
if (addAsAnnotation) {
- if (Cast(this.dataDoc[this.props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) {
- Cast(this.dataDoc[this.props.fieldKey + '_annotations'], listSpec(Doc), []).push(anchor);
+ if (Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) {
+ Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), []).push(anchor);
} else {
- this.dataDoc[this.props.fieldKey + '_annotations'] = new List<Doc>([anchor]);
+ this.dataDoc[this._props.fieldKey + '_annotations'] = new List<Doc>([anchor]);
}
}
return anchor;
@@ -1435,7 +1444,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
infoUI = () => <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} />;
componentDidMount() {
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
super.componentDidMount?.();
setTimeout(
action(() => {
@@ -1473,15 +1482,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._disposers.pointerevents = reaction(
() => {
- const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
+ const engine = this._props.layoutEngine?.() || StrCast(this._props.Document._layoutEngine);
return DocumentView.Interacting
? 'none'
- : this.props.childPointerEvents?.() ??
- (this.props.viewDefDivClick || //
- (engine === computePassLayout.name && !this.props.isSelected()) ||
+ : this._props.childPointerEvents?.() ??
+ (this._props.viewDefDivClick || //
+ (engine === computePassLayout.name && !this._props.isSelected()) ||
this.isContentActive() === false
? 'none'
- : this.props.pointerEvents?.());
+ : this._props.pointerEvents?.());
},
pointerevents => (this._childPointerEvents = pointerevents as any),
{ fireImmediately: true }
@@ -1536,11 +1545,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
updateIcon = () =>
CollectionFreeFormView.UpdateIcon(
this.layoutDoc[Id] + '-icon' + new Date().getTime(),
- this.props.docViewPath().lastElement().ContentDiv!,
+ this._props.docViewPath().lastElement().ContentDiv!,
NumCast(this.layoutDoc._width),
NumCast(this.layoutDoc._height),
- this.props.PanelWidth(),
- this.props.PanelHeight(),
+ this._props.PanelWidth(),
+ this._props.PanelHeight(),
0,
1,
false,
@@ -1600,7 +1609,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
doc.x = scr?.[0];
doc.y = scr?.[1];
});
- this.props.addDocTab(childDocs as any as Doc, OpenWhere.inParentFromScreen);
+ this._props.addDocTab(childDocs as any as Doc, OpenWhere.inParentFromScreen);
};
@undoBatch
@@ -1643,28 +1652,28 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
onContextMenu = (e: React.MouseEvent) => {
- if (this.props.isAnnotationOverlay || !ContextMenu.Instance) return;
+ if (this._props.isAnnotationOverlay || !ContextMenu.Instance) return;
const appearance = ContextMenu.Instance.findByDescription('Appearance...');
const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : [];
- !this.props.Document.isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' });
- !this.props.Document.isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' });
+ !this._props.Document.isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' });
+ !this._props.Document.isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' });
!Doc.noviceMode &&
appearanceItems.push({
description: 'Toggle auto arrange',
event: () => (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange),
icon: 'compress-arrows-alt',
});
- if (this.props.setContentView === emptyFunction) {
+ if (this._props.setContentView === emptyFunction) {
!appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
return;
}
!Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' });
- appearanceItems.push({ description: `Pin View`, event: () => this.props.pinToPres(this.Document, { pinViewport: MarqueeView.CurViewBounds(this.dataDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' });
+ appearanceItems.push({ description: `Pin View`, event: () => this._props.pinToPres(this.Document, { pinViewport: MarqueeView.CurViewBounds(this.dataDoc, this._props.PanelWidth(), this._props.PanelHeight()) }), icon: 'map-pin' });
!Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' });
- this.props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
+ this._props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
- this.props.Document.isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: this.transcribeStrokes, icon: 'font' });
+ this._props.Document.isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: this.transcribeStrokes, icon: 'font' });
!Doc.noviceMode ? appearanceItems.push({ description: 'Arrange contents in grid', event: this.layoutDocsInGrid, icon: 'table' }) : null;
!Doc.noviceMode ? appearanceItems.push({ description: (this.Document._freeform_useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._freeform_useClusters), icon: 'braille' }) : null;
@@ -1672,11 +1681,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const options = ContextMenu.Instance.findByDescription('Options...');
const optionItems = options && 'subitems' in options ? options.subitems : [];
- !this.props.isAnnotationOverlay &&
+ !this._props.isAnnotationOverlay &&
!Doc.noviceMode &&
optionItems.push({ description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline', event: action(() => (this._showAnimTimeline = !this._showAnimTimeline)), icon: 'eye' });
- this.props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor)), icon: 'palette' });
- this.props.renderDepth && optionItems.push({ description: 'Fit Content Once', event: this.fitContentOnce, icon: 'object-group' });
+ this._props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor)), icon: 'palette' });
+ this._props.renderDepth && optionItems.push({ description: 'Fit Content Once', event: this.fitContentOnce, icon: 'object-group' });
if (!Doc.noviceMode) {
optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? 'Freeze' : 'Unfreeze') + ' Aspect', event: this.toggleNativeDimensions, icon: 'snowflake' });
}
@@ -1687,10 +1696,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
@undoBatch
- @action
transcribeStrokes = () => {
- if (this.props.Document.isGroup && this.props.Document.transcription) {
- const text = StrCast(this.props.Document.transcription);
+ if (this._props.Document.isGroup && this._props.Document.transcription) {
+ const text = StrCast(this._props.Document.transcription);
const lines = text.split('\n');
const height = 30 + 15 * lines.length;
@@ -1709,7 +1717,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
visited.add(this.Document);
showGroupDragTarget && (this.GroupChildDrag = BoolCast(this.Document.isGroup));
const activeDocs = this.getActiveDocuments();
- const size = this.screenToLocalXf.transformDirection(this.props.PanelWidth(), this.props.PanelHeight());
+ const size = this.screenToLocalXf.transformDirection(this._props.PanelWidth(), this._props.PanelHeight());
const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] };
const docDims = (doc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) });
const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect);
@@ -1738,7 +1746,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
incrementalRendering = () => this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])).length !== 0;
incrementalRender = action(() => {
- if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
+ if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath())) {
const layout_unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
const loadIncrement = 5;
for (var i = 0; i < Math.min(layout_unrendered.length, loadIncrement); i++) {
@@ -1752,28 +1760,28 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// which will be a DocumentView. In this sitation, this freeform views acts as an annotation overlay for
// the underlying DocumentView and will pan and scoll with the underlying Documen tView.
@computed get underlayViews() {
- return this.props.children ? [this.props.children] : [];
+ return this._props.children ? [toJS(this._props.children)] : [];
}
@computed get placeholder() {
return (
- <div className="collectionfreeformview-placeholder" style={{ background: this.props.styleProvider?.(this.Document, this.props, StyleProp.BackgroundColor) }}>
- <span className="collectionfreeformview-placeholderSpan">{this.props.Document.annotationOn ? '' : this.props.Document.title?.toString()}</span>
+ <div className="collectionfreeformview-placeholder" style={{ background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) }}>
+ <span className="collectionfreeformview-placeholderSpan">{this._props.Document.annotationOn ? '' : this._props.Document.title?.toString()}</span>
</div>
);
}
brushedView = () => this._brushedView;
gridColor = () =>
- DashColor(lightOrDark(this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor)))
+ DashColor(lightOrDark(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor)))
.fade(0.5)
.toString();
@computed get backgroundGrid() {
return (
<div>
<CollectionFreeFormBackgroundGrid // bcz : UGHH don't know why, but if we don't wrap in a div, then PDF's don't render when taking snapshot of a dashboard and the background grid is on!!?
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.props.PanelHeight}
+ PanelWidth={this._props.PanelWidth}
+ PanelHeight={this._props.PanelHeight}
panX={this.panX}
panY={this.panY}
color={this.gridColor}
@@ -1795,11 +1803,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
brushedView={this.brushedView}
isAnnotationOverlay={this.isAnnotationOverlay}
transform={this.PanZoomCenterXf}
- transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.Document._viewTransition, 'string', null))}
- viewDefDivClick={this.props.viewDefDivClick}>
+ transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this._props.DocumentView?.()?.Document._viewTransition, 'string', null))}
+ viewDefDivClick={this._props.viewDefDivClick}>
{this.underlayViews}
{this.contentViews}
- <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
+ <CollectionFreeFormRemoteCursors {...this._props} key="remoteCursors" />
</CollectionFreeFormPannableContents>
);
}
@@ -1807,10 +1815,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
TraceMobx();
return (
<MarqueeView
- {...this.props}
+ {...this._props}
ref={this._marqueeViewRef}
ungroup={this.Document.isGroup ? this.promoteCollection : undefined}
- nudge={this.isAnnotationOverlay || this.props.renderDepth > 0 ? undefined : this.nudge}
+ nudge={this.isAnnotationOverlay || this._props.renderDepth > 0 ? undefined : this.nudge}
addDocTab={this.addDocTab}
slowLoadDocuments={this.slowLoadDocuments}
trySelectCluster={this.trySelectCluster}
@@ -1818,25 +1826,25 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
selectDocuments={this.selectDocuments}
addDocument={this.addDocument}
addLiveTextDocument={this.addLiveTextBox}
- getContainerTransform={this.props.ScreenToLocalTransform}
+ getContainerTransform={this._props.ScreenToLocalTransform}
getTransform={this.ScreenToLocalXf}
panXFieldKey={this.panXFieldKey}
panYFieldKey={this.panYFieldKey}
isAnnotationOverlay={this.isAnnotationOverlay}>
{this.layoutDoc._freeform_backgroundGrid ? this.backgroundGrid : null}
{this.pannableContents}
- {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this.props} /> : null}
+ {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this._props} /> : null}
</MarqueeView>
);
}
@computed get nativeDimScaling() {
- if (this._firstRender || (this.props.isAnnotationOverlay && !this.props.annotationLayerHostsContent)) return 0;
+ if (this._firstRender || (this._props.isAnnotationOverlay && !this._props.annotationLayerHostsContent)) return 0;
const nw = this.nativeWidth;
const nh = this.nativeHeight;
- const hscale = nh ? this.props.PanelHeight() / nh : 1;
- const wscale = nw ? this.props.PanelWidth() / nw : 1;
- return wscale < hscale || (this.props.layout_fitWidth?.(this.Document) ?? this.layoutDoc.layout_fitWidth) ? wscale : hscale;
+ const hscale = nh ? this._props.PanelHeight() / nh : 1;
+ const wscale = nw ? this._props.PanelWidth() / nw : 1;
+ return wscale < hscale || (this._props.layout_fitWidth?.(this.Document) ?? this.layoutDoc.layout_fitWidth) ? wscale : hscale;
}
nativeDim = () => this.nativeDimScaling;
@@ -1853,13 +1861,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
transTime + 1
);
};
- lightboxPanelWidth = () => Math.max(0, this.props.PanelWidth() - 30);
- lightboxPanelHeight = () => Math.max(0, this.props.PanelHeight() - 30);
- lightboxScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-15, -15);
+ lightboxPanelWidth = () => Math.max(0, this._props.PanelWidth() - 30);
+ lightboxPanelHeight = () => Math.max(0, this._props.PanelHeight() - 30);
+ lightboxScreenToLocal = () => this._props.ScreenToLocalTransform().translate(-15, -15);
onPassiveWheel = (e: WheelEvent) => {
const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight);
- const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling;
- this.props.isSelected() && !scrollable && e.preventDefault();
+ const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling;
+ this._props.isSelected() && !scrollable && e.preventDefault();
};
_oldWheel: any;
render() {
@@ -1882,16 +1890,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onDragOver={e => e.preventDefault()}
onContextMenu={this.onContextMenu}
style={{
- pointerEvents: this.props.isContentActive() && SnappingManager.GetIsDragging() ? 'all' : (this.props.pointerEvents?.() as any),
+ pointerEvents: this._props.isContentActive() && SnappingManager.GetIsDragging() ? 'all' : (this._props.pointerEvents?.() as any),
textAlign: this.isAnnotationOverlay ? 'initial' : undefined,
transform: `scale(${this.nativeDimScaling || 1})`,
width: `${100 / (this.nativeDimScaling || 1)}%`,
- height: this.props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`,
+ height: this._props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`,
}}>
{this._lightboxDoc ? (
<div style={{ padding: 15, width: '100%', height: '100%' }}>
<DocumentView
- {...this.props}
+ {...this._props}
Document={this._lightboxDoc}
TemplateDataDocument={undefined}
PanelWidth={this.lightboxPanelWidth}
@@ -1905,8 +1913,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
childFilters={this.childDocFilters}
childFiltersByRanges={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
- isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
- isContentActive={this.props.childContentsActive ?? emptyFunction}
+ isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this.isContentActive}
+ isContentActive={this._props.childContentsActive ?? emptyFunction}
addDocTab={this.addDocTab}
ScreenToLocalTransform={this.lightboxScreenToLocal}
fitContentsToBox={undefined}
@@ -1916,7 +1924,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
) : (
<>
{this._firstRender ? this.placeholder : this.marqueeView}
- {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
+ {this._props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
{!this.GroupChildDrag ? null : <div className="collectionFreeForm-groupDropper" />}
</>
)}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 330c13e97..9f316bef3 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, Opt } from '../../../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols';
@@ -57,6 +57,13 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
return { left: NumCast(pinDoc._freeform_panX) - panelWidth / 2 / ps, top: NumCast(pinDoc._freeform_panY) - panelHeight / 2 / ps, width: panelWidth / ps, height: panelHeight / ps };
}
+ @observable _props: React.PropsWithChildren<SubCollectionViewProps & MarqueeViewProps>;
+ constructor(props: React.PropsWithChildren<SubCollectionViewProps & MarqueeViewProps>) {
+ super(props);
+ this._props = props;
+ makeObservable(this);
+ }
+
private _commandExecuted = false;
@observable _lastX: number = 0;
@observable _lastY: number = 0;
@@ -67,7 +74,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@observable _lassoFreehand: boolean = false;
@computed get Transform() {
- return this.props.getTransform();
+ return this._props.getTransform();
}
@computed get Bounds() {
// nda - ternary argument to transformPoint is returning the lower of the downX/Y and lastX/Y and passing in as args x,y
@@ -79,7 +86,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
componentDidMount() {
- this.props.setPreviewCursor?.(this.setPreviewCursor);
+ this._props.setPreviewCursor?.(this.setPreviewCursor);
}
@action
@@ -101,20 +108,20 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const cm = ContextMenu.Instance;
const [x, y] = this.Transform.transformPoint(this._downX, this._downY);
if (e.key === '?') {
- cm.setDefaultItem('?', (str: string) => this.props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', data_useCors: true }), OpenWhere.addRight));
+ cm.setDefaultItem('?', (str: string) => this._props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', data_useCors: true }), OpenWhere.addRight));
cm.displayMenu(this._downX, this._downY, undefined, true);
e.stopPropagation();
- } else if (e.key === 'u' && this.props.ungroup) {
+ } else if (e.key === 'u' && this._props.ungroup) {
e.stopPropagation();
- this.props.ungroup();
+ this._props.ungroup();
} else if (e.key === ':') {
- DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument || returnFalse, x, y);
+ DocUtils.addDocumentCreatorMenuItems(this._props.addLiveTextDocument, this._props.addDocument || returnFalse, x, y);
cm.displayMenu(this._downX, this._downY, undefined, true);
e.stopPropagation();
} else if (e.key === 'a' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
- this.props.selectDocuments(this.props.activeDocuments());
+ this._props.selectDocuments(this._props.activeDocuments());
e.stopPropagation();
} else if (e.key === 'q' && e.ctrlKey) {
e.preventDefault();
@@ -136,7 +143,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
ns.map(line => {
const indent = line.search(/\S|$/);
const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + (indent / 3) * 10, y: ypos, title: line });
- this.props.addDocument?.(newBox);
+ this._props.addDocument?.(newBox);
ypos += 40 * this.Transform.Scale;
});
})();
@@ -147,8 +154,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
pasteImageBitmap((data: any, error: any) => {
error && console.log(error);
data &&
- Utils.convertDataUri(data, this.props.Document[Id] + '-thumb-frozen').then(returnedfilename => {
- this.props.Document['thumb-frozen'] = new ImageField(returnedfilename);
+ Utils.convertDataUri(data, this._props.Document[Id] + '-thumb-frozen').then(returnedfilename => {
+ this._props.Document['thumb-frozen'] = new ImageField(returnedfilename);
});
})
);
@@ -159,7 +166,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
slide.y = y;
FormattedTextBox.SelectOnLoad = slide[Id];
TreeView._editTitleOnLoad = { id: slide[Id], parent: undefined };
- this.props.addDocument?.(slide);
+ this._props.addDocument?.(slide);
e.stopPropagation();
}*/ else if (e.key === 'p' && e.ctrlKey) {
e.preventDefault();
@@ -170,9 +177,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
})();
e.stopPropagation();
} else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views().length < 2) {
- FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this.props.childLayoutString ? e.key : '';
+ FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this._props.childLayoutString ? e.key : '';
FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('type new note');
- this.props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100, this.props.xPadding === 0));
+ this._props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100, this._props.xPadding === 0));
e.stopPropagation();
}
};
@@ -203,7 +210,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const file = new File([blob], 'droppedTable', options);
const loading = Docs.Create.LoadingDocument(file, options);
DocUtils.uploadFileToDoc(file, {}, loading);
- this.props.addDocument?.(loading);
+ this._props.addDocument?.(loading);
}
@action
@@ -214,9 +221,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode;
// allow marquee if right drag/meta drag, or pan mode
if (e.button === 2 || e.metaKey || scrollMode === freeformScrollMode.Pan) {
- this.setPreviewCursor(e.clientX, e.clientY, true, false, this.props.Document);
+ this.setPreviewCursor(e.clientX, e.clientY, true, false, this._props.Document);
e.preventDefault();
- } else PreviewCursor.Visible = false;
+ } else PreviewCursor.Instance.Visible = false;
};
@action
@@ -243,10 +250,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
if (this._visible) {
const mselect = this.marqueeSelect();
if (!e.shiftKey) {
- SelectionManager.DeselectAll(mselect.length ? undefined : this.props.Document);
+ SelectionManager.DeselectAll(mselect.length ? undefined : this._props.Document);
}
- const docs = mselect.length ? mselect : [this.props.Document];
- this.props.selectDocuments(docs);
+ const docs = mselect.length ? mselect : [this._props.Document];
+ this._props.selectDocuments(docs);
}
const hideMarquee = () => {
this.hideMarquee();
@@ -285,14 +292,14 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this._downX = this._lastX = x;
this._downY = this._lastY = y;
this._commandExecuted = false;
- PreviewCursor.Visible = false;
- PreviewCursor.Doc = undefined;
+ PreviewCursor.Instance.Visible = false;
+ PreviewCursor.Instance.Doc = undefined;
} else if (drag) {
this._downX = this._lastX = x;
this._downY = this._lastY = y;
this._commandExecuted = false;
- PreviewCursor.Visible = false;
- PreviewCursor.Doc = undefined;
+ PreviewCursor.Instance.Visible = false;
+ PreviewCursor.Instance.Doc = undefined;
this.cleanupInteractions(true);
document.addEventListener('pointermove', this.onPointerMove, true);
document.addEventListener('pointerup', this.onPointerUp, true);
@@ -300,10 +307,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
} else {
this._downX = x;
this._downY = y;
- const effectiveAcl = GetEffectiveAcl(this.props.Document[DocData]);
+ const effectiveAcl = GetEffectiveAcl(this._props.Document[DocData]);
if ([AclAdmin, AclEdit, AclAugment].includes(effectiveAcl)) {
- PreviewCursor.Doc = doc;
- PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge, this.props.slowLoadDocuments);
+ PreviewCursor.Instance.Doc = doc;
+ PreviewCursor.Show(x, y, this.onKeyPress, this._props.addLiveTextDocument, this._props.getTransform, this._props.addDocument, this._props.nudge, this._props.slowLoadDocuments);
}
this.clearSelection();
}
@@ -311,11 +318,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@action
onClick = (e: React.MouseEvent): void => {
- if (this.props.pointerEvents?.() === 'none') return;
+ if (this._props.pointerEvents?.() === 'none') return;
if (Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
if (Doc.ActiveTool === InkTool.None) {
- if (!this.props.trySelectCluster(e.shiftKey)) {
- !DocumentView.ExploreMode && this.setPreviewCursor(e.clientX, e.clientY, false, false, this.props.Document);
+ if (!this._props.trySelectCluster(e.shiftKey)) {
+ !DocumentView.ExploreMode && this.setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document);
} else e.stopPropagation();
}
// let the DocumentView stopPropagation of this event when it selects this document
@@ -332,16 +339,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
hideMarquee = () => (this._visible = false);
@undoBatch
- @action
- delete = (e?: React.PointerEvent<Element> | KeyboardEvent | undefined, hide?: boolean) => {
+ delete = action((e?: React.PointerEvent<Element> | KeyboardEvent | undefined, hide?: boolean) => {
const selected = this.marqueeSelect(false);
SelectionManager.DeselectAll();
- selected.forEach(doc => (hide ? (doc.hidden = true) : this.props.removeDocument?.(doc)));
+ selected.forEach(doc => (hide ? (doc.hidden = true) : this._props.removeDocument?.(doc)));
this.cleanupInteractions(false);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
- };
+ });
getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, makeGroup: Opt<boolean>) => {
const newCollection = creator
@@ -366,17 +372,16 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
});
@undoBatch
- @action
- pileup = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ pileup = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
const selected = this.marqueeSelect(false);
SelectionManager.DeselectAll();
- selected.forEach(d => this.props.removeDocument?.(d));
+ selected.forEach(d => this._props.removeDocument?.(d));
const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2)!;
- this.props.addDocument?.(newCollection);
- this.props.selectDocuments([newCollection]);
+ this._props.addDocument?.(newCollection);
+ this._props.selectDocuments([newCollection]);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
- };
+ });
/**
* This triggers the TabDocView.PinDoc method which is the universal method
@@ -385,35 +390,32 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
* This one is unique in that it includes the bounds associated with marquee view.
*/
@undoBatch
- @action
- pinWithView = () => {
- this.props.pinToPres(this.props.Document, { pinViewport: this.Bounds });
+ pinWithView = action(() => {
+ this._props.pinToPres(this._props.Document, { pinViewport: this.Bounds });
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
- };
+ });
@undoBatch
- @action
- collection = (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => {
+ collection = action((e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => {
const selected = selection ?? this.marqueeSelect(false);
const activeFrame = selected.reduce((v, d) => v ?? Cast(d._activeFrame, 'number', null), undefined as number | undefined);
if (e instanceof KeyboardEvent ? 'cg'.includes(e.key) : true) {
- this.props.removeDocument?.(selected);
+ this._props.removeDocument?.(selected);
}
const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === 't' ? Docs.Create.StackingDocument : undefined, group);
newCollection._freeform_panX = this.Bounds.left + this.Bounds.width / 2;
newCollection._freeform_panY = this.Bounds.top + this.Bounds.height / 2;
newCollection._currentFrame = activeFrame;
- this.props.addDocument?.(newCollection);
- this.props.selectDocuments([newCollection]);
+ this._props.addDocument?.(newCollection);
+ this._props.selectDocuments([newCollection]);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
- };
+ });
@undoBatch
- @action
- syntaxHighlight = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ syntaxHighlight = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
const selected = this.marqueeSelect(false);
if (e instanceof KeyboardEvent ? e.key === 'i' : true) {
const inks = selected.filter(s => s.type === DocumentType.INK);
@@ -472,15 +474,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
// }
const lines = results.filter((r: any) => r.category === 'line');
const text = lines.map((l: any) => l.recognizedText).join('\r\n');
- this.props.addDocument?.(Docs.Create.TextDocument(text, { _width: this.Bounds.width, _height: this.Bounds.height, x: this.Bounds.left + this.Bounds.width, y: this.Bounds.top, title: text }));
+ this._props.addDocument?.(Docs.Create.TextDocument(text, { _width: this.Bounds.width, _height: this.Bounds.height, x: this.Bounds.left + this.Bounds.width, y: this.Bounds.top, title: text }));
});
}
- };
+ });
@undoBatch
summary = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
const selected = this.marqueeSelect(false).map(d => {
- this.props.removeDocument?.(d);
+ this._props.removeDocument?.(d);
d.x = NumCast(d.x) - this.Bounds.left;
d.y = NumCast(d.y) - this.Bounds.top;
return d;
@@ -500,8 +502,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
DocUtils.MakeLink(summary, portal, { link_relationship: 'summary of:summarized by' });
portal.hidden = true;
- this.props.addDocument?.(portal);
- this.props.addLiveTextDocument(summary);
+ this._props.addDocument?.(portal);
+ this._props.addLiveTextDocument(summary);
MarqueeOptionsMenu.Instance.fadeOut(true);
});
@@ -582,17 +584,17 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
(this.touchesLine(bounds) || this.boundingShape(bounds)) && selection.push(doc);
}
};
- this.props
+ this._props
.activeDocuments()
.filter(doc => !doc.z && !doc._lockedPosition)
.map(selectFunc);
if (!selection.length && selectBackgrounds)
- this.props
+ this._props
.activeDocuments()
.filter(doc => doc.z === undefined)
.map(selectFunc);
if (!selection.length)
- this.props
+ this._props
.activeDocuments()
.filter(doc => doc.z !== undefined)
.map(selectFunc);
@@ -601,8 +603,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@computed get marqueeDiv() {
const cpt = this._lassoFreehand || !this._visible ? [0, 0] : [this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY];
- const p = this.props.getContainerTransform().transformPoint(cpt[0], cpt[1]);
- const v = this._lassoFreehand ? [0, 0] : this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
+ const p = this._props.getContainerTransform().transformPoint(cpt[0], cpt[1]);
+ const v = this._lassoFreehand ? [0, 0] : this._props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
return (
<div
className="marquee"
@@ -610,8 +612,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
transform: `translate(${p[0]}px, ${p[1]}px)`,
width: Math.abs(v[0]),
height: Math.abs(v[1]),
- color: lightOrDark(this.props.Document?.backgroundColor ?? 'white'),
- borderColor: lightOrDark(this.props.Document?.backgroundColor ?? 'white'),
+ color: lightOrDark(this._props.Document?.backgroundColor ?? 'white'),
+ borderColor: lightOrDark(this._props.Document?.backgroundColor ?? 'white'),
zIndex: 2000,
}}>
{' '}
@@ -620,7 +622,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
<polyline //
points={this._lassoPts.reduce((s, pt) => s + pt[0] + ',' + pt[1] + ' ', '')}
fill="none"
- stroke={lightOrDark(this.props.Document?.backgroundColor ?? 'white')}
+ stroke={lightOrDark(this._props.Document?.backgroundColor ?? 'white')}
strokeWidth="1"
strokeDasharray="3"
/>
@@ -635,19 +637,19 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@action
onDragAutoScroll = (e: CustomEvent<React.DragEvent>) => {
- if ((e as any).handlePan || this.props.isAnnotationOverlay) return;
+ if ((e as any).handlePan || this._props.isAnnotationOverlay) return;
(e as any).handlePan = true;
const bounds = this.MarqueeRef?.getBoundingClientRect();
- if (!this.props.Document._freeform_noAutoPan && !this.props.renderDepth && bounds) {
+ if (!this._props.Document._freeform_noAutoPan && !this._props.renderDepth && bounds) {
const dragX = e.detail.clientX;
const dragY = e.detail.clientY;
const deltaX = dragX - bounds.left < 25 ? -(25 + (bounds.left - dragX)) : bounds.right - dragX < 25 ? 25 - (bounds.right - dragX) : 0;
const deltaY = dragY - bounds.top < 25 ? -(25 + (bounds.top - dragY)) : bounds.bottom - dragY < 25 ? 25 - (bounds.bottom - dragY) : 0;
if (deltaX !== 0 || deltaY !== 0) {
- this.props.Document[this.props.panYFieldKey] = NumCast(this.props.Document[this.props.panYFieldKey]) + deltaY / 2;
- this.props.Document[this.props.panXFieldKey] = NumCast(this.props.Document[this.props.panXFieldKey]) + deltaX / 2;
+ this._props.Document[this._props.panYFieldKey] = NumCast(this._props.Document[this._props.panYFieldKey]) + deltaY / 2;
+ this._props.Document[this._props.panXFieldKey] = NumCast(this._props.Document[this._props.panXFieldKey]) + deltaX / 2;
}
}
e.stopPropagation();
@@ -661,7 +663,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.MarqueeRef = r;
}}
style={{
- overflow: StrCast(this.props.Document._overflow),
+ overflow: StrCast(this._props.Document._overflow),
cursor: [InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool) || this._visible ? 'crosshair' : 'pointer',
}}
onDragOver={e => e.preventDefault()}
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 8daed9746..697a11ccc 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -277,7 +277,6 @@ export class CollectionGridView extends CollectionSubView() {
/**
* Handles internal drop of Dash documents.
*/
- @action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
const savedLayouts = this.savedLayoutList;
const dropped = de.complete.docDragData?.droppedDocuments;
@@ -292,7 +291,6 @@ export class CollectionGridView extends CollectionSubView() {
/**
* Handles external drop of images/PDFs etc from outside Dash.
*/
- @action
onExternalDrop = async (e: React.DragEvent): Promise<void> => {
this.dropLocation = this.screenToCell(e.clientX, e.clientY);
super.onExternalDrop(e, {});
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 0ee5f3b40..a9782f699 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -1,14 +1,14 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { Toggle, ToggleType, Type } from 'browndash-components';
-import { action, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, IReactionDisposer, makeObservable, observable, override, reaction, runInAction, untracked } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, Opt } from '../../../../fields/Doc';
import { Height, Width } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils';
+import { copyProps, emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { BranchingTrailManager } from '../../../util/BranchingTrailManager';
import { DocumentManager } from '../../../util/DocumentManager';
@@ -20,7 +20,7 @@ import { DocumentView } from '../../nodes/DocumentView';
import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup';
import { UndoStack } from '../../UndoStack';
import { CollectionStackedTimeline } from '../CollectionStackedTimeline';
-import { CollectionSubView } from '../CollectionSubView';
+import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView';
import './CollectionLinearView.scss';
/**
@@ -39,6 +39,18 @@ export class CollectionLinearView extends CollectionSubView() {
private _widthDisposer?: IReactionDisposer;
private _selectedDisposer?: IReactionDisposer;
+ _prevProps: SubCollectionViewProps;
+ @override _props: SubCollectionViewProps;
+ constructor(props: SubCollectionViewProps) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
componentWillUnmount() {
this._dropDisposer?.();
this._widthDisposer?.();
@@ -170,28 +182,28 @@ export class CollectionLinearView extends CollectionSubView() {
}}>
<DocumentView
Document={doc}
- isContentActive={this.props.isContentActive}
+ isContentActive={this._props.isContentActive}
isDocumentActive={returnTrue}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- addDocTab={this.props.addDocTab}
+ addDocument={this._props.addDocument}
+ moveDocument={this._props.moveDocument}
+ addDocTab={this._props.addDocTab}
pinToPres={emptyFunction}
- dragAction={(this.layoutDoc.childDragAction ?? this.props.childDragAction) as dropActionType}
+ dragAction={(this.layoutDoc.childDragAction ?? this._props.childDragAction) as dropActionType}
rootSelected={this.rootSelected}
- removeDocument={this.props.removeDocument}
+ removeDocument={this._props.removeDocument}
ScreenToLocalTransform={docXf}
PanelWidth={doc[Width]}
PanelHeight={nested || doc._height ? doc[Height] : this.dimension}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
dontRegisterView={BoolCast(this.Document.childDontRegisterViews)}
focus={emptyFunction}
- styleProvider={this.props.styleProvider}
+ styleProvider={this._props.styleProvider}
docViewPath={returnEmptyDoclist}
whenChildContentsActiveChanged={emptyFunction}
bringToFront={emptyFunction}
- childFilters={this.props.childFilters}
- childFiltersByRanges={this.props.childFiltersByRanges}
- searchFilterDocs={this.props.searchFilterDocs}
+ childFilters={this._props.childFilters}
+ childFiltersByRanges={this._props.childFiltersByRanges}
+ searchFilterDocs={this._props.searchFilterDocs}
hideResizeHandles={true}
/>
</div>
@@ -205,8 +217,8 @@ export class CollectionLinearView extends CollectionSubView() {
const menuOpener = (
<Toggle
- text={Cast(this.props.Document.icon, 'string', null)}
- icon={Cast(this.props.Document.icon, 'string', null) ? undefined : <FontAwesomeIcon color={SettingsManager.userColor} icon={isExpanded ? 'minus' : 'plus'} />}
+ text={Cast(this.Document.icon, 'string', null)}
+ icon={Cast(this.Document.icon, 'string', null) ? undefined : <FontAwesomeIcon color={SettingsManager.userColor} icon={isExpanded ? 'minus' : 'plus'} />}
color={SettingsManager.userColor}
background={SettingsManager.userVariantColor}
type={Type.TERT}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index e7af91390..037786abf 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -192,7 +192,6 @@ export class CollectionMulticolumnView extends CollectionSubView() {
};
@undoBatch
- @action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
let dropInd = -1;
if (de.complete.docDragData && this._mainCont) {
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index ef66e8ce5..bbf3481dd 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -188,7 +188,6 @@ export class CollectionMultirowView extends CollectionSubView() {
};
@undoBatch
- @action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
let dropInd = -1;
if (de.complete.docDragData && this._mainCont) {
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 029d39f88..9a00595f9 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -68,16 +68,16 @@ export class CollectionSchemaView extends CollectionSubView() {
@observable _menuKeys: string[] = [];
@observable _rowEles: ObservableMap = new ObservableMap<Doc, HTMLDivElement>();
@observable _colEles: HTMLDivElement[] = [];
- @observable _displayColumnWidths: number[] | undefined;
- @observable _columnMenuIndex: number | undefined;
+ @observable _displayColumnWidths: number[] | undefined = undefined;
+ @observable _columnMenuIndex: number | undefined = undefined;
@observable _newFieldWarning: string = '';
@observable _makeNewField: boolean = false;
@observable _newFieldDefault: any = 0;
@observable _newFieldType: ColumnType = ColumnType.Number;
@observable _menuValue: string = '';
- @observable _filterColumnIndex: number | undefined;
+ @observable _filterColumnIndex: number | undefined = undefined;
@observable _filterSearchValue: string = '';
- @observable _selectedCell: [Doc, number] | undefined;
+ @observable _selectedCell: [Doc, number] | undefined = undefined;
// target HTMLelement portal for showing a popup menu to edit cell values.
public get MenuTarget() {
@@ -437,7 +437,6 @@ export class CollectionSchemaView extends CollectionSubView() {
setDropIndex = (index: number) => (this._closestDropIndex = index);
- @action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.columnDragData) {
const mouseX = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0];
@@ -473,7 +472,6 @@ export class CollectionSchemaView extends CollectionSubView() {
return false;
};
- @action
onExternalDrop = async (e: React.DragEvent): Promise<void> => {
super.onExternalDrop(e, {}, undoBatch(action(docus => docus.map((doc: Doc) => this.addDocument(doc)))));
};
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
index 0e37198bb..04443b4a7 100644
--- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -26,7 +26,7 @@ export interface SchemaColumnHeaderProps {
export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> {
@observable _ref: HTMLDivElement | null = null;
- @computed get fieldKey() {
+ get fieldKey() {
return this.props.columnKeys[this.props.columnIndex];
}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 53f4a4d51..8d2671a6e 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -34,11 +34,11 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo
bounds = () => this._ref?.getBoundingClientRect();
@computed get schemaView() {
- return this.props.DocumentView?.().props.docViewPath().lastElement()?.ComponentView as CollectionSchemaView;
+ return this.props.DocumentView?.()._props.docViewPath().lastElement()?.ComponentView as CollectionSchemaView;
}
@computed get schemaDoc() {
- return this.props.DocumentView?.().props.docViewPath().lastElement()?.Document;
+ return this.props.DocumentView?.()._props.docViewPath().lastElement()?.Document;
}
@computed get rowIndex() {
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index bd6d3bad6..af0fe73c3 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import Select, { MenuPlacement } from 'react-select';
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import { extname } from 'path';
import DatePicker from 'react-datepicker';
@@ -95,13 +95,19 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
return { color, textDecoration, fieldProps, cursor, pointerEvents };
}
+ @observable _props: React.PropsWithChildren<SchemaTableCellProps>;
+ constructor(props: React.PropsWithChildren<SchemaTableCellProps>) {
+ super(props);
+ this._props = props;
+ }
+
@computed get selected() {
- const selected: [Doc, number] | undefined = this.props.selectedCell();
- return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
+ const selected: [Doc, number] | undefined = this._props.selectedCell();
+ return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col;
}
@computed get defaultCellContent() {
- const { color, textDecoration, fieldProps, pointerEvents } = SchemaTableCell.renderProps(this.props);
+ const { color, textDecoration, fieldProps, pointerEvents } = SchemaTableCell.renderProps(this._props);
return (
<div
@@ -113,17 +119,17 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
pointerEvents,
}}>
<EditableView
- oneLine={this.props.oneLine}
- allowCRs={this.props.allowCRs}
+ oneLine={this._props.oneLine}
+ allowCRs={this._props.allowCRs}
contents={<FieldView {...fieldProps} />}
editing={this.selected ? undefined : false}
- GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
+ GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey)}
SetValue={undoable((value: string, shiftDown?: boolean, enterKey?: boolean) => {
if (shiftDown && enterKey) {
- this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), value);
+ this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value);
}
- const ret = KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value);
- this.props.finishEdit?.();
+ const ret = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value);
+ this._props.finishEdit?.();
return ret;
}, 'edit schema cell')}
/>
@@ -132,8 +138,8 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
}
get getCellType() {
- const columnTypeStr = this.props.getFinfo(this.props.fieldKey)?.fieldType;
- const cellValue = this.props.Document[this.props.fieldKey];
+ const columnTypeStr = this._props.getFinfo(this._props.fieldKey)?.fieldType;
+ const cellValue = this._props.Document[this._props.fieldKey];
if (cellValue instanceof ImageField) return ColumnType.Image;
if (cellValue instanceof DateField) return ColumnType.Date;
if (cellValue instanceof RichTextField) return ColumnType.RTF;
@@ -152,11 +158,11 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
const cellType: ColumnType = this.getCellType;
// prettier-ignore
switch (cellType) {
- case ColumnType.Image: return <SchemaImageCell {...this.props} />;
- case ColumnType.Boolean: return <SchemaBoolCell {...this.props} />;
- case ColumnType.RTF: return <SchemaRTFCell {...this.props} />;
- case ColumnType.Enumeration: return <SchemaEnumerationCell {...this.props} options={this.props.getFinfo(this.props.fieldKey)?.values?.map(val => val.toString())} />;
- case ColumnType.Date: // return <SchemaDateCell {...this.props} />;
+ case ColumnType.Image: return <SchemaImageCell {...this._props} />;
+ case ColumnType.Boolean: return <SchemaBoolCell {...this._props} />;
+ case ColumnType.RTF: return <SchemaRTFCell {...this._props} />;
+ case ColumnType.Enumeration: return <SchemaEnumerationCell {...this._props} options={this._props.getFinfo(this._props.fieldKey)?.values?.map(val => val.toString())} />;
+ case ColumnType.Date: // return <SchemaDateCell {...this._props} />;
default: return this.defaultCellContent;
}
}
@@ -165,8 +171,8 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
return (
<div
className="schema-table-cell"
- onPointerDown={action(e => !this.selected && this.props.selectCell(this.props.Document, this.props.col))}
- style={{ padding: this.props.padding, maxWidth: this.props.maxWidth?.(), width: this.props.columnWidth() || undefined, border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}>
+ onPointerDown={action(e => !this.selected && this._props.selectCell(this._props.Document, this._props.col))}
+ style={{ padding: this._props.padding, maxWidth: this._props.maxWidth?.(), width: this._props.columnWidth() || undefined, border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}>
{this.content}
</div>
);
@@ -176,7 +182,14 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
// mj: most of this is adapted from old schema code so I'm not sure what it does tbh
@observer
export class SchemaImageCell extends React.Component<SchemaTableCellProps> {
- @observable _previewRef: HTMLImageElement | undefined;
+ @observable _previewRef: HTMLImageElement | undefined = undefined;
+
+ _props: React.PropsWithChildren<SchemaTableCellProps>;
+ constructor(props: React.PropsWithChildren<SchemaTableCellProps>) {
+ super(props);
+ this._props = props;
+ makeObservable(this);
+ }
choosePath(url: URL) {
if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href
@@ -188,8 +201,8 @@ export class SchemaImageCell extends React.Component<SchemaTableCellProps> {
}
get url() {
- const field = Cast(this.props.Document[this.props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
- const alts = DocListCast(this.props.Document[this.props.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
+ const field = Cast(this._props.Document[this._props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
+ const alts = DocListCast(this._props.Document[this._props.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
const altpaths = alts
.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
.filter(url => url)
@@ -226,10 +239,10 @@ export class SchemaImageCell extends React.Component<SchemaTableCellProps> {
};
render() {
- const aspect = Doc.NativeAspect(this.props.Document); // aspect ratio
- // let width = Math.max(75, this.props.columnWidth); // get a with that is no smaller than 75px
+ const aspect = Doc.NativeAspect(this._props.Document); // aspect ratio
+ // let width = Math.max(75, this._props.columnWidth); // get a with that is no smaller than 75px
// const height = Math.max(75, width / aspect); // get a height either proportional to that or 75 px
- const height = this.props.rowHeight() ? this.props.rowHeight() - (this.props.padding || 6) * 2 : undefined;
+ const height = this._props.rowHeight() ? this._props.rowHeight() - (this._props.padding || 6) * 2 : undefined;
const width = height ? height * aspect : undefined; // increase the width of the image if necessary to maintain proportionality
return <img src={this.url} width={width ? width : undefined} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />;
@@ -240,19 +253,26 @@ export class SchemaImageCell extends React.Component<SchemaTableCellProps> {
export class SchemaDateCell extends React.Component<SchemaTableCellProps> {
@observable _pickingDate: boolean = false;
+ @observable _props: React.PropsWithChildren<SchemaTableCellProps>;
+ constructor(props: React.PropsWithChildren<SchemaTableCellProps>) {
+ super(props);
+ this._props = props;
+ makeObservable(this);
+ }
+
@computed get date(): DateField {
// if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined.
- return DateCast(this.props.Document[this.props.fieldKey]);
+ return DateCast(this._props.Document[this._props.fieldKey]);
}
@action
handleChange = (date: any) => {
// const script = CompileScript(date.toString(), { requiredType: "Date", addReturn: true, params: { this: Doc.name } });
// if (script.compiled) {
- // this.applyToDoc(this._document, this.props.row, this.props.col, script.run);
+ // this.applyToDoc(this._document, this._props.row, this._props.col, script.run);
// } else {
// ^ DateCast is always undefined for some reason, but that is what the field should be set to
- this.props.Document[this.props.fieldKey] = new DateField(date as Date);
+ this._props.Document[this._props.fieldKey] = new DateField(date as Date);
//}
};
@@ -262,13 +282,20 @@ export class SchemaDateCell extends React.Component<SchemaTableCellProps> {
}
@observer
export class SchemaRTFCell extends React.Component<SchemaTableCellProps> {
+ @observable _props: React.PropsWithChildren<SchemaTableCellProps>;
+ constructor(props: React.PropsWithChildren<SchemaTableCellProps>) {
+ super(props);
+ this._props = props;
+ makeObservable(this);
+ }
+
@computed get selected() {
- const selected: [Doc, number] | undefined = this.props.selectedCell();
- return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
+ const selected: [Doc, number] | undefined = this._props.selectedCell();
+ return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col;
}
selectedFunc = () => this.selected;
render() {
- const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props);
+ const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
fieldProps.isContentActive = this.selectedFunc;
return (
<div className="schemaRTFCell" style={{ display: 'flex', fontStyle: this.selected ? undefined : 'italic', width: '100%', height: '100%', position: 'relative', color, textDecoration, cursor, pointerEvents }}>
@@ -279,35 +306,42 @@ export class SchemaRTFCell extends React.Component<SchemaTableCellProps> {
}
@observer
export class SchemaBoolCell extends React.Component<SchemaTableCellProps> {
+ @observable _props: React.PropsWithChildren<SchemaTableCellProps>;
+ constructor(props: React.PropsWithChildren<SchemaTableCellProps>) {
+ super(props);
+ this._props = props;
+ makeObservable(this);
+ }
+
@computed get selected() {
- const selected: [Doc, number] | undefined = this.props.selectedCell();
- return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
+ const selected: [Doc, number] | undefined = this._props.selectedCell();
+ return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col;
}
render() {
- const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props);
+ const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
return (
<div className="schemaBoolCell" style={{ display: 'flex', color, textDecoration, cursor, pointerEvents }}>
<input
style={{ marginRight: 4 }}
type="checkbox"
- checked={BoolCast(this.props.Document[this.props.fieldKey])}
+ checked={BoolCast(this._props.Document[this._props.fieldKey])}
onChange={undoBatch((value: React.ChangeEvent<HTMLInputElement> | undefined) => {
if ((value?.nativeEvent as any).shiftKey) {
- this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
+ this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
}
- KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
+ KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
})}
/>
<EditableView
contents={<FieldView {...fieldProps} />}
editing={this.selected ? undefined : false}
- GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
+ GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey)}
SetValue={undoBatch((value: string, shiftDown?: boolean, enterKey?: boolean) => {
if (shiftDown && enterKey) {
- this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), value);
+ this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value);
}
- const set = KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value);
- this.props.finishEdit?.();
+ const set = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value);
+ this._props.finishEdit?.();
return set;
})}
/>
@@ -317,13 +351,20 @@ export class SchemaBoolCell extends React.Component<SchemaTableCellProps> {
}
@observer
export class SchemaEnumerationCell extends React.Component<SchemaTableCellProps> {
+ @observable _props: React.PropsWithChildren<SchemaTableCellProps>;
+ constructor(props: React.PropsWithChildren<SchemaTableCellProps>) {
+ super(props);
+ this._props = props;
+ makeObservable(this);
+ }
+
@computed get selected() {
- const selected: [Doc, number] | undefined = this.props.selectedCell();
- return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
+ const selected: [Doc, number] | undefined = this._props.selectedCell();
+ return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col;
}
render() {
- const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props);
- const options = this.props.options?.map(facet => ({ value: facet, label: facet }));
+ const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
+ const options = this._props.options?.map(facet => ({ value: facet, label: facet }));
return (
<div className="schemaSelectionCell" style={{ color, textDecoration, cursor, pointerEvents }}>
<div style={{ width: '100%' }}>
@@ -357,17 +398,17 @@ export class SchemaEnumerationCell extends React.Component<SchemaTableCellProps>
...base,
left: 0,
top: 0,
- transform: `translate(${this.props.transform().TranslateX}px, ${this.props.transform().TranslateY}px)`,
- width: Number(base.width) * this.props.transform().Scale,
+ transform: `translate(${this._props.transform().TranslateX}px, ${this._props.transform().TranslateY}px)`,
+ width: Number(base.width) * this._props.transform().Scale,
zIndex: 9999,
}),
}}
- menuPortalTarget={this.props.menuTarget}
+ menuPortalTarget={this._props.menuTarget}
menuPosition={'absolute'}
- placeholder={StrCast(this.props.Document[this.props.fieldKey], 'select...')}
+ placeholder={StrCast(this._props.Document[this._props.fieldKey], 'select...')}
options={options}
isMulti={false}
- onChange={val => KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)}
+ onChange={val => KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)}
/>
</div>
</div>
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index de29d8602..dcd3bffac 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -42,14 +42,14 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
if (checkResult) {
const selView = selectedViews.lastElement();
const fieldKey = selView.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
- const layoutFrameNumber = Cast(selView.props.docViewPath().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+ const layoutFrameNumber = Cast(selView._props.docViewPath().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
const contentFrameNumber = Cast(selView.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
return CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] ?? 'transparent';
}
selectedViews.some(dv => dv.ComponentView instanceof InkingStroke) && SetActiveFillColor(color ?? 'transparent');
selectedViews.forEach(dv => {
const fieldKey = dv.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
- const layoutFrameNumber = Cast(dv.props.docViewPath().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+ const layoutFrameNumber = Cast(dv._props.docViewPath().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
const contentFrameNumber = Cast(dv.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
if (contentFrameNumber !== undefined) {
const obj: { [key: string]: Opt<string> } = {};
@@ -280,7 +280,7 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
return d;
})
);
- ffView.props.removeDocument?.(selected);
+ ffView._props.removeDocument?.(selected);
// TODO: nda - this is the code to actually get a new grouped collection
const newCollection = marqViewRef?.getCollection(selected, undefined, true);
if (newCollection) {
@@ -289,7 +289,7 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
}
// nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
- newCollection && ffView.props.addDocument?.(newCollection);
+ newCollection && ffView._props.addDocument?.(newCollection);
// TODO: nda - will probably need to go through and only remove the unprocessed selected docs
ffView.unprocessedDocs = [];
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 3d9cf1893..91d812f35 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -1,4 +1,4 @@
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../../fields/Doc';
import { LinkManager } from '../../util/LinkManager';
@@ -23,6 +23,10 @@ interface Props {
export class LinkMenu extends React.Component<Props> {
_editorRef = React.createRef<HTMLDivElement>();
@observable _linkMenuRef = React.createRef<HTMLDivElement>();
+ constructor(props: Props) {
+ super(props);
+ makeObservable(this);
+ }
clear = () => this.props.clearLinkEditor?.();
@@ -47,7 +51,7 @@ export class LinkMenu extends React.Component<Props> {
*/
renderAllGroups = (groups: Map<string, Array<Doc>>): Array<JSX.Element> => {
const linkItems = Array.from(groups.entries()).map(group => (
- <LinkMenuGroup key={group[0]} itemHandler={this.props.itemHandler} docView={this.props.docView} sourceDoc={this.props.docView.props.Document} group={group[1]} groupType={group[0]} clearLinkEditor={this.clear} />
+ <LinkMenuGroup key={group[0]} itemHandler={this.props.itemHandler} docView={this.props.docView} sourceDoc={this.props.docView.Document} group={group[1]} groupType={group[0]} clearLinkEditor={this.clear} />
));
return linkItems.length ? linkItems : this.props.style ? [] : [<p key="none">No links have been created yet. Drag the linking button onto another document to create a link.</p>];
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 12db7fa4b..858cc2e5e 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -1,12 +1,12 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, observable } from 'mobx';
+import { action, computed, observable, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../../fields/Doc';
import { Cast, DocCast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, returnFalse, setupMoveUpEvents, copyProps } from '../../../Utils';
import { DocumentType } from '../../documents/DocumentTypes';
import { DragManager } from '../../util/DragManager';
import { LinkFollower } from '../../util/LinkFollower';
@@ -52,9 +52,19 @@ export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: Docume
@observer
export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
private _drag = React.createRef<HTMLDivElement>();
-
_editRef = React.createRef<HTMLDivElement>();
+ _prevProps: React.PropsWithChildren<LinkMenuItemProps>;
+ @observable _props: React.PropsWithChildren<LinkMenuItemProps>;
+ constructor(props: React.PropsWithChildren<LinkMenuItemProps>) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
@observable private _showMore: boolean = false;
@action toggleShowMore(e: React.PointerEvent) {
e.stopPropagation();
@@ -62,12 +72,12 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
}
@computed get sourceAnchor() {
- const ldoc = this.props.linkDoc;
- if (this.props.sourceDoc !== ldoc.link_anchor_1 && this.props.sourceDoc !== ldoc.link_anchor_2) {
- if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_1).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.link_anchor_1);
- if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_2).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.link_anchor_2);
+ const ldoc = this._props.linkDoc;
+ if (this._props.sourceDoc !== ldoc.link_anchor_1 && this._props.sourceDoc !== ldoc.link_anchor_2) {
+ if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_1).annotationOn), this._props.sourceDoc)) return DocCast(ldoc.link_anchor_1);
+ if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_2).annotationOn), this._props.sourceDoc)) return DocCast(ldoc.link_anchor_2);
}
- return this.props.sourceDoc;
+ return this._props.sourceDoc;
}
onEdit = (e: React.PointerEvent) => {
@@ -75,24 +85,24 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
this,
e,
e => {
- const dragData = new DragManager.DocumentDragData([this.props.linkDoc], 'embed');
+ const dragData = new DragManager.DocumentDragData([this._props.linkDoc], 'embed');
dragData.dropPropertiesToRemove = ['hidden'];
DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y);
return true;
},
emptyFunction,
action(() => {
- const trail = DocCast(this.props.docView.Document.presentationTrail);
+ const trail = DocCast(this._props.docView.Document.presentationTrail);
if (trail) {
Doc.ActivePresentation = trail;
DocumentViewInternal.addDocTabFunc(trail, OpenWhere.replaceRight);
} else {
- SelectionManager.SelectView(this.props.docView, false);
- LinkManager.currentLink = this.props.linkDoc === LinkManager.currentLink ? undefined : this.props.linkDoc;
+ SelectionManager.SelectView(this._props.docView, false);
+ LinkManager.currentLink = this._props.linkDoc === LinkManager.currentLink ? undefined : this._props.linkDoc;
LinkManager.currentLinkAnchor = LinkManager.currentLink ? this.sourceAnchor : undefined;
- if ((SettingsManager.propertiesWidth ?? 0) < 100) {
- setTimeout(action(() => (SettingsManager.propertiesWidth = 250)));
+ if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
+ setTimeout(action(() => (SettingsManager.Instance.propertiesWidth = 250)));
}
}
})
@@ -106,43 +116,43 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
e => {
const eleClone: any = this._drag.current!.cloneNode(true);
eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`;
- StartLinkTargetsDrag(eleClone, this.props.docView, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]);
- this.props.clearLinkEditor?.();
+ StartLinkTargetsDrag(eleClone, this._props.docView, e.x, e.y, this._props.sourceDoc, [this._props.linkDoc]);
+ this._props.clearLinkEditor?.();
return true;
},
emptyFunction,
() => {
- this.props.clearLinkEditor?.();
- if (this.props.itemHandler) {
- this.props.itemHandler?.(this.props.linkDoc);
+ this._props.clearLinkEditor?.();
+ if (this._props.itemHandler) {
+ this._props.itemHandler?.(this._props.linkDoc);
} else {
const focusDoc =
- Cast(this.props.linkDoc.link_anchor_1, Doc, null)?.annotationOn === this.props.sourceDoc
- ? Cast(this.props.linkDoc.link_anchor_1, Doc, null)
- : Cast(this.props.linkDoc.link_anchor_2, Doc, null)?.annotationOn === this.props.sourceDoc
- ? Cast(this.props.linkDoc.link_anchor_12, Doc, null)
- : undefined;
-
- if (focusDoc) this.props.docView.props.focus(focusDoc, { instant: true });
- LinkFollower.FollowLink(this.props.linkDoc, this.props.sourceDoc, false);
+ Cast(this._props.linkDoc.link_anchor_1, Doc, null)?.annotationOn === this._props.sourceDoc
+ ? Cast(this._props.linkDoc.link_anchor_1, Doc, null)
+ : Cast(this._props.linkDoc.link_anchor_2, Doc, null)?.annotationOn === this._props.sourceDoc
+ ? Cast(this._props.linkDoc.link_anchor_12, Doc, null)
+ : undefined;
+
+ if (focusDoc) this._props.docView._props.focus(focusDoc, { instant: true });
+ LinkFollower.FollowLink(this._props.linkDoc, this._props.sourceDoc, false);
}
}
);
};
- deleteLink = (e: React.PointerEvent): void => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this.props.linkDoc))));
+ deleteLink = (e: React.PointerEvent): void => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this._props.linkDoc))));
@observable _hover = false;
render() {
- const destinationIcon = Doc.toIcon(this.props.destinationDoc) as any as IconProp;
+ const destinationIcon = Doc.toIcon(this._props.destinationDoc) as any as IconProp;
- const title = StrCast(this.props.destinationDoc.title).length > 18 ? StrCast(this.props.destinationDoc.title).substr(0, 14) + '...' : this.props.destinationDoc.title;
+ const title = StrCast(this._props.destinationDoc.title).length > 18 ? StrCast(this._props.destinationDoc.title).substr(0, 14) + '...' : this._props.destinationDoc.title;
const source =
- this.props.sourceDoc.type === DocumentType.RTF
- ? this.props.linkDoc.storedText
- ? StrCast(this.props.linkDoc.storedText).length > 17
- ? StrCast(this.props.linkDoc.storedText).substr(0, 18)
- : this.props.linkDoc.storedText
+ this._props.sourceDoc.type === DocumentType.RTF
+ ? this._props.linkDoc.storedText
+ ? StrCast(this._props.linkDoc.storedText).length > 17
+ ? StrCast(this._props.linkDoc.storedText).substr(0, 18)
+ : this._props.linkDoc.storedText
: undefined
: undefined;
@@ -154,7 +164,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
style={{
fontSize: this._hover ? 'larger' : undefined,
fontWeight: this._hover ? 'bold' : undefined,
- background: LinkManager.currentLink === this.props.linkDoc ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+ background: LinkManager.currentLink === this._props.linkDoc ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
}}>
<div className="linkMenu-item-content expand-two">
<div
@@ -172,12 +182,12 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
className="linkMenu-text"
onPointerLeave={LinkDocPreview.Clear}
onPointerEnter={e =>
- this.props.linkDoc &&
- this.props.clearLinkEditor &&
+ this._props.linkDoc &&
+ this._props.clearLinkEditor &&
LinkDocPreview.SetLinkInfo({
- docProps: this.props.docView.props,
- linkSrc: this.props.sourceDoc,
- linkDoc: this.props.linkDoc,
+ docProps: this._props.docView._props,
+ linkSrc: this._props.sourceDoc,
+ linkDoc: this._props.linkDoc,
showHeader: false,
location: [(this._drag.current?.getBoundingClientRect().left ?? 100) + 40, (this._drag.current?.getBoundingClientRect().top ?? e.clientY) + 25],
noPreview: false,
@@ -194,10 +204,10 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
<FontAwesomeIcon className="destination-icon" icon={destinationIcon} size="sm" />
</div>
<p className="linkMenu-destination-title">
- {this.props.linkDoc.linksToAnnotation && Cast(this.props.destinationDoc.data, WebField)?.url.href === this.props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}
+ {this._props.linkDoc.linksToAnnotation && Cast(this._props.destinationDoc.data, WebField)?.url.href === this._props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}
</p>
</div>
- {!this.props.linkDoc.link_description ? null : <p className="linkMenu-description">{StrCast(this.props.linkDoc.link_description).split('\n')[0].substring(0, 50)}</p>}
+ {!this._props.linkDoc.link_description ? null : <p className="linkMenu-description">{StrCast(this._props.linkDoc.link_description).split('\n')[0].substring(0, 50)}</p>}
</div>
<div className="linkMenu-item-buttons">
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 1fb26c99b..0c671f7e3 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,14 +1,14 @@
import * as React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, observable, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, override, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { DateField } from '../../../fields/DateField';
import { Doc } from '../../../fields/Doc';
import { ComputedField } from '../../../fields/ScriptField';
import { Cast, DateCast, NumCast } from '../../../fields/Types';
import { AudioField, nullAudio } from '../../../fields/URLField';
-import { emptyFunction, formatTime, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { copyProps, emptyFunction, formatTime, returnFalse, setupMoveUpEvents } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { Networking } from '../../Network';
import { DragManager } from '../../util/DragManager';
@@ -49,10 +49,22 @@ export enum media_state {
}
@observer
-export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(AudioBox, fieldKey);
}
+
+ _prevProps: React.PropsWithChildren<FieldViewProps>;
+ @override _props: React.PropsWithChildren<FieldViewProps>;
+ constructor(props: React.PropsWithChildren<FieldViewProps>) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+
+ componentDidUpdate() {
+ copyProps(this);
+ }
public static Enabled = false;
static topControlsHeight = 30; // height of upper controls above timeline
@@ -68,7 +80,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
_stream: MediaStream | undefined; // passed to MediaRecorder, records device input audio
_play: any = null; // timeout for playback
- @observable _stackedTimeline: CollectionStackedTimeline | null | undefined; // CollectionStackedTimeline ref
+ @observable _stackedTimeline: CollectionStackedTimeline | null | undefined = undefined; // CollectionStackedTimeline ref
@observable _finished: boolean = false; // has playback reached end of clip
@observable _volume: number = 1;
@observable _muted: boolean = false;
@@ -84,7 +96,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// if you get rid of it and set the value to 0 the timeline and waveform will set their bounds incorrectly
@computed get miniPlayer() {
- return this.props.PanelHeight() < 50;
+ return this._props.PanelHeight() < 50;
} // used to collapse timeline when node is shrunk
@computed get links() {
return LinkManager.Links(this.dataDoc);
@@ -94,7 +106,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
@computed get path() {
// returns the path of the audio file
- const path = Cast(this.props.Document[this.fieldKey], AudioField, null)?.url.href || '';
+ const path = Cast(this.Document[this.fieldKey], AudioField, null)?.url.href || '';
return path === nullAudio ? '' : path;
}
set mediaState(value) {
@@ -115,7 +127,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@action
componentDidMount() {
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
if (this.path) {
this.mediaState = media_state.Paused;
this.setPlayheadTime(NumCast(this.layoutDoc.clipStart));
@@ -142,7 +154,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this.Document,
this.dataDoc,
this.annotationKey,
- this._ele?.currentTime || Cast(this.props.Document._layout_currentTimecode, 'number', null) || (this.mediaState === media_state.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined),
+ this._ele?.currentTime || Cast(this.Document._layout_currentTimecode, 'number', null) || (this.mediaState === media_state.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined),
undefined,
undefined,
addAsAnnotation
@@ -186,13 +198,16 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this._ele.play();
this.mediaState = media_state.Playing;
this.addCurrentlyPlaying();
- this._play = setTimeout(() => {
- // need to keep track of if end of clip is reached so on next play, clip restarts
- if (fullPlay) this._finished = true;
- // removes from currently playing if playback has reached end of range marker
- else this.removeCurrentlyPlaying();
- this.Pause();
- }, (end - start) * 1000);
+ this._play = setTimeout(
+ () => {
+ // need to keep track of if end of clip is reached so on next play, clip restarts
+ if (fullPlay) this._finished = true;
+ // removes from currently playing if playback has reached end of range marker
+ else this.removeCurrentlyPlaying();
+ this.Pause();
+ },
+ (end - start) * 1000
+ );
} else {
this.Pause();
}
@@ -202,7 +217,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// removes from currently playing display
@action
removeCurrentlyPlaying = () => {
- const docView = this.props.DocumentView?.();
+ const docView = this._props.DocumentView?.();
if (CollectionStackedTimeline.CurrentlyPlaying && docView) {
const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView);
index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
@@ -212,7 +227,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// adds doc to currently playing display
@action
addCurrentlyPlaying = () => {
- const docView = this.props.DocumentView?.();
+ const docView = this._props.DocumentView?.();
if (!CollectionStackedTimeline.CurrentlyPlaying) {
CollectionStackedTimeline.CurrentlyPlaying = [];
}
@@ -240,7 +255,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this._recorder.ondataavailable = async (e: any) => {
const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
if (!(result instanceof Error)) {
- this.props.Document[this.fieldKey] = new AudioField(result.accessPaths.agnostic.client);
+ this.Document[this.fieldKey] = new AudioField(result.accessPaths.agnostic.client);
}
};
this._recordStart = new Date().getTime();
@@ -373,7 +388,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
newDoc.overlayY = NumCast(this.Document.y) + NumCast(this.layoutDoc._height);
Doc.AddToMyOverlay(newDoc);
} else {
- this.props.addDocument?.(newDoc);
+ this._props.addDocument?.(newDoc);
}
}),
false
@@ -449,9 +464,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
};
@action
- timelineWhenChildContentsActiveChanged = (isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive));
+ timelineWhenChildContentsActiveChanged = (isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive));
- timelineScreenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -AudioBox.topControlsHeight);
+ timelineScreenToLocal = () => this._props.ScreenToLocalTransform().translate(0, -AudioBox.topControlsHeight);
setPlayheadTime = (time: number) => (this._ele!.currentTime /*= this.layoutDoc._layout_currentTimecode*/ = time);
@@ -460,8 +475,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
isActiveChild = () => this._isAnyChildContentActive;
// timeline dimensions
- timelineWidth = () => this.props.PanelWidth();
- timelineHeight = () => this.props.PanelHeight() - (AudioBox.topControlsHeight + AudioBox.bottomControlsHeight);
+ timelineWidth = () => this._props.PanelWidth();
+ timelineHeight = () => this._props.PanelHeight() - (AudioBox.topControlsHeight + AudioBox.bottomControlsHeight);
// ends trim, hides trim controls and displays new clip
@undoBatch
@@ -555,7 +570,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this._dropDisposer = DragManager.MakeDropTarget(
r,
(e, de) => {
- const [xp, yp] = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+ const [xp, yp] = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y);
de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData, xp);
},
this.layoutDoc,
@@ -597,7 +612,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
<div
className="audiobox-file"
style={{
- pointerEvents: this._isAnyChildContentActive || this.props.isContentActive() ? 'all' : 'none',
+ pointerEvents: this._isAnyChildContentActive || this._props.isContentActive() ? 'all' : 'none',
flexDirection: this.miniPlayer ? 'row' : 'column',
justifyContent: this.miniPlayer ? 'flex-start' : 'space-between',
}}>
@@ -670,7 +685,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
<div className="audiobox-timecodes">
<div className="timecode-current">{this.timeline && formatTime(Math.round(NumCast(this.layoutDoc._layout_currentTimecode) - NumCast(this.timeline.clipStart)))}</div>
{this.miniPlayer ? (
- <div>/</div>
+ <div />
) : (
<div className="bottom-controls-middle">
<FontAwesomeIcon icon="search-plus" />
@@ -699,13 +714,13 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return (
<CollectionStackedTimeline
ref={action((r: CollectionStackedTimeline | null) => (this._stackedTimeline = r))}
- {...this.props}
+ {...this._props}
CollectionFreeFormDocumentView={undefined}
dataFieldKey={this.fieldKey}
fieldKey={this.annotationKey}
dictationKey={this.fieldKey + '_dictation'}
mediaPath={this.path}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
startTag={'_timecodeToShow' /* audioStart */}
endTag={'_timecodeToHide' /* audioEnd */}
bringToFront={emptyFunction}
@@ -719,7 +734,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
ScreenToLocalTransform={this.timelineScreenToLocal}
Play={this.Play}
Pause={this.Pause}
- isContentActive={this.props.isContentActive}
+ isContentActive={this._props.isContentActive}
isAnyChildContentActive={this.isAnyChildContentActive}
playLink={this.playLink}
PanelWidth={this.timelineWidth}
@@ -734,7 +749,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return (
<audio
ref={this.setRef}
- className={`audiobox-control${this.props.isContentActive() ? '-interactive' : ''}`}
+ className={`audiobox-control${this._props.isContentActive() ? '-interactive' : ''}`}
onLoadedData={action(e => this._ele?.duration && this._ele?.duration !== Infinity && (this.dataDoc[this.fieldKey + '_duration'] = this._ele.duration))}>
<source src={this.path} type="audio/mpeg" />
Not supported.
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 624f28413..8e7a6914f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, observable, makeObservable, reaction, runInAction, override } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { List } from '../../../fields/List';
@@ -6,7 +6,7 @@ import { listSpec } from '../../../fields/Schema';
import { ComputedField } from '../../../fields/ScriptField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { numberRange, OmitKeys } from '../../../Utils';
+import { copyProps, numberRange, OmitKeys } from '../../../Utils';
import { DocumentManager } from '../../util/DocumentManager';
import { SelectionManager } from '../../util/SelectionManager';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
@@ -37,6 +37,13 @@ export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentView
}
@observer
export class CollectionFreeFormDocumentViewWrapper extends DocComponent<CollectionFreeFormDocumentViewWrapperProps & { fieldKey: string }>() implements CollectionFreeFormDocumentViewProps {
+ _prevProps: React.PropsWithChildren<CollectionFreeFormDocumentViewWrapperProps & { fieldKey: string }>;
+ @override _props: React.PropsWithChildren<CollectionFreeFormDocumentViewWrapperProps & { fieldKey: string }>;
+ constructor(props: React.PropsWithChildren<CollectionFreeFormDocumentViewWrapperProps & { fieldKey: string }>) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
@observable X = this.props.x;
@observable Y = this.props.y;
@observable Z = this.props.z;
@@ -64,11 +71,11 @@ export class CollectionFreeFormDocumentViewWrapper extends DocComponent<Collecti
w_X = () => this.X; // prettier-ignore
w_Y = () => this.Y; // prettier-ignore
w_Z = () => this.Z; // prettier-ignore
- w_ZIndex = () => this.ZIndex ?? NumCast(this.props.Document.zIndex); // prettier-ignore
- w_Rotation = () => this.Rotation ?? NumCast(this.props.Document._rotation); // prettier-ignore
+ w_ZIndex = () => this.ZIndex ?? NumCast(this.Document.zIndex); // prettier-ignore
+ w_Rotation = () => this.Rotation ?? NumCast(this.Document._rotation); // prettier-ignore
w_Opacity = () => this.Opacity; // prettier-ignore
- w_BackgroundColor = () => this.BackgroundColor ?? Cast(this.props.Document._backgroundColor, 'string', null); // prettier-ignore
- w_Color = () => this.Color ?? Cast(this.props.Document._color, 'string', null); // prettier-ignore
+ w_BackgroundColor = () => this.BackgroundColor ?? Cast(this.Document._backgroundColor, 'string', null); // prettier-ignore
+ w_Color = () => this.Color ?? Cast(this.Document._color, 'string', null); // prettier-ignore
w_Highlight = () => this.Highlight; // prettier-ignore
w_Width = () => this.Width; // prettier-ignore
w_Height = () => this.Height; // prettier-ignore
@@ -76,17 +83,18 @@ export class CollectionFreeFormDocumentViewWrapper extends DocComponent<Collecti
w_Transition = () => this.Transition; // prettier-ignore
w_DataTransition = () => this.DataTransition; // prettier-ignore
- PanelWidth = () => this.props.autoDim ? this.props.PanelWidth?.() : this.Width; // prettier-ignore
- PanelHeight = () => this.props.autoDim ? this.props.PanelHeight?.() : this.Height; // prettier-ignore
- @action
+ PanelWidth = () => this._props.autoDim ? this._props.PanelWidth?.() : this.Width; // prettier-ignore
+ PanelHeight = () => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
+
componentDidUpdate() {
- this.WrapperKeys.forEach(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower]));
+ copyProps(this);
+ this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
}
render() {
const layoutProps = this.WrapperKeys.reduce((val, keys) => [(val['w_' + keys.upper] = (this as any)['w_' + keys.upper]), val][1], {} as { [key: string]: Function });
return (
<CollectionFreeFormDocumentView
- {...OmitKeys(this.props, this.WrapperKeys.map(keys => keys.lower) ).omit} // prettier-ignore
+ {...OmitKeys(this._props, this.WrapperKeys.map(keys => keys.lower) ).omit} // prettier-ignore
{...layoutProps}
PanelWidth={this.PanelWidth}
PanelHeight={this.PanelHeight}
@@ -115,7 +123,7 @@ export interface CollectionFreeFormDocumentViewProps {
}
@observer
-export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & DocumentViewProps & { fieldKey: string }>() {
+export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & DocumentViewProps>() {
get displayName() { // this makes mobx trace() statements more descriptive
return 'CollectionFreeFormDocumentView(' + this.Document.title + ')';
} // prettier-ignore
@@ -135,33 +143,46 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
public static animStringFields = ['backgroundColor', 'color', 'fillColor']; // fields that are configured to be animatable using animation frames
public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames
+ _props: React.PropsWithChildren<CollectionFreeFormDocumentViewProps & DocumentViewProps>;
+ constructor(props: React.PropsWithChildren<CollectionFreeFormDocumentViewProps & DocumentViewProps>) {
+ super(props);
+ this._props = Object.assign({}, props);
+ makeObservable(this);
+ }
+
get CollectionFreeFormView() {
- return this.props.CollectionFreeFormView;
+ return this._props.CollectionFreeFormView;
}
styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
if (doc === this.layoutDoc) {
switch (property) {
- case StyleProp.Opacity: return this.props.w_Opacity(); // only change the opacity for this specific document, not its children
- case StyleProp.BackgroundColor: return this.props.w_BackgroundColor();
- case StyleProp.Color: return this.props.w_Color();
+ case StyleProp.Opacity: return this._props.w_Opacity(); // only change the opacity for this specific document, not its children
+ case StyleProp.BackgroundColor: return this._props.w_BackgroundColor();
+ case StyleProp.Color: return this._props.w_Color();
} // prettier-ignore
}
- return this.props.styleProvider?.(doc, props, property);
+ return this._props.styleProvider?.(doc, props, property);
};
public static getValues(doc: Doc, time: number, fillIn: boolean = true) {
- return CollectionFreeFormDocumentView.animFields.reduce((p, val) => {
- p[val.key] = Cast(doc[`${val.key}_indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number);
- return p;
- }, {} as { [val: string]: Opt<number> });
+ return CollectionFreeFormDocumentView.animFields.reduce(
+ (p, val) => {
+ p[val.key] = Cast(doc[`${val.key}_indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number);
+ return p;
+ },
+ {} as { [val: string]: Opt<number> }
+ );
}
public static getStringValues(doc: Doc, time: number) {
- return CollectionFreeFormDocumentView.animStringFields.reduce((p, val) => {
- p[val] = Cast(doc[`${val}_indexed`], listSpec('string'), [StrCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as string);
- return p;
- }, {} as { [val: string]: Opt<string> });
+ return CollectionFreeFormDocumentView.animStringFields.reduce(
+ (p, val) => {
+ p[val] = Cast(doc[`${val}_indexed`], listSpec('string'), [StrCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as string);
+ return p;
+ },
+ {} as { [val: string]: Opt<string> }
+ );
}
public static setStringValues(time: number, d: Doc, vals: { [val: string]: Opt<string> }) {
@@ -213,7 +234,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
@action public float = () => {
const topDoc = this.Document;
- const containerDocView = this.props.docViewPath().lastElement();
+ const containerDocView = this._props.docViewPath().lastElement();
const screenXf = containerDocView?.screenToLocalTransform();
if (screenXf) {
SelectionManager.DeselectAll();
@@ -222,8 +243,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
topDoc.z = 0;
topDoc.x = spt[0];
topDoc.y = spt[1];
- this.props.removeDocument?.(topDoc);
- this.props.addDocTab(topDoc, OpenWhere.inParentFromScreen);
+ this._props.removeDocument?.(topDoc);
+ this._props.addDocTab(topDoc, OpenWhere.inParentFromScreen);
} else {
const spt = this.screenToLocalTransform().inverse().transformPoint(0, 0);
const fpt = screenXf.transformPoint(spt[0], spt[1]);
@@ -236,15 +257,15 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
};
nudge = (x: number, y: number) => {
- const [locX, locY] = this.props.ScreenToLocalTransform().transformDirection(x, y);
- this.props.Document.x = this.props.w_X() + locX;
- this.props.Document.y = this.props.w_Y() + locY;
+ const [locX, locY] = this._props.ScreenToLocalTransform().transformDirection(x, y);
+ this._props.Document.x = this._props.w_X() + locX;
+ this._props.Document.y = this._props.w_Y() + locY;
};
screenToLocalTransform = () =>
- this.props
+ this._props
.ScreenToLocalTransform()
- .translate(-this.props.w_X(), -this.props.w_Y())
- .rotateDeg(-(this.props.w_Rotation?.() || 0));
+ .translate(-this._props.w_X(), -this._props.w_Y())
+ .rotateDeg(-(this._props.w_Rotation?.() || 0));
returnThis = () => this;
/// this indicates whether the doc view is activated because of its relationshop to a group
@@ -255,25 +276,25 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
isGroupActive = () => {
if (this.CollectionFreeFormView.isAnyChildContentActive()) return undefined;
const isGroup = this.dataDoc.isGroup && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
- return isGroup ? (this.props.isDocumentActive?.() ? 'group' : this.props.isGroupActive?.() ? 'child' : 'inactive') : this.props.isGroupActive?.() ? 'child' : undefined;
+ return isGroup ? (this._props.isDocumentActive?.() ? 'group' : this._props.isGroupActive?.() ? 'child' : 'inactive') : this._props.isGroupActive?.() ? 'child' : undefined;
};
public static CollectionFreeFormDocViewClassName = 'collectionFreeFormDocumentView-container';
render() {
TraceMobx();
- const passOnProps = OmitKeys(this.props, Object.keys(this.props).filter(key => key.startsWith('w_'))).omit; // prettier-ignore
+ const passOnProps = OmitKeys(this._props, Object.keys(this._props).filter(key => key.startsWith('w_'))).omit; // prettier-ignore
return (
<div
className={CollectionFreeFormDocumentView.CollectionFreeFormDocViewClassName}
style={{
- width: this.props.PanelWidth(),
- height: this.props.PanelHeight(),
- transform: `translate(${this.props.w_X()}px, ${this.props.w_Y()}px) rotate(${NumCast(this.props.w_Rotation?.())}deg)`,
- transition: this.props.w_Transition?.() ?? (this.props.w_DataTransition?.() || this.props.w_Transition?.()),
- zIndex: this.props.w_ZIndex?.(),
- display: this.props.w_Width?.() ? undefined : 'none',
+ width: this._props.PanelWidth(),
+ height: this._props.PanelHeight(),
+ transform: `translate(${this._props.w_X()}px, ${this._props.w_Y()}px) rotate(${NumCast(this._props.w_Rotation?.())}deg)`,
+ transition: this._props.w_Transition?.() ?? (this._props.w_DataTransition?.() || this._props.w_Transition?.()),
+ zIndex: this._props.w_ZIndex?.(),
+ display: this._props.w_Width?.() ? undefined : 'none',
}}>
- {this.props.RenderCutoffProvider(this.props.Document) ? (
- <div style={{ position: 'absolute', width: this.props.PanelWidth(), height: this.props.PanelHeight(), background: 'lightGreen' }} />
+ {this._props.RenderCutoffProvider(this._props.Document) ? (
+ <div style={{ position: 'absolute', width: this._props.PanelWidth(), height: this._props.PanelHeight(), background: 'lightGreen' }} />
) : (
<DocumentView {...passOnProps} CollectionFreeFormDocumentView={this.returnThis} styleProvider={this.styleProvider} ScreenToLocalTransform={this.screenToLocalTransform} isGroupActive={this.isGroupActive} />
)}
diff --git a/src/client/views/nodes/ColorBox.scss b/src/client/views/nodes/ColorBox.scss
deleted file mode 100644
index d5f2a7ec7..000000000
--- a/src/client/views/nodes/ColorBox.scss
+++ /dev/null
@@ -1,22 +0,0 @@
-.colorBox-container, .colorBox-container-interactive {
- width:100%;
- height:100%;
- position: relative;
- pointer-events: none;
- transform-origin: top left;
-
- .sketch-picker {
- div {
- cursor: crosshair;
- }
- .flexbox-fix {
- cursor: pointer;
- div {
- cursor:pointer;
- }
- }
- }
-}
-.colorBox-container-interactive {
- pointer-events:all;
-} \ No newline at end of file
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
deleted file mode 100644
index b4ba51814..000000000
--- a/src/client/views/nodes/ColorBox.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import * as React from 'react';
-import { action } from 'mobx';
-import { observer } from 'mobx-react';
-import { ColorResult, SketchPicker } from 'react-color';
-import { Doc } from '../../../fields/Doc';
-import { InkTool } from '../../../fields/InkField';
-import { NumCast, StrCast } from '../../../fields/Types';
-import { DashColor } from '../../../Utils';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { SelectionManager } from '../../util/SelectionManager';
-import { undoBatch } from '../../util/UndoManager';
-import { ViewBoxBaseComponent } from '../DocComponent';
-import { ActiveInkColor, ActiveInkWidth, SetActiveInkColor, SetActiveInkWidth } from '../InkingStroke';
-import './ColorBox.scss';
-import { FieldView, FieldViewProps } from './FieldView';
-import { RichTextMenu } from './formattedText/RichTextMenu';
-
-@observer
-export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
- public static LayoutString(fieldKey: string) {
- return FieldView.LayoutString(ColorBox, fieldKey);
- }
-
- @undoBatch
- @action
- static switchColor(color: ColorResult) {
- SetActiveInkColor(color.hex);
-
- SelectionManager.Views().map(view => {
- const targetDoc =
- view.props.Document.dragFactory instanceof Doc
- ? view.props.Document.dragFactory
- : view.props.Document.layout instanceof Doc
- ? view.props.Document.layout
- : view.props.Document.isTemplateForField
- ? view.props.Document
- : Doc.GetProto(view.props.Document);
- if (targetDoc) {
- if (view.props.LayoutTemplate?.() || view.props.LayoutTemplateString) {
- // this situation typically occurs when you have a link dot
- targetDoc.backgroundColor = color.hex; // bcz: don't know how to change the color of an inline template...
- } else if (RichTextMenu.Instance?.TextViewFieldKey && window.getSelection()?.toString() !== '') {
- Doc.Layout(view.props.Document)[RichTextMenu.Instance.TextViewFieldKey + '-color'] = color.hex;
- } else {
- Doc.Layout(view.props.Document)._backgroundColor = color.hex + (color.rgb.a ? Math.round(color.rgb.a * 255).toString(16) : ''); // '_backgroundColor' is template specific. 'backgroundColor' would apply to all templates, but has no UI at the moment
- }
- }
- });
- }
-
- render() {
- const scaling = Math.min(this.layoutDoc.layout_fitWidth ? 10000 : this.props.PanelHeight() / NumCast(this.Document._height), this.props.PanelWidth() / NumCast(this.Document._width));
- return (
- <div
- className={`colorBox-container${this.props.isContentActive() ? '-interactive' : ''}`}
- onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
- onClick={e => e.stopPropagation()}
- style={{ transform: `scale(${scaling})`, width: `${100 * scaling}%`, height: `${100 * scaling}%` }}>
- <SketchPicker
- onChange={c => Doc.ActiveTool === InkTool.None && ColorBox.switchColor(c)}
- color={StrCast(SelectionManager.Views()?.[0]?.Document?._backgroundColor, ActiveInkColor())}
- presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
- />
-
- <div style={{ width: this.props.PanelWidth() / scaling, display: 'flex', paddingTop: '10px' }}>
- <div> {ActiveInkWidth()}</div>
- <input
- type="range"
- defaultValue={ActiveInkWidth()}
- min={1}
- max={100}
- onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
- SetActiveInkWidth(e.target.value);
- SelectionManager.Views()
- .filter(i => StrCast(i.Document.type) === DocumentType.INK)
- .map(i => (i.Document.stroke_width = Number(e.target.value)));
- }}
- />
- </div>
- </div>
- );
- }
-}
-
-ScriptingGlobals.add(function interpColors(c1: string, c2: string, weight = 0.5) {
- return DashColor(c1).mix(DashColor(c2), weight);
-});
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 0e766e5f0..33be87f46 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -77,7 +77,7 @@ export class TableBox extends React.Component<TableBoxProps> {
};
@computed get viewScale() {
- return this.props.docView?.()?.props.ScreenToLocalTransform().Scale || 1;
+ return this.props.docView?.()?._props.ScreenToLocalTransform().Scale || 1;
}
@computed get rowHeight() {
console.log('scale = ' + this.viewScale + ' table = ' + this._tableHeight + ' ids = ' + this._tableDataIds.length);
@@ -162,8 +162,6 @@ export class TableBox extends React.Component<TableBoxProps> {
};
render() {
- console.log(this.endID);
- trace();
if (this._tableData.length > 0) {
return (
<div
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 54cfba506..82d346206 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,4 +1,4 @@
-import { computed } from 'mobx';
+import { computed, makeObservable, observable, runInAction, untracked } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, Opt } from '../../../fields/Doc';
import { AclPrivate, DocData } from '../../../fields/DocSymbols';
@@ -18,7 +18,6 @@ import { SearchBox } from '../search/SearchBox';
import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo';
import { YoutubeBox } from './../../apis/youtube/YoutubeBox';
import { AudioBox } from './AudioBox';
-import { ColorBox } from './ColorBox';
import { ComparisonBox } from './ComparisonBox';
import { DataVizBox } from './DataVizBox/DataVizBox';
import { DocumentViewProps } from './DocumentView';
@@ -46,7 +45,7 @@ import { PresBox } from './trails/PresBox';
import { VideoBox } from './VideoBox';
import { WebBox } from './WebBox';
import * as React from 'react';
-import XRegExp = require('xregexp');
+import * as XRegExp from 'xregexp';
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -123,30 +122,42 @@ export class DocumentContentsView extends React.Component<
layout_fieldKey: string;
}
> {
+ @observable _props!: DocumentViewProps &
+ FieldViewProps & {
+ setHeight?: (height: number) => void;
+ layout_fieldKey: string;
+ };
+ constructor(props: any) {
+ super(props);
+ this._props = props;
+ makeObservable(this);
+ }
+
+ componentDidUpdate(prevProps: Readonly<DocumentViewProps & { setHeight?: ((height: number) => void) | undefined; layout_fieldKey: string }>, prevState: Readonly<{}>, snapshot?: any): void {
+ // untracked(() => (this._props = this.props));
+ // Object.keys(prevProps).forEach(pkey => (prevProps as any)[pkey] !== (this._props as any)[pkey] && console.log(pkey + ' ' + (prevProps as any)[pkey] + ' ' + (this._props as any)[pkey]));
+ }
+
@computed get layout(): string {
TraceMobx();
- if (this.props.LayoutTemplateString) return this.props.LayoutTemplateString;
+ if (this._props.LayoutTemplateString) return this._props.LayoutTemplateString;
if (!this.layoutDoc) return '<p>awaiting layout</p>';
- if (this.props.layout_fieldKey === 'layout_keyValue') return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString());
- const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layout_fieldKey ? this.props.layout_fieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
- if (layout === undefined) return this.props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString();
+ if (this._props.layout_fieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString());
+ const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layout_fieldKey ? this._props.layout_fieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
+ if (layout === undefined) return this._props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString();
if (typeof layout === 'string') return layout;
return '<p>Loading layout</p>';
}
- componentDidUpdate(prevProps: Readonly<DocumentViewProps & { setHeight?: ((height: number) => void) | undefined; layout_fieldKey: string }>, prevState: Readonly<{}>, snapshot?: any): void {
- Object.keys(prevProps).forEach(pkey => (prevProps as any)[pkey] !== (this.props as any)[pkey] && console.log(pkey + ' ' + (prevProps as any)[pkey] + ' ' + (this.props as any)[pkey]));
- }
-
get layoutDoc() {
// bcz: replaced this with below : is it correct? change was made to accommodate passing fieldKey's from a layout script
- // const template: Doc = this.props.LayoutTemplate?.() || Doc.Layout(this.props.Document, this.props.layout_fieldKey ? Cast(this.props.Document[this.props.layout_fieldKey], Doc, null) : undefined);
+ // const template: Doc = this._props.LayoutTemplate?.() || Doc.Layout(this._props.Document, this._props.layout_fieldKey ? Cast(this._props.Document[this._props.layout_fieldKey], Doc, null) : undefined);
const template: Doc =
- this.props.LayoutTemplate?.() ||
- (this.props.LayoutTemplateString && this.props.Document) ||
- (this.props.layout_fieldKey && StrCast(this.props.Document[this.props.layout_fieldKey]) && this.props.Document) ||
- Doc.Layout(this.props.Document, this.props.layout_fieldKey ? Cast(this.props.Document[this.props.layout_fieldKey], Doc, null) : undefined);
- return Doc.expandTemplateLayout(template, this.props.Document);
+ this._props.LayoutTemplate?.() ||
+ (this._props.LayoutTemplateString && this._props.Document) ||
+ (this._props.layout_fieldKey && StrCast(this._props.Document[this._props.layout_fieldKey]) && this._props.Document) ||
+ Doc.Layout(this._props.Document, this._props.layout_fieldKey ? Cast(this._props.Document[this._props.layout_fieldKey], Doc, null) : undefined);
+ return Doc.expandTemplateLayout(template, this._props.Document);
}
CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings {
@@ -165,10 +176,10 @@ export class DocumentContentsView extends React.Component<
'onPointerDown',
'onPointerUp',
];
- const templateDataDoc = this.props.TemplateDataDocument ?? (this.layoutDoc !== this.props.Document ? this.props.Document[DocData] : undefined);
+ const templateDataDoc = this._props.TemplateDataDocument ?? (this.layoutDoc !== this._props.Document ? this._props.Document[DocData] : undefined);
const list: BindingProps & React.DetailedHTMLProps<React.HtmlHTMLAttributes<HTMLDivElement>, HTMLDivElement> = {
- ...this.props,
- Document: this.layoutDoc ?? this.props.Document,
+ ...this._props,
+ Document: this.layoutDoc ?? this._props.Document,
TemplateDataDocument: templateDataDoc instanceof Promise ? undefined : templateDataDoc,
onClick: onClick as any as React.MouseEventHandler, // pass onClick script as if it were a real function -- it will be interpreted properly in the HTMLtag
onInput: onInput as any as React.FormEventHandler,
@@ -181,7 +192,7 @@ export class DocumentContentsView extends React.Component<
}
// componentWillUpdate(oldProps: any, newState: any) {
- // // console.log("willupdate", oldProps, this.props); // bcz: if you get a message saying something invalidated because reactive props changed, then this method allows you to figure out which prop changed
+ // // console.log("willupdate", oldProps, this._props); // bcz: if you get a message saying something invalidated because reactive props changed, then this method allows you to figure out which prop changed
// }
@computed get renderData() {
@@ -190,13 +201,13 @@ export class DocumentContentsView extends React.Component<
// replace code content with a script >{content}< as in <HTMLdiv>{this.title}</HTMLdiv>
const replacer = (match: any, prefix: string, expr: string, postfix: string, offset: any, string: any) => {
- return prefix + ((ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this.props.Document }).result as string) || '') + postfix;
+ return prefix + ((ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this._props.Document }).result as string) || '') + postfix;
};
layoutFrame = layoutFrame.replace(/(>[^{]*)[^=]\{([^.'][^<}]+)\}([^}]*<)/g, replacer);
// replace HTML<tag> with corresponding HTML tag as in: <HTMLdiv> becomes <HTMLtag Document={props.Document} htmltag='div'>
const replacer2 = (match: any, p1: string, offset: any, string: any) => {
- return `<HTMLtag Document={props.Document} scaling='${this.props.NativeDimScaling?.() || 1}' htmltag='${p1}'`;
+ return `<HTMLtag Document={props.Document} scaling='${this._props.NativeDimScaling?.() || 1}' htmltag='${p1}'`;
};
layoutFrame = layoutFrame.replace(/<HTML([a-zA-Z0-9_-]+)/g, replacer2);
@@ -228,7 +239,7 @@ export class DocumentContentsView extends React.Component<
TraceMobx();
const { bindings, layoutFrame } = this.renderData;
- return this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate ? null : (
+ return this._props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate ? null : (
<ObserverJsxParser
key={42}
blacklistedAttrs={emptyPath}
@@ -256,7 +267,6 @@ export class DocumentContentsView extends React.Component<
PresElementBox,
SearchBox,
FunctionPlotBox,
- ColorBox,
DashWebRTCVideo,
LinkAnchorBox,
InkingStroke,
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 90b044374..19b43fd52 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -1,10 +1,10 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../../fields/Doc';
import { StrCast } from '../../../fields/Types';
-import { emptyFunction, returnFalse, setupMoveUpEvents, StopEvent } from '../../../Utils';
+import { copyProps, emptyFunction, returnFalse, setupMoveUpEvents, StopEvent } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { Hypothesis } from '../../util/HypothesisUtils';
@@ -16,7 +16,6 @@ import { LinkDescriptionPopup } from './LinkDescriptionPopup';
import { TaskCompletionBox } from './TaskCompletedBox';
import { PinProps } from './trails';
import * as React from 'react';
-import * as _ from 'lodash';
interface DocumentLinksButtonProps {
View: DocumentView;
@@ -29,25 +28,51 @@ interface DocumentLinksButtonProps {
scaling?: () => number; // how uch doc is scaled so that link buttons can invert it
hideCount?: () => boolean;
}
+
+export class DocButtonState {
+ @observable public StartLink: Doc | undefined = undefined; //origin's Doc, if defined
+ @observable public StartLinkView: DocumentView | undefined = undefined;
+ @observable public AnnotationId: string | undefined = undefined;
+ @observable public AnnotationUri: string | undefined = undefined;
+ @observable public LinkEditorDocView: DocumentView | undefined = undefined;
+ public static _instance: DocButtonState | undefined;
+ public static get Instance() {
+ return DocButtonState._instance ?? (DocButtonState._instance = new DocButtonState());
+ }
+ constructor() {
+ makeObservable(this);
+ }
+}
@observer
export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
private _linkButton = React.createRef<HTMLDivElement>();
- @observable public static StartLink: Doc | undefined; //origin's Doc, if defined
- @observable public static StartLinkView: DocumentView | undefined;
- @observable public static AnnotationId: string | undefined;
- @observable public static AnnotationUri: string | undefined;
- @observable public static LinkEditorDocView: DocumentView | undefined;
+ public static get StartLink() { return DocButtonState.Instance.StartLink; } // prettier-ignore
+ public static set StartLink(value) { runInAction(() => (DocButtonState.Instance.StartLink = value)); } // prettier-ignore
+ @observable public static StartLinkView: DocumentView | undefined = undefined;
+ @observable public static AnnotationId: string | undefined = undefined;
+ @observable public static AnnotationUri: string | undefined = undefined;
+
+ _prevProps: React.PropsWithChildren<DocumentLinksButtonProps>;
+ @observable _props: React.PropsWithChildren<DocumentLinksButtonProps>;
+ constructor(props: React.PropsWithChildren<DocumentLinksButtonProps>) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+
+ componentDidUpdate() {
+ copyProps(this);
+ }
- @action
@undoBatch
onLinkButtonMoved = (e: PointerEvent) => {
- if (this.props.InMenu && this.props.StartLink) {
+ if (this._props.InMenu && this._props.StartLink) {
if (this._linkButton.current !== null) {
const linkDrag = UndoManager.StartBatch('Drag Link');
- this.props.View &&
- DragManager.StartLinkDrag(this._linkButton.current, this.props.View, this.props.View.ComponentView?.getAnchor, e.pageX, e.pageY, {
+ this._props.View &&
+ DragManager.StartLinkDrag(this._linkButton.current, this._props.View, this._props.View.ComponentView?.getAnchor, e.pageX, e.pageY, {
dragComplete: dropEv => {
- if (this.props.View && dropEv.linkDocument) {
+ if (this._props.View && dropEv.linkDocument) {
// dropEv.linkDocument equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
!dropEv.linkDocument.link_relationship && (Doc.GetProto(dropEv.linkDocument).link_relationship = 'hyperlink');
}
@@ -69,11 +94,11 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
this.onLinkButtonMoved,
emptyFunction,
action((e, doubleTap) => {
- doubleTap && DocumentView.showBackLinks(this.props.View.Document);
+ doubleTap && DocumentView.showBackLinks(this._props.View.Document);
}),
undefined,
undefined,
- action(() => (DocumentLinksButton.LinkEditorDocView = this.props.View))
+ action(() => (DocButtonState.Instance.LinkEditorDocView = this._props.View))
);
};
@@ -84,33 +109,32 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
this.onLinkButtonMoved,
emptyFunction,
action((e, doubleTap) => {
- if (doubleTap && this.props.InMenu && this.props.StartLink) {
- //action(() => Doc.BrushDoc(this.props.View.Document));
- if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
+ if (doubleTap && this._props.InMenu && this._props.StartLink) {
+ //action(() => Doc.BrushDoc(this._props.View.Document));
+ if (DocumentLinksButton.StartLink === this._props.View.Document) {
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
} else {
- DocumentLinksButton.StartLink = this.props.View.props.Document;
- DocumentLinksButton.StartLinkView = this.props.View;
+ DocumentLinksButton.StartLink = this._props.View.Document;
+ DocumentLinksButton.StartLinkView = this._props.View;
}
}
})
);
};
- @action
@undoBatch
onLinkClick = (e: React.MouseEvent): void => {
- if (this.props.InMenu && this.props.StartLink) {
+ if (this._props.InMenu && this._props.StartLink) {
DocumentLinksButton.AnnotationId = undefined;
DocumentLinksButton.AnnotationUri = undefined;
- if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
+ if (DocumentLinksButton.StartLink === this._props.View.Document) {
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
} else {
//if this LinkButton's Document is undefined
- DocumentLinksButton.StartLink = this.props.View.props.Document;
- DocumentLinksButton.StartLinkView = this.props.View;
+ DocumentLinksButton.StartLink = this._props.View.Document;
+ DocumentLinksButton.StartLinkView = this._props.View;
}
}
};
@@ -121,7 +145,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
e,
returnFalse,
emptyFunction,
- action(e => DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View))
+ action(e => DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this._props.View.Document, true, this._props.View))
);
};
@@ -133,7 +157,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
DocumentLinksButton.StartLinkView = undefined;
DocumentLinksButton.AnnotationId = undefined;
DocumentLinksButton.AnnotationUri = undefined;
- //!this.props.StartLink
+ //!this._props.StartLink
} else if (startLink !== endLink) {
endLink = endLinkView?.docView?._componentView?.getAnchor?.(true, pinProps) || endLink;
startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.(true) || startLink;
@@ -190,8 +214,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
@computed get filteredLinks() {
const results = [] as Doc[];
- const filters = this.props.View.props.childFilters();
- Array.from(new Set<Doc>(this.props.View.allLinks)).forEach(link => {
+ const filters = this._props.View._props.childFilters();
+ Array.from(new Set<Doc>(this._props.View.allLinks)).forEach(link => {
if (DocUtils.FilterDocs([link], filters, []).length || DocUtils.FilterDocs([link.link_anchor_2 as Doc], filters, []).length || DocUtils.FilterDocs([link.link_anchor_1 as Doc], filters, []).length) {
results.push(link);
}
@@ -206,8 +230,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
*/
@computed get linkButtonInner() {
const btnDim = 30;
- const isActive = DocumentLinksButton.StartLink === this.props.View.props.Document && this.props.StartLink;
- const scaling = Math.min(1, this.props.scaling?.() || 1);
+ const isActive = DocumentLinksButton.StartLink === this._props.View.Document && this._props.StartLink;
+ const scaling = Math.min(1, this._props.scaling?.() || 1);
const showLinkCount = (onHover?: boolean, offset?: boolean) => (
<div
className="documentLinksButton-showCount"
@@ -221,16 +245,16 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
<span style={{ width: '100%', display: 'inline-block', textAlign: 'center' }}>{Array.from(this.filteredLinks).length}</span>
</div>
);
- return this.props.ShowCount ? (
- showLinkCount(this.props.OnHover, this.props.Bottom)
+ return this._props.ShowCount ? (
+ showLinkCount(this._props.OnHover, this._props.Bottom)
) : (
<div className="documentLinksButton-menu">
- {this.props.StartLink ? ( //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again
+ {this._props.StartLink ? ( //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again
<div className={`documentLinksButton ${isActive ? `startLink` : ``}`} ref={this._linkButton} onPointerDown={isActive ? StopEvent : this.onLinkButtonDown} onClick={isActive ? this.clearLinks : this.onLinkClick}>
<FontAwesomeIcon className="documentdecorations-icon" icon="link" />
</div>
) : null}
- {!this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? ( //if the origin node is not this node
+ {!this._props.StartLink && DocumentLinksButton.StartLink !== this._props.View.Document ? ( //if the origin node is not this node
<div className={'documentLinksButton-endLink'} ref={this._linkButton} onPointerDown={DocumentLinksButton.StartLink && this.completeLink}>
<FontAwesomeIcon className="documentdecorations-icon" icon="link" />
</div>
@@ -240,21 +264,21 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
}
render() {
- if (this.props.hideCount?.()) return null;
- const menuTitle = this.props.StartLink ? 'Drag or tap to start link' : 'Tap to complete link';
+ if (this._props.hideCount?.()) return null;
+ const menuTitle = this._props.StartLink ? 'Drag or tap to start link' : 'Tap to complete link';
const buttonTitle = 'Tap to view links; double tap to open link collection';
- const title = this.props.ShowCount ? buttonTitle : menuTitle;
+ const title = this._props.ShowCount ? buttonTitle : menuTitle;
//render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu
- return !Array.from(this.filteredLinks).length && !this.props.AlwaysOn ? null : (
+ return !Array.from(this.filteredLinks).length && !this._props.AlwaysOn ? null : (
<div
className="documentLinksButton-wrapper"
style={{
- position: this.props.InMenu ? 'relative' : 'absolute',
+ position: this._props.InMenu ? 'relative' : 'absolute',
top: 0,
pointerEvents: 'none',
}}>
- {DocumentLinksButton.LinkEditorDocView ? this.linkButtonInner : <Tooltip title={<div className="dash-tooltip">{title}</div>}>{this.linkButtonInner}</Tooltip>}
+ {DocButtonState.Instance.LinkEditorDocView ? this.linkButtonInner : <Tooltip title={<div className="dash-tooltip">{title}</div>}>{this.linkButtonInner}</Tooltip>}
</div>
);
}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f2a910023..b3acb08e2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,9 +1,9 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { Dropdown, DropdownType, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction, untracked } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
-import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
+// import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
import { AclPrivate, Animation, AudioPlay, DocViews } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -15,7 +15,7 @@ import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { AudioField } from '../../../fields/URLField';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
-import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
+import { copyProps, emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { DocServer } from '../../DocServer';
import { DocOptions, Docs, DocUtils, FInfo } from '../../documents/Documents';
@@ -254,6 +254,19 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
private _titleRef = React.createRef<EditableView>();
private _dropDisposer?: DragManager.DragDropDisposer;
+ @override _props: DocumentViewInternalProps;
+ _prevProps: DocumentViewInternalProps;
+
+ constructor(props: DocumentViewInternalProps) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+
+ componentDidUpdate() {
+ // untracked(() => (this._props = this._props));
+ }
+
@observable _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class
@observable _animateScaleTime: Opt<number>; // milliseconds for animating between views. defaults to 300 if not uset
@observable _animateScalingTo = 0;
@@ -262,7 +275,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
return this._animateScaleTime ?? 100;
}
public get displayName() {
- return 'DocumentViewInternal(' + this.props.Document.title + ')';
+ return 'DocumentViewInternal(' + this._props.Document.title + ')';
} // this makes mobx trace() statements more descriptive
public get ContentDiv() {
@@ -272,43 +285,43 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
return Doc.LayoutFieldKey(this.layoutDoc);
}
@computed get layout_showTitle() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.ShowTitle) as Opt<string>;
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.ShowTitle) as Opt<string>;
}
@computed get NativeDimScaling() {
- return this.props.NativeDimScaling?.() || 1;
+ return this._props.NativeDimScaling?.() || 1;
}
@computed get thumb() {
return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png');
}
@computed get opacity() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Opacity);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Opacity);
}
@computed get boxShadow() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BoxShadow);
}
@computed get borderRounding() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding);
}
@computed get widgetDecorations() {
TraceMobx();
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Decorations);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Decorations);
}
@computed get backgroundBoxColor() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box');
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor + ':box');
}
@computed get docContents() {
- return this.props.styleProvider?.(this.Document, this.props, StyleProp.DocContents);
+ return this._props.styleProvider?.(this.Document, this._props, StyleProp.DocContents);
}
@computed get headerMargin() {
- return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0;
+ return this._props?.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin) || 0;
}
@computed get layout_showCaption() {
- return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.ShowCaption) || 0;
+ return this._props?.styleProvider?.(this.layoutDoc, this._props, StyleProp.ShowCaption) || 0;
}
@computed get titleHeight() {
- return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0;
+ return this._props?.styleProvider?.(this.layoutDoc, this._props, StyleProp.TitleHeight) || 0;
}
- @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined;
+ @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined = undefined;
@computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined {
return this._pointerEvents;
}
@@ -316,7 +329,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
return StrCast(this.Document.layout_fieldKey, 'layout');
}
@computed get disableClickScriptFunc() {
- const onScriptDisable = this.props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
+ const onScriptDisable = this._props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
// prettier-ignore
return (
DocumentView.LongPress ||
@@ -325,49 +338,49 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
);
}
@computed get onClickHandler() {
- return this.props.onClick?.() ?? this.props.onBrowseClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null));
+ return this._props.onClick?.() ?? this._props.onBrowseClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null));
}
@computed get onDoubleClickHandler() {
- return this.props.onDoubleClick?.() ?? Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick;
+ return this._props.onDoubleClick?.() ?? Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick;
}
@computed get onPointerDownHandler() {
- return this.props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown);
+ return this._props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown);
}
@computed get onPointerUpHandler() {
- return this.props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp);
+ return this._props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp);
}
componentWillUnmount() {
this.cleanupHandlers(true);
}
@observable _mounted = false; // turn off all pointer events if component isn't yet mounted (enables nested Docs in alternate UI textboxes that appear on hover which otherwise would grab focus from the text box, reverting to the original UI )
- @action
+
componentDidMount() {
- this._mounted = true;
+ runInAction(() => (this._mounted = true));
this.setupHandlers();
this._disposers.contentActive = reaction(
() => {
// true - if the document has been activated directly or indirectly (by having its children selected)
// false - if its pointer events are explicitly turned off or if it's container tells it that it's inactive
// undefined - it is not active, but it should be responsive to actions that might activate it or its contents (eg clicking)
- return this.props.isContentActive() === false || this.props.pointerEvents?.() === 'none'
+ return this._props.isContentActive() === false || this._props.pointerEvents?.() === 'none'
? false
- : Doc.ActiveTool !== InkTool.None || SnappingManager.GetCanEmbed() || this.rootSelected() || this.Document.forceActive || this._componentView?.isAnyChildContentActive?.() || this.props.isContentActive()
- ? true
- : undefined;
+ : Doc.ActiveTool !== InkTool.None || SnappingManager.GetCanEmbed() || this.rootSelected() || this.Document.forceActive || this._componentView?.isAnyChildContentActive?.() || this._props.isContentActive()
+ ? true
+ : undefined;
},
active => (this._isContentActive = active),
{ fireImmediately: true }
);
this._disposers.pointerevents = reaction(
- () => this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents),
+ () => this._props.styleProvider?.(this.Document, this._props, StyleProp.PointerEvents),
pointerevents => (this._pointerEvents = pointerevents),
{ fireImmediately: true }
);
}
preDropFunc = (e: Event, de: DragManager.DropEvent) => {
const dropAction = this.layoutDoc.dropAction as dropActionType;
- if (de.complete.docDragData && this.isContentActive() && !this.props.treeViewDoc) {
+ if (de.complete.docDragData && this.isContentActive() && !this._props.treeViewDoc) {
dropAction && (de.complete.docDragData.dropAction = dropAction);
e.stopPropagation();
}
@@ -375,38 +388,38 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
setupHandlers() {
this.cleanupHandlers(false);
if (this._mainCont.current) {
- this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document, this.preDropFunc);
+ this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this._props.Document, this.preDropFunc);
}
}
- @action
+
cleanupHandlers(unbrush: boolean) {
this._dropDisposer?.();
- unbrush && Doc.UnBrushDoc(this.props.Document);
+ unbrush && Doc.UnBrushDoc(this._props.Document);
Object.values(this._disposers).forEach(disposer => disposer?.());
}
startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
if (this._mainCont.current) {
const views = SelectionManager.Views().filter(dv => dv.docView?._mainCont.current);
- const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [this.props.DocumentView()];
+ const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [this._props.DocumentView()];
const dragData = new DragManager.DocumentDragData(selected.map(dv => dv.Document));
- const [left, top] = this.props.ScreenToLocalTransform().scale(this.NativeDimScaling).inverse().transformPoint(0, 0);
- dragData.offset = this.props
+ const [left, top] = this._props.ScreenToLocalTransform().scale(this.NativeDimScaling).inverse().transformPoint(0, 0);
+ dragData.offset = this._props
.ScreenToLocalTransform()
.scale(this.NativeDimScaling)
.transformDirection(x - left, y - top);
dragData.dropAction = dropAction;
- dragData.treeViewDoc = this.props.treeViewDoc;
- dragData.removeDocument = this.props.removeDocument;
- dragData.moveDocument = this.props.moveDocument;
- dragData.draggedViews = [this.props.DocumentView()];
- dragData.canEmbed = this.Document.dragAction ?? this.props.dragAction ? true : false;
+ dragData.treeViewDoc = this._props.treeViewDoc;
+ dragData.removeDocument = this._props.removeDocument;
+ dragData.moveDocument = this._props.moveDocument;
+ dragData.draggedViews = [this._props.DocumentView()];
+ dragData.canEmbed = this.Document.dragAction ?? this._props.dragAction ? true : false;
DragManager.StartDocumentDrag(
selected.map(dv => dv.docView!._mainCont.current!),
dragData,
x,
y,
- { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart && !this.props.dontHideOnDrag) }
+ { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart && !this._props.dontHideOnDrag) }
); // this needs to happen after the drop event is processed.
}
}
@@ -429,28 +442,28 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse;
onClick = action((e: React.MouseEvent | React.PointerEvent) => {
- if (this.props.isGroupActive?.() === 'child' && !this.props.isDocumentActive?.()) return;
- if (!this.Document.ignoreClick && this.props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
+ if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
+ if (!this.Document.ignoreClick && this._props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
let stopPropagate = true;
let preventDefault = true;
- !this.layoutDoc._keepZWhenDragged && this.props.bringToFront(this.Document);
+ !this.layoutDoc._keepZWhenDragged && this._props.bringToFront(this.Document);
if (this._doubleTap) {
- const defaultDblclick = this.props.defaultDoubleClick?.() || this.Document.defaultDoubleClick;
+ const defaultDblclick = this._props.defaultDoubleClick?.() || this.Document.defaultDoubleClick;
if (this.onDoubleClickHandler?.script) {
const { clientX, clientY, shiftKey, altKey, ctrlKey } = e; // or we could call e.persist() to capture variables
// prettier-ignore
const func = () => this.onDoubleClickHandler.script.run( {
this: this.Document,
- scriptContext: this.props.scriptContext,
- documentView: this.props.DocumentView(),
+ scriptContext: this._props.scriptContext,
+ documentView: this._props.DocumentView(),
clientX, clientY, altKey, shiftKey, ctrlKey,
value: undefined,
}, console.log );
- UndoManager.RunInBatch(() => (func().result?.select === true ? this.props.select(false) : ''), 'on double click');
+ UndoManager.RunInBatch(() => (func().result?.select === true ? this._props.select(false) : ''), 'on double click');
} else if (!Doc.IsSystem(this.Document) && (defaultDblclick === undefined || defaultDblclick === 'default')) {
UndoManager.RunInBatch(() => LightboxView.Instance.AddDocTab(this.Document, OpenWhere.lightbox), 'double tap');
SelectionManager.DeselectAll();
- Doc.UnBrushDoc(this.props.Document);
+ Doc.UnBrushDoc(this._props.Document);
} else {
this._singleClickFunc?.();
}
@@ -467,13 +480,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
// e.g., if this document is part of a labeled 'lightbox' container, then documents will be shown in place
// instead of in the global lightbox
const oldFunc = DocumentViewInternal.addDocTabFunc;
- DocumentViewInternal.addDocTabFunc = this.props.addDocTab;
+ DocumentViewInternal.addDocTabFunc = this._props.addDocTab;
this.onClickHandler?.script.run(
{
this: this.Document,
_readOnly_: false,
- scriptContext: this.props.scriptContext,
- documentView: this.props.DocumentView(),
+ scriptContext: this._props.scriptContext,
+ documentView: this._props.DocumentView(),
clientX,
clientY,
shiftKey,
@@ -482,14 +495,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
},
console.log
).result?.select === true
- ? this.props.select(false)
+ ? this._props.select(false)
: '';
DocumentViewInternal.addDocTabFunc = oldFunc;
};
clickFunc = () => UndoManager.RunInBatch(func, 'click ' + this.Document.title);
} else {
// onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
- if ((this.layoutDoc.onDragStart || this.props.TemplateDataDocument) && !(e.ctrlKey || e.button > 0)) {
+ if ((this.layoutDoc.onDragStart || this._props.TemplateDataDocument) && !(e.ctrlKey || e.button > 0)) {
stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
}
preventDefault = false;
@@ -497,10 +510,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const sendToBack = e.altKey;
this._singleClickFunc =
// prettier-ignore
- clickFunc ?? (() => (sendToBack ? this.props.DocumentView().props.bringToFront(this.Document, true) :
+ clickFunc ?? (() => (sendToBack ? this._props.DocumentView()._props.bringToFront(this.Document, true) :
this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ??
- this.props.select(e.ctrlKey||e.shiftKey, e.metaKey)));
- const waitFordblclick = this.props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
+ this._props.select(e.ctrlKey||e.shiftKey, e.metaKey)));
+ const waitFordblclick = this._props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') {
this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
this._doubleClickTimeout = setTimeout(this._singleClickFunc, 300);
@@ -514,40 +527,39 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
});
- @action
onPointerDown = (e: React.PointerEvent): void => {
- if (this.props.isGroupActive?.() === 'child' && !this.props.isDocumentActive?.()) return;
+ if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
this._longPressSelector = setTimeout(() => {
if (DocumentView.LongPress) {
if (this.Document.undoIgnoreFields) {
runInAction(() => (UndoStack.HideInline = !UndoStack.HideInline));
} else {
- this.props.select(false);
+ this._props.select(false);
}
}
}, 1000);
- if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this.props.DocumentView();
+ if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this._props.DocumentView();
this._downX = e.clientX;
this._downY = e.clientY;
this._downTime = Date.now();
- if ((Doc.ActiveTool === InkTool.None || this.props.addDocTab === returnFalse) && !(this.props.TemplateDataDocument && !(e.ctrlKey || e.button > 0))) {
+ if ((Doc.ActiveTool === InkTool.None || this._props.addDocTab === returnFalse) && !(this._props.TemplateDataDocument && !(e.ctrlKey || e.button > 0))) {
// click events stop here if the document is active and no modes are overriding it
// if this is part of a template, let the event go up to the template root unless right/ctrl clicking
if (
// prettier-ignore
- (this.props.isDocumentActive?.() || this.props.isContentActive?.()) &&
- !this.props.onBrowseClick?.() &&
+ (this._props.isDocumentActive?.() || this._props.isContentActive?.()) &&
+ !this._props.onBrowseClick?.() &&
!this.Document.ignoreClick &&
e.button === 0 &&
!Doc.IsInMyOverlay(this.layoutDoc)
) {
e.stopPropagation();
// don't preventDefault anymore. Goldenlayout, PDF text selection and RTF text selection all need it to go though
- //if (this.props.isSelected(true) && this.Document.type !== DocumentType.PDF && this.layoutDoc._type_collection !== CollectionViewType.Docking) e.preventDefault();
+ //if (this._props.isSelected(true) && this.Document.type !== DocumentType.PDF && this.layoutDoc._type_collection !== CollectionViewType.Docking) e.preventDefault();
// listen to move events if document content isn't active or document is draggable
- if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || BoolCast(this.layoutDoc._dragWhenActive, this.props.dragWhenActive))) {
+ if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || BoolCast(this.layoutDoc._dragWhenActive, this._props.dragWhenActive))) {
document.addEventListener('pointermove', this.onPointerMove);
}
}
@@ -555,14 +567,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
};
- @action
onPointerMove = (e: PointerEvent): void => {
if (e.buttons !== 1 || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return;
if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
this.cleanupPointerEvents();
this._longPressSelector && clearTimeout(this._longPressSelector);
- this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'embed') || ((this.Document.dragAction || this.props.dragAction || undefined) as dropActionType));
+ this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'embed') || ((this.Document.dragAction || this._props.dragAction || undefined) as dropActionType));
}
};
@@ -571,7 +582,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
document.removeEventListener('pointerup', this.onPointerUp);
};
- @action
onPointerUp = (e: PointerEvent): void => {
this.cleanupPointerEvents();
this._longPressSelector && clearTimeout(this._longPressSelector);
@@ -586,7 +596,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
};
@undoBatch
- @action
toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => {
const hadOnClick = this.Document.onClick;
this.noOnClick();
@@ -594,8 +603,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
this.Document.waitForDoubleClickToClick = hadOnClick ? undefined : 'never';
};
@undoBatch
- @action
- followLinkOnClick = (): void => {
+ followLinkOnClick = () => {
this.Document.ignoreClick = false;
this.Document.onClick = FollowLinkScript();
this.Document.followLinkToggle = false;
@@ -603,12 +611,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
this.Document.followLinkLocation = undefined;
};
@undoBatch
- noOnClick = (): void => {
+ noOnClick = () => {
this.Document.ignoreClick = false;
this.Document.onClick = Doc.GetProto(this.Document).onClick = undefined;
};
- @undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document);
+ @undoBatch deleteClicked = () => this._props.removeDocument?.(this._props.Document);
@undoBatch setToggleDetail = () =>
(this.Document.onClick = ScriptField.MakeScript(
`toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
@@ -618,10 +626,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
));
@undoBatch
- @action
drop = (e: Event, de: DragManager.DropEvent) => {
- if (this.props.dontRegisterView || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false;
- if (this.props.Document === Doc.ActiveDashboard) {
+ if (this._props.dontRegisterView || this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false;
+ if (this._props.Document === Doc.ActiveDashboard) {
e.stopPropagation();
e.preventDefault();
alert(
@@ -643,7 +650,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]);
if (de.complete.linkDocument) {
de.complete.linkDocument.layout_isSvg = true;
- this.props.DocumentView().CollectionFreeFormView?.addDocument(de.complete.linkDocument);
+ this._props.DocumentView().CollectionFreeFormView?.addDocument(de.complete.linkDocument);
}
}
e.stopPropagation();
@@ -654,18 +661,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
};
@undoBatch
- @action
makeIntoPortal = () => {
- const portalLink = this.allLinks.find(d => d.link_anchor_1 === this.props.Document && d.link_relationship === 'portal to:portal from');
+ const portalLink = this.allLinks.find(d => d.link_anchor_1 === this._props.Document && d.link_relationship === 'portal to:portal from');
if (!portalLink) {
DocUtils.MakeLink(
- this.props.Document,
+ this._props.Document,
Docs.Create.FreeformDocument([], {
_width: NumCast(this.layoutDoc._width) + 10,
_height: Math.max(NumCast(this.layoutDoc._height), NumCast(this.layoutDoc._width) + 10),
_isLightbox: true,
_layout_fitWidth: true,
- title: StrCast(this.props.Document.title) + ' [Portal]',
+ title: StrCast(this._props.Document.title) + ' [Portal]',
}),
{ link_relationship: 'portal to:portal from' }
);
@@ -683,7 +689,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const batch = UndoManager.StartBatch('importing');
Doc.importDocument(input.files[0]).then(doc => {
if (doc instanceof Doc) {
- this.props.addDocTab(doc, OpenWhere.addRight);
+ this._props.addDocTab(doc, OpenWhere.addRight);
batch.end();
}
});
@@ -692,12 +698,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
input.click();
};
- @action
onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => {
if (e && this.layoutDoc._layout_hideContextMenu && Doc.noviceMode) {
e.preventDefault();
e.stopPropagation();
- //!this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false);
+ //!this._props.isSelected(true) && SelectionManager.SelectView(this._props.DocumentView(), false);
}
// the touch onContextMenu is button 0, the pointer onContextMenu is button 2
if (e) {
@@ -719,7 +724,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (e && !(e.nativeEvent as any).dash) {
const onDisplay = () => {
- if (this.Document.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected() && SelectionManager.SelectView(this.props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
+ if (this.Document.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this._props.isSelected() && SelectionManager.SelectView(this._props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
setTimeout(() => simulateMouseClick(document.elementFromPoint(e.clientX, e.clientY), e.clientX, e.clientY, e.screenX, e.screenY));
};
if (navigator.userAgent.includes('Macintosh')) {
@@ -730,33 +735,33 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
return;
}
- const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
+ const customScripts = Cast(this._props.Document.contextMenuScripts, listSpec(ScriptField), []);
StrListCast(this.Document.contextMenuLabels).forEach((label, i) =>
- cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.Document, scriptContext: this.props.scriptContext }), icon: 'sticky-note' })
+ cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.Document, scriptContext: this._props.scriptContext }), icon: 'sticky-note' })
);
- this.props.contextMenuItems?.().forEach(item => item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.Document, scriptContext: this.props.scriptContext }), icon: item.icon as IconProp }));
+ this._props.contextMenuItems?.().forEach(item => item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.Document, scriptContext: this._props.scriptContext }), icon: item.icon as IconProp }));
- if (!this.props.Document.isFolder) {
- const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layout_fieldKey)], Doc, null);
+ if (!this._props.Document.isFolder) {
+ const templateDoc = Cast(this._props.Document[StrCast(this._props.Document.layout_fieldKey)], Doc, null);
const appearance = cm.findByDescription('Appearance...');
const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : [];
- if (this.props.renderDepth === 0) {
+ if (this._props.renderDepth === 0) {
appearanceItems.splice(0, 0, { description: 'Open in Lightbox', event: () => LightboxView.Instance.SetLightboxDoc(this.Document), icon: 'external-link-alt' });
}
- appearanceItems.push({ description: 'Pin', event: () => this.props.pinToPres(this.Document, {}), icon: 'eye' });
- !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
+ appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'eye' });
+ !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this._props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
!appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'compass' });
if (!Doc.IsSystem(this.Document) && this.Document.type !== DocumentType.PRES && ![CollectionViewType.Docking, CollectionViewType.Tree].includes(this.Document._type_collection as any)) {
const existingOnClick = cm.findByDescription('OnClick...');
const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
- if (this.props.bringToFront !== emptyFunction) {
+ if (this._props.bringToFront !== emptyFunction) {
const zorders = cm.findByDescription('ZOrder...');
const zorderItems: ContextMenuProps[] = zorders && 'subitems' in zorders ? zorders.subitems : [];
- zorderItems.push({ description: 'Bring to Front', event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.Document, false)), icon: 'arrow-up' });
- zorderItems.push({ description: 'Send to Back', event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.Document, true)), icon: 'arrow-down' });
+ zorderItems.push({ description: 'Bring to Front', event: () => SelectionManager.Views().forEach(dv => dv._props.bringToFront(dv.Document, false)), icon: 'arrow-up' });
+ zorderItems.push({ description: 'Send to Back', event: () => SelectionManager.Views().forEach(dv => dv._props.bringToFront(dv.Document, true)), icon: 'arrow-down' });
zorderItems.push({
description: !this.layoutDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged',
event: undoBatch(action(() => (this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged))),
@@ -768,14 +773,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
onClicks.push({ description: 'Enter Portal', event: this.makeIntoPortal, icon: 'window-restore' });
!Doc.noviceMode && onClicks.push({ description: 'Toggle Detail', event: this.setToggleDetail, icon: 'concierge-bell' });
- if (!this.props.treeViewDoc) {
+ if (!this._props.treeViewDoc) {
if (!this.Document.annotationOn) {
const options = cm.findByDescription('Options...');
const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
- !Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
+ !Doc.noviceMode && 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...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
} else if (LinkManager.Links(this.Document).length) {
onClicks.push({ description: 'Restore On Click default', event: () => this.noOnClick(), icon: 'link' });
@@ -797,15 +802,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const moreItems = more && 'subitems' in more ? more.subitems : [];
if (!Doc.IsSystem(this.Document)) {
if (!Doc.noviceMode) {
- moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.TemplateDataDocument), icon: 'concierge-bell' });
+ moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this._props.Document, this._props.TemplateDataDocument), icon: 'concierge-bell' });
moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => (this.Document._chromeHidden = !this.Document._chromeHidden), icon: 'project-diagram' });
- if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
- moreItems.push({ description: 'Export to Google Photos Album', event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: 'caret-square-right' });
- moreItems.push({ description: 'Tag Child Images via Google Photos', event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: 'caret-square-right' });
- moreItems.push({ description: 'Write Back Link to Album', event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: 'caret-square-right' });
+ if (Cast(Doc.GetProto(this._props.Document).data, listSpec(Doc))) {
+ moreItems.push({ description: 'Export to Google Photos Album', event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this._props.Document }).then(console.log), icon: 'caret-square-right' });
+ moreItems.push({ description: 'Tag Child Images via Google Photos', event: () => GooglePhotos.Query.TagChildImages(this._props.Document), icon: 'caret-square-right' });
+ moreItems.push({ description: 'Write Back Link to Album', event: () => GooglePhotos.Transactions.AddTextEnrichment(this._props.Document), icon: 'caret-square-right' });
}
- moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this.props.Document)), icon: 'fingerprint' });
+ moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this._props.Document)), icon: 'fingerprint' });
}
}
@@ -813,25 +818,25 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
const constantItems: ContextMenuProps[] = [];
if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking) {
- constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this.props.Document) });
- (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' });
- if (this.props.removeDocument && Doc.ActiveDashboard !== this.props.Document) {
+ constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this._props.Document) });
+ (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this._props.DocumentView()), icon: 'users' });
+ if (this._props.removeDocument && Doc.ActiveDashboard !== this._props.Document) {
// need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
}
}
- constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, OpenWhere.addRightKeyvalue), icon: 'table-columns' });
+ constantItems.push({ description: 'Show Metadata', event: () => this._props.addDocTab(this._props.Document, OpenWhere.addRightKeyvalue), icon: 'table-columns' });
cm.addItem({ description: 'General...', noexpand: false, subitems: constantItems, icon: 'question' });
const help = cm.findByDescription('Help...');
const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
- !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'keyboard' });
+ !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this._props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'keyboard' });
!Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.Document), icon: 'hand-point-right' });
!Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.dataDoc), icon: 'hand-point-right' });
let documentationDescription: string | undefined = undefined;
let documentationLink: string | undefined = undefined;
- switch (this.props.Document.type) {
+ switch (this._props.Document.type) {
case DocumentType.COL:
documentationDescription = 'See collection documentation';
documentationLink = 'https://brown-dash.github.io/Dash-Documentation/views/';
@@ -866,7 +871,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
break;
}
// Add link to help documentation
- if (!this.props.treeViewDoc && documentationDescription && documentationLink) {
+ if (!this._props.treeViewDoc && documentationDescription && documentationLink) {
helpItems.push({
description: documentationDescription,
event: () => window.open(documentationLink, '_blank'),
@@ -881,26 +886,26 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
};
@computed get _rootSelected() {
- return this.props.isSelected() || BoolCast(this.props.TemplateDataDocument && this.props.rootSelected?.());
+ return this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.());
}
rootSelected = () => this._rootSelected;
- panelHeight = () => this.props.PanelHeight() - this.headerMargin;
- screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
+ panelHeight = () => this._props.PanelHeight() - this.headerMargin;
+ screenToLocal = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
- setHeight = (height: number) => !this.props.suppressSetHeight && (this.layoutDoc._height = height);
+ setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = height);
setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
- @observable _isContentActive: boolean | undefined;
+ @observable _isContentActive: boolean | undefined = undefined;
isContentActive = (): boolean | undefined => this._isContentActive;
- childFilters = () => [...this.props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
+ childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
/// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive
@computed get _contentPointerEvents() {
TraceMobx();
- return this.props.contentPointerEvents ??
+ return this._props.contentPointerEvents ??
((!this.disableClickScriptFunc && //
this.onClickHandler &&
- !this.props.onBrowseClick?.() &&
+ !this._props.onBrowseClick?.() &&
this.isContentActive() !== true) ||
this.isContentActive() === false)
? 'none'
@@ -909,8 +914,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
contentPointerEvents = () => this._contentPointerEvents;
@computed get contents() {
TraceMobx();
- const isInk = this.layoutDoc._layout_isSvg && !this.props.LayoutTemplateString;
- const noBackground = this.Document.isGroup && !this.props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
+ const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
+ const noBackground = this.Document.isGroup && !this._props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
return (
<div
className="documentView-contentsView"
@@ -920,10 +925,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}}>
<DocumentContentsView
key={1}
- {...this.props}
+ {...this._props}
fieldKey=""
pointerEvents={this.contentPointerEvents}
- docViewPath={this.props.viewPath}
+ docViewPath={this._props.viewPath}
setContentView={this.setContentView}
childFilters={this.childFilters}
PanelHeight={this.panelHeight}
@@ -940,8 +945,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
);
}
- anchorPanelWidth = () => this.props.PanelWidth() || 1;
- anchorPanelHeight = () => this.props.PanelHeight() || 1;
+ anchorPanelWidth = () => this._props.PanelWidth() || 1;
+ anchorPanelHeight = () => this._props.PanelHeight() || 1;
anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
// prettier-ignore
switch (property.split(':')[0]) {
@@ -949,11 +954,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
case StyleProp.PointerEvents: return 'none';
case StyleProp.Highlighting: return undefined;
case StyleProp.Opacity: {
- const filtered = DocUtils.FilterDocs(this.directLinks, this.props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
+ const filtered = DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
return filtered.some(link => link._link_displayArrow) ? 0 : undefined;
}
}
- return this.props.styleProvider?.(doc, props, property);
+ return this._props.styleProvider?.(doc, props, property);
};
// We need to use allrelatedLinks to get not just links to the document as a whole, but links to
// anchors that are not rendered as DocumentViews (marked as 'layout_unrendered' with their 'annotationOn' set to this document). e.g.,
@@ -979,15 +984,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
@computed get allLinkEndpoints() {
// the small blue dots that mark the endpoints of links
TraceMobx();
- if (this._componentView instanceof KeyValueBox || this.props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this.props.dontRegisterView || this.layoutDoc.layout_unrendered) return null;
- const filtered = DocUtils.FilterDocs(this.directLinks, this.props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
+ if (this._componentView instanceof KeyValueBox || this._props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this._props.dontRegisterView || this.layoutDoc.layout_unrendered) return null;
+ const filtered = DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
return filtered.map(link => (
<div className="documentView-anchorCont" key={link[Id]}>
<DocumentView
- {...this.props}
+ {...this._props}
isContentActive={returnFalse}
Document={link}
- docViewPath={this.props.viewPath}
+ docViewPath={this._props.viewPath}
PanelWidth={this.anchorPanelWidth}
PanelHeight={this.anchorPanelHeight}
dontRegisterView={false}
@@ -1077,7 +1082,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
};
- captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ':caption');
+ captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
@observable _changingTitleField = false;
@observable _dropDownInnerWidth = 0;
fieldsDropdown = (inputOptions: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => {
@@ -1121,12 +1126,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
<div
className="documentView-captionWrapper"
style={{
- pointerEvents: this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
+ pointerEvents: this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
background: StrCast(this.layoutDoc._backgroundColor, 'rgba(0,0,0,0.2)'),
color: lightOrDark(StrCast(this.layoutDoc._backgroundColor, 'black')),
}}>
<FormattedTextBox
- {...this.props}
+ {...this._props}
yPadding={10}
xPadding={10}
fieldKey={this.layout_showCaption}
@@ -1134,7 +1139,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
dontRegisterView={true}
noSidebar={true}
dontScale={true}
- renderDepth={this.props.renderDepth}
+ renderDepth={this._props.renderDepth}
isContentActive={this.isContentActive}
/>
</div>
@@ -1160,7 +1165,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
width: 100 - sidebarWidthPercent + '%',
color: background === 'transparent' ? SettingsManager.userColor : lightOrDark(background),
background,
- pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
+ pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
}}>
{!dropdownWidth
? null
@@ -1171,7 +1176,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
action((field: string | number) => {
if (this.layoutDoc.layout_showTitle) {
this.layoutDoc._layout_showTitle = field;
- } else if (!this.props.layout_showTitle) {
+ } else if (!this._props.layout_showTitle) {
Doc.UserDoc().layout_showTitle = field;
}
this._changingTitleField = false;
@@ -1199,7 +1204,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (input?.startsWith('#')) {
if (this.layoutDoc.layout_showTitle) {
this.layoutDoc._layout_showTitle = input?.substring(1);
- } else if (!this.props.layout_showTitle) {
+ } else if (!this._props.layout_showTitle) {
Doc.UserDoc().layout_showTitle = input?.substring(1) ?? 'author_date';
}
} else if (showTitle && !showTitle.includes('Date') && showTitle !== 'author') {
@@ -1211,7 +1216,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
</div>
</div>
);
- return this.props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
+ return this._props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
this.contents
) : (
<div className="documentView-styleWrapper">
@@ -1267,31 +1272,31 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) {
default:
case PresEffect.None: return renderDoc;
- case PresEffect.Zoom: return <Zoom {...effectProps}>{renderDoc}</Zoom>;
- case PresEffect.Fade: return <Fade {...effectProps}>{renderDoc}</Fade>;
- case PresEffect.Flip: return <Flip {...effectProps}>{renderDoc}</Flip>;
- case PresEffect.Rotate: return <Rotate {...effectProps}>{renderDoc}</Rotate>;
- case PresEffect.Bounce: return <Bounce {...effectProps}>{renderDoc}</Bounce>;
- case PresEffect.Roll: return <Roll {...effectProps}>{renderDoc}</Roll>;
- case PresEffect.Lightspeed: return <LightSpeed {...effectProps}>{renderDoc}</LightSpeed>;
+ // case PresEffect.Zoom: return <Zoom {...effectProps}>{renderDoc}</Zoom>;
+ // case PresEffect.Fade: return <Fade {...effectProps}>{renderDoc}</Fade>;
+ // case PresEffect.Flip: return <Flip {...effectProps}>{renderDoc}</Flip>;
+ // case PresEffect.Rotate: return <Rotate {...effectProps}>{renderDoc}</Rotate>;
+ // case PresEffect.Bounce: return <Bounce {...effectProps}>{renderDoc}</Bounce>;
+ // case PresEffect.Roll: return <Roll {...effectProps}>{renderDoc}</Roll>;
+ // case PresEffect.Lightspeed: return <LightSpeed {...effectProps}>{renderDoc}</LightSpeed>;
}
}
@computed get highlighting() {
- return this.props.styleProvider?.(this.Document, this.props, StyleProp.Highlighting);
+ return this._props.styleProvider?.(this.Document, this._props, StyleProp.Highlighting);
}
@computed get borderPath() {
- return this.props.styleProvider?.(this.Document, this.props, StyleProp.BorderPath);
+ return this._props.styleProvider?.(this.Document, this._props, StyleProp.BorderPath);
}
render() {
TraceMobx();
const highlighting = this.highlighting;
const borderPath = this.borderPath;
const boxShadow =
- this.props.treeViewDoc || !highlighting
+ this._props.treeViewDoc || !highlighting
? this.boxShadow
: highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed'
- ? `0 0 0 ${highlighting.highlightIndex}px ${highlighting.highlightColor}`
- : this.boxShadow || (this.Document.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
+ ? `0 0 0 ${highlighting.highlightIndex}px ${highlighting.highlightColor}`
+ : this.boxShadow || (this.Document.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
const renderDoc = this.renderDoc({
borderRadius: this.borderRounding,
outline: highlighting && !this.borderRounding && !highlighting.highlightStroke ? `${highlighting.highlightColor} ${highlighting.highlightStyle} ${highlighting.highlightIndex}px` : 'solid 0px',
@@ -1326,6 +1331,18 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
@observer
export class DocumentView extends React.Component<DocumentViewProps> {
public static ROOT_DIV = 'documentView-effectsWrapper';
+
+ @observable _props: DocumentViewProps;
+ _prevProps: DocumentViewProps;
+ constructor(props: any) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
@observable _selected = false;
public get SELECTED() {
return this._selected;
@@ -1340,14 +1357,14 @@ export class DocumentView extends React.Component<DocumentViewProps> {
@computed public static get exploreMode() {
return () => (DocumentView.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
}
- @observable public docView: DocumentViewInternal | undefined | null;
+ @observable public docView: DocumentViewInternal | undefined | null = undefined;
@observable public textHtmlOverlay: Opt<string>;
@observable public textHtmlOverlayTime: Opt<number>;
@observable private _isHovering = false;
public htmlOverlayEffect: Opt<Doc>;
public get displayName() {
- return 'DocumentView(' + this.props.Document?.title + ')';
+ return 'DocumentView(' + this._props.Document?.title + ')';
} // this makes mobx trace() statements more descriptive
public ContentRef = React.createRef<HTMLDivElement>();
public ViewTimer: NodeJS.Timeout | undefined; // timer for res
@@ -1405,10 +1422,10 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
get Document() {
- return this.props.Document;
+ return this._props.Document;
}
get topMost() {
- return this.props.renderDepth === 0;
+ return this._props.renderDepth === 0;
}
get dataDoc() {
return this.docView?.dataDoc ?? this.Document;
@@ -1426,32 +1443,32 @@ export class DocumentView extends React.Component<DocumentViewProps> {
return this.docView?.LayoutFieldKey || 'layout';
}
@computed get layout_fitWidth() {
- return this.props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth;
+ return this._props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth;
}
@computed get anchorViewDoc() {
- return this.props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this.props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined;
+ return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined;
}
@computed get hideLinkButton() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkBtn + (this.SELECTED ? ':selected' : ''));
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HideLinkBtn + (this.SELECTED ? ':selected' : ''));
}
- hideLinkCount = () => this.props.renderDepth === -1 || (this.SELECTED && this.props.renderDepth) || !this._isHovering || this.hideLinkButton;
+ hideLinkCount = () => this._props.renderDepth === -1 || (this.SELECTED && this._props.renderDepth) || !this._isHovering || this.hideLinkButton;
@computed get linkCountView() {
return <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.scaleToScreenSpace} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
}
@computed get docViewPath(): DocumentView[] {
- return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this];
+ return this._props.docViewPath ? [...this._props.docViewPath(), this] : [this];
}
@computed get layoutDoc() {
- return Doc.Layout(this.Document, this.props.LayoutTemplate?.());
+ return Doc.Layout(this.Document, this._props.LayoutTemplate?.());
}
@computed get nativeWidth() {
- return this.props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.TemplateDataDocument, !this.layout_fitWidth));
+ return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
}
@computed get nativeHeight() {
- return this.props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.TemplateDataDocument, !this.layout_fitWidth));
+ return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
}
@computed get shouldNotScale() {
- return (this.layout_fitWidth && !this.nativeWidth) || this.props.LayoutTemplateString?.includes(KeyValueBox.name) || [CollectionViewType.Docking].includes(this.Document._type_collection as any);
+ return (this.layout_fitWidth && !this.nativeWidth) || this._props.LayoutTemplateString?.includes(KeyValueBox.name) || [CollectionViewType.Docking].includes(this.Document._type_collection as any);
}
@computed get effectiveNativeWidth() {
return this.shouldNotScale ? 0 : this.nativeWidth || NumCast(this.layoutDoc.width);
@@ -1462,57 +1479,57 @@ export class DocumentView extends React.Component<DocumentViewProps> {
@computed get nativeScaling() {
if (this.shouldNotScale) return 1;
const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
- if (this.layout_fitWidth || this.props.PanelHeight() / (this.effectiveNativeHeight || 1) > this.props.PanelWidth() / (this.effectiveNativeWidth || 1)) {
- return Math.max(minTextScale, this.props.PanelWidth() / (this.effectiveNativeWidth || 1)); // width-limited or layout_fitWidth
+ if (this.layout_fitWidth || this._props.PanelHeight() / (this.effectiveNativeHeight || 1) > this._props.PanelWidth() / (this.effectiveNativeWidth || 1)) {
+ return Math.max(minTextScale, this._props.PanelWidth() / (this.effectiveNativeWidth || 1)); // width-limited or layout_fitWidth
}
- return Math.max(minTextScale, this.props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
+ return Math.max(minTextScale, this._props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
}
@computed get panelWidth() {
- return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth();
+ return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this._props.PanelWidth();
}
@computed get panelHeight() {
if (this.effectiveNativeHeight && (!this.layout_fitWidth || !this.layoutDoc.layout_reflowVertical)) {
- return Math.min(this.props.PanelHeight(), this.effectiveNativeHeight * this.nativeScaling);
+ return Math.min(this._props.PanelHeight(), this.effectiveNativeHeight * this.nativeScaling);
}
- return this.props.PanelHeight();
+ return this._props.PanelHeight();
}
@computed get Xshift() {
- return this.effectiveNativeWidth ? Math.max(0, (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2) : 0;
+ return this.effectiveNativeWidth ? Math.max(0, (this._props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2) : 0;
}
@computed get Yshift() {
return this.effectiveNativeWidth &&
this.effectiveNativeHeight &&
Math.abs(this.Xshift) < 0.001 &&
- (!this.layoutDoc.layout_reflowVertical || (!this.layout_fitWidth && this.effectiveNativeHeight * this.nativeScaling <= this.props.PanelHeight()))
- ? Math.max(0, (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2)
+ (!this.layoutDoc.layout_reflowVertical || (!this.layout_fitWidth && this.effectiveNativeHeight * this.nativeScaling <= this._props.PanelHeight()))
+ ? Math.max(0, (this._props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2)
: 0;
}
@computed get centeringX() {
- return this.props.dontCenter?.includes('x') ? 0 : this.Xshift;
+ return this._props.dontCenter?.includes('x') ? 0 : this.Xshift;
}
@computed get centeringY() {
- return this.props.dontCenter?.includes('y') ? 0 : this.Yshift;
+ return this._props.dontCenter?.includes('y') ? 0 : this.Yshift;
}
@computed get CollectionFreeFormView() {
return this.CollectionFreeFormDocumentView?.CollectionFreeFormView;
}
@computed get CollectionFreeFormDocumentView() {
- return this.props.CollectionFreeFormDocumentView?.();
+ return this._props.CollectionFreeFormDocumentView?.();
}
- public toggleNativeDimensions = () => this.docView && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this.props.PanelWidth(), this.props.PanelHeight());
+ public toggleNativeDimensions = () => this.docView && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this._props.PanelWidth(), this._props.PanelHeight());
public getBounds = () => {
- if (!this.docView?.ContentDiv || this.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
+ if (!this.docView?.ContentDiv || this._props.treeViewDoc || Doc.AreProtosEqual(this._props.Document, Doc.UserDoc())) {
return undefined;
}
if (this.docView._componentView?.screenBounds?.()) {
return this.docView._componentView.screenBounds();
}
- const xf = this.docView.props.ScreenToLocalTransform().scale(this.nativeScaling).inverse();
+ const xf = this.docView._props.ScreenToLocalTransform().scale(this.nativeScaling).inverse();
const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
- if (this.docView.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
+ if (this.docView._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
const docuBox = this.docView.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
}
@@ -1535,18 +1552,17 @@ export class DocumentView extends React.Component<DocumentViewProps> {
const deiconifyLayout = Cast(this.Document.deiconifyLayout, 'string', null);
this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finalFinished);
this.Document.deiconifyLayout = undefined;
- this.props.bringToFront(this.Document);
+ this._props.bringToFront(this.Document);
}
}
@undoBatch
- @action
setCustomView = (custom: boolean, layout: string): void => {
- Doc.setNativeView(this.props.Document);
- custom && DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
+ Doc.setNativeView(this._props.Document);
+ custom && DocUtils.makeCustomViewClicked(this._props.Document, Docs.Create.StackingDocument, layout, undefined);
};
- @action
+
switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
- this.docView && (this.docView._animateScalingTo = 0.1); // shrink doc
+ runInAction(() => this.docView && (this.docView._animateScalingTo = 0.1)); // shrink doc
setTimeout(
action(() => {
if (useExistingLayout && custom && this.Document['layout_' + view]) {
@@ -1568,7 +1584,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
};
layout_fitWidthFunc = (doc: Doc) => BoolCast(this.layout_fitWidth);
- scaleToScreenSpace = () => (1 / (this.props.NativeDimScaling?.() || 1)) * this.screenToLocalTransform().Scale;
+ scaleToScreenSpace = () => (1 / (this._props.NativeDimScaling?.() || 1)) * this.screenToLocalTransform().Scale;
docViewPathFunc = () => this.docViewPath;
isSelected = () => this.SELECTED;
select = (extendSelection: boolean, focusSelection?: boolean) => {
@@ -1592,14 +1608,13 @@ export class DocumentView extends React.Component<DocumentViewProps> {
NativeDimScaling = () => this.nativeScaling;
selfView = () => this;
screenToLocalTransform = () =>
- this.props
+ this._props
.ScreenToLocalTransform()
.translate(-this.centeringX, -this.centeringY)
.scale(1 / this.nativeScaling);
- @action
componentDidMount() {
- this.Document[DocViews].add(this);
+ runInAction(() => this.Document[DocViews].add(this));
this._disposers.updateContentsScript = reaction(() => ScriptCast(this.Document.updateContentsScript)?.script?.run({ this: this.Document }).result, emptyFunction);
this._disposers.height = reaction(
// increase max auto height if document has been resized to be greater than current max
@@ -1609,13 +1624,13 @@ export class DocumentView extends React.Component<DocumentViewProps> {
if (docMax && docMax < height) this.layoutDoc.layout_maxAutoHeight = height;
})
);
- !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.AddView(this);
+ !BoolCast(this._props.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.AddView(this);
}
- @action
+
componentWillUnmount() {
this.Document[DocViews].delete(this);
Object.values(this._disposers).forEach(disposer => disposer?.());
- !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
+ !BoolCast(this._props.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
}
// want the htmloverlay to be able to fade in but we also want it to be display 'none' until it is needed.
// unfortunately, CSS can't transition animate any properties for something that is display 'none'.
@@ -1652,23 +1667,23 @@ export class DocumentView extends React.Component<DocumentViewProps> {
render() {
TraceMobx();
- const xshift = Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined;
- const yshift = Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined;
+ const xshift = Math.abs(this.Xshift) <= 0.001 ? this._props.PanelWidth() : undefined;
+ const yshift = Math.abs(this.Yshift) <= 0.001 ? this._props.PanelHeight() : undefined;
return (
<div className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}>
- {!this.props.Document || !this.props.PanelWidth() ? null : (
+ {!this._props.Document || !this._props.PanelWidth() ? null : (
<div
className="contentFittingDocumentView-previewDoc"
ref={this.ContentRef}
style={{
- transition: 'inherit', // this.props.dataTransition,
+ transition: 'inherit', // this._props.dataTransition,
transform: `translate(${this.centeringX}px, ${this.centeringY}px)`,
- width: xshift ?? `${this.props.PanelWidth() - this.Xshift * 2}px`,
- height: this.props.forceAutoHeight ? undefined : yshift ?? (this.layout_fitWidth ? `${this.panelHeight}px` : `${(this.effectiveNativeHeight / this.effectiveNativeWidth) * this.props.PanelWidth()}px`),
+ width: xshift ?? `${this._props.PanelWidth() - this.Xshift * 2}px`,
+ height: this._props.forceAutoHeight ? undefined : yshift ?? (this.layout_fitWidth ? `${this.panelHeight}px` : `${(this.effectiveNativeHeight / this.effectiveNativeWidth) * this._props.PanelWidth()}px`),
}}>
<DocumentViewInternal
- {...this.props}
+ {...this._props}
DocumentView={this.selfView}
viewPath={this.docViewPathFunc}
PanelWidth={this.PanelWidth}
@@ -1680,7 +1695,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
select={this.select}
layout_fitWidth={this.layout_fitWidthFunc}
ScreenToLocalTransform={this.screenToLocalTransform}
- focus={this.props.focus || emptyFunction}
+ focus={this._props.focus || emptyFunction}
ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))}
/>
{this.htmlOverlay}
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index 23413b9d1..02ed56333 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -1,5 +1,5 @@
import EquationEditor from './formattedText/EquationEditor';
-import { action, reaction } from 'mobx';
+import { action, makeObservable, override, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Id } from '../../../fields/FieldSymbols';
@@ -11,6 +11,7 @@ import { ViewBoxBaseComponent } from '../DocComponent';
import { LightboxView } from '../LightboxView';
import './EquationBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
+import { copyProps } from '../../../Utils';
@observer
export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -19,10 +20,23 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
public static SelectOnLoad: string = '';
_ref: React.RefObject<EquationEditor> = React.createRef();
+
+ _prevProps: React.PropsWithChildren<FieldViewProps>;
+ @override _props: React.PropsWithChildren<FieldViewProps>;
+ constructor(props: React.PropsWithChildren<FieldViewProps>) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
componentDidMount() {
- this.props.setContentView?.(this);
- if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) {
- this.props.select(false);
+ this._props.setContentView?.(this);
+ if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath()))) {
+ this._props.select(false);
this._ref.current!.mathField.focus();
this.dataDoc.text === 'x' && this._ref.current!.mathField.select();
@@ -38,7 +52,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
//{ fireImmediately: true }
);
reaction(
- () => this.props.isSelected(),
+ () => this._props.isSelected(),
selected => {
if (this._ref.current) {
if (selected) this._ref.current.element.current.children[0].addEventListener('keydown', this.keyPressed, true);
@@ -62,7 +76,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
y: NumCast(this.layoutDoc.y) + _height + 10,
});
EquationBox.SelectOnLoad = nextEq[Id];
- this.props.addDocument?.(nextEq);
+ this._props.addDocument?.(nextEq);
e.stopPropagation();
}
if (e.key === 'Tab') {
@@ -73,10 +87,10 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
_height: 300,
backgroundColor: 'white',
});
- this.props.addDocument?.(graph);
+ this._props.addDocument?.(graph);
e.stopPropagation();
}
- if (e.key === 'Backspace' && !this.dataDoc.text) this.props.removeDocument?.(this.Document);
+ if (e.key === 'Backspace' && !this.dataDoc.text) this._props.removeDocument?.(this.Document);
};
@undoBatch
onChange = (str: string) => (this.dataDoc.text = str);
@@ -100,7 +114,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
render() {
TraceMobx();
- const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
+ const scale = (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
return (
<div
ref={r => this.updateSize()}
@@ -110,7 +124,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
transform: `scale(${scale})`,
width: 'fit-content', // `${100 / scale}%`,
height: `${100 / scale}%`,
- pointerEvents: !this.props.isSelected() ? 'none' : undefined,
+ pointerEvents: !this._props.isSelected() ? 'none' : undefined,
fontSize: StrCast(this.layoutDoc._text_fontSize),
}}
onKeyDown={e => e.stopPropagation()}>
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 8f6550663..22339907f 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -1,13 +1,13 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, MultiToggle, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, observable } from 'mobx';
+import { computed, makeObservable, observable, override } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
import { ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { copyProps, emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { SelectionManager } from '../../../util/SelectionManager';
import { SettingsManager } from '../../../util/SettingsManager';
@@ -45,6 +45,16 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(FontIconBox, fieldKey);
}
+ _prevProps: React.PropsWithChildren<ButtonProps>;
+ @override _props: React.PropsWithChildren<ButtonProps>;
+ constructor(props: React.PropsWithChildren<ButtonProps>) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+ componentDidUpdate() {
+ copyProps(this);
+ }
//
// This controls whether fontIconButtons will display labels under their icons or not
//
@@ -57,7 +67,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
@observable noTooltip = false;
showTemplate = (): void => {
const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
- dragFactory && this.props.addDocTab(dragFactory, OpenWhere.addRight);
+ dragFactory && this._props.addDocTab(dragFactory, OpenWhere.addRight);
};
dragAsTemplate = (): void => {
this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
@@ -131,7 +141,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
break;
}
const numScript = (value?: number) => ScriptCast(this.Document.script).script.run({ this: this.Document, self: this.Document, value, _readOnly_: value === undefined });
- const color = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
+ const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
// Script for checking the outcome of the toggle
const checkResult = Number(Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)));
@@ -246,7 +256,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
* Color button
*/
@computed get colorButton() {
- const color = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
+ const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
const curColor = this.colorScript?.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result ?? 'transparent';
const tooltip: string = StrCast(this.Document.toolTip);
@@ -279,7 +289,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
const script = ScriptCast(this.Document.onClick);
const toggleStatus = script ? script.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result : false;
// Colors
- const color = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
+ const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
const items = DocListCast(this.dataDoc.data);
return (
<MultiToggle
@@ -310,8 +320,8 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
const script = ScriptCast(this.Document.onClick);
const toggleStatus = script ? script.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result : false;
// Colors
- const color = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
- const backgroundColor = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor);
+ const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
+ const backgroundColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
return (
<Toggle
@@ -333,8 +343,8 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
* Default
*/
@computed get defaultButton() {
- const color = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
- const backgroundColor = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor);
+ const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
+ const backgroundColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
const tooltip: string = StrCast(this.Document.toolTip);
return <IconButton tooltip={tooltip} icon={this.Icon(color)!} label={this.label} />;
@@ -361,7 +371,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
}
render() {
- const color = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
+ const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
const tooltip = StrCast(this.Document.toolTip);
const scriptFunc = () => ScriptCast(this.Document.onClick)?.script.run({ this: this.Document, self: this.Document, _readOnly_: false });
const btnProps = { tooltip, icon: this.Icon(color)!, label: this.label };
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index 5ed5fa8fd..a04c27e01 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -1,5 +1,5 @@
import functionPlot from 'function-plot';
-import { action, computed, reaction } from 'mobx';
+import { action, computed, makeObservable, override, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast } from '../../../fields/Doc';
@@ -9,19 +9,14 @@ import { List } from '../../../fields/List';
import { createSchema, listSpec, makeInterface } from '../../../fields/Schema';
import { Cast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
+import { copyProps } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
-import { DocFocusOptions, DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { PinProps, PresBox } from './trails';
-const EquationSchema = createSchema({});
-
-type EquationDocument = makeInterface<[typeof EquationSchema, typeof documentSchema]>;
-const EquationDocument = makeInterface(EquationSchema, documentSchema);
-
@observer
export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
@@ -31,12 +26,22 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
_plot: any;
_plotId = '';
_plotEle: any;
- constructor(props: any) {
+
+ _prevProps: React.PropsWithChildren<FieldViewProps>;
+ @override _props: React.PropsWithChildren<FieldViewProps>;
+ constructor(props: React.PropsWithChildren<FieldViewProps>) {
super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
this._plotId = 'graph' + FunctionPlotBox.GraphCount++;
}
+
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
componentDidMount() {
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
reaction(
() => [DocListCast(this.dataDoc[this.fieldKey]).map(doc => doc?.text), this.layoutDoc.width, this.layoutDoc.height, this.layoutDoc.xRange, this.layoutDoc.yRange],
() => this.createGraph()
@@ -52,8 +57,8 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
};
createGraph = (ele?: HTMLDivElement) => {
this._plotEle = ele || this._plotEle;
- const width = this.props.PanelWidth();
- const height = this.props.PanelHeight();
+ const width = this._props.PanelWidth();
+ const height = this._props.PanelHeight();
const fns = DocListCast(this.dataDoc.data).map(doc => StrCast(doc.text, 'x^2').replace(/\\frac\{(.*)\}\{(.*)\}/, '($1/$2)'));
try {
this._plotEle.children.length && this._plotEle.removeChild(this._plotEle.children[0]);
@@ -77,7 +82,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
@undoBatch
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData?.droppedDocuments.length) {
- const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => res && Doc.AddDocToList(this.dataDoc, this.props.fieldKey, doc), true);
+ const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc), true);
!added && e.preventDefault();
e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
return added;
@@ -102,14 +107,14 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
<div
ref={this.createDropTarget}
style={{
- pointerEvents: !this.props.isContentActive() ? 'all' : undefined,
- width: this.props.PanelWidth(),
- height: this.props.PanelHeight(),
+ pointerEvents: !this._props.isContentActive() ? 'all' : undefined,
+ width: this._props.PanelWidth(),
+ height: this._props.PanelHeight(),
}}>
{this.theGraph}
<div
style={{
- display: this.props.isSelected() ? 'none' : undefined,
+ display: this._props.isSelected() ? 'none' : undefined,
position: 'absolute',
width: '100%',
height: '100%',
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index dbfeec1c3..091c1a32f 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,25 +1,20 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, override, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { extname } from 'path';
+import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
-import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
-import { createSchema } from '../../../fields/Schema';
-import { ComputedField } from '../../../fields/ScriptField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
-import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
-import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
+import { copyProps, DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
-import { Networking } from '../../Network';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
@@ -31,22 +26,9 @@ import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { AnchorMenu } from '../pdf/AnchorMenu';
import { StyleProp } from '../StyleProvider';
import { DocFocusOptions, OpenWhere } from './DocumentView';
-import { FaceRectangles } from './FaceRectangles';
import { FieldView, FieldViewProps } from './FieldView';
import './ImageBox.scss';
import { PinProps, PresBox } from './trails';
-import * as React from 'react';
-
-export const pageSchema = createSchema({
- googlePhotosUrl: 'string',
- googlePhotosTags: 'string',
-});
-const uploadIcons = {
- idle: 'downarrow.png',
- loading: 'loading.gif',
- success: 'greencheck.png',
- failure: 'redx.png',
-};
@observer
export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
@@ -54,10 +36,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return FieldView.LayoutString(ImageBox, fieldKey);
}
- @observable public static imageRootDoc: Doc | undefined;
+ @observable public static imageRootDoc: Doc | undefined = undefined;
@observable public static imageEditorOpen: boolean = false;
@observable public static imageEditorSource: string = '';
- @observable public static addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined;
+ @observable public static addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined = undefined;
@action public static setImageEditorOpen(open: boolean) {
ImageBox.imageEditorOpen = open;
}
@@ -72,16 +54,23 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
private _overlayIconRef = React.createRef<HTMLDivElement>();
private _marqueeref = React.createRef<MarqueeAnnotator>();
@observable _curSuffix = '';
- @observable _uploadIcon = uploadIcons.idle;
- constructor(props: any) {
+ _prevProps: ViewBoxAnnotatableProps & FieldViewProps;
+ @override _props: ViewBoxAnnotatableProps & FieldViewProps;
+ constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
super(props);
- this.props.setContentView?.(this);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ this._props.setContentView?.(this);
+ }
+
+ componentDidUpdate() {
+ copyProps(this);
}
protected createDropTarget = (ele: HTMLDivElement) => {
this._dropDisposer?.();
- ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
+ ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.Document));
};
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
@@ -107,9 +96,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
componentDidMount() {
this._disposers.sizer = reaction(
() => ({
- forceFull: this.props.renderDepth < 1 || this.layoutDoc._showFullRes,
- scrSize: (this.props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth) * NumCast(this.layoutDoc._freeform_scale, 1),
- selected: this.props.isSelected(),
+ forceFull: this._props.renderDepth < 1 || this.layoutDoc._showFullRes,
+ scrSize: (this._props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth) * NumCast(this.layoutDoc._freeform_scale, 1),
+ selected: this._props.isSelected(),
}),
({ forceFull, scrSize, selected }) => (this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o'),
{ fireImmediately: true, delay: 1000 }
@@ -141,7 +130,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
@undoBatch
- @action
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData) {
let added: boolean | undefined = undefined;
@@ -177,8 +165,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@undoBatch
setNativeSize = action(() => {
- const scaling = (this.props.DocumentView?.().props.ScreenToLocalTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
- const nscale = NumCast(this.props.PanelWidth()) / scaling;
+ const scaling = (this._props.DocumentView?.()._props.ScreenToLocalTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
+ const nscale = NumCast(this._props.PanelWidth()) / scaling;
const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw;
this.dataDoc[this.fieldKey + '_nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) * nw;
@@ -217,8 +205,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
cropping.title = 'crop: ' + this.Document.title;
cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
cropping.y = NumCast(this.Document.y);
- cropping._width = anchw * (this.props.NativeDimScaling?.() || 1);
- cropping._height = anchh * (this.props.NativeDimScaling?.() || 1);
+ cropping._width = anchw * (this._props.NativeDimScaling?.() || 1);
+ cropping._height = anchh * (this._props.NativeDimScaling?.() || 1);
cropping.onClick = undefined;
const croppingProto = Doc.GetProto(cropping);
croppingProto.annotationOn = undefined;
@@ -242,10 +230,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
cropping.y = NumCast(this.Document.y);
- this.props.addDocTab(cropping, OpenWhere.inParent);
+ this._props.addDocTab(cropping, OpenWhere.inParent);
}
DocumentManager.Instance.AddViewRenderedCb(cropping, dv => setTimeout(() => (dv.ComponentView as ImageBox).setNativeSize(), 200));
- this.props.bringToFront(cropping);
+ this._props.bringToFront(cropping);
return cropping;
};
@@ -262,55 +250,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
event: action(() => {
ImageBox.setImageEditorOpen(true);
ImageBox.setImageEditorSource(this.choosePath(field.url));
- ImageBox.addDoc = this.props.addDocument;
+ ImageBox.addDoc = this._props.addDocument;
ImageBox.imageRootDoc = this.Document;
}),
icon: 'pencil-alt',
});
- if (!Doc.noviceMode) {
- funcs.push({ description: 'Export to Google Photos', event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: 'caret-square-right' });
-
- const existingAnalyze = ContextMenu.Instance?.findByDescription('Analyzers...');
- const modes: ContextMenuProps[] = existingAnalyze && 'subitems' in existingAnalyze ? existingAnalyze.subitems : [];
- modes.push({ description: 'Generate Tags', event: this.generateMetadata, icon: 'tag' });
- modes.push({ description: 'Find Faces', event: this.extractFaces, icon: 'camera' });
- //modes.push({ description: "Recommend", event: this.extractText, icon: "brain" });
- !existingAnalyze && ContextMenu.Instance?.addItem({ description: 'Analyzers...', subitems: modes, icon: 'hand-point-right' });
- }
-
ContextMenu.Instance?.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' });
}
};
- extractFaces = () => {
- const converter = (results: any) => {
- return results.map((face: CognitiveServices.Image.Face) => Doc.Get.FromJson({ data: face, title: `Face: ${face.faceId}` })!);
- };
- this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + '-faces'], this.url, Service.Face, converter);
- };
-
- generateMetadata = (threshold: Confidence = Confidence.Excellent) => {
- const converter = (results: any) => {
- const tagDoc = new Doc();
- const tagsList = new List();
- results.tags.map((tag: Tag) => {
- tagsList.push(tag.name);
- const sanitized = tag.name.replace(' ', '_');
- tagDoc[sanitized] = ComputedField.MakeFunction(`(${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`);
- });
- this.dataDoc[this.fieldKey + '-generatedTags'] = tagsList;
- tagDoc.title = 'Generated Tags Doc';
- tagDoc.confidence = threshold;
- return tagDoc;
- };
- this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + '-generatedTagsDoc'], this.url, Service.ComputerVision, converter);
- };
-
- @computed private get url() {
- const data = Cast(this.dataDoc[this.fieldKey], ImageField);
- return data ? data.url?.href : undefined;
- }
-
choosePath(url: URL) {
if (!url?.href) return '';
const lower = url.href.toLowerCase();
@@ -321,60 +269,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
const ext = extname(url.href);
return url.href.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
}
-
- considerGooglePhotosLink = () => {
- const remoteUrl = StrCast(this.dataDoc.googlePhotosUrl); // bcz: StrCast or URLCast???
- return !remoteUrl ? null : <img draggable={false} style={{ transformOrigin: 'bottom right' }} id={'google-photos'} src={'/assets/google_photos.png'} onClick={() => window.open(remoteUrl)} />;
- };
-
- considerGooglePhotosTags = () => {
- const tags = this.dataDoc.googlePhotosTags;
- return !tags ? null : <img id={'google-tags'} src={'/assets/google_tags.png'} />;
- };
-
- getScrollHeight = () => (this.props.layout_fitWidth?.(this.Document) !== false && NumCast(this.layoutDoc._freeform_scale, 1) === NumCast(this.dataDoc._freeform_scaleMin, 1) ? this.nativeSize.nativeHeight : undefined);
-
- @computed
- private get considerDownloadIcon() {
- const data = this.dataDoc[this.fieldKey];
- if (!(data instanceof ImageField)) {
- return null;
- }
- const primary = data.url?.href;
- if (primary.includes(window.location.origin)) {
- return null;
- }
- return (
- <img
- id={'upload-icon'}
- draggable={false}
- style={{ transformOrigin: 'bottom right' }}
- src={`/assets/${this._uploadIcon}`}
- onClick={async () => {
- const { dataDoc } = this;
- const { success, failure, idle, loading } = uploadIcons;
- runInAction(() => (this._uploadIcon = loading));
- const [{ accessPaths }] = await Networking.PostToServer('/uploadRemoteImage', { sources: [primary] });
- dataDoc[this.props.fieldKey + '-originalUrl'] = primary;
- let succeeded = true;
- let data: ImageField | undefined;
- try {
- data = new ImageField(accessPaths.agnostic.client);
- } catch {
- succeeded = false;
- }
- runInAction(() => (this._uploadIcon = succeeded ? success : failure));
- setTimeout(
- action(() => {
- this._uploadIcon = idle;
- data && (dataDoc[this.fieldKey] = data);
- }),
- 2000
- );
- }}
- />
- );
- }
+ getScrollHeight = () => (this._props.layout_fitWidth?.(this.Document) !== false && NumCast(this.layoutDoc._freeform_scale, 1) === NumCast(this.dataDoc._freeform_scaleMin, 1) ? this.nativeSize.nativeHeight : undefined);
@computed get nativeSize() {
TraceMobx();
@@ -407,7 +302,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
ref={this._overlayIconRef}
onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))}
style={{
- display: (this.props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none',
+ display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none',
width: 'min(10%, 25px)',
height: 'min(10%, 25px)',
background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
@@ -436,7 +331,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@computed get content() {
TraceMobx();
- const backColor = DashColor(this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor));
+ const backColor = DashColor(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor));
const backAlpha = backColor.red() === 0 && backColor.green() === 0 && backColor.blue() === 0 ? backColor.alpha() : 1;
const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0];
const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement();
@@ -466,9 +361,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
</div>
)}
</div>
- {!Doc.noviceMode && this.considerDownloadIcon}
- {this.considerGooglePhotosLink()}
- <FaceRectangles document={this.dataDoc} color={'#0000FF'} backgroundColor={'#0000FF'} />
{this.overlayImageIcon}
</div>
);
@@ -479,11 +371,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@computed get annotationLayer() {
TraceMobx();
- return <div className="imageBox-annotationLayer" style={{ height: this.props.PanelHeight() }} ref={this._annotationLayer} />;
+ return <div className="imageBox-annotationLayer" style={{ height: this._props.PanelHeight() }} ref={this._annotationLayer} />;
}
- screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.props.ScreenToLocalTransform().Scale);
+ screenToLocalTransform = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this._props.ScreenToLocalTransform().Scale);
marqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
setupMoveUpEvents(
this,
e,
@@ -502,7 +394,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
finishMarquee = () => {
this._getAnchor = AnchorMenu.Instance?.GetAnchor;
this._marqueeref.current?.onTerminateSelection();
- this.props.select(false);
+ this._props.select(false);
};
focus = (anchor: Doc, options: DocFocusOptions) => (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options));
@@ -510,8 +402,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
savedAnnotations = () => this._savedAnnotations;
render() {
TraceMobx();
- const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
- const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this.props.NativeDimScaling?.() || 1)}px` : borderRad;
+ const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding);
+ const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this._props.NativeDimScaling?.() || 1)}px` : borderRad;
return (
<div
className="imageBox"
@@ -527,25 +419,25 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
})}
style={{
- width: this.props.PanelWidth() ? undefined : `100%`,
- height: this.props.PanelWidth() ? undefined : `100%`,
+ width: this._props.PanelWidth() ? undefined : `100%`,
+ height: this._props.PanelWidth() ? undefined : `100%`,
pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
borderRadius,
- overflow: this.layoutDoc.layout_fitWidth || this.props.layout_fitWidth?.(this.Document) ? 'auto' : undefined,
+ overflow: this.layoutDoc.layout_fitWidth || this._props.layout_fitWidth?.(this.Document) ? 'auto' : undefined,
}}>
<CollectionFreeFormView
ref={this._ffref}
- {...this.props}
+ {...this._props}
setContentView={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
fieldKey={this.annotationKey}
- styleProvider={this.props.styleProvider}
+ styleProvider={this._props.styleProvider}
isAnnotationOverlay={true}
annotationLayerHostsContent={true}
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.props.PanelHeight}
+ PanelWidth={this._props.PanelWidth}
+ PanelHeight={this._props.PanelHeight}
ScreenToLocalTransform={this.screenToLocalTransform}
select={emptyFunction}
focus={this.focus}
@@ -566,8 +458,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
scrollTop={0}
annotationLayerScrollTop={0}
scaling={returnOne}
- annotationLayerScaling={this.props.NativeDimScaling}
- docView={this.props.DocumentView!}
+ annotationLayerScaling={this._props.NativeDimScaling}
+ docView={this._props.DocumentView!}
addDocument={this.addDocument}
finishMarquee={this.finishMarquee}
savedAnnotations={this.savedAnnotations}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index d2325a807..9aab53daf 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, Field, FieldResult } from '../../../fields/Doc';
import { List } from '../../../fields/List';
@@ -6,7 +6,7 @@ import { RichTextField } from '../../../fields/RichTextField';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { DocCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
-import { returnAll, returnAlways, returnTrue } from '../../../Utils';
+import { copyProps, returnAll, returnAlways, returnTrue } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { SetupDrag } from '../../util/DragManager';
import { CompiledScript, CompileScript, ScriptOptions } from '../../util/Scripting';
@@ -38,8 +38,20 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
private _keyInput = React.createRef<HTMLInputElement>();
private _valInput = React.createRef<HTMLInputElement>();
+ _prevProps: FieldViewProps;
+ @observable _props: FieldViewProps;
+ constructor(props: FieldViewProps) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
componentDidMount() {
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
}
isKeyValueBox = returnTrue;
able = returnAlways;
@@ -50,7 +62,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
@observable _splitPercentage = 50;
get fieldDocToLayout() {
- return this.props.fieldKey ? DocCast(this.props.Document[this.props.fieldKey], DocCast(this.props.Document)) : this.props.Document;
+ return this._props.fieldKey ? DocCast(this._props.Document[this._props.fieldKey], DocCast(this._props.Document)) : this._props.Document;
}
@action
@@ -109,7 +121,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
}
onPointerDown = (e: React.PointerEvent): void => {
- if (e.buttons === 1 && this.props.isSelected()) {
+ if (e.buttons === 1 && this._props.isSelected()) {
e.stopPropagation();
}
};
@@ -155,8 +167,8 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
rows.push(
<KeyValuePair
doc={realDoc}
- addDocTab={this.props.addDocTab}
- PanelWidth={this.props.PanelWidth}
+ addDocTab={this._props.addDocTab}
+ PanelWidth={this._props.PanelWidth}
PanelHeight={this.rowHeight}
ref={(function () {
let oldEl: KeyValuePair | undefined;
@@ -220,19 +232,19 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
getFieldView = () => {
const rows = this.rows.filter(row => row.isChecked);
if (rows.length > 1) {
- const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this.props.Document).title}`, _chromeHidden: true });
+ const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this._props.Document).title}`, _chromeHidden: true });
for (const row of rows) {
- const field = this.createFieldView(DocCast(this.props.Document), row);
+ const field = this.createFieldView(DocCast(this._props.Document), row);
field && Doc.AddDocToList(parent, 'data', field);
row.uncheck();
}
return parent;
}
- return rows.length ? this.createFieldView(DocCast(this.props.Document), rows.lastElement()) : undefined;
+ return rows.length ? this.createFieldView(DocCast(this._props.Document), rows.lastElement()) : undefined;
};
createFieldView = (templateDoc: Doc, row: KeyValuePair) => {
- const metaKey = row.props.keyName;
+ const metaKey = row._props.keyName;
const fieldTemplate = Doc.IsDelegateField(templateDoc, metaKey) ? Doc.MakeDelegate(templateDoc) : Doc.MakeEmbedding(templateDoc);
fieldTemplate.title = metaKey;
fieldTemplate.layout_fitWidth = true;
@@ -278,8 +290,8 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
openItems.push({
description: 'Default Perspective',
event: () => {
- this.props.addDocTab(this.props.Document, OpenWhere.close);
- this.props.addDocTab(this.fieldDocToLayout, OpenWhere.addRight);
+ this._props.addDocTab(this._props.Document, OpenWhere.close);
+ this._props.addDocTab(this.fieldDocToLayout, OpenWhere.addRight);
},
icon: 'image',
});
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 3cda70648..40991f371 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,7 +1,7 @@
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable, toJS } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, Field } from '../../../fields/Doc';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../Utils';
+import { copyProps, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../Utils';
import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
@@ -34,6 +34,18 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
@observable public isChecked = false;
private checkbox = React.createRef<HTMLInputElement>();
+ _prevProps: KeyValuePairProps;
+ @observable _props: KeyValuePairProps;
+ constructor(props: KeyValuePairProps) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
@action
handleCheck = (e: React.ChangeEvent<HTMLInputElement>) => {
this.isChecked = e.currentTarget.checked;
@@ -46,24 +58,24 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
};
onContextMenu = (e: React.MouseEvent) => {
- const value = this.props.doc[this.props.keyName];
+ const value = this._props.doc[this._props.keyName];
if (value instanceof Doc) {
e.stopPropagation();
e.preventDefault();
- ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this.props.addDocTab(value, OpenWhere.addRightKeyvalue), icon: 'layer-group' });
+ ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this._props.addDocTab(value, OpenWhere.addRightKeyvalue), icon: 'layer-group' });
ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
}
};
render() {
const props: FieldViewProps = {
- Document: this.props.doc,
+ Document: this._props.doc,
childFilters: returnEmptyFilter,
childFiltersByRanges: returnEmptyFilter,
searchFilterDocs: returnEmptyDoclist,
styleProvider: DefaultStyleProvider,
docViewPath: returnEmptyDoclist,
- fieldKey: this.props.keyName,
+ fieldKey: this._props.keyName,
isSelected: returnFalse,
setHeight: returnFalse,
select: emptyFunction,
@@ -73,12 +85,11 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
whenChildContentsActiveChanged: emptyFunction,
ScreenToLocalTransform: Transform.Identity,
focus: emptyFunction,
- PanelWidth: this.props.PanelWidth,
- PanelHeight: this.props.PanelHeight,
+ PanelWidth: this._props.PanelWidth,
+ PanelHeight: this._props.PanelHeight,
addDocTab: returnFalse,
pinToPres: returnZero,
};
- const contents = <FieldView {...props} />;
// let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")";
let protoCount = 0;
let doc: Doc | undefined = props.Document;
@@ -95,8 +106,8 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
const hover = { transition: '0.3s ease opacity', opacity: this.isPointerOver || this.isChecked ? 1 : 0 };
return (
- <tr className={this.props.rowStyle} onPointerEnter={action(() => (this.isPointerOver = true))} onPointerLeave={action(() => (this.isPointerOver = false))}>
- <td className="keyValuePair-td-key" style={{ width: `${this.props.keyWidth}%` }}>
+ <tr className={this._props.rowStyle} onPointerEnter={action(() => (this.isPointerOver = true))} onPointerLeave={action(() => (this.isPointerOver = false))}>
+ <td className="keyValuePair-td-key" style={{ width: `${this._props.keyWidth}%` }}>
<div className="keyValuePair-td-key-container">
<button
style={hover}
@@ -118,9 +129,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
</Tooltip>
</div>
</td>
- <td className="keyValuePair-td-value" style={{ width: `${100 - this.props.keyWidth}%` }} onContextMenu={this.onContextMenu}>
+ <td className="keyValuePair-td-value" style={{ width: `${100 - this._props.keyWidth}%` }} onContextMenu={this.onContextMenu}>
<div className="keyValuePair-td-value-container">
- <EditableView contents={contents} GetValue={() => Field.toKeyValueString(props.Document, props.fieldKey)} SetValue={(value: string) => KeyValueBox.SetField(props.Document, props.fieldKey, value)} />
+ <EditableView contents={undefined} fieldContents={props} GetValue={() => Field.toKeyValueString(props.Document, props.fieldKey)} SetValue={(value: string) => KeyValueBox.SetField(props.Document, props.fieldKey, value)} />
</div>
</td>
</tr>
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index f8140af93..52ca8b5b1 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -1,10 +1,11 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable, override } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { Cast, StrCast, NumCast, BoolCast } from '../../../fields/Types';
+import { copyProps } from '../../../Utils';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
@@ -29,21 +30,32 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
}
private dropDisposer?: DragManager.DragDropDisposer;
private _timeout: any;
+
+ _prevProps: React.PropsWithChildren<FieldViewProps & LabelBoxProps>;
+ @override _props: React.PropsWithChildren<FieldViewProps & LabelBoxProps>;
+ constructor(props: React.PropsWithChildren<FieldViewProps & LabelBoxProps>) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+ componentDidUpdate() {
+ copyProps(this);
+ }
componentDidMount() {
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
}
componentWillUnMount() {
this._timeout && clearTimeout(this._timeout);
}
@computed get Title() {
- return this.dataDoc.title_custom ? StrCast(this.Document.title) : this.props.label ? this.props.label : typeof this.dataDoc[this.fieldKey] === 'string' ? StrCast(this.dataDoc[this.fieldKey]) : StrCast(this.Document.title);
+ return this.dataDoc.title_custom ? StrCast(this.Document.title) : this._props.label ? this._props.label : typeof this.dataDoc[this.fieldKey] === 'string' ? StrCast(this.dataDoc[this.fieldKey]) : StrCast(this.Document.title);
}
protected createDropTarget = (ele: HTMLDivElement) => {
this.dropDisposer?.();
if (ele) {
- this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document);
+ this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.Document);
}
};
@@ -66,7 +78,6 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
};
@undoBatch
- @action
drop = (e: Event, de: DragManager.DropEvent) => {
const docDragData = de.complete.docDragData;
const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []);
@@ -81,7 +92,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
@observable _mouseOver = false;
@computed get hoverColor() {
- return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor);
+ return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
}
fitTextToBox = (
@@ -142,7 +153,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
onMouseOver={action(() => (this._mouseOver = true))}
ref={this.createDropTarget}
onContextMenu={this.specificContextMenu}
- style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow) }}>
+ style={{ boxShadow: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BoxShadow) }}>
<div
className="labelBox-mainButton"
style={{
@@ -156,8 +167,8 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
paddingRight: NumCast(this.layoutDoc._xPadding),
paddingTop: NumCast(this.layoutDoc._yPadding),
paddingBottom: NumCast(this.layoutDoc._yPadding),
- width: this.props.PanelWidth(),
- height: this.props.PanelHeight(),
+ width: this._props.PanelWidth(),
+ height: this._props.PanelHeight(),
whiteSpace: 'singleLine' in boxParams && boxParams.singleLine ? 'pre' : 'pre-wrap',
}}>
<span style={{ width: 'singleLine' in boxParams ? '' : '100%' }} ref={action((r: any) => this.fitTextToBox(r))}>
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index ed448ecfb..ff2597fb4 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -103,7 +103,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
componentWillUnmount(): void {
this.disposer?.();
}
- @observable renderProps: { lx: number; rx: number; ty: number; by: number; pts: number[][] } | undefined;
+ @observable renderProps: { lx: number; rx: number; ty: number; by: number; pts: number[][] } | undefined = undefined;
render() {
if (this.renderProps) {
const highlight = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Highlighting);
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 4df8e792e..dd102edef 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -144,8 +144,8 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
LinkManager.currentLink = this._linkDoc;
LinkManager.currentLinkAnchor = this._linkSrc;
this.props.docProps.DocumentView?.().select(false);
- if ((SettingsManager.propertiesWidth ?? 0) < 100) {
- SettingsManager.propertiesWidth = 250;
+ if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
+ SettingsManager.Instance.propertiesWidth = 250;
}
})
);
diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx
index e554cb8ad..bbb725a3d 100644
--- a/src/client/views/nodes/LoadingBox.tsx
+++ b/src/client/views/nodes/LoadingBox.tsx
@@ -6,6 +6,7 @@ import { Doc } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { StrCast } from '../../../fields/Types';
import { Networking } from '../../Network';
+import { DocumentManager } from '../../util/DocumentManager';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { FieldView, FieldViewProps } from './FieldView';
import './LoadingBox.scss';
@@ -36,28 +37,27 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(LoadingBox, fieldKey);
}
- @observable public static CurrentlyLoading: Doc[] = []; // this assignment doesn't work. the actual assignment happens in DocumentManager's constructor
// removes from currently loading display
@action
public static removeCurrentlyLoading(doc: Doc) {
- if (LoadingBox.CurrentlyLoading) {
- const index = LoadingBox.CurrentlyLoading.indexOf(doc);
- index !== -1 && LoadingBox.CurrentlyLoading.splice(index, 1);
+ if (DocumentManager.Instance.CurrentlyLoading) {
+ const index = DocumentManager.Instance.CurrentlyLoading.indexOf(doc);
+ index !== -1 && DocumentManager.Instance.CurrentlyLoading.splice(index, 1);
}
}
// adds doc to currently loading display
@action
public static addCurrentlyLoading(doc: Doc) {
- if (LoadingBox.CurrentlyLoading.indexOf(doc) === -1) {
- LoadingBox.CurrentlyLoading.push(doc);
+ if (DocumentManager.Instance.CurrentlyLoading.indexOf(doc) === -1) {
+ DocumentManager.Instance.CurrentlyLoading.push(doc);
}
}
_timer: any;
@observable progress = '';
componentDidMount() {
- if (!LoadingBox.CurrentlyLoading?.includes(this.Document)) {
+ if (!DocumentManager.Instance.CurrentlyLoading?.includes(this.Document)) {
this.Document.loadingError = 'Upload interrupted, please try again';
} else {
const updateFunc = async () => {
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 69723b171..98a302834 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1,20 +1,18 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import BingMapsReact from 'bingmaps-react';
import { Button, EditableText, IconButton, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, override, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
import { DocCss, Highlight } from '../../../../fields/DocSymbols';
-import { Id } from '../../../../fields/FieldSymbols';
import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { copyProps, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
import { Docs, DocUtils } from '../../../documents/Documents';
import { DocumentType } from '../../../documents/DocumentTypes';
import { DocumentManager } from '../../../util/DocumentManager';
import { DragManager } from '../../../util/DragManager';
import { LinkManager } from '../../../util/LinkManager';
-import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { undoable, UndoManager } from '../../../util/UndoManager';
import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
@@ -68,7 +66,19 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
private _sidebarRef = React.createRef<SidebarAnnos>();
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
private _disposers: { [key: string]: IReactionDisposer } = {};
- private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
+
+ _unmounting = false;
+ _prevProps: ViewBoxAnnotatableProps & FieldViewProps;
+ @override _props: ViewBoxAnnotatableProps & FieldViewProps;
+ constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+
+ componentDidUpdate() {
+ copyProps(this);
+ }
@observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@computed get allSidebarDocs() {
@@ -88,7 +98,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
}
@computed get sidebarColor() {
- return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4'));
+ return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
}
@computed get SidebarKey() {
return this.fieldKey + '_sidebar';
@@ -96,10 +106,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
componentDidMount() {
this._unmounting = false;
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
}
- _unmounting = false;
componentWillUnmount(): void {
this._unmounting = true;
this.deselectPin();
@@ -161,9 +170,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
e,
(e, down, delta) =>
runInAction(() => {
- const localDelta = this.props
+ const localDelta = this._props
.ScreenToLocalTransform()
- .scale(this.props.NativeDimScaling?.() || 1)
+ .scale(this._props.NativeDimScaling?.() || 1)
.transformDirection(delta[0], delta[1]);
const fullWidth = NumCast(this.layoutDoc._width);
const mapWidth = fullWidth - this.sidebarWidth();
@@ -182,7 +191,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
() => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map')
);
};
- sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
+ sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
/**
* Handles toggle of sidebar on click the little comment button
@@ -194,7 +203,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
key="sidebar"
title="Toggle Sidebar"
style={{
- display: !this.props.isContentActive() ? 'none' : undefined,
+ display: !this._props.isContentActive() ? 'none' : undefined,
top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 5,
backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
}}
@@ -231,12 +240,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
FormattedTextBox.SetSelectOnLoad(target);
return target;
};
- const docView = this.props.DocumentView?.();
+ const docView = this._props.DocumentView?.();
docView &&
DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
dragComplete: e => {
if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
- e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.Document;
+ e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.Document;
e.annoDragData.linkSourceDoc.followLinkZoom = false;
}
},
@@ -271,19 +280,17 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return false;
};
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
-
addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey);
- pointerEvents = () => (this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
+ pointerEvents = () => (this._props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
- panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth();
- panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
- scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
- transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter];
- opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter];
- infoWidth = () => this.props.PanelWidth() / 5;
- infoHeight = () => this.props.PanelHeight() / 5;
+ panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
+ panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
+ scrollXf = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+ transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
+ opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter];
+ infoWidth = () => this._props.PanelWidth() / 5;
+ infoHeight = () => this._props.PanelHeight() / 5;
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
savedAnnotations = () => this._savedAnnotations;
@@ -348,7 +355,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}, 'createpin');
// The pin that is selected
- @observable selectedPin: Doc | undefined;
+ @observable selectedPin: Doc | undefined = undefined;
@action
deselectPin = () => {
@@ -398,9 +405,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPin.latitude, this.selectedPin.longitude));
- const x = point.x + (this.props.PanelWidth() - this.sidebarWidth()) / 2;
- const y = point.y + this.props.PanelHeight() / 2 + 32;
- const cpt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+ const x = point.x + (this._props.PanelWidth() - this.sidebarWidth()) / 2;
+ const y = point.y + this._props.PanelHeight() / 2 + 32;
+ const cpt = this._props.ScreenToLocalTransform().inverse().transformPoint(x, y);
MapAnchorMenu.Instance.jumpTo(cpt[0], cpt[1], true);
document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
@@ -411,7 +418,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
*/
@action
mapOnClick = (e: { location: { latitude: any; longitude: any } }) => {
- this.props.select(false);
+ this._props.select(false);
this.deselectPin();
};
/*
@@ -454,6 +461,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
/*
* Returns doc w/ relevant info
*/
+
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps, existingPin?: Doc) => {
/// this should use SELECTED pushpin for lat/long if there is a selection, otherwise CENTER
const anchor = Docs.Create.ConfigDocument({
@@ -599,6 +607,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
* Initializes starting values
*/
@observable _mapReady = false;
+
@action
bingMapReady = (map: any) => {
this._mapReady = true;
@@ -676,9 +685,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
let target = document.elementFromPoint(e.x, e.y);
while (target) {
if (target === this._ref.current) {
- const cpt = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
- const x = cpt[0] - (this.props.PanelWidth() - this.sidebarWidth()) / 2;
- const y = cpt[1] - 32 /* height of search bar */ - this.props.PanelHeight() / 2;
+ const cpt = this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+ const x = cpt[0] - (this._props.PanelWidth() - this.sidebarWidth()) / 2;
+ const y = cpt[1] - 32 /* height of search bar */ - this._props.PanelHeight() / 2;
const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
this.createPushpin(location.latitude, location.longitude);
break;
@@ -717,7 +726,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return null;
}
- const renderAnnotations = (childFilters?: () => string[]) => null;
return (
<div className="mapBox" ref={this._ref}>
<div
@@ -727,10 +735,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
e.button === 0 && !e.ctrlKey && e.stopPropagation();
}}
style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
- <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
- {renderAnnotations(this.opaqueFilter)}
- {SnappingManager.GetIsDragging() ? null : renderAnnotations()}
-
<div className="mapBox-searchbar">
<EditableText
// editing
@@ -771,8 +775,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
.map((pushpin, i) => (
<DocumentView
key={i}
- {...this.props}
- renderDepth={this.props.renderDepth + 1}
+ {...this._props}
+ renderDepth={this._props.renderDepth + 1}
Document={pushpin}
TemplateDataDocument={undefined}
PanelWidth={returnOne}
@@ -799,7 +803,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
<div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
<SidebarAnnos
ref={this._sidebarRef}
- {...this.props}
+ {...this._props}
fieldKey={this.fieldKey}
Document={this.Document}
layoutDoc={this.layoutDoc}
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index 39ed6a47e..8a6d68a71 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -101,7 +101,7 @@
// }
// @observable private _map: google.maps.Map = null as unknown as google.maps.Map;
-// @observable private selectedPlace: Doc | undefined;
+// @observable private selectedPlace: Doc | undefined = undefined;
// @observable private markerMap: { [id: string]: google.maps.Marker } = {};
// @observable private center = navigator.geolocation ? navigator.geolocation.getCurrentPosition : defaultCenter;
// @observable private inputRef = React.createRef<HTMLInputElement>();
diff --git a/src/client/views/nodes/MapBox/MapPushpinBox.tsx b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
index 590f8735c..8760c8600 100644
--- a/src/client/views/nodes/MapBox/MapPushpinBox.tsx
+++ b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
@@ -1,15 +1,11 @@
-import { observer } from 'mobx-react';
-// import { SettingsManager } from '../../../util/SettingsManager';
+import * as React from 'react';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { FieldView, FieldViewProps } from '../FieldView';
-import * as React from 'react';
-import { computed } from 'mobx';
import { MapBox } from './MapBox';
/**
* Map Pushpin doc class
*/
-@observer
export class MapPushpinBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(MapPushpinBox, fieldKey);
@@ -21,11 +17,11 @@ export class MapPushpinBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.mapBoxView.deletePushpin(this.Document);
}
- @computed get mapBoxView() {
- return this.props.DocumentView?.()?.props.docViewPath().lastElement()?.ComponentView as MapBox;
+ get mapBoxView() {
+ return this.props.DocumentView?.()?._props.docViewPath().lastElement()?.ComponentView as MapBox;
}
- @computed get mapBox() {
- return this.props.DocumentView?.().props.docViewPath().lastElement()?.Document;
+ get mapBox() {
+ return this.props.DocumentView?.()._props.docViewPath().lastElement()?.Document;
}
render() {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 8de8498d7..2a884cef8 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,5 +1,5 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as Pdfjs from 'pdfjs-dist';
import 'pdfjs-dist/web/pdf_viewer.css';
@@ -10,7 +10,7 @@ import { ComputedField } from '../../../fields/ScriptField';
import { Cast, FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField, PdfField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils';
+import { copyProps, emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
@@ -52,14 +52,18 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@observable private _pageControls = false;
@computed get pdfUrl() {
- return Cast(this.dataDoc[this.props.fieldKey], PdfField);
+ return Cast(this.dataDoc[this._props.fieldKey], PdfField);
}
@computed get pdfThumb() {
return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url;
}
- constructor(props: any) {
+ _prevProps: ViewBoxAnnotatableProps & FieldViewProps;
+ @override _props: ViewBoxAnnotatableProps & FieldViewProps;
+ constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
const nw = Doc.NativeWidth(this.Document, this.dataDoc) || 927;
const nh = Doc.NativeHeight(this.Document, this.dataDoc) || 1200;
!this.Document._layout_fitWidth && (this.Document._height = NumCast(this.Document._width) * (nh / nw));
@@ -68,6 +72,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action((pdf: any) => (this._pdf = pdf)));
}
}
+ componentDidUpdate() {
+ copyProps(this);
+ }
replaceCanvases = (oldDiv: HTMLElement, newDiv: HTMLElement) => {
if (oldDiv.childNodes) {
@@ -101,7 +108,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
Doc.GetProto(region).followLinkToggle = true;
this.addDocument(region);
- const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
+ const docViewContent = this._props.docViewPath().lastElement().ContentDiv!;
const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
newDiv.style.width = NumCast(this.layoutDoc._width).toString();
newDiv.style.height = NumCast(this.layoutDoc._height).toString();
@@ -110,8 +117,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const anchx = NumCast(cropping.x);
const anchy = NumCast(cropping.y);
- const anchw = NumCast(cropping._width) * (this.props.NativeDimScaling?.() || 1);
- const anchh = NumCast(cropping._height) * (this.props.NativeDimScaling?.() || 1);
+ const anchw = NumCast(cropping._width) * (this._props.NativeDimScaling?.() || 1);
+ const anchh = NumCast(cropping._height) * (this._props.NativeDimScaling?.() || 1);
const viewScale = 1;
cropping.title = 'crop: ' + this.Document.title;
cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
@@ -131,7 +138,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (addCrop) {
DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
}
- this.props.bringToFront(cropping);
+ this._props.bringToFront(cropping);
CreateImage(
'',
@@ -139,8 +146,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
htmlString,
anchw,
anchh,
- (NumCast(region.y) * this.props.PanelWidth()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']),
- (NumCast(region.x) * this.props.PanelWidth()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']),
+ (NumCast(region.y) * this._props.PanelWidth()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']),
+ (NumCast(region.x) * this._props.PanelWidth()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']),
4
)
.then((data_url: any) => {
@@ -162,7 +169,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
updateIcon = () => {
// currently we render pdf icons as text labels
- const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
+ const docViewContent = this._props.docViewPath().lastElement().ContentDiv!;
const filename = this.layoutDoc[Id] + '-icon' + new Date().getTime();
this._pdfViewer?._mainCont.current &&
CollectionFreeFormView.UpdateIcon(
@@ -170,8 +177,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
docViewContent,
NumCast(this.layoutDoc._width),
NumCast(this.layoutDoc._height),
- this.props.PanelWidth(),
- this.props.PanelHeight(),
+ this._props.PanelWidth(),
+ this._props.PanelHeight(),
NumCast(this.layoutDoc._layout_scrollTop),
NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], 1),
true,
@@ -190,20 +197,20 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
Object.values(this._disposers).forEach(disposer => disposer?.());
}
componentDidMount() {
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
this._disposers.select = reaction(
- () => this.props.isSelected(),
+ () => this._props.isSelected(),
() => {
document.removeEventListener('keydown', this.onKeyDown);
- this.props.isSelected() && document.addEventListener('keydown', this.onKeyDown);
+ this._props.isSelected() && document.addEventListener('keydown', this.onKeyDown);
},
{ fireImmediately: true }
);
this._disposers.scroll = reaction(
() => this.layoutDoc.layout_scrollTop,
() => {
- if (!(ComputedField.WithoutComputed(() => FieldValue(this.props.Document[this.SidebarKey + '_panY'])) instanceof ComputedField)) {
- this.props.Document[this.SidebarKey + '_panY'] = ComputedField.MakeFunction('this.layout_scrollTop');
+ if (!(ComputedField.WithoutComputed(() => FieldValue(this.Document[this.SidebarKey + '_panY'])) instanceof ComputedField)) {
+ this.Document[this.SidebarKey + '_panY'] = ComputedField.MakeFunction('this.layout_scrollTop');
}
this.layoutDoc[this.SidebarKey + '_freeform_scale'] = 1;
this.layoutDoc[this.SidebarKey + '_freeform_panX'] = 0;
@@ -212,11 +219,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
sidebarAddDocTab = (doc: Doc, where: OpenWhere) => {
- if (DocListCast(this.props.Document[this.props.fieldKey + '_sidebar']).includes(doc) && !this.SidebarShown) {
+ if (DocListCast(this.Document[this._props.fieldKey + '_sidebar']).includes(doc) && !this.SidebarShown) {
this.toggleSidebar(false);
return true;
}
- return this.props.addDocTab(doc, where);
+ return this._props.addDocTab(doc, where);
};
focus = (anchor: Doc, options: DocFocusOptions) => {
this._initialScrollTarget = anchor;
@@ -251,7 +258,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
loaded = (nw: number, nh: number, np: number) => {
- this.dataDoc[this.props.fieldKey + '_numPages'] = np;
+ this.dataDoc[this._props.fieldKey + '_numPages'] = np;
Doc.SetNativeWidth(this.dataDoc, Math.max(Doc.NativeWidth(this.dataDoc), (nw * 96) / 72));
Doc.SetNativeHeight(this.dataDoc, (nh * 96) / 72);
this.layoutDoc._height = NumCast(this.layoutDoc._width) / (Doc.NativeAspect(this.dataDoc) || 1);
@@ -276,7 +283,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return true;
};
public forwardPage = () => {
- this.Document._layout_curPage = Math.min(NumCast(this.dataDoc[this.props.fieldKey + '_numPages']), (NumCast(this.Document._layout_curPage) || 1) + 1);
+ this.Document._layout_curPage = Math.min(NumCast(this.dataDoc[this._props.fieldKey + '_numPages']), (NumCast(this.Document._layout_curPage) || 1) + 1);
return true;
};
public gotoPage = (p: number) => (this.Document._layout_curPage = p);
@@ -300,7 +307,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
setPdfViewer = (pdfViewer: PDFViewer) => {
this._pdfViewer = pdfViewer;
- const docView = this.props.DocumentView?.();
+ const docView = this._props.DocumentView?.();
if (this._initialScrollTarget && docView) {
this.focus(this._initialScrollTarget, { instant: true });
this._initialScrollTarget = undefined;
@@ -321,13 +328,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this,
e,
(e, down, delta) => {
- const localDelta = this.props
+ const localDelta = this._props
.ScreenToLocalTransform()
- .scale(this.props.NativeDimScaling?.() || 1)
+ .scale(this._props.NativeDimScaling?.() || 1)
.transformDirection(delta[0], delta[1]);
const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
- const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.NativeDimScaling?.() || 1)) / nativeWidth;
+ const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this._props.NativeDimScaling?.() || 1)) / nativeWidth;
if (ratio >= 1) {
this.layoutDoc.nativeWidth = nativeWidth * ratio;
onButton && (this.layoutDoc._width = NumCast(this.layoutDoc._width) + localDelta[0]);
@@ -372,12 +379,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
);
const searchTitle = `${!this._searching ? 'Open' : 'Close'} Search Bar`;
const curPage = NumCast(this.Document._layout_curPage) || 1;
- return !this.props.isContentActive() || this._pdfViewer?.isAnnotating ? null : (
+ return !this._props.isContentActive() || this._pdfViewer?.isAnnotating ? null : (
<div
className="pdfBox-ui"
onKeyDown={e => ([KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true)}
onPointerDown={e => e.stopPropagation()}
- style={{ display: this.props.isContentActive() ? 'flex' : 'none' }}>
+ style={{ display: this._props.isContentActive() ? 'flex' : 'none' }}>
<div className="pdfBox-overlayCont" onPointerDown={e => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
<button className="pdfBox-overlayButton" title={searchTitle} />
<input
@@ -431,7 +438,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (!this.SidebarShown) return 0;
if (this._previewWidth) return PDFBox.sidebarResizerWidth + PDFBox.openSidebarWidth; // return default sidebar if previewing (as in viewing a link target)
const nativeDiff = NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc);
- return PDFBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1);
+ return PDFBox.sidebarResizerWidth + nativeDiff * (this._props.NativeDimScaling?.() || 1);
};
@undoBatch
toggleSidebarType = () => (this.dataDoc[this.SidebarKey + '_type_collection'] = this.dataDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform);
@@ -450,11 +457,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
};
@computed get renderTitleBox() {
- const classname = 'pdfBox' + (this.props.isContentActive() ? '-interactive' : '');
+ const classname = 'pdfBox' + (this._props.isContentActive() ? '-interactive' : '');
return (
<div className={classname}>
<div className="pdfBox-title-outer">
- <strong className="pdfBox-title">{StrCast(this.props.Document.title)}</strong>
+ <strong className="pdfBox-title">{StrCast(this.Document.title)}</strong>
</div>
</div>
);
@@ -472,7 +479,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
key="sidebar"
title="Toggle Sidebar"
style={{
- display: !this.props.isContentActive() ? 'none' : undefined,
+ display: !this._props.isContentActive() ? 'none' : undefined,
top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 5,
backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
}}
@@ -489,26 +496,26 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const pdfNativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
const nativeWidth = NumCast(this.layoutDoc.nativeWidth, pdfNativeWidth);
const pdfRatio = pdfNativeWidth / nativeWidth;
- return (pdfRatio * this.props.PanelWidth()) / pdfNativeWidth;
+ return (pdfRatio * this._props.PanelWidth()) / pdfNativeWidth;
}
@computed get sidebarNativeWidth() {
return this.sidebarWidth() / this.pdfScale;
}
@computed get sidebarNativeHeight() {
- return this.props.PanelHeight() / this.pdfScale;
+ return this._props.PanelHeight() / this.pdfScale;
}
sidebarNativeWidthFunc = () => this.sidebarNativeWidth;
sidebarNativeHeightFunc = () => this.sidebarNativeHeight;
sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey);
- sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate((this.sidebarWidth() - this.props.PanelWidth()) / this.pdfScale, 0);
+ sidebarScreenToLocal = () => this._props.ScreenToLocalTransform().translate((this.sidebarWidth() - this._props.PanelWidth()) / this.pdfScale, 0);
@computed get sidebarCollection() {
const renderComponent = (tag: string) => {
const ComponentTag = tag === CollectionViewType.Freeform ? CollectionFreeFormView : CollectionStackingView;
return ComponentTag === CollectionStackingView ? (
<SidebarAnnos
ref={this._sidebarRef}
- {...this.props}
+ {...this._props}
Document={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
@@ -521,13 +528,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
removeDocument={this.removeDocument}
/>
) : (
- <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
+ <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this._props.DocumentView?.()!, false), true)}>
<ComponentTag
- {...this.props}
+ {...this._props}
setContentView={emptyFunction} // override setContentView to do nothing
NativeWidth={this.sidebarNativeWidthFunc}
NativeHeight={this.sidebarNativeHeightFunc}
- PanelHeight={this.props.PanelHeight}
+ PanelHeight={this._props.PanelHeight}
PanelWidth={this.sidebarWidth}
xPadding={0}
yPadding={0}
@@ -541,7 +548,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
moveDocument={this.sidebarMoveDocument}
addDocument={this.sidebarAddDocument}
ScreenToLocalTransform={this.sidebarScreenToLocal}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
noSidebar={true}
fieldKey={this.SidebarKey}
/>
@@ -557,13 +564,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@computed get renderPdfView() {
TraceMobx();
const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
- const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
+ const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
return !this._pdf ? null : (
<div
className="pdfBox"
onContextMenu={this.specificContextMenu}
style={{
- height: this.props.Document._layout_scrollTop && !this.Document._layout_fitWidth && window.screen.width > 600 ? (NumCast(this.Document._height) * this.props.PanelWidth()) / NumCast(this.Document._width) : undefined,
+ height: this.Document._layout_scrollTop && !this.Document._layout_fitWidth && window.screen.width > 600 ? (NumCast(this.Document._height) * this._props.PanelWidth()) / NumCast(this.Document._width) : undefined,
}}>
<div className="pdfBox-background" onPointerDown={e => this.sidebarBtnDown(e, false)} />
<div
@@ -576,7 +583,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
top: 0,
}}>
<PDFViewer
- {...this.props}
+ {...this._props}
sidebarAddDoc={this.sidebarAddDocument}
addDocTab={this.sidebarAddDocTab}
layoutDoc={this.layoutDoc}
@@ -594,7 +601,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
crop={this.crop}
/>
</div>
- <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this.props.PanelWidth()}%` }}>{this.sidebarCollection}</div>
+ <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this._props.PanelWidth()}%` }}>{this.sidebarCollection}</div>
{this.settingsPanel()}
</div>
);
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 7c21b1893..a7ff8ff8f 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -43,7 +43,7 @@ declare class MediaRecorder {
// @observer
// export class VideoTile extends React.Component<VideoTileProps> {
-// @observable _videoRef: HTMLVideoElement | undefined;
+// @observable _videoRef: HTMLVideoElement | undefined = undefined;
// _mesh: any = undefined;
// render() {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 04780155d..2dad115e1 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, untracked } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, override, reaction, runInAction, untracked } from 'mobx';
import { observer } from 'mobx-react';
import { basename } from 'path';
import { Doc, StrListCast } from '../../../fields/Doc';
@@ -9,7 +9,7 @@ import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { AudioField, ImageField, VideoField } from '../../../fields/URLField';
-import { emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
+import { copyProps, emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { Networking } from '../../Network';
@@ -33,7 +33,6 @@ import { FieldView, FieldViewProps } from './FieldView';
import { RecordingBox } from './RecordingBox';
import { PinProps, PresBox } from './trails';
import './VideoBox.scss';
-const path = require('path');
/**
* VideoBox
@@ -52,7 +51,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(VideoBox, fieldKey);
}
-
static _youtubeIframeCounter: number = 0;
static heightPercent = 80; // height of video relative to videoBox when timeline is open
static numThumbnails = 20;
@@ -70,6 +68,19 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
private _playRegionTimer: any = null; // timeout for playback
private _controlsFadeTimer: any = null; // timeout for controls fade
+ _prevProps: ViewBoxAnnotatableProps & FieldViewProps;
+ @override _props: ViewBoxAnnotatableProps & FieldViewProps;
+ constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ this._props.setContentView?.(this);
+ }
+
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
@observable _stackedTimeline: any; // CollectionStackedTimeline ref
@observable static _nativeControls: boolean; // default html controls
@observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@@ -96,13 +107,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@observable rawDuration: number = 0;
@computed get youtubeVideoId() {
- const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
+ const field = Cast(this.dataDoc[this._props.fieldKey], VideoField);
return field && field.url.href.indexOf('youtube') !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split('/')) : '';
}
// returns the path of the audio file
@computed get audiopath() {
- const field = Cast(this.props.Document[this.props.fieldKey + '_audio'], AudioField, null);
+ const field = Cast(this.Document[this._props.fieldKey + '_audio'], AudioField, null);
const vfield = Cast(this.dataDoc[this.fieldKey], VideoField, null);
return field?.url.href ?? vfield?.url.href ?? '';
}
@@ -125,7 +136,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
componentDidMount() {
this.unmounting = false;
- this.props.setContentView?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link.
+ this._props.setContentView?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link.
if (this.youtubeVideoId) {
const youtubeaspect = 400 / 315;
const nativeWidth = Doc.NativeWidth(this.layoutDoc);
@@ -162,7 +173,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
if (
// need to include range inputs because after dragging time slider it becomes target element
!(e.target instanceof HTMLInputElement && !(e.target.type === 'range')) &&
- this.props.isSelected()
+ this._props.isSelected()
) {
switch (e.key) {
case 'ArrowLeft':
@@ -267,7 +278,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this.player && this._contentRef && this._contentRef.requestFullscreen();
}
try {
- this._youtubePlayer && this.props.addDocTab(this.Document, OpenWhere.add);
+ this._youtubePlayer && this._props.addDocTab(this.Document, OpenWhere.add);
} catch (e) {
console.log('Video FullScreen Exception:', e);
}
@@ -328,7 +339,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
title: (this.layoutDoc._layout_currentTimecode || 0).toString(),
onClick: FollowLinkScript(),
});
- this.props.addDocument?.(b);
+ this._props.addDocument?.(b);
DocUtils.MakeLink(b, this.Document, { link_relationship: 'video snapshot' });
Networking.PostToServer('/youtubeScreenshot', {
id: this.youtubeVideoId,
@@ -336,7 +347,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}).then(response => {
const resolved = response?.accessPaths?.agnostic?.client;
if (resolved) {
- this.props.removeDocument?.(b);
+ this._props.removeDocument?.(b);
this.createSnapshotLink(resolved);
}
});
@@ -377,7 +388,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
});
Doc.SetNativeWidth(Doc.GetProto(imageSnapshot), Doc.NativeWidth(this.layoutDoc));
Doc.SetNativeHeight(Doc.GetProto(imageSnapshot), Doc.NativeHeight(this.layoutDoc));
- this.props.addDocument?.(imageSnapshot);
+ this._props.addDocument?.(imageSnapshot);
const link = DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
link && (Doc.GetProto(link.link_anchor_2 as Doc).timecodeToHide = NumCast((link.link_anchor_2 as Doc).timecodeToShow) + 3);
setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, 'move', true));
@@ -492,7 +503,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// context menu
specificContextMenu = (e: React.MouseEvent): void => {
- const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
+ const field = Cast(this.dataDoc[this._props.fieldKey], VideoField);
if (field) {
const url = field.url.href;
const subitems: ContextMenuProps[] = [];
@@ -532,7 +543,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
event: () => {
this.dataDoc.layout = RecordingBox.LayoutString(this.fieldKey);
// delete assoicated video data
- this.dataDoc[this.props.fieldKey] = '';
+ this.dataDoc[this._props.fieldKey] = '';
this.dataDoc[this.fieldKey + '_duration'] = '';
// delete assoicated presentation data
this.dataDoc[this.fieldKey + '_presentation'] = '';
@@ -550,7 +561,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// renders the video and audio
@computed get content() {
const field = Cast(this.dataDoc[this.fieldKey], VideoField);
- const interactive = Doc.ActiveTool !== InkTool.None || !this.props.isSelected() ? '' : '-interactive';
+ const interactive = Doc.ActiveTool !== InkTool.None || !this._props.isSelected() ? '' : '-interactive';
const classname = 'videoBox-content' + (this._fullScreen ? '-fullScreen' : '') + interactive;
const opacity = this._scrubbing ? 0.3 : this._controlsVisible ? 1 : 0;
return !field ? (
@@ -586,7 +597,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
Not supported.
</video>
{!this.audiopath || this.audiopath === field.url.href ? null : (
- <audio ref={this.setAudioRef} className={`audiobox-control${this.props.isContentActive() ? '-interactive' : ''}`}>
+ <audio ref={this.setAudioRef} className={`audiobox-control${this._props.isContentActive() ? '-interactive' : ''}`}>
<source src={this.audiopath} type="audio/mpeg" />
Not supported.
</audio>
@@ -626,7 +637,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
() => !this._playing && this.Seek(NumCast(this.layoutDoc._layout_currentTimecode))
);
this._disposers.youtubeReactionDisposer = reaction(
- () => Doc.ActiveTool === InkTool.None && this.props.isSelected() && !SnappingManager.GetIsDragging() && !DocumentView.Interacting,
+ () => Doc.ActiveTool === InkTool.None && this._props.isSelected() && !SnappingManager.GetIsDragging() && !DocumentView.Interacting,
interactive => (iframe.style.pointerEvents = interactive ? 'all' : 'none'),
{ fireImmediately: true }
);
@@ -636,8 +647,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
(YT as any)?.ready(() => {
this._youtubePlayer = new YT.Player(`${this.youtubeVideoId + this._youtubeIframeId}-player`, {
events: {
- onReady: this.props.dontRegisterView ? undefined : onYoutubePlayerReady,
- onStateChange: this.props.dontRegisterView ? undefined : onYoutubePlayerStateChange,
+ onReady: this._props.dontRegisterView ? undefined : onYoutubePlayerReady,
+ onStateChange: this._props.dontRegisterView ? undefined : onYoutubePlayerStateChange,
},
});
});
@@ -677,9 +688,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
e,
action(encodeURIComponent => {
this._clicking = false;
- if (this.props.isContentActive()) {
- // const local = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
- // this.layoutDoc._layout_timelineHeightPercent = Math.max(0, Math.min(100, local[1] / this.props.PanelHeight() * 100));
+ if (this._props.isContentActive()) {
+ // const local = this._props.ScreenToLocalTransform().scale(this._props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
+ // this.layoutDoc._layout_timelineHeightPercent = Math.max(0, Math.min(100, local[1] / this._props.PanelHeight() * 100));
this.layoutDoc._layout_timelineHeightPercent = 80;
}
@@ -693,15 +704,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
500
);
},
- this.props.isContentActive(),
- this.props.isContentActive()
+ this._props.isContentActive(),
+ this._props.isContentActive()
);
};
// removes from currently playing display
@action
removeCurrentlyPlaying = () => {
- const docView = this.props.DocumentView?.();
+ const docView = this._props.DocumentView?.();
if (CollectionStackedTimeline.CurrentlyPlaying && docView) {
const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView);
index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
@@ -710,7 +721,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// adds doc to currently playing display
@action
addCurrentlyPlaying = () => {
- const docView = this.props.DocumentView?.();
+ const docView = this._props.DocumentView?.();
if (!CollectionStackedTimeline.CurrentlyPlaying) {
CollectionStackedTimeline.CurrentlyPlaying = [];
}
@@ -861,7 +872,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// starts marquee selection
marqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) {
+ if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) === 1 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) {
setupMoveUpEvents(
this,
e,
@@ -882,31 +893,31 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@action
finishMarquee = () => {
this._marqueeref.current?.onTerminateSelection();
- this.props.select(true);
+ this._props.select(true);
};
- timelineWhenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
+ timelineWhenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
timelineScreenToLocal = () =>
- this.props
+ this._props
.ScreenToLocalTransform()
.scale(this.scaling())
- .translate(0, (-this.heightPercent / 100) * this.props.PanelHeight());
+ .translate(0, (-this.heightPercent / 100) * this._props.PanelHeight());
setPlayheadTime = (time: number) => (this.player!.currentTime = this.layoutDoc._layout_currentTimecode = time);
- timelineHeight = () => (this.props.PanelHeight() * (100 - this.heightPercent)) / 100;
+ timelineHeight = () => (this._props.PanelHeight() * (100 - this.heightPercent)) / 100;
playing = () => this._playing;
- scaling = () => this.props.NativeDimScaling?.() || 1;
+ scaling = () => this._props.NativeDimScaling?.() || 1;
- panelWidth = () => (this.props.PanelWidth() * this.heightPercent) / 100;
- panelHeight = () => (this.layoutDoc._layout_fitWidth ? this.panelWidth() / (Doc.NativeAspect(this.dataDoc) || 1) : (this.props.PanelHeight() * this.heightPercent) / 100);
+ panelWidth = () => (this._props.PanelWidth() * this.heightPercent) / 100;
+ panelHeight = () => (this.layoutDoc._layout_fitWidth ? this.panelWidth() / (Doc.NativeAspect(this.dataDoc) || 1) : (this._props.PanelHeight() * this.heightPercent) / 100);
screenToLocalTransform = () => {
- const offset = (this.props.PanelWidth() - this.panelWidth()) / 2 / this.scaling();
- return this.props
+ const offset = (this._props.PanelWidth() - this.panelWidth()) / 2 / this.scaling();
+ return this._props
.ScreenToLocalTransform()
.translate(-offset, 0)
.scale(100 / this.heightPercent);
@@ -918,10 +929,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// renders video controls
componentUI = (boundsLeft: number, boundsTop: number) => {
- const xf = this.props.ScreenToLocalTransform().inverse();
- const height = this.props.PanelHeight();
+ const xf = this._props.ScreenToLocalTransform().inverse();
+ const height = this._props.PanelHeight();
const vidHeight = (height * this.heightPercent) / 100 / this.scaling();
- const vidWidth = this.props.PanelWidth() / this.scaling();
+ const vidWidth = this._props.PanelWidth() / this.scaling();
const uiHeight = 25;
const uiMargin = 10;
const yBot = xf.transformPoint(0, vidHeight)[1];
@@ -936,7 +947,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
<div
className="videoBox-ui"
style={{
- transform: `rotate(${this.props.ScreenToLocalTransform().inverse().RotateDeg}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
+ transform: `rotate(${this._props.ScreenToLocalTransform().inverse().RotateDeg}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
left: xPos,
top: yMid,
height: uiHeight,
@@ -957,13 +968,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
<div className="videoBox-stackPanel" style={{ transition: this.transition, height: `${100 - this.heightPercent}%`, display: this.heightPercent === 100 ? 'none' : '' }}>
<CollectionStackedTimeline
ref={action((r: any) => (this._stackedTimeline = r))}
- {...this.props}
+ {...this._props}
dataFieldKey={this.fieldKey}
fieldKey={this.annotationKey}
dictationKey={this.fieldKey + '_dictation'}
mediaPath={this.audiopath}
thumbnails={this.thumbnails}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
startTag={'_timecodeToShow' /* videoStart */}
endTag={'_timecodeToHide' /* videoEnd */}
bringToFront={emptyFunction}
@@ -1011,8 +1022,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
cropping.title = 'crop: ' + this.Document.title;
cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
cropping.y = NumCast(this.Document.y);
- cropping._width = anchw * (this.props.NativeDimScaling?.() || 1);
- cropping._height = anchh * (this.props.NativeDimScaling?.() || 1);
+ cropping._width = anchw * (this._props.NativeDimScaling?.() || 1);
+ cropping._height = anchh * (this._props.NativeDimScaling?.() || 1);
cropping.timecodeToHide = undefined;
cropping.timecodeToShow = undefined;
cropping.onClick = undefined;
@@ -1038,12 +1049,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
if (addCrop) {
DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
}
- this.props.bringToFront(cropping);
+ this._props.bringToFront(cropping);
return cropping;
};
savedAnnotations = () => this._savedAnnotations;
render() {
- const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
+ const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding);
const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / this.scaling()}px` : borderRad;
return (
<div
@@ -1053,7 +1064,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
style={{
pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
borderRadius,
- overflow: this.props.docViewPath?.().slice(-1)[0].layout_fitWidth ? 'auto' : undefined,
+ overflow: this._props.docViewPath?.().slice(-1)[0].layout_fitWidth ? 'auto' : undefined,
}}>
<div className="videoBox-viewer" onPointerDown={this.marqueeDown}>
<div
@@ -1063,19 +1074,19 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
width: this.panelWidth(),
height: this.panelHeight(),
top: 0,
- left: (this.props.PanelWidth() - this.panelWidth()) / 2,
+ left: (this._props.PanelWidth() - this.panelWidth()) / 2,
}}>
<CollectionFreeFormView
- {...this.props}
+ {...this._props}
setContentView={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
fieldKey={this.annotationKey}
isAnnotationOverlay={true}
annotationLayerHostsContent={true}
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.props.PanelHeight}
+ PanelWidth={this._props.PanelWidth}
+ PanelHeight={this._props.PanelHeight}
isAnyChildContentActive={returnFalse}
ScreenToLocalTransform={this.screenToLocalTransform}
childFilters={this.timelineDocFilter}
@@ -1097,8 +1108,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
scrollTop={0}
annotationLayerScrollTop={0}
scaling={returnOne}
- annotationLayerScaling={this.props.NativeDimScaling}
- docView={this.props.DocumentView!}
+ annotationLayerScaling={this._props.NativeDimScaling}
+ docView={this._props.DocumentView!}
containerOffset={this.marqueeOffset}
addDocument={this.addDocWithTimecode}
finishMarquee={this.finishMarquee}
@@ -1116,7 +1127,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
@computed get UIButtons() {
- const bounds = this.props.docViewPath().lastElement().getBounds();
+ const bounds = this._props.docViewPath().lastElement().getBounds();
const width = (bounds?.right || 0) - (bounds?.left || 0);
const curTime = NumCast(this.layoutDoc._layout_currentTimecode);
return (
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index d02e5f3e8..c722399c1 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -67,7 +67,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@observable private _searching: boolean = false;
@observable private _showSidebar = false;
@observable private _webPageHasBeenRendered = false;
- @observable private _marqueeing: number[] | undefined;
+ @observable private _marqueeing: number[] | undefined = undefined;
get marqueeing() {
return this._marqueeing;
}
@@ -403,7 +403,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const locpt = {
x: (e.clientX / NumCast(this.Document.nativeWidth)) * this.props.PanelWidth(),
y: ((e.clientY - NumCast(this.layoutDoc.layout_scrollTop))/ NumCast(this.Document.nativeHeight)) * this.props.PanelHeight() }; // prettier-ignore
- const scrclick = this.props.DocumentView?.().props.ScreenToLocalTransform().inverse().transformPoint(locpt.x, locpt.y)!;
+ const scrclick = this.props.DocumentView?.()._props.ScreenToLocalTransform().inverse().transformPoint(locpt.x, locpt.y)!;
const scrcent = this.props
.DocumentView?.()
.props.ScreenToLocalTransform()
diff --git a/src/client/views/AudioWaveform.scss b/src/client/views/nodes/audio/AudioWaveform.scss
index 6cbd1759a..6cbd1759a 100644
--- a/src/client/views/AudioWaveform.scss
+++ b/src/client/views/nodes/audio/AudioWaveform.scss
diff --git a/src/client/views/nodes/audio/AudioWaveform.tsx b/src/client/views/nodes/audio/AudioWaveform.tsx
new file mode 100644
index 000000000..1b1a85800
--- /dev/null
+++ b/src/client/views/nodes/audio/AudioWaveform.tsx
@@ -0,0 +1,127 @@
+import axios from 'axios';
+import { computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, NumListCast } from '../../../../fields/Doc';
+import { List } from '../../../../fields/List';
+import { listSpec } from '../../../../fields/Schema';
+import { Cast } from '../../../../fields/Types';
+import { copyProps, numberRange } from '../../../../Utils';
+import { Colors } from './../../global/globalEnums';
+import './AudioWaveform.scss';
+import { WaveCanvas } from './WaveCanvas';
+
+/**
+ * AudioWaveform
+ *
+ * Used in CollectionStackedTimeline to render a canvas with a visual of an audio waveform for AudioBox and VideoBox documents.
+ * Uses react-audio-waveform package.
+ * Bins the audio data into audioBuckets which are passed to package to render the lines.
+ * Calculates new buckets each time a new zoom factor or new set of trim bounds is created and stores it in a field on the layout doc with a title indicating the bounds and zoom for that list (see audioBucketField)
+ */
+
+export interface AudioWaveformProps {
+ duration: number; // length of media clip
+ rawDuration: number; // length of underlying media data
+ mediaPath: string;
+ layoutDoc: Doc;
+ clipStart: number;
+ clipEnd: number;
+ zoomFactor: number;
+ PanelHeight: number;
+ PanelWidth: number;
+ fieldKey: string;
+ progress?: number;
+}
+
+@observer
+export class AudioWaveform extends React.Component<AudioWaveformProps> {
+ public static NUMBER_OF_BUCKETS = 100; // number of buckets data is divided into to draw waveform lines
+ _disposer: IReactionDisposer | undefined;
+ _prevProps: React.PropsWithChildren<AudioWaveformProps>;
+ @observable _props: React.PropsWithChildren<AudioWaveformProps>;
+ constructor(props: React.PropsWithChildren<AudioWaveformProps>) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
+ get waveHeight() {
+ return Math.max(50, this._props.PanelHeight);
+ }
+
+ get clipStart() {
+ return this._props.clipStart;
+ }
+ get clipEnd() {
+ return this._props.clipEnd;
+ }
+ get zoomFactor() {
+ return this._props.zoomFactor;
+ }
+
+ @computed get audioBuckets() {
+ return NumListCast(this._props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)]);
+ }
+
+ audioBucketField = (start: number, end: number, zoomFactor: number) => this._props.fieldKey + '_audioBuckets/' + '/' + start.toFixed(2).replace('.', '_') + '/' + end.toFixed(2).replace('.', '_') + '/' + zoomFactor * 10;
+
+ componentWillUnmount() {
+ this._disposer?.();
+ }
+
+ componentDidMount() {
+ this._disposer = reaction(
+ () => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor), zoomFactor: this._props.zoomFactor }),
+ ({ clipStart, clipEnd, fieldKey, zoomFactor }) => {
+ if (!this._props.layoutDoc[fieldKey] && this._props.layoutDoc.layout_fieldKey != 'layout_icon') {
+ // setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time.
+ const waveform = Cast(this._props.layoutDoc[this.audioBucketField(0, this._props.rawDuration, 1)], listSpec('number'));
+ this._props.layoutDoc[fieldKey] = waveform && new List<number>(waveform.slice((clipStart / this._props.rawDuration) * waveform.length, (clipEnd / this._props.rawDuration) * waveform.length));
+ setTimeout(() => this.createWaveformBuckets(fieldKey, clipStart, clipEnd, zoomFactor));
+ }
+ },
+ { fireImmediately: true }
+ );
+ }
+
+ // decodes the audio file into peaks for generating the waveform
+ createWaveformBuckets = (fieldKey: string, clipStart: number, clipEnd: number, zoomFactor: number) => {
+ axios({ url: this._props.mediaPath, responseType: 'arraybuffer' }).then(response =>
+ new window.AudioContext().decodeAudioData(response.data, buffer => {
+ const rawDecodedAudioData = buffer.getChannelData(0);
+ const startInd = clipStart / this._props.rawDuration;
+ const endInd = clipEnd / this._props.rawDuration;
+ const decodedAudioData = rawDecodedAudioData.slice(Math.floor(startInd * rawDecodedAudioData.length), Math.floor(endInd * rawDecodedAudioData.length));
+ const numBuckets = Math.floor(AudioWaveform.NUMBER_OF_BUCKETS * zoomFactor);
+
+ const bucketDataSize = Math.floor(decodedAudioData.length / numBuckets);
+ const brange = Array.from(Array(bucketDataSize));
+ const bucketList = numberRange(numBuckets).map((i: number) => brange.reduce((p, x, j) => Math.abs(Math.max(p, decodedAudioData[i * bucketDataSize + j])), 0) / 2);
+ this._props.layoutDoc[fieldKey] = new List<number>(bucketList);
+ })
+ );
+ };
+
+ render() {
+ return (
+ <div className="audioWaveform">
+ <WaveCanvas
+ color={Colors.LIGHT_GRAY}
+ progressColor={Colors.MEDIUM_BLUE_ALT}
+ progress={this._props.progress ?? 1}
+ barWidth={200 / this.audioBuckets.length}
+ //gradientColors={this.props.gradientColors}
+ peaks={this.audioBuckets}
+ width={(this._props.PanelWidth ?? 0) * window.devicePixelRatio}
+ height={this.waveHeight * window.devicePixelRatio}
+ pixelRatio={window.devicePixelRatio}
+ />
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/nodes/audio/WaveCanvas.tsx b/src/client/views/nodes/audio/WaveCanvas.tsx
new file mode 100644
index 000000000..d3f5669a2
--- /dev/null
+++ b/src/client/views/nodes/audio/WaveCanvas.tsx
@@ -0,0 +1,100 @@
+import React from 'react';
+
+interface WaveCanvasProps {
+ barWidth: number;
+ color: string;
+ progress: number;
+ progressColor: string;
+ gradientColors?: { stopPosition: number; color: string }[]; // stopPosition between 0 and 1
+ peaks: number[];
+ width: number;
+ height: number;
+ pixelRatio: number;
+}
+
+export class WaveCanvas extends React.Component<WaveCanvasProps> {
+ // If the first value of peaks is negative, addToIndices will be 1
+ posPeaks = (peaks: number[], addToIndices: number) => peaks.filter((_, index) => (index + addToIndices) % 2 == 0);
+
+ drawBars = (waveCanvasCtx: CanvasRenderingContext2D, width: number, halfH: number, peaks: number[]) => {
+ // Bar wave draws the bottom only as a reflection of the top,
+ // so we don't need negative values
+ const posPeaks = peaks.some(val => val < 0) ? this.posPeaks(peaks, peaks[0] < 0 ? 1 : 0) : peaks;
+
+ // A half-pixel offset makes lines crisp
+ const $ = 0.5 / this.props.pixelRatio;
+ const bar = this.props.barWidth * this.props.pixelRatio;
+ const gap = Math.max(this.props.pixelRatio, 2);
+
+ const max = Math.max(...posPeaks);
+ const scale = posPeaks.length / width;
+
+ for (let i = 0; i < width; i += bar + gap) {
+ if (i > width * this.props.progress) waveCanvasCtx.fillStyle = this.props.color;
+
+ const h = Math.round((posPeaks[Math.floor(i * scale)] / max) * halfH) || 1;
+
+ waveCanvasCtx.fillRect(i + $, halfH - h, bar + $, h * 2);
+ }
+ };
+
+ addNegPeaks = (peaks: number[]) =>
+ peaks.reduce((reflectedPeaks, peak) => reflectedPeaks.push(peak, -peak) ? reflectedPeaks:[],
+ [] as number[]); // prettier-ignore
+
+ drawWaves = (waveCanvasCtx: CanvasRenderingContext2D, width: number, halfH: number, peaks: number[]) => {
+ const allPeaks = peaks.some(val => val < 0) ? peaks : this.addNegPeaks(peaks); // add negative peaks to arrays without negative peaks
+
+ // A half-pixel offset makes lines crisp
+ const $ = 0.5 / this.props.pixelRatio;
+ const length = ~~(allPeaks.length / 2); // ~~ is Math.floor for positive numbers.
+
+ const scale = width / length;
+ const absmax = Math.max(...allPeaks.map(peak => Math.abs(peak)));
+
+ waveCanvasCtx.beginPath();
+ waveCanvasCtx.moveTo($, halfH);
+
+ for (var i = 0; i < length; i++) {
+ var h = Math.round((allPeaks[2 * i] / absmax) * halfH);
+ waveCanvasCtx.lineTo(i * scale + $, halfH - h);
+ }
+
+ // Draw the bottom edge going backwards, to make a single closed hull to fill.
+ for (var i = length - 1; i >= 0; i--) {
+ var h = Math.round((allPeaks[2 * i + 1] / absmax) * halfH);
+ waveCanvasCtx.lineTo(i * scale + $, halfH - h);
+ }
+
+ waveCanvasCtx.fill();
+
+ // Always draw a median line
+ waveCanvasCtx.fillRect(0, halfH - $, width, $);
+ };
+
+ updateSize = (width: number, height: number, peaks: number[], waveCanvasCtx: CanvasRenderingContext2D) => {
+ const displayWidth = Math.round(width / this.props.pixelRatio);
+ const displayHeight = Math.round(height / this.props.pixelRatio);
+ waveCanvasCtx.canvas.width = width;
+ waveCanvasCtx.canvas.height = height;
+ waveCanvasCtx.canvas.style.width = `${displayWidth}px`;
+ waveCanvasCtx.canvas.style.height = `${displayHeight}px`;
+
+ waveCanvasCtx.clearRect(0, 0, width, height);
+
+ const gradient = this.props.gradientColors && waveCanvasCtx.createLinearGradient(0, 0, width, 0);
+ gradient && this.props.gradientColors?.forEach(color => gradient.addColorStop(color.stopPosition, color.color));
+ waveCanvasCtx.fillStyle = gradient ?? this.props.progressColor;
+
+ const waveDrawer = this.props.barWidth ? this.drawBars : this.drawWaves;
+ waveDrawer(waveCanvasCtx, width, height / 2, peaks);
+ };
+
+ render() {
+ return this.props.peaks ? (
+ <div style={{ position: 'relative', width: '100%', height: '100%', cursor: 'pointer' }}>
+ <canvas ref={instance => (ctx => ctx && this.updateSize(this.props.width, this.props.height, this.props.peaks, ctx))(instance?.getContext('2d'))} />
+ </div>
+ ) : null;
+ }
+}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 4384c8958..6332b200d 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -82,7 +82,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
_spanRef = React.createRef<HTMLDivElement>();
_disposers: { [name: string]: IReactionDisposer } = {};
_textBox: FormattedTextBox;
- @observable _dashDoc: Doc | undefined;
+ @observable _dashDoc: Doc | undefined = undefined;
@observable _finalLayout: any;
@observable _width: number = 0;
@observable _height: number = 0;
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index a395296d0..19e14d5a7 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -25,7 +25,7 @@ export class DashFieldView {
node: any;
tbox: FormattedTextBox;
- unclickable = () => !this.tbox.props.isSelected() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
+ unclickable = () => !this.tbox._props.isSelected() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
this.node = node;
this.tbox = tbox;
@@ -97,13 +97,13 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
_textBoxDoc: Doc;
_fieldKey: string;
_fieldStringRef = React.createRef<HTMLSpanElement>();
- @observable _dashDoc: Doc | undefined;
+ @observable _dashDoc: Doc | undefined = undefined;
@observable _expanded = false;
constructor(props: IDashFieldViewInternal) {
super(props);
this._fieldKey = this.props.fieldKey;
- this._textBoxDoc = this.props.tbox.props.Document;
+ this._textBoxDoc = this.props.tbox.Document;
if (this.props.docId) {
DocServer.GetRefField(this.props.docId).then(action(dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
@@ -126,7 +126,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
deselectCell={emptyFunction}
selectCell={emptyFunction}
maxWidth={this.props.hideKey ? undefined : this.return100}
- columnWidth={this.props.hideKey ? () => this.props.tbox.props.PanelWidth() - 20 : returnZero}
+ columnWidth={this.props.hideKey ? () => this.props.tbox._props.PanelWidth() - 20 : returnZero}
selectedCell={() => [this._dashDoc!, 0]}
fieldKey={this._fieldKey}
rowHeight={returnZero}
@@ -145,7 +145,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
}
createPivotForField = (e: React.MouseEvent) => {
- let container = this.props.tbox.props.DocumentView?.().props.docViewPath().lastElement();
+ let container = this.props.tbox._props.DocumentView?.()._props.docViewPath().lastElement();
if (container) {
const embedding = Doc.MakeEmbedding(container.Document);
embedding._type_collection = CollectionViewType.Time;
@@ -157,7 +157,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb'));
list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb'));
embedding._pivotField = this._fieldKey.startsWith('#') ? 'tags' : this._fieldKey;
- this.props.tbox.props.addDocTab(embedding, OpenWhere.addRight);
+ this.props.tbox._props.addDocTab(embedding, OpenWhere.addRight);
}
};
@@ -177,7 +177,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
style={{
width: this.props.width,
height: this.props.height,
- pointerEvents: this.props.tbox.props.isSelected() || this.props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
+ pointerEvents: this.props.tbox._props.isSelected() || this.props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
}}>
{this.props.hideKey ? null : (
<span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
diff --git a/src/client/views/nodes/formattedText/EquationEditor.scss b/src/client/views/nodes/formattedText/EquationEditor.scss
new file mode 100644
index 000000000..b0c17e56e
--- /dev/null
+++ b/src/client/views/nodes/formattedText/EquationEditor.scss
@@ -0,0 +1,468 @@
+// using this import, we get runtime errors when trying to load the specified font-faces
+// so we copy the .css and remove the @font-face imports
+
+// @import 'mathquill/build/mathquill.css'
+/*
+ * MathQuill v0.10.1 http://mathquill.com
+ * by Han, Jeanine, and Mary maintainers@mathquill.com
+ *
+ * This Source Code Form is subject to the terms of the
+ * Mozilla Public License, v. 2.0. If a copy of the MPL
+ * was not distributed with this file, You can obtain
+ * one at http://mozilla.org/MPL/2.0/.
+ */
+// @font-face {
+// font-family: Symbola;
+// src: url(font/Symbola.eot);
+// src:
+// local('Symbola Regular'),
+// local('Symbola'),
+// url(font/Symbola.woff2) format('woff2'),
+// url(font/Symbola.woff) format('woff'),
+// url(font/Symbola.ttf) format('truetype'),
+// url(font/Symbola.otf) format('opentype'),
+// url(font/Symbola.svg#Symbola) format('svg');
+// }
+.mq-editable-field {
+ display: -moz-inline-box;
+ display: inline-block;
+}
+.mq-editable-field .mq-cursor {
+ border-left: 1px solid black;
+ margin-left: -1px;
+ position: relative;
+ z-index: 1;
+ padding: 0;
+ display: -moz-inline-box;
+ display: inline-block;
+}
+.mq-editable-field .mq-cursor.mq-blink {
+ visibility: hidden;
+}
+.mq-editable-field,
+.mq-math-mode .mq-editable-field {
+ border: 1px solid gray;
+}
+.mq-editable-field.mq-focused,
+.mq-math-mode .mq-editable-field.mq-focused {
+ -webkit-box-shadow:
+ #8bd 0 0 1px 2px,
+ inset #6ae 0 0 2px 0;
+ -moz-box-shadow:
+ #8bd 0 0 1px 2px,
+ inset #6ae 0 0 2px 0;
+ box-shadow:
+ #8bd 0 0 1px 2px,
+ inset #6ae 0 0 2px 0;
+ border-color: #709ac0;
+ border-radius: 1px;
+}
+.mq-math-mode .mq-editable-field {
+ margin: 1px;
+}
+.mq-editable-field .mq-latex-command-input {
+ color: inherit;
+ font-family: 'Courier New', monospace;
+ border: 1px solid gray;
+ padding-right: 1px;
+ margin-right: 1px;
+ margin-left: 2px;
+}
+.mq-editable-field .mq-latex-command-input.mq-empty {
+ background: transparent;
+}
+.mq-editable-field .mq-latex-command-input.mq-hasCursor {
+ border-color: ActiveBorder;
+}
+.mq-editable-field.mq-empty:after,
+.mq-editable-field.mq-text-mode:after,
+.mq-math-mode .mq-empty:after {
+ visibility: hidden;
+ content: 'c';
+}
+.mq-editable-field .mq-cursor:only-child:after,
+.mq-editable-field .mq-textarea + .mq-cursor:last-child:after {
+ visibility: hidden;
+ content: 'c';
+}
+.mq-editable-field .mq-text-mode .mq-cursor:only-child:after {
+ content: '';
+}
+.mq-editable-field.mq-text-mode {
+ overflow-x: auto;
+ overflow-y: hidden;
+}
+.mq-root-block,
+.mq-math-mode .mq-root-block {
+ display: -moz-inline-box;
+ display: inline-block;
+ width: 100%;
+ padding: 2px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ white-space: nowrap;
+ overflow: hidden;
+ vertical-align: middle;
+}
+.mq-math-mode {
+ font-variant: normal;
+ font-weight: normal;
+ font-style: normal;
+ font-size: 115%;
+ line-height: 1;
+ display: -moz-inline-box;
+ display: inline-block;
+}
+.mq-math-mode .mq-non-leaf,
+.mq-math-mode .mq-scaled {
+ display: -moz-inline-box;
+ display: inline-block;
+}
+.mq-math-mode var,
+.mq-math-mode .mq-text-mode,
+.mq-math-mode .mq-nonSymbola {
+ font-family: 'Times New Roman', Symbola, serif;
+ line-height: 0.9;
+}
+.mq-math-mode * {
+ font-size: inherit;
+ line-height: inherit;
+ margin: 0;
+ padding: 0;
+ border-color: black;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ box-sizing: border-box;
+}
+.mq-math-mode .mq-empty {
+ background: #ccc;
+}
+.mq-math-mode .mq-empty.mq-root-block {
+ background: transparent;
+}
+.mq-math-mode.mq-empty {
+ background: transparent;
+}
+.mq-math-mode .mq-text-mode {
+ display: inline-block;
+}
+.mq-math-mode .mq-text-mode.mq-hasCursor {
+ box-shadow: inset darkgray 0 0.1em 0.2em;
+ padding: 0 0.1em;
+ margin: 0 -0.1em;
+ min-width: 1ex;
+}
+.mq-math-mode .mq-font {
+ font:
+ 1em 'Times New Roman',
+ Symbola,
+ serif;
+}
+.mq-math-mode .mq-font * {
+ font-family: inherit;
+ font-style: inherit;
+}
+.mq-math-mode b,
+.mq-math-mode b.mq-font {
+ font-weight: bolder;
+}
+.mq-math-mode var,
+.mq-math-mode i,
+.mq-math-mode i.mq-font {
+ font-style: italic;
+}
+.mq-math-mode var.mq-f {
+ margin-right: 0.2em;
+ margin-left: 0.1em;
+}
+.mq-math-mode .mq-roman var.mq-f {
+ margin: 0;
+}
+.mq-math-mode big {
+ font-size: 200%;
+}
+.mq-math-mode .mq-int > big {
+ display: inline-block;
+ -webkit-transform: scaleX(0.7);
+ -moz-transform: scaleX(0.7);
+ -ms-transform: scaleX(0.7);
+ -o-transform: scaleX(0.7);
+ transform: scaleX(0.7);
+ vertical-align: -0.16em;
+}
+.mq-math-mode .mq-int > .mq-supsub {
+ font-size: 80%;
+ vertical-align: -1.1em;
+ padding-right: 0.2em;
+}
+.mq-math-mode .mq-int > .mq-supsub > .mq-sup > .mq-sup-inner {
+ vertical-align: 1.3em;
+}
+.mq-math-mode .mq-int > .mq-supsub > .mq-sub {
+ margin-left: -0.35em;
+}
+.mq-math-mode .mq-roman {
+ font-style: normal;
+}
+.mq-math-mode .mq-sans-serif {
+ font-family: sans-serif, Symbola, serif;
+}
+.mq-math-mode .mq-monospace {
+ font-family: monospace, Symbola, serif;
+}
+.mq-math-mode .mq-overline {
+ border-top: 1px solid black;
+ margin-top: 1px;
+}
+.mq-math-mode .mq-underline {
+ border-bottom: 1px solid black;
+ margin-bottom: 1px;
+}
+.mq-math-mode .mq-binary-operator {
+ padding: 0 0.2em;
+ display: -moz-inline-box;
+ display: inline-block;
+}
+.mq-math-mode .mq-supsub {
+ text-align: left;
+ font-size: 90%;
+ vertical-align: -0.5em;
+}
+.mq-math-mode .mq-supsub.mq-sup-only {
+ vertical-align: 0.5em;
+}
+.mq-math-mode .mq-supsub.mq-sup-only .mq-sup {
+ display: inline-block;
+ vertical-align: text-bottom;
+}
+.mq-math-mode .mq-supsub .mq-sup {
+ display: block;
+}
+.mq-math-mode .mq-supsub .mq-sub {
+ display: block;
+ float: left;
+}
+.mq-math-mode .mq-supsub .mq-binary-operator {
+ padding: 0 0.1em;
+}
+.mq-math-mode .mq-supsub .mq-fraction {
+ font-size: 70%;
+}
+.mq-math-mode sup.mq-nthroot {
+ font-size: 80%;
+ vertical-align: 0.8em;
+ margin-right: -0.6em;
+ margin-left: 0.2em;
+ min-width: 0.5em;
+}
+.mq-math-mode .mq-paren {
+ padding: 0 0.1em;
+ vertical-align: top;
+ -webkit-transform-origin: center 0.06em;
+ -moz-transform-origin: center 0.06em;
+ -ms-transform-origin: center 0.06em;
+ -o-transform-origin: center 0.06em;
+ transform-origin: center 0.06em;
+}
+.mq-math-mode .mq-paren.mq-ghost {
+ color: silver;
+}
+.mq-math-mode .mq-paren + span {
+ margin-top: 0.1em;
+ margin-bottom: 0.1em;
+}
+.mq-math-mode .mq-array {
+ vertical-align: middle;
+ text-align: center;
+}
+.mq-math-mode .mq-array > span {
+ display: block;
+}
+.mq-math-mode .mq-operator-name {
+ font-family: Symbola, 'Times New Roman', serif;
+ line-height: 0.9;
+ font-style: normal;
+}
+.mq-math-mode var.mq-operator-name.mq-first {
+ padding-left: 0.2em;
+}
+.mq-math-mode var.mq-operator-name.mq-last,
+.mq-math-mode .mq-supsub.mq-after-operator-name {
+ padding-right: 0.2em;
+}
+.mq-math-mode .mq-fraction {
+ font-size: 90%;
+ text-align: center;
+ vertical-align: -0.4em;
+ padding: 0 0.2em;
+}
+.mq-math-mode .mq-fraction,
+.mq-math-mode .mq-large-operator,
+.mq-math-mode x:-moz-any-link {
+ display: -moz-groupbox;
+}
+.mq-math-mode .mq-fraction,
+.mq-math-mode .mq-large-operator,
+.mq-math-mode x:-moz-any-link,
+.mq-math-mode x:default {
+ display: inline-block;
+}
+.mq-math-mode .mq-numerator,
+.mq-math-mode .mq-denominator {
+ display: block;
+}
+.mq-math-mode .mq-numerator {
+ padding: 0 0.1em;
+}
+.mq-math-mode .mq-denominator {
+ border-top: 1px solid;
+ float: right;
+ width: 100%;
+ padding: 0.1em;
+}
+.mq-math-mode .mq-sqrt-prefix {
+ padding-top: 0;
+ position: relative;
+ top: 0.1em;
+ vertical-align: top;
+ -webkit-transform-origin: top;
+ -moz-transform-origin: top;
+ -ms-transform-origin: top;
+ -o-transform-origin: top;
+ transform-origin: top;
+}
+.mq-math-mode .mq-sqrt-stem {
+ border-top: 1px solid;
+ margin-top: 1px;
+ padding-left: 0.15em;
+ padding-right: 0.2em;
+ margin-right: 0.1em;
+ padding-top: 1px;
+}
+.mq-math-mode .mq-vector-prefix {
+ display: block;
+ text-align: center;
+ line-height: 0.25em;
+ margin-bottom: -0.1em;
+ font-size: 0.75em;
+}
+.mq-math-mode .mq-vector-stem {
+ display: block;
+}
+.mq-math-mode .mq-large-operator {
+ vertical-align: -0.2em;
+ padding: 0.2em;
+ text-align: center;
+}
+.mq-math-mode .mq-large-operator .mq-from,
+.mq-math-mode .mq-large-operator big,
+.mq-math-mode .mq-large-operator .mq-to {
+ display: block;
+}
+.mq-math-mode .mq-large-operator .mq-from,
+.mq-math-mode .mq-large-operator .mq-to {
+ font-size: 80%;
+}
+.mq-math-mode .mq-large-operator .mq-from {
+ float: right;
+ /* take out of normal flow to manipulate baseline */
+ width: 100%;
+}
+.mq-math-mode,
+.mq-math-mode .mq-editable-field {
+ cursor: text;
+ font-family: Symbola, 'Times New Roman', serif;
+}
+.mq-math-mode .mq-overarrow {
+ border-top: 1px solid black;
+ margin-top: 1px;
+ padding-top: 0.2em;
+}
+.mq-math-mode .mq-overarrow:before {
+ display: block;
+ position: relative;
+ top: -0.34em;
+ font-size: 0.5em;
+ line-height: 0em;
+ content: '\27A4';
+ text-align: right;
+}
+.mq-math-mode .mq-overarrow.mq-arrow-left:before {
+ -moz-transform: scaleX(-1);
+ -o-transform: scaleX(-1);
+ -webkit-transform: scaleX(-1);
+ transform: scaleX(-1);
+ filter: FlipH;
+ -ms-filter: 'FlipH';
+}
+.mq-math-mode .mq-selection,
+.mq-editable-field .mq-selection,
+.mq-math-mode .mq-selection .mq-non-leaf,
+.mq-editable-field .mq-selection .mq-non-leaf,
+.mq-math-mode .mq-selection .mq-scaled,
+.mq-editable-field .mq-selection .mq-scaled {
+ background: #b4d5fe !important;
+ background: Highlight !important;
+ color: HighlightText;
+ border-color: HighlightText;
+}
+.mq-math-mode .mq-selection .mq-matrixed,
+.mq-editable-field .mq-selection .mq-matrixed {
+ background: #39f !important;
+}
+.mq-math-mode .mq-selection .mq-matrixed-container,
+.mq-editable-field .mq-selection .mq-matrixed-container {
+ filter: progid:DXImageTransform.Microsoft.Chroma(color='#3399FF') !important;
+}
+.mq-math-mode .mq-selection.mq-blur,
+.mq-editable-field .mq-selection.mq-blur,
+.mq-math-mode .mq-selection.mq-blur .mq-non-leaf,
+.mq-editable-field .mq-selection.mq-blur .mq-non-leaf,
+.mq-math-mode .mq-selection.mq-blur .mq-scaled,
+.mq-editable-field .mq-selection.mq-blur .mq-scaled,
+.mq-math-mode .mq-selection.mq-blur .mq-matrixed,
+.mq-editable-field .mq-selection.mq-blur .mq-matrixed {
+ background: #d4d4d4 !important;
+ color: black;
+ border-color: black;
+}
+.mq-math-mode .mq-selection.mq-blur .mq-matrixed-container,
+.mq-editable-field .mq-selection.mq-blur .mq-matrixed-container {
+ filter: progid:DXImageTransform.Microsoft.Chroma(color='#D4D4D4') !important;
+}
+.mq-editable-field .mq-textarea,
+.mq-math-mode .mq-textarea {
+ position: relative;
+ -webkit-user-select: text;
+ -moz-user-select: text;
+ user-select: text;
+}
+.mq-editable-field .mq-textarea *,
+.mq-math-mode .mq-textarea *,
+.mq-editable-field .mq-selectable,
+.mq-math-mode .mq-selectable {
+ -webkit-user-select: text;
+ -moz-user-select: text;
+ user-select: text;
+ position: absolute;
+ clip: rect(1em 1em 1em 1em);
+ -webkit-transform: scale(0);
+ -moz-transform: scale(0);
+ -ms-transform: scale(0);
+ -o-transform: scale(0);
+ transform: scale(0);
+ resize: none;
+ width: 1px;
+ height: 1px;
+}
+.mq-math-mode .mq-matrixed {
+ background: white;
+ display: -moz-inline-box;
+ display: inline-block;
+}
+.mq-math-mode .mq-matrixed-container {
+ filter: progid:DXImageTransform.Microsoft.Chroma(color='white');
+ margin-top: -0.1em;
+}
diff --git a/src/client/views/nodes/formattedText/EquationEditor.tsx b/src/client/views/nodes/formattedText/EquationEditor.tsx
index bde6c1315..07c70af77 100644
--- a/src/client/views/nodes/formattedText/EquationEditor.tsx
+++ b/src/client/views/nodes/formattedText/EquationEditor.tsx
@@ -3,8 +3,7 @@ import React, { Component, createRef } from 'react';
// Import JQuery, required for the functioning of the equation editor
import $ from 'jquery';
-// Import the styles from the Mathquill editor
-import 'mathquill/build/mathquill.css';
+import './EquationEditor.scss';
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
@@ -14,6 +13,8 @@ window.jQuery = $;
// @ts-ignore
require('mathquill/build/mathquill');
+(window as any).MathQuill = (window as any).MathQuill.getInterface(1);
+
type EquationEditorProps = {
onChange(latex: string): void;
value: string;
@@ -74,8 +75,7 @@ class EquationEditor extends Component<EquationEditorProps> {
autoOperatorNames,
};
- // @ts-ignore
- this.mathField = (MathQuill as any).MathField(this.element.current, config);
+ this.mathField = (window as any).MathQuill.MathField(this.element.current, config);
this.mathField.latex(value || '');
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 4f8e8769a..244de7849 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { isEqual } from 'lodash';
-import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, override, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { baseKeymap, selectAll } from 'prosemirror-commands';
import { history } from 'prosemirror-history';
@@ -24,7 +24,7 @@ import { RichTextUtils } from '../../../../fields/RichTextUtils';
import { ComputedField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, copyProps, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
import { DocServer } from '../../../DocServer';
@@ -129,7 +129,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
@computed get noSidebar() {
- return this.props.docViewPath().lastElement()?.props.hideDecorationTitle || this.props.noSidebar || this.Document._layout_noSidebar;
+ return this._props.docViewPath().lastElement()?._props.hideDecorationTitle || this._props.noSidebar || this.Document._layout_noSidebar;
}
@computed get layout_sidebarWidthPercent() {
return this._showSidebar ? '20%' : StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
@@ -138,7 +138,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.fieldKey + '_backgroundColor'], '#e4e4e4'));
}
@computed get layout_autoHeight() {
- return (this.props.forceAutoHeight || this.layoutDoc._layout_autoHeight) && !this.props.ignoreAutoHeight;
+ return (this._props.forceAutoHeight || this.layoutDoc._layout_autoHeight) && !this._props.ignoreAutoHeight;
}
@computed get textHeight() {
return NumCast(this.dataDoc[this.fieldKey + '_height']);
@@ -150,7 +150,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return !this.sidebarWidth() ? 0 : NumCast(this.dataDoc[this.SidebarKey + '_height']);
}
@computed get titleHeight() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0;
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin) || 0;
}
@computed get layout_autoHeightMargins() {
return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins);
@@ -162,7 +162,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
!this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? media_state.Recording : undefined);
}
@computed get config() {
- this._keymap = buildKeymap(schema, this.props);
+ this._keymap = buildKeymap(schema, this._props);
this._rules = new RichTextRules(this.Document, this);
return {
schema,
@@ -208,12 +208,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId
}
- constructor(props: any) {
+ _prevProps: React.PropsWithChildren<FieldViewProps & FormattedTextBoxProps>;
+ @override _props: React.PropsWithChildren<FieldViewProps & FormattedTextBoxProps>;
+ constructor(props: React.PropsWithChildren<FieldViewProps & FormattedTextBoxProps>) {
super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
FormattedTextBox.Instance = this;
this._recordingStart = Date.now();
}
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
// removes all hyperlink anchors for the removed linkDoc
// TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one.
// but since removing one anchor from the list of attr anchors isn't implemented, this will end up removing nothing.
@@ -315,10 +323,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return target;
};
- DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docViewPath().lastElement(), () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
+ DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this._props.docViewPath().lastElement(), () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
});
const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
- this.props.rootSelected?.() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
+ this._props.rootSelected?.() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
let ele: Opt<HTMLDivElement> = undefined;
try {
const contents = window.getSelection()?.getRangeAt(0).cloneContents();
@@ -361,7 +369,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now())));
if ((!prevData && !protoData) || newText || (!newText && !templateData)) {
// if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
- if ((this._finishingLink || this.props.isContentActive()) && removeSelection(newJson) !== removeSelection(prevData?.Data)) {
+ if ((this._finishingLink || this._props.isContentActive()) && removeSelection(newJson) !== removeSelection(prevData?.Data)) {
const numstring = NumCast(dataDoc[this.fieldKey], null);
dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : new RichTextField(newJson, newText);
dataDoc[this.fieldKey + '_noTemplate'] = true; // mark the data field as being split from the template if it has been edited
@@ -390,7 +398,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._editorView.updateState(EditorState.fromJSON(this.config, json));
}
}
- if (window.getSelection()?.isCollapsed && this.props.rootSelected?.()) {
+ if (window.getSelection()?.isCollapsed && this._props.rootSelected?.()) {
AnchorMenu.Instance.fadeOut(true);
}
}
@@ -438,7 +446,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
autoLink = () => {
const newAutoLinks = new Set<Doc>();
- const oldAutoLinks = LinkManager.Links(this.props.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords);
+ const oldAutoLinks = LinkManager.Links(this._props.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords);
if (this._editorView?.state.doc.textContent) {
const isNodeSel = this._editorView.state.selection instanceof NodeSelection;
const f = this._editorView.state.selection.from;
@@ -456,7 +464,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
updateTitle = () => {
const title = StrCast(this.dataDoc.title, Cast(this.dataDoc.title, RichTextField, null)?.Text);
if (
- !this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
+ !this._props.dontRegisterView && // (this._props.Document.isTemplateForField === "text" || !this._props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
(title.startsWith('-') || title.startsWith('@')) &&
this._editorView &&
!this.dataDoc.title_custom &&
@@ -499,7 +507,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
DocUtils.MakeLink(this.Document, target, { link_relationship: LinkManager.AutoKeywords })!);
newAutoLinks.add(alink);
// DocCast(alink.link_anchor_1).followLinkLocation = 'add:right';
- const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }];
+ const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this._props.Document[Id] }];
allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term' });
tr = tr.addMark(pos, pos + node.nodeSize, link);
@@ -568,7 +576,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
@undoBatch
- @action
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.annoDragData) {
de.complete.annoDragData.dropDocCreator = () => this.getAnchor(true);
@@ -584,7 +591,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
// replace text contents when dragging with Alt
if (de.altKey) {
const fieldKey = Doc.LayoutFieldKey(draggedDoc);
- if (draggedDoc[fieldKey] instanceof RichTextField && !Doc.AreProtosEqual(draggedDoc, this.props.Document)) {
+ if (draggedDoc[fieldKey] instanceof RichTextField && !Doc.AreProtosEqual(draggedDoc, this._props.Document)) {
Doc.GetProto(this.dataDoc)[this.fieldKey] = Field.Copy(draggedDoc[fieldKey]);
}
@@ -742,9 +749,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
);
};
sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
- const localDelta = this.props
+ const localDelta = this._props
.ScreenToLocalTransform()
- .scale(this.props.NativeDimScaling?.() || 1)
+ .scale(this._props.NativeDimScaling?.() || 1)
.transformDirection(delta[0], delta[1]);
const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.layout_sidebarWidthPercent.replace('%', ''))) / 100;
const width = NumCast(this.layoutDoc._width) + localDelta[0];
@@ -758,15 +765,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
deleteAnnotation = (anchor: Doc) => {
const batch = UndoManager.StartBatch('delete link');
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));
+ // 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);
- this.props.select(false);
+ this._props.select(false);
setTimeout(batch.end); // wait for reaction to remove link from document
};
@undoBatch
- pinToPres = (anchor: Doc) => this.props.pinToPres(anchor, {});
+ pinToPres = (anchor: Doc) => this._props.pinToPres(anchor, {});
@undoBatch
makeTargetToggle = (anchor: Doc) => (anchor.followLinkToggle = !anchor.followLinkToggle);
@@ -776,7 +783,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const trail = DocCast(anchor.presentationTrail);
if (trail) {
Doc.ActivePresentation = trail;
- this.props.addDocTab(trail, OpenWhere.replaceRight);
+ this._props.addDocTab(trail, OpenWhere.replaceRight);
}
};
@@ -913,7 +920,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const optionItems = options && 'subitems' in options ? options.subitems : [];
optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' });
- this.props.renderDepth &&
+ this._props.renderDepth &&
optionItems.push({
description: !this.Document._createDocOnCR ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns',
event: () => (this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR),
@@ -958,7 +965,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
generateImage = async () => {
GPTPopup.Instance?.setTextAnchor(this.getAnchor(false));
GPTPopup.Instance?.setImgTargetDoc(this.Document);
- GPTPopup.Instance.addToCollection = this.props.addDocument;
+ GPTPopup.Instance.addToCollection = this._props.addDocument;
GPTPopup.Instance.setImgDesc((this.dataDoc.text as RichTextField)?.Text);
GPTPopup.Instance.generateImage();
};
@@ -1127,7 +1134,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000));
return focusSpeed;
} else {
- return this.props.focus(this.Document, options);
+ return this._props.focus(this.Document, options);
}
}
};
@@ -1141,10 +1148,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
@computed get contentScaling() {
- return Doc.NativeAspect(this.Document, this.dataDoc, false) ? this.props.NativeDimScaling?.() || 1 : 1;
+ return Doc.NativeAspect(this.Document, this.dataDoc, false) ? this._props.NativeDimScaling?.() || 1 : 1;
}
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._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 = LinkManager.Links(this.Document);
this._disposers.breakupDictation = reaction(() => Doc.RecordingEvent, this.breakupDictation);
this._disposers.layout_autoHeight = reaction(
@@ -1157,7 +1164,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
{ fireImmediately: true }
);
this._disposers.width = reaction(
- () => this.props.PanelWidth(),
+ () => this._props.PanelWidth(),
width => this.tryUpdateScrollHeight()
);
this._disposers.scrollHeight = reaction(
@@ -1171,13 +1178,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
({ sidebarHeight, textHeight, layout_autoHeight, marginsHeight }) => {
const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight));
if (
- (!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this.props.isSelected()) && //
+ (!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this._props.isSelected()) && //
layout_autoHeight &&
newHeight &&
newHeight !== this.layoutDoc.height &&
- !this.props.dontRegisterView
+ !this._props.dontRegisterView
) {
- this.props.setHeight?.(newHeight);
+ this._props.setHeight?.(newHeight);
}
},
{ fireImmediately: !Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') }
@@ -1219,7 +1226,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
);
this._disposers.pullDoc = reaction(
- () => this.props.Document[Pulls],
+ () => this._props.Document[Pulls],
() => {
if (!DocumentButtonBar.hasPulledHack) {
DocumentButtonBar.hasPulledHack = true;
@@ -1228,7 +1235,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
);
this._disposers.pushDoc = reaction(
- () => this.props.Document[Pushes],
+ () => this._props.Document[Pushes],
() => {
if (!DocumentButtonBar.hasPushedHack) {
DocumentButtonBar.hasPushedHack = true;
@@ -1244,7 +1251,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
);
this._disposers.selected = reaction(
- () => this.props.rootSelected?.(),
+ () => this._props.rootSelected?.(),
action(selected => {
//selected && setTimeout(() => this.prepareForTyping());
if (FormattedTextBox._globalHighlights.has('Bold Text')) {
@@ -1254,14 +1261,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
}
if (this._editorView && selected) {
- RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props, this.layoutDoc);
+ RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this._props, this.layoutDoc);
setTimeout(this.autoLink, 20);
}
}),
{ fireImmediately: true }
);
- if (!this.props.dontRegisterView) {
+ if (!this._props.dontRegisterView) {
this._disposers.record = reaction(
() => this._recordingDictation,
() => {
@@ -1276,7 +1283,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._disposers.scroll = reaction(
() => NumCast(this.layoutDoc._layout_scrollTop),
pos => {
- if (!this._ignoreScroll && this._scrollRef.current && !this.props.dontSelectOnLoad) {
+ if (!this._ignoreScroll && this._scrollRef.current && !this._props.dontSelectOnLoad) {
const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
const durationSecStr = viewTrans.match(/([0-9.]*)s/);
@@ -1439,8 +1446,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const self = this;
return new Plugin({
view(newView) {
- runInAction(() => self.props.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));
- return new RichTextMenuPlugin({ editorProps: this.props });
+ runInAction(() => self._props.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));
+ return new RichTextMenuPlugin({ editorProps: this._props });
},
});
}
@@ -1462,7 +1469,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined;
if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) {
const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
- const scrollPos = scrollRef.scrollTop + shift * self.props.ScreenToLocalTransform().Scale;
+ const scrollPos = scrollRef.scrollTop + shift * self._props.ScreenToLocalTransform().Scale;
if (this._focusSpeed !== undefined) {
scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed, scrollRef, scrollPos, 'ease', this._scrollStopper));
} else {
@@ -1514,11 +1521,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
(this._editorView as any).TextView = this;
}
- const selectOnLoad = Doc.AreProtosEqual(this.props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()));
- if (this._editorView && selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
+ const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath()));
+ if (this._editorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
const selLoadChar = FormattedTextBox.SelectOnLoadChar;
FormattedTextBox.SelectOnLoad = undefined;
- this.props.select(false);
+ this._props.select(false);
if (selLoadChar) {
const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
@@ -1534,7 +1541,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
}
selectOnLoad && this._editorView!.focus();
- if (this.props.isContentActive()) this.prepareForTyping();
+ if (this._props.isContentActive()) this.prepareForTyping();
if (this._editorView) {
const tr = this._editorView.state.tr;
const { from, to } = tr.selection;
@@ -1557,15 +1564,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []),
...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []),
- ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily) })] : []),
- ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize) })] : []),
+ ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily) })] : []),
+ ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize) })] : []),
...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []),
...[schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })],
];
this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks));
};
- @action
componentWillUnmount() {
if (this._recordingDictation) {
this._recordingDictation = !this._recordingDictation;
@@ -1598,7 +1604,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const func = () => {
const docView = DocumentManager.Instance.getDocumentView(audiodoc);
if (!docView) {
- this.props.addDocTab(audiodoc, OpenWhere.addBottom);
+ this._props.addDocTab(audiodoc, OpenWhere.addBottom);
setTimeout(func);
} else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, 'number', null)); // bcz: would be nice to find the next audio tag in the doc and play until that
};
@@ -1614,7 +1620,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._downTime = Date.now();
this._hadDownFocus = this.ProseRef?.children[0].className.includes('focused') ?? false;
FormattedTextBoxComment.textBox = this;
- if (e.button === 0 && this.props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) {
+ if (e.button === 0 && this._props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) {
if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
// stop propagation if not in sidebar, otherwise nested boxes will lose focus to outer boxes.
e.stopPropagation(); // if the text box's content is active, then it consumes all down events
@@ -1636,7 +1642,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
if (!state || !editor || !this.ProseRef?.children[0].className.includes('-focused')) return;
if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
- else if (this.props.isContentActive(true)) {
+ else if (this._props.isContentActive(true)) {
const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
let xpos = pcords?.pos || 0;
while (xpos > 0 && !state.doc.resolve(xpos).node()?.isTextblock) {
@@ -1651,7 +1657,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
@action
onDoubleClick = (e: React.MouseEvent): void => {
FormattedTextBoxComment.textBox = this;
- if (e.button === 0 && this.props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) {
+ if (e.button === 0 && this._props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) {
if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
// stop propagation if not in sidebar
e.stopPropagation(); // if the text box is selected, then it consumes all click events
@@ -1662,7 +1668,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
FormattedTextBoxComment.Hide();
- if (e.buttons === 1 && this.props.rootSelected?.() && !e.altKey) {
+ if (e.buttons === 1 && this._props.rootSelected?.() && !e.altKey) {
e.stopPropagation();
}
};
@@ -1673,14 +1679,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
@action
onFocused = (e: React.FocusEvent): void => {
- console.log('FOCUSED = ' + this.layoutDoc.title + ' ' + this.props.rootSelected?.());
//applyDevTools.applyDevTools(this._editorView);
- this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props, this.layoutDoc);
+ this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this._props, this.layoutDoc);
e.stopPropagation();
};
onClick = (e: React.MouseEvent): void => {
- if (!this.props.isContentActive()) return;
+ if (!this._props.isContentActive()) return;
if ((e.nativeEvent as any).handledByInnerReactInstance) {
e.stopPropagation();
return;
@@ -1709,7 +1714,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos)));
}
}
- if (this.props.rootSelected?.()) {
+ if (this._props.rootSelected?.()) {
// if text box is selected, then it consumes all click events
(e.nativeEvent as any).handledByInnerReactInstance = true;
this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, this._forceDownNode, e.shiftKey);
@@ -1723,7 +1728,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
const clickPos = this._editorView!.posAtCoords({ left: x, top: y });
let olistPos = clickPos?.pos;
- if (clickPos && olistPos && this.props.rootSelected?.()) {
+ if (clickPos && olistPos && this._props.rootSelected?.()) {
const clickNode = this._editorView?.state.doc.nodeAt(olistPos);
const nodeBef = this._editorView?.state.doc.nodeAt(Math.max(0, olistPos - 1));
olistPos = nodeBef?.type === this._editorView?.state.schema.nodes.ordered_list ? olistPos - 1 : olistPos;
@@ -1772,7 +1777,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
tr && this._editorView.dispatch(tr);
}
}
- if (RichTextMenu.Instance?.view === this._editorView && !this.props.rootSelected?.()) {
+ if (RichTextMenu.Instance?.view === this._editorView && !this._props.rootSelected?.()) {
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
}
FormattedTextBox._hadSelection = window.getSelection()?.toString() !== '';
@@ -1797,7 +1802,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if ((e.altKey || e.ctrlKey) && e.key === 't') {
e.preventDefault();
e.stopPropagation();
- this.props.setTitleFocus?.();
+ this._props.setTitleFocus?.();
return;
}
const state = this._editorView!.state;
@@ -1851,7 +1856,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
onScroll = (e: React.UIEvent) => {
if (!LinkDocPreview.LinkInfo && this._scrollRef.current) {
- if (!this.props.dontSelectOnLoad) {
+ if (!this._props.dontSelectOnLoad) {
this._ignoreScroll = true;
this.layoutDoc._layout_scrollTop = this._scrollRef.current.scrollTop;
this._ignoreScroll = false;
@@ -1861,7 +1866,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
};
tryUpdateScrollHeight = () => {
- const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
+ const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
if (children && !SnappingManager.GetIsDragging()) {
const toNum = (val: string) => Number(val.replace('px', '').replace('auto', '0'));
@@ -1871,7 +1876,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + toHgt(child), margins);
const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.layout_maxAutoHeight, proseHeight), proseHeight);
- if (this.props.setHeight && scrollHeight && !this.props.dontRegisterView) {
+ if (this._props.setHeight && scrollHeight && !this._props.dontRegisterView) {
// if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
const setScrollHeight = () => (this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight);
@@ -1883,8 +1888,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
}
};
- fitContentsToBox = () => BoolCast(this.props.Document._freeform_fitContentsToBox);
- sidebarContentScaling = () => (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
+ fitContentsToBox = () => BoolCast(this._props.Document._freeform_fitContentsToBox);
+ sidebarContentScaling = () => (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => {
if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
return this.addDocument(doc, sidebarKey);
@@ -1892,12 +1897,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey);
setSidebarHeight = (height: number) => (this.dataDoc[this.SidebarKey + '_height'] = height);
- sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
+ sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
sidebarScreenToLocal = () =>
- this.props
+ this._props
.ScreenToLocalTransform()
- .translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.NativeDimScaling?.() || 1), 0)
- .scale(1 / NumCast(this.layoutDoc._freeform_scale, 1) / (this.props.NativeDimScaling?.() || 1));
+ .translate(-(this._props.PanelWidth() - this.sidebarWidth()) / (this._props.NativeDimScaling?.() || 1), 0)
+ .scale(1 / NumCast(this.layoutDoc._freeform_scale, 1) / (this._props.NativeDimScaling?.() || 1));
@computed get audioHandle() {
return !this._recordingDictation ? null : (
@@ -1920,9 +1925,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
TraceMobx();
const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
const color = !annotated ? Colors.WHITE : Colors.BLACK;
- const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.WidgetColor + (annotated ? ':annotated' : ''));
+ const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.WidgetColor + (annotated ? ':annotated' : ''));
- return !annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? null : (
+ return !annotated && (!this._props.isContentActive() || SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? null : (
<div
className="formattedTextBox-sidebar-handle"
onPointerDown={this.sidebarDown}
@@ -1941,7 +1946,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return ComponentTag === CollectionStackingView ? (
<SidebarAnnos
ref={this._sidebarRef}
- {...this.props}
+ {...this._props}
Document={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
@@ -1958,14 +1963,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
setHeight={this.setSidebarHeight}
/>
) : (
- <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
+ <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this._props.DocumentView?.()!, false), true)}>
<ComponentTag
- {...this.props}
+ {...this._props}
ref={this._sidebarTagRef as any}
setContentView={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
- PanelHeight={this.props.PanelHeight}
+ PanelHeight={this._props.PanelHeight}
PanelWidth={this.sidebarWidth}
xPadding={0}
yPadding={0}
@@ -1979,7 +1984,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
moveDocument={this.sidebarMoveDocument}
addDocument={this.sidebarAddDocument}
ScreenToLocalTransform={this.sidebarScreenToLocal}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
setHeight={this.setSidebarHeight}
fitContentsToBox={this.fitContentsToBox}
noSidebar={true}
@@ -1997,12 +2002,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
cycleAlternateText = () => {
if (this.layoutDoc._layout_enableAltContentUI) {
- const usePath = this.layoutDoc[`_${this.props.fieldKey}_usePath`];
- this.layoutDoc[`_${this.props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined;
+ const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
+ this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined;
}
};
@computed get overlayAlternateIcon() {
- const usePath = this.layoutDoc[`_${this.props.fieldKey}_usePath`];
+ const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
return (
<Tooltip
title={
@@ -2024,7 +2029,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
className="formattedTextBox-alternateButton"
onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => this.cycleAlternateText())}
style={{
- display: this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'flex' : 'none',
+ display: this._props.isContentActive() && !SnappingManager.GetIsDragging() ? 'flex' : 'none',
background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
color: usePath === undefined ? 'black' : 'white',
}}>
@@ -2033,9 +2038,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
</Tooltip>
);
}
- @computed get fieldKey() {
- const usePath = StrCast(this.layoutDoc[`${this.props.fieldKey}_usePath`]);
- return this.props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering || this.props.isContentActive()) ? `_${usePath.replace(':hover', '')}` : '');
+ get fieldKey() {
+ const usePath = StrCast(this.layoutDoc[`${this._props.fieldKey}_usePath`]);
+ return this._props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering || this._props.isContentActive()) ? `_${usePath.replace(':hover', '')}` : '');
}
@observable _isHovering = false;
onPassiveWheel = (e: WheelEvent) => {
@@ -2049,14 +2054,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
// if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
- if (this.props.isContentActive()) {
- const scale = this.props.NativeDimScaling?.() || 1;
- const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this.props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
+ if (this._props.isContentActive()) {
+ const scale = this._props.NativeDimScaling?.() || 1;
+ const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
const height = Number(styleFromLayoutString.height?.replace('px', ''));
// prevent default if selected || child is active but this doc isn't scrollable
if (
- (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil((height ? height : this.props.PanelHeight()) / scale) && //
- (this.props.rootSelected?.() || this.isAnyChildContentActive())
+ (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil((height ? height : this._props.PanelHeight()) / scale) && //
+ (this._props.rootSelected?.() || this.isAnyChildContentActive())
) {
e.preventDefault();
}
@@ -2065,25 +2070,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
_oldWheel: any;
@computed get fontColor() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
}
@computed get fontSize() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
}
@computed get fontFamily() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily);
}
@computed get fontWeight() {
- return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontWeight);
}
render() {
TraceMobx();
- const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
+ const scale = (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : '';
- setTimeout(() => !this.props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
- const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0);
- const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
- const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this.props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
+ setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
+ const paddingX = NumCast(this.layoutDoc._xMargin, this._props.xPadding || 0);
+ const paddingY = NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
+ const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
return styleFromLayoutString?.height === '0px' ? null : (
<div
className="formattedTextBox"
@@ -2095,7 +2100,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
}}
style={{
- ...(this.props.dontScale
+ ...(this._props.dontScale
? {}
: {
transform: `scale(${scale})`,
@@ -2114,9 +2119,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
className="formattedTextBox-cont"
ref={this._ref}
style={{
- cursor: this.props.isContentActive() ? 'text' : undefined,
- height: this.props.height ? 'max-content' : undefined,
- pointerEvents: Doc.ActiveTool === InkTool.None && !this.props.onBrowseClick?.() ? undefined : 'none',
+ cursor: this._props.isContentActive() ? 'text' : undefined,
+ height: this._props.height ? 'max-content' : undefined,
+ pointerEvents: Doc.ActiveTool === InkTool.None && !this._props.onBrowseClick?.() ? undefined : 'none',
}}
onContextMenu={this.specificContextMenu}
onKeyDown={this.onKeyDown}
@@ -2131,7 +2136,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
className="formattedTextBox-outer"
ref={this._scrollRef}
style={{
- width: this.props.dontSelectOnLoad || this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
+ width: this._props.dontSelectOnLoad || this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
}}
onScroll={this.onScroll}
@@ -2148,8 +2153,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}}
/>
</div>
- {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
- {this.noSidebar || this.Document._layout_noSidebar || this.props.dontSelectOnLoad || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle}
+ {this.noSidebar || this._props.dontSelectOnLoad || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
+ {this.noSidebar || this.Document._layout_noSidebar || this._props.dontSelectOnLoad || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle}
{this.audioHandle}
{this.layoutDoc._layout_enableAltContentUI && !this.layoutDoc._chromeHidden ? this.overlayAlternateIcon : null}
</div>
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 7ce06cf7f..d7e799161 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { lift, wrapIn } from 'prosemirror-commands';
import { Mark, MarkType, Node as ProsNode, ResolvedPos } from 'prosemirror-model';
@@ -10,7 +10,7 @@ import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { Doc } from '../../../../fields/Doc';
import { BoolCast, Cast, StrCast } from '../../../../fields/Types';
-import { numberRange } from '../../../../Utils';
+import { copyProps, numberRange } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { LinkManager } from '../../../util/LinkManager';
import { SelectionManager } from '../../../util/SelectionManager';
@@ -63,8 +63,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable private showLinkDropdown: boolean = false;
_reaction: IReactionDisposer | undefined;
- constructor(props: Readonly<{}>) {
+ _prevProps: AntimodeMenuProps;
+ @override _props: AntimodeMenuProps;
+ constructor(props: AntimodeMenuProps) {
super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
runInAction(() => {
RichTextMenu.Instance = this;
this.updateMenu(undefined, undefined, props, this.layoutDoc);
@@ -107,6 +111,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return BoolCast(this.layoutDoc?.layout_centered);
}
_disposer: IReactionDisposer | undefined;
+ componentDidUpdate() {
+ copyProps(this);
+ }
componentDidMount() {
this._disposer = reaction(
() => SelectionManager.Views().slice(),
@@ -361,7 +368,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
} else if (SelectionManager.Views().some(dv => dv.ComponentView instanceof EquationBox)) {
SelectionManager.Views().forEach(dv => (dv.Document._text_fontSize = fontSize));
} else Doc.UserDoc().fontSize = fontSize;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+ this.updateMenu(this.view, undefined, this._props, this.layoutDoc);
};
setFontFamily = (family: string) => {
@@ -370,7 +377,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
this.view.focus();
} else Doc.UserDoc().fontFamily = family;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+ this.updateMenu(this.view, undefined, this._props, this.layoutDoc);
};
setHighlight(color: string) {
@@ -379,7 +386,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.setMark(highlightMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(highlightMark)), true);
this.view.focus();
} else Doc.UserDoc()._fontHighlight = color;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+ this.updateMenu(this.view, undefined, this._props, this.layoutDoc);
}
setColor(color: string) {
@@ -388,7 +395,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true);
this.view.focus();
} else Doc.UserDoc().fontColor = color;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+ this.updateMenu(this.view, undefined, this._props, this.layoutDoc);
}
// TODO: remove doesn't work
@@ -429,7 +436,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
}
this.view.focus();
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+ this.updateMenu(this.view, undefined, this._props, this.layoutDoc);
};
insertSummarizer(state: EditorState, dispatch: any) {
@@ -669,7 +676,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
};
@undoBatch
- @action
deleteLink = () => {
if (this.view) {
const linkAnchor = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor);
@@ -816,6 +822,18 @@ export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
@observable private showDropdown: boolean = false;
private ref: HTMLDivElement | null = null;
+ _prevProps: React.PropsWithChildren<ButtonDropdownProps>;
+ @observable _props: React.PropsWithChildren<ButtonDropdownProps>;
+ constructor(props: React.PropsWithChildren<ButtonDropdownProps>) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
componentDidMount() {
document.addEventListener('pointerdown', this.onBlur);
}
@@ -850,22 +868,22 @@ export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
render() {
return (
<div className="button-dropdown-wrapper" ref={node => (this.ref = node)}>
- {!this.props.pdf ? (
- <div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this.props.openDropdownOnButton ? this.onDropdownClick : undefined}>
- {this.props.button}
- <div style={{ marginTop: '-8.5', position: 'relative' }} onPointerDown={!this.props.openDropdownOnButton ? this.onDropdownClick : undefined}>
+ {!this._props.pdf ? (
+ <div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this._props.openDropdownOnButton ? this.onDropdownClick : undefined}>
+ {this._props.button}
+ <div style={{ marginTop: '-8.5', position: 'relative' }} onPointerDown={!this._props.openDropdownOnButton ? this.onDropdownClick : undefined}>
<FontAwesomeIcon icon="caret-down" size="sm" />
</div>
</div>
) : (
<>
- {this.props.button}
+ {this._props.button}
<button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
<FontAwesomeIcon icon="caret-down" size="sm" />
</button>
</>
)}
- {this.showDropdown ? this.props.dropdownContent : null}
+ {this.showDropdown ? this._props.dropdownContent : null}
</div>
);
}
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 1317ca814..a224ec7fa 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -36,9 +36,6 @@ import { FieldView, FieldViewProps } from '../FieldView';
import { ScriptingBox } from '../ScriptingBox';
import './PresBox.scss';
import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
-import _ = require('lodash');
-const { Howl } = require('howler');
-
export interface pinDataTypes {
scrollable?: boolean;
dataviz?: number[];
@@ -156,7 +153,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
addToSelectedArray = action((doc: Doc) => this.selectedArray.add(doc));
removeFromSelectedArray = action((doc: Doc) => this.selectedArray.delete(doc));
- @action
componentWillUnmount() {
this._unmounting = true;
if (this._presTimer) clearTimeout(this._presTimer);
@@ -187,7 +183,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
},
{ fireImmediately: true }
);
- this.props.setContentView?.(this);
+ this._props.setContentView?.(this);
this._unmounting = false;
this.turnOffEdit(true);
this._disposers.selection = reaction(
@@ -603,7 +599,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const dv = DocumentManager.Instance.getDocumentView(bestTarget);
if (dv) {
changed = true;
- const computedScale = NumCast(activeItem.config_zoom, 1) * Math.min(dv.props.PanelWidth() / viewport.width, dv.props.PanelHeight() / viewport.height);
+ const computedScale = NumCast(activeItem.config_zoom, 1) * Math.min(dv._props.PanelWidth() / viewport.width, dv._props.PanelHeight() / viewport.height);
activeItem.presentation_movement === PresMovement.Zoom && (bestTarget._freeform_scale = computedScale);
dv.ComponentView?.brushView?.(viewport, transTime, 2500);
}
@@ -748,7 +744,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const dragViewCache = Array.from(this._dragArray);
const eleViewCache = Array.from(this._eleArray);
const resetSelection = action(() => {
- if (!this.props.isSelected()) {
+ if (!this._props.isSelected()) {
const presDocView = DocumentManager.Instance.getDocumentView(this.Document);
if (presDocView) SelectionManager.SelectView(presDocView, false);
this.clearSelectedArray();
@@ -982,9 +978,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
enterMinimize = () => {
this.updateCurrentPresentation(this.Document);
clearTimeout(this._presTimer);
- const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
- this.props.removeDocument?.(this.layoutDoc);
- return PresBox.OpenPresMinimized(this.Document, [pt[0] + (this.props.PanelWidth() - 250), pt[1] + 10]);
+ const pt = this._props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ this._props.removeDocument?.(this.layoutDoc);
+ return PresBox.OpenPresMinimized(this.Document, [pt[0] + (this._props.PanelWidth() - 250), pt[1] + 10]);
};
exitMinimize = () => {
if (Doc.IsInMyOverlay(this.layoutDoc)) {
@@ -1052,7 +1048,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return StrCast(activeItem.presentation_movement);
});
- whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isChildActive = isActive)));
+ whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isChildActive = isActive)));
// For dragging documents into the presentation trail
addDocumentFilter = (docs: Doc[]) => {
docs.forEach((doc, i) => {
@@ -1063,7 +1059,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
audio.config_clipStart = NumCast(doc._timecodeToShow /* audioStart */, NumCast(doc._timecodeToShow /* videoStart */));
audio.config_clipEnd = NumCast(doc._timecodeToHide /* audioEnd */, NumCast(doc._timecodeToHide /* videoEnd */));
audio.presentation_duration = audio.config_clipStart - audio.config_clipEnd;
- this.props.pinToPres(audio, { audioRange: true });
+ this._props.pinToPres(audio, { audioRange: true });
setTimeout(() => this.removeDocument(doc), 0);
return false;
}
@@ -1079,8 +1075,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
childLayoutTemplate = () => Docs.Create.PresElementBoxDocument();
removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.Document, this.fieldKey, doc);
- getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
- panelHeight = () => this.props.PanelHeight() - 40;
+ getTransform = () => this._props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
+ panelHeight = () => this._props.PanelHeight() - 40;
/**
* For sorting the array so that the order is maintained when it is dropped.
*/
@@ -1313,7 +1309,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
} else if (doc.config_pinView && presCollection === tagDoc && dv) {
// Case B: Document is presPinView and is presCollection
const scale = 1 / NumCast(doc.config_viewScale);
- const viewBounds = NumListCast(doc.config_viewBounds, [0, 0, dv.props.PanelWidth(), dv.props.PanelHeight()]);
+ const viewBounds = NumListCast(doc.config_viewBounds, [0, 0, dv._props.PanelWidth(), dv._props.PanelHeight()]);
const height = (viewBounds[3] - viewBounds[1]) * scale;
const width = (viewBounds[2] - viewBounds[0]) * scale;
const indWidth = width / 10;
@@ -1433,46 +1429,39 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
updateMovement = action((movement: PresMovement, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_movement = movement)));
@undoBatch
- @action
updateHideBefore = (activeItem: Doc) => {
activeItem.presentation_hideBefore = !activeItem.presentation_hideBefore;
this.selectedArray.forEach(doc => (doc.presentation_hideBefore = activeItem.presentation_hideBefore));
};
@undoBatch
- @action
updateHide = (activeItem: Doc) => {
activeItem.presentation_hide = !activeItem.presentation_hide;
this.selectedArray.forEach(doc => (doc.presentation_hide = activeItem.presentation_hide));
};
@undoBatch
- @action
updateHideAfter = (activeItem: Doc) => {
activeItem.presentation_hideAfter = !activeItem.presentation_hideAfter;
this.selectedArray.forEach(doc => (doc.presentation_hideAfter = activeItem.presentation_hideAfter));
};
@undoBatch
- @action
updateOpenDoc = (activeItem: Doc) => {
activeItem.presentation_openInLightbox = !activeItem.presentation_openInLightbox;
this.selectedArray.forEach(doc => (doc.presentation_openInLightbox = activeItem.presentation_openInLightbox));
};
@undoBatch
- @action
updateEaseFunc = (activeItem: Doc) => {
activeItem.presEaseFunc = activeItem.presEaseFunc === 'linear' ? 'ease' : 'linear';
this.selectedArray.forEach(doc => (doc.presEaseFunc = activeItem.presEaseFunc));
};
@undoBatch
- @action
updateEffectDirection = (effect: PresEffectDirection, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_effectDirection = effect));
@undoBatch
- @action
updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (bullet ? (doc.presBulletEffect = effect) : (doc.presentation_effect = effect)));
static _sliderBatch: any;
@@ -1505,7 +1494,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
@undoBatch
- @action
applyTo = (array: Doc[]) => {
this.updateMovement(this.activeItem.presentation_movement as PresMovement, true);
this.updateEffect(this.activeItem.presentation_effect as PresEffect, false, true);
@@ -2219,10 +2207,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const config_data = Cast(this.Document.data, listSpec(Doc));
if (data && config_data) {
data.push(doc);
- this.props.pinToPres(doc, {});
+ this._props.pinToPres(doc, {});
this.gotoDocument(this.childDocs.length, this.activeItem);
} else {
- this.props.addDocTab(doc, OpenWhere.addRight);
+ this._props.addDocTab(doc, OpenWhere.addRight);
}
}
};
@@ -2283,15 +2271,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed
get toolbarWidth(): number {
- return this.props.PanelWidth();
+ return this._props.PanelWidth();
}
@action
- toggleProperties = () => (SettingsManager.propertiesWidth = SettingsManager.propertiesWidth > 0 ? 0 : 250);
+ toggleProperties = () => (SettingsManager.Instance.propertiesWidth = SettingsManager.Instance.propertiesWidth > 0 ? 0 : 250);
@computed get toolbar() {
- const propIcon = SettingsManager.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
- const propTitle = SettingsManager.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel';
+ const propIcon = SettingsManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
+ const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel';
const mode = StrCast(this.Document._type_collection) as CollectionViewType;
const isMini: boolean = this.toolbarWidth <= 100;
const activeColor = SettingsManager.userVariantColor;
@@ -2320,7 +2308,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</Tooltip>
<Tooltip title={<div className="dash-tooltip">{propTitle}</div>}>
<div className="toolbar-button" style={{ position: 'absolute', right: 4, fontSize: 16 }} onClick={this.toggleProperties}>
- <FontAwesomeIcon className={'toolbar-thumbtack'} icon={propIcon} style={{ color: SettingsManager.propertiesWidth > 0 ? activeColor : inactiveColor }} />
+ <FontAwesomeIcon className={'toolbar-thumbtack'} icon={propIcon} style={{ color: SettingsManager.Instance.propertiesWidth > 0 ? activeColor : inactiveColor }} />
</div>
</Tooltip>
</>
@@ -2366,7 +2354,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
})}>
<FontAwesomeIcon icon={'play-circle'} />
- <div style={{ display: this.props.PanelWidth() > 200 ? 'inline-flex' : 'none' }}>&nbsp; Present</div>
+ <div style={{ display: this._props.PanelWidth() > 200 ? 'inline-flex' : 'none' }}>&nbsp; Present</div>
</div>
{mode === CollectionViewType.Carousel3D || isMini ? null : (
<div
@@ -2476,12 +2464,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<b>1</b>
</div>
</Tooltip>
- <div className="presPanel-button-text" onClick={() => this.gotoDocument(0, this.activeItem)} style={{ display: inOverlay || this.props.PanelWidth() > 250 ? 'inline-flex' : 'none' }}>
+ <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.activeItem?.presentation_indexed !== undefined ? `(${this.activeItem.presentation_indexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length}
</div>
<div className="presPanel-divider"></div>
- {this.props.PanelWidth() > 250 ? (
+ {this._props.PanelWidth() > 250 ? (
<div
className="presPanel-button-text"
onClick={undoBatch(
@@ -2527,7 +2515,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
@undoBatch
- @action
exitClicked = () => {
this.layoutDoc.presentation_status = this._exitTrail?.() ?? this.exitMinimize();
clearTimeout(this._presTimer);
@@ -2570,7 +2557,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.itemIndex === this.childDocs.length - 1 &&
(this.activeItem.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
const presStart = !this.layoutDoc.presLoop && this.itemIndex === 0;
- 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
+ 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
<div className="miniPres" onClick={e => e.stopPropagation()} onPointerEnter={action(e => (this._forceKeyEvents = true))}>
<div
className="presPanelOverlay"
@@ -2620,8 +2607,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="Slide">
{mode !== CollectionViewType.Invalid ? (
<CollectionView
- {...this.props}
- PanelWidth={this.props.PanelWidth}
+ {...this._props}
+ PanelWidth={this._props.PanelWidth}
PanelHeight={this.panelHeight}
childIgnoreNativeSize={true}
moveDocument={returnFalse}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index cd6beac57..9fcb496b8 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -1,12 +1,12 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
+import { copyProps, emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { DocumentManager } from '../../../util/DocumentManager';
@@ -43,9 +43,21 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
@observable _dragging = false;
+ _prevProps: FieldViewProps;
+ @override _props: FieldViewProps;
+ constructor(props: FieldViewProps) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ }
+
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
// the presentation view that renders this slide
@computed get presBoxView() {
- return this.props.DocumentView?.()?.props.docViewPath().lastElement()?.ComponentView as PresBox;
+ return this._props.DocumentView?.()?._props.docViewPath().lastElement()?.ComponentView as PresBox;
}
// the presentation view document that renders this slide
@@ -56,7 +68,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
// Since this node is being rendered with a template, this method retrieves
// the actual slide being rendered from the auto-generated rendering template
@computed get slideDoc() {
- return this.props.TemplateDataDocument ?? this.props.Document;
+ return this._props.TemplateDataDocument ?? this.Document;
}
// this is the document in the workspaces that is targeted by the slide
@@ -91,9 +103,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
presExpandDocumentClick = () => (this.slideDoc.presentation_expandInlineButton = !this.slideDoc.presentation_expandInlineButton);
embedHeight = () => this.collapsedHeight + this.expandViewHeight;
- embedWidth = () => this.props.PanelWidth() / 2;
+ embedWidth = () => this._props.PanelWidth() / 2;
styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
- return property === StyleProp.Opacity ? 1 : this.props.styleProvider?.(doc, props, property);
+ return property === StyleProp.Opacity ? 1 : this._props.styleProvider?.(doc, props, property);
};
/**
* The function that is responsible for rendering a preview or not for this
@@ -106,19 +118,19 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
Document={PresBox.targetRenderedDoc(this.slideDoc)}
PanelWidth={this.embedWidth}
PanelHeight={this.embedHeight}
- isContentActive={this.props.isContentActive}
+ isContentActive={this._props.isContentActive}
styleProvider={this.styleProvider}
hideLinkButton={true}
ScreenToLocalTransform={Transform.Identity}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
docViewPath={returnEmptyDoclist}
- childFilters={this.props.childFilters}
- childFiltersByRanges={this.props.childFiltersByRanges}
- searchFilterDocs={this.props.searchFilterDocs}
+ childFilters={this._props.childFilters}
+ childFiltersByRanges={this._props.childFiltersByRanges}
+ searchFilterDocs={this._props.searchFilterDocs}
addDocument={returnFalse}
removeDocument={returnFalse}
fitContentsToBox={returnTrue}
- moveDocument={this.props.moveDocument!}
+ moveDocument={this._props.moveDocument!}
focus={emptyFunction}
whenChildContentsActiveChanged={returnFalse}
addDocTab={returnFalse}
@@ -191,8 +203,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
const dragArray = this.presBoxView?._dragArray ?? [];
const dragData = new DragManager.DocumentDragData(this.presBoxView?.sortArray() ?? []);
if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.slideDoc);
- dragData.treeViewDoc = this.presBox?._type_collection === CollectionViewType.Tree ? this.presBox : undefined; // this.props.DocumentView?.()?.props.treeViewDoc;
- dragData.moveDocument = this.props.moveDocument;
+ dragData.treeViewDoc = this.presBox?._type_collection === CollectionViewType.Tree ? this.presBox : undefined; // this._props.DocumentView?.()?._props.treeViewDoc;
+ dragData.moveDocument = this._props.moveDocument;
const dragItem: HTMLElement[] = [];
const classesToRestore = new Map<HTMLElement, string>();
if (dragArray.length === 1) {
@@ -265,8 +277,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
toggleProperties = () => {
- if (SettingsManager.propertiesWidth < 5) {
- SettingsManager.propertiesWidth = 250;
+ if (SettingsManager.Instance.propertiesWidth < 5) {
+ SettingsManager.Instance.propertiesWidth = 250;
}
};
@@ -276,7 +288,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (this.presBox && this.indexInPres < (this.presBoxView?.itemIndex || 0)) {
this.presBox.itemIndex = (this.presBoxView?.itemIndex || 0) - 1;
}
- this.props.removeDocument?.(this.slideDoc);
+ this._props.removeDocument?.(this.slideDoc);
this.presBoxView?.removeFromSelectedArray(this.slideDoc);
this.removeAllRecordingInOverlay();
}),
@@ -299,7 +311,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
* @param activeItem
*/
@undoBatch
- @action
updateCapturedContainerLayout = (presTargetDoc: Doc, activeItem: Doc) => {
const targetDoc = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc;
activeItem.config_x = NumCast(targetDoc.x);
@@ -389,7 +400,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
@undoBatch
- @action
lfg = (e: React.MouseEvent) => {
e.stopPropagation();
// TODO: fix this bug
@@ -404,7 +414,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
get toolbarWidth(): number {
const presBoxDocView = DocumentManager.Instance.getDocumentView(this.presBox);
const width = NumCast(this.presBox?._width);
- return presBoxDocView ? presBoxDocView.props.PanelWidth() : width ? width : 300;
+ return presBoxDocView ? presBoxDocView._props.PanelWidth() : width ? width : 300;
}
@computed get presButtons() {
@@ -453,8 +463,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
{!activeItem.presentation_groupWithUp
? 'Not grouped with previous slide (click to group)'
: activeItem.presentation_groupWithUp === 1
- ? 'Run simultaneously with previous slide (click again to run after)'
- : 'Run after previous slide (click to ungroup from previous)'}
+ ? 'Run simultaneously with previous slide (click again to run after)'
+ : 'Run after previous slide (click to ungroup from previous)'}
</div>
}>
<div
@@ -530,10 +540,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
style={{
backgroundColor: presColorBool ? (isSelected ? 'rgba(250,250,250,0.3)' : 'transparent') : isSelected ? Colors.LIGHT_BLUE : 'transparent',
opacity: this._dragging ? 0.3 : 1,
- paddingLeft: NumCast(this.layoutDoc._xPadding, this.props.xPadding),
- paddingRight: NumCast(this.layoutDoc._xPadding, this.props.xPadding),
- paddingTop: NumCast(this.layoutDoc._yPadding, this.props.yPadding),
- paddingBottom: NumCast(this.layoutDoc._yPadding, this.props.yPadding),
+ paddingLeft: NumCast(this.layoutDoc._xPadding, this._props.xPadding),
+ paddingRight: NumCast(this.layoutDoc._xPadding, this._props.xPadding),
+ paddingTop: NumCast(this.layoutDoc._yPadding, this._props.yPadding),
+ paddingBottom: NumCast(this.layoutDoc._yPadding, this._props.yPadding),
}}
onDoubleClick={action(e => {
this.toggleProperties();
@@ -552,7 +562,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
className={`presItem-slide ${isCurrent ? 'active' : ''}${this.slideDoc.runProcess ? ' testingv2' : ''}`}
style={{
display: 'infline-block',
- backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
+ backgroundColor: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor),
//layout_boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined,
border: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? presBoxColor + ' solid 2.5px' : undefined) : undefined,
}}>
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 2d8e9e2a2..3c26c307f 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -1,11 +1,11 @@
import * as React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, override, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { ColorResult } from 'react-color';
import { Doc, Opt } from '../../../fields/Doc';
-import { returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from '../../../Utils';
+import { copyProps, returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from '../../../Utils';
import { gptAPICall, GPTCallType } from '../../apis/gpt/GPT';
import { DocumentType } from '../../documents/DocumentTypes';
import { SelectionManager } from '../../util/SelectionManager';
@@ -23,6 +23,19 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
private _commentRef = React.createRef<HTMLDivElement>();
private _cropRef = React.createRef<HTMLDivElement>();
+ _prevProps: AntimodeMenuProps;
+ @override _props: AntimodeMenuProps;
+ constructor(props: AntimodeMenuProps) {
+ super(props);
+ this._props = this._prevProps = props;
+ makeObservable(this);
+ AnchorMenu.Instance = this;
+ AnchorMenu.Instance._canFade = false;
+ }
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
@observable private highlightColor: string = 'rgba(245, 230, 95, 0.616)';
@observable public Status: 'marquee' | 'annotation' | '' = '';
@@ -50,13 +63,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
return this._left > 0;
}
- constructor(props: Readonly<{}>) {
- super(props);
-
- AnchorMenu.Instance = this;
- AnchorMenu.Instance._canFade = false;
- }
-
componentWillUnmount() {
this._disposer?.();
}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index b2373b190..62df77c0a 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as Pdfjs from 'pdfjs-dist';
import 'pdfjs-dist/web/pdf_viewer.css';
@@ -8,7 +8,7 @@ import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnAll, returnFalse, returnNone, returnZero, smoothScroll, Utils } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, copyProps, emptyFunction, returnAll, returnFalse, returnNone, returnZero, smoothScroll, Utils } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
@@ -58,6 +58,17 @@ export class PDFViewer extends React.Component<IViewerProps> {
@observable private _showWaiting = true;
@observable private Index: number = -1;
+ _prevProps: IViewerProps;
+ @observable _props: IViewerProps;
+ constructor(props: IViewerProps) {
+ super(props);
+ this._prevProps = this._props = props;
+ makeObservable(this);
+ }
+ componentDidUpdate() {
+ copyProps(this);
+ }
+
private _pdfViewer: any;
private _styleRule: any; // stylesheet rule for making hyperlinks clickable
private _retries = 0; // number of times tried to create the PDF viewer
@@ -84,7 +95,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
@observable isAnnotating = false;
// key where data is stored
@computed get allAnnotations() {
- return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']), this.props.childFilters(), this.props.childFiltersByRanges());
+ return DocUtils.FilterDocs(DocListCast(this._props.dataDoc[this._props.fieldKey + '_annotations']), this._props.childFilters(), this._props.childFiltersByRanges());
}
@computed get inlineTextAnnotations() {
return this.allAnnotations.filter(a => a.text_inlineAnnotations);
@@ -96,22 +107,22 @@ export class PDFViewer extends React.Component<IViewerProps> {
this._mainCont.current?.addEventListener('scroll', e => ((e.target as any).scrollLeft = 0));
this._disposers.layout_autoHeight = reaction(
- () => this.props.layoutDoc._layout_autoHeight,
+ () => this._props.layoutDoc._layout_autoHeight,
layout_autoHeight => {
if (layout_autoHeight) {
- this.props.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + '_nativeHeight']);
- this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '_nativeHeight']) * (this.props.NativeDimScaling?.() || 1));
+ this._props.layoutDoc._nativeHeight = NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']);
+ this._props.setHeight?.(NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
}
}
);
this._disposers.selected = reaction(
- () => this.props.isSelected(),
+ () => this._props.isSelected(),
selected => SelectionManager.Views().length === 1 && this.setupPdfJsViewer(),
{ fireImmediately: true }
);
this._disposers.curPage = reaction(
- () => Cast(this.props.Document._layout_curPage, 'number', null),
+ () => Cast(this._props.Document._layout_curPage, 'number', null),
page => page !== undefined && page !== this._pdfViewer?.currentPageNumber && this.gotoPage(page),
{ fireImmediately: true }
);
@@ -123,7 +134,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
};
copy = (e: ClipboardEvent) => {
- if (this.props.isContentActive() && e.clipboardData) {
+ if (this._props.isContentActive() && e.clipboardData) {
e.clipboardData.setData('text/plain', this._selectionText);
const anchor = this._getAnchor(undefined, false);
if (anchor) {
@@ -139,18 +150,18 @@ export class PDFViewer extends React.Component<IViewerProps> {
@action
initialLoad = async () => {
if (this._pageSizes.length === 0) {
- this._pageSizes = Array<{ width: number; height: number }>(this.props.pdf.numPages);
+ this._pageSizes = Array<{ width: number; height: number }>(this._props.pdf.numPages);
await Promise.all(
this._pageSizes.map((val, i) =>
- this.props.pdf.getPage(i + 1).then(
+ this._props.pdf.getPage(i + 1).then(
action((page: Pdfjs.PDFPageProxy) => {
const page0or180 = page.rotate === 0 || page.rotate === 180;
this._pageSizes.splice(i, 1, {
width: page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1],
height: page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0],
});
- if (i === this.props.pdf.numPages - 1) {
- this.props.loaded?.(page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1], page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], this.props.pdf.numPages);
+ if (i === this._props.pdf.numPages - 1) {
+ this._props.loaded?.(page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1], page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], this._props.pdf.numPages);
}
})
)
@@ -167,27 +178,27 @@ export class PDFViewer extends React.Component<IViewerProps> {
scrollFocus = (doc: Doc, scrollTop: number, options: DocFocusOptions) => {
const mainCont = this._mainCont.current;
let focusSpeed: Opt<number>;
- if (doc !== this.props.Document && mainCont) {
- const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
- const scrollTo = Utils.scrollIntoView(scrollTop, doc[Height](), NumCast(this.props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
- if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._layout_scrollTop) {
+ if (doc !== this._props.Document && mainCont) {
+ const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
+ const scrollTo = Utils.scrollIntoView(scrollTop, doc[Height](), NumCast(this._props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
+ if (scrollTo !== undefined && scrollTo !== this._props.layoutDoc._layout_scrollTop) {
if (!this._pdfViewer) this._initialScroll = { loc: scrollTo, easeFunc: options.easeFunc };
else if (!options.instant) this._scrollStopper = smoothScroll((focusSpeed = options.zoomTime ?? 500), mainCont, scrollTo, options.easeFunc, this._scrollStopper);
else this._mainCont.current?.scrollTo({ top: Math.abs(scrollTo || 0) });
}
} else {
- this._initialScroll = { loc: NumCast(this.props.layoutDoc._layout_scrollTop), easeFunc: options.easeFunc };
+ this._initialScroll = { loc: NumCast(this._props.layoutDoc._layout_scrollTop), easeFunc: options.easeFunc };
}
return focusSpeed;
};
- crop = (region: Doc | undefined, addCrop?: boolean) => this.props.crop(region, addCrop);
+ crop = (region: Doc | undefined, addCrop?: boolean) => this._props.crop(region, addCrop);
@action
setupPdfJsViewer = async () => {
if (this._viewerIsSetup) return;
this._viewerIsSetup = true;
this._showWaiting = true;
- this.props.setPdfViewer(this);
+ this._props.setPdfViewer(this);
await this.initialLoad();
this.createPdfViewer();
@@ -195,22 +206,22 @@ export class PDFViewer extends React.Component<IViewerProps> {
pagesinit = () => {
if (this._pdfViewer._setDocumentViewerElement?.offsetParent) {
- runInAction(() => (this._pdfViewer.currentScaleValue = this.props.layoutDoc._freeform_scale = 1));
- this.gotoPage(NumCast(this.props.Document._layout_curPage, 1));
+ runInAction(() => (this._pdfViewer.currentScaleValue = this._props.layoutDoc._freeform_scale = 1));
+ this.gotoPage(NumCast(this._props.Document._layout_curPage, 1));
}
document.removeEventListener('pagesinit', this.pagesinit);
var quickScroll: { loc?: string; easeFunc?: 'ease' | 'linear' } | undefined = { loc: this._initialScroll ? this._initialScroll.loc?.toString() : '', easeFunc: this._initialScroll ? this._initialScroll.easeFunc : undefined };
this._disposers.scale = reaction(
- () => NumCast(this.props.layoutDoc._freeform_scale, 1),
+ () => NumCast(this._props.layoutDoc._freeform_scale, 1),
scale => (this._pdfViewer.currentScaleValue = scale),
{ fireImmediately: true }
);
this._disposers.scroll = reaction(
- () => Math.abs(NumCast(this.props.Document._layout_scrollTop)),
+ () => Math.abs(NumCast(this._props.Document._layout_scrollTop)),
pos => {
if (!this._ignoreScroll) {
this._showWaiting && this.setupPdfJsViewer();
- const viewTrans = quickScroll?.loc ?? StrCast(this.props.Document._viewTransition);
+ const viewTrans = quickScroll?.loc ?? StrCast(this._props.Document._viewTransition);
const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
const durationSecStr = viewTrans.match(/([0-9.]*)s/);
const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
@@ -268,8 +279,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
eventBus,
});
pdfLinkService.setViewer(this._pdfViewer);
- pdfLinkService.setDocument(this.props.pdf, null);
- this._pdfViewer.setDocument(this.props.pdf);
+ pdfLinkService.setDocument(this._props.pdf, null);
+ this._pdfViewer.setDocument(this._props.pdf);
}
@action
@@ -303,12 +314,12 @@ export class PDFViewer extends React.Component<IViewerProps> {
if (this._mainCont.current && !this._forcedScroll) {
this._ignoreScroll = true; // the pdf scrolled, so we need to tell the Doc to scroll but we don't want the doc to then try to set the PDF scroll pos (which would interfere with the smooth scroll animation)
if (!LinkDocPreview.LinkInfo) {
- this.props.layoutDoc._layout_scrollTop = this._mainCont.current.scrollTop;
+ this._props.layoutDoc._layout_scrollTop = this._mainCont.current.scrollTop;
}
this._ignoreScroll = false;
if (this._scrollTimer) clearTimeout(this._scrollTimer); // wait until a scrolling pause, then create an anchor to audio
this._scrollTimer = setTimeout(() => {
- DocUtils.MakeLinkToActiveAudio(() => this.props.DocumentView?.().ComponentView?.getAnchor!(true)!, false);
+ DocUtils.MakeLinkToActiveAudio(() => this._props.DocumentView?.().ComponentView?.getAnchor!(true)!, false);
this._scrollTimer = undefined;
}, 200);
}
@@ -358,12 +369,12 @@ export class PDFViewer extends React.Component<IViewerProps> {
// if alt+left click, drag and annotate
this._downX = e.clientX;
this._downY = e.clientY;
- if ((this.props.Document._freeform_scale || 1) !== 1) return;
- if ((e.button !== 0 || e.altKey) && this.props.isContentActive(true)) {
- this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this.props.Document);
+ if ((this._props.Document._freeform_scale || 1) !== 1) return;
+ if ((e.button !== 0 || e.altKey) && this._props.isContentActive(true)) {
+ this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this._props.Document);
}
- if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
- this.props.select(false);
+ if (!e.altKey && e.button === 0 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ this._props.select(false);
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
this.isAnnotating = true;
@@ -393,7 +404,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
this._getAnchor = AnchorMenu.Instance?.GetAnchor;
this.isAnnotating = false;
clearStyleSheetRules(PDFViewer._annotationStyle);
- this.props.select(false);
+ this._props.select(false);
document.removeEventListener('pointerup', this.onSelectEnd);
const sel = window.getSelection();
@@ -408,21 +419,21 @@ export class PDFViewer extends React.Component<IViewerProps> {
// Changing which document to add the annotation to (the currently selected PDF)
GPTPopup.Instance.setSidebarId('data_sidebar');
- GPTPopup.Instance.addDoc = this.props.sidebarAddDoc;
+ GPTPopup.Instance.addDoc = this._props.sidebarAddDoc;
};
@action
createTextAnnotation = (sel: Selection, selRange: Range) => {
if (this._mainCont.current) {
- this._mainCont.current.style.transform = `rotate(${NumCast(this.props.DocumentView!().screenToLocalTransform().RotateDeg)}deg)`;
+ this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToLocalTransform().RotateDeg)}deg)`;
const boundingRect = this._mainCont.current.getBoundingClientRect();
const clientRects = selRange.getClientRects();
for (let i = 0; i < clientRects.length; i++) {
const rect = clientRects.item(i);
- if (rect && rect?.width && rect.width < this._mainCont.current.clientWidth / this.props.ScreenToLocalTransform().Scale) {
+ if (rect && rect?.width && rect.width < this._mainCont.current.clientWidth / this._props.ScreenToLocalTransform().Scale) {
const scaleX = this._mainCont.current.offsetWidth / boundingRect.width;
const scaleY = this._mainCont.current.offsetHeight / boundingRect.height;
- const pdfScale = NumCast(this.props.layoutDoc._freeform_scale, 1);
+ const pdfScale = NumCast(this._props.layoutDoc._freeform_scale, 1);
const annoBox = document.createElement('div');
annoBox.className = 'marqueeAnnotator-annotationBox';
// transforms the positions from screen onto the pdf div
@@ -451,7 +462,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
onClick = (e: React.MouseEvent) => {
this._scrollStopper?.();
if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
- this._setPreviewCursor(e.clientX, e.clientY, false, false, this.props.Document);
+ this._setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document);
}
// e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks
};
@@ -460,48 +471,48 @@ export class PDFViewer extends React.Component<IViewerProps> {
@action
onZoomWheel = (e: React.WheelEvent) => {
- if (this.props.isContentActive(true)) {
+ if (this._props.isContentActive(true)) {
e.stopPropagation();
if (e.ctrlKey) {
const curScale = Number(this._pdfViewer.currentScaleValue);
this._pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale - (curScale * e.deltaY) / 1000));
- this.props.layoutDoc._freeform_scale = Number(this._pdfViewer.currentScaleValue);
+ this._props.layoutDoc._freeform_scale = Number(this._pdfViewer.currentScaleValue);
}
}
};
pointerEvents = () =>
- this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown()
+ this._props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown()
? 'all' //
: 'none';
@computed get annotationLayer() {
const inlineAnnos = this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).filter(anno => !anno.hidden);
return (
- <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${NumCast(this.props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
+ <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this._props.Document), transform: `scale(${NumCast(this._props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
{inlineAnnos.map(anno => (
- <Annotation {...this.props} fieldKey={this.props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
+ <Annotation {...this._props} fieldKey={this._props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} dataDoc={this._props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
))}
</div>
);
}
getScrollHeight = () => this._scrollHeight;
- scrollXf = () => (this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.layoutDoc._layout_scrollTop)) : this.props.ScreenToLocalTransform());
- overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._freeform_scale, 1));
- panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1);
- panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
- transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter];
- opaqueFilter = () => [...this.props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.GetCanEmbed() && this.props.isContentActive() ? [] : [Utils.OpaqueBackgroundFilter])];
+ scrollXf = () => (this._mainCont.current ? this._props.ScreenToLocalTransform().translate(0, NumCast(this._props.layoutDoc._layout_scrollTop)) : this._props.ScreenToLocalTransform());
+ overlayTransform = () => this.scrollXf().scale(1 / NumCast(this._props.layoutDoc._freeform_scale, 1));
+ panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1);
+ panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
+ transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
+ opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.GetCanEmbed() && this._props.isContentActive() ? [] : [Utils.OpaqueBackgroundFilter])];
childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
if (doc instanceof Doc && property === StyleProp.PointerEvents) {
- if (this.inlineTextAnnotations.includes(doc) || this.props.isContentActive() === false) return 'none';
+ if (this.inlineTextAnnotations.includes(doc) || this._props.isContentActive() === false) return 'none';
const isInk = doc.layout_isSvg && !props?.LayoutTemplateString;
return isInk ? 'visiblePainted' : 'all';
}
- return this.props.styleProvider?.(doc, props, property);
+ return this._props.styleProvider?.(doc, props, property);
};
- childPointerEvents = () => (this.props.isContentActive() !== false ? 'all' : 'none');
+ childPointerEvents = () => (this._props.isContentActive() !== false ? 'all' : 'none');
renderAnnotations = (childFilters: () => string[], mixBlendMode?: any, display?: string) => (
<div
className="pdfViewerDash-overlay"
@@ -511,15 +522,15 @@ export class PDFViewer extends React.Component<IViewerProps> {
pointerEvents: Doc.ActiveTool !== InkTool.None ? 'all' : undefined,
}}>
<CollectionFreeFormView
- {...this.props}
+ {...this._props}
NativeWidth={returnZero}
NativeHeight={returnZero}
setContentView={emptyFunction} // override setContentView to do nothing
- pointerEvents={this.props.isContentActive() && (SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it.
+ pointerEvents={this._props.isContentActive() && (SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it.
childPointerEvents={this.childPointerEvents} // but freeform children need to get events to allow text editing, etc
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
isAnnotationOverlay={true}
- fieldKey={this.props.fieldKey + '_annotations'}
+ fieldKey={this._props.fieldKey + '_annotations'}
getScrollHeight={this.getScrollHeight}
setPreviewCursor={this.setPreviewCursor}
PanelHeight={this.panelHeight}
@@ -535,39 +546,39 @@ export class PDFViewer extends React.Component<IViewerProps> {
</div>
);
@computed get overlayTransparentAnnotations() {
- const transparentChildren = DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']), this.transparentFilter(), []);
- return !transparentChildren.length ? null : this.renderAnnotations(this.transparentFilter, 'multiply', SnappingManager.GetCanEmbed() && this.props.isContentActive() ? 'none' : undefined);
+ const transparentChildren = DocUtils.FilterDocs(DocListCast(this._props.dataDoc[this._props.fieldKey + '_annotations']), this.transparentFilter(), []);
+ return !transparentChildren.length ? null : this.renderAnnotations(this.transparentFilter, 'multiply', SnappingManager.GetCanEmbed() && this._props.isContentActive() ? 'none' : undefined);
}
@computed get overlayOpaqueAnnotations() {
return this.renderAnnotations(this.opaqueFilter, this.allAnnotations.some(anno => anno.mixBlendMode) ? 'hard-light' : undefined);
}
@computed get overlayLayer() {
return (
- <div style={{ pointerEvents: this.props.isContentActive() && SnappingManager.GetIsDragging() ? 'all' : 'none' }}>
+ <div style={{ pointerEvents: this._props.isContentActive() && SnappingManager.GetIsDragging() ? 'all' : 'none' }}>
{this.overlayTransparentAnnotations}
{this.overlayOpaqueAnnotations}
</div>
);
}
@computed get pdfViewerDiv() {
- return <div className={'pdfViewerDash-text' + (this.props.pointerEvents?.() !== 'none' && this._textSelecting && this.props.isContentActive() ? '-selected' : '')} ref={this._viewer} />;
+ return <div className={'pdfViewerDash-text' + (this._props.pointerEvents?.() !== 'none' && this._textSelecting && this._props.isContentActive() ? '-selected' : '')} ref={this._viewer} />;
}
savedAnnotations = () => this._savedAnnotations;
- addDocumentWrapper = (doc: Doc | Doc[]) => this.props.addDocument!(doc);
+ addDocumentWrapper = (doc: Doc | Doc[]) => this._props.addDocument!(doc);
render() {
TraceMobx();
return (
<div className="pdfViewer-content">
<div
- className={`pdfViewerDash${this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' ? '-interactive' : ''}`}
+ className={`pdfViewerDash${this._props.isContentActive() && this._props.pointerEvents?.() !== 'none' ? '-interactive' : ''}`}
ref={this._mainCont}
onScroll={this.onScroll}
onWheel={this.onZoomWheel}
onPointerDown={this.onPointerDown}
onClick={this.onClick}
style={{
- overflowX: NumCast(this.props.layoutDoc._freeform_scale, 1) !== 1 ? 'scroll' : undefined,
- height: !this.props.Document._layout_fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this.props.Document) : `100%`,
+ overflowX: NumCast(this._props.layoutDoc._freeform_scale, 1) !== 1 ? 'scroll' : undefined,
+ height: !this._props.Document._layout_fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this._props.Document) : `100%`,
}}>
{this.pdfViewerDiv}
{this.annotationLayer}
@@ -576,13 +587,13 @@ export class PDFViewer extends React.Component<IViewerProps> {
{!this._mainCont.current || !this._annotationLayer.current ? null : (
<MarqueeAnnotator
ref={this._marqueeref}
- Document={this.props.Document}
+ Document={this._props.Document}
getPageFromScroll={this.getPageFromScroll}
- anchorMenuClick={this.props.anchorMenuClick}
+ anchorMenuClick={this._props.anchorMenuClick}
scrollTop={0}
- annotationLayerScrollTop={NumCast(this.props.Document._layout_scrollTop)}
+ annotationLayerScrollTop={NumCast(this._props.Document._layout_scrollTop)}
addDocument={this.addDocumentWrapper}
- docView={this.props.DocumentView!}
+ docView={this._props.DocumentView!}
finishMarquee={this.finishMarquee}
savedAnnotations={this.savedAnnotations}
selectionText={this.selectionText}
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 7f71ee678..d1c039d95 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -19,7 +19,6 @@ import { SharingManager } from '../../util/SharingManager';
import { Transform } from '../../util/Transform';
import { CollectionDockingView } from '../collections/CollectionDockingView';
import { CollectionLinearView } from '../collections/collectionLinear';
-import { ContextMenu } from '../ContextMenu';
import { DashboardView } from '../DashboardView';
import { Colors } from '../global/globalEnums';
import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 6fb97d70c..5449c8dea 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1,5 +1,5 @@
import { saveAs } from 'file-saver';
-import { action, computed, observable, ObservableMap, ObservableSet, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, ObservableMap, ObservableSet, runInAction } from 'mobx';
import { computedFn } from 'mobx-utils';
import { alias, map, serializable } from 'serializr';
import { DocServer } from '../client/DocServer';
@@ -131,9 +131,9 @@ export function updateCachedAcls(doc: Doc) {
@Deserializable('Doc', updateCachedAcls, ['id'])
export class Doc extends RefField {
@observable public static RecordingEvent = 0;
- @observable public static GuestDashboard: Doc | undefined;
- @observable public static GuestTarget: Doc | undefined;
- @observable public static GuestMobile: Doc | undefined;
+ @observable public static GuestDashboard: Doc | undefined = undefined;
+ @observable public static GuestTarget: Doc | undefined = undefined;
+ @observable public static GuestMobile: Doc | undefined = undefined;
public static CurrentUserEmail: string = '';
public static get MySharedDocs() { return DocCast(Doc.UserDoc().mySharedDocs); } // prettier-ignore
@@ -178,6 +178,7 @@ export class Doc extends RefField {
constructor(id?: FieldId, forceSave?: boolean) {
super(id);
+ makeObservable(this);
const docProxy = new Proxy<this>(this, {
set: setter,
get: getter,
@@ -185,7 +186,36 @@ export class Doc extends RefField {
has: (target, key) => GetEffectiveAcl(target) !== AclPrivate && key in target.__fieldTuples,
ownKeys: target => {
const keys = GetEffectiveAcl(target) !== AclPrivate ? Object.keys(target[FieldKeys]) : [];
- return [...keys, '__LAYOUT__'];
+ return [
+ ...keys,
+ AclAdmin,
+ AclAugment,
+ AclEdit,
+ AclPrivate,
+ AclReadonly,
+ Animation,
+ AudioPlay,
+ Brushed,
+ CachedUpdates,
+ DirectLinks,
+ DocAcl,
+ DocCss,
+ DocData,
+ DocFields,
+ DocLayout,
+ DocViews,
+ FieldKeys,
+ FieldTuples,
+ ForceServerWrite,
+ Height,
+ Highlight,
+ Initializing,
+ Self,
+ SelfProxy,
+ UpdatingFromServer,
+ Width,
+ '__LAYOUT__',
+ ];
},
getOwnPropertyDescriptor: (target, prop) => {
if (prop.toString() === '__LAYOUT__' || !(prop in target[FieldKeys])) {
diff --git a/src/fields/List.ts b/src/fields/List.ts
index da007e972..8c8ff1ea3 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
import { alias, list, serializable } from 'serializr';
import { DocServer } from '../client/DocServer';
import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
@@ -233,12 +233,13 @@ class ListImpl<T extends Field> extends ObjectField {
}
constructor(fields?: T[]) {
super();
+ makeObservable(this);
const list = new Proxy<this>(this, {
set: setter,
get: ListImpl.listGetter,
ownKeys: target => {
const keys = Object.keys(target.__fieldTuples);
- return [...keys, '__realFields'];
+ return [...keys, FieldTuples, Self, SelfProxy, '__realFields'];
},
getOwnPropertyDescriptor: (target, prop) => {
if (prop in target[FieldTuples]) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index ca02284da..545fe4478 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -21,7 +21,7 @@ function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
}
-const tracing = false;
+var tracing = false;
export function TraceMobx() {
tracing && trace();
}
diff --git a/src/server/ApiManagers/GooglePhotosManager.ts b/src/server/ApiManagers/GooglePhotosManager.ts
index c0da39a03..4c2004681 100644
--- a/src/server/ApiManagers/GooglePhotosManager.ts
+++ b/src/server/ApiManagers/GooglePhotosManager.ts
@@ -1,23 +1,23 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method, _error, _success, _invalid } from "../RouteManager";
-import * as path from "path";
-import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils";
-import { BatchedArray, TimeUnit } from "array-batcher";
-import { Opt } from "../../fields/Doc";
-import { DashUploadUtils, InjectSize, SizeSuffix } from "../DashUploadUtils";
-import { Database } from "../database";
-import { red } from "colors";
-import { Upload } from "../SharedMediaTypes";
+import ApiManager, { Registration } from './ApiManager';
+import { Method, _error, _success, _invalid } from '../RouteManager';
+import * as path from 'path';
+import { GoogleApiServerUtils } from '../apis/google/GoogleApiServerUtils';
+import { BatchedArray, TimeUnit } from 'array-batcher';
+import { Opt } from '../../fields/Doc';
+import { DashUploadUtils, InjectSize, SizeSuffix } from '../DashUploadUtils';
+import { Database } from '../database';
+import { red } from 'colors';
+import { Upload } from '../SharedMediaTypes';
import * as request from 'request-promise';
-import { NewMediaItemResult } from "../apis/google/SharedTypes";
+import { NewMediaItemResult } from '../apis/google/SharedTypes';
-const prefix = "google_photos_";
+const prefix = 'google_photos_';
const remoteUploadError = "None of the preliminary uploads to Google's servers was successful.";
-const authenticationError = "Unable to authenticate Google credentials before uploading to Google Photos!";
-const mediaError = "Unable to convert all uploaded bytes to media items!";
+const authenticationError = 'Unable to authenticate Google credentials before uploading to Google Photos!';
+const mediaError = 'Unable to convert all uploaded bytes to media items!';
const localUploadError = (count: number) => `Unable to upload ${count} images to Dash's server`;
const requestError = "Unable to execute download: the body's media items were malformed.";
-const downloadError = "Encountered an error while executing downloads.";
+const downloadError = 'Encountered an error while executing downloads.';
interface GooglePhotosUploadFailure {
batch: number;
@@ -41,17 +41,15 @@ interface NewMediaItem {
* This manager handles the creation of routes for google photos functionality.
*/
export default class GooglePhotosManager extends ApiManager {
-
protected initialize(register: Registration): void {
-
/**
* This route receives a list of urls that point to images stored
* on Dash's file system, and, in a two step process, uploads them to Google's servers and
- * returns the information Google generates about the associated uploaded remote images.
+ * returns the information Google generates about the associated uploaded remote images.
*/
register({
method: Method.POST,
- subscription: "/googlePhotosMediaPost",
+ subscription: '/googlePhotosMediaPost',
secureHandler: async ({ user, req, res }) => {
const { media } = req.body;
@@ -67,38 +65,35 @@ export default class GooglePhotosManager extends ApiManager {
const failed: GooglePhotosUploadFailure[] = [];
const batched = BatchedArray.from<Uploader.UploadSource>(media, { batchSize: 25 });
const interval = { magnitude: 100, unit: TimeUnit.Milliseconds };
- const newMediaItems = await batched.batchedMapPatientInterval<NewMediaItem>(
- interval,
- async (batch, collector, { completedBatches }) => {
- for (let index = 0; index < batch.length; index++) {
- const { url, description } = batch[index];
- // a local function used to record failure of an upload
- const fail = (reason: string) => failed.push({ reason, batch: completedBatches + 1, index, url });
- // see image resizing - we store the size-agnostic url in our logic, but write out size-suffixed images to the file system
- // so here, given a size agnostic url, we're just making that conversion so that the file system knows which bytes to actually upload
- const imageToUpload = InjectSize(url, SizeSuffix.Original);
- // STEP 1/2: send the raw bytes of the image from our server to Google's servers. We'll get back an upload token
- // which acts as a pointer to those bytes that we can use to locate them later on
- const uploadToken = await Uploader.SendBytes(token, imageToUpload).catch(fail);
- if (!uploadToken) {
- fail(`${path.extname(url)} is not an accepted extension`);
- } else {
- // gather the upload token return from Google (a pointer they give us to the raw, currently useless bytes
- // we've uploaded to their servers) and put in the JSON format that the API accepts for image creation (used soon, below)
- collector.push({
- description,
- simpleMediaItem: { uploadToken }
- });
- }
+ const newMediaItems = await batched.batchedMapPatientInterval<NewMediaItem>(interval, async (batch, collector, { completedBatches }) => {
+ for (let index = 0; index < batch.length; index++) {
+ const { url, description } = batch[index];
+ // a local function used to record failure of an upload
+ const fail = (reason: string) => failed.push({ reason, batch: completedBatches + 1, index, url });
+ // see image resizing - we store the size-agnostic url in our logic, but write out size-suffixed images to the file system
+ // so here, given a size agnostic url, we're just making that conversion so that the file system knows which bytes to actually upload
+ const imageToUpload = InjectSize(url, SizeSuffix.Original);
+ // STEP 1/2: send the raw bytes of the image from our server to Google's servers. We'll get back an upload token
+ // which acts as a pointer to those bytes that we can use to locate them later on
+ const uploadToken = await Uploader.SendBytes(token, imageToUpload).catch(fail);
+ if (!uploadToken) {
+ fail(`${path.extname(url)} is not an accepted extension`);
+ } else {
+ // gather the upload token return from Google (a pointer they give us to the raw, currently useless bytes
+ // we've uploaded to their servers) and put in the JSON format that the API accepts for image creation (used soon, below)
+ collector.push({
+ description,
+ simpleMediaItem: { uploadToken },
+ });
}
}
- );
+ });
// inform the developer / server console of any failed upload attempts
// does not abort the operation, since some subset of the uploads may have been successful
const { length } = failed;
if (length) {
- console.error(`Unable to upload ${length} image${length === 1 ? "" : "s"} to Google's servers`);
+ console.error(`Unable to upload ${length} image${length === 1 ? '' : 's'} to Google's servers`);
console.log(failed.map(({ reason, batch, index, url }) => `@${batch}.${index}: ${url} failed:\n${reason}`).join('\n\n'));
}
@@ -115,7 +110,7 @@ export default class GooglePhotosManager extends ApiManager {
results => _success(res, { results, failed }),
error => _error(res, mediaError, error)
);
- }
+ },
});
/**
@@ -128,11 +123,11 @@ export default class GooglePhotosManager extends ApiManager {
* since the same bytes on their server might now be associated with a new, random url.
* So, we do the next best thing and try to use an intrinsic attribute of those bytes as
* an identifier: the precise content size. This works in small cases, but has the obvious flaw of failing to upload
- * an image locally if we already have uploaded another Google user content image with the exact same content size.
+ * an image locally if we already have uploaded another Google user content image with the exact same content size.
*/
register({
method: Method.POST,
- subscription: "/googlePhotosMediaGet",
+ subscription: '/googlePhotosMediaGet',
secureHandler: async ({ req, res }) => {
const { mediaItems } = req.body as { mediaItems: MediaItem[] };
if (!mediaItems) {
@@ -167,7 +162,7 @@ export default class GooglePhotosManager extends ApiManager {
failed++;
}
} else {
- // if we have, the variable 'found' is handily the upload information of the
+ // if we have, the variable 'found' is handily the upload information of the
// existing image, so we add it to the list as if we had just uploaded it now without actually
// making a duplicate write
completed.push(found);
@@ -180,9 +175,8 @@ export default class GooglePhotosManager extends ApiManager {
// otherwise, return the image upload information list corresponding to the newly (or previously)
// uploaded images
_success(res, completed);
- }
+ },
});
-
}
}
@@ -192,11 +186,10 @@ export default class GooglePhotosManager extends ApiManager {
* and then initialize / create those images in the Photos
* API given the upload tokens returned from the initial
* uploading process.
- *
+ *
* https://developers.google.com/photos/library/reference/rest/v1/mediaItems/batchCreate
*/
export namespace Uploader {
-
/**
* Specifies the structure of the object
* necessary to upload bytes to Google's servers.
@@ -214,7 +207,7 @@ export namespace Uploader {
* into the BatchCreate API request
* to take a reference to raw uploaded bytes
* and actually create an image in Google Photos.
- *
+ *
* So, to instantiate this interface you must have already dispatched an upload
* and received an upload token.
*/
@@ -245,7 +238,7 @@ export namespace Uploader {
function headers(type: string, token: string) {
return {
'Content-Type': `application/${type}`,
- 'Authorization': `Bearer ${token}`,
+ Authorization: `Bearer ${token}`,
};
}
@@ -272,17 +265,19 @@ export namespace Uploader {
headers: {
...headers('octet-stream', bearerToken),
'X-Goog-Upload-File-Name': filename || path.basename(url),
- 'X-Goog-Upload-Protocol': 'raw'
+ 'X-Goog-Upload-Protocol': 'raw',
},
- body
+ body,
};
- return new Promise((resolve, reject) => request(parameters, (error, _response, body) => {
- if (error) {
- // on rejection, the server logs the error and the offending image
- return reject(error);
- }
- resolve(body);
- }));
+ return new Promise((resolve, reject) =>
+ request(parameters, (error, _response, body) => {
+ if (error) {
+ // on rejection, the server logs the error and the offending image
+ return reject(error);
+ }
+ resolve(body);
+ })
+ );
};
/**
@@ -303,19 +298,18 @@ export namespace Uploader {
// seems to need at least some latency between requests (spamming it synchronously has led to the server returning errors)...
const batched = BatchedArray.from(newMediaItems, { batchSize: 50 });
// ...so we execute them in delayed batches and await the entire execution
- return batched.batchedMapPatientInterval(
- { magnitude: 100, unit: TimeUnit.Milliseconds },
- async (batch: NewMediaItem[], collector): Promise<void> => {
- const parameters = {
- method: 'POST',
- headers: headers('json', bearerToken),
- uri: prepend('mediaItems:batchCreate'),
- body: { newMediaItems: batch } as any,
- json: true
- };
- // register the target album, if provided
- album && (parameters.body.albumId = album.id);
- collector.push(...(await new Promise<NewMediaItemResult[]>((resolve, reject) => {
+ return batched.batchedMapPatientInterval({ magnitude: 100, unit: TimeUnit.Milliseconds }, async (batch: NewMediaItem[], collector): Promise<void> => {
+ const parameters = {
+ method: 'POST',
+ headers: headers('json', bearerToken),
+ uri: prepend('mediaItems:batchCreate'),
+ body: { newMediaItems: batch } as any,
+ json: true,
+ };
+ // register the target album, if provided
+ album && (parameters.body.albumId = album.id);
+ collector.push(
+ ...(await new Promise<NewMediaItemResult[]>((resolve, reject) => {
request(parameters, (error, _response, body) => {
if (error) {
reject(error);
@@ -323,9 +317,8 @@ export namespace Uploader {
resolve(body.newMediaItemResults);
}
});
- })));
- }
- );
+ }))
+ );
+ });
};
-
-} \ No newline at end of file
+}
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 7765349ff..dd2a857d9 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -25,6 +25,7 @@ const ffmpeg = require('fluent-ffmpeg');
const fs = require('fs');
const requestImageSize = require('../client/util/request-image-size');
const md5File = require('md5-file');
+const autorotate = require('jpeg-autorotate');
export enum SizeSuffix {
Small = '_s',
@@ -604,17 +605,21 @@ export namespace DashUploadUtils {
sizes.filter(({ width }) => !width).map(({ suffix }) =>
new Promise<void>(res => createReadStream(sourcePath).pipe(createWriteStream(outputPath(suffix))).on('close', res))
)); // prettier-ignore
- return Jimp.read(sourcePath)
- .then(async img => {
- await Promise.all( sizes.filter(({ width }) => width) .map(({ width, suffix }) =>
- img = img.resize(width, Jimp.AUTO).write(outputPath(suffix))
- )); // prettier-ignore
- return writtenFiles;
- })
- .catch(e => {
- console.log('ERROR' + e);
- return writtenFiles;
- });
+
+ const fileIn = fs.readFileSync(sourcePath);
+ return autorotate.rotate(fileIn, { quality: 30 }).then(({ buffer }: { buffer: any }) =>
+ Jimp.read(buffer)
+ .then(async img => {
+ await Promise.all( sizes.filter(({ width }) => width) .map(({ width, suffix }) =>
+ img = img.resize(width, Jimp.AUTO).write(outputPath(suffix))
+ )); // prettier-ignore
+ return writtenFiles;
+ })
+ .catch(e => {
+ console.log('ERROR' + e);
+ return writtenFiles;
+ })
+ );
}
/**