diff options
author | bobzel <zzzman@gmail.com> | 2022-09-29 20:44:33 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2022-09-29 20:44:33 -0400 |
commit | 2de2e154912d95a15e5f890c9d1a3a7a11610107 (patch) | |
tree | ddc5fad406050b3629f588a3a09e88d3214f13bb | |
parent | 4580a4193edb451f628eee8be2d3c31d06b1a3a7 (diff) |
a bunch of changes to trails: link to trail to initiate trail when following link. cleanly separate pin layout data vs. content data.
18 files changed, 225 insertions, 170 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index fb68fba38..4ca9972f8 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1398,6 +1398,9 @@ export namespace DocUtils { heading: Doc.name, checked: 'boolean', containingTreeView: Doc.name, + altKey: 'boolean', + ctrlKey: 'boolean', + shiftKey: 'boolean', }); } }); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index fdac3c00a..6f9975be4 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -657,7 +657,7 @@ export class CurrentUserUtils { CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, CollectionViewType.Grid, CollectionViewType.NoteTaking]), title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}}, - { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "tab")'}, width: 20, scripts: { onClick: 'pinWithView(_readOnly_)'}}, + { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "tab")'}, width: 20, scripts: { onClick: 'pinWithView(_readOnly_, altKey)'}}, { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, { title: "Num", icon: "", toolTip: "Frame Number", btnType: ButtonType.TextButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement()?.currentFrame.toString()'}, width: 20, scripts: {}}, { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 50190061a..9a46d20de 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -244,7 +244,7 @@ export class DocumentManager { res(ViewAdjustment.doNothing); }), }); - if (focusView.props.Document.layoutKey === 'layout_icon') { + if (focusView.props.Document.layoutKey === 'layout_icon' && focusView.rootDoc.type !== DocumentType.SCRIPTING) { focusView.iconify(() => doFocus(true)); } else { doFocus(false); diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d index 9063dc894..1a93bbe59 100644 --- a/src/client/util/type_decls.d +++ b/src/client/util/type_decls.d @@ -67,6 +67,9 @@ interface RegExp { readonly sticky: boolean; readonly unicode: boolean; } +interface Date { + now() : string; +} interface String { codePointAt(pos: number): number | undefined; includes(searchString: string, position?: number): boolean; diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss index a112f4745..1e93ba5e2 100644 --- a/src/client/views/DocumentButtonBar.scss +++ b/src/client/views/DocumentButtonBar.scss @@ -1,6 +1,6 @@ -@import "global/globalCssVariables"; +@import 'global/globalCssVariables'; -$linkGap : 3px; +$linkGap: 3px; .documentButtonBar-linkFlyout { grid-column: 2/4; @@ -18,6 +18,21 @@ $linkGap : 3px; cursor: pointer; } +.documentButtonBar-pinTypes { + position: absolute; + display: flex; + width: 60px; + top: -14px; + background: black; + height: 20px; + align-items: center; +} +.documentButtonBar-pinIcon { + &:hover { + background-color: lightblue; + } +} + .documentButtonBar-linkButton-empty, .documentButtonBar-linkButton-nonempty { height: 20px; @@ -99,7 +114,6 @@ $linkGap : 3px; transform: scale(1.05); } - @-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); @@ -127,4 +141,4 @@ $linkGap : 3px; 100% { box-shadow: 0 0 0 10px rgba(0, 255, 0, 0); } -}
\ No newline at end of file +} diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 0bfe6e87a..d42ff436f 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -5,8 +5,8 @@ import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../fields/Doc'; import { RichTextField } from '../../fields/RichTextField'; -import { Cast, DocCast, NumCast } from '../../fields/Types'; -import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils'; +import { Cast, NumCast } from '../../fields/Types'; +import { emptyFunction, setupMoveUpEvents, simulateMouseClick } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; import { Docs } from '../documents/Documents'; @@ -26,7 +26,6 @@ import { DashFieldView } from './nodes/formattedText/DashFieldView'; import { GoogleRef } from './nodes/formattedText/FormattedTextBox'; import { TemplateMenu } from './TemplateMenu'; import React = require('react'); -import { DocumentType } from '../documents/DocumentTypes'; const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -159,12 +158,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV })(); return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? null : ( - <Tooltip - title={ - <> - <div className="dash-tooltip">{title}</div> - </> - }> + <Tooltip title={<div className="dash-tooltip">{title}</div>}> <div className="documentButtonBar-button" style={{ backgroundColor: this.pullColor }} @@ -202,14 +196,12 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV size="sm" style={{ WebkitAnimation: animation, MozAnimation: animation }} icon={(() => { + // prettier-ignore switch (this.openHover) { default: - case UtilityButtonState.Default: - return dataDoc.googleDocUnchanged === false ? (this.pullIcon as any) : fetch; - case UtilityButtonState.OpenRight: - return 'arrow-alt-circle-right'; - case UtilityButtonState.OpenExternally: - return 'share'; + case UtilityButtonState.Default: return dataDoc.googleDocUnchanged === false ? (this.pullIcon as any) : fetch; + case UtilityButtonState.OpenRight: return 'arrow-alt-circle-right'; + case UtilityButtonState.OpenExternally: return 'share'; } })()} /> @@ -231,21 +223,66 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV </Tooltip> ); } + @observable expandPin = false; + @observable subPin = ''; @computed get pinButton() { const targetDoc = this.view0?.props.Document; + const pinBtn = (pinDocLayout: boolean, pinDocContent: boolean, icon: IconProp) => { + const tooltip = `Pin Document and Save ${this.subPin} to trail`; + return !tooltip ? null : ( + <Tooltip title={<div className="dash-tooltip">{tooltip}</div>}> + <div className="documentButtonBar-pinIcon"> + <FontAwesomeIcon + className="documentdecorations-icon" + style={{ width: 20 }} + key={icon.toString()} + size="sm" + icon={icon} + onPointerEnter={action( + e => + (this.subPin = + (pinDocLayout ? 'Layout' : '') + + (pinDocLayout && pinDocContent ? ' &' : '') + + (pinDocContent ? ' Content View' : '') + + (pinDocLayout && pinDocContent ? '(shift+alt)' : pinDocLayout ? '(shift)' : pinDocContent ? '(alt)' : '')) + )} + onPointerLeave={action(e => (this.subPin = ''))} + onClick={e => { + const docs = this.props + .views() + .filter(v => v) + .map(dv => dv!.rootDoc); + TabDocView.PinDoc(docs, { pinDocLayout, pinDocContent, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + e.stopPropagation(); + }} + /> + </div> + </Tooltip> + ); + }; return !targetDoc ? null : ( - <Tooltip title={<div className="dash-tooltip">{SelectionManager.Views().length > 1 ? 'Pin multiple documents to trail (use shift to pin with view)' : 'Pin to trail (use shift to pin with view)'}</div>}> + <Tooltip title={<div className="dash-tooltip">{`Pin Document ${SelectionManager.Views().length > 1 ? 'multiple documents' : ''} to Trail`}</div>}> <div className="documentButtonBar-icon" style={{ color: 'white' }} + onPointerEnter={action(e => (this.expandPin = true))} + onPointerLeave={action(e => (this.expandPin = false))} onClick={e => { const docs = this.props .views() .filter(v => v) .map(dv => dv!.rootDoc); - TabDocView.PinDoc(docs, { pinDocView: e.shiftKey, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + TabDocView.PinDoc(docs, { pinDocLayout: e.shiftKey, pinDocContent: e.altKey, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + e.stopPropagation(); }}> + {this.expandPin ? ( + <div className="documentButtonBar-pinTypes"> + {pinBtn(true, false, 'window-maximize')} + {pinBtn(false, true, 'address-card')} + {pinBtn(true, true, 'id-card')} + </div> + ) : null} <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" /> </div> </Tooltip> diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 4675571c2..3e0de6d15 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -678,8 +678,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth / 2) - this._resizeBorderWidth / 2 - borderRadiusDraggerWidth)); bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight)); - // Rotation constants: Only allow rotation on ink and images - const useRotation = seldoc.ComponentView instanceof InkingStroke || seldoc.ComponentView instanceof ImageBox || seldoc.ComponentView instanceof VideoBox; + const useRotation = true; // when do we want an object to not rotate? const rotation = NumCast(seldoc.rootDoc._jitterRotation); const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : ''; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 857a5522c..350883f70 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -303,6 +303,7 @@ export class MainView extends React.Component { fa.faStop, fa.faCalculator, fa.faWindowMaximize, + fa.faIdCard, fa.faAddressCard, fa.faQuestionCircle, fa.faArrowLeft, diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index b1f57f3fd..c4da70371 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -233,7 +233,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { pinDoc.presentationTargetDoc = doc; pinDoc.title = doc.title + ' - Slide'; pinDoc.data = new List<Doc>(); // the children of the alias' layout are the presentation slide children. the alias' data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data - pinDoc.presMovement = pinProps?.pinDocView && !pinProps?.pinWithView ? PresMovement.None : PresMovement.Zoom; + pinDoc.presMovement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom; pinDoc.groupWithUp = false; pinDoc.context = curPres; // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index c34a6faaa..640984ad6 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -782,7 +782,7 @@ export class TreeView extends React.Component<TreeViewProps> { onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null); - refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document); + refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document, {}); ignoreEvent = (e: any) => { if (this.props.isContentActive(true)) { e.stopPropagation(); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7b6944e1e..8824c2b01 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1719,7 +1719,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection event: () => (this.Document._fitContentsToBox = !this.fitContentsToBox), icon: !this.fitContentsToBox ? 'expand-arrows-alt' : 'compress-arrows-alt', }); - appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinDocView: true, panelWidth: this.props.PanelWidth(), panelHeight: this.props.PanelHeight() }), icon: 'map-pin' }); + appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' }); //appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" }); this.props.ContainingCollectionView && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' }); @@ -2249,6 +2249,6 @@ ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true); }); -ScriptingGlobals.add(function pinWithView(readOnly: boolean) { - !readOnly && SelectionManager.Views().forEach(view => TabDocView.PinDoc(view.rootDoc, { pinDocView: true, panelWidth: view.props.PanelWidth(), panelHeight: view.props.PanelHeight() })); +ScriptingGlobals.add(function pinWithView(readOnly: boolean, pinDocContent: boolean) { + !readOnly && SelectionManager.Views().forEach(view => TabDocView.PinDoc(view.rootDoc, { pinDocContent, pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()) })); }); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index 8a8b528f6..488f51d77 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -1,11 +1,11 @@ -import React = require("react"); -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Tooltip } from "@material-ui/core"; -import { observer } from "mobx-react"; -import { unimplementedFunction } from "../../../../Utils"; -import { DocumentType } from "../../../documents/DocumentTypes"; -import { SelectionManager } from "../../../util/SelectionManager"; -import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu"; +import React = require('react'); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@material-ui/core'; +import { observer } from 'mobx-react'; +import { unimplementedFunction } from '../../../../Utils'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { SelectionManager } from '../../../util/SelectionManager'; +import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; @observer export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { @@ -25,44 +25,34 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { } render() { - const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: "auto", width: 19, transform: 'translate(-2px, -2px)' }} />; + const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: 'auto', width: 19, transform: 'translate(-2px, -2px)' }} />; const buttons = [ <Tooltip key="collect" title={<div className="dash-tooltip">Create a Collection</div>} placement="bottom"> - <button - className="antimodeMenu-button" - onPointerDown={this.createCollection}> + <button className="antimodeMenu-button" onPointerDown={this.createCollection}> <FontAwesomeIcon icon="object-group" size="lg" /> </button> </Tooltip>, <Tooltip key="group" title={<div className="dash-tooltip">Create a Grouping</div>} placement="bottom"> - <button - className="antimodeMenu-button" - onPointerDown={e => this.createCollection(e, true)}> + <button className="antimodeMenu-button" onPointerDown={e => this.createCollection(e, true)}> <FontAwesomeIcon icon="layer-group" size="lg" /> </button> </Tooltip>, <Tooltip key="summarize" title={<div className="dash-tooltip">Summarize Documents</div>} placement="bottom"> - <button - className="antimodeMenu-button" - onPointerDown={this.summarize}> + <button className="antimodeMenu-button" onPointerDown={this.summarize}> <FontAwesomeIcon icon="compress-arrows-alt" size="lg" /> </button> </Tooltip>, <Tooltip key="delete" title={<div className="dash-tooltip">Delete Documents</div>} placement="bottom"> - <button - className="antimodeMenu-button" - onPointerDown={this.delete}> + <button className="antimodeMenu-button" onPointerDown={this.delete}> <FontAwesomeIcon icon="trash-alt" size="lg" /> </button> </Tooltip>, - <Tooltip key="pinWithView" title={<div className="dash-tooltip">Pin with selected region</div>} placement="bottom"> - <button - className="antimodeMenu-button" - onPointerDown={this.pinWithView}> + <Tooltip key="pinWithView" title={<div className="dash-tooltip">Pin selected region to trail</div>} placement="bottom"> + <button className="antimodeMenu-button" onPointerDown={this.pinWithView}> {presPinWithViewIcon} </button> </Tooltip>, ]; return this.getElement(buttons); } -}
\ No newline at end of file +} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 584c9690f..a020b67cd 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -18,7 +18,7 @@ import { Transform } from '../../../util/Transform'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; -import { PinViewProps, PresBox } from '../../nodes/trails/PresBox'; +import { PresBox } from '../../nodes/trails/PresBox'; import { VideoBox } from '../../nodes/VideoBox'; import { pasteImageBitmap } from '../../nodes/WebBoxRenderer'; import { PreviewCursor } from '../../PreviewCursor'; @@ -61,6 +61,11 @@ export interface MarqueeViewBounds { @observer export class MarqueeView extends React.Component<SubCollectionViewProps & MarqueeViewProps> { + public static CurViewBounds(pinDoc: Doc, panelWidth: number, panelHeight: number) { + const ps = NumCast(pinDoc._viewScale, 1); + return { left: NumCast(pinDoc._panX) - panelWidth / 2 / ps, top: NumCast(pinDoc._panY) - panelHeight / 2 / ps, width: panelWidth / ps, height: panelHeight / ps }; + } + private _commandExecuted = false; @observable _lastX: number = 0; @observable _lastY: number = 0; @@ -426,10 +431,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @action pinWithView = async () => { const doc = this.props.Document; - const viewOptions: PinViewProps = { - bounds: this.Bounds, - }; - TabDocView.PinDoc(doc, { pinWithView: viewOptions, pinDocView: true }); + TabDocView.PinDoc(doc, { pinViewport: this.Bounds }); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); }; diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index f6eb2fce4..92f6bbb64 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -208,7 +208,7 @@ export class CollectionLinearView extends CollectionSubView() { checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle} onChange={action(e => { - ScriptCast(this.Document.onClick).script.run({ + ScriptCast(this.Document.onClick)?.script.run({ this: this.layoutDoc, self: this.rootDoc, _readOnly_: false, diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index ba061ce8f..913d5a7ef 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -577,6 +577,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps documentView: this.props.DocumentView(), clientX, clientY, + altKey, shiftKey, }, console.log @@ -589,7 +590,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps } } else if (this.onClickHandler?.script && !isScriptBox()) { // bcz: hack? don't execute script if you're clicking on a scripting box itself - const { clientX, clientY, shiftKey } = e; + const { clientX, clientY, shiftKey, altKey } = e; const func = () => this.onClickHandler.script.run( { @@ -602,6 +603,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps clientX, clientY, shiftKey, + altKey, }, console.log ).result?.select === true diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx index 1c9b0bc0e..37afcdd8b 100644 --- a/src/client/views/nodes/ScriptingBox.tsx +++ b/src/client/views/nodes/ScriptingBox.tsx @@ -115,6 +115,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable @action componentDidMount() { + this.props.setContentView?.(this); this.rawText = this.rawScript; const observer = new _global.ResizeObserver( action((entries: any) => { @@ -480,6 +481,10 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable return value; } + scrollFocus = () => { + return undefined; + }; + getSuggestedParams(pos: number) { const firstScript = this.rawText.slice(0, pos); const indexP = firstScript.lastIndexOf('.'); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 0c4d514cd..18441aace 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -31,19 +31,16 @@ import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentVie import { FieldView, FieldViewProps } from '../FieldView'; import './PresBox.scss'; import { PresEffect, PresMovement, PresStatus } from './PresEnums'; +import { privateEncrypt } from 'crypto'; +import { ScriptingBox } from '../ScriptingBox'; export interface PinProps { audioRange?: boolean; activeFrame?: number; hidePresBox?: boolean; - pinWithView?: PinViewProps; - pinDocView?: boolean; // whether the current view specs of the document should be saved the pinned document - panelWidth?: number; // panel width and height of the document (used to compute the bounds of the pinned view area) - panelHeight?: number; -} - -export interface PinViewProps { - bounds: MarqueeViewBounds; + pinViewport?: MarqueeViewBounds; // pin a specific viewport on a freeform view (use MarqueeView.CurViewBounds to compute if no region has been selected) + pinDocLayout?: boolean; // pin layout info (width/height/x/y) + pinDocContent?: boolean; // pin data info (scroll/pan/zoom/text) } @observer @@ -268,6 +265,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { // Case 3: Last slide and presLoop is toggled ON or it is in Edit mode this.nextSlide(0); } + return this.itemIndex; }; // Called when the user activates 'back' - to move to the previous part of the pres. trail @@ -298,6 +296,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { // Case 3: Pres loop is on so it should go to the last slide this.gotoDocument(this.childDocs.length - 1, activeItem); } + return this.itemIndex; }; //The function that is called when a document is clicked or reached through next or back. @@ -351,7 +350,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const pannable = [DocumentType.IMG].includes(target.type as any) || (target.type === DocumentType.COL && target._viewType === CollectionViewType.Freeform); const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(target.type as any); const clippable = [DocumentType.COMPARISON].includes(target.type as any); - const dataview = [DocumentType.INK].includes(target.type as any) && target.activeFrame === undefined; + const dataview = [DocumentType.INK, DocumentType.COL].includes(target.type as any) && target.activeFrame === undefined; const textview = [DocumentType.RTF].includes(target.type as any) && target.activeFrame === undefined; return { scrollable, pannable, temporal, clippable, dataview, textview }; } @@ -383,7 +382,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const dv = DocumentManager.Instance.getDocumentView(bestTarget); if (dv) { const computedScale = NumCast(activeItem.presZoom, 1) * Math.min(dv.props.PanelWidth() / viewport.width, dv.props.PanelHeight() / viewport.height); - activeItem.presMovement === 'zoom' && (bestTarget._viewScale = activeItem.presZoom !== undefined ? computedScale : Math.min(computedScale, NumCast(bestTarget._viewScale))); + activeItem.presMovement === 'zoom' && (bestTarget._viewScale = computedScale); dv.ComponentView?.brushView?.(viewport); } } else { @@ -400,47 +399,40 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { /// target doc when navigating to it. @action static pinDocView(pinDoc: Doc, pinProps: PinProps | undefined, targetDoc: Doc) { - if (pinProps?.pinWithView) { - // If pinWithView option set then update scale and x / y props of slide - const bounds = pinProps.pinWithView.bounds; - pinDoc.presPinView = true; - pinDoc.presPinViewX = bounds.left + bounds.width / 2; - pinDoc.presPinViewY = bounds.top + bounds.height / 2; - pinDoc.presPinViewBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]); - } - if (pinProps?.pinDocView) { - const { scrollable, pannable, temporal, clippable, dataview, textview } = this.pinDataTypes(pinDoc); - pinDoc.presPinView = (pinProps?.pinWithView ? true : false) || scrollable || temporal || pannable || clippable || dataview || textview || pinProps.activeFrame !== undefined; + const { scrollable, pannable, temporal, clippable, dataview, textview } = this.pinDataTypes(pinDoc); + if (pinProps?.pinDocLayout) { + pinDoc.presPinLayout = true; pinDoc.presX = NumCast(targetDoc.x); pinDoc.presY = NumCast(targetDoc.y); pinDoc.presRot = NumCast(targetDoc.jitterRotation); pinDoc.presWidth = NumCast(targetDoc.width); pinDoc.presHeight = NumCast(targetDoc.height); - - if (scrollable) { - pinDoc.presPinViewScroll = pinDoc._scrollTop; - } + } + if (pinProps?.pinDocContent) { + pinDoc.presPinData = scrollable || temporal || pannable || clippable || dataview || textview || pinProps.activeFrame !== undefined; + if (textview) pinDoc.presData = targetDoc.text instanceof ObjectField ? targetDoc.text[Copy]() : targetDoc.text; + if (scrollable) pinDoc.presPinViewScroll = pinDoc._scrollTop; if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth; + if (dataview) pinDoc.presData = targetDoc.data instanceof ObjectField ? targetDoc.data[Copy]() : targetDoc.data; + if (pannable) { + pinDoc.presPinViewX = NumCast(pinDoc._panX); + pinDoc.presPinViewY = NumCast(pinDoc._panY); + pinDoc.presPinViewScale = NumCast(pinDoc._viewScale, 1); + } if (temporal) { pinDoc.presStartTime = pinDoc._currentTimecode; const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], NumCast(pinDoc.presStartTime) + 0.1); pinDoc.presEndTime = NumCast(pinDoc.clipEnd, duration); } - if (textview) pinDoc.presData = targetDoc.text instanceof ObjectField ? targetDoc.text[Copy]() : targetDoc.text; - if (dataview) pinDoc.presData = targetDoc.data instanceof ObjectField ? targetDoc.data[Copy]() : targetDoc.data; - if (pannable || scrollable) { - const panX = NumCast(pinDoc._panX); - const panY = NumCast(pinDoc._panY); - const pw = NumCast(pinProps.panelWidth); - const ph = NumCast(pinProps.panelHeight); - const ps = NumCast(pinDoc._viewScale, 1); - if (pw && ph && ps) { - pinDoc.presPinViewBounds = new List<number>([panX - pw / 2 / ps, panY - ph / 2 / ps, panX + pw / 2 / ps, panY + ph / 2 / ps]); - } - pinDoc.presPinViewX = panX; - pinDoc.presPinViewY = panY; - pinDoc.presPinViewScale = ps; - } + } + if (pinProps?.pinViewport) { + // If pinWithView option set then update scale and x / y props of slide + const bounds = pinProps.pinViewport; + pinDoc.presPinView = true; + pinDoc.presPinViewScale = NumCast(pinDoc._viewScale, 1); + pinDoc.presPinViewX = bounds.left + bounds.width / 2; + pinDoc.presPinViewY = bounds.top + bounds.height / 2; + pinDoc.presPinViewBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]); } } @@ -497,7 +489,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { }; static NavigateToTarget(targetDoc: Doc, activeItem: Doc, openInTab: any, srcContext: Doc, finished?: () => void) { - if (activeItem.presPinView && DocCast(targetDoc.context)?._currentFrame === undefined) { + if ((activeItem.presPinLayout || activeItem.presPinView) && DocCast(targetDoc.context)?._currentFrame === undefined) { const transTime = NumCast(activeItem.presTransition, 500); const presTransitionTime = `all ${transTime}ms`; targetDoc._dataTransition = presTransitionTime; @@ -514,11 +506,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } else if (targetDoc && activeItem.presMovement !== PresMovement.None) { LightboxView.SetLightboxDoc(undefined); const zooming = activeItem.presMovement !== PresMovement.Pan; - DocumentManager.Instance.jumpToDocument(targetDoc, zooming, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, finished, undefined, true, NumCast(activeItem.presZoom)); + DocumentManager.Instance.jumpToDocument(targetDoc, zooming, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, finished, undefined, true, NumCast(activeItem.presZoom, 1)); + } else if (activeItem.presMovement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) { + (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.(); } // 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. - if (activeItem.presPinView) { + if (activeItem.presPinData || activeItem.presPinView) { clearTimeout(PresBox._navTimer); // targetDoc may or may not be displayed. this gets the first available document (or alias) view that matches targetDoc const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document; @@ -615,42 +609,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { //The function that starts or resets presentaton functionally, depending on presStatus of the layoutDoc @action startAutoPres = (startSlide: number) => { - this.updateCurrentPresentation(); - let activeItem: Doc = this.activeItem; - let targetDoc: Doc = this.targetDoc; - let duration = NumCast(activeItem.presDuration) + NumCast(activeItem.presTransition); - const timer = (ms: number) => new Promise(res => (this._presTimer = setTimeout(res, ms))); - const load = async () => { - // Wrap the loop into an async function for this to work - for (var i = startSlide; i < this.childDocs.length; i++) { - activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); - duration = NumCast(activeItem.presDuration) + NumCast(activeItem.presTransition); - if (duration < 100) { - duration = 2500; - } - if (NumCast(targetDoc.lastFrame) > 0) { - for (var f = 0; f < NumCast(targetDoc.lastFrame); f++) { - await timer(duration / NumCast(targetDoc.lastFrame)); - this.next(); - } - } - - await timer(duration); - this.next(); // then the created Promise can be awaited - if (i === this.childDocs.length - 1) { - setTimeout(() => { - clearTimeout(this._presTimer); - 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 = PresStatus.Autoplay; this.startPresentation(startSlide); - this.gotoDocument(startSlide, activeItem); - load(); + clearTimeout(this._presTimer); + const func = (itemIndex: number) => { + if (itemIndex === this.next()) this.layoutDoc.presStatus = PresStatus.Manual; + else + this._presTimer = setTimeout( + () => this.layoutDoc.presStatus !== PresStatus.Manual && func(this.itemIndex), + NumCast(this.activeItem.presDuration, this.activeItem.type === DocumentType.SCRIPTING ? 0 : 2500) + NumCast(this.activeItem.presTransition) + ); + }; + + func(this.itemIndex); }; // The function pauses the auto presentation @@ -659,7 +630,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (this.layoutDoc.presStatus === PresStatus.Autoplay) { if (this._presTimer) clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; - this.layoutDoc.presLoop = false; this.childDocs.forEach(this.stopTempMedia); } }; @@ -667,7 +637,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { //The function that resets the presentation by removing every action done by it. It also //stops the presentaton. resetPresentation = () => { - this.rootDoc._itemIndex = 0; this.childDocs .map(doc => Cast(doc.presentationTargetDoc, Doc, null)) .filter(doc => doc instanceof Doc) @@ -706,8 +675,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { * @param startIndex: index that the presentation will start at */ startPresentation = (startIndex: number) => { - this.updateCurrentPresentation(); - this.childDocs.map(doc => { + this.childDocs.forEach(doc => { const tagDoc = doc.presentationTargetDoc as Doc; if (doc.presHideBefore && this.childDocs.indexOf(doc) > startIndex) { tagDoc.opacity = 0; @@ -716,6 +684,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { tagDoc.opacity = 0; } }); + this.gotoDocument(startIndex, this.activeItem); }; /** @@ -996,7 +965,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { break; case 'Spacebar': case ' ': - if (this.layoutDoc.presStatus === PresStatus.Manual) this.startAutoPres(this.itemIndex); + if (this.layoutDoc.presStatus === PresStatus.Manual) this.startOrPause(true); else if (this.layoutDoc.presStatus === PresStatus.Autoplay) if (this._presTimer) clearTimeout(this._presTimer); handled = true; break; @@ -1254,7 +1223,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (activeItem && targetDoc) { const type = targetDoc.type; const transitionSpeed = activeItem.presTransition ? NumCast(activeItem.presTransition) / 1000 : 0.5; - const zoom = activeItem.presZoom ? NumCast(activeItem.presZoom) * 100 : 75; + const zoom = NumCast(activeItem.presZoom, 1) * 100; let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 2; if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration); const effect = this.activeItem.presEffect ? this.activeItem.presEffect : 'None'; @@ -1808,6 +1777,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { ); } + scrollFocus = () => { + this.startOrPause(false); + return undefined; + }; + // Case in which the document has keyframes to navigate to next key frame @action nextKeyframe = (tagDoc: Doc, curDoc: Doc): void => { @@ -2480,7 +2454,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <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}> + <div className="presPanel-button" onClick={e => this.startOrPause(true)}> <FontAwesomeIcon icon={this.layoutDoc.presStatus === PresStatus.Autoplay ? 'pause' : 'play'} /> </div> </Tooltip> @@ -2529,7 +2503,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } @action - startOrPause = () => { + startOrPause = (makeActive = true) => { + makeActive && this.updateCurrentPresentation(); if (this.layoutDoc.presStatus === PresStatus.Manual || this.layoutDoc.presStatus === PresStatus.Edit) this.startAutoPres(this.itemIndex); else this.pauseAutoPres(); }; @@ -2613,7 +2588,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <FontAwesomeIcon icon={'arrow-left'} /> </div> <Tooltip title={<div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>}> - <div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.startOrPause, false, false)}> + <div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.startOrPause(true), false, false)}> <FontAwesomeIcon icon={this.layoutDoc.presStatus === 'auto' ? 'pause' : 'play'} /> </div> </Tooltip> diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index e6d08cd53..fe2668492 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -26,6 +26,7 @@ import { PresMovement } from './PresEnums'; import React = require('react'); import { InkField } from '../../../../fields/InkField'; import { RichTextField } from '../../../../fields/RichTextField'; +import { MarqueeView } from '../../collections/collectionFreeForm'; /** * This class models the view a document added to presentation will have in the presentation. * It involves some functionality for its buttons and options. @@ -142,7 +143,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { this.presExpandDocumentClick(); }}> <div className="presItem-groupNum">{`${ind + 1}.`}</div> - {/* style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }} */} <div className="presItem-name"> <EditableView ref={this._titleRef} @@ -304,7 +304,22 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { */ @undoBatch @action - updateView = (targetDoc: Doc, activeItem: Doc) => { + updateCapturedContainerLayout = (targetDoc: Doc, activeItem: Doc) => { + activeItem.presX = NumCast(targetDoc.x); + activeItem.presY = NumCast(targetDoc.y); + activeItem.presRot = NumCast(targetDoc.jitterRotation); + activeItem.presWidth = NumCast(targetDoc.width); + activeItem.presHeight = NumCast(targetDoc.height); + }; + /** + * Method called for updating the view of the currently selected document + * + * @param targetDoc + * @param activeItem + */ + @undoBatch + @action + updateCapturedViewContents = (targetDoc: Doc, activeItem: Doc) => { switch (targetDoc.type) { case DocumentType.PDF: case DocumentType.WEB: @@ -326,20 +341,22 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { const clipWidth = targetDoc._clipWidth; activeItem.presPinClipWidth = clipWidth; break; + case DocumentType.COL: default: - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeItem.presPinViewX = x; - activeItem.presPinViewY = y; - activeItem.presPinViewScale = scale; + const bestView = DocumentManager.Instance.getFirstDocumentView(targetDoc); + if (activeItem.presPinViewBounds && bestView) { + const bounds = MarqueeView.CurViewBounds(targetDoc, bestView.props.PanelWidth(), bestView.props.PanelHeight()); + activeItem.presPinView = true; + activeItem.presPinViewScale = NumCast(targetDoc._viewScale, 1); + activeItem.presPinViewX = bounds.left + bounds.width / 2; + activeItem.presPinViewY = bounds.top + bounds.height / 2; + activeItem.presPinViewBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]); + } else { + activeItem.presPinViewX = targetDoc._panX; + activeItem.presPinViewY = targetDoc._panY; + activeItem.presPinViewScale = targetDoc._viewScale; + } } - - activeItem.presX = NumCast(targetDoc.x); - activeItem.presY = NumCast(targetDoc.y); - activeItem.presRot = NumCast(targetDoc.jitterRotation); - activeItem.presWidth = NumCast(targetDoc.width); - activeItem.presHeight = NumCast(targetDoc.height); }; @computed get recordingIsInOverlay() { @@ -506,16 +523,23 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { {/* <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.transition}</div></Tooltip> */} {/* <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.duration}</div></Tooltip> */} <div className="presItem-slideButtons"> - <Tooltip title={<div className="dash-tooltip">Update view</div>}> - <div className="slideButton" onClick={() => this.updateView(targetDoc, activeItem)} style={{ fontWeight: 700, display: activeItem.presPinView ? 'flex' : 'none' }}> - V + <Tooltip title={<div className="dash-tooltip">Update captured doc layout</div>}> + <div className="slideButton" onClick={() => this.updateCapturedContainerLayout(targetDoc, activeItem)} style={{ fontWeight: 700, display: activeItem.presPinLayout ? 'flex' : 'none' }}> + L </div> </Tooltip> - {!Doc.noviceMode && <Tooltip title={<div className="dash-tooltip">{this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}> - <div className="slideButton" onClick={e => (this.recordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}> - <FontAwesomeIcon icon={`video${this.recordingIsInOverlay ? '-slash' : ''}`} onPointerDown={e => e.stopPropagation()} /> + <Tooltip title={<div className="dash-tooltip">Update captured doc content</div>}> + <div className="slideButton" onClick={() => this.updateCapturedViewContents(targetDoc, activeItem)} style={{ fontWeight: 700, display: activeItem.presPinData || activeItem.presPinView ? 'flex' : 'none' }}> + C </div> - </Tooltip>} + </Tooltip> + {!Doc.noviceMode && ( + <Tooltip title={<div className="dash-tooltip">{this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}> + <div className="slideButton" onClick={e => (this.recordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}> + <FontAwesomeIcon icon={`video${this.recordingIsInOverlay ? '-slash' : ''}`} onPointerDown={e => e.stopPropagation()} /> + </div> + </Tooltip> + )} <Tooltip title={<div className="dash-tooltip">{activeItem.groupWithUp ? 'Ungroup' : 'Group with up'}</div>}> <div className="slideButton" |