aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/MainView.tsx7
-rw-r--r--src/client/views/collections/TreeView.tsx16
-rw-r--r--src/client/views/nodes/DocumentView.tsx74
-rw-r--r--src/client/views/nodes/button/FontIconBox.tsx12
-rw-r--r--src/fields/Doc.ts4
-rw-r--r--src/fields/List.ts8
-rw-r--r--src/fields/Proxy.ts79
7 files changed, 102 insertions, 98 deletions
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 4dc1ebd99..052846e71 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -841,15 +841,12 @@ export class MainView extends React.Component {
}
expandFlyout = action((button: Doc) => {
- // bcz: What's going on here!?
+ // bcz: What's going on here!? --- may be fixed now, so commenting out ...
// Chrome(not firefox) seems to have a bug when the flyout expands and there's a zoomed freeform tab. All of the div below the CollectionFreeFormView's main div
// generate the wrong value from getClientRectangle() -- specifically they return an 'x' that is the flyout's width greater than it should be.
// interactively adjusting the flyout fixes the problem. So does programmatically changing the value after a timeout to something *fractionally* different (ie, 1.5, not 1);)
this._leftMenuFlyoutWidth = this._leftMenuFlyoutWidth || 250;
- setTimeout(
- action(() => (this._leftMenuFlyoutWidth += 0.5)),
- 0
- );
+ //setTimeout(action(() => (this._leftMenuFlyoutWidth += 0.5)));
this._sidebarContent.proto = button.target as any;
this.LastButton = button;
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 1e97eee37..ac8562d5a 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -55,7 +55,7 @@ export interface TreeViewProps {
indentDocument?: (editTitle: boolean) => void;
outdentDocument?: (editTitle: boolean) => void;
ScreenToLocalTransform: () => Transform;
- contextMenuItems: { script: ScriptField; filter: ScriptField; icon: string; label: string }[];
+ contextMenuItems?: { script: ScriptField; filter: ScriptField; icon: string; label: string }[];
dontRegisterView?: boolean;
styleProvider?: StyleProviderFunc | undefined;
treeViewHideHeaderFields: () => boolean;
@@ -302,7 +302,7 @@ export class TreeView extends React.Component<TreeViewProps> {
const pt = [e.clientX, e.clientY];
const rect = this._header.current!.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
- const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length);
+ const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length);
this._header.current!.className = 'treeView-header';
if (!this.props.treeView.outlineMode || DragManager.DocDragData?.treeViewDoc === this.props.treeView.rootDoc) {
if (inside) this._header.current!.className += ' treeView-header-inside';
@@ -362,7 +362,7 @@ export class TreeView extends React.Component<TreeViewProps> {
if (!this._header.current) return;
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] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length);
+ const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
if (de.complete.linkDragData) {
const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor();
const destDoc = this.doc;
@@ -401,7 +401,6 @@ export class TreeView extends React.Component<TreeViewProps> {
getTransform = () => this.refTransform(this._tref.current);
embeddedPanelWidth = () => this.props.panelWidth() / (this.props.treeView.props.NativeDimScaling?.() || 1);
embeddedPanelHeight = () => {
- console.log(this.props.treeView.rootDoc.title);
const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
return Math.min(
layoutDoc[HeightSym](),
@@ -509,7 +508,7 @@ 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; label: string } };
+ const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; label: string } }) ?? {};
if (['links', 'annotations', 'aliases', this.fieldKey].includes(expandKey)) {
const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None);
const sortKeys = Object.keys(sortings);
@@ -729,7 +728,7 @@ export class TreeView extends React.Component<TreeViewProps> {
const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, icon: 'copy', label: 'Open Alias' };
const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Focus or Open' };
return [
- ...this.props.contextMenuItems.filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)),
+ ...(this.props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)),
...(this.doc.isFolder
? folderOp
: Doc.IsSystem(this.doc)
@@ -831,7 +830,6 @@ export class TreeView extends React.Component<TreeViewProps> {
*/
@computed
get renderTitle() {
- //
TraceMobx();
const view = this._editTitle ? (
<EditableView
@@ -868,7 +866,7 @@ export class TreeView extends React.Component<TreeViewProps> {
}
})}
Document={this.doc}
- fitWidth={(doc: Doc) => true}
+ fitWidth={returnTrue}
DataDoc={undefined}
scriptContext={this}
hideDecorationTitle={this.props.treeView.outlineMode}
@@ -1035,7 +1033,7 @@ 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] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length);
+ const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, false));
};
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5b26469ed..b5dde211b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,7 +1,7 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
import { observer } from 'mobx-react';
import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
import { Document } from '../../../fields/documentSchemas';
@@ -53,7 +53,6 @@ import { RadialMenu } from './RadialMenu';
import { ScriptingBox } from './ScriptingBox';
import { PresBox } from './trails/PresBox';
import React = require('react');
-import { CollectionTreeView } from '../collections/CollectionTreeView';
const { Howl } = require('howler');
interface Window {
@@ -1056,24 +1055,36 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
: false;
};
linkButtonInverseScaling = () => (this.props.NativeDimScaling?.() || 1) * this.props.DocumentView().screenToLocalTransform().Scale;
- @computed get contents() {
- TraceMobx();
+ get audioAnnoState() {
+ return this.dataDoc.audioAnnoState ?? 'stopped';
+ }
+ @computed get audioAnnoView() {
const audioAnnosCount = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null)?.length;
const audioTextAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations-text'], listSpec('string'), null);
- const audioView =
- (!this.props.isSelected() && !this._isHovering && this.dataDoc.audioAnnoState !== 2) || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (!audioAnnosCount && !this.dataDoc.audioAnnoState) ? null : (
- <Tooltip title={<div>{audioTextAnnos?.lastElement()}</div>}>
- <div className="documentView-audioBackground" onPointerDown={this.playAnnotation}>
- <FontAwesomeIcon
- className="documentView-audioFont"
- style={{ color: [audioAnnosCount ? 'blue' : 'gray', 'green', 'red'][NumCast(this.dataDoc.audioAnnoState)] }}
- icon={!audioAnnosCount ? 'microphone' : 'file-audio'}
- size="sm"
- />
- </div>
- </Tooltip>
- );
-
+ const audioIconColors = new Map<string, string>([
+ ['recording', 'red'],
+ ['playing', 'green'],
+ ['stopped', audioAnnosCount ? 'blue' : 'gray'],
+ ]);
+ return this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (!this.props.isSelected() && !this._isHovering && this.audioAnnoState !== 'recording') || (!audioAnnosCount && this.audioAnnoState === 'stopped') ? null : (
+ <Tooltip title={<div>{audioTextAnnos?.lastElement()}</div>}>
+ <div className="documentView-audioBackground" onPointerDown={this.playAnnotation}>
+ <FontAwesomeIcon className="documentView-audioFont" style={{ color: audioIconColors.get(StrCast(this.audioAnnoState)) }} icon={!audioAnnosCount ? 'microphone' : 'file-audio'} size="sm" />
+ </div>
+ </Tooltip>
+ );
+ }
+ @computed get linkCountView() {
+ return this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (!this.props.isSelected() && !this._isHovering) || this.hideLinkButton ? null : (
+ <DocumentLinksButton
+ View={this.props.DocumentView()}
+ scaling={this.linkButtonInverseScaling}
+ Offset={[this.topMost ? 0 : !this.props.isSelected() ? -15 : -36, undefined, undefined, this.topMost ? 10 : !this.props.isSelected() ? -15 : -32]}
+ />
+ );
+ }
+ @computed get contents() {
+ TraceMobx();
return (
<div
className="documentView-contentsView"
@@ -1095,10 +1106,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
width={this.props.PanelWidth()}
height={this.props.PanelHeight()}
onError={(e: any) => {
- setTimeout(
- action(() => (this._retryThumb = 0)),
- 0
- );
+ setTimeout(action(() => (this._retryThumb = 0)));
setTimeout(
action(() => (this._retryThumb = 1)),
150
@@ -1124,14 +1132,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
layoutKey={this.finalLayoutKey}
/>
{this.layoutDoc.hideAllLinks ? null : this.allLinkEndpoints}
- {(!this.props.isSelected() && !this._isHovering) || this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? null : (
- <DocumentLinksButton
- View={this.props.DocumentView()}
- scaling={this.linkButtonInverseScaling}
- Offset={[this.topMost ? 0 : !this.props.isSelected() ? -15 : -36, undefined, undefined, this.topMost ? 10 : !this.props.isSelected() ? -15 : -32]}
- />
- )}
- {audioView}
+ {this.linkCountView}
+ {this.audioAnnoView}
</div>
);
}
@@ -1210,7 +1212,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const self = this;
const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null);
const anno = audioAnnos.lastElement();
- if (anno instanceof AudioField && this.dataDoc.audioAnnoState === 0) {
+ if (anno instanceof AudioField && this.audioAnnoState === 'stopped') {
new Howl({
src: [anno.url.href],
format: ['mp3'],
@@ -1218,12 +1220,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
loop: false,
volume: 0.5,
onend: function () {
- runInAction(() => {
- self.dataDoc.audioAnnoState = 0;
- });
+ runInAction(() => (self.dataDoc.audioAnnoState = 'stopped'));
},
});
- this.dataDoc.audioAnnoState = 1;
+ this.dataDoc.audioAnnoState = 'playing';
}
};
@@ -1262,12 +1262,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
}
};
- runInAction(() => (dataDoc.audioAnnoState = 2));
+ runInAction(() => (dataDoc.audioAnnoState = 'recording'));
recorder.start();
setTimeout(() => {
recorder.stop();
DictationManager.Controls.stop(false);
- runInAction(() => (dataDoc.audioAnnoState = 0));
+ runInAction(() => (dataDoc.audioAnnoState = 'stopped'));
gumStream.getAudioTracks()[0].stop();
}, 5000);
});
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 883c4460b..2b83e9da8 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -198,7 +198,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
e.preventDefault();
}}
onClick={action(() => (this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen))}>
- <input style={{ width: 30 }} className="button-input" type="number" value={checkResult} onChange={action(e => setValue(Number(e.target.value)))} />
+ <input style={{ width: 30 }} className="button-input" type="number" value={checkResult} onChange={undoBatch(action(e => setValue(Number(e.target.value))))} />
</div>
<div className={`button`} onClick={action(e => setValue(Number(checkResult) + 1))}>
<FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'plus'} />
@@ -221,7 +221,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
</div>
);
} else {
- return <div></div>;
+ return <div />;
}
}
@@ -303,7 +303,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
fontFamily: script.script.originalScript.startsWith('setFont') ? value : undefined,
backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined,
}}
- onClick={() => script.script.run({ value }).result}>
+ onClick={undoBatch(() => script.script.run({ value }))}>
{value[0].toUpperCase() + value.slice(1)}
</div>
));
@@ -352,12 +352,14 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
}
colorPicker = (curColor: string) => {
- const change = (value: ColorState) => {
+ const change = (value: ColorState, ev: MouseEvent) => {
+ ev.preventDefault();
+ ev.stopPropagation();
const s = this.colorScript;
s && undoBatch(() => s.script.run({ value: Utils.colorString(value), _readOnly_: false }).result)();
};
const presets = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent'];
- return <SketchPicker onChange={change} color={curColor} presetColors={presets} />;
+ return <SketchPicker onChange={change as any /* SketchPicker passes the mouse event to the callback, but the type system doesn't know that */} color={curColor} presetColors={presets} />;
};
/**
* Color button
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index fc43325fe..70cb10970 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -756,12 +756,13 @@ export namespace Doc {
}
cloneMap.set(doc[Id], copy);
}
+ Doc.IsPrototype(copy) && Doc.AddDocToList(Doc.MyFileOrphans, undefined, copy);
return copy;
}
export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map<string, Doc> = new Map()) {
const linkMap = new Map<Doc, Doc>();
const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = [];
- const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf'], dontCreate, asBranch);
+ const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf', 'context'], dontCreate, asBranch);
Array.from(linkMap.entries()).map((links: Doc[]) => LinkManager.Instance.addLink(links[1], true));
rtfMap.map(({ copy, key, field }) => {
const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
@@ -974,6 +975,7 @@ export namespace Doc {
if (retitle) {
copy.title = incrementTitleCopy(StrCast(copy.title));
}
+ Doc.IsPrototype(copy) && Doc.AddDocToList(Doc.MyFileOrphans, undefined, copy);
return copy;
}
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 5cc4ca543..edaa16003 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -278,7 +278,13 @@ class ListImpl<T extends Field> extends ObjectField {
const batchPromise = DocServer.GetRefFields(promised.map(p => p.promisedFieldId));
// as soon as we get the fields from the server, set all the list values in one
// action to generate one React dom update.
- batchPromise.then(pfields => promised.forEach(p => p.field.setValue(pfields[p.promisedFieldId])));
+ batchPromise.then(
+ action(pfields => {
+ for (let i = 0; i < promised.length; i++) {
+ promised[i].field.setValue(pfields[promised[i].promisedFieldId]);
+ }
+ })
+ );
// we also have to mark all lists items with this promise so that any calls to them
// will await the batch request and return the requested field value.
// This assumes the handler for 'promise' in the call above being invoked before the
diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts
index 2c5f38818..e924ef7a3 100644
--- a/src/fields/Proxy.ts
+++ b/src/fields/Proxy.ts
@@ -1,28 +1,28 @@
-import { Deserializable } from "../client/util/SerializationHelper";
-import { FieldWaiting } from "./Doc";
-import { primitive, serializable } from "serializr";
-import { observable, action, runInAction } from "mobx";
-import { DocServer } from "../client/DocServer";
-import { RefField } from "./RefField";
-import { ObjectField } from "./ObjectField";
-import { Id, Copy, ToScriptString, ToString } from "./FieldSymbols";
-import { scriptingGlobal } from "../client/util/ScriptingGlobals";
-import { Plugins } from "./util";
+import { Deserializable } from '../client/util/SerializationHelper';
+import { FieldWaiting } from './Doc';
+import { primitive, serializable } from 'serializr';
+import { observable, action, runInAction } from 'mobx';
+import { DocServer } from '../client/DocServer';
+import { RefField } from './RefField';
+import { ObjectField } from './ObjectField';
+import { Id, Copy, ToScriptString, ToString } from './FieldSymbols';
+import { scriptingGlobal } from '../client/util/ScriptingGlobals';
+import { Plugins } from './util';
function deserializeProxy(field: any) {
if (!field.cache) {
field.cache = DocServer.GetCachedRefField(field.fieldId) as any;
}
}
-@Deserializable("proxy", deserializeProxy)
+@Deserializable('proxy', deserializeProxy)
export class ProxyField<T extends RefField> extends ObjectField {
constructor();
constructor(value: T);
constructor(fieldId: string);
constructor(value?: T | string) {
super();
- if (typeof value === "string") {
- this.cache = DocServer.GetCachedRefField(value) as any;
+ if (typeof value === 'string') {
+ //this.cache = DocServer.GetCachedRefField(value) as any;
this.fieldId = value;
} else if (value) {
this.cache = value;
@@ -36,16 +36,16 @@ export class ProxyField<T extends RefField> extends ObjectField {
}
[ToScriptString]() {
- return "invalid";
+ return 'invalid';
}
[ToString]() {
- return "ProxyField";
+ return 'ProxyField';
}
@serializable(primitive())
- readonly fieldId: string = "";
+ readonly fieldId: string = '';
- // This getter/setter and nested object thing is
+ // This getter/setter and nested object thing is
// because mobx doesn't play well with observable proxies
@observable.ref
private _cache: { readonly field: T | undefined } = { field: undefined };
@@ -59,29 +59,29 @@ export class ProxyField<T extends RefField> extends ObjectField {
private failed = false;
private promise?: Promise<any>;
+ @action
value(): T | undefined | FieldWaiting<T> {
- if (this.cache) {
- return this.cache;
- }
- if (this.failed) {
- return undefined;
- }
- if (!this.promise) {
- const cached = DocServer.GetCachedRefField(this.fieldId);
- if (cached !== undefined) {
- runInAction(() => this.cache = cached as any);
- return cached as any;
- }
- this.promise = DocServer.GetRefField(this.fieldId).then(action((field: any) => {
- this.promise = undefined;
- this.cache = field;
- if (field === undefined) this.failed = true;
- return field;
- }));
+ if (this.cache) return this.cache;
+ if (this.failed) return undefined;
+
+ const cached = DocServer.GetCachedRefField(this.fieldId) as T;
+ if (cached !== undefined) {
+ this.cache = cached;
+ } else if (!this.promise) {
+ this.promise = DocServer.GetRefField(this.fieldId).then(
+ action((field: any) => {
+ this.promise = undefined;
+ this.cache = field;
+ this.failed = field === undefined;
+ return field;
+ })
+ ) as FieldWaiting<T>;
}
- return DocServer.GetCachedRefField(this.fieldId) ?? (this.promise as any);
+ return cached ?? this.promise;
+ }
+ promisedValue(): string {
+ return !this.cache && !this.failed && !this.promise && !DocServer.GetCachedRefField(this.fieldId) ? this.fieldId : '';
}
- promisedValue(): string { return !this.cache && !this.failed && !this.promise ? this.fieldId : ""; }
setPromise(promise: any) {
this.promise = promise;
}
@@ -127,6 +127,5 @@ function prefetchValue(proxy: PrefetchProxy<RefField>) {
}
@scriptingGlobal
-@Deserializable("prefetch_proxy", prefetchValue)
-export class PrefetchProxy<T extends RefField> extends ProxyField<T> {
-}
+@Deserializable('prefetch_proxy', prefetchValue)
+export class PrefetchProxy<T extends RefField> extends ProxyField<T> {}