diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/MainView.tsx | 7 | ||||
-rw-r--r-- | src/client/views/collections/TreeView.tsx | 16 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 74 | ||||
-rw-r--r-- | src/client/views/nodes/button/FontIconBox.tsx | 12 | ||||
-rw-r--r-- | src/fields/Doc.ts | 4 | ||||
-rw-r--r-- | src/fields/List.ts | 8 | ||||
-rw-r--r-- | src/fields/Proxy.ts | 79 |
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> {} |