diff options
Diffstat (limited to 'src/client/views/nodes')
| -rw-r--r-- | src/client/views/nodes/AudioBox.tsx | 92 | ||||
| -rw-r--r-- | src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 232 | ||||
| -rw-r--r-- | src/client/views/nodes/ContentFittingDocumentView.tsx | 1 | ||||
| -rw-r--r-- | src/client/views/nodes/DocHolderBox.tsx | 4 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 52 | ||||
| -rw-r--r-- | src/client/views/nodes/FieldView.tsx | 3 | ||||
| -rw-r--r-- | src/client/views/nodes/FilterBox.tsx | 1 | ||||
| -rw-r--r-- | src/client/views/nodes/FontIconBox.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/nodes/KeyValuePair.tsx | 1 | ||||
| -rw-r--r-- | src/client/views/nodes/LinkBox.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/nodes/LinkDocPreview.tsx | 11 | ||||
| -rw-r--r-- | src/client/views/nodes/PresBox.tsx | 39 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/DashDocView.tsx | 1 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 142 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx | 1 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/RichTextSchema.tsx | 1 |
16 files changed, 251 insertions, 334 deletions
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 447668ee8..c89e21312 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -1,34 +1,34 @@ import React = require("react"); -import { FieldViewProps, FieldView } from './FieldView'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import axios from "axios"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import "./AudioBox.scss"; -import { Cast, DateCast, NumCast, FieldValue, ScriptCast } from "../../../fields/Types"; -import { AudioField, nullAudio } from "../../../fields/URLField"; -import { ViewBoxAnnotatableComponent } from "../DocComponent"; -import { makeInterface, createSchema } from "../../../fields/Schema"; -import { documentSchema } from "../../../fields/documentSchemas"; -import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent, returnFalse, returnZero, formatTime, setupMoveUpEvents } from "../../../Utils"; -import { runInAction, observable, reaction, IReactionDisposer, computed, action, trace, toJS } from "mobx"; +import Waveform from "react-audio-waveform"; import { DateField } from "../../../fields/DateField"; -import { SelectionManager } from "../../util/SelectionManager"; import { Doc, DocListCast, Opt } from "../../../fields/Doc"; -import { ContextMenuProps } from "../ContextMenuItem"; -import { ContextMenu } from "../ContextMenu"; +import { documentSchema } from "../../../fields/documentSchemas"; import { Id } from "../../../fields/FieldSymbols"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { DocumentView, DocumentViewProps } from "./DocumentView"; -import { Docs, DocUtils } from "../../documents/Documents"; +import { List } from "../../../fields/List"; +import { createSchema, listSpec, makeInterface } from "../../../fields/Schema"; import { ComputedField, ScriptField } from "../../../fields/ScriptField"; +import { Cast, DateCast, NumCast } from "../../../fields/Types"; +import { AudioField, nullAudio } from "../../../fields/URLField"; +import { emptyFunction, formatTime, returnFalse, returnOne, returnTrue, setupMoveUpEvents, Utils, numberRange } from "../../../Utils"; +import { Docs, DocUtils } from "../../documents/Documents"; import { Networking } from "../../Network"; -import { LinkAnchorBox } from "./LinkAnchorBox"; -import { List } from "../../../fields/List"; +import { CurrentUserUtils } from "../../util/CurrentUserUtils"; import { Scripting } from "../../util/Scripting"; -import Waveform from "react-audio-waveform"; -import axios from "axios"; +import { SelectionManager } from "../../util/SelectionManager"; import { SnappingManager } from "../../util/SnappingManager"; -import { CurrentUserUtils } from "../../util/CurrentUserUtils"; -import { LinkDocPreview } from "./LinkDocPreview"; +import { ContextMenu } from "../ContextMenu"; +import { ContextMenuProps } from "../ContextMenuItem"; +import { ViewBoxAnnotatableComponent } from "../DocComponent"; +import "./AudioBox.scss"; +import { DocumentView, DocumentViewProps } from "./DocumentView"; +import { FieldView, FieldViewProps } from './FieldView'; import { FormattedTextBoxComment } from "./formattedText/FormattedTextBoxComment"; +import { LinkAnchorBox } from "./LinkAnchorBox"; +import { LinkDocPreview } from "./LinkDocPreview"; declare class MediaRecorder { // whatever MediaRecorder has @@ -43,6 +43,7 @@ const AudioDocument = makeInterface(documentSchema, audioSchema); export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioDocument>(AudioDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); } public static Enabled = false; + public static NUMBER_OF_BUCKETS = 100; static Instance: AudioBox; static RangeScript: ScriptField; @@ -76,7 +77,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD @observable _visible: boolean = false; @observable _markerEnd: number = 0; @observable _position: number = 0; - @observable _buckets: Array<number> = new Array<number>(); @observable _waveHeight: Opt<number> = this.layoutDoc._height; @observable private _paused: boolean = false; @observable private static _scrubTime = 0; @@ -508,6 +508,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD // returns the audio waveform @computed get waveform() { + const audioBuckets = Cast(this.dataDoc.audioBuckets, listSpec("number"), []); + !audioBuckets.length && setTimeout(() => this.createWaveformBuckets()); return <Waveform color={"darkblue"} height={this._waveHeight} @@ -515,44 +517,23 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD // pos={this.layoutDoc._currentTimecode} need to correctly resize parent to make this work (not very necessary for function) pos={this.audioDuration} duration={this.audioDuration} - peaks={this._buckets.length === 100 ? this._buckets : undefined} + peaks={audioBuckets.length === AudioBox.NUMBER_OF_BUCKETS ? audioBuckets : undefined} progressColor={"blue"} />; } // decodes the audio file into peaks for generating the waveform - @action - buckets = async () => { - const audioCtx = new (window.AudioContext)(); - + createWaveformBuckets = async () => { axios({ url: this.path, responseType: "arraybuffer" }) - .then(response => { - const audioData = response.data; - - audioCtx.decodeAudioData(audioData, action(buffer => { + .then(response => (new (window.AudioContext)()).decodeAudioData(response.data, + action(buffer => { const decodedAudioData = buffer.getChannelData(0); - const NUMBER_OF_BUCKETS = 100; - const bucketDataSize = Math.floor(decodedAudioData.length / NUMBER_OF_BUCKETS); - - for (let i = 0; i < NUMBER_OF_BUCKETS; i++) { - const startingPoint = i * bucketDataSize; - const endingPoint = i * bucketDataSize + bucketDataSize; - let max = 0; - for (let j = startingPoint; j < endingPoint; j++) { - if (decodedAudioData[j] > max) { - max = decodedAudioData[j]; - } - } - const size = Math.abs(max); - this._buckets.push(size / 2); - } - - })); - }); - } - - // Returns the peaks of the audio waveform - @computed get peaks() { - return this.buckets(); + const bucketDataSize = Math.floor(decodedAudioData.length / AudioBox.NUMBER_OF_BUCKETS); + const brange = Array.from(Array(bucketDataSize)); + this.dataDoc.audioBuckets = new List<number>( + numberRange(AudioBox.NUMBER_OF_BUCKETS).map(i => + brange.reduce((p, x, j) => Math.abs(Math.max(p, decodedAudioData[i * bucketDataSize + j])), 0) / 2)); + })) + ); } rangeScript = () => AudioBox.RangeScript; @@ -561,7 +542,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD render() { const interactive = SnappingManager.GetIsDragging() || this.active() ? "-interactive" : ""; this._first = true; // for indicating the first marker that is rendered - this.path && this._buckets.length !== 100 ? this.peaks : null; // render waveform if audio is done recording const markerDoc = (mark: Doc, script: undefined | (() => ScriptField)) => { return <DocumentView {...this.props} Document={mark} @@ -596,7 +576,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD : <button className={`audiobox-record${interactive}`} style={{ backgroundColor: "black" }}> RECORD - </button>} + </button>} </div> : <div className="audiobox-controls" > <div className="audiobox-dictation"></div> diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 94793ffff..b4b887617 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,25 +1,21 @@ -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 { Doc, HeightSym, Opt, WidthSym } 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 { ContentFittingDocumentView } from "./ContentFittingDocumentView"; 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 { PresBox, PresEffect } from "./PresBox"; -import { InkingStroke } from "../InkingStroke"; -import { SnappingManager } from "../../util/SnappingManager"; -import { InkTool } from "../../../fields/InkField"; 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,98 +58,48 @@ 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) { - // console.log('update scroll frame'); - // const timecode = Math.round(time); - // const scrollIndexed = Cast(doc['scroll-indexed'], listSpec("number"), null); - // scrollIndexed?.length <= timecode + 1 && scrollIndexed.push(undefined as any as number); - // setTimeout(() => doc.dataTransition = "inherit", 1010); - // } - - // public static setupScroll(doc: Doc, timecode: number) { - // const scrollList = new List<number>(); - // scrollList[timecode] = NumCast(doc._scrollTop); - // doc["scroll-indexed"] = scrollList; - // doc.activeFrame = ComputedField.MakeFunction("self._currentFrame"); - // doc._scrollTop = ComputedField.MakeInterpolated("scroll", "activeFrame"); - // } - - 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 +118,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"; }); } @@ -201,46 +132,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF this.props.Document.x = NumCast(this.props.Document.x) + x; this.props.Document.y = NumCast(this.props.Document.y) + y; } - - @computed get freeformNodeDiv() { - const node = <DocumentView {...this.props} - nudge={this.nudge} - dragDivName={"collectionFreeFormDocumentView-container"} - ContentScaling={this.contentScaling} - ScreenToLocalTransform={this.getTransform} - styleProvider={this.props.styleProvider} - opacity={this.opacity} - layerProvider={this.props.layerProvider} - NativeHeight={this.NativeHeight} - NativeWidth={this.NativeWidth} - PanelWidth={this.panelWidth} - PanelHeight={this.panelHeight} />; - if (PresBox.Instance && this.layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc) { - const effectProps = { - left: this.layoutDoc.presEffectDirection === PresEffect.Left, - right: this.layoutDoc.presEffectDirection === PresEffect.Right, - top: this.layoutDoc.presEffectDirection === PresEffect.Top, - bottom: this.layoutDoc.presEffectDirection === PresEffect.Bottom, - opposite: true, - delay: this.layoutDoc.presTransition, - // when: this.layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc, - }; - switch (this.layoutDoc.presEffect) { - case "Zoom": return (<Zoom {...effectProps}>{node}</Zoom>); break; - case PresEffect.Fade: return (<Fade {...effectProps}>{node}</Fade>); break; - case PresEffect.Flip: return (<Flip {...effectProps}>{node}</Flip>); break; - case PresEffect.Rotate: return (<Rotate {...effectProps}>{node}</Rotate>); break; - case PresEffect.Bounce: return (<Bounce {...effectProps}>{node}</Bounce>); break; - case PresEffect.Roll: return (<Roll {...effectProps}>{node}</Roll>); break; - case "LightSpeed": return (<LightSpeed {...effectProps}>{node}</LightSpeed>); break; - case PresEffect.None: return node; break; - default: return node; break; - } - } else { - return node; - } - } - contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox && !this.freezeDimensions ? this.width / this.nativeWidth : 1; panelWidth = () => (this.sizeProvider?.width || this.props.PanelWidth?.()); panelHeight = () => (this.sizeProvider?.height || this.props.PanelHeight?.()); @@ -251,18 +142,30 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF NativeHeight = () => this.nativeHeight; @computed get pointerEvents() { if (this.props.pointerEvents === "none") return "none"; - return this.props.styleProvider?.(this.Document, this.props, !this._contentView?.docView?.isSelected() ? "pointerEvents:selected" : "pointerEvents", this.props.layerProvider); + return this.props.styleProvider?.(this.Document, this.props, !this._contentView?.docView?.isSelected() ? "pointerEvents:selected" : "pointerEvents"); } render() { TraceMobx(); - const backgroundColor = this.props.styleProvider?.(this.Document, this.props, "backgroundColor", this.props.layerProvider); + const backgroundColor = this.props.styleProvider?.(this.Document, this.props, "backgroundColor"); const borderRounding = StrCast(Doc.Layout(this.layoutDoc).borderRounding) || StrCast(this.layoutDoc.borderRounding) || StrCast(this.Document.borderRounding) || undefined; + + const divProps = { + ...this.props, + nudge: this.nudge, + dragDivName: "collectionFreeFormDocumentView-container", + opacity: this.opacity, + ScreenToLocalTransform: this.getTransform, + NativeHeight: this.NativeHeight, + NativeWidth: this.NativeWidth, + PanelWidth: this.panelWidth, + PanelHeight: this.panelHeight + }; return <div className="collectionFreeFormDocumentView-container" style={{ boxShadow: this.Opacity === 0 ? undefined : // if it's not visible, then no shadow this.layoutDoc.z ? `#9c9396 ${StrCast(this.layoutDoc.boxShadow, "10px 10px 0.9vw")}` : // if it's a floating doc, give it a big shadow - this.props.backgroundHalo?.(this.props.Document) && this.props.Document.type !== DocumentType.INK ? (`${this.props.styleProvider?.(this.props.Document, this.props, "backgroundColor", this.props.layerProvider)} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(Cast(this.layoutDoc.layers, listSpec("string"), []).includes("background") ? 100 : 50) / this.props.ContentScaling()}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent + this.props.backgroundHalo?.(this.props.Document) && this.props.Document.type !== DocumentType.INK ? (`${this.props.styleProvider?.(this.props.Document, this.props, "backgroundColor")} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(Cast(this.layoutDoc.layers, listSpec("string"), []).includes("background") ? 100 : 50) / this.props.ContentScaling()}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent Cast(this.layoutDoc.layers, listSpec("string"), []).includes('background') ? undefined : // if it's a background & has a cluster color, make the shadow spread really big StrCast(this.layoutDoc.boxShadow, ""), borderRadius: borderRounding, @@ -286,18 +189,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF </svg> </div>} - {!this.props.fitToBox ? - <>{this.freeformNodeDiv}</> - : <ContentFittingDocumentView {...this.props} - ref={action((r: ContentFittingDocumentView | null) => this._contentView = r)} - ContainingCollectionDoc={this.props.ContainingCollectionDoc} - DataDoc={this.props.DataDoc} - ScreenToLocalTransform={this.getTransform} - NativeHeight={this.NativeHeight} - NativeWidth={this.NativeWidth} - PanelWidth={this.panelWidth} - PanelHeight={this.panelHeight} - />} + {this.props.fitToBox ? + <ContentFittingDocumentView {...divProps} ref={action((r: ContentFittingDocumentView | null) => this._contentView = r)} /> : + <DocumentView {...divProps} ContentScaling={this.contentScaling} />} </div>; } } diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index a54479f55..74d7cb24e 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -71,7 +71,6 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp DataDoc={this.props.DataDoc} LayoutTemplate={this.props.LayoutTemplate} LayoutTemplateString={this.props.LayoutTemplateString} - LibraryPath={this.props.LibraryPath} PanelWidth={this.PanelWidth} PanelHeight={this.PanelHeight} ContentScaling={returnOne} diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx index f0a1a3278..e14093e70 100644 --- a/src/client/views/nodes/DocHolderBox.tsx +++ b/src/client/views/nodes/DocHolderBox.tsx @@ -118,7 +118,6 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do <DocumentView Document={containedDoc} DataDoc={undefined} - LibraryPath={emptyPath} docFilters={this.props.docFilters} docRangeFilters={this.props.docRangeFilters} searchFilterDocs={this.props.searchFilterDocs} @@ -147,7 +146,6 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do <ContentFittingDocumentView Document={containedDoc} DataDoc={undefined} - LibraryPath={emptyPath} docFilters={this.props.docFilters} docRangeFilters={this.props.docRangeFilters} searchFilterDocs={this.props.searchFilterDocs} @@ -184,7 +182,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do onContextMenu={this.specificContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} style={{ - background: this.props.styleProvider?.(containedDoc, this.props, "backgroundColor", this.props.layerProvider), + background: this.props.styleProvider?.(containedDoc, this.props, "backgroundColor"), border: `#00000021 solid ${this.xPad}px`, borderTop: `#0000005e solid ${this.yPad}px`, borderBottom: `#0000005e solid ${this.yPad}px`, diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 8cf274651..5555c30e4 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,7 +1,6 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym, StrListCast } from "../../../fields/Doc"; +import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt } from "../../../fields/Doc"; import { Document } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; @@ -12,7 +11,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Ty import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util'; import { MobileInterface } from '../../../mobile/MobileInterface'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; -import { emptyFunction, OmitKeys, returnOne, returnTransparent, returnVal, Utils, returnFalse, returnTrue } from "../../../Utils"; +import { emptyFunction, OmitKeys, returnFalse, returnOne, returnTrue, returnVal, Utils } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; @@ -38,6 +37,7 @@ import { DocumentLinksButton } from './DocumentLinksButton'; import "./DocumentView.scss"; import { LinkAnchorBox } from './LinkAnchorBox'; import { LinkDescriptionPopup } from './LinkDescriptionPopup'; +import { PresBox } from './PresBox'; import { RadialMenu } from './RadialMenu'; import { TaskCompletionBox } from './TaskCompletedBox'; import React = require("react"); @@ -63,7 +63,6 @@ export interface DocumentViewProps { getView?: (view: DocumentView) => any; LayoutTemplateString?: string; LayoutTemplate?: () => Opt<Doc>; - LibraryPath: Doc[]; fitToBox?: boolean; ignoreAutoHeight?: boolean; contextMenuItems?: () => { script: ScriptField, label: string }[]; @@ -91,10 +90,10 @@ export interface DocumentViewProps { parentActive: (outsideReaction: boolean) => boolean; whenActiveChanged: (isActive: boolean) => void; bringToFront: (doc: Doc, sendToBack?: boolean) => void; - addDocTab: (doc: Doc, where: string, libraryPath?: Doc[]) => boolean; + addDocTab: (doc: Doc, where: string) => boolean; pinToPres: (document: Doc) => void; backgroundHalo?: (doc: Doc) => boolean; - styleProvider?: (doc: Opt<Doc>, props: DocumentViewProps, property: string, layerProvider?: (doc: Doc, assign?: boolean) => boolean) => any; + styleProvider?: (doc: Opt<Doc>, props: DocumentViewProps, property: string) => any; forceHideLinkButton?: () => boolean; opacity?: () => number | undefined; ChromeHeight?: () => number; @@ -387,7 +386,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu // depending on the followLinkLocation property of the source (or the link itself as a fallback); public static followLinkClick = async (linkDoc: Opt<Doc>, sourceDoc: Doc, docView: { focus: DocFocusFunc, - addDocTab: (doc: Doc, where: string, libraryPath?: Doc[]) => boolean, + addDocTab: (doc: Doc, where: string) => boolean, ContainingCollectionDoc?: Doc }, shiftKey: boolean, altKey: boolean) => { const batch = UndoManager.StartBatch("follow link click"); @@ -657,7 +656,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this.Document.onClick = this.layoutDoc.onClick = undefined; } - @undoBatch noOnClick = (): void => { this.Document.ignoreClick = false; @@ -934,7 +932,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu backgroundHalo={this.props.backgroundHalo} dontRegisterView={this.props.dontRegisterView} fitToBox={this.props.fitToBox} - LibraryPath={this.props.LibraryPath} addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} moveDocument={this.props.moveDocument} @@ -1085,7 +1082,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } @computed get pointerEvents() { if (this.props.pointerEvents === "none") return "none"; - return this.props.styleProvider?.(this.Document, this.props, this.isSelected() ? "pointerEvents:selected" : "pointerEvents", this.props.layerProvider); + return this.props.styleProvider?.(this.Document, this.props, this.isSelected() ? "pointerEvents:selected" : "pointerEvents"); } @undoBatch @action @@ -1105,13 +1102,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu }), 400); }); - - render() { + @computed get renderDoc() { TraceMobx(); if (!(this.props.Document instanceof Doc)) return (null); if (GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate) return (null); - if (this.props.styleProvider?.(this.layoutDoc, this.props, "hidden", this.props.layerProvider)) return null; - const backgroundColor = this.props.styleProvider?.(this.layoutDoc, this.props, "backgroundColor", this.props.layerProvider); + if (this.props.styleProvider?.(this.layoutDoc, this.props, "hidden")) return null; + const backgroundColor = this.props.styleProvider?.(this.layoutDoc, this.props, "backgroundColor"); const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null))); const finalOpacity = this.props.opacity ? this.props.opacity() : opacity; const finalColor = this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc._viewType === CollectionViewType.Linear ? undefined : backgroundColor; @@ -1125,23 +1121,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear && this.props.Document.type !== DocumentType.INK; highlighting = highlighting && this.props.focus !== emptyFunction && this.layoutDoc.title !== "[pres element template]"; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way const topmost = this.topMost ? "-topmost" : ""; - return this.props.styleProvider?.(this.rootDoc, this.props, "docContents", this.props.layerProvider) ?? <div className={`documentView-node${topmost}`} + return this.props.styleProvider?.(this.rootDoc, this.props, "docContents") ?? <div className={`documentView-node${topmost}`} id={this.props.Document[Id]} - ref={this._mainCont} onKeyDown={this.onKeyDown} + onKeyDown={this.onKeyDown} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} onPointerEnter={action(e => !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document))} onPointerLeave={action(e => { let entered = false; - const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y); - for (let child: any = target; child; child = child?.parentElement) { - if (child === this.ContentDiv) { - entered = true; - } + for (let child = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y); !entered && child; child = child.parentElement) { + entered = entered || child === this.ContentDiv; } - // if (this.props.Document !== DocumentLinksButton.StartLink?.Document) { !entered && Doc.UnBrushDoc(this.props.Document); - //} - })} style={{ transformOrigin: this._animateScalingTo ? "center center" : undefined, @@ -1161,12 +1151,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu fontFamily: StrCast(this.Document._fontFamily, "inherit"), fontSize: !this.props.treeViewDoc ? Cast(this.Document._fontSize, "string", null) : undefined, }}> - {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <> - {this.innards} - <div className="documentView-conentBlocker" /> - </> : - this.innards} - {!this.props.treeViewDoc && this.props.styleProvider?.(this.rootDoc, this.props, this.isSelected() ? "decorations:selected" : "decorations", this.props.layerProvider) || (null)} + {this.innards} + {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <div className="documentView-contentBlocker" /> : (null)} + {!this.props.treeViewDoc && this.props.styleProvider?.(this.rootDoc, this.props, this.isSelected() ? "decorations:selected" : "decorations") || (null)} + </div>; + } + render() { + return <div className="documentView-effectsWrapper" ref={this._mainCont} > + {PresBox.EffectsProvider(this.layoutDoc, this.renderDoc) || this.renderDoc} </div>; } } diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index ed91cf47d..5cd752fdb 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -25,7 +25,6 @@ export interface FieldViewProps { ContainingCollectionDoc: Opt<Doc>; Document: Doc; DataDoc?: Doc; - LibraryPath: Doc[]; layerProvider?: (doc: Doc, assign?: boolean) => boolean; contentsActive?: (setActive: () => boolean) => void; onClick?: () => ScriptField; @@ -43,7 +42,7 @@ export interface FieldViewProps { pinToPres: (document: Doc) => void; removeDocument?: (document: Doc | Doc[]) => boolean; moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; - styleProvider?: (document: Opt<Doc>, props: Opt<DocumentViewProps>, property: string, layerProvider?: (doc: Doc, assign?: boolean) => boolean) => any; + styleProvider?: (document: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => any; ScreenToLocalTransform: () => Transform; bringToFront: (doc: Doc, sendToBack?: boolean) => void; parentActive: (outsideReaction: boolean) => boolean; diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index 6f0828a7d..0161f51fd 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -193,7 +193,6 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc ContainingCollectionView={this.props.ContainingCollectionView} PanelWidth={this.props.PanelWidth} PanelHeight={this.props.PanelHeight} - LibraryPath={emptyPath} rootSelected={this.props.rootSelected} renderDepth={1} dropAction={this.props.dropAction} diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index c9ee4126e..a1bb0604e 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -61,7 +61,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>( render() { const label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title)); const color = StrCast(this.layoutDoc.color, this._foregroundColor); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, "backgroundColor", this.props.layerProvider); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, "backgroundColor"); const shape = StrCast(this.layoutDoc.iconShape, label ? "round" : "circle"); const icon = StrCast(this.dataDoc.icon, "user") as any; const presSize = shape === 'round' ? 25 : 30; diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 5e1f8fcea..aaf544c50 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -55,7 +55,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { const props: FieldViewProps = { Document: this.props.doc, DataDoc: this.props.doc, - LibraryPath: [], docFilters: returnEmptyFilter, docRangeFilters: returnEmptyFilter, searchFilterDocs: returnEmptyDoclist, diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 1b181cef1..bf19457da 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -17,7 +17,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps, LinkDocument>( public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkBox, fieldKey); } render() { return <div className={`linkBox-container${this.active() ? "-interactive" : ""}`} - style={{ background: this.props.styleProvider?.(this.props.Document, this.props, "backgroundColor", this.props.layerProvider) }} > + style={{ background: this.props.styleProvider?.(this.props.Document, this.props, "backgroundColor") }} > <CollectionTreeView {...this.props} ChromeHeight={returnZero} diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 0256d394e..f66811098 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -12,12 +12,13 @@ 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; linkSrc?: Doc; href?: string; - styleProvider?: (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string, layerProvider?: (doc: Doc, assign?: boolean) => boolean) => any; + styleProvider?: (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => any; addDocTab: (document: Doc, where: string) => boolean; location: number[]; } @@ -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); + } } }); } @@ -90,7 +94,6 @@ export class LinkDocPreview extends React.Component<Props> { : <ContentFittingDocumentView Document={this._targetDoc} - LibraryPath={emptyPath} fitToBox={true} moveDocument={returnFalse} rootSelected={returnFalse} diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index a4f79571e..5985225e3 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -1,10 +1,11 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from "@material-ui/core"; -import { action, computed, observable, runInAction, ObservableMap, IReactionDisposer, reaction } from "mobx"; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { ColorState, SketchPicker } from "react-color"; -import { Doc, DocCastAsync, DocListCast, DocListCastAsync } from "../../../fields/Doc"; +import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal'; +import { Doc, DocListCast, DocListCastAsync } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; import { InkTool } from "../../../fields/InkField"; import { List } from "../../../fields/List"; @@ -12,7 +13,7 @@ import { PrefetchProxy } from "../../../fields/Proxy"; import { listSpec, makeInterface } from "../../../fields/Schema"; import { ScriptField } from "../../../fields/ScriptField"; import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types"; -import { returnFalse, returnOne, returnZero } from "../../../Utils"; +import { returnFalse, returnOne } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DocumentType } from "../../documents/DocumentTypes"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; @@ -28,7 +29,6 @@ import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView import { FieldView, FieldViewProps } from './FieldView'; import "./PresBox.scss"; import Color = require("color"); -import { clear } from "console"; export enum PresMovement { Zoom = "zoom", @@ -38,6 +38,8 @@ export enum PresMovement { } export enum PresEffect { + Zoom = "Zoom", + Lightspeed = "Lightspeed", Fade = "Fade in", Flip = "Flip", Rotate = "Rotate", @@ -71,6 +73,35 @@ const PresBoxDocument = makeInterface(documentSchema); export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>(PresBoxDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } + static renderEffectsDoc(renderDoc: any, layoutDoc: Doc) { + const effectProps = { + left: layoutDoc.presEffectDirection === PresEffect.Left, + right: layoutDoc.presEffectDirection === PresEffect.Right, + top: layoutDoc.presEffectDirection === PresEffect.Top, + bottom: layoutDoc.presEffectDirection === PresEffect.Bottom, + opposite: true, + delay: layoutDoc.presTransition, + // when: this.layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc, + }; + switch (layoutDoc.presEffect) { + case PresEffect.Zoom: return (<Zoom {...effectProps}>{renderDoc}</Zoom>); + case PresEffect.Fade: return (<Fade {...effectProps}>{renderDoc}</Fade>); + case PresEffect.Flip: return (<Flip {...effectProps}>{renderDoc}</Flip>); + case PresEffect.Rotate: return (<Rotate {...effectProps}>{renderDoc}</Rotate>); + case PresEffect.Bounce: return (<Bounce {...effectProps}>{renderDoc}</Bounce>); + case PresEffect.Roll: return (<Roll {...effectProps}>{renderDoc}</Roll>); + case PresEffect.Lightspeed: return (<LightSpeed {...effectProps}>{renderDoc}</LightSpeed>); + case PresEffect.None: + default: return renderDoc; + } + } + public static EffectsProvider(layoutDoc: Doc, renderDoc: any) { + return PresBox.Instance && layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc ? + PresBox.renderEffectsDoc(renderDoc, layoutDoc) + : + renderDoc; + } + @observable public static Instance: PresBox; @observable _isChildActive = false; diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 67bfd435b..450f0b6bc 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -228,7 +228,6 @@ export class DashDocView extends React.Component<IDashDocView> { <DocumentView Document={finalLayout} DataDoc={resolvedDataDoc} - LibraryPath={this._textBox.props.LibraryPath} fitToBox={BoolCast(dashDoc._fitToBox)} addDocument={returnFalse} rootSelected={this._textBox.props.isSelected} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 125447f28..22e3ac1e9 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: number = 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) { @@ -1572,7 +1598,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const annotated = DocListCast(this.dataDoc[this.annotationKey]).filter(d => d?.author).length; return !this.props.isSelected() && !(annotated && !this.sidebarWidth()) ? (null) : <div className="formattedTextBox-sidebar-handle" - style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, background: annotated ? "lightBlue" : this.props.styleProvider?.(this.props.Document, this.props, "widgetColor", this.props.layerProvider) }} + style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, background: annotated ? "lightBlue" : this.props.styleProvider?.(this.props.Document, this.props, "widgetColor") }} onPointerDown={this.sidebarDown} />; } diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 3e5a40084..1d7b7ec91 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -297,7 +297,6 @@ export class FormattedTextBoxComment { <div className="FormattedTextBoxComment-preview-wrapper"> <ContentFittingDocumentView Document={target} - LibraryPath={emptyPath} fitToBox={true} moveDocument={returnFalse} rootSelected={returnFalse} diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx index b5d984aac..bb544e5e8 100644 --- a/src/client/views/nodes/formattedText/RichTextSchema.tsx +++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx @@ -136,7 +136,6 @@ export class DashDocView { ReactDOM.render(<DocumentView Document={finalLayout} DataDoc={resolvedDataDoc} - LibraryPath={this._textBox.props.LibraryPath} fitToBox={BoolCast(dashDoc._fitToBox)} addDocument={returnFalse} rootSelected={this._textBox.props.isSelected} |
