diff options
| author | usodhi <61431818+usodhi@users.noreply.github.com> | 2020-11-17 19:28:30 +0530 |
|---|---|---|
| committer | usodhi <61431818+usodhi@users.noreply.github.com> | 2020-11-17 19:28:30 +0530 |
| commit | 86408e6d93fbe6501694371736fe74b81ed39cf3 (patch) | |
| tree | 17b89a6209c66284f89e2636a8157435ce1045c0 /src/client/views/nodes | |
| parent | a002e0e5c5910f78c8f3910ad4101386d30ebf70 (diff) | |
| parent | 28dccafaa4aa446dd88c1b6f4218a0d7f79fa1bb (diff) | |
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into acls_uv
Diffstat (limited to 'src/client/views/nodes')
25 files changed, 1107 insertions, 762 deletions
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 5a8ce4e14..c87239ee9 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -16,7 +16,7 @@ import { ComputedField } from "../../../fields/ScriptField"; import { listSpec } from "../../../fields/Schema"; import { DocumentType } from "../../documents/DocumentTypes"; import { Zoom, Fade, Flip, Rotate, Bounce, Roll, LightSpeed } from 'react-reveal'; -import { PresBox } from "./PresBox"; +import { PresBox, PresEffect } from "./PresBox"; import { InkingStroke } from "../InkingStroke"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { @@ -55,8 +55,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF @computed get freezeDimensions() { return this.props.FreezeDimensions; } @computed get dataProvider() { return this.props.dataProvider?.(this.props.Document, this.props.replica); } @computed get sizeProvider() { return this.props.sizeProvider?.(this.props.Document, this.props.replica); } - @computed get nativeWidth() { return returnVal(this.props.NativeWidth?.(), NumCast(this.layoutDoc._nativeWidth, this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); } - @computed get nativeHeight() { return returnVal(this.props.NativeHeight?.(), NumCast(this.layoutDoc._nativeHeight, this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); } + @computed get nativeWidth() { return returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, undefined, this.freezeDimensions)); } + @computed get nativeHeight() { return returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, undefined, this.freezeDimensions)); } public static getValues(doc: Doc, time: number) { const timecode = Math.round(time); @@ -212,23 +212,23 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF PanelHeight={this.panelHeight} />; if (PresBox.Instance && this.layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc) { const effectProps = { - left: this.layoutDoc.presEffectDirection === 'left', - right: this.layoutDoc.presEffectDirection === 'right', - top: this.layoutDoc.presEffectDirection === 'top', - bottom: this.layoutDoc.presEffectDirection === 'bottom', + left: this.layoutDoc.presEffectDirection === PresEffect.Left, + right: this.layoutDoc.presEffectDirection === PresEffect.Right, + top: this.layoutDoc.presEffectDirection === PresEffect.Top, + bottom: this.layoutDoc.presEffectDirection === PresEffect.Bottom, opposite: true, delay: this.layoutDoc.presTransition, // when: this.layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc, }; switch (this.layoutDoc.presEffect) { case "Zoom": return (<Zoom {...effectProps}>{node}</Zoom>); break; - case "Fade": return (<Fade {...effectProps}>{node}</Fade>); break; - case "Flip": return (<Flip {...effectProps}>{node}</Flip>); break; - case "Rotate": return (<Rotate {...effectProps}>{node}</Rotate>); break; - case "Bounce": return (<Bounce {...effectProps}>{node}</Bounce>); break; - case "Roll": return (<Roll {...effectProps}>{node}</Roll>); break; + case PresEffect.Fade: return (<Fade {...effectProps}>{node}</Fade>); break; + case PresEffect.Flip: return (<Flip {...effectProps}>{node}</Flip>); break; + case PresEffect.Rotate: return (<Rotate {...effectProps}>{node}</Rotate>); break; + case PresEffect.Bounce: return (<Bounce {...effectProps}>{node}</Bounce>); break; + case PresEffect.Roll: return (<Roll {...effectProps}>{node}</Roll>); break; case "LightSpeed": return (<LightSpeed {...effectProps}>{node}</LightSpeed>); break; - case "None": return node; break; + case PresEffect.None: return node; break; default: return node; break; } } else { diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index fcc9e50f5..4fb350b55 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -69,11 +69,11 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument SetActiveInkWidth(e.target.value); SelectionManager.SelectedDocuments().filter(i => StrCast(i.rootDoc.type) === DocumentType.INK).map(i => i.rootDoc.strokeWidth = Number(e.target.value)); }} /> - <div> {ActiveInkBezierApprox() ?? 2}</div> + {/* <div> {ActiveInkBezierApprox() ?? 2}</div> <input type="range" defaultValue={ActiveInkBezierApprox() ?? 2} min={0} max={300} onChange={(e: React.ChangeEvent<HTMLInputElement>) => { SetActiveBezierApprox(e.target.value); SelectionManager.SelectedDocuments().filter(i => StrCast(i.rootDoc.type) === DocumentType.INK).map(i => i.rootDoc.strokeBezier = e.target.value); - }} /> + }} /> */} <br /> <br /> </div> diff --git a/src/client/views/nodes/ComparisonBox.scss b/src/client/views/nodes/ComparisonBox.scss index acf6b1636..44cf5d046 100644 --- a/src/client/views/nodes/ComparisonBox.scss +++ b/src/client/views/nodes/ComparisonBox.scss @@ -13,6 +13,7 @@ left: 0; height: 100%; overflow: hidden; + transition: 200ms; .beforeBox-cont { height: 100%; @@ -26,6 +27,7 @@ width: 3px; display: inline-block; background: white; + transition: 200ms; .slide-handle { position: absolute; diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 2af5b3182..0ba53dee6 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -51,9 +51,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C private registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => { setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, action(() => { // on click, animate slider movement to the targetWidth - this._animating = "all 1s"; + this._animating = "all 200ms"; this.layoutDoc._clipWidth = targetWidth * 100 / this.props.PanelWidth(); - setTimeout(action(() => this._animating = ""), 1000); + setTimeout(action(() => this._animating = ""), 200); }), false); } @@ -102,12 +102,12 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C return ( <div className={`comparisonBox${this.active() || SnappingManager.GetIsDragging() ? "-interactive" : ""}` /* change className to easily disable/enable pointer events in CSS */}> - {displayBox("after", 1, this.props.PanelWidth() - 5)} + {displayBox("after", 1, this.props.PanelWidth() - 3)} <div className="clip-div" style={{ width: clipWidth, transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, "gray") }}> - {displayBox("before", 0, 5)} + {displayBox("before", 0, 0)} </div> - <div className="slide-bar" style={{ left: `calc(${clipWidth} - 0.5px)` }} + <div className="slide-bar" style={{ left: `calc(${clipWidth} - 0.5px)`, cursor: NumCast(this.layoutDoc._clipWidth) < 5 ? "e-resize" : NumCast(this.layoutDoc._clipWidth) / 100 > (this.props.PanelWidth() - 5) / this.props.PanelWidth() ? "w-resize" : undefined }} onPointerDown={e => this.registerSliding(e, this.props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */ > <div className="slide-handle" /> </div> diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index 09051da78..0c52b9044 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -22,12 +22,8 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp } @computed get freezeDimensions() { return this.props.FreezeDimensions; } - nativeWidth = () => returnVal(this.props.NativeWidth?.(), - NumCast(this.layoutDoc?._nativeWidth || this.props.DataDoc?.[Doc.LayoutFieldKey(this.layoutDoc) + "-nativeWidth"], - (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[WidthSym]() : this.props.PanelWidth()))) - nativeHeight = () => returnVal(this.props.NativeHeight?.(), - NumCast(this.layoutDoc?._nativeHeight || this.props.DataDoc?.[Doc.LayoutFieldKey(this.layoutDoc) + "-nativeHeight"], - (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[HeightSym]() : this.props.PanelHeight()))) + nativeWidth = () => returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.freezeDimensions) || this.props.PanelWidth()); + nativeHeight = () => returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.freezeDimensions) || this.props.PanelHeight()); @computed get scaling() { const wscale = this.props.PanelWidth() / this.nativeWidth(); const hscale = this.props.PanelHeight() / this.nativeHeight(); @@ -42,7 +38,13 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp private PanelHeight = () => this.panelHeight; @computed get panelWidth() { return this.nativeWidth() && !this.props.Document._fitWidth ? this.nativeWidth() * this.contentScaling() : this.props.PanelWidth(); } - @computed get panelHeight() { return this.nativeHeight() && !this.props.Document._fitWidth ? this.nativeHeight() * this.contentScaling() : this.props.PanelHeight(); } + @computed get panelHeight() { + if (this.nativeHeight()) { + if (!this.props.Document._fitWidth) return this.nativeHeight() * this.contentScaling(); + else return this.panelWidth / Doc.NativeAspect(this.layoutDoc, this.props.DataDoc, this.freezeDimensions) || 1; + } + return this.props.PanelHeight(); + } @computed get childXf() { return this.props.DataDoc ? 1 : 1 / this.contentScaling(); } // this is intended to detect when a document is being rendered inside itself as part of a template, but not as a leaf node where nativeWidth & height would apply. private getTransform = () => this.props.dontCenter ? diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss index 9328fb96b..735aa669f 100644 --- a/src/client/views/nodes/DocumentLinksButton.scss +++ b/src/client/views/nodes/DocumentLinksButton.scss @@ -1,6 +1,11 @@ @import "../globalCssVariables.scss"; +.documentLinksButton-cont { + min-width: 20; + min-height: 20; + position: absolute; +} .documentLinksButton, .documentLinksButton-endLink, .documentLinksButton-startLink { @@ -29,6 +34,7 @@ .documentLinksButton { background-color: black; + font-weight: bold; &:hover { background: $main-accent; diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index ddfb3cc34..95c007175 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -3,7 +3,6 @@ import { Tooltip } from "@material-ui/core"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, Opt } from "../../../fields/Doc"; -import { DocumentType } from "../../documents/DocumentTypes"; import { emptyFunction, setupMoveUpEvents, returnFalse, Utils, emptyPath } from "../../../Utils"; import { TraceMobx } from "../../../fields/util"; import { DocUtils, Docs } from "../../documents/Documents"; @@ -39,6 +38,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp @observable public static StartLinkView: DocumentView | undefined; @observable public static AnnotationId: string | undefined; @observable public static AnnotationUri: string | undefined; + @observable public static EditLink: DocumentView | undefined; @observable public static invisibleWebDoc: Opt<Doc>; public static invisibleWebRef = React.createRef<HTMLDivElement>(); @@ -209,94 +209,75 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp } })); - @observable - public static EditLink: DocumentView | undefined; @action clearLinks() { DocumentLinksButton.StartLink = undefined; DocumentLinksButton.StartLinkView = undefined; } - @computed - get linkButton() { - TraceMobx(); - const links = DocUtils.FilterDocs(Array.from(new Set<Doc>(this.props.links)), this.props.View.props.docFilters(), []); - - const menuTitle = this.props.StartLink ? "Drag or tap to start link" : "Tap to complete link"; - const buttonTitle = "Tap to view links"; - const title = this.props.InMenu ? menuTitle : buttonTitle; - - - const startLink = <img - style={{ width: "11px", height: "11px" }} - id={"startLink-icon"} - src={`/assets/${"startLink.png"}`} />; - - const endLink = <img - style={{ width: "14px", height: "9px" }} - id={"endLink-icon"} - src={`/assets/${"endLink.png"}`} />; + @computed get filteredLinks() { + return DocUtils.FilterDocs(Array.from(new Set<Doc>(this.props.links)), this.props.View.props.docFilters(), []); + } - const link = <img - style={{ width: "22px", height: "16px" }} - id={"link-icon"} - src={`/assets/${"link.png"}`} />; + @computed get linkButtonInner() { + const btnDim = this.props.InMenu ? "20px" : "30px"; + const link = <img style={{ width: "22px", height: "16px" }} src={`/assets/${"link.png"}`} />; - const linkButton = <div className="documentLinksButton-cont" ref={this._linkButton} style={{ - minWidth: 20, minHeight: 20, position: "absolute", - left: this.props.Offset?.[0], top: this.props.Offset?.[1], right: this.props.Offset?.[2], bottom: this.props.Offset?.[3] - }}> - <div className={"documentLinksButton"} style={{ - backgroundColor: this.props.InMenu ? "" : "#add8e6", - color: this.props.InMenu ? "white" : "black", - width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px", fontWeight: "bold" - }} + return <div className="documentLinksButton-cont" ref={this._linkButton} + style={{ left: this.props.Offset?.[0], top: this.props.Offset?.[1], right: this.props.Offset?.[2], bottom: this.props.Offset?.[3] }} + > + <div className={"documentLinksButton"} onPointerDown={this.onLinkButtonDown} onClick={this.onLinkClick} - // onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)} - // onPointerEnter={action(e => links.length && (LinkDocPreview.LinkInfo = { - // addDocTab: this.props.View.props.addDocTab, - // linkSrc: this.props.View.props.Document, - // linkDoc: links[0], - // Location: [e.clientX, e.clientY + 20] - // }))} - > - - {/* {this.props.InMenu ? this.props.StartLink ? <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> : - <FontAwesomeIcon className="documentdecorations-icon" icon="hand-paper" size="sm" /> : links.length} */} - - {this.props.InMenu ? this.props.StartLink ? <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> : - link : Array.from(links).length} - + style={{ + backgroundColor: this.props.InMenu ? "" : "#add8e6", + color: this.props.InMenu ? "white" : "black", + width: btnDim, + height: btnDim, + }} > + {this.props.InMenu ? + this.props.StartLink ? + <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> + : link + : Array.from(this.filteredLinks).length} </div> {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? <div className={"documentLinksButton-endLink"} style={{ - width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px", + width: btnDim, height: btnDim, backgroundColor: DocumentLinksButton.StartLink ? "" : "grey", opacity: DocumentLinksButton.StartLink ? "" : "50%", border: DocumentLinksButton.StartLink ? "" : "none", cursor: DocumentLinksButton.StartLink ? "pointer" : "default" }} - onPointerDown={DocumentLinksButton.StartLink ? this.completeLink : emptyFunction} - onClick={e => DocumentLinksButton.StartLink ? DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View) : emptyFunction} /> : (null) + onPointerDown={DocumentLinksButton.StartLink && this.completeLink} + onClick={e => DocumentLinksButton.StartLink && DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View)} /> + : (null) } - { - DocumentLinksButton.StartLink === this.props.View.props.Document && this.props.InMenu && this.props.StartLink ? <div className={"documentLinksButton-startLink"} - style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }} - onPointerDown={this.clearLinks} onClick={this.clearLinks} - /> : (null) + {DocumentLinksButton.StartLink === this.props.View.props.Document && this.props.InMenu && this.props.StartLink ? + <div className={"documentLinksButton-startLink"} onPointerDown={this.clearLinks} onClick={this.clearLinks} style={{ width: btnDim, height: btnDim }} /> + : (null) } </div >; + } + + @computed get linkButton() { + TraceMobx(); + + const menuTitle = this.props.StartLink ? "Drag or tap to start link" : "Tap to complete link"; + const buttonTitle = "Tap to view links"; + const title = this.props.InMenu ? menuTitle : buttonTitle; - return (!Array.from(links).length) && !this.props.AlwaysOn ? (null) : + return !Array.from(this.filteredLinks).length && !this.props.AlwaysOn ? (null) : this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink) ? <Tooltip title={<><div className="dash-tooltip">{title}</div></>}> - {linkButton} - </Tooltip> : !!!DocumentLinksButton.EditLink && !this.props.InMenu ? + {this.linkButtonInner} + </Tooltip> + : + !DocumentLinksButton.EditLink && !this.props.InMenu ? <Tooltip title={<><div className="dash-tooltip">{title}</div></>}> - {linkButton} - </Tooltip> : - linkButton; + {this.linkButtonInner} + </Tooltip> + : this.linkButtonInner; } render() { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e980322d5..77f63b457 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -42,7 +42,8 @@ import { RadialMenu } from './RadialMenu'; import { TaskCompletionBox } from './TaskCompletedBox'; import React = require("react"); -export type DocFocusFunc = () => boolean; +export type DocAfterFocusFunc = (notFocused: boolean) => boolean; +export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => void; export interface DocumentViewProps { ContainingCollectionView: Opt<CollectionView>; @@ -82,7 +83,7 @@ export interface DocumentViewProps { PanelHeight: () => number; pointerEvents?: string; contentsPointerEvents?: string; - focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: DocFocusFunc) => void; + focus: DocFocusFunc; parentActive: (outsideReaction: boolean) => boolean; whenActiveChanged: (isActive: boolean) => void; bringToFront: (doc: Doc, sendToBack?: boolean) => void; @@ -129,8 +130,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } @computed get topMost() { return this.props.renderDepth === 0; } @computed get freezeDimensions() { return this.props.FreezeDimensions; } - @computed get nativeWidth() { return returnVal(this.props.NativeWidth?.(), NumCast(this.layoutDoc[(this.props.DataDoc ? this.LayoutFieldKey + "-" : "_") + "nativeWidth"], (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0))); } - @computed get nativeHeight() { return returnVal(this.props.NativeHeight?.(), NumCast(this.layoutDoc[(this.props.DataDoc ? this.LayoutFieldKey + "-" : "_") + "nativeHeight"], (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0))); } + @computed get nativeWidth() { return returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.dataDoc, this.freezeDimensions)); } + @computed get nativeHeight() { return returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.dataDoc, this.freezeDimensions)); } @computed get onClickHandler() { return this.props.onClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); } @computed get onDoubleClickHandler() { return this.props.onDoubleClick?.() ?? (Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick); } @computed get onPointerDownHandler() { return this.props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown); } @@ -325,17 +326,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu thisContainer: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey }, console.log); - func(); + undoBatch(func)(); } else if (!Doc.IsSystem(this.props.Document)) { if (this.props.Document.type === DocumentType.INK) { InkStrokeProperties.Instance && (InkStrokeProperties.Instance._controlBtn = true); } else { UndoManager.RunInBatch(() => { - let fullScreenDoc = this.props.Document; - if (StrCast(this.props.Document.layoutKey) !== "layout_fullScreen" && this.props.Document.layout_fullScreen) { - fullScreenDoc = Doc.MakeAlias(this.props.Document); - fullScreenDoc.layoutKey = "layout_fullScreen"; - } + const fullScreenDoc = Cast(this.props.Document._fullScreenView, Doc, null) || this.props.Document; this.props.addDocTab(fullScreenDoc, "add"); }, "double tap"); SelectionManager.DeselectAll(); @@ -385,7 +382,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu // if the target isn't onscreen, then it will open up the target in a tab, on the right, or in place // depending on the followLinkLocation property of the source (or the link itself as a fallback); public static followLinkClick = async (linkDoc: Opt<Doc>, sourceDoc: Doc, docView: { - focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: DocFocusFunc) => void, + focus: DocFocusFunc, addDocTab: (doc: Doc, where: string, libraryPath?: Doc[]) => boolean, ContainingCollectionDoc?: Doc }, shiftKey: boolean, altKey: boolean) => { @@ -423,7 +420,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this._downX = touch.clientX; this._downY = touch.clientY; if (!e.nativeEvent.cancelBubble) { - if ((this.active || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc.lockedPosition && !this.layoutDoc.inOverlay) e.stopPropagation(); + if ((this.active || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc.lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) e.stopPropagation(); this.removeMoveListeners(); this.addMoveListeners(); this.removeEndListeners(); @@ -438,7 +435,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if (e.cancelBubble && this.active) { this.removeMoveListeners(); } - else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc.lockedPosition && !this.layoutDoc.inOverlay) { + else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc.lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { const touch = me.touchEvent.changedTouches.item(0); if (touch && (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3)) { @@ -494,8 +491,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { const doc = Document(this.props.Document); const layoutDoc = Document(Doc.Layout(this.props.Document)); - let nwidth = layoutDoc._nativeWidth || 0; - let nheight = layoutDoc._nativeHeight || 0; + let nwidth = Doc.NativeWidth(layoutDoc); + let nheight = Doc.NativeHeight(layoutDoc); const width = (layoutDoc._width || 0); const height = (layoutDoc._height || (nheight / nwidth * width)); const scale = this.props.ScreenToLocalTransform().Scale * this.props.ContentScaling(); @@ -505,13 +502,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu doc.y = (doc.y || 0) + dY * (actualdH - height); const fixedAspect = e.ctrlKey || (nwidth && nheight); if (fixedAspect && (!nwidth || !nheight)) { - layoutDoc._nativeWidth = nwidth = layoutDoc._width || 0; - layoutDoc._nativeHeight = nheight = layoutDoc._height || 0; + Doc.SetNativeWidth(layoutDoc, nwidth = layoutDoc._width || 0); + Doc.SetNativeHeight(layoutDoc, nheight = layoutDoc._height || 0); } if (nwidth > 0 && nheight > 0) { if (Math.abs(dW) > Math.abs(dH)) { if (!fixedAspect) { - layoutDoc._nativeWidth = actualdW / (layoutDoc._width || 1) * (layoutDoc._nativeWidth || 0); + Doc.SetNativeWidth(layoutDoc, actualdW / (layoutDoc._width || 1) * Doc.NativeWidth(layoutDoc)); } layoutDoc._width = actualdW; if (fixedAspect && !layoutDoc._fitWidth) layoutDoc._height = nheight / nwidth * layoutDoc._width; @@ -519,7 +516,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } else { if (!fixedAspect) { - layoutDoc._nativeHeight = actualdH / (layoutDoc._height || 1) * (doc._nativeHeight || 0); + Doc.SetNativeHeight(layoutDoc, actualdH / (layoutDoc._height || 1) * Doc.NativeHeight(doc)); } layoutDoc._height = actualdH; if (fixedAspect && !layoutDoc._fitWidth) layoutDoc._width = nwidth / nheight * layoutDoc._height; @@ -554,7 +551,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if ((this.active || this.layoutDoc.onDragStart) && !e.ctrlKey && (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) && - !this.layoutDoc.inOverlay) { + !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { e.stopPropagation(); if (SelectionManager.IsSelected(this, true) && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it } @@ -573,7 +570,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if (e.cancelBubble && this.active) { document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView) } - else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.layoutDoc.onDragStart) && !this.layoutDoc.lockedPosition && !this.layoutDoc.inOverlay) { + else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.layoutDoc.onDragStart) && !this.layoutDoc.lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) { document.removeEventListener("pointermove", this.onPointerMove); @@ -754,8 +751,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this.props.bringToFront(this.props.Document, true); const wid = this.Document[WidthSym](); // change the nativewidth and height if the background is to be a collection that aggregates stuff that is added to it. const hgt = this.Document[HeightSym](); - this.props.Document[DataSym][this.LayoutFieldKey + "-nativeWidth"] = wid; - this.props.Document[DataSym][this.LayoutFieldKey + "-nativeHeight"] = hgt; + Doc.SetNativeWidth(this.props.Document[DataSym], wid); + Doc.SetNativeHeight(this.props.Document[DataSym], hgt); } } @@ -839,8 +836,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu onClicks.push({ description: "Select on Click", event: () => this.selectOnClick(), icon: "link" }); onClicks.push({ description: "Follow Link on Click", event: () => this.followLinkOnClick(undefined, false), icon: "link" }); onClicks.push({ description: "Toggle Link Target on Click", event: () => this.toggleTargetOnClick(), icon: "map-pin" }); + !existingOnClick && cm.addItem({ description: "OnClick...", addDivider: true, subitems: onClicks, icon: "mouse-pointer" }); } - !existingOnClick && cm.addItem({ description: "OnClick...", addDivider: true, subitems: onClicks, icon: "mouse-pointer" }); } const funcs: ContextMenuProps[] = []; @@ -889,7 +886,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu e?.stopPropagation(); // DocumentViews should stop propagation of this event } cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15); - !this.isSelected(true) && SelectionManager.SelectDoc(this, false); + !this.isSelected(true) && setTimeout(() => SelectionManager.SelectDoc(this, false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear. }); } @@ -907,7 +904,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const excluded = ["PresBox", /* "FormattedTextBox", */ "FontIconBox"]; // bcz: shifting the title for texst causes problems with collaborative use when some people see titles, and others don't return !excluded.includes(StrCast(this.layoutDoc.layout)); } - chromeHeight = () => this.showOverlappingTitle ? 1 : 25; + chromeHeight = () => this.showOverlappingTitle ? 0 : 25; @computed get finalLayoutKey() { if (typeof this.props.layoutKey === "string") { @@ -920,7 +917,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu return this.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false; } childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling()); - @computed.struct get linkOffset() { return this.topMost ? [0, undefined, undefined, 10] : [-15, undefined, undefined, undefined]; } + @computed.struct get linkOffset() { return this.topMost ? [0, undefined, undefined, 10] : [-15, undefined, undefined, -20]; } @computed get contents() { TraceMobx(); return (<div className="documentView-contentsView" style={{ pointerEvents: this.props.contentsPointerEvents as any, borderRadius: "inherit", width: "100%", height: "100%" }}> @@ -990,7 +987,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu anchorPanelHeight = () => this.props.PanelHeight() || 1; @computed get directLinks() { TraceMobx(); return LinkManager.Instance.getAllDirectLinks(this.rootDoc); } - @computed get allLinks() { TraceMobx(); return DocListCast(this.Document.links); } + @computed get allLinks() { TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc); } @computed get allAnchors() { TraceMobx(); if (this.props.LayoutTemplateString?.includes("LinkAnchorBox")) return null; @@ -1144,19 +1141,20 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu style={{ transformOrigin: this._animateScalingTo ? "center center" : undefined, transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined, - transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : this._animateScalingTo < 1 ? "transform 0.5s ease-in" : "transform 0.5s ease-out", + transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform 0.5s ease-${this._animateScalingTo < 1 ? "in" : "out"}`, pointerEvents: this.ignorePointerEvents ? "none" : undefined, color: StrCast(this.layoutDoc.color, "inherit"), outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px", - border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined, - boxShadow: this.Document.isLinkButton && !this.props.dontRegisterView && this.props.forcedBackgroundColor?.(this.Document) !== "transparent" ? - StrCast(this.props.Document._linkButtonShadow, "lightblue 0em 0em 1em") : - this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : - undefined, + border: highlighting && borderRounding && highlightStyles[fullDegree] === "dashed" ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined, + boxShadow: highlighting && borderRounding && highlightStyles[fullDegree] !== "dashed" ? `0 0 0 ${localScale}px ${highlightColors[fullDegree]}` : + this.Document.isLinkButton && !this.props.dontRegisterView && this.props.forcedBackgroundColor?.(this.Document) !== "transparent" ? + StrCast(this.layoutDoc._linkButtonShadow, "lightblue 0em 0em 1em") : + this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : + undefined, background: finalColor, opacity: finalOpacity, fontFamily: StrCast(this.Document._fontFamily, "inherit"), - fontSize: Cast(this.Document._fontSize, "string", null), + fontSize: !this.props.treeViewDoc ? Cast(this.Document._fontSize, "string", null) : undefined, }}> {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <> {this.innards} diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 3a5b27b21..79947c023 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -11,6 +11,7 @@ import { CollectionView } from "../collections/CollectionView"; import { AudioBox } from "./AudioBox"; import { VideoBox } from "./VideoBox"; import { dropActionType } from "../../util/DragManager"; +import { DocAfterFocusFunc, DocFocusFunc } from "./DocumentView"; // // these properties get assigned through the render() method of the DocumentView when it creates this node. @@ -47,7 +48,7 @@ export interface FieldViewProps { whenActiveChanged: (isActive: boolean) => void; LayoutTemplateString?: string; dontRegisterView?: boolean; - focus: (doc: Doc) => void; + focus: DocFocusFunc; presMultiSelect?: (doc: Doc) => void; //added for selecting multiple documents in a presentation ignoreAutoHeight?: boolean; PanelWidth: () => number; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index bc38a17eb..88dc3b241 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -76,14 +76,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD componentDidMount() { this._pathDisposer = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }), - ({ nativeSize, width }) => { - if (this.props.NativeWidth?.() !== 0 || !this.layoutDoc._height) { - this.layoutDoc._nativeWidth = nativeSize.nativeWidth; - this.layoutDoc._nativeHeight = nativeSize.nativeHeight; - this.layoutDoc._nativeOrientation = nativeSize.nativeOrientation; + action(({ nativeSize, width }) => { + if (!this.layoutDoc._height) { this.layoutDoc._height = width * nativeSize.nativeHeight / nativeSize.nativeWidth; } - }, + }), { fireImmediately: true }); } @@ -106,8 +103,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD const targetDoc = layoutDoc[DataSym]; if (targetDoc[targetField] instanceof ImageField) { this.dataDoc[this.fieldKey] = ObjectField.MakeCopy(targetDoc[targetField] as ImageField); - this.dataDoc[this.fieldKey + "-nativeWidth"] = NumCast(targetDoc[targetField + "-nativeWidth"]); - this.dataDoc[this.fieldKey + "-nativeHeight"] = NumCast(targetDoc[targetField + "-nativeHeight"]); + Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(targetDoc)); + Doc.SetNativeWidth(this.dataDoc, Doc.NativeHeight(targetDoc)); e.stopPropagation(); } } diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 867be9735..d47942bd9 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -77,19 +77,16 @@ export class LinkDocPreview extends React.Component<Props> { this.props.addDocTab(Docs.Create.WebDocument(this.props.href, { _fitWidth: true, title: this.props.href, _width: 200, _height: 400, useCors: true }), "add:right"); } } - width = () => Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225)) - 16; - height = () => Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225)) - 16; + width = () => Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225)); + height = () => Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225)); @computed get targetDocView() { return !this._targetDoc ? - <div style={{ - pointerEvents: "all", maxWidth: 225, maxHeight: 225, width: "100%", height: "100%", - overflow: "hidden" - }}> + <div style={{ pointerEvents: "all", maxWidth: 225, maxHeight: 225, width: "100%", height: "100%", overflow: "hidden" }}> <div style={{ width: "100%", height: "100%", textOverflow: "ellipsis", }} onPointerDown={this.pointerDown}> {this._toolTipText} </div> - </div> : - + </div> + : <ContentFittingDocumentView Document={this._targetDoc} LibraryPath={emptyPath} @@ -109,8 +106,8 @@ export class LinkDocPreview extends React.Component<Props> { ContainingCollectionDoc={undefined} ContainingCollectionView={undefined} renderDepth={-1} - PanelWidth={this.width} //Math.min(350, NumCast(target._width, 350))} - PanelHeight={this.height} //Math.min(250, NumCast(target._height, 250))} + PanelWidth={this.width} + PanelHeight={this.height} focus={emptyFunction} whenActiveChanged={returnFalse} bringToFront={returnFalse} diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index e2bdd98cc..16cc9d27e 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -208,6 +208,7 @@ pointer-events: none; .pdfViewerDash-text { .textLayer { + display: none; span { user-select: none; } @@ -228,6 +229,7 @@ z-index: -1; .pdfViewerDash-text { .textLayer { + display: contents; span { user-select: text; } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 79e00eed7..42b24f6f6 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -50,8 +50,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum constructor(props: any) { super(props); this._initialScale = this.props.ScreenToLocalTransform().Scale; - const nw = this.Document._nativeWidth = NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"], NumCast(this.Document._nativeWidth, 927)); - const nh = this.Document._nativeHeight = NumCast(this.dataDoc[this.props.fieldKey + "-nativeHeight"], NumCast(this.Document._nativeHeight, 1200)); + const nw = Doc.NativeWidth(this.Document, this.dataDoc) || 927; + const nh = Doc.NativeHeight(this.Document, this.dataDoc) || 1200; !this.Document._fitWidth && (this.Document._height = this.Document[WidthSym]() * (nh / nw)); const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); if (pdfUrl) { @@ -97,8 +97,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum loaded = (nw: number, nh: number, np: number) => { this.dataDoc[this.props.fieldKey + "-numPages"] = np; - this.dataDoc[this.props.fieldKey + "-nativeWidth"] = this.Document._nativeWidth = Math.max(NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"]), nw * 96 / 72); - this.dataDoc[this.props.fieldKey + "-nativeHeight"] = this.Document._nativeHeight = nh * 96 / 72; + Doc.SetNativeWidth(this.dataDoc, Math.max(Doc.NativeWidth(this.dataDoc), nw * 96 / 72)); + Doc.SetNativeHeight(this.dataDoc, nh * 96 / 72); + this.layoutDoc._height = this.layoutDoc[WidthSym]() / (Doc.NativeAspect(this.dataDoc) || 1); !this.Document._fitWidth && (this.Document._height = this.Document[WidthSym]() * (nh / nw)); } @@ -254,7 +255,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); return <div className={"pdfBox"} onContextMenu={this.specificContextMenu} style={{ height: this.props.Document._scrollTop && !this.Document._fitWidth && (window.screen.width > 600) ? NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined }}> <div className="pdfBox-background"></div> - <PDFViewer {...this.props} pdf={this._pdf!} url={pdfUrl!.url.pathname} active={this.props.active} loaded={this.loaded} + <PDFViewer {...this.props} pdf={this._pdf!} url={pdfUrl!.url.pathname} active={this.props.active} loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined} setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView} renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth} addDocTab={this.props.addDocTab} focus={this.props.focus} searchFilterDocs={this.props.searchFilterDocs} diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss index 9a8b861ba..de2aee8fa 100644 --- a/src/client/views/nodes/PresBox.scss +++ b/src/client/views/nodes/PresBox.scss @@ -56,11 +56,14 @@ $light-background: #ececec; letter-spacing: 0; display: flex; align-items: center; + justify-content: center; transition: 0.5s; } .toolbar-button.active { color: $light-blue; + background-color: white; + border-radius: 100%; } .toolbar-transitionButtons { @@ -128,7 +131,6 @@ $light-background: #ececec; position: relative; display: inline; font-family: Roboto; - color: black; z-index: 100; transition: 0.7s; @@ -509,6 +511,7 @@ $light-background: #ececec; background-color: $light-background; border: solid 1px rgba(0, 0, 0, 0.5); display: flex; + color: black; margin-top: 5px; margin-bottom: 5px; border-radius: 5px; @@ -569,6 +572,7 @@ $light-background: #ececec; border-radius: 5px; font-size: 10; height: 25; + color: black; padding-left: 5px; align-items: center; margin-top: 5px; @@ -997,6 +1001,7 @@ $light-background: #ececec; transition: all 0.2s; .presPanel-button-text { + cursor: pointer; display: flex; height: 20; width: max-content; @@ -1049,6 +1054,7 @@ $light-background: #ececec; .presPanel-button:hover { background-color: #5a5a5a; + transform: scale(1.2); } .presPanel-button-text:hover { diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 8e3679452..683cb938a 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -1,7 +1,7 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from "@material-ui/core"; -import { action, computed, observable, runInAction } from "mobx"; +import { action, computed, observable, runInAction, ObservableMap, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; import { ColorState, SketchPicker } from "react-color"; import { Doc, DocCastAsync, DocListCast, DocListCastAsync } from "../../../fields/Doc"; @@ -11,7 +11,7 @@ import { List } from "../../../fields/List"; import { PrefetchProxy } from "../../../fields/Proxy"; import { listSpec, makeInterface } from "../../../fields/Schema"; import { ScriptField } from "../../../fields/ScriptField"; -import { Cast, NumCast, StrCast } from "../../../fields/Types"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types"; import { returnFalse, returnOne, returnZero } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DocumentType } from "../../documents/DocumentTypes"; @@ -19,7 +19,7 @@ import { CurrentUserUtils } from "../../util/CurrentUserUtils"; import { DocumentManager } from "../../util/DocumentManager"; import { Scripting } from "../../util/Scripting"; import { SelectionManager } from "../../util/SelectionManager"; -import { undoBatch } from "../../util/UndoManager"; +import { undoBatch, UndoManager } from "../../util/UndoManager"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionView, CollectionViewType } from "../collections/CollectionView"; import { TabDocView } from "../collections/TabDocView"; @@ -28,7 +28,7 @@ import { AudioBox } from "./AudioBox"; import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import "./PresBox.scss"; -import { VideoBox } from "./VideoBox"; +import Color = require("color"); export enum PresMovement { Zoom = "zoom", @@ -38,12 +38,30 @@ export enum PresMovement { } export enum PresEffect { - Fade = "fade", - Flip = "flip", - Rotate = "rotate", - Bounce = "bounce", - Roll = "roll", - None = "none", + Fade = "Fade in", + Flip = "Flip", + Rotate = "Rotate", + Bounce = "Bounce", + Roll = "Roll", + None = "None", + Left = "left", + Right = "right", + Center = "center", + Top = "top", + Bottom = "bottom" +} + +enum PresStatus { + Autoplay = "auto", + Manual = "manual", + Edit = "edit" +} + +enum PresColor { + LightBlue = "#AEDDF8", + DarkBlue = "#5B9FDD", + LightBackground = "#ececec", + SlideBackground = "#d5dce2", } type PresBoxSchema = makeInterface<[typeof documentSchema]>; @@ -53,35 +71,53 @@ const PresBoxDocument = makeInterface(documentSchema); export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>(PresBoxDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } - public static Instance: PresBox; + @observable public static Instance: PresBox; @observable _isChildActive = false; @observable _moveOnFromAudio: boolean = true; @observable _presTimer!: NodeJS.Timeout; + @observable _presKeyEventsActive: boolean = false; - @observable _selectedArray: Doc[] = []; - @observable _sortedSelectedArray: Doc[] = []; + @observable _selectedArray: ObservableMap = new ObservableMap<Doc, any>(); @observable _eleArray: HTMLElement[] = []; @observable _dragArray: HTMLElement[] = []; + @observable _pathBoolean: boolean = false; + @observable _expandBoolean: boolean = false; + private _disposers: { [name: string]: IReactionDisposer } = {}; @observable private transitionTools: boolean = false; @observable private newDocumentTools: boolean = false; @observable private progressivizeTools: boolean = false; - @observable private presentTools: boolean = false; - @observable private pathBoolean: boolean = false; @observable private openMovementDropdown: boolean = false; @observable private openEffectDropdown: boolean = false; + @observable private presentTools: boolean = false; @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); } + @computed get tagDocs() { + const tagDocs: Doc[] = []; + for (const doc of this.childDocs) { + const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); + tagDocs.push(tagDoc); + } + return tagDocs; + } @computed get itemIndex() { return NumCast(this.rootDoc._itemIndex); } - @computed get activeItem() { return Cast(this.childDocs[this.itemIndex], Doc, null); } + @computed get activeItem() { return Cast(this.childDocs[NumCast(this.rootDoc._itemIndex)], Doc, null); } @computed get targetDoc() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); } + @computed get scrollable(): boolean { + if (this.targetDoc.type === DocumentType.PDF || this.targetDoc.type === DocumentType.WEB || this.targetDoc.type === DocumentType.RTF || this.targetDoc._viewType === CollectionViewType.Stacking) return true; + else return false; + } + @computed get panable(): boolean { + if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._viewType === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true; + else return false; + } @computed get presElement() { return Cast(Doc.UserDoc().presElement, Doc, null); } constructor(props: any) { super(props); - if (Doc.UserDoc().activePresentation = this.rootDoc) PresBox.Instance = this; + if (Doc.UserDoc().activePresentation = this.rootDoc) runInAction(() => PresBox.Instance = this); if (!this.presElement) { // create exactly one presElmentBox template to use by any and all presentations. Doc.UserDoc().presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({ - title: "pres element template", backgroundColor: "transparent", _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data" + title: "pres element template", type: DocumentType.PRESELEMENT, backgroundColor: "transparent", _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data" })); // this script will be called by each presElement to get rendering-specific info that the PresBox knows about but which isn't written to the PresElement // this is a design choice -- we could write this data to the presElements which would require a reaction to keep it up to date, and it would prevent @@ -94,139 +130,145 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } @computed get selectedDocumentView() { if (SelectionManager.SelectedDocuments().length) return SelectionManager.SelectedDocuments()[0]; - if (PresBox.Instance && PresBox.Instance._selectedArray) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); - return undefined; + if (this._selectedArray.size) return DocumentManager.Instance.getDocumentView(this.rootDoc); } @computed get isPres(): boolean { - document.removeEventListener("keydown", this.keyEvents, true); + document.removeEventListener("keydown", PresBox.keyEventsWrapper, true); if (this.selectedDoc?.type === DocumentType.PRES) { - document.addEventListener("keydown", this.keyEvents, true); + document.removeEventListener("keydown", PresBox.keyEventsWrapper, true); + document.addEventListener("keydown", PresBox.keyEventsWrapper, true); return true; } return false; } @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } + @action componentWillUnmount() { - document.removeEventListener("keydown", this.keyEvents, true); + document.removeEventListener("keydown", PresBox.keyEventsWrapper, true); + this._presKeyEventsActive = false; + this.resetPresentation(); // Turn of progressivize editors this.turnOffEdit(true); + Object.values(this._disposers).forEach(disposer => disposer?.()); } - componentDidMount = async () => { + @action + componentDidMount() { this.rootDoc.presBox = this.rootDoc; this.rootDoc._forceRenderEngine = "timeline"; this.rootDoc._replacedChrome = "replaced"; - this.layoutDoc.presStatus = "edit"; + this.layoutDoc.presStatus = PresStatus.Edit; this.layoutDoc._gridGap = 0; this.layoutDoc._yMargin = 0; this.turnOffEdit(true); DocListCastAsync((Doc.UserDoc().myPresentations as Doc).data).then(pres => !pres?.includes(this.rootDoc) && Doc.AddDocToList(Doc.UserDoc().myPresentations as Doc, "data", this.rootDoc)); + this._disposers.selection = reaction(() => SelectionManager.SelectedDocuments(), + views => views.some(view => view.props.Document === this.rootDoc) && this.updateCurrentPresentation()); } - updateCurrentPresentation = () => { - Doc.UserDoc().activePresentation = this.rootDoc; + @action + updateCurrentPresentation = (pres?: Doc) => { + if (pres) Doc.UserDoc().activePresentation = pres; + else Doc.UserDoc().activePresentation = this.rootDoc; + document.removeEventListener("keydown", PresBox.keyEventsWrapper, true); + document.addEventListener("keydown", PresBox.keyEventsWrapper, true); + this._presKeyEventsActive = true; PresBox.Instance = this; } - /** - * Called when the user moves to the next slide in the presentation trail. - */ - @undoBatch + // There are still other internal frames and should go through all frames before going to next slide + nextInternalFrame = (targetDoc: Doc, activeItem: Doc) => { + const currentFrame = Cast(targetDoc?._currentFrame, "number", null); + const childDocs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); + targetDoc._viewTransition = "all 1s"; + setTimeout(() => targetDoc._viewTransition = undefined, 1010); + this.nextKeyframe(targetDoc, activeItem); + if (activeItem.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, targetDoc); + else targetDoc.editing = true; + } + + // 'Play on next' for audio or video therefore first navigate to the audio/video before it should be played + nextAudioVideo = (targetDoc: Doc, activeItem: Doc) => { + if (targetDoc.type === DocumentType.AUDIO) AudioBox.Instance.playFrom(NumCast(activeItem.presStartTime)); + // if (targetDoc.type === DocumentType.VID) { VideoBox.Instance.Play() }; + activeItem.playNow = false; + } + + // No more frames in current doc and next slide is defined, therefore move to next slide + nextSlide = (targetDoc: Doc, activeNext: Doc) => { + const nextSelected = this.itemIndex + 1; + this.gotoDocument(nextSelected); + + // const targetNext = Cast(activeNext.presentationTargetDoc, Doc, null); + // If next slide is audio / video 'Play automatically' then the next slide should be played + // if (activeNext && (targetNext.type === DocumentType.AUDIO || targetNext.type === DocumentType.VID) && activeNext.playAuto) { + // console.log('play next automatically'); + // if (targetNext.type === DocumentType.AUDIO) AudioBox.Instance.playFrom(NumCast(activeNext.presStartTime)); + // // if (targetNext.type === DocumentType.VID) { VideoBox.Instance.Play() }; + // } else if (targetNext.type === DocumentType.AUDIO || targetNext.type === DocumentType.VID) { activeNext.playNow = true; console.log('play next after it is navigated to'); } + } + + // Called when the user activates 'next' - to move to the next part of the pres. trail @action next = () => { - this.updateCurrentPresentation(); const activeNext = Cast(this.childDocs[this.itemIndex + 1], Doc, null); const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - const childDocs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); - const currentFrame = Cast(targetDoc._currentFrame, "number", null); - const lastFrame = Cast(targetDoc.lastFrame, "number", null); - const curFrame = NumCast(targetDoc._currentFrame); + const lastFrame = Cast(targetDoc?.lastFrame, "number", null); + const curFrame = NumCast(targetDoc?._currentFrame); let internalFrames: boolean = false; if (activeItem.presProgressivize || activeItem.zoomProgressivize || targetDoc.scrollProgressivize) internalFrames = true; - // Case 1: There are still other frames and should go through all frames before going to next slide if (internalFrames && lastFrame !== undefined && curFrame < lastFrame) { - targetDoc._viewTransition = "all 1s"; - setTimeout(() => targetDoc._viewTransition = undefined, 1010); - // targetDoc._currentFrame = curFrame + 1; - this.nextKeyframe(targetDoc, activeItem); - if (activeItem.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, targetDoc); - else targetDoc.editing = true; - // if (activeItem.zoomProgressivize) this.zoomProgressivizeNext(targetDoc); - // Case 2: 'Play on next' for audio or video therefore first navigate to the audio/video before it should be played - } else if ((targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && !activeItem.playAuto && activeItem.playNow && this.layoutDoc.presStatus !== 'auto') { - if (targetDoc.type === DocumentType.AUDIO) AudioBox.Instance.playFrom(NumCast(activeItem.presStartTime)); - // if (targetDoc.type === DocumentType.VID) { VideoBox.Instance.Play() }; - activeItem.playNow = false; - // Case 3: No more frames in current doc and next slide is defined, therefore move to next slide + // Case 1: There are still other frames and should go through all frames before going to next slide + this.nextInternalFrame(targetDoc, activeItem); } else if (this.childDocs[this.itemIndex + 1] !== undefined) { - if (activeNext.presPinView) setTimeout(() => this.selectPres(), 0); - else this.selectPres(); - const nextSelected = this.itemIndex + 1; - if (targetDoc.type === DocumentType.AUDIO) { if (AudioBox.Instance._ele) AudioBox.Instance.pause(); } - // if (targetDoc.type === DocumentType.VID) { if (AudioBox.Instance._ele) VideoBox.Instance.Pause(); } - const targetNext = Cast(activeNext.presentationTargetDoc, Doc, null); - // If next slide is audio / video 'Play automatically' then the next slide should be played - if (activeNext && (targetNext.type === DocumentType.AUDIO || targetNext.type === DocumentType.VID) && activeNext.playAuto) { - console.log('play next automatically'); - if (targetNext.type === DocumentType.AUDIO) AudioBox.Instance.playFrom(NumCast(activeNext.presStartTime)); - // if (targetNext.type === DocumentType.VID) { VideoBox.Instance.Play() }; - } else if (targetNext.type === DocumentType.AUDIO || targetNext.type === DocumentType.VID) { activeNext.playNow = true; console.log('play next after it is navigated to'); } - this.gotoDocument(nextSelected, this.itemIndex); + // Case 3: No more frames in current doc and next slide is defined, therefore move to next slide + this.nextSlide(targetDoc, activeNext); } else if (this.childDocs[this.itemIndex + 1] === undefined && this.layoutDoc.presLoop) { - this.gotoDocument(0, this.itemIndex); + // Case 4: Last slide and presLoop is toggled ON + this.gotoDocument(0); } + // else if ((targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && !activeItem.playAuto && activeItem.playNow && this.layoutDoc.presStatus !== PresStatus.Autoplay) { + // // Case 2: 'Play on next' for audio or video therefore first navigate to the audio/video before it should be played + // this.nextAudioVideo(targetDoc, activeItem); + // } } - /** - * Called when the user moves back - * Design choice: If there are frames within the presentation, moving back will not - * got back through the frames but instead directly to the next point in the presentation. - */ - @undoBatch + // Called when the user activates 'back' - to move to the previous part of the pres. trail @action back = () => { - this.updateCurrentPresentation(); const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const prevItem = Cast(this.childDocs[Math.max(0, this.itemIndex - 1)], Doc, null); const prevTargetDoc = Cast(prevItem.presentationTargetDoc, Doc, null); const lastFrame = Cast(targetDoc.lastFrame, "number", null); const curFrame = NumCast(targetDoc._currentFrame); - if (prevItem.presPinView) setTimeout(() => this.selectPres(), 0); - else this.selectPres(); + let prevSelected = this.itemIndex; if (lastFrame !== undefined && curFrame >= 1) { + // Case 1: There are still other frames and should go through all frames before going to previous slide this.prevKeyframe(targetDoc, activeItem); - } else if (activeItem) { - let prevSelected = this.itemIndex; + } else if (activeItem && this.childDocs[this.itemIndex - 1] !== undefined) { + // Case 2: There are no other frames so it should go to the previous slide prevSelected = Math.max(0, prevSelected - 1); - this.gotoDocument(prevSelected, this.itemIndex); + this.gotoDocument(prevSelected); if (NumCast(prevTargetDoc.lastFrame) > 0) prevTargetDoc._currentFrame = NumCast(prevTargetDoc.lastFrame); + } else if (this.childDocs[this.itemIndex - 1] === undefined && this.layoutDoc.presLoop) { + // Case 3: Pres loop is on so it should go to the last slide + this.gotoDocument(this.childDocs.length - 1); } } //The function that is called when a document is clicked or reached through next or back. //it'll also execute the necessary actions if presentation is playing. - public gotoDocument = action((index: number, fromDoc: number) => { - this.updateCurrentPresentation(); + public gotoDocument = action((index: number) => { Doc.UnBrushAllDocs(); if (index >= 0 && index < this.childDocs.length) { this.rootDoc._itemIndex = index; const activeItem: Doc = this.activeItem; const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null); - if (activeItem.presPinView) { - const bestTarget = DocumentManager.Instance.getFirstDocumentView(presTargetDoc)?.props.Document; - bestTarget && runInAction(() => { - if (activeItem.presMovement === PresMovement.Jump) { - bestTarget._viewTransition = '0s'; - } else { - bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 1s'; - setTimeout(() => bestTarget._viewTransition = undefined, activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 1010); - } - }); - } else { + if (presTargetDoc) { presTargetDoc && runInAction(() => { if (activeItem.presMovement === PresMovement.Jump) presTargetDoc.focusSpeed = 0; else presTargetDoc.focusSpeed = activeItem.presTransition ? activeItem.presTransition : 500; @@ -236,13 +278,35 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> if (presTargetDoc?.lastFrame !== undefined) { presTargetDoc._currentFrame = 0; } - this._selectedArray = [this.childDocs[index]]; //Update selected array - //Handles movement to element - if (this.layoutDoc._viewType === "stacking") this.navigateToElement(this.childDocs[index]); + this._selectedArray.clear(); + this.childDocs[index] && this._selectedArray.set(this.childDocs[index], undefined); //Update selected array + if (this.layoutDoc._viewType === "stacking") this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list this.onHideDocument(); //Handles hide after/before } }); + _navTimer!: NodeJS.Timeout; + navigateToView = (targetDoc: Doc, activeItem: Doc) => { + clearTimeout(this._navTimer); + const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document; + bestTarget && runInAction(() => { + if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) { + bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; + bestTarget._scrollY = activeItem.presPinViewScroll; + } else if (bestTarget.type === DocumentType.COMPARISON) { + bestTarget._clipWidth = activeItem.presPinClipWidth; + } else if (bestTarget.type === DocumentType.VID) { + bestTarget._currentTimecode = activeItem.presPinTimecode; + } else { + bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; + bestTarget._panX = activeItem.presPinViewX; + bestTarget._panY = activeItem.presPinViewY; + bestTarget._viewScale = activeItem.presPinViewScale; + } + this._navTimer = setTimeout(() => bestTarget._viewTransition = undefined, activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510); + }); + } + /** * This method makes sure that cursor navigates to the element that * has the option open and last in the group. @@ -254,68 +318,55 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> navigateToElement = async (curDoc: Doc) => { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - const srcContext = await DocCastAsync(targetDoc.context); + const srcContext = Cast(targetDoc.context, Doc, null); const presCollection = Cast(this.layoutDoc.presCollection, Doc, null); - const collectionDocView = presCollection ? await DocumentManager.Instance.getDocumentView(presCollection) : undefined; + const collectionDocView = presCollection ? DocumentManager.Instance.getDocumentView(presCollection) : undefined; + const includesDoc: boolean = DocListCast(presCollection?.data).includes(targetDoc); + const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === srcContext); this.turnOffEdit(); - if (this.itemIndex >= 0) { - if (srcContext && targetDoc) { - this.layoutDoc.presCollection = srcContext; - } else if (targetDoc) this.layoutDoc.presCollection = targetDoc; + // Handles the setting of presCollection + if (includesDoc) { + //Case 1: Pres collection should not change as it is already the same + } else if (tab !== undefined) { + // Case 2: Pres collection should update + this.layoutDoc.presCollection = srcContext; } - // if (collectionDocView) { - // if (srcContext && srcContext !== presCollection) { - // // Case 1: new srcContext inside of current collection so add a new tab to the current pres collection - // collectionDocView.props.addDocTab(srcContext, "inPlace"); - // } - // } - this.updateCurrentPresentation(); - const docToJump = curDoc; - const willZoom = false; - + const presStatus = this.rootDoc.presStatus; + const selViewCache = Array.from(this._selectedArray.keys()); + const dragViewCache = Array.from(this._dragArray); + const eleViewCache = Array.from(this._eleArray); + const self = this; + const resetSelection = action(() => { + const presDocView = DocumentManager.Instance.getDocumentView(self.rootDoc); + if (presDocView) SelectionManager.SelectDoc(presDocView, false); + self.rootDoc.presStatus = presStatus; + self._selectedArray.clear(); + selViewCache.forEach(doc => self._selectedArray.set(doc, undefined)); + self._dragArray.splice(0, self._dragArray.length, ...dragViewCache); + self._eleArray.splice(0, self._eleArray.length, ...eleViewCache); + }); + const openInTab = () => { + collectionDocView ? collectionDocView.props.addDocTab(targetDoc, "") : this.props.addDocTab(targetDoc, ":left"); + this.layoutDoc.presCollection = targetDoc; + // this still needs some fixing + setTimeout(resetSelection, 500); + }; // If openDocument is selected then it should open the document for the user if (activeItem.openDocument) { - collectionDocView ? collectionDocView.props.addDocTab(activeItem, "replace") : this.props.addDocTab(activeItem, "replace:left"); - } else - //docToJump stayed same meaning, it was not in the group or was the last element in the group - if (activeItem.zoomProgressivize && this.rootDoc.presStatus !== 'edit') { - this.zoomProgressivizeNext(targetDoc); - } else if (docToJump === curDoc) { - //checking if curDoc has navigation open - if (curDoc.presMovement === PresMovement.Pan && targetDoc) { - await DocumentManager.Instance.jumpToDocument(targetDoc, false, undefined, srcContext); - } else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) { - //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(targetDoc, true, undefined, srcContext); - } - } else { - //awaiting jump so that new scale can be found, since jumping is async - targetDoc && await DocumentManager.Instance.jumpToDocument(targetDoc, willZoom, undefined, srcContext); - } + openInTab(); + } else if (curDoc.presMovement === PresMovement.Pan && targetDoc) { + await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection); // documents open in new tab instead of on right + } else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) { + //awaiting jump so that new scale can be found, since jumping is async + await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection); // documents open in new tab instead of on right + } // After navigating to the document, if it is added as a presPinView then it will // adjust the pan and scale to that of the pinView when it was added. - // TODO: Add option to remove presPinView if (activeItem.presPinView) { // if targetDoc is not displayed but one of its aliases is, then we need to modify that alias, not the original target - const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document; - bestTarget && runInAction(() => { - if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) { - bestTarget._scrollY = activeItem.presPinViewScroll; - } else { - bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; - bestTarget._panX = activeItem.presPinViewX; - bestTarget._panY = activeItem.presPinViewY; - bestTarget._viewScale = activeItem.presPinViewScale; - } - }); - //setTimeout(() => targetDoc._viewTransition = undefined, 1010); + this.navigateToView(targetDoc, activeItem); } - // If website and has presWebsite data associated then on click it should - // go back to that specific website // TODO: Add progressivize for navigating web (storing websites for given frames) - if (targetDoc.presWebsiteData) { - targetDoc.data = targetDoc.presWebsiteData; - } } /** @@ -363,7 +414,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } } - /** * For 'Hide Before' and 'Hide After' buttons making sure that * they are hidden each time the presentation is updated. @@ -372,20 +422,27 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> onHideDocument = () => { this.childDocs.forEach((doc, index) => { const curDoc = Cast(doc, Doc, null); - const tagDoc = Cast(curDoc.presentationTargetDoc!, Doc, null); + const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); if (tagDoc) tagDoc.opacity = 1; - if (curDoc.presHideTillShownButton) { - if (index > this.itemIndex) { - tagDoc.opacity = 0; - } else if (!curDoc.presHideAfterButton) { - tagDoc.opacity = 1; + const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, tagDoc); + const curInd: number = itemIndexes.indexOf(index); + if (tagDoc === this.layoutDoc.presCollection) { tagDoc.opacity = 1; } + else { + if (itemIndexes.length > 1 && curDoc.presHideBefore && curInd !== 0) { } + else if (curDoc.presHideBefore) { + if (index > this.itemIndex) { + tagDoc.opacity = 0; + } else if (!curDoc.presHideAfter) { + tagDoc.opacity = 1; + } } - } - if (curDoc.presHideAfterButton) { - if (index < this.itemIndex) { - tagDoc.opacity = 0; - } else if (!curDoc.presHideTillShownButton) { - tagDoc.opacity = 1; + if (itemIndexes.length > 1 && curDoc.presHideAfter && curInd !== (itemIndexes.length - 1)) { } + else if (curDoc.presHideAfter) { + if (index < this.itemIndex) { + tagDoc.opacity = 0; + } else if (!curDoc.presHideBefore) { + tagDoc.opacity = 1; + } } } }); @@ -394,7 +451,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> //The function that starts or resets presentaton functionally, depending on presStatus of the layoutDoc - @undoBatch @action startAutoPres = (startSlide: number) => { this.updateCurrentPresentation(); @@ -418,23 +474,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> if (i === this.childDocs.length - 1) { setTimeout(() => { clearTimeout(this._presTimer); - if (this.layoutDoc.presStatus === 'auto' && !this.layoutDoc.presLoop) this.layoutDoc.presStatus = "manual"; + if (this.layoutDoc.presStatus === 'auto' && !this.layoutDoc.presLoop) this.layoutDoc.presStatus = PresStatus.Manual; else if (this.layoutDoc.presLoop) this.startAutoPres(0); }, duration); } } }; - this.layoutDoc.presStatus = "auto"; + this.layoutDoc.presStatus = PresStatus.Autoplay; this.startPresentation(startSlide); - this.gotoDocument(startSlide, this.itemIndex); + this.gotoDocument(startSlide); load(); } @action pauseAutoPres = () => { - if (this.layoutDoc.presStatus === "auto") { + if (this.layoutDoc.presStatus === PresStatus.Autoplay) { if (this._presTimer) clearTimeout(this._presTimer); - this.layoutDoc.presStatus = "manual"; + this.layoutDoc.presStatus = PresStatus.Manual; this.layoutDoc.presLoop = false; } } @@ -442,26 +498,32 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> //The function that resets the presentation by removing every action done by it. It also //stops the presentaton. resetPresentation = () => { - this.updateCurrentPresentation(); this.rootDoc._itemIndex = 0; + this.childDocs.map(doc => Cast(doc.presentationTargetDoc, Doc, null)).filter(doc => doc instanceof Doc).forEach(doc => { + try { + doc.opacity = 1; + } catch (e) { + console.log("REset presentation error: ", e); + } + }); + ///for (const doc of this.childDocs) Cast(doc.presentationTargetDoc, Doc, null).opacity = 1; } @action togglePath = (srcContext: Doc, off?: boolean) => { if (off) { - this.pathBoolean = false; + this._pathBoolean = false; srcContext.presPathView = false; } else { - this.pathBoolean = !this.pathBoolean; - srcContext.presPathView = this.pathBoolean; + runInAction(() => this._pathBoolean = !this._pathBoolean); + srcContext.presPathView = this._pathBoolean; } } - @undoBatch @action toggleExpandMode = () => { - this.rootDoc.expandBoolean = !this.rootDoc.expandBoolean; + runInAction(() => this._expandBoolean = !this._expandBoolean); + this.rootDoc.expandBoolean = this._expandBoolean; this.childDocs.forEach((doc) => { - if (this.rootDoc.expandBoolean) doc.presExpandInlineButton = true; - else if (!this.rootDoc.expandBoolean) doc.presExpandInlineButton = false; + doc.presExpandInlineButton = this._expandBoolean; }); } @@ -474,10 +536,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> this.updateCurrentPresentation(); this.childDocs.map(doc => { const tagDoc = doc.presentationTargetDoc as Doc; - if (doc.presHideTillShownButton && this.childDocs.indexOf(doc) > startIndex) { + if (doc.presHideBefore && this.childDocs.indexOf(doc) > startIndex) { tagDoc.opacity = 0; } - if (doc.presHideAfterButton && this.childDocs.indexOf(doc) < startIndex) { + if (doc.presHideAfter && this.childDocs.indexOf(doc) < startIndex) { tagDoc.opacity = 0; } }); @@ -486,17 +548,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> /** * The method called to open the presentation as a minimized view */ - @undoBatch @action updateMinimize = () => { const docView = DocumentManager.Instance.getDocumentView(this.layoutDoc); - if (this.layoutDoc.inOverlay) { - this.layoutDoc.presStatus = 'edit'; + if (CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { + this.layoutDoc.presStatus = PresStatus.Edit; Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc); CollectionDockingView.AddSplit(this.rootDoc, "right"); - this.layoutDoc.inOverlay = false; } else if (this.layoutDoc.context && docView) { - this.layoutDoc.presStatus = 'edit'; + this.layoutDoc.presStatus = PresStatus.Edit; clearTimeout(this._presTimer); const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); this.rootDoc.x = pt[0] + (this.props.PanelWidth() - 250); @@ -506,7 +566,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> docView.props.removeDocument?.(this.layoutDoc); Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc); } else { - this.layoutDoc.presStatus = 'edit'; + this.layoutDoc.presStatus = PresStatus.Edit; clearTimeout(this._presTimer); const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); this.rootDoc.x = pt[0] + (this.props.PanelWidth() - 250); @@ -522,7 +582,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> * Called when the user changes the view type * Either 'List' (stacking) or 'Slides' (carousel) */ - @undoBatch + // @undoBatch viewChanged = action((e: React.ChangeEvent) => { //@ts-ignore const viewType = e.target.selectedOptions[0].value as CollectionViewType; @@ -532,32 +592,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> if (viewType === CollectionViewType.Stacking) this.layoutDoc._gridGap = 0; }); - /** - * When the movement dropdown is changes - */ - @undoBatch - updateMovement = action((movement: any, activeItem: Doc, targetDoc: Doc) => { - switch (movement) { - case PresMovement.Zoom: //Pan and zoom - activeItem.presMovement = PresMovement.Zoom; - break; - case PresMovement.Pan: //Pan - activeItem.presMovement = PresMovement.Pan; - break; - case PresMovement.Jump: //Jump Cut - activeItem.presJump = true; - activeItem.presMovement = PresMovement.Jump; - break; - case PresMovement.None: default: - activeItem.presMovement = PresMovement.None; - break; - } - }); setMovementName = action((movement: any, activeItem: Doc): string => { let output: string = 'none'; switch (movement) { - case PresMovement.Zoom: output = 'Zoom'; break; //Pan and zoom + case PresMovement.Zoom: output = 'Pan & Zoom'; break; //Pan and zoom case PresMovement.Pan: output = 'Pan'; break; //Pan case PresMovement.Jump: output = 'Jump cut'; break; //Jump Cut case PresMovement.None: output = 'None'; break; //None @@ -571,29 +610,34 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> addDocumentFilter = (doc: Doc | Doc[]) => { const docs = doc instanceof Doc ? [doc] : doc; docs.forEach((doc, i) => { - if (this.childDocs.includes(doc)) { - if (docs.length === i + 1) return false; - } else if (doc.type === DocumentType.LABEL) { + if (doc.type === DocumentType.LABEL) { const audio = Cast(doc.annotationOn, Doc, null); if (audio) { - audio.aliasOf instanceof Doc; audio.presStartTime = NumCast(doc.audioStart); audio.presEndTime = NumCast(doc.audioEnd); audio.presDuration = NumCast(doc.audioEnd) - NumCast(doc.audioStart); TabDocView.PinDoc(audio, false, true); - setTimeout(() => this.removeDocument(doc), 1); + setTimeout(() => this.removeDocument(doc), 0); return false; } } else { - doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf); - !this.childDocs.includes(doc) && (doc.presMovement = PresMovement.Zoom); - if (this.rootDoc.expandBoolean) doc.presExpandInlineButton = true; + if (!doc.aliasOf) { + const original = Doc.MakeAlias(doc); + TabDocView.PinDoc(original); + setTimeout(() => this.removeDocument(doc), 0); + return false; + } else { + if (!doc.presentationTargetDoc) doc.title = doc.title + " - Slide"; + doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf); + doc.presMovement = PresMovement.Zoom; + if (this._expandBoolean) doc.presExpandInlineButton = true; + } } }); return true; } childLayoutTemplate = () => this.rootDoc._viewType !== CollectionViewType.Stacking ? undefined : this.presElement; - removeDocument = (doc: Doc) => { Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc); }; + removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc); getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight panelHeight = () => this.props.PanelHeight() - 40; active = (outsideReaction?: boolean) => ((Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc._isBackground) && @@ -604,55 +648,67 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> */ @action sortArray = (): Doc[] => { - return this.childDocs.filter(doc => this._selectedArray.includes(doc)); + return this.childDocs.filter(doc => this._selectedArray.has(doc)); } /** * Method to get the list of selected items in the order in which they have been selected */ @computed get listOfSelected() { - const list = this._selectedArray.map((doc: Doc, index: any) => { + const list = Array.from(this._selectedArray.keys()).map((doc: Doc, index: any) => { const curDoc = Cast(doc, Doc, null); - const tagDoc = Cast(curDoc.presentationTargetDoc!, Doc, null); - if (tagDoc) return <div className="selectedList-items">{index + 1}. {curDoc.title}</div>; - else if (curDoc) return <div className="selectedList-items">{index + 1}. {curDoc.title}</div>; + const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); + if (curDoc && curDoc === this.activeItem) return <div key={index} className="selectedList-items"><b>{index + 1}. {curDoc.title}</b></div>; + else if (tagDoc) return <div key={index} className="selectedList-items">{index + 1}. {curDoc.title}</div>; + else if (curDoc) return <div key={index} className="selectedList-items">{index + 1}. {curDoc.title}</div>; }); return list; } @action selectPres = () => { - const presDocView = DocumentManager.Instance.getDocumentView(this.rootDoc)!; - SelectionManager.SelectDoc(presDocView, false); + const presDocView = DocumentManager.Instance.getDocumentView(this.rootDoc); + presDocView && SelectionManager.SelectDoc(presDocView, false); } //Regular click @action selectElement = (doc: Doc) => { - this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.itemIndex)); - if (doc.presPinView) setTimeout(() => this.selectPres(), 0); - else this.selectPres(); + const context = Cast(doc.context, Doc, null); + this.gotoDocument(this.childDocs.indexOf(doc)); + if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0); + else this.updateCurrentPresentation(context); } //Command click @action multiSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => { - if (!this._selectedArray.includes(doc)) { - this._selectedArray.push(doc); + if (!this._selectedArray.has(doc)) { + this._selectedArray.set(doc, undefined); this._eleArray.push(ref); this._dragArray.push(drag); + } else { + this._selectedArray.delete(doc); + this.removeFromArray(this._eleArray, doc); + this.removeFromArray(this._dragArray, doc); } this.selectPres(); } + removeFromArray = (arr: any[], val: any) => { + const index: number = arr.indexOf(val); + const ret: any[] = arr.splice(index, 1); + arr = ret; + } + //Shift click @action shiftSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => { - this._selectedArray = []; + this._selectedArray.clear(); // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); if (this.activeItem) { for (let i = Math.min(this.itemIndex, this.childDocs.indexOf(doc)); i <= Math.max(this.itemIndex, this.childDocs.indexOf(doc)); i++) { - this._selectedArray.push(this.childDocs[i]); + this._selectedArray.set(this.childDocs[i], undefined); this._eleArray.push(ref); this._dragArray.push(drag); } @@ -660,68 +716,100 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> this.selectPres(); } + //regular click + @action + regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean) => { + this._selectedArray.clear(); + this._selectedArray.set(doc, undefined); + this._eleArray.splice(0, this._eleArray.length, ref); + this._dragArray.splice(0, this._dragArray.length, drag); + focus && this.selectElement(doc); + this.selectPres(); + } + + modifierSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean, cmdClick: boolean, shiftClick: boolean) => { + if (cmdClick) this.multiSelect(doc, ref, drag); + else if (shiftClick) this.shiftSelect(doc, ref, drag); + else this.regularSelect(doc, ref, drag, focus); + } + + static keyEventsWrapper = (e: KeyboardEvent) => { + PresBox.Instance.keyEvents(e); + } + // Key for when the presentaiton is active - @undoBatch - keyEvents = action((e: KeyboardEvent) => { + @action + keyEvents = (e: KeyboardEvent) => { if (e.target instanceof HTMLInputElement) return; let handled = false; const anchorNode = document.activeElement as HTMLDivElement; if (anchorNode && anchorNode.className?.includes("lm_title")) return; - if (e.keyCode === 27) { // Escape key - if (this.layoutDoc.inOverlay) { this.updateMinimize(); } - else if (this.layoutDoc.presStatus === "edit") { this._selectedArray = []; this._eleArray = []; this._dragArray = []; } - else this.layoutDoc.presStatus = "edit"; - if (this._presTimer) clearTimeout(this._presTimer); - handled = true; - } if ((e.metaKey || e.altKey) && e.keyCode === 65) { // Ctrl-A to select all - if (this.layoutDoc.presStatus === "edit") { - this._selectedArray = this.childDocs; + switch (e.key) { + case "Backspace": + if (this.layoutDoc.presStatus === "edit") { + undoBatch(action(() => { + for (const doc of Array.from(this._selectedArray.keys())) { + this.removeDocument(doc); + } + this._selectedArray.clear(); + this._eleArray.length = 0; + this._dragArray.length = 0; + }))(); + handled = true; + } + break; + case "Escape": + if (CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { this.updateMinimize(); } + else if (this.layoutDoc.presStatus === "edit") { this._selectedArray.clear(); this._eleArray.length = this._dragArray.length = 0; } + else this.layoutDoc.presStatus = "edit"; + if (this._presTimer) clearTimeout(this._presTimer); handled = true; - } - } if (e.keyCode === 37 || e.keyCode === 38) { // left(37) / a(65) / up(38) to go back - this.back(); - if (this._presTimer) clearTimeout(this._presTimer); - handled = true; - } if (e.keyCode === 39 || e.keyCode === 40) { // right (39) / d(68) / down(40) to go to next - this.next(); - if (this._presTimer) clearTimeout(this._presTimer); - handled = true; - } if (e.keyCode === 32) { // spacebar to 'present' or autoplay - if (this.layoutDoc.presStatus !== "edit") this.startAutoPres(0); - else this.next(); - handled = true; - } if (e.keyCode === 8) { // delete selected items - if (this.layoutDoc.presStatus === "edit") { - this._selectedArray.forEach((doc, i) => this.removeDocument(doc)); - this._selectedArray = []; - this._eleArray = []; - this._dragArray = []; + break; + case "Down": case "ArrowDown": + case "Right": case "ArrowRight": + if (e.shiftKey && this.itemIndex < this.childDocs.length - 1) { // TODO: update to work properly + this.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) + 1; + this._selectedArray.set(this.childDocs[this.rootDoc._itemIndex], undefined); + } else { + this.next(); + if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } + } handled = true; - } - } if (handled) { + break; + case "Up": case "ArrowUp": + case "Left": case "ArrowLeft": + if (e.shiftKey && this.itemIndex !== 0) { // TODO: update to work properly + this.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) - 1; + this._selectedArray.set(this.childDocs[this.rootDoc._itemIndex], undefined); + } else { + this.back(); + if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } + } + handled = true; + break; + case "Spacebar": case " ": + if (this.layoutDoc.presStatus === PresStatus.Manual) this.startAutoPres(this.itemIndex); + else if (this.layoutDoc.presStatus === PresStatus.Autoplay) if (this._presTimer) clearTimeout(this._presTimer); + handled = true; + break; + case "a": + if ((e.metaKey || e.altKey) && this.layoutDoc.presStatus === "edit") { + this._selectedArray.clear(); + this.childDocs.forEach(doc => this._selectedArray.set(doc, undefined)); + handled = true; + } + default: + break; + } + if (handled) { e.stopPropagation(); e.preventDefault(); - } if ((e.keyCode === 37 || e.keyCode === 38) && e.shiftKey) { // left(37) / a(65) / up(38) to go back - if (this.layoutDoc.presStatus === "edit" && this._selectedArray.length > 0) { - const index = this.childDocs.indexOf(this._selectedArray[this._selectedArray.length]); - if ((index - 1) > 0) this._selectedArray.push(this.childDocs[index - 1]); - } - handled = true; - } if ((e.keyCode === 39 || e.keyCode === 40) && e.shiftKey) { // left(37) / a(65) / up(38) to go back - if (this.layoutDoc.presStatus === "edit" && this._selectedArray.length > 0) { - const index = this.childDocs.indexOf(this._selectedArray[this._selectedArray.length]); - if ((index - 1) > 0) { - this._selectedArray.push(this.childDocs[index - 1]); - } - } - handled = true; } - }); + } /** * */ - @undoBatch @action viewPaths = () => { const srcContext = Cast(this.rootDoc.presCollection, Doc, null); @@ -730,28 +818,73 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } } + getAllIndexes = (arr: Doc[], val: Doc): number[] => { + const indexes = []; + for (let i = 0; i < arr.length; i++) { + arr[i] === val && indexes.push(i); + } + return indexes; + } + // Adds the index in the pres path graphically @computed get order() { const order: JSX.Element[] = []; - this.childDocs.forEach((doc, index) => { + const docs: Doc[] = []; + const presCollection = Cast(this.rootDoc.presCollection, Doc, null); + const dv = DocumentManager.Instance.getDocumentView(presCollection); + this.childDocs.filter(doc => Cast(doc.presentationTargetDoc, Doc, null)).forEach((doc, index) => { const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); - const srcContext = Cast(tagDoc?.context, Doc, null); + const srcContext = Cast(tagDoc.context, Doc, null); const width = NumCast(tagDoc._width) / 10; const height = Math.max(NumCast(tagDoc._height) / 10, 15); const edge = Math.max(width, height); const fontSize = edge * 0.8; - // Case A: Document is contained within the colleciton - if (this.rootDoc.presCollection === srcContext) { - order.push( - <div className="pathOrder" style={{ top: NumCast(tagDoc.y) - (edge / 2), left: NumCast(tagDoc.x) - (edge / 2), width: edge, height: edge, fontSize: fontSize }}> - <div className="pathOrder-frame">{index + 1}</div> - </div>); - // Case B: Document is not inside of the collection - } else { + const gap = 2; + if (presCollection === srcContext) { + // Case A: Document is contained within the collection + if (docs.includes(tagDoc)) { + const prevOccurances: number = this.getAllIndexes(docs, tagDoc).length; + docs.push(tagDoc); + order.push( + <div className="pathOrder" + key={tagDoc.id + 'pres' + index} + style={{ top: NumCast(tagDoc.y) + (prevOccurances * (edge + gap) - (edge / 2)), left: NumCast(tagDoc.x) - (edge / 2), width: edge, height: edge, fontSize: fontSize }} + onClick={() => this.selectElement(doc)}> + <div className="pathOrder-frame">{index + 1}</div> + </div>); + } else { + docs.push(tagDoc); + order.push( + <div className="pathOrder" + key={tagDoc.id + 'pres' + index} + style={{ top: NumCast(tagDoc.y) - (edge / 2), left: NumCast(tagDoc.x) - (edge / 2), width: edge, height: edge, fontSize: fontSize }} + onClick={() => this.selectElement(doc)}> + <div className="pathOrder-frame">{index + 1}</div> + </div>); + } + } else if (doc.presPinView && presCollection === tagDoc && dv) { + // Case B: Document is presPinView and is presCollection + const scale: number = 1 / NumCast(doc.presPinViewScale); + const height: number = dv.props.PanelHeight() * scale; + const width: number = dv.props.PanelWidth() * scale; + const indWidth = width / 10; + const indHeight = Math.max(height / 10, 15); + const indEdge = Math.max(indWidth, indHeight); + const indFontSize = indEdge * 0.8; + const xLoc: number = NumCast(doc.presPinViewX) - (width / 2); + const yLoc: number = NumCast(doc.presPinViewY) - (height / 2); + docs.push(tagDoc); order.push( - <div className="pathOrder" style={{ top: 0, left: 0 }}> - <div className="pathOrder-frame">{index + 1}</div> - </div>); + <> + <div className="pathOrder" + key={tagDoc.id + 'pres' + index} + style={{ top: yLoc - (indEdge / 2), left: xLoc - (indEdge / 2), width: indEdge, height: indEdge, fontSize: indFontSize }} + onClick={() => this.selectElement(doc)} + > + <div className="pathOrder-frame">{index + 1}</div> + </div> + <div className="pathOrder-presPinView" style={{ top: yLoc, left: xLoc, width: width, height: height, borderWidth: indEdge / 10 }}></div> + </>); } }); return order; @@ -767,17 +900,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> */ @computed get paths() { let pathPoints = ""; + const presCollection = Cast(this.rootDoc.presCollection, Doc, null); this.childDocs.forEach((doc, index) => { const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); const srcContext = Cast(tagDoc?.context, Doc, null); - if (tagDoc && this.rootDoc.presCollection === srcContext) { + if (tagDoc && presCollection === srcContext) { const n1x = NumCast(tagDoc.x) + (NumCast(tagDoc._width) / 2); const n1y = NumCast(tagDoc.y) + (NumCast(tagDoc._height) / 2); if (index = 0) pathPoints = n1x + "," + n1y; else pathPoints = pathPoints + " " + n1x + "," + n1y; - } else { - if (index = 0) pathPoints = 0 + "," + 0; - else pathPoints = pathPoints + " " + 0 + "," + 0; + } else if (doc.presPinView && presCollection === tagDoc) { + const n1x = NumCast(doc.presPinViewX); + const n1y = NumCast(doc.presPinViewY); + if (index = 0) pathPoints = n1x + "," + n1y; + else pathPoints = pathPoints + " " + n1x + "," + n1y; } }); return (<polyline @@ -796,53 +932,134 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> />); } - /** - * The function that is called on click to turn fading document after presented option on/off. - * It also makes sure that the option swithches from hide-after to this one, since both - * can't coexist. - */ - @action - onFadeDocumentAfterPresentedClick = (e: React.MouseEvent) => { - e.stopPropagation(); - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - activeItem.presFadeButton = !activeItem.presFadeButton; - if (!activeItem.presFadeButton) { - if (targetDoc) { - targetDoc.opacity = 1; - } - } else { - activeItem.presHideAfterButton = false; - if (this.rootDoc.presStatus !== "edit" && targetDoc) { - targetDoc.opacity = 0.5; - } - } - } - // Converts seconds to ms and updates presTransition - @undoBatch setTransitionTime = (number: String, change?: number) => { let timeInMS = Number(number) * 1000; if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; if (timeInMS > 10000) timeInMS = 10000; - if (this.activeItem) this.activeItem.presTransition = timeInMS; + Array.from(this._selectedArray.keys()).forEach((doc) => doc.presTransition = timeInMS); } // Converts seconds to ms and updates presDuration - @undoBatch setDurationTime = (number: String, change?: number) => { let timeInMS = Number(number) * 1000; if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; if (timeInMS > 20000) timeInMS = 20000; - if (this.activeItem) this.activeItem.presDuration = timeInMS; + Array.from(this._selectedArray.keys()).forEach((doc) => doc.presDuration = timeInMS); } + /** + * When the movement dropdown is changes + */ + @undoBatch + updateMovement = action((movement: any, all?: boolean) => { + const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys()); + array.forEach((doc) => { + switch (movement) { + case PresMovement.Zoom: //Pan and zoom + doc.presMovement = PresMovement.Zoom; + break; + case PresMovement.Pan: //Pan + doc.presMovement = PresMovement.Pan; + break; + case PresMovement.Jump: //Jump Cut + doc.presJump = true; + doc.presMovement = PresMovement.Jump; + break; + case PresMovement.None: default: + doc.presMovement = PresMovement.None; + break; + } + }); + }); + + @undoBatch + @action + updateHideBefore = (activeItem: Doc) => { + activeItem.presHideBefore = !activeItem.presHideBefore; + Array.from(this._selectedArray.keys()).forEach((doc) => doc.presHideBefore = activeItem.presHideBefore); + } + + @undoBatch + @action + updateHideAfter = (activeItem: Doc) => { + activeItem.presHideAfter = !activeItem.presHideAfter; + Array.from(this._selectedArray.keys()).forEach((doc) => doc.presHideAfter = activeItem.presHideAfter); + } + + @undoBatch + @action + updateOpenDoc = (activeItem: Doc) => { + activeItem.openDocument = !activeItem.openDocument; + Array.from(this._selectedArray.keys()).forEach((doc) => { + doc.openDocument = activeItem.openDocument; + }); + } + + @undoBatch + @action + updateEffectDirection = (effect: any, all?: boolean) => { + const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys()); + array.forEach((doc) => { + const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); + switch (effect) { + case PresEffect.Left: + tagDoc.presEffectDirection = PresEffect.Left; + break; + case PresEffect.Right: + tagDoc.presEffectDirection = PresEffect.Right; + break; + case PresEffect.Top: + tagDoc.presEffectDirection = PresEffect.Top; + break; + case PresEffect.Bottom: + tagDoc.presEffectDirection = PresEffect.Bottom; + break; + case PresEffect.Center: default: + tagDoc.presEffectDirection = PresEffect.Center; + break; + } + }); + } + + @undoBatch + @action + updateEffect = (effect: any, all?: boolean) => { + const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys()); + array.forEach((doc) => { + const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); + switch (effect) { + case PresEffect.Bounce: + tagDoc.presEffect = PresEffect.Bounce; + break; + case PresEffect.Fade: + tagDoc.presEffect = PresEffect.Fade; + break; + case PresEffect.Flip: + tagDoc.presEffect = PresEffect.Flip; + break; + case PresEffect.Roll: + tagDoc.presEffect = PresEffect.Roll; + break; + case PresEffect.Rotate: + tagDoc.presEffect = PresEffect.Rotate; + break; + case PresEffect.None: default: + tagDoc.presEffect = PresEffect.None; + break; + } + }); + } + + _batch: UndoManager.Batch | undefined = undefined; @computed get transitionDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; + const isPresCollection: boolean = (targetDoc === this.layoutDoc.presCollection); + const isPinWithView: boolean = BoolCast(activeItem.presPinView); if (activeItem && targetDoc) { const transitionSpeed = activeItem.presTransition ? NumCast(activeItem.presTransition) / 1000 : 0.5; let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 2; @@ -853,16 +1070,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div className={`presBox-ribbon ${this.transitionTools && this.layoutDoc.presStatus === "edit" ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = false; this.openEffectDropdown = false; })}> <div className="ribbon-box"> Movement - <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = !this.openMovementDropdown; })} style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? 'solid 2px #5B9FDD' : 'solid 1px black' }}> - {this.setMovementName(activeItem.presMovement, activeItem)} - <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openMovementDropdown ? '#5B9FDD' : 'black' }} icon={"angle-down"} /> - <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={e => e.stopPropagation()} style={{ display: this.openMovementDropdown ? "grid" : "none" }}> - <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.None ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.None, activeItem, targetDoc)}>None</div> - <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Zoom ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Zoom, activeItem, targetDoc)}>Pan and Zoom</div> - <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Pan ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Pan, activeItem, targetDoc)}>Pan</div> - <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Jump ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Jump, activeItem, targetDoc)}>Jump cut</div> + {isPresCollection || (isPresCollection && isPinWithView) ? + <div className="ribbon-property" style={{ marginLeft: 0, height: 25, textAlign: 'left', paddingLeft: 5, paddingRight: 5, fontSize: 10 }}> + {this.scrollable ? "Scroll to pinned view" : !isPinWithView ? "No movement" : "Pan & Zoom to pinned view"} </div> - </div> + : + <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = !this.openMovementDropdown; })} style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? `solid 2px ${PresColor.DarkBlue}` : 'solid 1px black' }}> + {this.setMovementName(activeItem.presMovement, activeItem)} + <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openMovementDropdown ? PresColor.DarkBlue : 'black' }} icon={"angle-down"} /> + <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={e => e.stopPropagation()} style={{ display: this.openMovementDropdown ? "grid" : "none" }}> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.None ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.None)}>None</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Zoom ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Zoom)}>Pan {"&"} Zoom</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Pan ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Pan)}>Pan</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Jump ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Jump)}>Jump cut</div> + </div> + </div> + } <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "inline-flex" : "none" }}> <div className="presBox-subheading">Transition Speed</div> <div className="ribbon-property"> @@ -871,15 +1094,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> onChange={action((e) => this.setTransitionTime(e.target.value))} /> s </div> <div className="ribbon-propertyUpDown"> - <div className="ribbon-propertyUpDownItem" onClick={() => this.setTransitionTime(String(transitionSpeed), 1000)}> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setTransitionTime(String(transitionSpeed), 1000))}> <FontAwesomeIcon icon={"caret-up"} /> </div> - <div className="ribbon-propertyUpDownItem" onClick={() => this.setTransitionTime(String(transitionSpeed), -1000)}> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setTransitionTime(String(transitionSpeed), -1000))}> <FontAwesomeIcon icon={"caret-down"} /> </div> </div> </div> - <input type="range" step="0.1" min="0.1" max="10" value={transitionSpeed} className={`toolbar-slider ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`} id="toolbar-slider" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setTransitionTime(e.target.value); }} /> + <input type="range" step="0.1" min="0.1" max="10" value={transitionSpeed} + className={`toolbar-slider ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`} + id="toolbar-slider" + onPointerDown={() => this._batch = UndoManager.StartBatch("presTransition")} + onPointerUp={() => this._batch?.end()} + onChange={(e: React.ChangeEvent<HTMLInputElement>) => { + e.stopPropagation(); + this.setTransitionTime(e.target.value); + }} /> <div className={`slider-headers ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`}> <div className="slider-text">Fast</div> <div className="slider-text">Medium</div> @@ -889,46 +1120,51 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div className="ribbon-box"> Visibility {"&"} Duration <div className="ribbon-doubleButton"> - <Tooltip title={<><div className="dash-tooltip">{"Hide before presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideTillShownButton ? "active" : ""}`} onClick={undoBatch(() => activeItem.presHideTillShownButton = !activeItem.presHideTillShownButton)}>Hide before</div></Tooltip> - <Tooltip title={<><div className="dash-tooltip">{"Hide after presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideAfterButton ? "active" : ""}`} onClick={undoBatch(() => activeItem.presHideAfterButton = !activeItem.presHideAfterButton)}>Hide after</div></Tooltip> - <Tooltip title={<><div className="dash-tooltip">{"Open document in a new tab"}</div></>}><div className="ribbon-toggle" style={{ backgroundColor: activeItem.openDocument ? "#aedef8" : "" }} onClick={undoBatch(() => activeItem.openDocument = !activeItem.openDocument)}>Open</div></Tooltip> + {isPresCollection ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Hide before presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideBefore ? "active" : ""}`} onClick={() => this.updateHideBefore(activeItem)}>Hide before</div></Tooltip>} + {isPresCollection ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Hide after presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideAfter ? "active" : ""}`} onClick={() => this.updateHideAfter(activeItem)}>Hide after</div></Tooltip>} + <Tooltip title={<><div className="dash-tooltip">{"Open document in a new tab"}</div></>}><div className="ribbon-toggle" style={{ backgroundColor: activeItem.openDocument ? PresColor.LightBlue : "" }} onClick={() => this.updateOpenDoc(activeItem)}>Open</div></Tooltip> </div> <div className="ribbon-doubleButton" > <div className="presBox-subheading">Slide Duration</div> <div className="ribbon-property"> <input className="presBox-input" type="number" value={duration} - // onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)} onChange={action((e) => this.setDurationTime(e.target.value))} /> s </div> <div className="ribbon-propertyUpDown"> - <div className="ribbon-propertyUpDownItem" onClick={() => this.setDurationTime(String(duration), 1000)}> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), 1000))}> <FontAwesomeIcon icon={"caret-up"} /> </div> - <div className="ribbon-propertyUpDownItem" onClick={() => this.setDurationTime(String(duration), -1000)}> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), -1000))}> <FontAwesomeIcon icon={"caret-down"} /> </div> </div> </div> - <input type="range" step="0.1" min="0.1" max="20" value={duration} style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "block" }} className={"toolbar-slider"} id="duration-slider" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setDurationTime(e.target.value); }} /> + <input type="range" step="0.1" min="0.1" max="20" value={duration} + style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "block" }} + className={"toolbar-slider"} id="duration-slider" + onPointerDown={() => { this._batch = UndoManager.StartBatch("presDuration"); }} + onPointerUp={() => { if (this._batch) this._batch.end(); }} + onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setDurationTime(e.target.value); }} + /> <div className={"slider-headers"} style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "grid" }}> <div className="slider-text">Short</div> <div className="slider-text">Medium</div> <div className="slider-text">Long</div> </div> </div> - <div className="ribbon-box"> + {isPresCollection ? (null) : <div className="ribbon-box"> Effects - <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openEffectDropdown = !this.openEffectDropdown; })} style={{ borderBottomLeftRadius: this.openEffectDropdown ? 0 : 5, border: this.openEffectDropdown ? 'solid 2px #5B9FDD' : 'solid 1px black' }}> + <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openEffectDropdown = !this.openEffectDropdown; })} style={{ borderBottomLeftRadius: this.openEffectDropdown ? 0 : 5, border: this.openEffectDropdown ? `solid 2px ${PresColor.DarkBlue}` : 'solid 1px black' }}> {effect} - <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openEffectDropdown ? '#5B9FDD' : 'black' }} icon={"angle-down"} /> + <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openEffectDropdown ? PresColor.DarkBlue : 'black' }} icon={"angle-down"} /> <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} style={{ display: this.openEffectDropdown ? "grid" : "none" }} onPointerDown={e => e.stopPropagation()}> - <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={undoBatch(() => targetDoc.presEffect = 'None')}>None</div> - <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={undoBatch(() => targetDoc.presEffect = 'Fade')}>Fade In</div> - <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={undoBatch(() => targetDoc.presEffect = 'Flip')}>Flip</div> - <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={undoBatch(() => targetDoc.presEffect = 'Rotate')}>Rotate</div> - <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={undoBatch(() => targetDoc.presEffect = 'Bounce')}>Bounce</div> - <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={undoBatch(() => targetDoc.presEffect = 'Roll')}>Roll</div> + <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.None || !targetDoc.presEffect ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.None)}>None</div> + <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Fade ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}>Fade In</div> + <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Flip ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Flip)}>Flip</div> + <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Rotate ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Rotate)}>Rotate</div> + <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Bounce ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Bounce)}>Bounce</div> + <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Roll ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Roll)}>Roll</div> </div> </div> <div className="ribbon-doubleButton" style={{ display: effect === 'None' ? "none" : "inline-flex" }}> @@ -938,17 +1174,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> </div> </div> <div className="effectDirection" style={{ display: effect === 'None' ? "none" : "grid", width: 40 }}> - <Tooltip title={<><div className="dash-tooltip">{"Enter from left"}</div></>}><div style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === "left" ? "#5a9edd" : "black", cursor: "pointer" }} onClick={undoBatch(() => targetDoc.presEffectDirection = 'left')}><FontAwesomeIcon icon={"angle-right"} /></div></Tooltip> - <Tooltip title={<><div className="dash-tooltip">{"Enter from right"}</div></>}><div style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === "right" ? "#5a9edd" : "black", cursor: "pointer" }} onClick={undoBatch(() => targetDoc.presEffectDirection = 'right')}><FontAwesomeIcon icon={"angle-left"} /></div></Tooltip> - <Tooltip title={<><div className="dash-tooltip">{"Enter from top"}</div></>}><div style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: targetDoc.presEffectDirection === "top" ? "#5a9edd" : "black", cursor: "pointer" }} onClick={undoBatch(() => targetDoc.presEffectDirection = 'top')}><FontAwesomeIcon icon={"angle-down"} /></div></Tooltip> - <Tooltip title={<><div className="dash-tooltip">{"Enter from bottom"}</div></>}><div style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: targetDoc.presEffectDirection === "bottom" ? "#5a9edd" : "black", cursor: "pointer" }} onClick={undoBatch(() => targetDoc.presEffectDirection = 'bottom')}><FontAwesomeIcon icon={"angle-up"} /></div></Tooltip> - <Tooltip title={<><div className="dash-tooltip">{"Enter from center"}</div></>}><div style={{ gridColumn: 2, gridRow: 2, width: 10, height: 10, alignSelf: 'center', justifySelf: 'center', border: targetDoc.presEffectDirection ? "solid 2px black" : "solid 2px #5a9edd", borderRadius: "100%", cursor: "pointer" }} onClick={undoBatch(() => targetDoc.presEffectDirection = false)}></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Enter from left"}</div></>}><div style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Left ? PresColor.LightBlue : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Left)}><FontAwesomeIcon icon={"angle-right"} /></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Enter from right"}</div></>}><div style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Right ? PresColor.LightBlue : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Right)}><FontAwesomeIcon icon={"angle-left"} /></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Enter from top"}</div></>}><div style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Top ? PresColor.LightBlue : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Top)}><FontAwesomeIcon icon={"angle-down"} /></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Enter from bottom"}</div></>}><div style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Bottom ? PresColor.LightBlue : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Bottom)}><FontAwesomeIcon icon={"angle-up"} /></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Enter from center"}</div></>}><div style={{ gridColumn: 2, gridRow: 2, width: 10, height: 10, alignSelf: 'center', justifySelf: 'center', border: targetDoc.presEffectDirection === PresEffect.Center || !targetDoc.presEffectDirection ? `solid 2px ${PresColor.LightBlue}` : "solid 2px black", borderRadius: "100%", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Center)}></div></Tooltip> </div> - </div> + </div>} <div className="ribbon-final-box"> - <div className={this._selectedArray.length === 0 ? "ribbon-final-button" : "ribbon-final-button-hidden"} onClick={() => this.applyTo(this._selectedArray)}> - Apply to selected - </div> <div className="ribbon-final-button-hidden" onClick={() => this.applyTo(this.childDocs)}> Apply to all </div> @@ -975,19 +1208,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> applyTo = (array: Doc[]) => { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; + this.updateMovement(activeItem.presMovement, true); + this.updateEffect(targetDoc.presEffect, true); + this.updateEffectDirection(targetDoc.presEffectDirection, true); array.forEach((doc) => { const curDoc = Cast(doc, Doc, null); const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); if (tagDoc && targetDoc) { curDoc.presTransition = activeItem.presTransition; curDoc.presDuration = activeItem.presDuration; - tagDoc.presEffect = targetDoc.presEffect; - tagDoc.presEffectDirection = targetDoc.presEffectDirection; - tagDoc.presMovement = targetDoc.presMovement; - curDoc.presMovement = activeItem.presMovement; - this.updateMovement(activeItem.presMovement, curDoc, tagDoc); - curDoc.presHideTillShownButton = activeItem.presHideTillShownButton; - curDoc.presHideAfterButton = activeItem.presHideAfterButton; + curDoc.presHideBefore = activeItem.presHideBefore; + curDoc.presHideAfter = activeItem.presHideAfter; } }); } @@ -1000,12 +1231,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div> <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> <div className="ribbon-box"> - <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.VID || targetDoc.type === DocumentType.AUDIO ? "inline-flex" : "none" }}> - <div className="ribbon-toggle" style={{ backgroundColor: activeItem.playAuto ? "#aedef8" : "" }} onClick={() => activeItem.playAuto = !activeItem.playAuto}>Play automatically</div> - <div className="ribbon-toggle" style={{ display: "flex", backgroundColor: activeItem.playAuto ? "" : "#aedef8" }} onClick={() => activeItem.playAuto = !activeItem.playAuto}>Play on next</div> + <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.AUDIO ? "inline-flex" : "none" }}> + <div className="ribbon-toggle" style={{ backgroundColor: activeItem.playAuto ? PresColor.LightBlue : "" }} onClick={() => activeItem.playAuto = !activeItem.playAuto}>Play automatically</div> + <div className="ribbon-toggle" style={{ display: "flex", backgroundColor: activeItem.playAuto ? "" : PresColor.LightBlue }} onClick={() => activeItem.playAuto = !activeItem.playAuto}>Play on next</div> </div> - {targetDoc.type === DocumentType.VID ? <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presVidFullScreen ? "#aedef8" : "" }} onClick={() => activeItem.presVidFullScreen = !activeItem.presVidFullScreen}>Full screen</div> : (null)} - {targetDoc.type === DocumentType.VID || targetDoc.type === DocumentType.AUDIO ? <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + {/* {targetDoc.type === DocumentType.VID ? <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presVidFullScreen ? PresColor.LightBlue : "" }} onClick={() => activeItem.presVidFullScreen = !activeItem.presVidFullScreen}>Full screen</div> : (null)} */} + {targetDoc.type === DocumentType.AUDIO ? <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> <div className="presBox-subheading">Start time</div> <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> <input className="presBox-input" @@ -1014,7 +1245,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { activeItem.presStartTime = Number(e.target.value); })} /> </div> </div> : (null)} - {targetDoc.type === DocumentType.VID || targetDoc.type === DocumentType.AUDIO ? <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + {targetDoc.type === DocumentType.AUDIO ? <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> <div className="presBox-subheading">End time</div> <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> <input className="presBox-input" @@ -1023,13 +1254,45 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presEndTime = Number(val); })} /> </div> </div> : (null)} - {targetDoc.type === DocumentType.COL ? 'Presentation Pin View' : (null)} - <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.COL ? "inline-flex" : "none" }}> - <div className="ribbon-toggle" style={{ width: 20, padding: 0, backgroundColor: activeItem.presPinView ? "#aedef8" : "" }} + {this.panable || this.scrollable || this.targetDoc.type === DocumentType.COMPARISON ? 'Pinned view' : (null)} + <div className="ribbon-doubleButton"> + <Tooltip title={<><div className="dash-tooltip">{activeItem.presPinView ? "Turn off pin with view" : "Turn on pin with view"}</div></>}><div className="ribbon-toggle" style={{ width: 20, padding: 0, backgroundColor: activeItem.presPinView ? PresColor.LightBlue : "" }} onClick={() => { activeItem.presPinView = !activeItem.presPinView; targetDoc.presPinView = activeItem.presPinView; if (activeItem.presPinView) { + if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking) { + const scroll = targetDoc._scrollTop; + activeItem.presPinView = true; + activeItem.presPinViewScroll = scroll; + } else if (targetDoc.type === DocumentType.VID) { + activeItem.presPinTimecode = targetDoc._currentTimecode; + } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG) { + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeItem.presPinView = true; + activeItem.presPinViewX = x; + activeItem.presPinViewY = y; + activeItem.presPinViewScale = scale; + } else if (targetDoc.type === DocumentType.COMPARISON) { + const width = targetDoc._clipWidth; + activeItem.presPinClipWidth = width; + activeItem.presPinView = true; + } + } + }}>{presPinWithViewIcon}</div></Tooltip> + {activeItem.presPinView ? <Tooltip title={<><div className="dash-tooltip">{"Update the pinned view with the view of the selected document"}</div></>}><div className="ribbon-button" + onClick={() => { + if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) { + const scroll = targetDoc._scrollTop; + activeItem.presPinViewScroll = scroll; + } else if (targetDoc.type === DocumentType.VID) { + activeItem.presPinTimecode = targetDoc._currentTimecode; + } else if (targetDoc.type === DocumentType.COMPARISON) { + const clipWidth = targetDoc._clipWidth; + activeItem.presPinClipWidth = clipWidth; + } else { const x = targetDoc._panX; const y = targetDoc._panY; const scale = targetDoc._viewScale; @@ -1037,18 +1300,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> activeItem.presPinViewY = y; activeItem.presPinViewScale = scale; } - }}>{presPinWithViewIcon}</div> - {activeItem.presPinView ? <div className="ribbon-button" - onClick={() => { - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeItem.presPinViewX = x; - activeItem.presPinViewY = y; - activeItem.presPinViewScale = scale; - }}>Update</div> : (null)} + }}>Update</div></Tooltip> : (null)} </div> - <div style={{ display: activeItem.presPinView ? "block" : "none" }}> + {this.panable ? <div style={{ display: activeItem.presPinView ? "block" : "none" }}> <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> <div className="presBox-subheading">Pan X</div> <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> @@ -1076,7 +1330,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScale = Number(val); })} /> </div> </div> - </div> + </div> : (null)} + {this.scrollable ? <div style={{ display: activeItem.presPinView ? "block" : "none" }}> + <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">Scroll</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" value={NumCast(activeItem.presPinViewScroll)} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScroll = Number(val); })} /> + </div> + </div> + </div> : (null)} {/* <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.WEB ? "inline-flex" : "none" }}> <div className="ribbon-toggle" onClick={this.progressivizeText}>Store original website</div> </div> */} @@ -1092,15 +1357,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div> <div className={'presBox-toolbar-dropdown'} style={{ display: this.newDocumentTools && this.layoutDoc.presStatus === "edit" ? "inline-flex" : "none" }} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> <div className="layout-container" style={{ height: 'max-content' }}> - <div className="layout" style={{ border: this.layout === 'blank' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => { this.layout = 'blank'; this.createNewSlide(this.layout); })} /> - <div className="layout" style={{ border: this.layout === 'title' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => { this.layout = 'title'; this.createNewSlide(this.layout); })}> + <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => { this.layout = 'blank'; this.createNewSlide(this.layout); })} /> + <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => { this.layout = 'title'; this.createNewSlide(this.layout); })}> <div className="title">Title</div> <div className="subtitle">Subtitle</div> </div> - <div className="layout" style={{ border: this.layout === 'header' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => { this.layout = 'header'; this.createNewSlide(this.layout); })}> + <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => { this.layout = 'header'; this.createNewSlide(this.layout); })}> <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>Section header</div> </div> - <div className="layout" style={{ border: this.layout === 'content' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => { this.layout = 'content'; this.createNewSlide(this.layout); })}> + <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => { this.layout = 'content'; this.createNewSlide(this.layout); })}> <div className="title" style={{ alignSelf: 'center' }}>Title</div> <div className="content">Text goes here</div> </div> @@ -1132,26 +1397,26 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div className="ribbon-box"> Choose type: <div className="ribbon-doubleButton"> - <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? "" : "#aedef8" }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Text</div> - <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? "#aedef8" : "" }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Freeform</div> + <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? "" : PresColor.LightBlue }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Text</div> + <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? PresColor.LightBlue : "" }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Freeform</div> </div> </div> <div className="ribbon-box" style={{ display: this.addFreeform ? "grid" : "none" }}> Preset layouts: <div className="layout-container" style={{ height: this.openLayouts ? 'max-content' : '75px' }}> - <div className="layout" style={{ border: this.layout === 'blank' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => this.layout = 'blank')} /> - <div className="layout" style={{ border: this.layout === 'title' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => this.layout = 'title')}> + <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => this.layout = 'blank')} /> + <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => this.layout = 'title')}> <div className="title">Title</div> <div className="subtitle">Subtitle</div> </div> - <div className="layout" style={{ border: this.layout === 'header' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => this.layout = 'header')}> + <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => this.layout = 'header')}> <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>Section header</div> </div> - <div className="layout" style={{ border: this.layout === 'content' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => this.layout = 'content')}> + <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => this.layout = 'content')}> <div className="title" style={{ alignSelf: 'center' }}>Title</div> <div className="content">Text goes here</div> </div> - <div className="layout" style={{ border: this.layout === 'twoColumns' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => this.layout = 'twoColumns')}> + <div className="layout" style={{ border: this.layout === 'twoColumns' ? `solid 2px ${PresColor.DarkBlue}` : '' }} onClick={action(() => this.layout = 'twoColumns')}> <div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}>Title</div> <div className="content" style={{ gridColumn: 1, gridRow: 2 }}>Column one text</div> <div className="content" style={{ gridColumn: 2, gridRow: 2 }}>Column two text</div> @@ -1183,7 +1448,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> if (data && presData) { data.push(doc); TabDocView.PinDoc(doc, false); - this.gotoDocument(this.childDocs.length, this.itemIndex); + this.gotoDocument(this.childDocs.length); } else { this.props.addDocTab(doc, "add:right"); } @@ -1233,10 +1498,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @computed get presentDropdown() { return ( <div className={`dropdown-play ${this.presentTools ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> - <div className="dropdown-play-button" onClick={(action(() => { this.updateMinimize(); this.turnOffEdit(true); }))}> + <div className="dropdown-play-button" onClick={undoBatch(action(() => { this.updateMinimize(); this.turnOffEdit(true); }))}> Minimize </div> - <div className="dropdown-play-button" onClick={(action(() => { this.layoutDoc.presStatus = "manual"; this.turnOffEdit(true); }))}> + <div className="dropdown-play-button" onClick={undoBatch(action(() => { this.layoutDoc.presStatus = "manual"; this.turnOffEdit(true); }))}> Sidebar view </div> </div> @@ -1244,7 +1509,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } // Case in which the document has keyframes to navigate to next key frame - @undoBatch @action nextKeyframe = (tagDoc: Doc, curDoc: Doc): void => { const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]); @@ -1260,7 +1524,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> tagDoc.lastFrame = Math.max(NumCast(tagDoc._currentFrame), NumCast(tagDoc.lastFrame)); } - @undoBatch @action prevKeyframe = (tagDoc: Doc, actItem: Doc): void => { const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]); @@ -1312,8 +1575,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> {/* <div className="ribbon-box"> {this.stringType} selected <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG || targetDoc.type === DocumentType.RTF ? "inline-flex" : "none" }}> - <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeChild}>Contents</div> - <div className="ribbon-toggle" style={{ opacity: activeItem.presProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editProgressivize ? "#aedef8" : "" }} onClick={this.editProgressivize}>Edit</div> + <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presProgressivize ? PresColor.LightBlue : "" }} onClick={this.progressivizeChild}>Contents</div> + <div className="ribbon-toggle" style={{ opacity: activeItem.presProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editProgressivize ? PresColor.LightBlue : "" }} onClick={this.editProgressivize}>Edit</div> </div> <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? "inline-flex" : "none" }}> <div className="presBox-subheading">Active text color</div> @@ -1328,12 +1591,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> </div> {this.viewedColorPicker} <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG ? "inline-flex" : "none" }}> - <div className="ribbon-toggle" style={{ backgroundColor: activeItem.zoomProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeZoom}>Zoom</div> - <div className="ribbon-toggle" style={{ opacity: activeItem.zoomProgressivize ? 1 : 0.4, backgroundColor: activeItem.editZoomProgressivize ? "#aedef8" : "" }} onClick={this.editZoomProgressivize}>Edit</div> + <div className="ribbon-toggle" style={{ backgroundColor: activeItem.zoomProgressivize ? PresColor.LightBlue : "" }} onClick={this.progressivizeZoom}>Zoom</div> + <div className="ribbon-toggle" style={{ opacity: activeItem.zoomProgressivize ? 1 : 0.4, backgroundColor: activeItem.editZoomProgressivize ? PresColor.LightBlue : "" }} onClick={this.editZoomProgressivize}>Edit</div> </div> <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: targetDoc._viewType === "stacking" || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF ? "inline-flex" : "none" }}> - <div className="ribbon-toggle" style={{ backgroundColor: activeItem.scrollProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeScroll}>Scroll</div> - <div className="ribbon-toggle" style={{ opacity: activeItem.scrollProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editScrollProgressivize ? "#aedef8" : "" }} onClick={this.editScrollProgressivize}>Edit</div> + <div className="ribbon-toggle" style={{ backgroundColor: activeItem.scrollProgressivize ? PresColor.LightBlue : "" }} onClick={this.progressivizeScroll}>Scroll</div> + <div className="ribbon-toggle" style={{ opacity: activeItem.scrollProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editScrollProgressivize ? PresColor.LightBlue : "" }} onClick={this.editScrollProgressivize}>Edit</div> </div> </div> */} <div className="ribbon-final-box"> @@ -1343,7 +1606,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div key="back" title="back frame" className="backKeyframe" onClick={e => { e.stopPropagation(); this.prevKeyframe(targetDoc, activeItem); }}> <FontAwesomeIcon icon={"caret-left"} size={"lg"} /> </div> - <div key="num" title="toggle view all" className="numKeyframe" style={{ color: targetDoc.editing ? "white" : "black", backgroundColor: targetDoc.editing ? "#5B9FDD" : "#AEDDF8" }} + <div key="num" title="toggle view all" className="numKeyframe" style={{ color: targetDoc.editing ? "white" : "black", backgroundColor: targetDoc.editing ? PresColor.DarkBlue : PresColor.LightBlue }} onClick={action(() => targetDoc.editing = !targetDoc.editing)} > {NumCast(targetDoc._currentFrame)} </div> @@ -1357,7 +1620,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> {this.frameListHeader} {this.frameList} </div> - <div className="ribbon-toggle" style={{ height: 20, backgroundColor: "#AEDDF8" }} onClick={() => console.log(" TODO: play frames")}>Play</div> + <div className="ribbon-toggle" style={{ height: 20, backgroundColor: PresColor.LightBlue }} onClick={() => console.log(" TODO: play frames")}>Play</div> </div> </div> </div> @@ -1406,12 +1669,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @action turnOffEdit = (paths?: boolean) => { + // Turn off paths if (paths) { - // Turn off paths const srcContext = Cast(this.rootDoc.presCollection, Doc, null); if (srcContext) this.togglePath(srcContext, true); } - // Turn off the progressivize editors for each + // Turn off the progressivize editors for each document this.childDocs.forEach((doc) => { doc.editSnapZoomProgressivize = false; doc.editZoomProgressivize = false; @@ -1573,7 +1836,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> tags.push(<div style={{ position: 'absolute', display: doc.displayMovement ? "block" : "none" }}>{this.checkMovementLists(doc, doc["x-indexed"], doc["y-indexed"])}</div>); } tags.push( - <div className="progressivizeButton" key={index} onPointerLeave={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; }} onPointerOver={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; }} onClick={e => { this.toggleDisplayMovement(doc); e.stopPropagation(); }} style={{ backgroundColor: doc.displayMovement ? "#aedff8" : "#c8c8c8", top: NumCast(doc.y), left: NumCast(doc.x) }}> + <div className="progressivizeButton" key={index} onPointerLeave={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; }} onPointerOver={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; }} onClick={e => { this.toggleDisplayMovement(doc); e.stopPropagation(); }} style={{ backgroundColor: doc.displayMovement ? PresColor.LightBlue : "#c8c8c8", top: NumCast(doc.y), left: NumCast(doc.x) }}> <div className="progressivizeButton-prev"><FontAwesomeIcon icon={"caret-left"} size={"lg"} onClick={e => { e.stopPropagation(); this.prevAppearFrame(doc, index); }} /></div> <div className="progressivizeButton-frame">{doc.appearFrame}</div> <div className="progressivizeButton-next"><FontAwesomeIcon icon={"caret-right"} size={"lg"} onClick={e => { e.stopPropagation(); this.nextAppearFrame(doc, index); }} /></div> @@ -1582,7 +1845,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> return tags; } - @undoBatch @action nextAppearFrame = (doc: Doc, i: number): void => { // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); @@ -1595,7 +1857,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> this.updateOpacityList(doc["opacity-indexed"], NumCast(doc.appearFrame)); } - @undoBatch @action prevAppearFrame = (doc: Doc, i: number): void => { // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); @@ -1656,30 +1917,42 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const propIcon = CurrentUserUtils.propertiesWidth > 0 ? "angle-double-right" : "angle-double-left"; const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Presentation Panel" : "Open Presentation Panel"; const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; + const isMini: boolean = this.toolbarWidth <= 100; + const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.UserDoc().activePresentation); return (mode === CollectionViewType.Carousel3D) ? (null) : ( - <div id="toolbarContainer" className={'presBox-toolbar'} style={{ display: this.layoutDoc.presStatus === "edit" ? "inline-flex" : "none" }}> + <div id="toolbarContainer" className={'presBox-toolbar'}> {/* <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}> <FontAwesomeIcon icon={"plus"} /> <FontAwesomeIcon className={`dropdown ${this.newDocumentTools ? "active" : ""}`} icon={"angle-down"} /> </div></Tooltip> */} <Tooltip title={<><div className="dash-tooltip">{"View paths"}</div></>}> - <div style={{ opacity: this.childDocs.length > 1 ? 1 : 0.3 }} className={`toolbar-button ${this.pathBoolean ? "active" : ""}`} onClick={this.childDocs.length > 1 ? this.viewPaths : undefined}> + <div style={{ opacity: this.childDocs.length > 1 ? 1 : 0.3, color: this._pathBoolean ? PresColor.DarkBlue : 'white', width: isMini ? "100%" : undefined }} className={"toolbar-button"} onClick={this.childDocs.length > 1 ? this.viewPaths : undefined}> <FontAwesomeIcon icon={"exchange-alt"} /> </div> </Tooltip> - <div className="toolbar-divider" /> - <Tooltip title={<><div className="dash-tooltip">{this.rootDoc.expandBoolean ? "Minimize all" : "Expand all"}</div></>}> - <div className={`toolbar-button ${this.rootDoc.expandBoolean ? "active" : ""}`} onClick={this.toggleExpandMode}> - {/* <FontAwesomeIcon icon={this.rootDoc.expandBoolean ? "eye-slash" : "eye"} /> */} - <FontAwesomeIcon icon={"eye"} /> - </div> - </Tooltip> - <div className="toolbar-divider" /> - <Tooltip title={<><div className="dash-tooltip">{propTitle}</div></>}> - <div className="toolbar-button" style={{ position: 'absolute', right: 4, fontSize: 16 }} onClick={this.toggleProperties}> - <FontAwesomeIcon className={"toolbar-thumbtack"} icon={propIcon} style={{ color: CurrentUserUtils.propertiesWidth > 0 ? '#AEDDF8' : 'white' }} /> - </div> - </Tooltip> + {isMini ? (null) : + <> + <div className="toolbar-divider" /> + {/* <Tooltip title={<><div className="dash-tooltip">{this._expandBoolean ? "Minimize all" : "Expand all"}</div></>}> + <div className={"toolbar-button"} + style={{ color: this._expandBoolean ? PresColors.DarkBlue : 'white' }} + onClick={this.toggleExpandMode}> + <FontAwesomeIcon icon={"eye"} /> + </div> + </Tooltip> + <div className="toolbar-divider" /> */} + <Tooltip title={<><div className="dash-tooltip">{presKeyEvents ? "Keys are active" : "Keys are not active - click anywhere on the presentation trail to activate keys"}</div></>}> + <div className="toolbar-button" style={{ cursor: presKeyEvents ? 'default' : 'pointer', position: 'absolute', right: 30, fontSize: 16 }}> + <FontAwesomeIcon className={"toolbar-thumbtack"} icon={"keyboard"} style={{ color: presKeyEvents ? PresColor.DarkBlue : 'white' }} /> + </div> + </Tooltip> + <Tooltip title={<><div className="dash-tooltip">{propTitle}</div></>}> + <div className="toolbar-button" style={{ position: 'absolute', right: 4, fontSize: 16 }} onClick={this.toggleProperties}> + <FontAwesomeIcon className={"toolbar-thumbtack"} icon={propIcon} style={{ color: CurrentUserUtils.propertiesWidth > 0 ? PresColor.DarkBlue : 'white' }} /> + </div> + </Tooltip> + </> + } </div> ); } @@ -1691,25 +1964,26 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> */ @computed get topPanel() { const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; + const isMini: boolean = this.toolbarWidth <= 100; return ( <div className="presBox-buttons" style={{ display: this.rootDoc._chromeStatus === "disabled" ? "none" : undefined }}> - <select className="presBox-viewPicker" + {isMini ? (null) : <select className="presBox-viewPicker" style={{ display: this.layoutDoc.presStatus === "edit" ? "block" : "none" }} onPointerDown={e => e.stopPropagation()} onChange={this.viewChanged} value={mode}> <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Stacking}>List</option> <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel3D}>3D Carousel</option> - </select> - <div className="presBox-presentPanel" style={{ opacity: this.childDocs.length > 0 ? 1 : 0.3 }}> + </select>} + <div className="presBox-presentPanel" style={{ opacity: this.childDocs.length ? 1 : 0.3 }}> <span className={`presBox-button ${this.layoutDoc.presStatus === "edit" ? "present" : ""}`}> - <div className="presBox-button-left" onClick={() => (this.childDocs.length > 0) && (this.layoutDoc.presStatus = "manual")}> + <div className="presBox-button-left" onClick={undoBatch(() => (this.childDocs.length) && (this.layoutDoc.presStatus = "manual"))}> <FontAwesomeIcon icon={"play-circle"} /> <div style={{ display: this.props.PanelWidth() > 200 ? "inline-flex" : "none" }}> Present</div> </div> - {(mode === CollectionViewType.Carousel3D) ? (null) : <div className={`presBox-button-right ${this.presentTools ? "active" : ""}`} + {(mode === CollectionViewType.Carousel3D || isMini) ? (null) : <div className={`presBox-button-right ${this.presentTools ? "active" : ""}`} onClick={(action(() => { - if (this.childDocs.length > 0) this.presentTools = !this.presentTools; + if (this.childDocs.length) this.presentTools = !this.presentTools; }))}> <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this.presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={"angle-down"} /> {this.presentDropdown} @@ -1755,7 +2029,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @computed get frameListHeader() { return (<div className="frameList-header"> - Frames + Frames {this.panable ? <i>Panable</i> : this.scrollable ? <i>Scrollable</i> : (null)} <div className={"frameList-headerButtons"}> <Tooltip title={<><div className="dash-tooltip">{"Add frame by example"}</div></>}><div className={"headerButton"} onClick={e => { e.stopPropagation(); this.newFrame(); }}> <FontAwesomeIcon icon={"plus"} onPointerDown={e => e.stopPropagation()} /> @@ -1801,21 +2075,27 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } @computed get playButtons() { + const presEnd: boolean = !this.layoutDoc.presLoop && (this.itemIndex === this.childDocs.length - 1); + const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0); // Case 1: There are still other frames and should go through all frames before going to next slide return (<div className="presPanelOverlay" style={{ display: this.layoutDoc.presStatus !== "edit" ? "inline-flex" : "none" }}> - <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? '#AEDDF8' : 'white' }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? PresColor.DarkBlue : 'white' }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip> <div className="presPanel-divider"></div> - <div className="presPanel-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></div> - <Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === "auto" ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div></Tooltip> - <div className="presPanel-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></div> + <div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} onClick={() => { this.back(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}><FontAwesomeIcon icon={"arrow-left"} /></div> + <Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === PresStatus.Autoplay ? "pause" : "play"} /></div></Tooltip> + <div className="presPanel-button" style={{ opacity: presEnd ? 0.4 : 1 }} onClick={() => { this.next(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}><FontAwesomeIcon icon={"arrow-right"} /></div> <div className="presPanel-divider"></div> - <div className="presPanel-button-text" style={{ display: this.props.PanelWidth() > 250 ? "inline-flex" : "none" }}> + <Tooltip title={<><div className="dash-tooltip">{"Click to return to 1st slide"}</div></>}><div className="presPanel-button" style={{ border: 'solid 1px white' }} onClick={() => this.gotoDocument(0)}><b>1</b></div></Tooltip> + <div + className="presPanel-button-text" + onClick={() => this.gotoDocument(0)} + style={{ display: this.props.PanelWidth() > 250 ? "inline-flex" : "none" }}> Slide {this.itemIndex + 1} / {this.childDocs.length} {this.playButtonFrames} </div> <div className="presPanel-divider"></div> - {this.props.PanelWidth() > 250 ? <div className="presPanel-button-text" onClick={() => { this.layoutDoc.presStatus = "edit"; clearTimeout(this._presTimer); }}>EXIT</div> - : <div className="presPanel-button" onClick={() => this.layoutDoc.presStatus = "edit"}> + {this.props.PanelWidth() > 250 ? <div className="presPanel-button-text" onClick={undoBatch(action(() => { this.layoutDoc.presStatus = "edit"; clearTimeout(this._presTimer); }))}>EXIT</div> + : <div className="presPanel-button" onClick={undoBatch(action(() => this.layoutDoc.presStatus = "edit"))}> <FontAwesomeIcon icon={"times"} /> </div>} </div>); @@ -1823,7 +2103,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @action startOrPause = () => { - if (this.layoutDoc.presStatus === "manual" || this.layoutDoc.presStatus === "edit") this.startAutoPres(this.itemIndex); + if (this.layoutDoc.presStatus === PresStatus.Manual || this.layoutDoc.presStatus === PresStatus.Edit) this.startAutoPres(this.itemIndex); else this.pauseAutoPres(); } @@ -1833,25 +2113,29 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> // needed to ensure that the childDocs are loaded for looking up fields this.childDocs.slice(); const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; - return this.layoutDoc.inOverlay ? + const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.UserDoc().activePresentation); + const presEnd: boolean = !this.layoutDoc.presLoop && (this.itemIndex === this.childDocs.length - 1); + const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0); + return CurrentUserUtils.OverlayDocs.includes(this.rootDoc) ? <div className="miniPres"> - <div className="presPanelOverlay" style={{ display: "inline-flex", height: 35, background: '#323232', top: 0, zIndex: 3000000 }}> - <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? '#AEDDF8' : undefined }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip> + <div className="presPanelOverlay" style={{ display: "inline-flex", height: 30, background: '#323232', top: 0, zIndex: 3000000, boxShadow: presKeyEvents ? '0 0 0px 3px ' + PresColor.DarkBlue : undefined }}> + <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? PresColor.DarkBlue : undefined }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip> <div className="presPanel-divider"></div> - <div className="presPanel-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></div> - <Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === "auto" ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div></Tooltip> - <div className="presPanel-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></div> + <div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} onClick={() => { this.back(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}><FontAwesomeIcon icon={"arrow-left"} /></div> + <Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div></Tooltip> + <div className="presPanel-button" style={{ opacity: presEnd ? 0.4 : 1 }} onClick={() => { this.next(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}><FontAwesomeIcon icon={"arrow-right"} /></div> <div className="presPanel-divider"></div> + <Tooltip title={<><div className="dash-tooltip">{"Click to return to 1st slide"}</div></>}><div className="presPanel-button" style={{ border: 'solid 1px white' }} onClick={() => this.gotoDocument(0)}><b>1</b></div></Tooltip> <div className="presPanel-button-text"> Slide {this.itemIndex + 1} / {this.childDocs.length} {this.playButtonFrames} </div> <div className="presPanel-divider"></div> - <div className="presPanel-button-text" onClick={() => { this.updateMinimize(); this.layoutDoc.presStatus = "edit"; clearTimeout(this._presTimer); }}>EXIT</div> + <div className="presPanel-button-text" onClick={undoBatch(action(() => { this.updateMinimize(); this.layoutDoc.presStatus = PresStatus.Edit; clearTimeout(this._presTimer); }))}>EXIT</div> </div> </div> : - <div className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined }} > + <div className="presBox-cont" style={{ minWidth: CurrentUserUtils.OverlayDocs.includes(this.layoutDoc) ? 240 : undefined }} > {this.topPanel} {this.toolbar} {this.newDocumentToolbarDropdown} @@ -1878,7 +2162,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } Scripting.addGlobal(function lookupPresBoxField(container: Doc, field: string, data: Doc) { if (field === 'indexInPres') return DocListCast(container[StrCast(container.presentationFieldKey)]).indexOf(data); - if (field === 'presCollapsedHeight') return container._viewType === CollectionViewType.Stacking ? 30 : 26; + if (field === 'presCollapsedHeight') return container._viewType === CollectionViewType.Stacking ? 35 : 31; if (field === 'presStatus') return container.presStatus; if (field === '_itemIndex') return container._itemIndex; if (field === 'presBox') return container; diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 16ce749bc..f467fef12 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -3,7 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, IReactionDisposer, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as rp from 'request-promise'; -import { Doc } from "../../../fields/Doc"; +import { Doc, WidthSym } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; import { InkTool } from "../../../fields/InkField"; import { listSpec, makeInterface } from "../../../fields/Schema"; @@ -34,12 +34,12 @@ export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, Screensh videoLoad = () => { const aspect = this.player!.videoWidth / this.player!.videoHeight; - const nativeWidth = (this.layoutDoc._nativeWidth || 0); - const nativeHeight = (this.layoutDoc._nativeHeight || 0); + const nativeWidth = Doc.NativeWidth(this.layoutDoc); + const nativeHeight = Doc.NativeHeight(this.layoutDoc); if (!nativeWidth || !nativeHeight) { - if (!this.layoutDoc._nativeWidth) this.layoutDoc._nativeWidth = 400; - this.layoutDoc._nativeHeight = NumCast(this.layoutDoc._nativeWidth) / aspect; - this.layoutDoc._height = NumCast(this.layoutDoc._width) / aspect; + if (!nativeWidth) Doc.SetNativeWidth(this.dataDoc, 1200); + Doc.SetNativeHeight(this.dataDoc, (nativeWidth || 1200) / aspect); + this.layoutDoc._height = (this.layoutDoc[WidthSym]() || 0) / aspect; } } @@ -48,7 +48,7 @@ export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, Screensh const height = NumCast(this.layoutDoc._height); const canvas = document.createElement('canvas'); canvas.width = 640; - canvas.height = 640 * NumCast(this.layoutDoc._nativeHeight) / NumCast(this.layoutDoc._nativeWidth, 1); + canvas.height = 640 / (Doc.NativeAspect(this.layoutDoc) || 1); const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions if (ctx) { ctx.rect(0, 0, canvas.width, canvas.height); diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index 0c0854ac2..05714f665 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -1,5 +1,8 @@ .videoBox { transform-origin: top left; + width: 100%; + height: 100%; + position: relative; .videoBox-viewer { opacity: 0.99; // hack! overcomes some kind of Chrome weirdness where buttons (e.g., snapshot) disappear at some point as the video is resized larger } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 998ddde9a..bc69a3954 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -6,7 +6,7 @@ import * as rp from 'request-promise'; import { Doc } from "../../../fields/Doc"; import { InkTool } from "../../../fields/InkField"; import { createSchema, makeInterface } from "../../../fields/Schema"; -import { Cast, StrCast } from "../../../fields/Types"; +import { Cast, StrCast, NumCast } from "../../../fields/Types"; import { VideoField } from "../../../fields/URLField"; import { Utils, emptyFunction, returnOne, returnZero, OmitKeys } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; @@ -20,6 +20,7 @@ import "./VideoBox.scss"; import { documentSchema } from "../../../fields/documentSchemas"; import { Networking } from "../../Network"; import { SnappingManager } from "../../util/SnappingManager"; +import { SelectionManager } from "../../util/SelectionManager"; const path = require('path'); export const timeSchema = createSchema({ @@ -51,30 +52,44 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD videoLoad = () => { const aspect = this.player!.videoWidth / this.player!.videoHeight; - this.layoutDoc._nativeWidth = this.player!.videoWidth; - this.layoutDoc._nativeHeight = (this.layoutDoc._nativeWidth || 0) / aspect; + Doc.SetNativeWidth(this.dataDoc, this.player!.videoWidth); + Doc.SetNativeHeight(this.dataDoc, this.player!.videoHeight); this.layoutDoc._height = (this.layoutDoc._width || 0) / aspect; - this.dataDoc[this.fieldKey + "-" + "duration"] = this.player!.duration; + this.dataDoc[this.fieldKey + "-duration"] = this.player!.duration; } @action public Play = (update: boolean = true) => { this._playing = true; - update && this.player?.play(); - update && this._youtubePlayer?.playVideo(); - this._youtubePlayer && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 5)); + try { + update && this.player?.play(); + update && this._youtubePlayer?.playVideo(); + this._youtubePlayer && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 5)); + } catch (e) { + console.log("Video Play Exception:", e); + } this.updateTimecode(); } @action public Seek(time: number) { - this._youtubePlayer?.seekTo(Math.round(time), true); + try { + this._youtubePlayer?.seekTo(Math.round(time), true); + } catch (e) { + console.log("Video Seek Exception:", e); + } this.player && (this.player.currentTime = time); } @action public Pause = (update: boolean = true) => { this._playing = false; - update && this.player?.pause(); - update && this._youtubePlayer?.pauseVideo(); - this._youtubePlayer && this._playTimer && clearInterval(this._playTimer); + try { + update && this.player?.pause(); + update && this._youtubePlayer?.pauseVideo(); + this._youtubePlayer && this._playTimer && clearInterval(this._playTimer); + this._youtubePlayer?.seekTo(this._youtubePlayer?.getCurrentTime(), true); + } catch (e) { + console.log("Video Pause Exception:", e); + } + this._youtubePlayer && SelectionManager.DeselectAll(); // if we don't deselect the player, then we get an annoying YouTube spinner I guess telling us we're paused. this._playTimer = undefined; this.updateTimecode(); } @@ -82,7 +97,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD @action public FullScreen() { this._fullScreen = true; this.player && this.player.requestFullscreen(); - this._youtubePlayer && this.props.addDocTab(this.rootDoc, "add"); + try { + this._youtubePlayer && this.props.addDocTab(this.rootDoc, "add"); + } catch (e) { + console.log("Video FullScreen Exception:", e); + } } choosePath(url: string) { @@ -97,7 +116,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD const height = (this.layoutDoc._height || 0); const canvas = document.createElement('canvas'); canvas.width = 640; - canvas.height = 640 * (this.layoutDoc._nativeHeight || 0) / (this.layoutDoc._nativeWidth || 1); + canvas.height = 640 * Doc.NativeHeight(this.layoutDoc) / (Doc.NativeWidth(this.layoutDoc) || 1); const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions if (ctx) { // ctx.rect(0, 0, canvas.width, canvas.height); @@ -129,25 +148,22 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' // if you want to preview the captured image, const filename = path.basename(encodeURIComponent("snapshot" + StrCast(this.rootDoc.title).replace(/\..*$/, "") + "_" + (this.layoutDoc._currentTimecode || 0).toString().replace(/\./, "_"))); - VideoBox.convertDataUri(dataUrl, filename).then((returnedFilename: string) => { - if (returnedFilename) { - this.createRealSummaryLink(returnedFilename); - } - }); + VideoBox.convertDataUri(dataUrl, filename).then((returnedFilename: string) => + returnedFilename && this.createRealSummaryLink(returnedFilename)); } } private createRealSummaryLink = (relative: string) => { const url = this.choosePath(Utils.prepend(relative)); - const width = (this.layoutDoc._width || 0); - const height = (this.layoutDoc._height || 0); + const width = this.layoutDoc._width || 0; + const height = this.layoutDoc._height || 0; const imageSummary = Docs.Create.ImageDocument(url, { - _nativeWidth: this.layoutDoc._nativeWidth, _nativeHeight: this.layoutDoc._nativeHeight, + _nativeWidth: Doc.NativeWidth(this.layoutDoc), _nativeHeight: Doc.NativeHeight(this.layoutDoc), x: (this.layoutDoc.x || 0) + width, y: (this.layoutDoc.y || 0), _width: 150, _height: height / width * 150, title: "--snapshot" + (this.layoutDoc._currentTimecode || 0) + " image-" }); - Doc.GetProto(imageSummary)["data-nativeWidth"] = this.layoutDoc._nativeWidth; - Doc.GetProto(imageSummary)["data-nativeHeight"] = this.layoutDoc._nativeHeight; + Doc.SetNativeWidth(Doc.GetProto(imageSummary), Doc.NativeWidth(this.layoutDoc)); + Doc.SetNativeHeight(Doc.GetProto(imageSummary), Doc.NativeHeight(this.layoutDoc)); imageSummary.isLinkButton = true; this.props.addDocument?.(imageSummary); DocUtils.MakeLink({ doc: imageSummary }, { doc: this.rootDoc }, "video snapshot"); @@ -156,7 +172,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD @action updateTimecode = () => { this.player && (this.layoutDoc._currentTimecode = this.player.currentTime); - this._youtubePlayer && (this.layoutDoc._currentTimecode = this._youtubePlayer.getCurrentTime()); + try { + this._youtubePlayer && (this.layoutDoc._currentTimecode = this._youtubePlayer.getCurrentTime?.()); + } catch (e) { + console.log("Video Timecode Exception:", e); + } } componentDidMount() { @@ -164,11 +184,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD if (this.youtubeVideoId) { const youtubeaspect = 400 / 315; - const nativeWidth = (this.layoutDoc._nativeWidth || 0); - const nativeHeight = (this.layoutDoc._nativeHeight || 0); + const nativeWidth = Doc.NativeWidth(this.layoutDoc); + const nativeHeight = Doc.NativeHeight(this.layoutDoc); if (!nativeWidth || !nativeHeight) { - if (!this.layoutDoc._nativeWidth) this.layoutDoc._nativeWidth = 600; - this.layoutDoc._nativeHeight = (this.layoutDoc._nativeWidth || 0) / youtubeaspect; + if (!nativeWidth) Doc.SetNativeWidth(this.dataDoc, 600); + Doc.SetNativeHeight(this.dataDoc, (nativeWidth || 600) / youtubeaspect); this.layoutDoc._height = (this.layoutDoc._width || 0) / youtubeaspect; } } @@ -258,7 +278,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD } else this._youtubeContentCreated = false; - const iframe = e.target; + this.loadYouTube(e.target); + } + private loadYouTube = (iframe: any) => { let started = true; const onYoutubePlayerStateChange = (event: any) => runInAction(() => { if (started && event.data === YT.PlayerState.PLAYING) { @@ -278,13 +300,17 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD () => !this.props.Document.isAnnotating && Doc.GetSelectedTool() === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting, (interactive) => iframe.style.pointerEvents = interactive ? "all" : "none", { fireImmediately: true }); }; - this._youtubePlayer = new YT.Player(`${this.youtubeVideoId + this._youtubeIframeId}-player`, { - events: { - 'onReady': this.props.dontRegisterView ? undefined : onYoutubePlayerReady, - 'onStateChange': this.props.dontRegisterView ? undefined : onYoutubePlayerStateChange, - } - }); - + if (typeof (YT) === undefined) setTimeout(() => this.loadYouTube(iframe), 100); + else { + (YT as any)?.ready(() => { + this._youtubePlayer = new YT.Player(`${this.youtubeVideoId + this._youtubeIframeId}-player`, { + events: { + 'onReady': this.props.dontRegisterView ? undefined : onYoutubePlayerReady, + 'onStateChange': this.props.dontRegisterView ? undefined : onYoutubePlayerStateChange, + } + }); + }); + } } private get uIButtons() { const curTime = (this.layoutDoc._currentTimecode || 0); @@ -346,26 +372,33 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD const style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : ""); const start = untracked(() => Math.round((this.layoutDoc._currentTimecode || 0))); return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`} - onLoad={this.youtubeIframeLoaded} className={`${style}`} width={(this.layoutDoc._nativeWidth || 640)} height={(this.layoutDoc._nativeHeight || 390)} + onPointerLeave={this.updateTimecode} + onLoad={this.youtubeIframeLoaded} className={`${style}`} width={Doc.NativeWidth(this.layoutDoc) || 640} height={Doc.NativeHeight(this.layoutDoc) || 390} src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=0&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`} />; } @action.bound addDocumentWithTimestamp(doc: Doc | Doc[]): boolean { const docs = doc instanceof Doc ? [doc] : doc; - docs.forEach(doc => { - const curTime = (this.layoutDoc._currentTimecode || -1); - curTime !== -1 && (doc.displayTimecode = curTime); - }); + const curTime = NumCast(this.layoutDoc._currentTimecode); + docs.forEach(doc => doc.displayTimecode = curTime); return this.addDocument(doc); } + @computed get contentScaling() { return this.props.ContentScaling(); } contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content]; render() { return (<div className="videoBox" onContextMenu={this.specificContextMenu} - style={{ transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} > + style={{ + transform: this.props.PanelWidth() ? undefined : `scale(${this.contentScaling})`, + width: this.props.PanelWidth() ? undefined : `${100 / this.contentScaling}%`, + height: this.props.PanelWidth() ? undefined : `${100 / this.contentScaling}%`, + pointerEvents: this.layoutDoc._isBackground ? "none" : undefined, + borderRadius: `${Number(StrCast(this.layoutDoc.borderRounding).replace("px", "")) / this.contentScaling}px` + }} > <div className="videoBox-viewer" > <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit} + forceScaling={true} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth} annotationsKey={this.annotationKey} diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 66dc3cdcc..80e2d3ce2 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -1,4 +1,3 @@ -import { faMousePointer, faPen, faStickyNote } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; @@ -44,6 +43,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); static _annotationStyle: any = addStyleSheet(); + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); } private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); private _startX: number = 0; private _startY: number = 0; @@ -52,39 +52,52 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum @observable private _marqueeWidth: number = 0; @observable private _marqueeHeight: number = 0; @observable private _marqueeing: boolean = false; - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); } - get _collapsed() { return StrCast(this.layoutDoc._chromeStatus) !== "enabled"; } - set _collapsed(value) { this.layoutDoc._chromeStatus = !value ? "enabled" : "disabled"; } @observable private _url: string = "hello"; @observable private _pressX: number = 0; @observable private _pressY: number = 0; + @observable private _iframe: HTMLIFrameElement | null = null; @observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>(); private _selectionReactionDisposer?: IReactionDisposer; private _scrollReactionDisposer?: IReactionDisposer; + private _scrollTopReactionDisposer?: IReactionDisposer; private _moveReactionDisposer?: IReactionDisposer; - private _keyInput = React.createRef<HTMLInputElement>(); private _longPressSecondsHack?: NodeJS.Timeout; private _outerRef = React.createRef<HTMLDivElement>(); - private _iframeRef = React.createRef<HTMLIFrameElement>(); private _iframeIndicatorRef = React.createRef<HTMLDivElement>(); private _iframeDragRef = React.createRef<HTMLDivElement>(); private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void); + get scrollHeight() { return this.webpage?.scrollHeight || 1000; } + get _collapsed() { return StrCast(this.layoutDoc._chromeStatus) !== "enabled"; } + set _collapsed(value) { this.layoutDoc._chromeStatus = !value ? "enabled" : "disabled"; } + get webpage() { return this._iframe?.contentDocument?.children[0]; } constructor(props: any) { super(props); - this.dataDoc[this.fieldKey + "-nativeWidth"] = this.Document._nativeWidth = NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"], NumCast(this.Document._nativeWidth, 850)); - this.dataDoc[this.fieldKey + "-nativeHeight"] = this.Document._nativeHeight = NumCast(this.dataDoc[this.props.fieldKey + "-nativeHeight"], NumCast(this.Document._nativeHeight, this.Document[HeightSym]() / this.Document[WidthSym]() * 850)); + if (this.dataDoc[this.fieldKey] instanceof WebField) { + Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || 850); + Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || this.Document[HeightSym]() / this.Document[WidthSym]() * 850); + } } iframeLoaded = action((e: any) => { - const iframe = this._iframeRef.current; - if (iframe && iframe.contentDocument) { + const iframe = this._iframe; + if (iframe?.contentDocument) { iframe.setAttribute("enable-annotation", "true"); - 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.addEventListener("click", undoBatch(action(e => { + let href = ""; + for (let ele = e.target; ele; ele = ele.parentElement) { + href = (typeof (ele.href) === "string" ? ele.href : ele.href?.baseVal) || ele.parentElement?.href || href; + } + if (href) { + this._url = href.replace(Utils.prepend(""), Cast(this.dataDoc[this.fieldKey], WebField, null)?.url.origin); + this.submitURL(); + } + }))); + iframe.contentDocument.addEventListener('wheel', this.iframeWheel, false); + if (this.webpage) { + this.webpage.scrollTop = NumCast(this.layoutDoc._scrollTop); + this.webpage.scrollLeft = NumCast(this.layoutDoc._scrollLeft); + } } this._scrollReactionDisposer?.(); this._scrollReactionDisposer = reaction(() => ({ scrollY: this.layoutDoc._scrollY, scrollX: this.layoutDoc._scrollX }), @@ -93,17 +106,30 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/); const duration = durationStr ? Number(durationStr[1]) : 1000; if (scrollY !== undefined) { - setTimeout(() => this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollY || 0)), delay); - setTimeout(() => { this.layoutDoc._scrollTop = scrollY; this.layoutDoc._scrollY = undefined; }, duration + delay); + this._forceSmoothScrollUpdate = true; + this.layoutDoc._scrollY = undefined; + setTimeout(() => this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollY || 0), () => this.layoutDoc._scrollTop = scrollY), delay); } if (scrollX !== undefined) { - setTimeout(() => this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollX || 0)), delay); - setTimeout(() => { this.layoutDoc._scrollLeft = scrollX; this.layoutDoc._scrollX = undefined; }, duration + delay); + this._forceSmoothScrollUpdate = true; + this.layoutDoc._scrollX = undefined; + setTimeout(() => this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollX || 0), () => this.layoutDoc._scrollLeft = scrollX), delay); } }, { fireImmediately: true } ); + this._scrollTopReactionDisposer = reaction(() => this.layoutDoc._scrollTop, + scrollTop => { + const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/); + const duration = durationStr ? Number(durationStr[1]) : 1000; + if (scrollTop !== this._outerRef.current?.scrollTop && scrollTop !== undefined && this._forceSmoothScrollUpdate) { + this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollTop || 0), () => this._forceSmoothScrollUpdate = true); + } else this._forceSmoothScrollUpdate = true; + }, + { fireImmediately: true } + ); }); + _forceSmoothScrollUpdate = true; updateScroll = (x: Opt<number>, y: Opt<number>) => { if (y !== undefined) { @@ -117,16 +143,23 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum } setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func; - iframedown = (e: PointerEvent) => { - this._setPreviewCursor?.(e.screenX, e.screenY, false); - } - iframeScrolled = (e: any) => { - if (e.target?.children) { - e.target.children[0].scrollLeft = 0; - 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; + iframeWheel = (e: any) => { + if (this._forceSmoothScrollUpdate && e.target?.children) { + this.webpage && setTimeout(action(() => { + this.webpage!.scrollLeft = 0; + const scrollTop = this.webpage!.scrollTop; + const scrollLeft = this.webpage!.scrollLeft; + this._outerRef.current!.scrollTop = scrollTop; + this._outerRef.current!.scrollLeft = scrollLeft; + if (this.layoutDoc._scrollTop !== scrollTop) { + this._forceSmoothScrollUpdate = false; + this.layoutDoc._scrollTop = scrollTop; + } + if (this.layoutDoc._scrollLeft !== scrollLeft) { + this._forceSmoothScrollUpdate = false; + this.layoutDoc._scrollLeft = scrollLeft; + } + })); } } async componentDidMount() { @@ -151,13 +184,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum const field = Cast(this.rootDoc[this.props.fieldKey], WebField); if (field?.url.href.indexOf("youtube") !== -1) { const youtubeaspect = 400 / 315; - const nativeWidth = NumCast(this.layoutDoc._nativeWidth); - const nativeHeight = NumCast(this.layoutDoc._nativeHeight); + const nativeWidth = Doc.NativeWidth(this.layoutDoc); + const nativeHeight = Doc.NativeHeight(this.layoutDoc); if (field) { if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) { - if (!nativeWidth) this.layoutDoc._nativeWidth = 600; - this.layoutDoc._nativeHeight = NumCast(this.layoutDoc._nativeWidth) / youtubeaspect; - this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect; + if (!nativeWidth) Doc.SetNativeWidth(this.layoutDoc, 600); + Doc.SetNativeHeight(this.layoutDoc, (nativeWidth || 600) / youtubeaspect); + this.layoutDoc._height = this.layoutDoc[WidthSym]() / youtubeaspect; } } // else it's an HTMLfield } else if (field?.url) { @@ -171,21 +204,18 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum componentWillUnmount() { this._moveReactionDisposer?.(); this._selectionReactionDisposer?.(); + this._scrollTopReactionDisposer?.(); this._scrollReactionDisposer?.(); document.removeEventListener("pointerup", this.onLongPressUp); document.removeEventListener("pointermove", this.onLongPressMove); - this._iframeRef.current?.contentDocument?.removeEventListener('pointerdown', this.iframedown); - this._iframeRef.current?.contentDocument?.removeEventListener('scroll', this.iframeScrolled); - } - - @action - onURLChange = (e: React.ChangeEvent<HTMLInputElement>) => { - this._url = e.target.value; + this._iframe?.removeEventListener('wheel', this.iframeWheel); } onUrlDragover = (e: React.DragEvent) => { e.preventDefault(); } + + @undoBatch @action onUrlDrop = (e: React.DragEvent) => { const { dataTransfer } = e; @@ -297,9 +327,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum let pressedBound: ClientRect | undefined; let selectedText: string = ""; let pressedImg: boolean = false; - if (this._iframeRef.current) { - const B = this._iframeRef.current.getBoundingClientRect(); - const iframeDoc = this._iframeRef.current.contentDocument; + if (this._iframe) { + const B = this._iframe.getBoundingClientRect(); + const iframeDoc = this._iframe.contentDocument; if (B && iframeDoc) { // TODO: this only works when scale = 1 as it is currently only inteded for mobile upload const element = iframeDoc.elementFromPoint(this._pressX - B.left, this._pressY - B.top); @@ -388,17 +418,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum } } - - @undoBatch - @action - toggleNativeDimensions = () => { - Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth?.() || 0, this.props.NativeHeight?.() || 0); - } specificContextMenu = (e: React.MouseEvent): void => { const cm = ContextMenu.Instance; const funcs: ContextMenuProps[] = []; funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" }); - funcs.push({ description: (this.layoutDoc[this.fieldKey + "-contentWidth"] ? "Unfreeze" : "Freeze") + " Content Width", event: () => this.layoutDoc[this.fieldKey + "-contentWidth"] = this.layoutDoc[this.fieldKey + "-contentWidth"] ? undefined : NumCast(this.layoutDoc._nativeWidth), icon: "snowflake" }); + funcs.push({ description: (this.layoutDoc[this.fieldKey + "-contentWidth"] ? "Unfreeze" : "Freeze") + " Content Width", event: () => this.layoutDoc[this.fieldKey + "-contentWidth"] = this.layoutDoc[this.fieldKey + "-contentWidth"] ? undefined : Doc.NativeWidth(this.layoutDoc), icon: "snowflake" }); cm.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); } @@ -416,11 +440,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum const url = this.layoutDoc.useCors ? Utils.CorsProxy(field.url.href) : field.url.href; // view = <iframe className="webBox-iframe" src={url} onLoad={e => { e.currentTarget.before((e.currentTarget.contentDocument?.body || e.currentTarget.contentDocument)?.children[0]!); e.currentTarget.remove(); }} - view = <iframe className="webBox-iframe" enable-annotation={"true"} ref={this._iframeRef} src={url} onLoad={this.iframeLoaded} + view = <iframe className="webBox-iframe" enable-annotation={"true"} ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={url} onLoad={this.iframeLoaded} // the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />; } else { - view = <iframe className="webBox-iframe" enable-annotation={"true"} ref={this._iframeRef} src={"https://crossorigin.me/https://cs.brown.edu"} />; + view = <iframe className="webBox-iframe" enable-annotation={"true"} ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={"https://crossorigin.me/https://cs.brown.edu"} />; } return view; } @@ -468,7 +492,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum } @computed get annotationLayer() { TraceMobx(); - return <div className="webBox-annotationLayer" style={{ height: NumCast(this.Document._nativeHeight) }} ref={this._annotationLayer}> + return <div className="webBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}> {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno => <Annotation {...this.props} showInfo={emptyFunction} focus={this.props.focus} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />) } @@ -542,10 +566,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum } else if (this._mainCont.current) { // set marquee x and y positions to the spatially transformed position + const nheight = Doc.NativeHeight(this.Document) || 1; + const nwidth = Doc.NativeWidth(this.Document) || 1; const boundingRect = this._mainCont.current.getBoundingClientRect(); - const boundingHeight = (this.Document._nativeHeight || 1) / (this.Document._nativeWidth || 1) * boundingRect.width; - this._startX = (e.clientX - boundingRect.left) / boundingRect.width * (this.Document._nativeWidth || 1); - this._startY = (e.clientY - boundingRect.top) / boundingHeight * (this.Document._nativeHeight || 1); + const boundingHeight = nheight / nwidth * boundingRect.width; + this._startX = (e.clientX - boundingRect.left) / boundingRect.width * nwidth; + this._startY = (e.clientY - boundingRect.top) / boundingHeight * nheight; this._marqueeHeight = this._marqueeWidth = 0; this._marqueeing = true; } @@ -560,9 +586,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum if (this._marqueeing && this._mainCont.current) { // transform positions and find the width and height to set the marquee to const boundingRect = this._mainCont.current.getBoundingClientRect(); - const boundingHeight = (this.Document._nativeHeight || 1) / (this.Document._nativeWidth || 1) * boundingRect.width; - const curX = (e.clientX - boundingRect.left) / boundingRect.width * (this.Document._nativeWidth || 1); - const curY = (e.clientY - boundingRect.top) / boundingHeight * (this.Document._nativeHeight || 1); + const boundingHeight = (Doc.NativeHeight(this.Document) || 1) / (Doc.NativeWidth(this.Document) || 1) * boundingRect.width; + const curX = (e.clientX - boundingRect.left) / boundingRect.width * (Doc.NativeWidth(this.Document) || 1); + const curY = (e.clientY - boundingRect.top) / boundingHeight * (Doc.NativeHeight(this.Document) || 1); this._marqueeWidth = curX - this._startX; this._marqueeHeight = curY - this._startY; this._marqueeX = Math.min(this._startX, this._startX + this._marqueeWidth); @@ -619,23 +645,24 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum marqueeX = () => this._marqueeX; marqueeY = () => this._marqueeY; marqueeing = () => this._marqueeing; - visibleHeiht = () => { + visibleHeight = () => { if (this._mainCont.current) { const boundingRect = this._mainCont.current.getBoundingClientRect(); - const scalin = (this.Document._nativeWidth || 0) / boundingRect.width; - return Math.min(boundingRect.height * scalin, this.props.PanelHeight() * scalin); + const scaling = (Doc.NativeWidth(this.Document) || 0) / boundingRect.width; + return Math.min(boundingRect.height * scaling, this.props.PanelHeight() * scaling); } return this.props.PanelHeight(); } scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc._scrollLeft), NumCast(this.layoutDoc._scrollTop)); render() { + const scaling = Number.isFinite(this.props.ContentScaling()) ? this.props.ContentScaling() || 1 : 1; return (<div className="webBox" ref={this._mainCont} > <div className={`webBox-container`} style={{ position: undefined, - transform: `scale(${this.props.ContentScaling()})`, - width: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}% ` : "100%", - height: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}% ` : "100%", + transform: `scale(${scaling})`, + width: `${100 / scaling}% `, + height: `${100 / scaling}% `, pointerEvents: this.layoutDoc._isBackground ? "none" : undefined }} onContextMenu={this.specificContextMenu}> @@ -643,33 +670,36 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum {this.content} <div className={"webBox-outerContent"} ref={this._outerRef} style={{ - width: Number.isFinite(this.props.ContentScaling()) ? `${Math.max(100, 100 / this.props.ContentScaling())}% ` : "100%", + width: `${Math.max(100, 100 / scaling)}% `, pointerEvents: this.layoutDoc.isAnnotating && !this.layoutDoc._isBackground ? "all" : "none" }} - onWheel={e => e.stopPropagation()} - onPointerDown={this.onMarqueeDown} - onScroll={e => { - const iframe = this._iframeRef?.current?.contentDocument; - const outerFrame = this._outerRef.current; - if (iframe && outerFrame) { - if (iframe.children[0].scrollTop !== outerFrame.scrollTop) { - iframe.children[0].scrollTop = outerFrame.scrollTop; - } - if (iframe.children[0].scrollLeft !== outerFrame.scrollLeft) { - iframe.children[0].scrollLeft = outerFrame.scrollLeft; - } + onWheel={e => { + const target = this._outerRef.current; + if (this._forceSmoothScrollUpdate && target && this.webpage) { + setTimeout(action(() => { + target.scrollLeft = 0; + const scrollTop = target.scrollTop; + const scrollLeft = target.scrollLeft; + this.webpage!.scrollTop = scrollTop; + this.webpage!.scrollLeft = scrollLeft; + if (this.layoutDoc._scrollTop !== scrollTop) this.layoutDoc._scrollTop = scrollTop; + if (this.layoutDoc._scrollLeft !== scrollLeft) this.layoutDoc._scrollLeft = scrollLeft; + })); } - //this._outerRef.current!.scrollTop !== this._scrollTop && (this._outerRef.current!.scrollTop = this._scrollTop) - }}> + e.stopPropagation(); + }} + onPointerDown={this.onMarqueeDown} + onScroll={e => e.stopPropagation()} + > <div className={"webBox-innerContent"} style={{ - height: NumCast(this.layoutDoc.scrollHeight), + height: NumCast(this.scrollHeight, 50), pointerEvents: this.layoutDoc._isBackground ? "none" : undefined }}> <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth} annotationsKey={this.annotationKey} - VisibleHeight={this.visibleHeiht} + VisibleHeight={this.visibleHeight} focus={this.props.focus} setPreviewCursor={this.setPreviewCursor} isSelected={this.props.isSelected} diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 123946dea..7cd92b8b9 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -82,7 +82,7 @@ export class DashDocView extends React.Component<IDashDocView> { const { scale, translateX, translateY } = Utils.GetScreenTransform(outerElement); return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale); } - contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1; + contentScaling = () => Doc.NativeWidth(this._dashDoc) > 0 ? this._dashDoc![WidthSym]() / Doc.NativeWidth(this._dashDoc) : 1; outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 0d92d7062..b75cc230f 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -22,8 +22,7 @@ border-style: solid; overflow-y: auto; overflow-x: hidden; - color: initial; - max-height: 100%; + color: inherit; display: flex; flex-direction: row; transition: opacity 1s; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 903bbaaa3..fe38939c5 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -11,7 +11,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from " import { ReplaceStep } from 'prosemirror-transform'; import { EditorView } from "prosemirror-view"; import { DateField } from '../../../../fields/DateField'; -import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit, AclAdmin, UpdatingFromServer } from "../../../../fields/Doc"; +import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit, AclAdmin, UpdatingFromServer, ForceServerWrite } from "../../../../fields/Doc"; import { documentSchema } from '../../../../fields/documentSchemas'; import applyDevTools = require("prosemirror-dev-tools"); import { removeMarkWithAttrs } from "./prosemirrorPatches"; @@ -663,7 +663,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const optionItems = options && "subitems" in options ? options.subitems : []; !Doc.UserDoc().noviceMode && optionItems.push({ description: !this.Document._singleLine ? "Make Single Line" : "Make Multi Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); optionItems.push({ description: `${this.Document._autoHeight ? "Lock" : "Auto"} Height`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); - optionItems.push({ description: `${!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Lock" : "Unlock"} Aspect`, event: this.toggleNativeDimensions, icon: "snowflake" }); + optionItems.push({ description: `${!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? "Lock" : "Unlock"} Aspect`, event: this.toggleNativeDimensions, icon: "snowflake" }); !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); this._downX = this._downY = Number.NaN; } @@ -805,13 +805,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp tr = tr.addMark(pos, pos + node.nodeSize, link); } }); - this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents + this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter)); - this.dataDoc[UpdatingFromServer] = false; + this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false; } } componentDidMount() { this._cachedLinks = DocListCast(this.Document.links); + this._disposers.sidebarheight = reaction( + () => ({ annoHeight: NumCast(this.rootDoc[this.annotationKey + "-height"]), textHeight: NumCast(this.rootDoc[this.fieldKey + "-height"]) }), + ({ annoHeight, textHeight }) => { + this.layoutDoc._autoHeight && (this.rootDoc._height = Math.max(annoHeight, textHeight)); + }); this._disposers.links = reaction(() => DocListCast(this.Document.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks newLinks => { this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l)); @@ -1454,10 +1459,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp public endUndoTypingBatch() { const wasUndoing = this._undoTyping; - if (this._undoTyping) { - this._undoTyping.end(); - this._undoTyping = undefined; - } + this._undoTyping?.end(); + this._undoTyping = undefined; return wasUndoing; } public static LiveTextUndo: UndoManager.Batch | undefined; @@ -1470,8 +1473,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp FormattedTextBox.LiveTextUndo?.end(); FormattedTextBox.LiveTextUndo = undefined; - // move the richtextmenu offscreen - //if (!RichTextMenu.Instance.Pinned) RichTextMenu.Instance.delayHide(); } _lastTimedMark: Mark | undefined = undefined; @@ -1544,18 +1545,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }, 10); } else { try { - const boxHeight = Number(getComputedStyle(this._boxRef.current!).height.replace("px", "")); + const boxHeight = Number(getComputedStyle(this._boxRef.current!).height.replace("px", "")) * NumCast(this.Document._viewScale, 1); const outer = this.rootDoc[HeightSym]() - boxHeight - (this.props.ChromeHeight ? this.props.ChromeHeight() : 0); - const finalHeight = newHeight + Math.max(0, outer); - const maxsidebar = !this.sidebarWidth() ? 0 : Array.from(this._boxRef.current!.getElementsByClassName("collectionStackingViewFieldColumn")).reduce((prev, ele) => Math.max(prev, Number(getComputedStyle(ele).height.replace("px", ""))), 0); - if (this.rootDoc._height !== finalHeight && finalHeight > maxsidebar) { - this.rootDoc._height = finalHeight; - this.layoutDoc._nativeHeight = nh ? scrollHeight : undefined; - } - this.rootDoc[this.fieldKey + "-height"] = finalHeight; + this.rootDoc[this.fieldKey + "-height"] = newHeight + Math.max(0, outer); } catch (e) { console.log("Error in tryUpdateHeight"); } } - } else this.rootDoc[this.fieldKey + "-height"] = 0; + } //else this.rootDoc[this.fieldKey + "-height"] = 0; } @computed get audioHandle() { @@ -1574,10 +1569,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp />; } + sidebarContentScaling = () => this.props.ContentScaling() * NumCast(this.layoutDoc._viewScale, 1); @computed get sidebarCollection() { const fitToBox = this.props.Document._fitToBox; const collectionProps = { ...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit, + NativeWidth: returnZero, + NativeHeight: returnZero, PanelHeight: this.props.PanelHeight, PanelWidth: this.sidebarWidth, xMargin: 0, @@ -1591,7 +1589,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp isSelected: this.props.isSelected, select: emptyFunction, active: this.annotationsActive, - ContentScaling: returnOne, + ContentScaling: this.sidebarContentScaling, whenActiveChanged: this.whenActiveChanged, removeDocument: this.removeDocument, moveDocument: this.moveDocument, @@ -1610,7 +1608,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); } sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); - sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / this.props.ContentScaling(), 0); + sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / this.props.ContentScaling(), 0).scale(1 / NumCast(this.layoutDoc._viewScale, 1)); @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); } render() { TraceMobx(); @@ -1626,12 +1624,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const padding = Math.max(margins + ((selected && !this.layoutDoc._singleLine) || minimal ? -selPad : 0), 0); const selclass = selected && !this.layoutDoc._singleLine && margins >= 10 ? "-selected" : ""; return ( - <div className={"formattedTextBox-cont"} ref={this._boxRef} + <div className="formattedTextBox-cont" ref={this._boxRef} style={{ transform: `scale(${scale})`, transformOrigin: "top left", width: `${100 / scale}%`, height: `calc(${100 / scale}% - ${this.props.ChromeHeight?.() || 0}px)`, + overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined, ...this.styleFromLayoutString(scale) // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' > }}> <div className={`formattedTextBox-cont`} ref={this._ref} diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 32038d1ee..3e5a40084 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -320,8 +320,8 @@ export class FormattedTextBoxComment { whenActiveChanged={returnFalse} bringToFront={returnFalse} ContentScaling={returnOne} - NativeWidth={target._nativeWidth ? (() => NumCast(target._nativeWidth)) : undefined} - NativeHeight={target._natvieHeight ? (() => NumCast(target._nativeHeight)) : undefined} + NativeWidth={Doc.NativeWidth(target) ? (() => Doc.NativeWidth(target)) : undefined} + NativeHeight={Doc.NativeHeight(target) ? (() => Doc.NativeHeight(target)) : undefined} /> </div> </div>; diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 2700c508b..cf9b03308 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -31,6 +31,7 @@ const { toggleMark } = require("prosemirror-commands"); export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { static Instance: RichTextMenu; public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable + private _linkToRef = React.createRef<HTMLInputElement>(); @observable public view?: EditorView; public editorProps: FieldViewProps & FormattedTextBoxProps | undefined; @@ -154,6 +155,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { @action public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: any) { + if (this._linkToRef.current?.getBoundingClientRect().width) { + return; + } this.view = view; if (!view || !view.hasFocus()) { return; @@ -792,7 +796,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { const self = this; function onLinkChange(e: React.ChangeEvent<HTMLInputElement>) { - self.TextView.endUndoTypingBatch(); + self.TextView?.endUndoTypingBatch(); UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), "link change"); } @@ -807,7 +811,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { const dropdownContent = <div className="dropdown link-menu"> <p>Linked to:</p> - <input value={link} placeholder="Enter URL" onChange={onLinkChange} /> + <input value={link} ref={this._linkToRef} placeholder="Enter URL" onChange={onLinkChange} /> <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "add:right")}>Apply hyperlink</button> <div className="divider"></div> <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button> diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx index 1767a04de..40c1d1cac 100644 --- a/src/client/views/nodes/formattedText/RichTextSchema.tsx +++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx @@ -31,7 +31,7 @@ export class DashDocView { } //moved - contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1; + contentScaling = () => Doc.NativeWidth(this._dashDoc) > 0 ? this._dashDoc![WidthSym]() / Doc.NativeWidth(this._dashDoc) : 1; //moved outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target |
