diff options
19 files changed, 198 insertions, 281 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9c807eff2..8d3e5a771 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -514,6 +514,7 @@ export namespace Docs { const dataDoc = MakeDataDelegate(proto, protoProps, data, fieldKey); const viewDoc = Doc.MakeDelegate(dataDoc, delegId); + viewDoc.author = Doc.CurrentUserEmail; viewDoc.type !== DocumentType.LINK && DocUtils.MakeLinkToActiveAudio(viewDoc); return Doc.assign(viewDoc, delegateProps, true); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 67f2f244c..78c05f572 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -168,7 +168,7 @@ export class DocumentManager { highlight(); } else { // otherwise try to get a view of the context of the target const targetDocContextView = getFirstDocView(targetDocContext); - targetDocContext.scrollY = 0; // this will force PDFs to activate and load their annotations / allow scrolling + targetDocContext._scrollY = 0; // this will force PDFs to activate and load their annotations / allow scrolling if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first.. targetDocContext.panTransformType = "Ease"; targetDocContextView.props.focus(targetDocContextView.props.Document, willZoom); diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index a4d4af2f0..0a96b058b 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -152,6 +152,11 @@ $linkGap : 3px; text-align: center; display: flex; border-bottom: solid 1px; + margin-left:10px; + width: calc(100% - 10px); + } + .focus-visible { + margin-left:0px; } .publishBox { width: 20px; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 04f02c683..e7a237e62 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -466,7 +466,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> const titleArea = this._edtingTitle ? <> - <input ref={this._keyinput} className="documentDecorations-title" type="text" name="dynbox" autoComplete="on" value={this._accumulatedTitle} style={{ width: minimal ? "100%" : "calc(100% - 20px)" }} + <input ref={this._keyinput} className="documentDecorations-title" type="text" name="dynbox" autoComplete="on" value={this._accumulatedTitle} onBlur={e => this.titleBlur(true)} onChange={action(e => this._accumulatedTitle = e.target.value)} onKeyPress={this.titleEntered} /> {minimal ? (null) : <div className="publishBox" title="make document referenceable by its title" onPointerDown={action(e => { @@ -485,7 +485,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> <FontAwesomeIcon size="lg" icon="cog" /> </div>} <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} > - <span style={{ width: "calc(100% - 25px)", display: "inline-block" }}>{`${this.selectionTitle}`}</span> + <span style={{ width: "100%", display: "inline-block" }}>{`${this.selectionTitle}`}</span> </div> </>; diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index 30692944d..43f15a33f 100644 --- a/src/client/views/animationtimeline/Timeline.tsx +++ b/src/client/views/animationtimeline/Timeline.tsx @@ -1,19 +1,17 @@ -import * as React from "react"; -import "./Timeline.scss"; -import { listSpec } from "../../../fields/Schema"; +import { faBackward, faForward, faGripLines, faPauseCircle, faPlayCircle } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; -import { Track } from "./Track"; -import { observable, action, computed, runInAction, IReactionDisposer, reaction, trace } from "mobx"; -import { Cast, NumCast, StrCast, BoolCast } from "../../../fields/Types"; -import { List } from "../../../fields/List"; +import * as React from "react"; import { Doc, DocListCast } from "../../../fields/Doc"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faPlayCircle, faBackward, faForward, faGripLines, faPauseCircle, faEyeSlash, faEye, faCheckCircle, faTimesCircle } from "@fortawesome/free-solid-svg-icons"; -import { ContextMenu } from "../ContextMenu"; -import { TimelineOverview } from "./TimelineOverview"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types"; +import { Utils, setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils"; import { FieldViewProps } from "../nodes/FieldView"; import { KeyframeFunc } from "./Keyframe"; -import { Utils } from "../../../Utils"; +import "./Timeline.scss"; +import { TimelineOverview } from "./TimelineOverview"; +import { Track } from "./Track"; +import clamp from "../../util/clamp"; /** * Timeline class controls most of timeline functions besides individual keyframe and track mechanism. Main functions are @@ -39,7 +37,6 @@ import { Utils } from "../../../Utils"; @observer export class Timeline extends React.Component<FieldViewProps> { - //readonly constants private readonly DEFAULT_TICK_SPACING: number = 50; private readonly MAX_TITLE_HEIGHT = 75; @@ -57,7 +54,7 @@ export class Timeline extends React.Component<FieldViewProps> { @observable private _infoContainer = React.createRef<HTMLDivElement>(); @observable private _roundToggleRef = React.createRef<HTMLDivElement>(); @observable private _roundToggleContainerRef = React.createRef<HTMLDivElement>(); - @observable private _timeInputRef = React.createRef<HTMLInputElement>(); + //boolean vars and instance vars @observable private _currentBarX: number = 0; @@ -71,27 +68,18 @@ export class Timeline extends React.Component<FieldViewProps> { @observable private _tickIncrement = this.DEFAULT_TICK_INCREMENT; @observable private _time = 100000; //DEFAULT @observable private _playButton = faPlayCircle; - @observable private _mouseToggled = false; - @observable private _doubleClickEnabled = false; @observable private _titleHeight = 0; - // so a reaction can be made - @observable public _isAuthoring = this.props.Document.isATOn; - /** * collection get method. Basically defines what defines collection's children. These will be tracked in the timeline. Do not edit. */ @computed - private get children(): List<Doc> { - const extendedDocument = ["image", "video", "pdf"].includes(StrCast(this.props.Document.type)); - if (extendedDocument) { - if (this.props.Document.data_ext) { - return Cast((Cast(this.props.Document[Doc.LayoutFieldKey(this.props.Document) + "-annotations"], Doc) as Doc).annotations, listSpec(Doc)) as List<Doc>; - } else { - return new List<Doc>(); - } + private get children(): Doc[] { + const annotatedDoc = ["image", "video", "pdf"].includes(StrCast(this.props.Document.type)); + if (annotatedDoc) { + return DocListCast(this.props.Document[Doc.LayoutFieldKey(this.props.Document) + "-annotations"]); } - return Cast(this.props.Document[this.props.fieldKey], listSpec(Doc)) as List<Doc>; + return DocListCast(this.props.Document[this.props.fieldKey]); } /////////lifecycle functions//////////// @@ -144,9 +132,7 @@ export class Timeline extends React.Component<FieldViewProps> { } //for playing - @action onPlay = (e: React.MouseEvent) => { - e.preventDefault(); e.stopPropagation(); this.play(); } @@ -156,24 +142,15 @@ export class Timeline extends React.Component<FieldViewProps> { */ @action play = () => { - if (this._isPlaying) { - this._isPlaying = false; - this._playButton = faPlayCircle; - } else { - this._isPlaying = true; - this._playButton = faPauseCircle; - const playTimeline = () => { - if (this._isPlaying) { - if (this._currentBarX >= this._totalLength) { - this.changeCurrentBarX(0); - } else { - this.changeCurrentBarX(this._currentBarX + this._windSpeed); - } - setTimeout(playTimeline, 15); - } - }; - playTimeline(); - } + const playTimeline = () => { + if (this._isPlaying) { + this.changeCurrentBarX(this._currentBarX >= this._totalLength ? 0 : this._currentBarX + this._windSpeed); + setTimeout(playTimeline, 15); + } + }; + this._isPlaying = !this._isPlaying; + this._playButton = this._isPlaying ? faPauseCircle : faPlayCircle; + this._isPlaying && playTimeline(); } @@ -206,12 +183,7 @@ export class Timeline extends React.Component<FieldViewProps> { */ @action onScrubberDown = (e: React.PointerEvent) => { - e.preventDefault(); - e.stopPropagation(); - document.addEventListener("pointermove", this.onScrubberMove); - document.addEventListener("pointerup", () => { - document.removeEventListener("pointermove", this.onScrubberMove); - }); + setupMoveUpEvents(this, e, this.onScrubberMove, emptyFunction, emptyFunction); } /** @@ -219,12 +191,11 @@ export class Timeline extends React.Component<FieldViewProps> { */ @action onScrubberMove = (e: PointerEvent) => { - e.preventDefault(); - e.stopPropagation(); const scrubberbox = this._infoContainer.current!; const left = scrubberbox.getBoundingClientRect().left; const offsetX = Math.round(e.clientX - left) * this.props.ScreenToLocalTransform().Scale; this.changeCurrentBarX(offsetX + this._visibleStart); //changes scrubber to clicked scrubber position + return false; } /** @@ -232,27 +203,8 @@ export class Timeline extends React.Component<FieldViewProps> { */ @action onPanDown = (e: React.PointerEvent) => { - e.preventDefault(); - e.stopPropagation(); - const clientX = e.clientX; - if (this._doubleClickEnabled) { - this._doubleClickEnabled = false; - } else { - setTimeout(() => { - if (!this._mouseToggled && this._doubleClickEnabled) this.changeCurrentBarX(this._trackbox.current!.scrollLeft + clientX - this._trackbox.current!.getBoundingClientRect().left); - this._mouseToggled = false; - this._doubleClickEnabled = false; - }, 200); - this._doubleClickEnabled = true; - document.addEventListener("pointermove", this.onPanMove); - document.addEventListener("pointerup", () => { - document.removeEventListener("pointermove", this.onPanMove); - if (!this._doubleClickEnabled) { - this._mouseToggled = false; - } - }); - - } + setupMoveUpEvents(this, e, this.onPanMove, emptyFunction, (e) => + this.changeCurrentBarX(this._trackbox.current!.scrollLeft + e.clientX - this._trackbox.current!.getBoundingClientRect().left)); } /** @@ -260,11 +212,6 @@ export class Timeline extends React.Component<FieldViewProps> { */ @action onPanMove = (e: PointerEvent) => { - e.preventDefault(); - e.stopPropagation(); - if (e.movementX !== 0 || e.movementY !== 0) { - this._mouseToggled = true; - } const trackbox = this._trackbox.current!; const titleContainer = this._titleContainer.current!; this.movePanX(this._visibleStart - e.movementX); @@ -276,43 +223,25 @@ export class Timeline extends React.Component<FieldViewProps> { this._time -= KeyframeFunc.convertPixelTime(e.movementX, "mili", "time", this._tickSpacing, this._tickIncrement); this.props.Document.AnimationLength = this._time; } - + return false; } @action movePanX = (pixel: number) => { - const infoContainer = this._infoContainer.current!; - infoContainer.scrollLeft = pixel; - this._visibleStart = infoContainer.scrollLeft; + this._infoContainer.current!.scrollLeft = pixel; + this._visibleStart = this._infoContainer.current!.scrollLeft; } /** * resizing timeline (in editing mode) (the hamburger drag icon) */ - @action onResizeDown = (e: React.PointerEvent) => { - e.preventDefault(); - e.stopPropagation(); - document.addEventListener("pointermove", this.onResizeMove); - document.addEventListener("pointerup", () => { - document.removeEventListener("pointermove", this.onResizeMove); - }); - } - - @action - onResizeMove = (e: PointerEvent) => { - e.preventDefault(); - e.stopPropagation(); - const offset = e.clientY - this._timelineContainer.current!.getBoundingClientRect().bottom; - // let offset = 0; - if (this._containerHeight + offset <= this.MIN_CONTAINER_HEIGHT) { - this._containerHeight = this.MIN_CONTAINER_HEIGHT; - } else if (this._containerHeight + offset >= this.MAX_CONTAINER_HEIGHT) { - this._containerHeight = this.MAX_CONTAINER_HEIGHT; - } else { - this._containerHeight += offset; - } + setupMoveUpEvents(this, e, action((e) => { + const offset = e.clientY - this._timelineContainer.current!.getBoundingClientRect().bottom; + this._containerHeight = clamp(this.MIN_CONTAINER_HEIGHT, this._containerHeight + offset, this.MAX_CONTAINER_HEIGHT); + return false; + }), emptyFunction, emptyFunction); } /** @@ -323,8 +252,8 @@ export class Timeline extends React.Component<FieldViewProps> { time = time / 1000; const inSeconds = Math.round(time * 100) / 100; - const min: (string | number) = Math.floor(inSeconds / 60); - const sec: (string | number) = (Math.round((inSeconds % 60) * 100) / 100); + const min = Math.floor(inSeconds / 60); + const sec = (Math.round((inSeconds % 60) * 100) / 100); let secString = sec.toFixed(2); if (Math.floor(sec / 10) === 0) { @@ -346,7 +275,7 @@ export class Timeline extends React.Component<FieldViewProps> { const offset = e.clientX - this._infoContainer.current!.getBoundingClientRect().left; const prevTime = KeyframeFunc.convertPixelTime(this._visibleStart + offset, "mili", "time", this._tickSpacing, this._tickIncrement); const prevCurrent = KeyframeFunc.convertPixelTime(this._currentBarX, "mili", "time", this._tickSpacing, this._tickIncrement); - e.deltaY < 0 ? this.zoom(true) : this.zoom(false); + this.zoom(e.deltaY < 0); const currPixel = KeyframeFunc.convertPixelTime(prevTime, "mili", "pixel", this._tickSpacing, this._tickIncrement); const currCurrent = KeyframeFunc.convertPixelTime(prevCurrent, "mili", "pixel", this._tickSpacing, this._tickIncrement); this._infoContainer.current!.scrollLeft = currPixel - offset; @@ -405,30 +334,17 @@ export class Timeline extends React.Component<FieldViewProps> { private timelineToolBox = (scale: number, totalTime: number) => { const size = 40 * scale; //50 is default const iconSize = 25; + const width: number = this.props.PanelWidth(); + const modeType = this.props.Document.isATOn ? "Author" : "Play"; //decides if information should be omitted because the timeline is very small // if its less than 950 pixels then it's going to be overlapping - let shouldCompress = false; - const width: number = this.props.PanelWidth(); + let modeString = modeType, overviewString = "", lengthString = ""; if (width < 850) { - shouldCompress = true; - } - - let modeString, overviewString, lengthString; - const modeType = this.props.Document.isATOn ? "Author" : "Play"; - - if (!shouldCompress) { modeString = "Mode: " + modeType; overviewString = "Overview:"; lengthString = "Length: "; } - else { - modeString = modeType; - overviewString = ""; - lengthString = ""; - } - - // let rightInfo = this.timeIndicator; return ( <div key="timeline_toolbox" className="timeline-toolbox" style={{ height: `${size}px` }}> @@ -451,8 +367,7 @@ export class Timeline extends React.Component<FieldViewProps> { <div className="time-box overview-tool" style={{ display: "flex" }}> {this.timeIndicator(lengthString, totalTime)} <div className="resetView-tool" title="Return to Default View" onClick={() => this.resetView(this.props.Document)}><FontAwesomeIcon icon="compress-arrows-alt" size="lg" /></div> - <div className="resetView-tool" style={{ display: this._isAuthoring ? "flex" : "none" }} title="Set Default View" onClick={() => this.setView(this.props.Document)}><FontAwesomeIcon icon="expand-arrows-alt" size="lg" /></div> - + <div className="resetView-tool" style={{ display: this.props.Document.isATOn ? "flex" : "none" }} title="Set Default View" onClick={() => this.setView(this.props.Document)}><FontAwesomeIcon icon="expand-arrows-alt" size="lg" /></div> </div> </div> </div> @@ -498,15 +413,15 @@ export class Timeline extends React.Component<FieldViewProps> { const roundToggle = this._roundToggleRef.current!; const roundToggleContainer = this._roundToggleContainerRef.current!; const timelineContainer = this._timelineContainer.current!; - if (BoolCast(this.props.Document.isATOn)) { + + this.props.Document.isATOn = !this.props.Document.isATOn; + if (!BoolCast(this.props.Document.isATOn)) { //turning on playmode... roundToggle.style.transform = "translate(0px, 0px)"; roundToggle.style.animationName = "turnoff"; roundToggleContainer.style.animationName = "turnoff"; roundToggleContainer.style.backgroundColor = "white"; timelineContainer.style.top = `${-this._containerHeight}px`; - this.props.Document.isATOn = false; - this._isAuthoring = false; this.toPlay(); } else { //turning on authoring mode... @@ -515,8 +430,6 @@ export class Timeline extends React.Component<FieldViewProps> { roundToggleContainer.style.animationName = "turnon"; roundToggleContainer.style.backgroundColor = "#9acedf"; timelineContainer.style.top = "0px"; - this.props.Document.isATOn = true; - this._isAuthoring = true; this.toAuthoring(); } } @@ -533,10 +446,7 @@ export class Timeline extends React.Component<FieldViewProps> { // @computed getCurrentTime = () => { let current = KeyframeFunc.convertPixelTime(this._currentBarX, "mili", "time", this._tickSpacing, this._tickIncrement); - if (current > this._time) { - current = this._time; - } - return this.toReadTime(current); + return this.toReadTime(current > this._time ? this._time : current); } @observable private mapOfTracks: (Track | null)[] = []; @@ -562,18 +472,13 @@ export class Timeline extends React.Component<FieldViewProps> { @action toAuthoring = () => { - let longestTime = this.findLongestTime(); - if (longestTime === 0) longestTime = 1; - const adjustedTime = Math.ceil(longestTime / 100000) * 100000; - // console.log(adjustedTime); - this._totalLength = KeyframeFunc.convertPixelTime(adjustedTime, "mili", "pixel", this._tickSpacing, this._tickIncrement); - this._time = adjustedTime; + this._time = Math.ceil((this.findLongestTime() ?? 1) / 100000) * 100000; + this._totalLength = KeyframeFunc.convertPixelTime(this._time, "mili", "pixel", this._tickSpacing, this._tickIncrement); } @action toPlay = () => { - const longestTime = this.findLongestTime(); - this._time = longestTime; + this._time = this.findLongestTime(); this._totalLength = KeyframeFunc.convertPixelTime(this._time, "mili", "pixel", this._tickSpacing, this._tickIncrement); } @@ -582,40 +487,35 @@ export class Timeline extends React.Component<FieldViewProps> { * basically the only thing you need to edit besides render methods in track (individual track lines) and keyframe (green region) */ render() { - setTimeout(() => { - this.changeLengths(); - // this.toPlay(); - // this._time = longestTime; - }, 0); + setTimeout(() => this.changeLengths(), 0); - const longestTime = this.findLongestTime(); trace(); // change visible and total width return ( <div style={{ visibility: "visible" }}> - <div key="timeline_wrapper" style={{ visibility: BoolCast(this.props.Document.isATOn) ? "visible" : "hidden", left: "0px", top: "0px", position: "absolute", width: "100%", transform: "translate(0px, 0px)" }}> + <div key="timeline_wrapper" style={{ visibility: this.props.Document.isATOn ? "visible" : "hidden", left: "0px", top: "0px", position: "absolute", width: "100%", transform: "translate(0px, 0px)" }}> <div key="timeline_container" className="timeline-container" ref={this._timelineContainer} style={{ height: `${this._containerHeight}px`, top: `0px` }}> - <div key="timeline_info" className="info-container" ref={this._infoContainer} onWheel={this.onWheelZoom}> + <div key="timeline_info" className="info-container" onPointerDown={this.onPanDown} ref={this._infoContainer} onWheel={this.onWheelZoom}> {this.drawTicks()} <div key="timeline_scrubber" className="scrubber" style={{ transform: `translate(${this._currentBarX}px)` }}> <div key="timeline_scrubberhead" className="scrubberhead" onPointerDown={this.onScrubberDown} ></div> </div> - <div key="timeline_trackbox" className="trackbox" ref={this._trackbox} onPointerDown={this.onPanDown} style={{ width: `${this._totalLength}px` }}> - {DocListCast(this.children).map(doc => + <div key="timeline_trackbox" className="trackbox" ref={this._trackbox} style={{ width: `${this._totalLength}px` }}> + {this.children.map(doc => <Track ref={ref => this.mapOfTracks.push(ref)} node={doc} currentBarX={this._currentBarX} changeCurrentBarX={this.changeCurrentBarX} transform={this.props.ScreenToLocalTransform()} time={this._time} tickSpacing={this._tickSpacing} tickIncrement={this._tickIncrement} collection={this.props.Document} timelineVisible={true} /> )} </div> </div> <div className="currentTime">Current: {this.getCurrentTime()}</div> <div key="timeline_title" className="title-container" ref={this._titleContainer}> - {DocListCast(this.children).map(doc => <div style={{ height: `${(this._titleHeight)}px` }} className="datapane" onPointerOver={() => { Doc.BrushDoc(doc); }} onPointerOut={() => { Doc.UnBrushDoc(doc); }}><p>{doc.title}</p></div>)} + {this.children.map(doc => <div style={{ height: `${(this._titleHeight)}px` }} className="datapane" onPointerOver={() => { Doc.BrushDoc(doc); }} onPointerOut={() => { Doc.UnBrushDoc(doc); }}><p>{doc.title}</p></div>)} </div> <div key="timeline_resize" onPointerDown={this.onResizeDown}> <FontAwesomeIcon className="resize" icon={faGripLines} /> </div> </div> </div> - {this.timelineToolBox(1, longestTime)} + {this.timelineToolBox(1, this.findLongestTime())} </div> ); } diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx index fc96c320a..4987006e7 100644 --- a/src/client/views/animationtimeline/Track.tsx +++ b/src/client/views/animationtimeline/Track.tsx @@ -31,13 +31,14 @@ export class Track extends React.Component<IProps> { @observable private _autoKfReaction: any; @observable private _newKeyframe: boolean = false; private readonly MAX_TITLE_HEIGHT = 75; - private _trackHeight = 0; + @observable private _trackHeight = 0; private primitiveWhitelist = [ "x", "y", "_width", "_height", "opacity", + "_scrollTop" ]; private objectWhitelist = [ "data" @@ -51,7 +52,7 @@ export class Track extends React.Component<IProps> { if (!regions) this.props.node.regions = new List<Doc>(); //if there is no region, then create new doc to store stuff //these two lines are exactly same from timeline.tsx const relativeHeight = window.innerHeight / 20; - this._trackHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; //for responsiveness + runInAction(() => this._trackHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT); //for responsiveness this._timelineVisibleReaction = this.timelineVisibleReaction(); this._currentBarXReaction = this.currentBarXReaction(); if (DocListCast(this.props.node.regions).length === 0) this.createRegion(this.time); diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 7fd19a23c..bd48d1727 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -474,7 +474,10 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) width: `${1 / this.scaling * 100}%`, transformOrigin: "top left", }} - onScroll={action((e: React.UIEvent<HTMLDivElement>) => this._scroll = e.currentTarget.scrollTop)} + onScroll={action(e => { + if (!this.props.isSelected()) e.currentTarget.scrollTop = this._scroll; + else this._scroll = e.currentTarget.scrollTop; + })} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu} onWheel={e => this.props.active() && e.stopPropagation()} > diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index b60419258..167bce131 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -335,7 +335,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: if (focusNode) { const rect = "getBoundingClientRect" in focusNode ? focusNode.getBoundingClientRect() : focusNode?.parentElement.getBoundingClientRect(); const x = (rect?.x || 0); - const y = NumCast(srcWeb.scrollTop) + (rect?.y || 0); + const y = NumCast(srcWeb._scrollTop) + (rect?.y || 0); const anchor = Docs.Create.FreeformDocument([], { _LODdisable: true, _backgroundColor: "transparent", _width: 25, _height: 25, x, y, annotationOn: srcWeb }); anchor.context = srcWeb; const key = Doc.LayoutFieldKey(srcWeb); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 9c0e5e917..379156179 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -118,10 +118,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P this.Document.scale || 1) @computed get cachedCenteringShiftX(): number { - return !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling / this.contentScaling : 0; // shift so pan position is at center of window for non-overlay collections + const scaling = this.fitToContent ? 1 : this.contentScaling; + return !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling / scaling : 0; // shift so pan position is at center of window for non-overlay collections } @computed get cachedCenteringShiftY(): number { - return !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling / this.contentScaling : 0;// shift so pan position is at center of window for non-overlay collections + const scaling = this.fitToContent ? 1 : this.contentScaling; + return !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling / scaling : 0;// shift so pan position is at center of window for non-overlay collections } @computed get cachedGetLocalTransform(): Transform { return Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY()); @@ -187,79 +189,76 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); } + onExternalDrop = (e: React.DragEvent) => { + return (pt => super.onExternalDrop(e, { x: pt[0], y: pt[1] }))(this.getTransform().transformPoint(e.pageX, e.pageY)); + } + + @undoBatch + @action + internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) { + if (!super.onInternalDrop(e, de)) return false; + const [xpo, ypo] = this.getTransformOverlay().transformPoint(de.x, de.y); + const z = NumCast(docDragData.droppedDocuments[0].z); + const x = (z ? xpo : xp) - docDragData.offset[0]; + const y = (z ? ypo : yp) - docDragData.offset[1]; + const zsorted = this.childLayoutPairs.map(pair => pair.layout).slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); + zsorted.forEach((doc, index) => doc.zIndex = index + 1); + const dropPos = [NumCast(docDragData.droppedDocuments[0].x), NumCast(docDragData.droppedDocuments[0].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 && !this.props.isAnnotationOverlay) { + 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.opacity); + } else { + d.x = x + NumCast(d.x) - dropPos[0]; + d.y = y + NumCast(d.y) - dropPos[1]; + } + const nd = [NumCast(layoutDoc._nativeWidth), NumCast(layoutDoc._nativeHeight)]; + layoutDoc._width = NumCast(layoutDoc._width, 300); + layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? nd[1] / nd[0] * NumCast(layoutDoc._width) : 300); + d.isBackground === undefined && (d.zIndex = zsorted.length + 1 + i); // bringToFront + } + + (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments); + return true; + } + + @undoBatch @action - onExternalDrop = (e: React.DragEvent): Promise<void> => { - const pt = this.getTransform().transformPoint(e.pageX, e.pageY); - return super.onExternalDrop(e, { x: pt[0], y: pt[1] }); + internalPdfAnnoDrop(e: Event, annoDragData: DragManager.PdfAnnoDragData, xp: number, yp: number) { + const dragDoc = annoDragData.dropDocument; + const dropPos = [NumCast(dragDoc.x), NumCast(dragDoc.y)]; + dragDoc.x = xp - annoDragData.offset[0] + (NumCast(dragDoc.x) - dropPos[0]); + dragDoc.y = yp - annoDragData.offset[1] + (NumCast(dragDoc.y) - dropPos[1]); + annoDragData.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation + this.bringToFront(dragDoc); + return true; } @undoBatch @action + internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) { + if (linkDragData.linkSourceDocument === this.props.Document) return false; + const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" }); + this.props.addDocument(source); + linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed + e.stopPropagation(); + return true; + } + + @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { // if (this.props.Document.isBackground) return false; - const xf = this.getTransform(); - const xfo = this.getTransformOverlay(); - const [xp, yp] = xf.transformPoint(de.x, de.y); - const [xpo, ypo] = xfo.transformPoint(de.x, de.y); - const zsorted = this.childLayoutPairs.map(pair => pair.layout).slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); - if (!this.isAnnotationOverlay && de.complete.linkDragData && de.complete.linkDragData.linkSourceDocument !== this.props.Document) { - const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" }); - this.props.addDocument(source); - (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceDocument }, - "doc annotation")); // TODODO this is where in text links get passed - e.stopPropagation(); + const [xp, yp] = this.getTransform().transformPoint(de.x, de.y); + if (this.isAnnotationOverlay !== true && de.complete.linkDragData) + return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp); + if (de.complete.annoDragData?.dropDocument && super.onInternalDrop(e, de)) + return this.internalPdfAnnoDrop(e, de.complete.annoDragData, xp, yp); + if (de.complete.docDragData?.droppedDocuments.length && this.internalDocDrop(e, de, de.complete.docDragData, xp, yp)) { return true; - } - if (super.onInternalDrop(e, de)) { - if (de.complete.docDragData) { - if (de.complete.docDragData.droppedDocuments.length) { - const firstDoc = de.complete.docDragData.droppedDocuments[0]; - const z = NumCast(firstDoc.z); - const x = (z ? xpo : xp) - de.complete.docDragData.offset[0]; - const y = (z ? ypo : yp) - de.complete.docDragData.offset[1]; - const dropX = NumCast(firstDoc.x); - const dropY = NumCast(firstDoc.y); - const droppedDocs = de.complete.docDragData.droppedDocuments; - runInAction(() => { - zsorted.forEach((doc, index) => doc.zIndex = index + 1); - for (let i = 0; i < droppedDocs.length; i++) { - const d = droppedDocs[i]; - const layoutDoc = Doc.Layout(d); - if (this.Document.currentFrame !== undefined && !this.props.isAnnotationOverlay) { - const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); - CollectionFreeFormDocumentView.setValues(this.Document.currentFrame, d, x + vals.x - dropX, y + vals.y - dropY, vals.opacity); - } else { - d.x = x + NumCast(d.x) - dropX; - d.y = y + NumCast(d.y) - dropY; - } - if (!NumCast(layoutDoc._width)) { - layoutDoc._width = 300; - } - if (!NumCast(layoutDoc._height)) { - const nw = NumCast(layoutDoc._nativeWidth); - const nh = NumCast(layoutDoc._nativeHeight); - layoutDoc._height = nw && nh ? nh / nw * NumCast(layoutDoc._width) : 300; - } - d.isBackground === undefined && (d.zIndex = zsorted.length + 1 + i); // bringToFront - } - }); - - (de.complete.docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(de.complete.docDragData.droppedDocuments); - } - } - else if (de.complete.annoDragData) { - if (de.complete.annoDragData.dropDocument) { - const dragDoc = de.complete.annoDragData.dropDocument; - const x = xp - de.complete.annoDragData.offset[0]; - const y = yp - de.complete.annoDragData.offset[1]; - const dropX = NumCast(dragDoc.x); - const dropY = NumCast(dragDoc.y); - dragDoc.x = x + NumCast(dragDoc.x) - dropX; - dragDoc.y = y + NumCast(dragDoc.y) - dropY; - de.complete.annoDragData.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation - this.bringToFront(dragDoc); - } - } + } else { + UndoManager.Undo(); } return false; } @@ -887,7 +886,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } else { const contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn._height); const offset = annotOn && (contextHgt / 2 * 96 / 72); - this.props.Document.scrollY = NumCast(doc.y) - offset; + this.props.Document._scrollY = NumCast(doc.y) - offset; } afterFocus && setTimeout(afterFocus, 1000); @@ -1345,7 +1344,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY} - shifted={!this.nativeHeight && !this.isAnnotationOverlay} easing={this.easing} transition={Cast(this.layoutDoc.transition, "string", null)} viewDefDivClick={this.props.viewDefDivClick} @@ -1431,7 +1429,6 @@ interface CollectionFreeFormViewPannableContentsProps { easing: () => boolean; viewDefDivClick?: ScriptField; children: () => JSX.Element[]; - shifted: boolean; transition?: string; } @@ -1446,7 +1443,6 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF const zoom = this.props.zoomScaling(); return <div className={freeformclass} style={{ - width: this.props.shifted ? 0 : undefined, height: this.props.shifted ? 0 : undefined, transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)`, transition: this.props.transition }}> diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 97244ed06..73dd41a15 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -684,7 +684,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @computed get marqueeDiv() { - const p: [number, number] = this._visible ? this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY) : [0, 0]; + const p = this._visible ? this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY) : [0, 0]; const v = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); /** * @RE - The commented out span below diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 98be1adc0..077a4d0d7 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -43,6 +43,7 @@ import "./DocumentView.scss"; import { LinkAnchorBox } from './LinkAnchorBox'; import { RadialMenu } from './RadialMenu'; import React = require("react"); +import { undo } from 'prosemirror-history'; library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight, fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale, @@ -681,6 +682,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true; } + @undoBatch + @action + setAcl = (acl: "readOnly" | "addOnly" | "ownerOnly") => { + this.layoutDoc.ACL = this.dataDoc.ACL = acl; + DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => { + if (d.author === Doc.CurrentUserEmail) d.ACL = acl; + const data = d[DataSym]; + if (data && data.author === Doc.CurrentUserEmail) data.ACL = acl; + }); + } + @action onContextMenu = async (e: React.MouseEvent | Touch): Promise<void> => { // the touch onContextMenu is button 0, the pointer onContextMenu is button 2 @@ -741,9 +753,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const more = cm.findByDescription("More..."); const moreItems: ContextMenuProps[] = more && "subitems" in more ? more.subitems : []; - moreItems.push({ description: "Make Add Only", event: () => this.dataDoc.ACL = this.layoutDoc.ACL = "addOnly", icon: "concierge-bell" }); - moreItems.push({ description: "Make Read Only", event: () => this.dataDoc.ACL = this.layoutDoc.ACL = "readOnly", icon: "concierge-bell" }); - moreItems.push({ description: "Make Private", event: () => this.dataDoc.ACL = this.layoutDoc.ACL = "noAccess", icon: "concierge-bell" }); + moreItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" }); + moreItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" }); + moreItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" }); moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" }); moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 493f23dc4..fae216f17 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -248,7 +248,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum _pdfjsRequested = false; render() { const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField, null); - if (this.props.isSelected() || this.props.renderDepth <= 1 || this.props.Document.scrollY !== undefined) this._everActive = true; + if (this.props.isSelected() || this.props.renderDepth <= 1 || this.props.Document._scrollY !== undefined) this._everActive = true; if (pdfUrl && (this._everActive || this.props.Document._scrollTop || (this.dataDoc[this.props.fieldKey + "-nativeWidth"] && this.props.ScreenToLocalTransform().Scale < 2.5))) { if (pdfUrl instanceof PdfField && this._pdf) { return this.renderPdfView; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 8fdde61e0..fb9e57b51 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -57,15 +57,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum iframe.contentDocument.addEventListener('pointerdown', this.iframedown, false); iframe.contentDocument.addEventListener('scroll', this.iframeScrolled, false); this.layoutDoc.scrollHeight = iframe.contentDocument.children?.[0].scrollHeight || 1000; - iframe.contentDocument.children[0].scrollTop = NumCast(this.layoutDoc.scrollTop); - iframe.contentDocument.children[0].scrollLeft = NumCast(this.layoutDoc.scrollLeft); + iframe.contentDocument.children[0].scrollTop = NumCast(this.layoutDoc._scrollTop); + iframe.contentDocument.children[0].scrollLeft = NumCast(this.layoutDoc._scrollLeft); } this._reactionDisposer?.(); - this._reactionDisposer = reaction(() => ({ y: this.layoutDoc.scrollY, x: this.layoutDoc.scrollX }), + this._reactionDisposer = reaction(() => ({ y: this.layoutDoc._scrollY, x: this.layoutDoc._scrollX }), ({ x, y }) => { if (y !== undefined) { this._outerRef.current!.scrollTop = y; - this.layoutDoc.scrollY = undefined; + this.layoutDoc._scrollY = undefined; } if (x !== undefined) { this._outerRef.current!.scrollLeft = x; @@ -82,8 +82,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum iframeScrolled = (e: any) => { const scrollTop = e.target?.children?.[0].scrollTop; const scrollLeft = e.target?.children?.[0].scrollLeft; - this.layoutDoc.scrollTop = this._outerRef.current!.scrollTop = scrollTop; - this.layoutDoc.scrollLeft = this._outerRef.current!.scrollLeft = scrollLeft; + this.layoutDoc._scrollTop = this._outerRef.current!.scrollTop = scrollTop; + this.layoutDoc._scrollLeft = this._outerRef.current!.scrollLeft = scrollLeft; } async componentDidMount() { const urlField = Cast(this.dataDoc[this.props.fieldKey], WebField); @@ -439,7 +439,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum {this.urlEditor()} </>); } - scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc.scrollLeft), NumCast(this.layoutDoc.scrollTop)); + scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc._scrollLeft), NumCast(this.layoutDoc._scrollTop)); render() { return (<div className={`webBox-container`} style={{ diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 2c1d93222..e55588f8a 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -719,7 +719,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }, { fireImmediately: true } ); - this._disposers.scroll = reaction(() => NumCast(this.layoutDoc.scrollPos), + this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop), pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true } ); @@ -1172,7 +1172,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } onscrolled = (ev: React.UIEvent) => { - this.layoutDoc.scrollPos = this._scrollRef.current!.scrollTop; + this.layoutDoc._scrollTop = this._scrollRef.current!.scrollTop; } @action tryUpdateHeight(limitHeight?: number) { diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index d47ae63af..59a6045ab 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -183,7 +183,7 @@ export class FormattedTextBoxComment { const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc); const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor; if (anchor !== target && anchor && target) { - target.scrollY = NumCast(anchor?.y); + target._scrollY = NumCast(anchor?.y); } if (target) { ReactDOM.render(<ContentFittingDocumentView diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 8cf5026c8..e39e96607 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -36,9 +36,7 @@ import { Networking } from "../../Network"; export const pageSchema = createSchema({ curPage: "number", - fitWidth: "boolean", rotation: "number", - scrollY: "number", scrollHeight: "number", serachMatch: "boolean" }); @@ -93,7 +91,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu @observable private _showWaiting = true; @observable private _showCover = false; @observable private _zoomed = 1; - @observable private _scrollTop = 0; private _pdfViewer: any; private _retries = 0; // number of times tried to create the PDF viewer @@ -141,6 +138,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu this._coverPath = href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" }; runInAction(() => this._showWaiting = this._showCover = true); this.props.startupLive && this.setupPdfJsViewer(); + this._mainCont.current!.scrollTop = this.layoutDoc._scrollTop || 0; this._searchReactionDisposer = reaction(() => this.Document.searchMatch, search => { if (search) { this.search(Doc.SearchQuery(), true); @@ -156,14 +154,12 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu () => (SelectionManager.SelectedDocuments().length === 1) && this.setupPdfJsViewer(), { fireImmediately: true }); this._reactionDisposer = reaction( - () => this.Document.scrollY, + () => this.Document._scrollY, (scrollY) => { if (scrollY !== undefined) { - if (this._showCover || this._showWaiting) { - this.setupPdfJsViewer(); - } - this._mainCont.current && smoothScroll(1000, this._mainCont.current, (this.Document.scrollY || 0)); - this.Document.scrollY = undefined; + (this._showCover || this._showWaiting) && this.setupPdfJsViewer(); + this._mainCont.current && smoothScroll(1000, this._mainCont.current, (this.Document._scrollY || 0)); + setTimeout(() => this.Document._scrollY = undefined, 1000); } }, { fireImmediately: true } @@ -218,7 +214,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu await this.initialLoad(); this._scrollTopReactionDisposer = reaction(() => Cast(this.layoutDoc._scrollTop, "number", null), - (stop) => (stop !== undefined) && this._mainCont.current && smoothScroll(500, this._mainCont.current, stop), { fireImmediately: true }); + (stop) => (stop !== undefined && this.layoutDoc._scrollY === undefined) && (this._mainCont.current!.scrollTop = stop), { fireImmediately: true }); + this._annotationReactionDisposer = reaction( () => DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]), annotations => annotations?.length && (this._annotations = annotations), @@ -350,7 +347,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu @action onScroll = (e: React.UIEvent<HTMLElement>) => { - this._scrollTop = this._mainCont.current!.scrollTop; + this.Document._scrollY === undefined && (this.layoutDoc._scrollTop = this._mainCont.current!.scrollTop); this._pdfViewer && (this.Document.curPage = this._pdfViewer.currentPageNumber); } @@ -602,7 +599,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu } scrollXf = () => { - return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this._scrollTop) : this.props.ScreenToLocalTransform(); + return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this.layoutDoc._scrollTop || 0) : this.props.ScreenToLocalTransform(); } onClick = (e: React.MouseEvent) => { this._setPreviewCursor && diff --git a/src/client/views/webcam/WebCamLogic.js b/src/client/views/webcam/WebCamLogic.js index a8a2f5fa4..5f6202bc8 100644 --- a/src/client/views/webcam/WebCamLogic.js +++ b/src/client/views/webcam/WebCamLogic.js @@ -1,8 +1,5 @@ 'use strict'; import io from "socket.io-client"; -import { - resolvedPorts -} from "../Main"; var socket; var isChannelReady = false; @@ -32,7 +29,7 @@ export function initialize(roomName, handlerUI) { room = roomName; - socket = io.connect(`${window.location.protocol}//${window.location.hostname}:${resolvedPorts.socket}`); + socket = io.connect(`${window.location.protocol}//${window.location.hostname}:4321`); if (room !== '') { socket.emit('create or join', room); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 981025483..6f712f2ed 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -93,7 +93,7 @@ export const HeightSym = Symbol("Height"); export const DataSym = Symbol("Data"); export const LayoutSym = Symbol("Layout"); export const AclSym = Symbol("Acl"); -export const AclPrivate = Symbol("AclNoAccess"); +export const AclPrivate = Symbol("AclOwnerOnly"); export const AclReadonly = Symbol("AclReadOnly"); export const AclAddonly = Symbol("AclAddonly"); export const UpdatingFromServer = Symbol("UpdatingFromServer"); @@ -102,13 +102,17 @@ const CachedUpdates = Symbol("Cached updates"); function fetchProto(doc: Doc) { if (doc.author !== Doc.CurrentUserEmail) { - if (doc.ACL === "noAccess") { - doc[AclSym] = AclPrivate; - return undefined; - } else if (doc.ACL === "readOnly") { - doc[AclSym] = AclReadonly; - } else if (doc.ACL === "addOnly") { - doc[AclSym] = AclAddonly; + const acl = Doc.Get(doc, "ACL", true); + switch (acl) { + case "ownerOnly": + doc[AclSym] = AclPrivate; + return undefined; + case "readOnly": + doc[AclSym] = AclReadonly; + break; + case "addOnly": + doc[AclSym] = AclAddonly; + break; } } @@ -116,7 +120,7 @@ function fetchProto(doc: Doc) { if (proto instanceof Promise) { proto.then(proto => { if (proto.author !== Doc.CurrentUserEmail) { - if (proto.ACL === "noAccess") { + if (proto.ACL === "ownerOnly") { proto[AclSym] = doc[AclSym] = AclPrivate; return undefined; } else if (proto.ACL === "readOnly") { @@ -483,6 +487,7 @@ export namespace Doc { } alias.aliasOf = doc; alias.title = ComputedField.MakeFunction(`renameAlias(this, ${Doc.GetProto(doc).aliasNumber = NumCast(Doc.GetProto(doc).aliasNumber) + 1})`); + alias.author = Doc.CurrentUserEmail; return alias; } @@ -685,7 +690,7 @@ export namespace Doc { } } }); - + copy["author"] = Doc.CurrentUserEmail; return copy; } diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index 31f589ec0..6474ed148 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -22,10 +22,10 @@ export const documentSchema = createSchema({ y: "number", // y coordinate when in a freeform view z: "number", // z "coordinate" - non-zero specifies the overlay layer of a freeformview zIndex: "number", // zIndex of a document in a freeform view - scrollY: "number", // "command" to scroll a document to a position on load (the value will be reset to 0 after that ) - scrollX: "number", // "command" to scroll a document to a position on load (the value will be reset to 0 after that ) - scrollTop: "number", // scroll position of a scrollable document (pdf, text, web) - scrollLeft: "number", // scroll position of a scrollable document (pdf, text, web) + _scrollY: "number", // "command" to scroll a document to a position on load (the value will be reset to 0 after that ) + _scrollX: "number", // "command" to scroll a document to a position on load (the value will be reset to 0 after that ) + _scrollTop: "number", // scroll position of a scrollable document (pdf, text, web) + _scrollLeft: "number", // scroll position of a scrollable document (pdf, text, web) // appearance properties on the layout _autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents |