From 627c2aa72f61a888866840810a7d31cb0648437b Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 28 Oct 2022 14:14:05 -0400 Subject: fixed proxyfields to use cache if it's available which saves orders of magnitude opening up Files list. Fixed copying docs to appear in Files list. fixed undo for change perspective and header color --- src/client/views/MainView.tsx | 7 +-- src/client/views/collections/TreeView.tsx | 16 +++--- src/client/views/nodes/DocumentView.tsx | 74 ++++++++++++------------- src/client/views/nodes/button/FontIconBox.tsx | 12 ++-- src/fields/Doc.ts | 4 +- src/fields/List.ts | 8 ++- src/fields/Proxy.ts | 79 +++++++++++++-------------- 7 files changed, 102 insertions(+), 98 deletions(-) (limited to 'src') 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 { 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 { 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 { 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 { @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 { 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 { */ @computed get renderTitle() { - // TraceMobx(); const view = this._editTitle ? ( { } })} 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 { 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 (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 : ( - {audioTextAnnos?.lastElement()}}> -
- -
-
- ); - + const audioIconColors = new Map([ + ['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 : ( + {audioTextAnnos?.lastElement()}}> +
+ +
+
+ ); + } + @computed get linkCountView() { + return this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (!this.props.isSelected() && !this._isHovering) || this.hideLinkButton ? null : ( + + ); + } + @computed get contents() { + TraceMobx(); return (
{ - 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 {this.layoutDoc.hideAllLinks ? null : this.allLinkEndpoints} - {(!this.props.isSelected() && !this._isHovering) || this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? null : ( - - )} - {audioView} + {this.linkCountView} + {this.audioAnnoView}
); } @@ -1210,7 +1212,7 @@ export class DocumentViewInternal extends DocComponent { - 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 (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() { e.preventDefault(); }} onClick={action(() => (this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen))}> - setValue(Number(e.target.value)))} /> + setValue(Number(e.target.value))))} />
setValue(Number(checkResult) + 1))}> @@ -221,7 +221,7 @@ export class FontIconBox extends DocComponent() {
); } else { - return
; + return
; } } @@ -303,7 +303,7 @@ export class FontIconBox extends DocComponent() { 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)}
)); @@ -352,12 +352,14 @@ export class FontIconBox extends DocComponent() { } 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 ; + return ; }; /** * 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 = new Map()) { const linkMap = new Map(); 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 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 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 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 extends ObjectField { private failed = false; private promise?: Promise; + @action value(): T | undefined | FieldWaiting { - 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; } - 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) { } @scriptingGlobal -@Deserializable("prefetch_proxy", prefetchValue) -export class PrefetchProxy extends ProxyField { -} +@Deserializable('prefetch_proxy', prefetchValue) +export class PrefetchProxy extends ProxyField {} -- cgit v1.2.3-70-g09d2 From 470af89b6ddc17dda613705d599a0ff221d0b927 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 31 Oct 2022 09:24:23 -0400 Subject: cleaned up collection linear view a bit. --- src/client/documents/Documents.ts | 10 +- src/client/util/CurrentUserUtils.ts | 6 +- src/client/views/StyleProvider.tsx | 3 +- .../collectionLinear/CollectionLinearView.scss | 8 +- .../collectionLinear/CollectionLinearView.tsx | 133 ++++++++++----------- 5 files changed, 77 insertions(+), 83 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index bbb896d6e..d789d4ee3 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -261,7 +261,7 @@ export class DocumentOptions { presDuration?: number; //the duration of the slide in presentation view presProgressivize?: boolean; borderRounding?: string; - boxShadow?: string; + boxShadow?: string; // box-shadow css string OR "standard" to use dash standard box shadow data?: any; baseProto?: boolean; // is this a base prototoype dontRegisterView?: boolean; @@ -298,7 +298,6 @@ export class DocumentOptions { linearViewExpandable?: boolean; // can linear view be expanded linearViewToggleButton?: string; // button to open close linear view group linearViewSubMenu?: boolean; - linearViewFloating?: boolean; flexGap?: number; // Linear view flex gap flexDirection?: 'unset' | 'row' | 'column' | 'row-reverse' | 'column-reverse'; @@ -1384,11 +1383,12 @@ export namespace DocUtils { ); } - export function AssignScripts(doc: Doc, scripts?: { [key: string]: string }, funcs?: { [key: string]: string }) { + export function AssignScripts(doc: Doc, scripts?: { [key: string]: string | undefined }, funcs?: { [key: string]: string }) { scripts && Object.keys(scripts).map(key => { - if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) { - doc[key] = ScriptField.MakeScript(scripts[key], { + const script = scripts[key]; + if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) { + doc[key] = ScriptField.MakeScript(script, { dragData: DragManager.DocumentDragData.name, value: 'any', _readOnly_: 'boolean', diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index a2974177e..114dbac93 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -582,20 +582,22 @@ export class CurrentUserUtils { /// initializes the required buttons in the expanding button menu at the bottom of the Dash window static setupDockedButtons(doc: Doc, field="myDockedBtns") { const dockedBtns = DocCast(doc[field]); - const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string}, funcs?: {[key:string]:string}) => + const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:string}) => DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ?? CurrentUserUtils.createToolButton(opts), scripts, funcs); const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet { scripts: { onClick: "undo()"}, opts: { title: "undo", icon: "undo-alt", toolTip: "Click to undo" }}, { scripts: { onClick: "redo()"}, opts: { title: "redo", icon: "redo-alt", toolTip: "Click to redo" }}, + { scripts: { }, opts: { title: "linker", icon: "linkui", toolTip: "link started"}}, + { scripts: { }, opts: { title: "currently playing", icon: "currentlyplayingui", toolTip: "currently playing audio"}}, // { scripts: { onClick: 'prevKeyFrame(_readOnly_)'}, opts: { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, width: 20}}, // { scripts: { onClick:""}, opts: { title: "Num", icon: "", toolTip: "Frame Number", btnType: ButtonType.TextButton, width: 20}, funcs: { buttonText: 'selectedDocs()?.lastElement()?.currentFrame.toString()'}}, // { scripts: { onClick: 'nextKeyFrame(_readOnly_)'}, opts:{title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, width: 20,} }, ]; const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...desc.opts}, desc.scripts)); const dockBtnsReqdOpts = { - title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, + title: "docked buttons", _height: 40, flexGap: 0, boxShadow: "standard", childDontRegisterViews: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true }; reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index bc8bd7b7f..869570a17 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -18,6 +18,7 @@ import { FieldViewProps } from './nodes/FieldView'; import { SliderBox } from './nodes/SliderBox'; import './StyleProvider.scss'; import React = require('react'); +import { Shadows } from 'browndash-components'; export enum StyleProp { TreeViewIcon = 'treeViewIcon', @@ -250,7 +251,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt { + return !DocumentLinksButton.StartLink ? null : ( + e.stopPropagation()}> + + Creating link from:{' '} + + {(DocumentLinksButton.AnnotationId ? 'Annotation in ' : ' ') + + (StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...')} + + + + {'Toggle description pop-up'} } placement="top"> + + Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : 'ON'} + + + + Exit linking mode} placement="top"> + + Stop + + + + ); + }; + getCurrentlyPlayingUI = () => { + return !CollectionStackedTimeline.CurrentlyPlaying?.length ? null : ( + + + Currently playing: + {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => ( + DocumentManager.Instance.jumpToDocument(clip, true, undefined, [])}> + {clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? '' : ',')} + + ))} + + + ); + }; getDisplayDoc = (doc: Doc, preview: boolean = false) => { + if (doc.icon === 'linkui') return this.getLinkUI(); + if (doc.icon === 'currentlyplayingui') return this.getCurrentlyPlayingUI(); + const nested = doc._viewType === CollectionViewType.Linear; const hidden = doc.hidden === true; @@ -172,40 +214,32 @@ export class CollectionLinearView extends CollectionSubView() { }; render() { - const guid = Utils.GenerateGuid(); // Generate a unique ID to use as the label - const flexDir: any = StrCast(this.Document.flexDirection); // Specify direction of linear view content - const flexGap: number = NumCast(this.Document.flexGap); // Specify the gap between linear view content - const expandable: boolean = BoolCast(this.props.Document.linearViewExpandable); // Specify whether it is expandable or not - const floating: boolean = BoolCast(this.props.Document.linearViewFloating); // Specify whether it is expandable or not + const flexDir = StrCast(this.Document.flexDirection); // Specify direction of linear view content + const flexGap = NumCast(this.Document.flexGap); // Specify the gap between linear view content + const isExpanded = BoolCast(this.layoutDoc.linearViewIsExpanded); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const icon: string = StrCast(this.props.Document.icon); // Menu opener toggle const menuOpener = ( ); return ( -
+
- {!expandable ? null : ( - {BoolCast(this.props.Document.linearViewIsExpanded) ? 'Close' : 'Open'}
} placement="top"> + {!this.props.Document.linearViewExpandable ? null : ( + {isExpanded ? 'Close' : 'Open'}
} placement="top"> {menuOpener} )} { ScriptCast(this.Document.onClick)?.script.run({ @@ -216,7 +250,7 @@ export class CollectionLinearView extends CollectionSubView() { thisContainer: this.props.ContainingCollectionDoc, documentView: this.props.docViewPath().lastElement(), }); - this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked; + this.layoutDoc.linearViewIsExpanded = this.addMenuToggle.current!.checked; })} /> @@ -224,58 +258,11 @@ export class CollectionLinearView extends CollectionSubView() { className="collectionLinearView-content" style={{ height: this.dimension(), - flexDirection: flexDir, + flexDirection: flexDir as any, gap: flexGap, }}> {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
- {!DocumentLinksButton.StartLink || this.layoutDoc !== Doc.MyDockedBtns ? null : ( - e.stopPropagation()}> - - Creating link from:{' '} - - {(DocumentLinksButton.AnnotationId ? 'Annotation in ' : ' ') + - (StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...')} - - - - -
{'Toggle description pop-up'}
- - } - placement="top"> - - Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : 'ON'} - -
- - -
Exit linking mode
- - } - placement="top"> - - Stop - -
-
- )} - {!CollectionStackedTimeline.CurrentlyPlaying || !CollectionStackedTimeline.CurrentlyPlaying.length || this.layoutDoc !== Doc.MyDockedBtns ? null : ( - - - Currently playing: - {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => ( - DocumentManager.Instance.jumpToDocument(clip, true, undefined, [])}> - {clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? '' : ',')} - - ))} - - - )} ); -- cgit v1.2.3-70-g09d2 From 70c998562c8560283a7d6b9a1ae78b9207e3720f Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 31 Oct 2022 13:26:52 -0400 Subject: cleaning up link count icon to be part of documentLinks view and to appear on tab views that have links. --- src/client/views/DocumentButtonBar.tsx | 31 ++-------- src/client/views/GlobalKeyHandler.ts | 5 -- src/client/views/nodes/DocumentLinksButton.scss | 34 +++++------ src/client/views/nodes/DocumentLinksButton.tsx | 67 ++++++++++----------- src/client/views/nodes/DocumentView.scss | 2 +- src/client/views/nodes/DocumentView.tsx | 80 ++++++++++++------------- 6 files changed, 93 insertions(+), 126 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index d42ff436f..ce77b7446 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -317,24 +317,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV ); } - - @computed - get moreButton() { - const targetDoc = this.view0?.props.Document; - return !targetDoc ? null : ( - -
{`${SettingsManager.propertiesWidth > 0 ? 'Close' : 'Open'} Properties Panel`}
- - }> -
(SettingsManager.propertiesWidth = SettingsManager.propertiesWidth > 0 ? 0 : 250))}> - -
-
- ); - } - @computed get metadataButton() { const view0 = this.view0; @@ -463,6 +445,9 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV const considerPush = isText && this.considerGoogleDocsPush; return (
+
+ +
@@ -475,11 +460,8 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV { Doc.noviceMode ? null :
{this.templateButton}
/*
- {this.metadataButton} -
-
- {this.contextButton} -
*/ + {this.metadataButton} +
*/ } {!SelectionManager.Views()?.some(v => v.allLinks.length) ? null :
{this.followLinkButton}
}
{this.pinButton}
@@ -493,9 +475,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV {this.considerGoogleDocsPull}
{this.menuButton}
- {/* {Doc.noviceMode ? (null) :
- {this.moreButton} -
} */} ); } diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 8e9c18cf3..c19e6831f 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -151,11 +151,6 @@ export class KeyManager { SelectionManager.DeselectAll(); } else DocumentDecorations.Instance.onCloseClick(true); }, 'backspace'); - // const selected = SelectionManager.Views().filter(dv => !dv.topMost); - // UndoManager.RunInBatch(() => { - // SelectionManager.DeselectAll(); - // selected.map(dv => !dv.props.Document._stayInCollection && dv.props.removeDocument?.(dv.props.Document)); - // }, "delete"); return { stopPropagation: true, preventDefault: true }; } break; diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss index 0f3eb14bc..6da0b73ba 100644 --- a/src/client/views/nodes/DocumentLinksButton.scss +++ b/src/client/views/nodes/DocumentLinksButton.scss @@ -1,8 +1,9 @@ -@import "../global/globalCssVariables.scss"; +@import '../global/globalCssVariables.scss'; .documentLinksButton-wrapper { transform-origin: top left; width: 100%; + height: 100%; } .documentLinksButton-menu { @@ -21,6 +22,16 @@ position: absolute; } +.documentLinksButton-showCount { + position: absolute; + border-radius: 50%; + opacity: 0.9; + pointer-events: auto; + display: flex; + align-items: center; + background-color: $light-blue; + color: black; +} .documentLinksButton, .documentLinksButton-endLink, .documentLinksButton-startLink { @@ -34,6 +45,7 @@ text-transform: uppercase; letter-spacing: 2px; font-size: 10px; + transform-origin: top left; transition: transform 0.2s; text-align: center; display: flex; @@ -46,13 +58,10 @@ cursor: pointer; } } - .documentLinksButton { background-color: $dark-gray; color: $white; font-weight: bold; - width: 80%; - height: 80%; font-size: 100%; font-family: 'Roboto'; transition: 0.2s ease all; @@ -61,31 +70,20 @@ background-color: $black; } } - .documentLinksButton.startLink { background-color: $medium-blue; + width: 75%; + height: 75%; color: $white; font-weight: bold; - width: 80%; - height: 80%; font-size: 100%; transition: 0.2s ease all; - - &:hover { - background-color: $black; - } } .documentLinksButton-endLink { border: $medium-blue 2px dashed; color: $medium-blue; background-color: none !important; - width: 80%; - height: 80%; font-size: 100%; transition: 0.2s ease all; - - &:hover { - background-color: $light-blue; - } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 9ffbf8e37..1d455ad08 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -4,19 +4,18 @@ import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Opt } from '../../../fields/Doc'; import { StrCast } from '../../../fields/Types'; -import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnFalse, setupMoveUpEvents, StopEvent } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { Hypothesis } from '../../util/HypothesisUtils'; import { LinkManager } from '../../util/LinkManager'; import { undoBatch, UndoManager } from '../../util/UndoManager'; -import { Colors } from '../global/globalEnums'; import './DocumentLinksButton.scss'; import { DocumentView } from './DocumentView'; import { LinkDescriptionPopup } from './LinkDescriptionPopup'; import { TaskCompletionBox } from './TaskCompletedBox'; import React = require('react'); +import _ = require('lodash'); const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; @@ -24,10 +23,12 @@ export const Flyout = higflyout.default; interface DocumentLinksButtonProps { View: DocumentView; - Offset?: (number | undefined)[]; + Bottom?: boolean; AlwaysOn?: boolean; InMenu?: boolean; + OnHover?: boolean; StartLink?: boolean; //whether the link HAS been started (i.e. now needs to be completed) + ShowCount?: boolean; scaling?: () => number; // how uch doc is scaled so that link buttons can invert it } @observer @@ -119,7 +120,6 @@ export class DocumentLinksButton extends React.Component Doc.BrushDoc(this.props.View.Document)); } }; @@ -256,28 +256,32 @@ export class DocumentLinksButton extends React.Component; + const btnDim = 30; const isActive = DocumentLinksButton.StartLink === this.props.View.props.Document && this.props.StartLink; - return !this.props.InMenu ? ( -
-
- {Array.from(this.filteredLinks).length} -
+ const scaling = Math.min(1, this.props.scaling?.() || 1); + const showLinkCount = (onHover?: boolean, offset?: boolean) => ( +
+ {Array.from(this.filteredLinks).length}
+ ); + return this.props.ShowCount ? ( + showLinkCount(this.props.OnHover, this.props.Bottom) ) : (
- {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? ( //if the origin node is not this node + {this.props.StartLink ? ( //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again +
+ +
+ ) : null} + {!this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? ( //if the origin node is not this node
) : null} - {this.props.InMenu && this.props.StartLink ? ( //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again -
- -
- ) : null}
); } render() { - TraceMobx(); - 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.InMenu ? menuTitle : buttonTitle; + 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 : (
- {(this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink)) || (!DocumentLinksButton.LinkEditorDocView && !this.props.InMenu) ? ( - {title}
}>{this.linkButtonInner} - ) : ( - this.linkButtonInner - )} + {title}
: <>}>{this.linkButtonInner} ); } diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 6cadeec41..5c95177c5 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -209,7 +209,7 @@ .contentFittingDocumentView { position: relative; - display: flex; + display: block; width: 100%; height: 100%; transition: inherit; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index b5dde211b..50b76896e 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, trace } from 'mobx'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } 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'; @@ -198,6 +198,7 @@ export interface DocumentViewInternalProps extends DocumentViewProps { NativeWidth: () => number; NativeHeight: () => number; isSelected: (outsideReaction?: boolean) => boolean; + isHovering: () => boolean; select: (ctrlPressed: boolean) => void; DocumentView: () => DocumentView; viewPath: () => DocumentView[]; @@ -257,9 +258,6 @@ export class DocumentViewInternal extends DocComponent (this.props.NativeDimScaling?.() || 1) * this.props.DocumentView().screenToLocalTransform().Scale; get audioAnnoState() { return this.dataDoc.audioAnnoState ?? 'stopped'; } @@ -1066,7 +1063,7 @@ export class DocumentViewInternal extends DocComponent{audioTextAnnos?.lastElement()}}>
@@ -1074,15 +1071,6 @@ export class DocumentViewInternal extends DocComponent ); } - @computed get linkCountView() { - return this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (!this.props.isSelected() && !this._isHovering) || this.hideLinkButton ? null : ( - - ); - } @computed get contents() { TraceMobx(); return ( @@ -1119,7 +1107,7 @@ export class DocumentViewInternal extends DocComponent {this.layoutDoc.hideAllLinks ? null : this.allLinkEndpoints} - {this.linkCountView} {this.audioAnnoView}
); @@ -1149,16 +1136,13 @@ export class DocumentViewInternal extends DocComponent this.props.PanelWidth() || 1; anchorPanelHeight = () => this.props.PanelHeight() || 1; anchorStyleProvider = (doc: Opt, props: Opt, property: string): any => { + // prettier-ignore switch (property) { - case StyleProp.ShowTitle: - return ''; - case StyleProp.PointerEvents: - return 'none'; - case StyleProp.LinkSource: - return this.props.Document; // pass the LinkSource to the LinkAnchorBox - default: - return this.props.styleProvider?.(doc, props, property); + case StyleProp.ShowTitle: return ''; + case StyleProp.PointerEvents: return 'none'; + case StyleProp.LinkSource: return this.props.Document; // pass the LinkSource to the LinkAnchorBox } + 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 'unrendered' with their 'annotationOn' set to this document). e.g., @@ -1360,10 +1344,7 @@ export class DocumentViewInternal extends DocComponent ); } - isHovering = () => this._isHovering; - @observable _isHovering = false; @observable _: string = ''; - _hoverTimeout: any = undefined; renderDoc = (style: object) => { TraceMobx(); const thumb = ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png'); @@ -1374,17 +1355,6 @@ export class DocumentViewInternal extends DocComponent { - clearTimeout(this._hoverTimeout); - this._isHovering = true; - })} - onPointerLeave={action(() => { - clearTimeout(this._hoverTimeout); - this._hoverTimeout = setTimeout( - action(() => (this._isHovering = false)), - 500 - ); - })} style={{ ...style, background: isButton || thumb ? undefined : this.backgroundColor, @@ -1535,6 +1505,18 @@ export class DocumentView extends React.Component { return this.props.fitWidth?.(this.rootDoc) || this.layoutDoc.fitWidth; } + @computed get hideLinkButton() { + return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkButton + (this.isSelected() ? ':selected' : '')); + } + linkButtonInverseScaling = () => (this.props.NativeDimScaling?.() || 1) * this.screenToLocalTransform().Scale; + + @computed get linkCountView() { + return (this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton) && + DocumentLinksButton.LinkEditorDocView?.rootDoc !== this.rootDoc ? null : ( + + ); + } + @computed get docViewPath(): DocumentView[] { return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this]; } @@ -1680,6 +1662,9 @@ export class DocumentView extends React.Component { Object.values(this._disposers).forEach(disposer => disposer?.()); !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.RemoveView(this); } + _hoverTimeout: any = undefined; + isHovering = () => this._isHovering; + @observable _isHovering = false; render() { TraceMobx(); @@ -1687,7 +1672,19 @@ export class DocumentView extends React.Component { const yshift = Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined; const isButton = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear; return ( -
+
{ + clearTimeout(this._hoverTimeout); + this._isHovering = true; + })} + onPointerLeave={action(() => { + clearTimeout(this._hoverTimeout); + this._hoverTimeout = setTimeout( + action(() => (this._isHovering = false)), + 500 + ); + })}> {!this.props.Document || !this.props.PanelWidth() ? null : (
{ NativeDimScaling={this.NativeDimScaling} isSelected={this.isSelected} select={this.select} + isHovering={this.isHovering} ScreenToLocalTransform={this.screenToLocalTransform} focus={this.props.focus || emptyFunction} ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))} />
)} + + {this.linkCountView}
); } -- cgit v1.2.3-70-g09d2 From 10f6c2c56fa76100817312f9e019340ae2094289 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 1 Nov 2022 09:32:45 -0400 Subject: from last --- src/client/views/nodes/DocumentView.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 5c95177c5..6cadeec41 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -209,7 +209,7 @@ .contentFittingDocumentView { position: relative; - display: block; + display: flex; width: 100%; height: 100%; transition: inherit; -- cgit v1.2.3-70-g09d2 From dc942e6228e003caa3754a72c0e126d64332a004 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 1 Nov 2022 18:29:11 -0400 Subject: fixes for goldenlayout to initialize more cleanly. updated all old ReactDOM.render() to ReactDom.createRoot(). fixes for PDF/Web sidebar sizing. added text from pdf selection anchors to sidebar notes. fixed PDF text selection to align properly. --- src/client/goldenLayout.js | 11 +- src/client/util/DragManager.ts | 2 +- src/client/util/HypothesisUtils.ts | 160 ++++++++++++--------- src/client/views/DocumentDecorations.scss | 5 +- src/client/views/DocumentDecorations.tsx | 9 +- src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/Main.tsx | 7 +- src/client/views/MainView.scss | 9 -- src/client/views/MainView.tsx | 94 +----------- src/client/views/MarqueeAnnotator.tsx | 6 +- src/client/views/SidebarAnnos.tsx | 22 +++ src/client/views/StyleProvider.tsx | 6 +- .../views/collections/CollectionDockingView.scss | 4 + .../views/collections/CollectionDockingView.tsx | 17 ++- src/client/views/collections/TabDocView.tsx | 6 +- src/client/views/nodes/DocumentLinksButton.tsx | 3 - src/client/views/nodes/PDFBox.tsx | 13 +- src/client/views/nodes/WebBox.tsx | 14 +- src/client/views/nodes/button/FontIconBox.tsx | 12 +- .../nodes/formattedText/DashDocCommentView.tsx | 8 +- .../views/nodes/formattedText/DashDocView.tsx | 12 +- .../views/nodes/formattedText/DashFieldView.tsx | 53 +++++-- .../views/nodes/formattedText/EquationView.tsx | 10 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 1 + .../views/nodes/formattedText/RichTextRules.ts | 7 +- .../views/nodes/formattedText/SummaryView.tsx | 9 +- src/client/views/nodes/formattedText/nodes_rts.ts | 1 + src/client/views/pdf/PDFViewer.scss | 12 +- src/client/views/pdf/PDFViewer.tsx | 6 +- src/debug/Repl.tsx | 37 +++-- 30 files changed, 278 insertions(+), 280 deletions(-) (limited to 'src') diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js index dd11e6466..bc08b4d0b 100644 --- a/src/client/goldenLayout.js +++ b/src/client/goldenLayout.js @@ -459,7 +459,7 @@ this._bDragging = false; this.emit('dragStop', oEvent, this._nOriginalX + this._nX); } else { // make title receive pointer events to allow setting insertion position or selecting texst range - const classname = typeof oEvent.target?.className === "string" ? oEvent.target.className : ""; + const classname = oEvent.target ? (typeof oEvent.target.className === "string" ? oEvent.target.className : ""): ""; if (classname.includes("lm_title_wrap")) { oEvent.target.children[0].style.pointerEvents = "all"; oEvent.target.children[0].focus(); @@ -5391,10 +5391,11 @@ * @returns {void} */ _render: function () { - this._reactComponent = ReactDOM.render(this._getReactComponent(), this._container.getElement()[0]); - this._originalComponentWillUpdate = this._reactComponent.componentWillUpdate || function () { + this._reactComponent = ReactDOM.createRoot(this._container.getElement()[0]) + this._reactComponent.render(this._getReactComponent()); + this._originalComponentWillUpdate = this._reactComponent.componentDidUpdate || function () { }; - this._reactComponent.componentWillUpdate = this._onUpdate.bind(this); + this._reactComponent.componentDidUpdate = this._onUpdate.bind(this); if (this._container.getState()) { this._reactComponent.setState(this._container.getState()); } @@ -5407,7 +5408,7 @@ * @returns {void} */ _destroy: function () { - ReactDOM.unmountComponentAtNode(this._container.getElement()[0]); + // ReactDOM.unmountComponentAtNode(this._container.getElement()[0]); this._container.off('open', this._render, this); this._container.off('destroy', this._destroy, this); }, diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 36e5a65fb..d0690fa10 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -450,7 +450,7 @@ export namespace DragManager { const hideDragShowOriginalElements = (hide: boolean) => { dragLabel.style.display = hide ? '' : 'none'; !hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement)); - eles.forEach(ele => (ele.hidden = hide)); + setTimeout(() => eles.forEach(ele => (ele.hidden = hide))); }; options?.hideSource && hideDragShowOriginalElements(true); diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts index e910a9118..297520d5d 100644 --- a/src/client/util/HypothesisUtils.ts +++ b/src/client/util/HypothesisUtils.ts @@ -1,30 +1,28 @@ -import { StrCast, Cast } from "../../fields/Types"; -import { SearchUtil } from "./SearchUtil"; -import { action, runInAction } from "mobx"; -import { Doc, Opt } from "../../fields/Doc"; -import { DocumentType } from "../documents/DocumentTypes"; -import { Docs } from "../documents/Documents"; -import { SelectionManager } from "./SelectionManager"; -import { WebField } from "../../fields/URLField"; -import { DocumentManager } from "./DocumentManager"; -import { DocumentLinksButton } from "../views/nodes/DocumentLinksButton"; -import { simulateMouseClick, Utils } from "../../Utils"; -import { DocumentView } from "../views/nodes/DocumentView"; -import { Id } from "../../fields/FieldSymbols"; +import { StrCast, Cast } from '../../fields/Types'; +import { SearchUtil } from './SearchUtil'; +import { action, runInAction } from 'mobx'; +import { Doc, Opt } from '../../fields/Doc'; +import { DocumentType } from '../documents/DocumentTypes'; +import { Docs } from '../documents/Documents'; +import { SelectionManager } from './SelectionManager'; +import { WebField } from '../../fields/URLField'; +import { DocumentManager } from './DocumentManager'; +import { DocumentLinksButton } from '../views/nodes/DocumentLinksButton'; +import { simulateMouseClick, Utils } from '../../Utils'; +import { DocumentView } from '../views/nodes/DocumentView'; +import { Id } from '../../fields/FieldSymbols'; export namespace Hypothesis { - /** - * Retrieve a WebDocument with the given url, prioritizing results that are on screen. + * Retrieve a WebDocument with the given url, prioritizing results that are on screen. * If none exist, create and return a new WebDocument. */ export const getSourceWebDoc = async (uri: string) => { const result = await findWebDoc(uri); - console.log(result ? "existing doc found" : "existing doc NOT found"); + console.log(result ? 'existing doc found' : 'existing doc NOT found'); return result || Docs.Create.WebDocument(uri, { title: uri, _nativeWidth: 850, _height: 512, _width: 400, useCors: true }); // create and return a new Web doc with given uri if no matching docs are found }; - /** * Search for a WebDocument whose url field matches the given uri, return undefined if not found */ @@ -33,18 +31,18 @@ export namespace Hypothesis { if (currentDoc && Cast(currentDoc.data, WebField)?.url.href === uri) return currentDoc; // always check first whether the currently selected doc is the annotation's source, only use Search otherwise const results: Doc[] = []; - await SearchUtil.Search("web", true).then(action(async (res: SearchUtil.DocSearchResult) => { - const docs = res.docs; - const filteredDocs = docs.filter(doc => - doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data - ); - filteredDocs.forEach(doc => { - uri === Cast(doc.data, WebField)?.url.href && results.push(doc); // TODO check visited sites history? - }); - })); + await SearchUtil.Search('web', true).then( + action(async (res: SearchUtil.DocSearchResult) => { + const docs = res.docs; + const filteredDocs = docs.filter(doc => doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data); + filteredDocs.forEach(doc => { + uri === Cast(doc.data, WebField)?.url.href && results.push(doc); // TODO check visited sites history? + }); + }) + ); const onScreenResults = results.filter(doc => DocumentManager.Instance.getFirstDocumentView(doc)); - return onScreenResults.length ? onScreenResults[0] : (results.length ? results[0] : undefined); // prioritize results that are currently on the screen + return onScreenResults.length ? onScreenResults[0] : results.length ? results[0] : undefined; // prioritize results that are currently on the screen }; /** @@ -52,17 +50,19 @@ export namespace Hypothesis { */ export const linkListener = async (e: any) => { const annotationId: string = e.detail.id; - const annotationUri: string = StrCast(e.detail.uri).split("#annotations:")[0]; // clean hypothes.is URLs that reference a specific annotation + const annotationUri: string = StrCast(e.detail.uri).split('#annotations:')[0]; // clean hypothes.is URLs that reference a specific annotation const sourceDoc: Doc = await getSourceWebDoc(annotationUri); - if (!DocumentLinksButton.StartLink || sourceDoc === DocumentLinksButton.StartLink) { // start new link if there were none already started, or if the old startLink came from the same web document (prevent links to itself) + if (!DocumentLinksButton.StartLink || sourceDoc === DocumentLinksButton.StartLink) { + // start new link if there were none already started, or if the old startLink came from the same web document (prevent links to itself) runInAction(() => { DocumentLinksButton.AnnotationId = annotationId; DocumentLinksButton.AnnotationUri = annotationUri; DocumentLinksButton.StartLink = sourceDoc; DocumentLinksButton.StartLinkView = undefined; }); - } else { // if a link has already been started, complete the link to sourceDoc + } else { + // if a link has already been started, complete the link to sourceDoc runInAction(() => { DocumentLinksButton.AnnotationId = annotationId; DocumentLinksButton.AnnotationUri = annotationUri; @@ -81,31 +81,41 @@ export namespace Hypothesis { export const makeLink = async (title: string, url: string, annotationId: string, annotationSourceDoc: Doc) => { // if the annotation's source webpage isn't currently loaded in Dash, we're not able to access and edit the annotation from the client // so we're loading the webpage and its annotations invisibly in a WebBox in MainView.tsx, until the editing is done - !DocumentManager.Instance.getFirstDocumentView(annotationSourceDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = annotationSourceDoc); + //!DocumentManager.Instance.getFirstDocumentView(annotationSourceDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = annotationSourceDoc); var success = false; const onSuccess = action(() => { - console.log("Edit success!!"); + console.log('Edit success!!'); success = true; clearTimeout(interval); - DocumentLinksButton.invisibleWebDoc = undefined; - document.removeEventListener("editSuccess", onSuccess); + //DocumentLinksButton.invisibleWebDoc = undefined; + document.removeEventListener('editSuccess', onSuccess); }); const newHyperlink = `[${title}\n](${url})`; - const interval = setInterval(() => // keep trying to edit until annotations have loaded and editing is successful - !success && document.dispatchEvent(new CustomEvent<{ newHyperlink: string, id: string }>("addLink", { - detail: { newHyperlink: newHyperlink, id: annotationId }, - bubbles: true - })), 300); - - setTimeout(action(() => { - if (!success) { - clearInterval(interval); - DocumentLinksButton.invisibleWebDoc = undefined; - } - }), 10000); // give up if no success after 10s - document.addEventListener("editSuccess", onSuccess); + const interval = setInterval( + () => + // keep trying to edit until annotations have loaded and editing is successful + !success && + document.dispatchEvent( + new CustomEvent<{ newHyperlink: string; id: string }>('addLink', { + detail: { newHyperlink: newHyperlink, id: annotationId }, + bubbles: true, + }) + ), + 300 + ); + + setTimeout( + action(() => { + if (!success) { + clearInterval(interval); + //DocumentLinksButton.invisibleWebDoc = undefined; + } + }), + 10000 + ); // give up if no success after 10s + document.addEventListener('editSuccess', onSuccess); }; /** @@ -114,33 +124,40 @@ export namespace Hypothesis { export const deleteLink = async (linkDoc: Doc, sourceDoc: Doc, destinationDoc: Doc) => { if (Cast(destinationDoc.data, WebField)?.url.href !== StrCast(linkDoc.annotationUri)) return; // check that the destinationDoc is a WebDocument containing the target annotation - !DocumentManager.Instance.getFirstDocumentView(destinationDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = destinationDoc); // see note in makeLink + //!DocumentManager.Instance.getFirstDocumentView(destinationDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = destinationDoc); // see note in makeLink var success = false; const onSuccess = action(() => { - console.log("Edit success!"); + console.log('Edit success!'); success = true; clearTimeout(interval); - DocumentLinksButton.invisibleWebDoc = undefined; - document.removeEventListener("editSuccess", onSuccess); + // DocumentLinksButton.invisibleWebDoc = undefined; + document.removeEventListener('editSuccess', onSuccess); }); const annotationId = StrCast(linkDoc.annotationId); const linkUrl = Doc.globalServerPath(sourceDoc); - const interval = setInterval(() => {// keep trying to edit until annotations have loaded and editing is successful - !success && document.dispatchEvent(new CustomEvent<{ targetUrl: string, id: string }>("deleteLink", { - detail: { targetUrl: linkUrl, id: annotationId }, - bubbles: true - })); + const interval = setInterval(() => { + // keep trying to edit until annotations have loaded and editing is successful + !success && + document.dispatchEvent( + new CustomEvent<{ targetUrl: string; id: string }>('deleteLink', { + detail: { targetUrl: linkUrl, id: annotationId }, + bubbles: true, + }) + ); }, 300); - setTimeout(action(() => { - if (!success) { - clearInterval(interval); - DocumentLinksButton.invisibleWebDoc = undefined; - } - }), 10000); // give up if no success after 10s - document.addEventListener("editSuccess", onSuccess); + setTimeout( + action(() => { + if (!success) { + clearInterval(interval); + //DocumentLinksButton.invisibleWebDoc = undefined; + } + }), + 10000 + ); // give up if no success after 10s + document.addEventListener('editSuccess', onSuccess); }; /** @@ -149,17 +166,20 @@ export namespace Hypothesis { export const scrollToAnnotation = (annotationId: string, target: Doc) => { var success = false; const onSuccess = () => { - console.log("Scroll success!!"); + console.log('Scroll success!!'); document.removeEventListener('scrollSuccess', onSuccess); clearInterval(interval); success = true; }; - const interval = setInterval(() => { // keep trying to scroll every 250ms until annotations have loaded and scrolling is successful - document.dispatchEvent(new CustomEvent('scrollToAnnotation', { - detail: annotationId, - bubbles: true - })); + const interval = setInterval(() => { + // keep trying to scroll every 250ms until annotations have loaded and scrolling is successful + document.dispatchEvent( + new CustomEvent('scrollToAnnotation', { + detail: annotationId, + bubbles: true, + }) + ); const targetView: Opt = DocumentManager.Instance.getFirstDocumentView(target); const position = targetView?.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); targetView && position && simulateMouseClick(targetView.ContentDiv!, position[0], position[1], position[0], position[1], false); @@ -168,4 +188,4 @@ export namespace Hypothesis { document.addEventListener('scrollSuccess', onSuccess); // listen for success message from client setTimeout(() => !success && clearInterval(interval), 10000); // give up if no success after 10s }; -} \ No newline at end of file +} diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 4e0b061a6..fe1190e27 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -250,8 +250,8 @@ $resizeHandler: 8px; .documentDecorations-lock { position: absolute; background: black; - right: 11; - top: 30px; + right: 23px; + top: 3px; color: gray; height: 14; width: 14; @@ -260,6 +260,7 @@ $resizeHandler: 8px; display: flex; align-items: center; flex-direction: column; + border-radius: 15%; } .documentDecorations-rotationPath { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 06eb6c6d7..8e0686038 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -782,6 +782,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth / 2) - this._resizeBorderWidth / 2 - borderRadiusDraggerWidth)); bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight)); + const useLock = bounds.r - bounds.x > 120; const useRotation = seldocview.rootDoc.type !== DocumentType.EQUATION; // when do we want an object to not rotate? const rotation = NumCast(seldocview.rootDoc._rotation); @@ -856,9 +857,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P onContextMenu={e => e.preventDefault()} /> )} -
e.preventDefault()}> - -
+ {!useLock ? null : ( +
e.preventDefault()}> + +
+ )} {hideDocumentButtonBar ? null : (
, document.getElementById('root')); + ReactDOM.createRoot(document.getElementById('root')!).render(); }, 0); })(); diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index c5ac1cf52..069206126 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -371,12 +371,3 @@ h1, width: 200px; height: 800px; } - -.mainVew-invisibleWebRef { - position: absolute; - left: 50; - top: 50; - display: block; - width: 500px; - height: 1000px; -} diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 052846e71..9648a7807 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -7,7 +7,7 @@ import { action, computed, configure, observable, reaction, runInAction } from ' import { observer } from 'mobx-react'; import 'normalize.css'; import * as React from 'react'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { ScriptField } from '../../fields/ScriptField'; import { DocCast, StrCast } from '../../fields/Types'; @@ -948,40 +948,6 @@ export class MainView extends React.Component { ); } - @computed get invisibleWebBox() { - // see note under the makeLink method in HypothesisUtils.ts - return !DocumentLinksButton.invisibleWebDoc ? null : ( -
- 500} - PanelHeight={() => 800} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - /> -
- ); - } - render() { return (
{this.snapLines} -
); } - - makeWebRef = (ele: HTMLDivElement) => { - reaction( - () => DocumentLinksButton.invisibleWebDoc, - invisibleDoc => { - ReactDOM.unmountComponentAtNode(ele); - invisibleDoc && - ReactDOM.render( - -
- 500} - PanelHeight={() => 800} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - /> -
- ; -
, - ele - ); - - let success = false; - const onSuccess = () => { - success = true; - clearTimeout(interval); - document.removeEventListener('editSuccess', onSuccess); - }; - - // For some reason, Hypothes.is annotations don't load until a click is registered on the page, - // so we keep simulating clicks until annotations have loaded and editing is successful - const interval = setInterval(() => !success && simulateMouseClick(ele, 50, 50, 50, 50), 500); - setTimeout(() => !success && clearInterval(interval), 10000); // give up if no success after 10s - document.addEventListener('editSuccess', onSuccess); - } - ); - }; } ScriptingGlobals.add(function selectMainMenu(doc: Doc, title: string) { diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index d9a989309..ce0e58d90 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -27,6 +27,7 @@ export interface MarqueeAnnotatorProps { mainCont: HTMLDivElement; docView: DocumentView; savedAnnotations: () => ObservableMap; + selectionText: () => string; annotationLayer: HTMLDivElement; addDocument: (doc: Doc) => boolean; getPageFromScroll?: (top: number) => number; @@ -147,7 +148,7 @@ export class MarqueeAnnotator extends React.Component { return marqueeAnno; } - const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, backgroundColor: 'transparent', title: 'Selection on ' + this.props.rootDoc.title }); + const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, text: this.props.selectionText(), backgroundColor: 'transparent', title: 'Selection on ' + this.props.rootDoc.title }); let minX = Number.MAX_VALUE; let maxX = -Number.MAX_VALUE; let minY = Number.MAX_VALUE; @@ -271,7 +272,8 @@ export class MarqueeAnnotator extends React.Component { width: `${this._width}px`, height: `${this._height}px`, border: `${this._width === 0 ? '' : '2px dashed black'}`, - }}/> + }} + /> ); } } diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 12f41394d..df1eb72ce 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -15,6 +15,7 @@ import { SearchBox } from './search/SearchBox'; import './SidebarAnnos.scss'; import { StyleProp } from './StyleProvider'; import React = require('react'); +import { RichTextField } from '../../fields/RichTextField'; interface ExtraProps { fieldKey: string; @@ -72,6 +73,27 @@ export class SidebarAnnos extends React.Component { FormattedTextBox.DontSelectInitialText = true; this.allMetadata.map(tag => (target[tag] = tag)); DocUtils.MakeLink({ doc: anchor }, { doc: target }, 'inline comment:comment on'); + + Doc.GetProto(target).text = new RichTextField( + JSON.stringify({ + doc: { + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, + content: [{ type: 'dashField', attrs: { fieldKey: 'text', docid: anchor[Id], hideKey: true, editable: false } }], + }, + { + type: 'paragraph', + attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, + }, + ], + }, + selection: { type: 'text', anchor: 4, head: 4 }, + }), + '' + ); this.addDocument(target); this._stackRef.current?.focusDocument(target, {}); return target; diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 869570a17..5abf4bde2 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -288,11 +288,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt 0 && - ((doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Pile) || [DocumentType.RTF, DocumentType.IMG, DocumentType.INK].includes(doc.type as DocumentType)) ? ( + return doc && isBackground() && !Doc.IsSystem(doc) && (props?.renderDepth || 0) > 0 ? (
toggleLockedPosition(doc)}>
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index ac3541f2c..78e44dfa2 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -89,6 +89,10 @@ position: relative; } +.lm_stack { + position: relative; +} + .lm_drag_tab { padding: 0; width: 15px !important; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index e9b41de25..92319d080 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -1,6 +1,6 @@ import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import * as GoldenLayout from '../../../client/goldenLayout'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; @@ -318,7 +318,7 @@ export class CollectionDockingView extends CollectionSubView() { } }; - componentDidMount: () => void = () => { + componentDidMount: () => void = async () => { if (this._containerRef.current) { this._lightboxReactionDisposer = reaction( () => LightboxView.LightboxDoc, @@ -335,8 +335,11 @@ export class CollectionDockingView extends CollectionSubView() { this._ignoreStateChange = ''; } ); - setTimeout(this.setupGoldenLayout); - //window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window + reaction( + () => 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 } + ); } }; @@ -460,7 +463,7 @@ export class CollectionDockingView extends CollectionSubView() { }; tabDestroyed = (tab: any) => { - if (![DocumentType.KVP, DocumentType.PRES].includes(tab.DashDoc?.type)) { + if (tab.DashDoc && ![DocumentType.KVP, DocumentType.PRES].includes(tab.DashDoc?.type)) { Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc); Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true); } @@ -470,8 +473,8 @@ export class CollectionDockingView extends CollectionSubView() { Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc); this.tabMap.delete(tab); tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); - tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele)); - setTimeout(this.stateChanged); + //tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele)); + this.stateChanged(); } }; tabCreated = (tab: any) => { diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index cde5132a3..31ed5a83b 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -4,7 +4,7 @@ import { Tooltip } from '@material-ui/core'; import { clamp } from 'lodash'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import { DataSym, Doc, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; @@ -143,8 +143,8 @@ export class TabDocView extends React.Component { const docIcon = ; const closeIcon = ; - ReactDOM.render(docIcon, iconWrap); - ReactDOM.render(closeIcon, closeWrap); + ReactDOM.createRoot(iconWrap).render(docIcon); + ReactDOM.createRoot(closeWrap).render(closeIcon); tab.reactComponents = [iconWrap, closeWrap]; tab.element[0].prepend(iconWrap); tab._disposers.layerDisposer = reaction( diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 1d455ad08..627487a9e 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -40,9 +40,6 @@ export class DocumentLinksButton extends React.Component; - public static invisibleWebRef = React.createRef(); - @action @undoBatch onLinkButtonMoved = (e: PointerEvent) => { diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index c7001f846..fcb3ccb07 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -74,7 +74,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent ); } - sidebarWidth = () => - !this.SidebarShown ? 0 : PDFBox.sidebarResizerWidth + (this._previewWidth ? PDFBox.openSidebarWidth : ((NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth()) / NumCast(this.layoutDoc.nativeWidth)); - + sidebarWidth = () => { + 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); + }; specificContextMenu = (e: React.MouseEvent): void => { const funcs: ContextMenuProps[] = []; funcs.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' }); @@ -483,7 +486,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent
-
+
- !this.SidebarShown ? 0 : WebBox.sidebarResizerWidth + (this._previewWidth ? WebBox.openSidebarWidth : ((NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth()) / NumCast(this.layoutDoc.nativeWidth)); - + sidebarWidth = () => { + if (!this.SidebarShown) return 0; + if (this._previewWidth) return WebBox.sidebarResizerWidth + WebBox.openSidebarWidth; // return default sidebar if previewing (as in viewing a link target) + const nativeDiff = NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc); + return WebBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1); + }; @computed get content() { const interactive = !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None && !DocumentDecorations.Instance?.Interacting; @@ -1000,6 +1003,7 @@ export class WebBox extends ViewBoxAnnotatableComponent{' '} @@ -1015,7 +1019,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this.sidebarBtnDown(e, false)} /> -
+
() { // style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}> // //
; - setTimeout(() => this.colorPicker(curColor)); // cause an update to the color picker rendered in MainView + //setTimeout(() => this.colorPicker(curColor)); // cause an update to the color picker rendered in MainView return (
(this.colorPickerClosed = !this.colorPickerClosed))} + onClick={action(e => { + this.colorPickerClosed = !this.colorPickerClosed; + e.stopPropagation(); + })} onPointerDown={e => e.stopPropagation()}> {this.Icon(color)}
@@ -395,7 +397,7 @@ export class FontIconBox extends DocComponent() { {/* {dropdownCaret} */} {this.colorPickerClosed ? null : (
-
e.stopPropagation()} onClick={e => e.stopPropagation()}> +
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onClick={e => e.stopPropagation()}> {this.colorPicker(curColor)}
, this.dom); + this.root = ReactDOM.createRoot(this.dom); + this.root.render(); (this as any).dom = this.dom; } destroy() { - ReactDOM.unmountComponentAtNode(this.dom); + // ReactDOM.unmountComponentAtNode(this.dom); } selectNode() {} diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 5f576be41..63ee7d1f3 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -1,7 +1,7 @@ import { action, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { NodeSelection } from 'prosemirror-state'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import { Doc, HeightSym, WidthSym } from '../../../../fields/Doc'; import { Cast, NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnFalse, Utils } from '../../../../Utils'; @@ -15,6 +15,7 @@ import React = require('react'); export class DashDocView { dom: HTMLSpanElement; // container for label and value + root: any; constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { this.dom = document.createElement('span'); @@ -38,14 +39,13 @@ export class DashDocView { e.stopPropagation(); }; - ReactDOM.render( -
}> , - ]; - - return this.getElement(buttons); + ]); } } diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx index 4895dcdc5..0fd2a7808 100644 --- a/src/client/views/nodes/formattedText/EquationView.tsx +++ b/src/client/views/nodes/formattedText/EquationView.tsx @@ -2,7 +2,7 @@ import EquationEditor from 'equation-editor-react'; import { IReactionDisposer } from 'mobx'; import { observer } from 'mobx-react'; import { TextSelection } from 'prosemirror-state'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import { Doc } from '../../../../fields/Doc'; import { StrCast } from '../../../../fields/Types'; import './DashFieldView.scss'; @@ -11,7 +11,7 @@ import React = require('react'); export class EquationView { dom: HTMLDivElement; // container for label and value - + root: any; constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { this.dom = document.createElement('div'); this.dom.style.width = node.attrs.width; @@ -22,13 +22,13 @@ export class EquationView { e.stopPropagation(); }; - ReactDOM.render(, this.dom); - (this as any).dom = this.dom; + this.root = ReactDOM.createRoot(this.dom); + this.root.render(); } _editor: EquationEditor | undefined; setEditor = (editor?: EquationEditor) => (this._editor = editor); destroy() { - ReactDOM.unmountComponentAtNode(this.dom); + // ReactDOM.unmountComponentAtNode(this.dom); } setSelection() { this._editor?.mathField.focus(); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 096f9a92c..0e48dd93a 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1132,6 +1132,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 47833dd43..dc5d8ada8 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -248,8 +248,7 @@ export class RichTextRules { // [[fieldKey:Doc]] => show field of doc new InputRule(new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), (state, match, start, end) => { const fieldKey = match[1]; - const rawdocid = match[3]; - const docid = rawdocid ? normalizeEmail(!rawdocid.includes('@') ? Doc.CurrentUserEmail + rawdocid : rawdocid.substring(1)) : undefined; + const docid = match[3]?.replace(':', ''); const value = match[2]?.substring(1); if (!fieldKey) { if (docid) { @@ -259,7 +258,7 @@ export class RichTextRules { if (rstate) { this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3)))); } - const target = (docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: rawdocid.replace(/^:/, ''), _width: 500, _height: 500 }, docid); + const target = (docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500 }, docid); DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, 'portal to:portal from', undefined); const fstate = this.TextBox.EditorView?.state; @@ -275,7 +274,7 @@ export class RichTextRules { const num = value.match(/^[0-9.]$/); this.Document[DataSym][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value; } - const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid }); + const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid, hideKey: true }); return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true); }), diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx index 01acc3de9..1fe6d822b 100644 --- a/src/client/views/nodes/formattedText/SummaryView.tsx +++ b/src/client/views/nodes/formattedText/SummaryView.tsx @@ -1,6 +1,6 @@ import { TextSelection } from 'prosemirror-state'; import { Fragment, Node, Slice } from 'prosemirror-model'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import React = require('react'); // an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked. @@ -9,6 +9,7 @@ import React = require('react'); // method instead of changing prosemirror's text when the expand/elide buttons are clicked. export class SummaryView { dom: HTMLSpanElement; // container for label and value + root: any; constructor(node: any, view: any, getPos: any) { const self = this; @@ -35,13 +36,13 @@ export class SummaryView { return js.apply(this, arguments); }; - ReactDOM.render(, this.dom); - (this as any).dom = this.dom; + this.root = ReactDOM.createRoot(this.dom); + this.root.render(); } className = (visible: boolean) => 'formattedTextBox-summarizer' + (visible ? '' : '-collapsed'); destroy() { - ReactDOM.unmountComponentAtNode(this.dom); + // ReactDOM.unmountComponentAtNode(this.dom); } selectNode() {} diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 66d747bf7..aa2475dca 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -263,6 +263,7 @@ export const nodes: { [index: string]: NodeSpec } = { fieldKey: { default: '' }, docid: { default: '' }, hideKey: { default: false }, + editable: { default: true }, }, group: 'inline', draggable: false, diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index 822af6a68..470aa3eb1 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -27,14 +27,14 @@ opacity: unset; mix-blend-mode: multiply; // bcz: makes text fuzzy! - span { - padding-right: 5px; - padding-bottom: 4px; - } + // span { + // padding-right: 5px; + // padding-bottom: 4px; + // } } .textLayer ::selection { - background: #ACCEF7; + background: #accef7; } // should match the backgroundColor in createAnnotation() @@ -111,4 +111,4 @@ .pdfViewerDash-interactive { pointer-events: all; -} \ No newline at end of file +} diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 5c10c7cef..abc7336bd 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -79,6 +79,8 @@ export class PDFViewer extends React.Component { private _initialScroll: Opt; private _forcedScroll = true; + selectionText = () => this._selectionText; + @observable isAnnotating = false; // key where data is stored @computed get allAnnotations() { @@ -358,7 +360,8 @@ export class PDFViewer extends React.Component { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; this.isAnnotating = true; - if (e.target && ((e.target as any).className.includes('endOfContent') || (e.target as any).parentElement.className !== 'textLayer')) { + const target = e.target as any; + if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.parentElement?.className !== 'textLayer'))) { this._textSelecting = false; document.addEventListener('pointermove', this.onSelectMove); // need this to prevent document from being dragged if stopPropagation doesn't get called } else { @@ -574,6 +577,7 @@ export class PDFViewer extends React.Component { docView={this.props.docViewPath().lastElement()} finishMarquee={this.finishMarquee} savedAnnotations={this.savedAnnotations} + selectionText={this.selectionText} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} anchorMenuCrop={this._textSelecting ? undefined : this.crop} diff --git a/src/debug/Repl.tsx b/src/debug/Repl.tsx index 1b12e208c..b8081648f 100644 --- a/src/debug/Repl.tsx +++ b/src/debug/Repl.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import { observer } from 'mobx-react'; import { observable, computed } from 'mobx'; import { CompileScript } from '../client/util/Scripting'; @@ -11,39 +11,40 @@ import { resolvedPorts } from '../client/util/CurrentUserUtils'; @observer class Repl extends React.Component { - @observable text: string = ""; + @observable text: string = ''; - @observable executedCommands: { command: string, result: any }[] = []; + @observable executedCommands: { command: string; result: any }[] = []; onChange = (e: React.ChangeEvent) => { this.text = e.target.value; - } + }; onKeyDown = (e: React.KeyboardEvent) => { - if (!e.ctrlKey && e.key === "Enter") { + if (!e.ctrlKey && e.key === 'Enter') { e.preventDefault(); const script = CompileScript(this.text, { - addReturn: true, typecheck: false, - params: { makeInterface: "any" } + addReturn: true, + typecheck: false, + params: { makeInterface: 'any' }, }); if (!script.compiled) { - this.executedCommands.push({ command: this.text, result: "Compile Error" }); + this.executedCommands.push({ command: this.text, result: 'Compile Error' }); } else { const result = script.run({ makeInterface }, e => this.executedCommands.push({ command: this.text, result: e.message || e })); result.success && this.executedCommands.push({ command: this.text, result: result.result }); } - this.text = ""; + this.text = ''; } - } + }; @computed get commands() { return this.executedCommands.map(command => { return ( -
+

{command.command}

{/*
{JSON.stringify(command.result, null, 2)}
*/} -
{command.result instanceof RefField || command.result instanceof ObjectField ? "object" : String(command.result)}
+
{command.result instanceof RefField || command.result instanceof ObjectField ? 'object' : String(command.result)}
); }); @@ -52,16 +53,14 @@ class Repl extends React.Component { render() { return (
-
- {this.commands} -
-