diff options
-rw-r--r-- | src/client/views/ScriptBox.tsx | 1 | ||||
-rw-r--r-- | src/client/views/collections/TabDocView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 7 | ||||
-rw-r--r-- | src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 144 | ||||
-rw-r--r-- | src/client/views/nodes/LinkDocPreview.tsx | 8 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 140 | ||||
-rw-r--r-- | src/fields/Doc.ts | 1 | ||||
-rw-r--r-- | src/fields/ScriptField.ts | 17 | ||||
-rw-r--r-- | src/server/websocket.ts | 4 |
9 files changed, 159 insertions, 165 deletions
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 2c185be86..e2b5d8dc3 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -12,7 +12,6 @@ import { CompileScript } from "../util/Scripting"; import { ScriptField } from "../../fields/ScriptField"; import { DragManager } from "../util/DragManager"; import { EditableView } from "./EditableView"; -import { getEffectiveTypeRoots } from "typescript"; export interface ScriptBoxProps { onSave: (text: string, onError: (error: string) => void) => void; diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 61650da6c..a8a95bf08 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -431,7 +431,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { // Doc.SetNativeWidth(this.props.Document[DataSym], wid); // Doc.SetNativeHeight(this.props.Document[DataSym], hgt); } - })) + })); // // a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab // diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ab0c3964b..fa84e5539 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -222,14 +222,17 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const zsorted = this.childLayoutPairs.map(pair => pair.layout).slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); zsorted.forEach((doc, index) => doc.zIndex = doc.isInkMask ? 5000 : index + 1); const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000)); - const dropPos = this.Document._currentFrame !== undefined ? [dvals.x, dvals.y] : [NumCast(refDoc.x), NumCast(refDoc.y)]; + const dropPos = this.Document._currentFrame !== undefined ? [dvals.x || 0, dvals.y || 0] : [NumCast(refDoc.x), NumCast(refDoc.y)]; for (let i = 0; i < docDragData.droppedDocuments.length; i++) { const d = docDragData.droppedDocuments[i]; const layoutDoc = Doc.Layout(d); if (this.Document._currentFrame !== undefined) { CollectionFreeFormDocumentView.setupKeyframes([d], this.Document._currentFrame, false); const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); - CollectionFreeFormDocumentView.setValues(this.Document._currentFrame, d, x + vals.x - dropPos[0], y + vals.y - dropPos[1], vals.h, vals.w, this.Document.editScrollProgressivize ? vals.scroll : undefined, vals.opacity); + vals.x = x + (vals.x || 0) - dropPos[0]; + vals.y = y + (vals.y || 0) - dropPos[1]; + vals._scrollTop = this.Document.editScrollProgressivize ? vals._scrollTop : undefined; + CollectionFreeFormDocumentView.setValues(this.Document._currentFrame, d, vals); } else { d.x = x + NumCast(d.x) - dropPos[0]; d.y = y + NumCast(d.y) - dropPos[1]; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 94793ffff..942182191 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,25 +1,23 @@ -import { computed, IReactionDisposer, observable, reaction, trace, action } from "mobx"; +import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, HeightSym, WidthSym } from "../../../fields/Doc"; +import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal'; +import { Doc, HeightSym, WidthSym, Opt } from "../../../fields/Doc"; +import { Document } from "../../../fields/documentSchemas"; +import { List } from "../../../fields/List"; +import { listSpec } from "../../../fields/Schema"; +import { ComputedField } from "../../../fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; +import { TraceMobx } from "../../../fields/util"; +import { numberRange, returnVal } from "../../../Utils"; +import { DocumentType } from "../../documents/DocumentTypes"; import { Transform } from "../../util/Transform"; import { DocComponent } from "../DocComponent"; +import { InkingStroke } from "../InkingStroke"; import "./CollectionFreeFormDocumentView.scss"; -import { DocumentView, DocumentViewProps } from "./DocumentView"; -import React = require("react"); -import { Document } from "../../../fields/documentSchemas"; -import { TraceMobx } from "../../../fields/util"; import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; -import { List } from "../../../fields/List"; -import { numberRange, smoothScroll, returnVal } from "../../../Utils"; -import { ComputedField } from "../../../fields/ScriptField"; -import { listSpec } from "../../../fields/Schema"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { Zoom, Fade, Flip, Rotate, Bounce, Roll, LightSpeed } from 'react-reveal'; +import { DocumentView, DocumentViewProps } from "./DocumentView"; import { PresBox, PresEffect } from "./PresBox"; -import { InkingStroke } from "../InkingStroke"; -import { SnappingManager } from "../../util/SnappingManager"; -import { InkTool } from "../../../fields/InkField"; +import React = require("react"); export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined; @@ -62,46 +60,24 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF @computed get nativeWidth() { return returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, undefined, this.freezeDimensions)); } @computed get nativeHeight() { return returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, undefined, this.freezeDimensions)); } + static animFields = ["_height", "_width", "x", "y", "_scrollTop", "opacity"]; public static getValues(doc: Doc, time: number) { - const timecode = Math.round(time); - return ({ - h: Cast(doc["h-indexed"], listSpec("number"), [NumCast(doc._height)]).reduce((p, h, i) => (i <= timecode && h !== undefined) || p === undefined ? h : p, undefined as any as number), - w: Cast(doc["w-indexed"], listSpec("number"), [NumCast(doc._width)]).reduce((p, w, i) => (i <= timecode && w !== undefined) || p === undefined ? w : p, undefined as any as number), - x: Cast(doc["x-indexed"], listSpec("number"), [NumCast(doc.x)]).reduce((p, x, i) => (i <= timecode && x !== undefined) || p === undefined ? x : p, undefined as any as number), - y: Cast(doc["y-indexed"], listSpec("number"), [NumCast(doc.y)]).reduce((p, y, i) => (i <= timecode && y !== undefined) || p === undefined ? y : p, undefined as any as number), - scroll: Cast(doc["scroll-indexed"], listSpec("number"), [NumCast(doc._scrollTop, 0)]).reduce((p, s, i) => (i <= timecode && s !== undefined) || p === undefined ? s : p, undefined as any as number), - opacity: Cast(doc["opacity-indexed"], listSpec("number"), [NumCast(doc.opacity, 1)]).reduce((p, o, i) => i <= timecode || p === undefined ? o : p, undefined as any as number), - }); + return CollectionFreeFormDocumentView.animFields.reduce((p, val) => { + p[val] = Cast(`${val}-indexed`, listSpec("number"), [NumCast(doc[val])]).reduce((p, v, i) => (i <= Math.round(time) && v !== undefined) || p === undefined ? v : p, undefined as any as number); + return p; + }, {} as { [val: string]: Opt<number> }); } - public static setValues(time: number, d: Doc, x?: number, y?: number, h?: number, w?: number, scroll?: number, opacity?: number) { + public static setValues(time: number, d: Doc, vals: { [val: string]: Opt<number> }) { const timecode = Math.round(time); - const hindexed = Cast(d["h-indexed"], listSpec("number"), []).slice(); - const windexed = Cast(d["w-indexed"], listSpec("number"), []).slice(); - const xindexed = Cast(d["x-indexed"], listSpec("number"), []).slice(); - const yindexed = Cast(d["y-indexed"], listSpec("number"), []).slice(); - const oindexed = Cast(d["opacity-indexed"], listSpec("number"), []).slice(); - const scrollIndexed = Cast(d["scroll-indexed"], listSpec("number"), []).slice(); - xindexed[timecode] = x as any as number; - yindexed[timecode] = y as any as number; - hindexed[timecode] = h as any as number; - windexed[timecode] = w as any as number; - oindexed[timecode] = opacity as any as number; - if (scroll) scrollIndexed[timecode] = scroll as any as number; - d["x-indexed"] = new List<number>(xindexed); - d["y-indexed"] = new List<number>(yindexed); - d["h-indexed"] = new List<number>(hindexed); - d["w-indexed"] = new List<number>(windexed); - d["opacity-indexed"] = new List<number>(oindexed); - d["scroll-indexed"] = new List<number>(scrollIndexed); - if (d.appearFrame) { - if (d.appearFrame === timecode + 1) { - d["text-color"] = "red"; - } else if (d.appearFrame < timecode + 1) { - d["text-color"] = "grey"; - } else { d["text-color"] = "black"; } - } else if (d.appearFrame === 0) { - } + Object.keys(vals).forEach(val => { + const findexed = Cast(d[`${val}-indexed`], listSpec("number"), []).slice(); + findexed[timecode] = vals[val] as any as number; + d[`${val}-indexed`] = new List<number>(findexed); + }); + d.appearFrame && (d["text-color"] = + d.appearFrame === timecode + 1 ? "red" : + d.appearFrame < timecode + 1 ? "grey" : "black"); } // public static updateScrollframe(doc: Doc, time: number) { @@ -123,37 +99,26 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF public static updateKeyframe(docs: Doc[], time: number, targetDoc?: Doc) { const timecode = Math.round(time); - docs.forEach(doc => { - const xindexed = Cast(doc['x-indexed'], listSpec("number"), null); - const yindexed = Cast(doc['y-indexed'], listSpec("number"), null); - const hindexed = Cast(doc['h-indexed'], listSpec("number"), null); - const windexed = Cast(doc['w-indexed'], listSpec("number"), null); - const opacityindexed = Cast(doc['opacity-indexed'], listSpec("number"), null); - hindexed?.length <= timecode + 1 && hindexed.push(undefined as any as number); - windexed?.length <= timecode + 1 && windexed.push(undefined as any as number); - xindexed?.length <= timecode + 1 && xindexed.push(undefined as any as number); - yindexed?.length <= timecode + 1 && yindexed.push(undefined as any as number); - opacityindexed?.length <= timecode + 1 && opacityindexed.push(undefined as any as number); - if (doc.appearFrame && targetDoc) { - if (doc.appearFrame === timecode + 1) { - doc["text-color"] = StrCast(targetDoc["pres-text-color"]); - } else if (doc.appearFrame < timecode + 1) { - doc["text-color"] = StrCast(targetDoc["pres-text-viewed-color"]); - } else { doc["text-color"] = "black"; } - } else if (doc.appearFrame === 0) { - doc["text-color"] = "black"; - } - doc.dataTransition = "all 1s"; - }); - setTimeout(() => docs.forEach(doc => doc.dataTransition = "inherit"), 1010); + docs.forEach(action(doc => { + doc._viewTransition = doc.dataTransition = "all 1s"; + doc["text-color"] = + !doc.appearFrame || !targetDoc ? "black" : + doc.appearFrame === timecode + 1 ? StrCast(targetDoc["pres-text-color"]) : + doc.appearFrame < timecode + 1 ? StrCast(targetDoc["pres-text-viewed-color"]) : + "black"; + CollectionFreeFormDocumentView.animFields.forEach(val => { + const findexed = Cast(doc[`${val}-indexed`], listSpec("number"), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any as number); + }); + })); + setTimeout(() => docs.forEach(doc => { doc._viewTransition = undefined; doc.dataTransition = "inherit"; }), 1010); } public static gotoKeyframe(docs: Doc[]) { - docs.forEach(doc => doc.dataTransition = "all 1s"); - setTimeout(() => docs.forEach(doc => doc.dataTransition = "inherit"), 1010); + docs.forEach(doc => doc._viewTransition = doc.dataTransition = "all 1s"); + setTimeout(() => docs.forEach(doc => { doc._viewTransition = undefined; doc.dataTransition = "inherit" }), 1010); } - public static setupZoom(doc: Doc, targDoc: Doc) { const width = new List<number>(); const height = new List<number>(); @@ -172,27 +137,12 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF public static setupKeyframes(docs: Doc[], currTimecode: number, makeAppear: boolean = false) { docs.forEach(doc => { if (doc.appearFrame === undefined) doc.appearFrame = currTimecode; - const curTimecode = currTimecode; - const xlist = new List<number>(numberRange(currTimecode + 1).map(i => undefined) as any as number[]); - const ylist = new List<number>(numberRange(currTimecode + 1).map(i => undefined) as any as number[]); - const wlist = new List<number>(numberRange(currTimecode + 1).map(i => undefined) as any as number[]); - const hlist = new List<number>(numberRange(currTimecode + 1).map(i => undefined) as any as number[]); - const olist = new List<number>(numberRange(currTimecode + 1).map(t => !doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1)); - wlist[curTimecode] = NumCast(doc._width); - hlist[curTimecode] = NumCast(doc._height); - xlist[curTimecode] = NumCast(doc.x); - ylist[curTimecode] = NumCast(doc.y); - doc["x-indexed"] = xlist; - doc["y-indexed"] = ylist; - doc["w-indexed"] = wlist; - doc["h-indexed"] = hlist; - doc["opacity-indexed"] = olist; + if (!doc["opacity-indexed"]) { // opacity is unlike other fields because it's value should not be undefined before it appears to enable it to fade-in + const olist = new List<number>(numberRange(currTimecode + 1).map(t => !doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1)); + doc["opacity-indexed"] = olist; + } + CollectionFreeFormDocumentView.animFields.forEach(val => doc[val] = ComputedField.MakeInterpolated(val, "activeFrame", doc, currTimecode)); doc.activeFrame = ComputedField.MakeFunction("self.context?._currentFrame||0"); - doc._height = ComputedField.MakeInterpolated("h", "activeFrame"); - doc._width = ComputedField.MakeInterpolated("w", "activeFrame"); - doc.x = ComputedField.MakeInterpolated("x", "activeFrame"); - doc.y = ComputedField.MakeInterpolated("y", "activeFrame"); - doc.opacity = ComputedField.MakeInterpolated("opacity", "activeFrame"); doc.dataTransition = "inherit"; }); } diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 0256d394e..c4bb2b8d8 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -12,6 +12,7 @@ import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; import { DocumentLinksButton } from './DocumentLinksButton'; import React = require("react"); import { DocumentViewProps } from './DocumentView'; +import { Id } from '../../../fields/FieldSymbols'; interface Props { linkDoc?: Doc; @@ -64,8 +65,11 @@ export class LinkDocPreview extends React.Component<Props> { runInAction(() => { this._toolTipText = ""; LinkDocPreview.TargetDoc = this._targetDoc = target; - if (anchor !== this._targetDoc && anchor && this._targetDoc) { - this._targetDoc._scrollPreviewY = NumCast(anchor?.y); + if (this._targetDoc) { + this._targetDoc._scrollToPreviewLinkID = linkDoc?.[Id]; + if (anchor !== this._targetDoc && anchor) { + this._targetDoc._scrollPreviewY = NumCast(anchor?.y); + } } }); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 125447f28..6768acce4 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -61,6 +61,7 @@ import { DocumentManager } from '../../../util/DocumentManager'; import { CollectionStackingView } from '../../collections/CollectionStackingView'; import { CollectionViewType } from '../../collections/CollectionView'; import { SnappingManager } from '../../../util/SnappingManager'; +import { LinkDocPreview } from '../LinkDocPreview'; export interface FormattedTextBoxProps { makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text @@ -98,6 +99,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp private _currentTime: number = 0; private _linkTime: number | null = null; private _pause: boolean = false; + private _animatingScroll: numher = 0; // hack to prevent scroll values from being written to document when scroll is animating @computed get _recording() { return this.dataDoc?.audioState === "recording"; } set _recording(value) { @@ -815,6 +817,50 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return this.active();//this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0; } + scrollToLinkId = (linkId: string) => { + const findLinkFrag = (frag: Fragment, editor: EditorView) => { + const nodes: Node[] = []; + let hadStart = start !== 0; + frag.forEach((node, index) => { + const examinedNode = findLinkNode(node, editor); + if (examinedNode?.node.textContent) { + nodes.push(examinedNode.node); + !hadStart && (start = index + examinedNode.start); + hadStart = true; + } + }); + return { frag: Fragment.fromArray(nodes), start }; + }; + const findLinkNode = (node: Node, editor: EditorView) => { + if (!node.isText) { + const content = findLinkFrag(node.content, editor); + return { node: node.copy(content.frag), start: content.start }; + } + const marks = [...node.marks]; + const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor); + return linkIndex !== -1 && marks[linkIndex].attrs.allLinks.find((item: { href: string }) => linkId === item.href.replace(/.*\/doc\//, "")) ? { node, start: 0 } : undefined; + }; + + let start = 0; + if (this._editorView && linkId) { + const editor = this._editorView; + const ret = findLinkFrag(editor.state.doc.content, editor); + + if (ret.frag.size > 2 && ret.start >= 0) { + let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start + if (ret.frag.firstChild) { + selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected + } + editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); + const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight); + setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0); + setTimeout(() => this.unhighlightSearchTerms(), 2000); + } + Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false); + Doc.SetInPlace(this.layoutDoc, "_scrollToPreviewLinkID", undefined, false); + } + } + componentDidMount() { this.props.contentsActive?.(this.IsActive); this._cachedLinks = DocListCast(this.Document.links); @@ -910,7 +956,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp search => search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms(), { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false }); - this._disposers.selected = reaction(() => this.props.isSelected(), action(() => this._recording = false)); + this._disposers.selected = reaction(() => this.props.isSelected(), + action((selected) => { + this._recording = false; + if (RichTextMenu.Instance?.view === this._editorView && !selected) { + RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined); + } + })); if (!this.props.dontRegisterView) { this._disposers.record = reaction(() => this._recording, @@ -924,65 +976,36 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } ); } - this._disposers.scrollToRegion = reaction( - () => StrCast(this.layoutDoc.scrollToLinkID), - async (scrollToLinkID) => { - const findLinkFrag = (frag: Fragment, editor: EditorView) => { - const nodes: Node[] = []; - let hadStart = start !== 0; - frag.forEach((node, index) => { - const examinedNode = findLinkNode(node, editor); - if (examinedNode?.node.textContent) { - nodes.push(examinedNode.node); - !hadStart && (start = index + examinedNode.start); - hadStart = true; - } - }); - return { frag: Fragment.fromArray(nodes), start }; - }; - const findLinkNode = (node: Node, editor: EditorView) => { - if (!node.isText) { - const content = findLinkFrag(node.content, editor); - return { node: node.copy(content.frag), start: content.start }; - } - const marks = [...node.marks]; - const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor); - return linkIndex !== -1 && marks[linkIndex].attrs.allLinks.find((item: { href: string }) => scrollToLinkID === item.href.replace(/.*\/doc\//, "")) ? { node, start: 0 } : undefined; - }; - - let start = 0; - if (this._editorView && scrollToLinkID) { - const editor = this._editorView; - const ret = findLinkFrag(editor.state.doc.content, editor); - - if (ret.frag.size > 2 && ret.start >= 0) { - let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start - if (ret.frag.firstChild) { - selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected - } - editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); - const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight); - setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0); - setTimeout(() => this.unhighlightSearchTerms(), 2000); - } - Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false); - } - - }, - { fireImmediately: true } + this._disposers.previewScrollToRegion = reaction(() => StrCast(this.layoutDoc._scrollToPreviewLinkID), + scrollToLinkID => this.props.renderDepth === -1 && scrollToLinkID && this.scrollToLinkId(scrollToLinkID), { fireImmediately: true } + ); + this._disposers.scrollToRegion = reaction(() => StrCast(this.layoutDoc.scrollToLinkID), + scrollToLinkID => scrollToLinkID && this.scrollToLinkId(scrollToLinkID), { fireImmediately: true } ); this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop), - pos => this._scrollRef.current?.scrollTo({ top: pos }), { fireImmediately: true } + pos => { + const durationMiliStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/); + const durationSecStr = StrCast(this.Document._viewTransition).match(/([0-9.]*)s/); + const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0; + if (duration) { + this._animatingScroll++; + this._scrollRef.current && smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0)); + setTimeout(() => this._animatingScroll--, duration); + } else { + this._scrollRef.current?.scrollTo({ top: pos }); + } + }, { fireImmediately: true } ); this._disposers.scrollY = reaction(() => Cast(this.layoutDoc._scrollY, "number", null), - scrollY => { - if (scrollY !== undefined) { - const delay = this._scrollRef.current ? 0 : 250; // wait for mainCont and try again to scroll - const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/); - const duration = durationStr ? Number(durationStr[1]) : 1000; - setTimeout(() => this._scrollRef.current && smoothScroll(duration, this._scrollRef.current, Math.abs(scrollY || 0)), delay); - setTimeout(() => { this.Document._scrollTop = scrollY; this.Document._scrollY = undefined; }, duration + delay); - } + pos => { + this.Document._scrollY = undefined; + pos !== undefined && setTimeout(() => this.layoutDoc._scrollTop = pos, this._scrollRef.current ? 0 : 250); + }, { fireImmediately: true } + ); + this._disposers.scrollPreviewY = reaction(() => Cast(this.layoutDoc.scrollPreviewY, "number", null), + pos => { + this.Document.scrollPreviewY = undefined; + pos !== undefined && setTimeout(() => this.layoutDoc._scrollTop = pos, this._scrollRef.current ? 0 : 250); }, { fireImmediately: true } ); @@ -1233,6 +1256,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp componentWillUnmount() { this.endUndoTypingBatch(); + this.unhighlightSearchTerms(); Object.values(this._disposers).forEach(disposer => disposer?.()); this._editorView?.destroy(); FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none"); @@ -1529,7 +1553,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp eve.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash. } onscrolled = (ev: React.UIEvent) => { - this.layoutDoc._scrollTop = this._scrollRef.current!.scrollTop; + if (!LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && !this._animatingScroll) { + this.layoutDoc._scrollTop = this._scrollRef.current!.scrollTop; + } } @action tryUpdateHeight(limitHeight?: number) { diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index ffb1bbf83..fdea47c84 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -36,6 +36,7 @@ export namespace Field { export function toScriptString(field: Field): string { if (typeof field === "string") return `"${field}"`; if (typeof field === "number" || typeof field === "boolean") return String(field); + if (field === undefined || field === null) return "null"; return field[ToScriptString](); } export function toString(field: Field): string { diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index 024017302..c949e0791 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -7,7 +7,9 @@ import { Doc, Field, Opt } from "./Doc"; import { Plugins, setter } from "./util"; import { computedFn } from "mobx-utils"; import { ProxyField } from "./Proxy"; -import { Cast } from "./Types"; +import { Cast, NumCast } from "./Types"; +import { List } from "./List"; +import { numberRange } from "../Utils"; function optional(propSchema: PropSchema) { return custom(value => { @@ -188,12 +190,21 @@ export class ComputedField extends ScriptField { const compiled = ScriptField.CompileScript(script, params, true, capturedVariables); return compiled.compiled ? new ComputedField(compiled) : undefined; } - public static MakeInterpolated(fieldKey: string, interpolatorKey: string) { + public static MakeInterpolated(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number) { + if (!doc[`${fieldKey}-indexed`]) { + const flist = new List<number>(numberRange(curTimecode + 1).map(i => undefined) as any as number[]); + flist[curTimecode] = NumCast(doc[fieldKey]); + doc[`${fieldKey}-indexed`] = flist; + } const getField = ScriptField.CompileScript(`getIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey})`, {}, true, {}); - const setField = ScriptField.CompileScript(`(self['${fieldKey}-indexed'])[self.${interpolatorKey}] = value`, { value: "any" }, true, {}); + const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: "any" }, true, {}); return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined; } } +Scripting.addGlobal(function setIndexVal(list: any[], index: number, value: any) { + while (list.length <= index) list.push(undefined); + list[index] = value; +}, "sets the value at a given index of a list", "(list: any[], index: number, value: any)"); Scripting.addGlobal(function getIndexVal(list: any[], index: number) { return list?.reduce((p, x, i) => (i <= index && x !== undefined) || p === undefined ? x : p, undefined as any); diff --git a/src/server/websocket.ts b/src/server/websocket.ts index 7d111f359..6850f0601 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -279,13 +279,13 @@ export namespace WebSocket { const updatefield = Array.from(Object.keys(diff.diff.$set))[0]; const newListItems = diff.diff.$set[updatefield].fields; const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields.filter((item: any) => item !== undefined) || []; - diff.diff.$set[updatefield].fields = [...curList, ...newListItems.filter((newItem: any) => newItem && !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))]; + diff.diff.$set[updatefield].fields = [...curList, ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))]; const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length; delete diff.diff.length; Database.Instance.update(diff.id, diff.diff, () => { if (sendBack) { - console.log("RET BACK"); + console.log("Warning: list modified during update. Composite list is being returned."); const id = socket.id; socket.id = ""; socket.broadcast.emit(MessageStore.UpdateField.Message, diff); |