diff options
Diffstat (limited to 'src')
43 files changed, 333 insertions, 371 deletions
diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index 748d571c0..2a1f55710 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -10,6 +10,7 @@ import { FieldView, FieldViewProps } from "../../views/nodes/FieldView"; import "../../views/nodes/WebBox.scss"; import "./YoutubeBox.scss"; import React = require("react"); +import { InkTool } from '../../../fields/InkField'; interface VideoTemplate { thumbnailUrl: string; @@ -349,7 +350,7 @@ export class YoutubeBox extends React.Component<FieldViewProps> { const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; - const classname = "webBox-cont" + (this.props.isSelected() && !Doc.GetSelectedTool() && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); + const classname = "webBox-cont" + (this.props.isSelected() && Doc.GetSelectedTool() === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); return ( <> <div className={classname} > diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9c807eff2..8d867348f 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); @@ -630,7 +631,7 @@ export namespace Docs { return doc; } - export function InkDocument(color: string, tool: number, strokeWidth: string, strokeBezier: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) { + export function InkDocument(color: string, tool: string, strokeWidth: string, strokeBezier: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) { const I = new Doc(); I.type = DocumentType.INK; I.layout = InkingStroke.LayoutString("data"); 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/util/DragManager.ts b/src/client/util/DragManager.ts index e7a14f9c5..f1afaf734 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -67,6 +67,7 @@ export function SetupDrag( export namespace DragManager { let dragDiv: HTMLDivElement; + let dragLabel: HTMLDivElement; export let StartWindowDrag: Opt<((e: any, dragDocs: Doc[]) => void)> = undefined; export function Root() { @@ -97,7 +98,8 @@ export namespace DragManager { readonly shiftKey: boolean, readonly altKey: boolean, readonly metaKey: boolean, - readonly ctrlKey: boolean + readonly ctrlKey: boolean, + readonly embedKey: boolean, ) { } } @@ -309,14 +311,24 @@ export namespace DragManager { }; } export let docsBeingDragged: Doc[] = []; + export let CanEmbed = false; export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) { eles = eles.filter(e => e); + CanEmbed = false; if (!dragDiv) { dragDiv = document.createElement("div"); dragDiv.className = "dragManager-dragDiv"; dragDiv.style.pointerEvents = "none"; + dragLabel = document.createElement("div") as HTMLDivElement; + dragLabel.className = "dragManager-dragLabel"; + dragLabel.style.zIndex = "100001"; + dragLabel.style.fontSize = "10"; + dragLabel.style.position = "absolute"; + dragLabel.innerText = "press 'a' to embed on drop"; + dragDiv.appendChild(dragLabel); DragManager.Root().appendChild(dragDiv); } + dragLabel.style.display = ""; SnappingManager.SetIsDragging(true); const scaleXs: number[] = []; const scaleYs: number[] = []; @@ -358,6 +370,7 @@ export namespace DragManager { dragElement.style.transform = `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0)}px) scale(${scaleX}, ${scaleY})`; dragElement.style.width = `${rect.width / scaleX}px`; dragElement.style.height = `${rect.height / scaleY}px`; + dragLabel.style.transform = `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0) - 20}px)`; if (docsBeingDragged.length) { const pdfBox = dragElement.getElementsByTagName("canvas"); @@ -399,20 +412,21 @@ export namespace DragManager { if (dragData instanceof DocumentDragData) { dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : undefined; } - if (e.shiftKey && dragData.droppedDocuments.length === 1) { - !dragData.dropAction && (dragData.dropAction = alias); - if (dragData.dropAction === "move") { - dragData.removeDocument?.(dragData.draggedDocuments[0]); + if (e) + if (e.shiftKey && dragData.droppedDocuments.length === 1) { + !dragData.dropAction && (dragData.dropAction = alias); + if (dragData.dropAction === "move") { + dragData.removeDocument?.(dragData.draggedDocuments[0]); + } + AbortDrag(); + finishDrag?.(new DragCompleteEvent(true, dragData)); + DragManager.StartWindowDrag?.({ + pageX: e.pageX, + pageY: e.pageY, + preventDefault: emptyFunction, + button: 0 + }, dragData.droppedDocuments); } - AbortDrag(); - finishDrag?.(new DragCompleteEvent(true, dragData)); - DragManager.StartWindowDrag?.({ - pageX: e.pageX, - pageY: e.pageY, - preventDefault: emptyFunction, - button: 0 - }, dragData.droppedDocuments); - } const { thisX, thisY } = snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom); @@ -421,12 +435,14 @@ export namespace DragManager { const moveY = thisY - lastY; lastX = thisX; lastY = thisY; + dragLabel.style.transform = `translate(${xs[0] + moveX + (options?.offsetX || 0)}px, ${ys[0] + moveY + (options?.offsetY || 0) - 20}px)`; dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveX) + (options?.offsetX || 0)}px, ${(ys[i] += moveY) + (options?.offsetY || 0)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`) ); }; const hideDragShowOriginalElements = () => { + dragLabel.style.display = "none"; dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement)); eles.map(ele => ele.parentElement && ele.parentElement?.className === dragData.dragDivName ? (ele.parentElement.hidden = false) : (ele.hidden = false)); }; @@ -481,7 +497,8 @@ export namespace DragManager { shiftKey: e.shiftKey, altKey: e.altKey, metaKey: e.metaKey, - ctrlKey: e.ctrlKey + ctrlKey: e.ctrlKey, + embedKey: CanEmbed } }) ); @@ -496,7 +513,8 @@ export namespace DragManager { shiftKey: e.shiftKey, altKey: e.altKey, metaKey: e.metaKey, - ctrlKey: e.ctrlKey + ctrlKey: e.ctrlKey, + embedKey: CanEmbed } }) ); diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 4e5cde558..25c556697 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -7,7 +7,7 @@ import Measure, { ContentRect } from "react-measure"; import { library } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faTag, faPlus, faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons'; -import { Docs, DocumentOptions } from "../../documents/Documents"; +import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; import { observer } from "mobx-react"; import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from "./ImportMetadataEntry"; import { Utils } from "../../../Utils"; @@ -123,7 +123,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps> } const { accessPaths, exifData } = result; const path = Utils.prepend(accessPaths.agnostic.client); - const document = await Doc.Get.DocumentFromType(type, path, { _width: 300, title: name }); + const document = await DocUtils.DocumentFromType(type, path, { _width: 300, title: name }); const { data, error } = exifData; if (document) { Doc.GetProto(document).exif = error || Doc.Get.FromJson({ data }); 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..6ca7331d6 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -403,7 +403,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> onPointerUp = (e: PointerEvent): void => { SelectionManager.SelectedDocuments().map(dv => { if (NumCast(dv.layoutDoc._delayAutoHeight) < this._dragHeights.get(dv.layoutDoc)!) { - dv.nativeWidth > 0 && Doc.toggleNativeDimensions(dv.layoutDoc, dv.props.ContentScaling(), dv.panelWidth(), dv.panelHeight()); + dv.nativeWidth > 0 && Doc.toggleNativeDimensions(dv.layoutDoc, dv.props.ContentScaling(), dv.props.PanelWidth(), dv.props.PanelHeight()); dv.layoutDoc._autoHeight = true; } dv.layoutDoc._delayAutoHeight = undefined; @@ -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/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 2d5af5386..a3b144055 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -72,6 +72,8 @@ export default class KeyManager { private unmodified = action((keyname: string, e: KeyboardEvent) => { switch (keyname) { + case "a": DragManager.CanEmbed = true; + break; case " ": MarqueeView.DragMarquee = !MarqueeView.DragMarquee; break; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index acbfde5a1..a7650163f 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -32,6 +32,14 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]); } + private makeMask = () => { + this.props.Document._backgroundColor = "rgba(0,0,0,0.7)"; + this.props.Document.mixBlendMode = "hard-light"; + this.props.Document.color = "#9b9b9bff"; + this.props.Document.stayInCollection = true; + this.props.Document.isInkMask = true; + } + render() { TraceMobx(); const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? []; @@ -59,13 +67,14 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume <svg className="inkingStroke" width={width} height={height} - style={{ pointerEvents: "none", mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset" }} + style={{ + pointerEvents: this.props.Document.isInkMask ? "all" : "none", + transform: this.props.Document.isInkMask ? "translate(2500px, 2500px)" : undefined, + mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset" + }} onContextMenu={() => { - ContextMenu.Instance.addItem({ - description: "Analyze Stroke", - event: this.analyzeStrokes, - icon: "paint-brush" - }); + ContextMenu.Instance.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" }); + ContextMenu.Instance.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" }); }} ><defs> <filter id="dangerShine"> @@ -108,5 +117,5 @@ Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any) { Scripting.addGlobal(function activateEraser(pen: any) { return Doc.SetSelectedTool(pen ? InkTool.Eraser : InkTool.None); }); Scripting.addGlobal(function activateStamp(pen: any) { return Doc.SetSelectedTool(pen ? InkTool.Stamp : InkTool.None); }); Scripting.addGlobal(function deactivateInk() { return Doc.SetSelectedTool(InkTool.None); }); -Scripting.addGlobal(function setInkWidth(width: any) { return Doc.SetSelectedTool(width); }); -Scripting.addGlobal(function setInkColor(color: any) { return Doc.SetSelectedTool(color); });
\ No newline at end of file +Scripting.addGlobal(function setInkWidth(width: any) { return SetActiveInkWidth(width); }); +Scripting.addGlobal(function setInkColor(color: any) { return SetActiveInkColor(color); });
\ No newline at end of file diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 77e6ebf44..f135823a8 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -7,7 +7,7 @@ import { DocumentView } from "./nodes/DocumentView"; import { Template } from "./Templates"; import React = require("react"); import { Doc, DocListCast } from "../../fields/Doc"; -import { Docs, } from "../documents/Documents"; +import { Docs, DocUtils, } from "../documents/Documents"; import { StrCast, Cast } from "../../fields/Types"; import { CollectionTreeView } from "./collections/CollectionTreeView"; import { returnTrue, emptyFunction, returnFalse, returnOne, emptyPath, returnZero } from "../../Utils"; @@ -176,5 +176,5 @@ Scripting.addGlobal(function switchView(doc: Doc, template: Doc | undefined) { template = Cast(template.dragFactory, Doc, null); } const templateTitle = StrCast(template?.title); - return templateTitle && Doc.makeCustomViewClicked(doc, Docs.Create.FreeformDocument, templateTitle, template); + return templateTitle && DocUtils.makeCustomViewClicked(doc, Docs.Create.FreeformDocument, templateTitle, template); }); diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx index b562bd957..3a7182a94 100644 --- a/src/client/views/animationtimeline/Keyframe.tsx +++ b/src/client/views/animationtimeline/Keyframe.tsx @@ -180,10 +180,10 @@ export class Keyframe extends React.Component<IProps> { const fadeIn = this.props.makeKeyData(this.regiondata, this.regiondata.position + this.regiondata.fadeIn, KeyframeFunc.KeyframeType.fade); const fadeOut = this.props.makeKeyData(this.regiondata, this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut, KeyframeFunc.KeyframeType.fade); const finish = this.props.makeKeyData(this.regiondata, this.regiondata.position + this.regiondata.duration, KeyframeFunc.KeyframeType.end); - (fadeIn as Doc).opacity = 1; - (fadeOut as Doc).opacity = 1; - (start as Doc).opacity = 0.1; - (finish as Doc).opacity = 0.1; + fadeIn.opacity = 1; + fadeOut.opacity = 1; + start.opacity = 0.1; + finish.opacity = 0.1; this.forceUpdate(); //not needed, if setTimeout is gone... }, 1000); } diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index 30692944d..ab984f727 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(); } } @@ -532,11 +445,8 @@ 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); + const current = KeyframeFunc.convertPixelTime(this._currentBarX, "mili", "time", this._tickSpacing, this._tickIncrement); + 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/TimelineOverview.tsx b/src/client/views/animationtimeline/TimelineOverview.tsx index 31e248823..81a5587e4 100644 --- a/src/client/views/animationtimeline/TimelineOverview.tsx +++ b/src/client/views/animationtimeline/TimelineOverview.tsx @@ -42,9 +42,9 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps>{ this.setOverviewWidth(); this._authoringReaction = reaction( - () => this.props.parent._isAuthoring, + () => this.props.isAuthoring, () => { - if (!this.props.parent._isAuthoring) { + if (!this.props.isAuthoring) { runInAction(() => { this.setOverviewWidth(); }); 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/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index fc48e0327..22a3877ab 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -12,6 +12,7 @@ import { SelectionManager } from "../../util/SelectionManager"; import { UndoManager, undoBatch } from "../../util/UndoManager"; import { SnappingManager } from "../../util/SnappingManager"; import { DragManager } from "../../util/DragManager"; +import { DocUtils } from "../../documents/Documents"; @observer export class CollectionPileView extends CollectionSubView(doc => doc) { @@ -45,12 +46,12 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { if (this.layoutEngine() === 'starburst') { const defaultSize = 110; this.layoutDoc._overflow = undefined; - this.childDocs.forEach(d => Doc.iconify(d)); + this.childDocs.forEach(d => DocUtils.iconify(d)); this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2; this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2; this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize); this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize); - Doc.pileup(this.childDocs); + DocUtils.pileup(this.childDocs); this.layoutDoc._panX = 0; this.layoutDoc._panY = -10; this.props.Document._pileLayoutEngine = 'pass'; @@ -76,7 +77,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { onInternalDrop = (e: Event, de: DragManager.DropEvent) => { if (super.onInternalDrop(e, de)) { if (de.complete.docDragData) { - Doc.pileup(this.childDocs); + DocUtils.pileup(this.childDocs); } } return true; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 7fd19a23c..0a1b03522 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -27,6 +27,7 @@ import { CollectionSubView } from "./CollectionSubView"; import { CollectionViewType } from "./CollectionView"; import { SnappingManager } from "../../util/SnappingManager"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; +import { DocUtils } from "../../documents/Documents"; const _global = (window /* browser */ || global /* node */) as any; type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>; @@ -412,7 +413,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) if (value && this.sectionHeaders) { const schemaHdrField = new SchemaHeaderField(value); this.sectionHeaders.push(schemaHdrField); - Doc.addFieldEnumerations(undefined, this.pivotField, [{ title: value, _backgroundColor: schemaHdrField.color }]); + DocUtils.addFieldEnumerations(undefined, this.pivotField, [{ title: value, _backgroundColor: schemaHdrField.color }]); return true; } return false; @@ -474,7 +475,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/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index a269b21f5..bcd55f0fe 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -235,7 +235,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof (dataDoc[fieldKey]) === "string").map(fieldKey => docItems.push({ description: ":" + fieldKey, event: () => { - const created = Docs.Get.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.parent.props.Document)); + const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.parent.props.Document)); if (created) { if (this.props.parent.Document.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(created, this.props.parent.props.Document); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index b60419258..93d20c475 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -10,7 +10,7 @@ import { WebField } from "../../../fields/URLField"; import { Cast, ScriptCast, NumCast } from "../../../fields/Types"; import { GestureUtils } from "../../../pen-gestures/GestureUtils"; import { Upload } from "../../../server/SharedMediaTypes"; -import { Utils } from "../../../Utils"; +import { Utils, returnFalse } from "../../../Utils"; import { DocServer } from "../../DocServer"; import { Networking } from "../../Network"; import { ImageUtils } from "../../util/Import & Export/ImageUtils"; @@ -217,11 +217,11 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d); const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d); const res = addedDocs.length ? this.addDocument(addedDocs) : true; - added = movedDocs.length ? docDragData.moveDocument(movedDocs, this.props.Document, this.addDocument) : res; + added = movedDocs.length ? docDragData.moveDocument(movedDocs, this.props.Document, de.embedKey || !this.props.isAnnotationOverlay ? this.addDocument : returnFalse) : res; } else { added = this.addDocument(docDragData.droppedDocuments); } - e.stopPropagation(); + added && e.stopPropagation(); return added; } else if (de.complete.annoDragData) { @@ -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/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 15bc0bfd5..c2d682361 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -19,6 +19,7 @@ const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; import React = require("react"); +import { DocUtils } from "../../documents/Documents"; @observer export class CollectionTimeView extends CollectionSubView(doc => doc) { @@ -28,7 +29,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { @observable _childClickedScript: Opt<ScriptField>; @observable _viewDefDivClick: Opt<ScriptField>; async componentDidMount() { - const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || Doc.findTemplate("detailView", StrCast(this.props.Document.type), ""); + const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.props.Document.type), ""); const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); "; runInAction(() => { this._childClickedScript = ScriptField.MakeScript(childText, { this: Doc.name, shiftKey: "boolean" }, { detailView: detailView! }); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 3330abbc4..a25a864af 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -32,7 +32,7 @@ import { CollectionDockingView } from "./CollectionDockingView"; import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import { CollectionLinearView } from './CollectionLinearView'; -import { CollectionMapView } from './CollectionMapView'; +import CollectionMapView from './CollectionMapView'; import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView'; import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView'; import { CollectionPileView } from './CollectionPileView'; @@ -163,7 +163,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { return true; } - return this.removeDocument(doc) ? addDocument(doc) : false; + const first = doc instanceof Doc ? doc : doc[0]; + return !first?.stayInCollection && addDocument !== returnFalse && this.removeDocument(doc) ? addDocument(doc) : false; } showIsTagged = () => { diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 29a3e559a..3dc740c25 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -93,7 +93,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro this.props.collapse?.(true); break; } - }) + }); @undoBatch viewChanged = (e: React.ChangeEvent) => { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 9c0e5e917..678ad2a53 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()); @@ -175,7 +177,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } } return retVal; - }) + }); private selectDocuments = (docs: Doc[]) => { SelectionManager.DeselectAll(); @@ -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 = doc.isInkMask ? 5000 : 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); + } else if (de.complete.annoDragData?.dropDocument && super.onInternalDrop(e, de)) { + return this.internalPdfAnnoDrop(e, de.complete.annoDragData, xp, yp); + } else 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; } @@ -531,9 +530,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } }); - console.log(this._wordPalette) CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => { - console.log(results); const wordResults = results.filter((r: any) => r.category === "inkWord"); for (const word of wordResults) { const indices: number[] = word.strokeIds; @@ -618,8 +615,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return; } if (!e.cancelBubble) { - const selectedTool = Doc.GetSelectedTool(); - if (selectedTool === InkTool.None) { + if (Doc.GetSelectedTool() === InkTool.None) { if (this._hitCluster && this.tryDragCluster(e)) { e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers e.preventDefault(); @@ -839,8 +835,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P bringToFront = action((doc: Doc, sendToBack?: boolean) => { if (sendToBack || doc.isBackground) { doc.zIndex = 0; - } - else { + } else if (doc.isInkMask) { + doc.zIndex = 5000; + } else { const docs = this.childLayoutPairs.map(pair => pair.layout); docs.slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); let zlast = docs.length ? NumCast(docs[docs.length - 1].zIndex) : 1; @@ -887,7 +884,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 +1342,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 +1427,6 @@ interface CollectionFreeFormViewPannableContentsProps { easing: () => boolean; viewDefDivClick?: ScriptField; children: () => JSX.Element[]; - shifted: boolean; transition?: string; } @@ -1446,7 +1441,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.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index a811dd15a..62510ce9d 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -28,6 +28,6 @@ white-space:nowrap; } .marquee-legend::after { - content: "Press: c (collection), s (summary), or Delete" + content: "Press <space> for lasso" } }
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b0461609a..085625e69 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -62,7 +62,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque } this._pointsX = []; this._pointsY = []; - this._freeHand = false; } @undoBatch @@ -353,7 +352,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque const selected = this.marqueeSelect(false); SelectionManager.DeselectAll(); selected.forEach(d => this.props.removeDocument(d)); - const newCollection = Doc.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2); + const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2); this.props.addDocument(newCollection!); this.props.selectDocuments([newCollection!], []); MarqueeOptionsMenu.Instance.fadeOut(true); @@ -528,7 +527,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque } this.cleanupInteractions(false); } - if (e.key === "r") { + if (e.key === "r" || e.key === " ") { this._commandExecuted = true; e.stopPropagation(); e.preventDefault(); @@ -538,7 +537,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @action changeFreeHand = (x: boolean) => { - this._freeHand = x; + this._freeHand = !this._freeHand; } // @action // marqueeInkSelect(ink: Map<any, any>) { @@ -685,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 @@ -698,7 +697,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}`, zIndex: 2000 }} > - {/* <span className="marquee-legend" /> */} + <span className="marquee-legend"></span> </div>; } else { diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 509f78021..f934945a6 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -37,7 +37,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF return min + rnd * (max - min); } get displayName() { return "CollectionFreeFormDocumentView(" + this.rootDoc.title + ")"; } // this makes mobx trace() statements more descriptive - get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${this.random(-1, 1) * this.props.jitterRotation}deg)`; } + get maskCentering() { return this.props.Document.isInkMask ? 2500 : 0; } + get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${this.random(-1, 1) * this.props.jitterRotation}deg)`; } get X() { return this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); } get Y() { return this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); } get Opacity() { return this.dataProvider ? this.dataProvider.opacity : Cast(this.layoutDoc.opacity, "number", null); } @@ -80,8 +81,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF public static setValues(time: number, d: Doc, x?: number, y?: number, opacity?: number) { const timecode = Math.round(time); - Cast(d["x-indexed"], listSpec("number"), [])[Math.max(0, timecode - 1)] = x as any as number; - Cast(d["y-indexed"], listSpec("number"), [])[Math.max(0, timecode - 1)] = y as any as number; Cast(d["x-indexed"], listSpec("number"), [])[timecode] = x as any as number; Cast(d["y-indexed"], listSpec("number"), [])[timecode] = y as any as number; Cast(d["opacity-indexed"], listSpec("number"), null)[timecode] = opacity as any as number; @@ -111,12 +110,12 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF const xlist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]); const ylist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]); const olist = new List<number>(numberRange(timecode + 1).map(t => progressivize && t < i ? 0 : 1)); - xlist[Math.max(curTimecode - 1, 0)] = xlist[curTimecode] = NumCast(doc.x); - ylist[Math.max(curTimecode - 1, 0)] = ylist[curTimecode] = NumCast(doc.y); + xlist[curTimecode] = NumCast(doc.x); + ylist[curTimecode] = NumCast(doc.y); doc["x-indexed"] = xlist; doc["y-indexed"] = ylist; doc["opacity-indexed"] = olist; - doc.activeFrame = ComputedField.MakeFunction("self.context ? (self.context.currentFrame||0) : 0"); + doc.activeFrame = ComputedField.MakeFunction("self.context?.currentFrame||0"); doc.x = ComputedField.MakeInterpolated("x", "activeFrame"); doc.y = ComputedField.MakeInterpolated("y", "activeFrame"); doc.opacity = ComputedField.MakeInterpolated("opacity", "activeFrame"); @@ -152,12 +151,12 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF outline: this.Highlight ? "orange solid 2px" : "", transform: this.transform, transition: this.props.transition ? this.props.transition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.transition), - width: this.width, - height: this.height, + width: this.props.Document.isInkMask ? 5000 : this.width, + height: this.props.Document.isInkMask ? 5000 : this.height, zIndex: this.ZInd, mixBlendMode: StrCast(this.layoutDoc.mixBlendMode) as any, display: this.ZInd === -99 ? "none" : undefined, - pointerEvents: this.props.Document.isBackground || this.Opacity === 0 || this.props.Document.type === DocumentType.INK ? "none" : this.props.pointerEvents ? "all" : undefined + pointerEvents: this.props.Document.isBackground || this.Opacity === 0 || this.props.Document.type === DocumentType.INK || this.props.Document.isInkMask ? "none" : this.props.pointerEvents ? "all" : undefined }} > {Doc.UserDoc().renderStyle !== "comic" ? (null) : <div style={{ width: "100%", height: "100%", position: "absolute" }}> diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9f2b0124a..1d98a2628 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/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index d75b864cf..f7dee0896 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -18,6 +18,7 @@ import { ContextMenuProps } from "../ContextMenuItem"; import { ViewBoxBaseComponent } from "../DocComponent"; import { FieldView, FieldViewProps } from './FieldView'; import "./ScreenshotBox.scss"; +import { InkTool } from "../../../fields/InkField"; const path = require('path'); type ScreenshotDocument = makeInterface<[typeof documentSchema]>; @@ -132,7 +133,7 @@ export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, Screensh } @computed get content() { - const interactive = Doc.GetSelectedTool() || !this.props.isSelected() ? "" : "-interactive"; + const interactive = Doc.GetSelectedTool() !== InkTool.None || !this.props.isSelected() ? "" : "-interactive"; const style = "videoBox-content" + interactive; return <video className={`${style}`} key="video" autoPlay={this._screenCapture} ref={this.setVideoRef} style={{ width: this._screenCapture ? "100%" : undefined, height: this._screenCapture ? "100%" : undefined }} diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 1e0a859d3..e4dbceca6 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -228,7 +228,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD @computed get content() { const field = Cast(this.dataDoc[this.fieldKey], VideoField); - const interactive = Doc.GetSelectedTool() || !this.props.isSelected() ? "" : "-interactive"; + const interactive = Doc.GetSelectedTool() !== InkTool.None || !this.props.isSelected() ? "" : "-interactive"; const style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive; return !field ? <div>Loading</div> : <video className={`${style}`} key="video" autoPlay={this._screenCapture} ref={this.setVideoRef} 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/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 05e6a5959..55b3f6f1e 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -7,7 +7,7 @@ import { ComputedField } from "../../../../fields/ScriptField"; import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types"; import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; -import { Docs } from "../../../documents/Documents"; +import { Docs, DocUtils } from "../../../documents/Documents"; import { DocumentView } from "../DocumentView"; import { FormattedTextBox } from "./FormattedTextBox"; import { Transform } from "../../../util/Transform"; @@ -48,7 +48,7 @@ export class DashDocView extends React.Component<IDashDocView> { if (dashDocBase instanceof Doc) { const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias); aliasedDoc.layoutKey = "layout"; - node.attrs.fieldKey && Doc.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined); + node.attrs.fieldKey && DocUtils.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined); this._dashDoc = aliasedDoc; // self.doRender(aliasedDoc, removeDoc, node, view, getPos); } diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index d05e8f1ea..9a1b909c1 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -12,6 +12,7 @@ import React = require("react"); import * as ReactDOM from 'react-dom'; import "./DashFieldView.scss"; import { observer } from "mobx-react"; +import { DocUtils } from "../../../documents/Documents"; export class DashFieldView { @@ -117,7 +118,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna @action fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => { if (e.key === "Enter") { // handle the enter key by "submitting" the current text to Dash's database. - e.ctrlKey && Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: span.textContent! }]); + e.ctrlKey && DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: span.textContent! }]); this.updateText(span.textContent!, true); e.preventDefault();// prevent default to avoid a newline from being generated and wiping out this field view } @@ -147,7 +148,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); if (modText) { // elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText; - Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, []); + DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, []); this._dashDoc![this._fieldKey] = modText; } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key else if (nodeText.startsWith(":=")) { @@ -167,7 +168,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna // display a collection of all the enumerable values for this field onPointerDownEnumerables = async (e: any) => { e.stopPropagation(); - const collview = await Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]); + const collview = await DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]); collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "onRight"); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 477a2ca08..49114d967 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -95,6 +95,17 @@ .formattedTextBox-inner { height: 100%; white-space: pre-wrap; + hr { + display: block; + unicode-bidi: isolate; + margin-block-start: 0.5em; + margin-block-end: 0.5em; + margin-inline-start: auto; + margin-inline-end: auto; + overflow: hidden; + border-style: inset; + border-width: 1px; + } } // .menuicon { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 2c1d93222..1fab54d7e 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) { @@ -1211,7 +1211,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp TraceMobx(); const scale = this.props.ContentScaling() * NumCast(this.layoutDoc.scale, 1); const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; - const interactive = Doc.GetSelectedTool() || this.layoutDoc.isBackground; + const interactive = Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc.isBackground; if (this.props.isSelected()) { this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props); } else if (FormattedTextBoxComment.textBox === this) { @@ -1232,7 +1232,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp background: Doc.UserDoc().renderStyle === "comic" ? "transparent" : this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : ""), opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1, color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"), - pointerEvents: interactive ? "none" : undefined, + pointerEvents: interactive ? undefined : "none", fontSize: Cast(this.layoutDoc._fontSize, "number", null), fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit") }} 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/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx index 91280dea4..1cc7ec8bf 100644 --- a/src/client/views/nodes/formattedText/RichTextSchema.tsx +++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx @@ -18,7 +18,7 @@ import { ComputedField } from "../../../../fields/ScriptField"; import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../../../fields/Types"; import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; -import { Docs } from "../../../documents/Documents"; +import { Docs, DocUtils } from "../../../documents/Documents"; import { CollectionViewType } from "../../collections/CollectionView"; import { DocumentView } from "../DocumentView"; import { FormattedTextBox } from "./FormattedTextBox"; @@ -245,7 +245,7 @@ export class DashDocView { if (dashDocBase instanceof Doc) { const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias); aliasedDoc.layoutKey = "layout"; - node.attrs.fieldKey && Doc.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined); + node.attrs.fieldKey && DocUtils.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined); self.doRender(aliasedDoc, removeDoc, node, view, getPos); } }); 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/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx index 83de9cc15..647e1ce6f 100644 --- a/src/client/views/webcam/DashWebRTCVideo.tsx +++ b/src/client/views/webcam/DashWebRTCVideo.tsx @@ -11,6 +11,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; import { faSync, faPhoneSlash } from "@fortawesome/free-solid-svg-icons"; import { Doc } from "../../../fields/Doc"; +import { InkTool } from "../../../fields/InkField"; library.add(faSync); library.add(faPhoneSlash); @@ -72,7 +73,7 @@ export class DashWebRTCVideo extends React.Component<CollectionFreeFormDocumentV </div >; const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; - const classname = "webBox-cont" + (this.props.isSelected() && !Doc.GetSelectedTool() && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); + const classname = "webBox-cont" + (this.props.isSelected() && Doc.GetSelectedTool() === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); return ( <> 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..ffef9a384 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; } @@ -817,7 +822,7 @@ export namespace Doc { export function UserDoc(): Doc { return manager._user_doc; } export function SetSelectedTool(tool: InkTool) { Doc.UserDoc().activeInkTool = tool; } - export function GetSelectedTool(): InkTool { return (FieldValue(StrCast(Doc.UserDoc().activeInkTool)) ?? InkTool.None) as InkTool; } + export function GetSelectedTool(): InkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkTool; } export function SetUserDoc(doc: Doc) { manager._user_doc = doc; } export function IsBrushed(doc: Doc) { return computedFn(function IsBrushed(doc: Doc) { diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts index c475d0d73..f81ec8c6d 100644 --- a/src/fields/RichTextUtils.ts +++ b/src/fields/RichTextUtils.ts @@ -3,7 +3,7 @@ import { docs_v1 } from "googleapis"; import { Fragment, Mark, Node } from "prosemirror-model"; import { sinkListItem } from "prosemirror-schema-list"; import { Utils } from "../Utils"; -import { Docs } from "../client/documents/Documents"; +import { Docs, DocUtils } from "../client/documents/Documents"; import { schema } from "../client/views/nodes/formattedText/schema_rts"; import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils"; import { DocServer } from "../client/DocServer"; @@ -272,7 +272,7 @@ export namespace RichTextUtils { const backingDocId = StrCast(textNote[guid]); if (!backingDocId) { const backingDoc = Docs.Create.ImageDocument(agnostic, { _width: 300, _height: 300 }); - Doc.makeCustomViewClicked(backingDoc, Docs.Create.FreeformDocument); + DocUtils.makeCustomViewClicked(backingDoc, Docs.Create.FreeformDocument); docid = backingDoc[Id]; textNote[guid] = docid; } else { @@ -401,7 +401,7 @@ export namespace RichTextUtils { let exported = (await Cast(linkDoc.anchor2, Doc))!; if (!exported.customLayout) { exported = Doc.MakeAlias(exported); - Doc.makeCustomViewClicked(exported, Docs.Create.FreeformDocument); + DocUtils.makeCustomViewClicked(exported, Docs.Create.FreeformDocument); linkDoc.anchor2 = exported; } url = Utils.shareUrl(exported[Id]); diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index 31f589ec0..40dadf5a8 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 @@ -58,6 +58,7 @@ export const documentSchema = createSchema({ color: "string", // foreground color of document fitToBox: "boolean", // whether freeform view contents should be zoomed/panned to fill the area of the document view fontSize: "string", + isInkMask: "boolean", // is the document a mask (ie, sits on top of other documents, has an unbounded width/height that is dark, and content uses 'hard-light' mix-blend-mode to let other documents pop through) layout: "string", // this is the native layout string for the document. templates can be added using other fields and setting layoutKey below layoutKey: "string", // holds the field key for the field that actually holds the current lyoat letterSpacing: "string", @@ -83,6 +84,7 @@ export const documentSchema = createSchema({ _lockedTransform: "boolean",// whether a freeformview can pan/zoom // drag drop properties + stayInCollection: "boolean",// whether document can be dropped into a different collection dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document. dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias", "copy", "move") targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move") NOTE: if the document is dropped within the same collection, the dropAction is coerced to 'move' diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 756bde738..fe39b84e6 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -301,8 +301,7 @@ async function captureYoutubeScreenshot(targetUrl: string): Promise<Opt<Buffer>> await videoPlayer?.click(); await delay(1000); // hide youtube player controls. - await page.evaluate(() => - (document.querySelector('.ytp-chrome-bottom') as any).style.display = 'none'); + await page.evaluate(() => (document.querySelector('.ytp-chrome-bottom') as HTMLElement).style.display = 'none'); const buffer = await videoPlayer?.screenshot({ encoding: "binary" }); await browser.close(); |