diff options
Diffstat (limited to 'src')
32 files changed, 508 insertions, 445 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index 9d3b9eb2b..22c8cb902 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -686,8 +686,9 @@ export function DashColor(color: string) { try { return color ? Color(color.toLowerCase()) : Color('transparent'); } catch (e) { - console.log('COLOR error:', e); - return Color('red'); + if (color.includes('gradient')) console.log("using color 'white' in place of :" + color); + else console.log('COLOR error:', e); + return Color('white'); } } @@ -816,7 +817,7 @@ export function setupMoveUpEvents( (target as any)._noClick = clickEvent(e, (target as any)._doubleTap); } document.removeEventListener('pointermove', _moveEvent); - document.removeEventListener('pointerup', _upEvent); + document.removeEventListener('pointerup', _upEvent, true); }; const _clickEvent = (e: MouseEvent): void => { if ((target as any)._noClick) e.stopPropagation(); @@ -827,6 +828,6 @@ export function setupMoveUpEvents( e.preventDefault(); } document.addEventListener('pointermove', _moveEvent); - document.addEventListener('pointerup', _upEvent); + document.addEventListener('pointerup', _upEvent, true); document.addEventListener('click', _clickEvent, true); } diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 3faa6e11d..debb11066 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -224,6 +224,7 @@ export class DocumentOptions { contextMenuScripts?: List<ScriptField>; contextMenuLabels?: List<string>; contextMenuIcons?: List<string>; + allowClickBeforeDoubleClick?: boolean; // whether a click function can fire before the timeout for a double click has expired dontUndo?: boolean; // whether button clicks should be undoable (this is set to true for Undo/Redo/and sidebar buttons that open the siebar panel) description?: string; // added for links layout?: string | Doc; // default layout string for a document @@ -583,7 +584,7 @@ export namespace Docs { DocumentType.FONTICON, { layout: { view: FontIconBox, dataField: defaultDataKey }, - options: { hideLinkButton: true, _width: 40, _height: 40, borderRounding: '100%', links: '@links(self)' }, + options: { allowClickBeforeDoubleClick: true, hideLinkButton: true, _width: 40, _height: 40, borderRounding: '100%', links: '@links(self)' }, }, ], [ @@ -1808,7 +1809,7 @@ export namespace DocUtils { _yMargin: noMargins ? 0 : undefined, annotationOn, docMaxAutoHeight: maxHeight, - backgroundColor: backgroundColor, + backgroundColor, _width: width || 200, _height: 35, x: x, diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index f678c8936..67c730089 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -258,7 +258,7 @@ export class CurrentUserUtils { }[] = [ {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _autoHeight: true }}, {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200 }}, - {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100 }}, + {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _fitWidth: true }}, {key: "Equation", creator: opts => Docs.Create.EquationDocument(opts), opts: { _width: 300, _height: 35, _backgroundGridShow: true, }}, {key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, useCors: true, }}, {key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }}, @@ -612,6 +612,7 @@ export class CurrentUserUtils { btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) }, { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setFontSize(value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions }, { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, scripts: {script: '{ return setFontColor(value, _readOnly_);}'}}, + { title: "Highlight",toolTip:"Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", ignoreClick: true, scripts: {script: '{ return setFontHighlight(value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} }, { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", scripts: {onClick: '{ return toggleBold(_readOnly_); }'} }, { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", scripts: {onClick: '{ return toggleItalic(_readOnly_);}'} }, { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", scripts: {onClick: '{ return toggleUnderline(_readOnly_);}'} }, @@ -635,9 +636,9 @@ export class CurrentUserUtils { { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts: {onClick:'{ return setActiveTool("write", false, _readOnly_);}'} }, { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts: {onClick:'{ return setActiveTool("eraser", false, _readOnly_);}' }}, // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} }, - { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", true, _readOnly_);}`} }, - { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", true, _readOnly_);}`} }, - { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", true, _readOnly_);}`} }, + { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", true, _readOnly_);}`} }, + { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", true, _readOnly_);}`} }, + { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", true, _readOnly_);}`} }, { title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle", scripts: {onClick:'{ return setIsInkMask(_readOnly_);}'} }, { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, icon: "fill-drip",ignoreClick: true, scripts: {script: '{ return setFillColor(value, _readOnly_);}'} }, { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1}, @@ -668,7 +669,7 @@ export class CurrentUserUtils { 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_, 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 (click to toggle edit mode)",btnType: ButtonType.TextButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { script: '{ return curKeyFrame(_readOnly_);}'}}, + { title: "Num",icon: "",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}}, { 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_)'}}, { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 41f4a17fb..c3ef7570b 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { IconButton } from 'browndash-components'; -import { action, computed, observable, reaction } from 'mobx'; +import { action, computed, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { FaUndo } from 'react-icons/fa'; import { DateField } from '../../fields/DateField'; @@ -66,7 +66,19 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P DocumentDecorations.Instance = this; reaction( () => SelectionManager.Views().slice(), - action(docs => (this._editingTitle = false)) + action(docs => { + docs.length > 1 && (this._showNothing = false); // show decorations if multiple docs are selected + this._editingTitle = false; + }) + ); + document.addEventListener( + // show decorations whenever pointer moves outside of selection bounds. + 'pointermove', + action(e => { + if (this.Bounds.x !== Number.MAX_VALUE && (this.Bounds.x > e.clientX || this.Bounds.r < e.clientX || this.Bounds.y > e.clientY || this.Bounds.b < e.clientY)) { + this._showNothing = false; + } + }) ); } @@ -182,7 +194,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P dragData.removeDocument = dragDocView.props.removeDocument; dragData.isDocDecorationMove = true; dragData.canEmbed = dragTitle; - this._hidden = this.Interacting = true; + this._hidden = true; DragManager.StartDocumentDrag( SelectionManager.Views().map(dv => dv.ContentDiv!), dragData, @@ -191,7 +203,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P { dragComplete: action(e => { dragData.canEmbed && SelectionManager.DeselectAll(); - this._hidden = this.Interacting = false; + this._hidden = false; }), hideSource: true, } @@ -293,7 +305,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P */ @action onRadiusDown = (e: React.PointerEvent): void => { - this._isRounding = true; + this._isRounding = DocumentDecorations.Instance.Interacting = true; this._resizeUndo = UndoManager.StartBatch('DocDecs set radius'); // Call util move event function setupMoveUpEvents( @@ -316,10 +328,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P return false; }, // moveEvent action(e => { - this._isRounding = false; + DocumentDecorations.Instance.Interacting = this._isRounding = false; this._resizeUndo?.end(); }), // upEvent - e => {} // clickEvent + e => {}, // clickEvent, + true ); }; @@ -710,11 +723,14 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P return this._rotCenter; } + @observable _showNothing = true; + render() { const { b, r, x, y } = this.Bounds; const bounds = { b, r, x, y }; - const seldocview = SelectionManager.Views().slice(-1)[0]; + const seldocview = SelectionManager.Views().lastElement(); if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { + setTimeout(action(() => (this._showNothing = true))); return null; } // hide the decorations if the parent chooses to hide it or if the document itself hides it @@ -812,7 +828,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P </div> ); return ( - <div className={`documentDecorations${colorScheme}`}> + <div className={`documentDecorations${colorScheme}`} style={{ opacity: this._showNothing ? 0.1 : undefined }}> <div className="documentDecorations-background" style={{ diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index d036a636a..9447b2e72 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -1,23 +1,23 @@ -import React = require("react"); -import { action, observable } from "mobx"; -import { observer } from "mobx-react"; -import { Doc } from "../../fields/Doc"; -import { ControlPoint, InkData, PointData, InkField } from "../../fields/InkField"; -import { List } from "../../fields/List"; -import { listSpec } from "../../fields/Schema"; -import { Cast, NumCast } from "../../fields/Types"; -import { setupMoveUpEvents, returnFalse } from "../../Utils"; -import { Transform } from "../util/Transform"; -import { UndoManager } from "../util/UndoManager"; -import { Colors } from "./global/globalEnums"; -import { InkingStroke } from "./InkingStroke"; -import { InkStrokeProperties } from "./InkStrokeProperties"; -import { DocumentView } from "./nodes/DocumentView"; -import { SelectionManager } from "../util/SelectionManager"; +import React = require('react'); +import { action, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc } from '../../fields/Doc'; +import { ControlPoint, InkData, PointData, InkField } from '../../fields/InkField'; +import { List } from '../../fields/List'; +import { listSpec } from '../../fields/Schema'; +import { Cast, NumCast } from '../../fields/Types'; +import { setupMoveUpEvents, returnFalse } from '../../Utils'; +import { Transform } from '../util/Transform'; +import { UndoManager } from '../util/UndoManager'; +import { Colors } from './global/globalEnums'; +import { InkingStroke } from './InkingStroke'; +import { InkStrokeProperties } from './InkStrokeProperties'; +import { DocumentView } from './nodes/DocumentView'; +import { SelectionManager } from '../util/SelectionManager'; export interface InkControlProps { inkDoc: Doc; - inkView: DocumentView; + inkView: InkingStroke; inkCtrlPoints: InkData; screenCtrlPoints: InkData; screenSpaceLineWidth: number; @@ -26,16 +26,16 @@ export interface InkControlProps { @observer export class InkControlPtHandles extends React.Component<InkControlProps> { - @observable private _overControl = -1; - - @observable controlUndo: UndoManager.Batch | undefined; + get docView() { + return this.props.inkView.props.docViewPath().lastElement(); + } componentDidMount() { - document.addEventListener("keydown", this.onDelete, true); + document.addEventListener('keydown', this.onDelete, true); } componentWillUnmount() { - document.removeEventListener("keydown", this.onDelete, true); + document.removeEventListener('keydown', this.onDelete, true); } /** * Handles the movement of a selected control point when the user clicks and drags. @@ -43,30 +43,32 @@ export class InkControlPtHandles extends React.Component<InkControlProps> { */ @action onControlDown = (e: React.PointerEvent, controlIndex: number): void => { - const ptFromScreen = this.props.inkView.ComponentView?.ptFromScreen; + const ptFromScreen = this.props.inkView.ptFromScreen; if (ptFromScreen) { const order = controlIndex % 4; const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length; const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length; - const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); + const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec('number')); const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex; if (!wasSelected) InkStrokeProperties.Instance._currentPoint = -1; const origInk = this.props.inkCtrlPoints.slice(); - setupMoveUpEvents(this, e, + setupMoveUpEvents( + this, + e, action((e: PointerEvent, down: number[], delta: number[]) => { - if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("drag ink ctrl pt"); + if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('drag ink ctrl pt'); const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] }); const inkMoveStart = ptFromScreen({ X: 0, Y: 0 }); - InkStrokeProperties.Instance.moveControlPtHandle(this.props.inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex, origInk); + InkStrokeProperties.Instance.moveControlPtHandle(this.docView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex, origInk); return false; }), action(() => { - if (this.controlUndo) { - InkStrokeProperties.Instance.snapControl(this.props.inkView, controlIndex); + if (this.props.inkView.controlUndo) { + InkStrokeProperties.Instance.snapControl(this.docView, controlIndex); } - this.controlUndo?.end(); - this.controlUndo = undefined; - UndoManager.FilterBatches(["data", "x", "y", "width", "height"]); + this.props.inkView.controlUndo?.end(); + this.props.inkView.controlUndo = undefined; + UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']); }), action((e: PointerEvent, doubleTap: boolean | undefined) => { const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex; @@ -76,44 +78,52 @@ export class InkControlPtHandles extends React.Component<InkControlProps> { else this.props.inkDoc.brokenInkIndices = new List<number>([controlIndex]); } else { if (brokenIndices?.includes(equivIndex)) { - if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth"); - InkStrokeProperties.Instance.snapHandleTangent(this.props.inkView, equivIndex, handleIndexA, handleIndexB); + if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('make smooth'); + InkStrokeProperties.Instance.snapHandleTangent(this.docView, equivIndex, handleIndexA, handleIndexB); } if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) { - if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth"); - InkStrokeProperties.Instance.snapHandleTangent(this.props.inkView, controlIndex, handleIndexA, handleIndexB); + if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('make smooth'); + InkStrokeProperties.Instance.snapHandleTangent(this.docView, controlIndex, handleIndexA, handleIndexB); } } - this.controlUndo?.end(); - this.controlUndo = undefined; + this.props.inkView.controlUndo?.end(); + this.props.inkView.controlUndo = undefined; } this.changeCurrPoint(controlIndex); - }), undefined, undefined, () => wasSelected && this.changeCurrPoint(-1)); + }), + undefined, + undefined, + () => wasSelected && this.changeCurrPoint(-1) + ); } - } + }; /** * Updates whether a user has hovered over a particular control point or point that could be added * on click. */ - @action onEnterControl = (i: number) => { this._overControl = i; }; - @action onLeaveControl = () => { this._overControl = -1; }; + @action onEnterControl = (i: number) => { + this._overControl = i; + }; + @action onLeaveControl = () => { + this._overControl = -1; + }; /** * Deletes the currently selected point. */ @action onDelete = (e: KeyboardEvent) => { - if (["-", "Backspace", "Delete"].includes(e.key)) { - InkStrokeProperties.Instance.deletePoints(this.props.inkView, e.shiftKey); + if (['-', 'Backspace', 'Delete'].includes(e.key)) { + InkStrokeProperties.Instance.deletePoints(this.docView, e.shiftKey); e.stopPropagation(); } - } + }; /** * Changes the current selected control point. */ @action - changeCurrPoint = (i: number) => InkStrokeProperties.Instance._currentPoint = i + changeCurrPoint = (i: number) => (InkStrokeProperties.Instance._currentPoint = i); render() { // Accessing the current ink's data and extracting all control points. @@ -133,101 +143,115 @@ export class InkControlPtHandles extends React.Component<InkControlProps> { const closed = InkingStroke.IsClosed(inkData); const nearestScreenPt = this.props.nearestScreenPt(); - const TagType = (broken?: boolean) => broken ? "rect" : "circle"; - const hdl = (control: { X: number, Y: number, I: number }, scale: number, color: string) => { - const broken = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number"))?.includes(control.I); + const TagType = (broken?: boolean) => (broken ? 'rect' : 'circle'); + const hdl = (control: { X: number; Y: number; I: number }, scale: number, color: string) => { + const broken = Cast(this.props.inkDoc.brokenInkIndices, listSpec('number'))?.includes(control.I); const Tag = TagType((control.I === 0 || control.I === inkData.length - 1) && !closed) as keyof JSX.IntrinsicElements; - return <Tag key={control.I.toString() + scale} - x={control.X - this.props.screenSpaceLineWidth * 2 * scale} - y={control.Y - this.props.screenSpaceLineWidth * 2 * scale} - cx={control.X} - cy={control.Y} - r={this.props.screenSpaceLineWidth * 2 * scale} - opacity={this.controlUndo ? 0.15 : 1} - height={this.props.screenSpaceLineWidth * 4 * scale} - width={this.props.screenSpaceLineWidth * 4 * scale} - strokeWidth={this.props.screenSpaceLineWidth / 2} - stroke={Colors.MEDIUM_BLUE} - fill={broken ? Colors.MEDIUM_BLUE : color} - onPointerDown={(e: React.PointerEvent) => this.onControlDown(e, control.I)} - onMouseEnter={() => this.onEnterControl(control.I)} - onMouseLeave={this.onLeaveControl} - pointerEvents="all" - cursor="default" - />; - }; - return (<svg> - {!nearestScreenPt ? (null) : - <circle key={"npt"} - cx={nearestScreenPt.X} - cy={nearestScreenPt.Y} - r={this.props.screenSpaceLineWidth * 2} - fill={"#00007777"} - stroke={"#00007777"} - strokeWidth={0} - pointerEvents="none" + return ( + <Tag + key={control.I.toString() + scale} + x={control.X - this.props.screenSpaceLineWidth * 2 * scale} + y={control.Y - this.props.screenSpaceLineWidth * 2 * scale} + cx={control.X} + cy={control.Y} + r={this.props.screenSpaceLineWidth * 2 * scale} + opacity={this.props.inkView.controlUndo ? 0.15 : 1} + height={this.props.screenSpaceLineWidth * 4 * scale} + width={this.props.screenSpaceLineWidth * 4 * scale} + strokeWidth={this.props.screenSpaceLineWidth / 2} + stroke={Colors.MEDIUM_BLUE} + fill={broken ? Colors.MEDIUM_BLUE : color} + onPointerDown={(e: React.PointerEvent) => this.onControlDown(e, control.I)} + onMouseEnter={() => this.onEnterControl(control.I)} + onMouseLeave={this.onLeaveControl} + pointerEvents="all" + cursor="default" /> - } - {sreenCtrlPoints.map(control => hdl(control, this._overControl !== control.I ? 1 : 3 / 2, Colors.WHITE))} - </svg> + ); + }; + return ( + <svg> + {!nearestScreenPt ? null : <circle key={'npt'} cx={nearestScreenPt.X} cy={nearestScreenPt.Y} r={this.props.screenSpaceLineWidth * 2} fill={'#00007777'} stroke={'#00007777'} strokeWidth={0} pointerEvents="none" />} + {sreenCtrlPoints.map(control => hdl(control, this._overControl !== control.I ? 1 : 3 / 2, Colors.WHITE))} + </svg> ); } } - export interface InkEndProps { inkDoc: Doc; - inkView: DocumentView; + inkView: InkingStroke; screenSpaceLineWidth: number; startPt: PointData; endPt: PointData; } @observer export class InkEndPtHandles extends React.Component<InkEndProps> { - @observable controlUndo: UndoManager.Batch | undefined; @observable _overStart: boolean = false; @observable _overEnd: boolean = false; @action - dragRotate = (e: React.PointerEvent, p1: () => { X: number, Y: number }, p2: () => { X: number, Y: number }) => { - setupMoveUpEvents(this, e, (e) => { - if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("stretch ink"); - // compute stretch factor by finding scaling along axis between start and end points - const v1 = { X: p1().X - p2().X, Y: p1().Y - p2().Y }; - const v2 = { X: e.clientX - p2().X, Y: e.clientY - p2().Y }; - const v1len = Math.sqrt(v1.X * v1.X + v1.Y * v1.Y); - const v2len = Math.sqrt(v2.X * v2.X + v2.Y * v2.Y); - const scaling = v2len / v1len; - const v1n = { X: v1.X / v1len, Y: v1.Y / v1len }; - const v2n = { X: v2.X / v2len, Y: v2.Y / v2len }; - const angle = Math.acos(v1n.X * v2n.X + v1n.Y * v2n.Y) * Math.sign(v1.X * v2.Y - v2.X * v1.Y); - InkStrokeProperties.Instance.stretchInk(SelectionManager.Views(), scaling, p2(), v1n, e.shiftKey); - InkStrokeProperties.Instance.rotateInk(SelectionManager.Views(), angle, p2()); - return false; - }, action(() => { - this.controlUndo?.end(); - this.controlUndo = undefined; - UndoManager.FilterBatches(["data", "x", "y", "width", "height"]); - }), returnFalse); - } + dragRotate = (e: React.PointerEvent, p1: () => { X: number; Y: number }, p2: () => { X: number; Y: number }) => { + setupMoveUpEvents( + this, + e, + action(e => { + if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('stretch ink'); + // compute stretch factor by finding scaling along axis between start and end points + const v1 = { X: p1().X - p2().X, Y: p1().Y - p2().Y }; + const v2 = { X: e.clientX - p2().X, Y: e.clientY - p2().Y }; + const v1len = Math.sqrt(v1.X * v1.X + v1.Y * v1.Y); + const v2len = Math.sqrt(v2.X * v2.X + v2.Y * v2.Y); + const scaling = v2len / v1len; + const v1n = { X: v1.X / v1len, Y: v1.Y / v1len }; + const v2n = { X: v2.X / v2len, Y: v2.Y / v2len }; + const angle = Math.acos(v1n.X * v2n.X + v1n.Y * v2n.Y) * Math.sign(v1.X * v2.Y - v2.X * v1.Y); + InkStrokeProperties.Instance.stretchInk(SelectionManager.Views(), scaling, p2(), v1n, e.shiftKey); + InkStrokeProperties.Instance.rotateInk(SelectionManager.Views(), angle, p2()); + return false; + }), + action(() => { + this.props.inkView.controlUndo?.end(); + this.props.inkView.controlUndo = undefined; + UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']); + }), + returnFalse + ); + }; render() { - const hdl = (key: string, pt: PointData, dragFunc: (e: React.PointerEvent) => void) => <circle key={key} - cx={pt.X} - cy={pt.Y} - r={this.props.screenSpaceLineWidth * 2} - fill={this._overStart ? "#aaaaaa" : "#99999977"} - stroke={"#00007777"} - strokeWidth={0} - onPointerLeave={action(() => this._overStart = false)} - onPointerEnter={action(() => this._overStart = true)} - onPointerDown={dragFunc} - pointerEvents="all" - />; - return (<svg> - {hdl("start", this.props.startPt, (e: React.PointerEvent) => this.dragRotate(e, () => this.props.startPt, () => this.props.endPt))} - {hdl("end", this.props.endPt, (e: React.PointerEvent) => this.dragRotate(e, () => this.props.endPt, () => this.props.startPt))} - </svg> + const hdl = (key: string, pt: PointData, dragFunc: (e: React.PointerEvent) => void) => ( + <circle + key={key} + cx={pt.X} + cy={pt.Y} + r={this.props.screenSpaceLineWidth * 2} + fill={this._overStart ? '#aaaaaa' : '#99999977'} + stroke={'#00007777'} + strokeWidth={0} + onPointerLeave={action(() => (this._overStart = false))} + onPointerEnter={action(() => (this._overStart = true))} + onPointerDown={dragFunc} + pointerEvents="all" + /> + ); + return ( + <svg> + {hdl('start', this.props.startPt, (e: React.PointerEvent) => + this.dragRotate( + e, + () => this.props.startPt, + () => this.props.endPt + ) + )} + {hdl('end', this.props.endPt, (e: React.PointerEvent) => + this.dragRotate( + e, + () => this.props.endPt, + () => this.props.startPt + ) + )} + </svg> ); } -}
\ No newline at end of file +} diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index ae35bc980..c4a2f603e 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -1,21 +1,21 @@ -import React = require("react"); -import { action } from "mobx"; -import { observer } from "mobx-react"; -import { Doc } from "../../fields/Doc"; -import { HandleLine, HandlePoint, InkData } from "../../fields/InkField"; -import { List } from "../../fields/List"; -import { listSpec } from "../../fields/Schema"; -import { Cast } from "../../fields/Types"; -import { emptyFunction, setupMoveUpEvents } from "../../Utils"; -import { Transform } from "../util/Transform"; -import { UndoManager } from "../util/UndoManager"; -import { Colors } from "./global/globalEnums"; -import { InkingStroke } from "./InkingStroke"; -import { InkStrokeProperties } from "./InkStrokeProperties"; -import { DocumentView } from "./nodes/DocumentView"; +import React = require('react'); +import { action } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc } from '../../fields/Doc'; +import { HandleLine, HandlePoint, InkData } from '../../fields/InkField'; +import { List } from '../../fields/List'; +import { listSpec } from '../../fields/Schema'; +import { Cast } from '../../fields/Types'; +import { emptyFunction, setupMoveUpEvents } from '../../Utils'; +import { Transform } from '../util/Transform'; +import { UndoManager } from '../util/UndoManager'; +import { Colors } from './global/globalEnums'; +import { InkingStroke } from './InkingStroke'; +import { InkStrokeProperties } from './InkStrokeProperties'; +import { DocumentView } from './nodes/DocumentView'; export interface InkHandlesProps { inkDoc: Doc; - inkView: DocumentView; + inkView: InkingStroke; screenCtrlPoints: InkData; screenSpaceLineWidth: number; ScreenToLocalTransform: () => Transform; @@ -23,49 +23,51 @@ export interface InkHandlesProps { @observer export class InkTangentHandles extends React.Component<InkHandlesProps> { + get docView() { + return this.props.inkView.props.docViewPath().lastElement(); + } /** * Handles the movement of a selected handle point when the user clicks and drags. * @param handleNum The index of the currently selected handle point. */ onHandleDown = (e: React.PointerEvent, handleIndex: number): void => { - const ptFromScreen = this.props.inkView.ComponentView?.ptFromScreen; - if (!ptFromScreen) return; - const screenScale = this.props.ScreenToLocalTransform().Scale; - var controlUndo: UndoManager.Batch | undefined; const order = handleIndex % 4; const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3; const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length; const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.screenCtrlPoints.length; - setupMoveUpEvents(this, e, + setupMoveUpEvents( + this, + e, (e: PointerEvent, down: number[], delta: number[]) => { - if (!controlUndo) controlUndo = UndoManager.StartBatch("DocDecs move tangent"); + if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('DocDecs move tangent'); if (e.altKey) this.onBreakTangent(controlIndex); - const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] }); - const inkMoveStart = ptFromScreen({ X: 0, Y: 0 }); - InkStrokeProperties.Instance.moveTangentHandle(this.props.inkView, -(inkMoveEnd.X - inkMoveStart.X), -(inkMoveEnd.Y - inkMoveStart.Y), handleIndex, oppositeHandleIndex, controlIndex); + const inkMoveEnd = this.props.inkView.ptFromScreen({ X: delta[0], Y: delta[1] }); + const inkMoveStart = this.props.inkView.ptFromScreen({ X: 0, Y: 0 }); + InkStrokeProperties.Instance.moveTangentHandle(this.docView, -(inkMoveEnd.X - inkMoveStart.X), -(inkMoveEnd.Y - inkMoveStart.Y), handleIndex, oppositeHandleIndex, controlIndex); return false; - }, () => { - controlUndo?.end(); - UndoManager.FilterBatches(["data", "x", "y", "width", "height"]); - }, emptyFunction + }, + () => { + this.props.inkView.controlUndo?.end(); + UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']); + }, + emptyFunction ); - } + }; /** - * Breaks tangent handle movement when ‘Alt’ key is held down. Adds the current handle index and + * Breaks tangent handle movement when ‘Alt’ key is held down. Adds the current handle index and * its matching (opposite) handle to a list of broken handle indices. * @param handleNum The index of the currently selected handle point. */ @action onBreakTangent = (controlIndex: number) => { const closed = InkingStroke.IsClosed(this.props.screenCtrlPoints); - const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); - if (!brokenIndices?.includes(controlIndex) && - ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) { + const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec('number')); + if (!brokenIndices?.includes(controlIndex) && ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) { if (brokenIndices) brokenIndices.push(controlIndex); else this.props.inkDoc.brokenInkIndices = new List<number>([controlIndex]); } - } + }; render() { // Accessing the current ink's data and extracting all handle points and handle lines. @@ -81,10 +83,18 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> { // Adding first and last (single) handle lines. if (closed) { tangentLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 }); - } - else { + } else { tangentLines.push({ X1: data[0].X, Y1: data[0].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: 0 }); - tangentLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 }); + tangentLines.push({ + X1: data[data.length - 2].X, + Y1: data[data.length - 2].Y, + X2: data[data.length - 1].X, + Y2: data[data.length - 1].Y, + X3: data[data.length - 1].X, + Y3: data[data.length - 1].Y, + dot1: data.length - 1, + dot2: data.length - 1, + }); } for (let i = 2; i < data.length - 4; i += 4) { tangentLines.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 }); @@ -94,7 +104,7 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> { return ( <> - {tangentHandles.map((pts, i) => + {tangentHandles.map((pts, i) => ( <svg height="10" width="10" key={`hdl${i}`}> <circle cx={pts.X} @@ -106,25 +116,31 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> { onPointerDown={e => this.onHandleDown(e, pts.I)} pointerEvents="all" cursor="default" - display={(pts.dot1 === InkStrokeProperties.Instance._currentPoint || pts.dot2 === InkStrokeProperties.Instance._currentPoint) ? "inherit" : "none"} /> - </svg>)} + display={pts.dot1 === InkStrokeProperties.Instance._currentPoint || pts.dot2 === InkStrokeProperties.Instance._currentPoint ? 'inherit' : 'none'} + /> + </svg> + ))} {tangentLines.map((pts, i) => { - const tangentLine = (x1: number, y1: number, x2: number, y2: number) => + const tangentLine = (x1: number, y1: number, x2: number, y2: number) => ( <line x1={x1} y1={y1} x2={x2} y2={y2} stroke={Colors.MEDIUM_BLUE} - strokeDasharray={"1 1"} + strokeDasharray={'1 1'} strokeWidth={1} - display={(pts.dot1 === InkStrokeProperties.Instance._currentPoint || pts.dot2 === InkStrokeProperties.Instance._currentPoint) ? "inherit" : "none"} />; - return <svg height="100" width="100" key={`line${i}`}> - {tangentLine(pts.X1, pts.Y1, pts.X2, pts.Y2)} - {tangentLine(pts.X2, pts.Y2, pts.X3, pts.Y3)} - </svg>; + display={pts.dot1 === InkStrokeProperties.Instance._currentPoint || pts.dot2 === InkStrokeProperties.Instance._currentPoint ? 'inherit' : 'none'} + /> + ); + return ( + <svg height="100" width="100" key={`line${i}`}> + {tangentLine(pts.X1, pts.Y1, pts.X2, pts.Y2)} + {tangentLine(pts.X2, pts.Y2, pts.X3, pts.Y3)} + </svg> + ); })} </> ); } -}
\ No newline at end of file +} diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 8291ff3f1..76c1aa80a 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -124,6 +124,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() { public static toggleMask = action((inkDoc: Doc) => { inkDoc.isInkMask = !inkDoc.isInkMask; }); + @observable controlUndo: UndoManager.Batch | undefined; /** * Drags the a simple bezier segment of the stroke. * Also adds a control point when double clicking on the stroke. @@ -143,15 +144,15 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() { const { nearestSeg } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY }); const controlIndex = nearestSeg; const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex; - var controlUndo: UndoManager.Batch | undefined; const isEditing = InkStrokeProperties.Instance._controlButton && this.props.isSelected(); + this.controlUndo = undefined; setupMoveUpEvents( this, e, !isEditing ? returnFalse : action((e: PointerEvent, down: number[], delta: number[]) => { - if (!controlUndo) controlUndo = UndoManager.StartBatch('drag ink ctrl pt'); + if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch('drag ink ctrl pt'); const inkMoveEnd = this.ptFromScreen({ X: delta[0], Y: delta[1] }); const inkMoveStart = this.ptFromScreen({ X: 0, Y: 0 }); InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex); @@ -161,8 +162,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() { !isEditing ? returnFalse : action(() => { - controlUndo?.end(); - controlUndo = undefined; + this.controlUndo?.end(); + this.controlUndo = undefined; UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']); }), action((e: PointerEvent, doubleTap: boolean | undefined) => { @@ -304,7 +305,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() { return SnappingManager.GetIsDragging() ? null : !InkStrokeProperties.Instance._controlButton ? ( !this.props.isSelected() || InkingStroke.IsClosed(inkData) ? null : ( <div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}> - <InkEndPtHandles inkView={this.props.docViewPath().lastElement()} inkDoc={inkDoc} startPt={screenPts[0]} endPt={screenPts.lastElement()} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} /> + <InkEndPtHandles inkView={this} inkDoc={inkDoc} startPt={screenPts[0]} endPt={screenPts.lastElement()} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} /> </div> ) ) : ( @@ -331,15 +332,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() { 1.0, false )} - <InkControlPtHandles - inkView={this.props.docViewPath().lastElement()} - inkDoc={inkDoc} - inkCtrlPoints={inkData} - screenCtrlPoints={screenHdlPts} - nearestScreenPt={this.nearestScreenPt} - screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} - /> - <InkTangentHandles inkView={this.props.docViewPath().lastElement()} inkDoc={inkDoc} screenCtrlPoints={screenHdlPts} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} ScreenToLocalTransform={this.screenToLocal} /> + <InkControlPtHandles inkView={this} inkDoc={inkDoc} inkCtrlPoints={inkData} screenCtrlPoints={screenHdlPts} nearestScreenPt={this.nearestScreenPt} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} /> + <InkTangentHandles inkView={this} inkDoc={inkDoc} screenCtrlPoints={screenHdlPts} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} ScreenToLocalTransform={this.screenToLocal} /> </div> ); }; @@ -389,7 +383,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() { 1.0, false ); - const highlight = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting); + const highlight = !this.controlUndo && this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting); const highlightIndex = highlight?.highlightIndex; const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : StrCast(this.layoutDoc.strokeOutlineColor, !closed && fillColor && fillColor !== 'transparent' ? StrCast(this.layoutDoc.color, 'transparent') : 'transparent'); // Invisible polygonal line that enables the ink to be selected by the user. diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index e0b4c5159..6b18caed0 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -22,7 +22,8 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0 }; // bcz: not sure (async () => { MainView.Live = window.location.search.includes('live'); - ReactDOM.createRoot(document.getElementById('root')!).render(<FieldLoader />); + const root = ReactDOM.createRoot(document.getElementById('root')!); + root.render(<FieldLoader />); window.location.search.includes('safe') && CollectionView.SetSafeMode(true); const info = await CurrentUserUtils.loadCurrentUser(); if (info.email === 'guest') DocServer.Control.makeReadOnly(); @@ -47,6 +48,6 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0 }; // bcz: not sure new LinkManager(); new TrackMovements(); new ReplayMovements(); - ReactDOM.createRoot(document.getElementById('root')!).render(<MainView />); + root.render(<MainView />); }, 0); })(); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 895ed9bda..625dc2748 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -606,7 +606,7 @@ export class MainView extends React.Component { @computed get mainDocView() { return ( <> - {this._hideUI ? null : this.headerBarDocView} + {this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView} <DocumentView key="main" Document={this.mainContainer!} diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 5ab91dd70..3bdf65d01 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -49,7 +49,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { super(props); AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight('', true), true); - AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true)); + AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true)); AnchorMenu.Instance.OnAudio = unimplementedFunction; AnchorMenu.Instance.Highlight = this.highlight; AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true); diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index a2bc37095..203b42cbc 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1814,12 +1814,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { </div> {!selectedItem ? null : ( <div className="propertiesView-presTrails"> - <div - className="propertiesView-presTrails-title" - onPointerDown={action(() => { - this.openPresTransitions = !this.openPresTransitions; - })} - style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}> + <div className="propertiesView-presTrails-title" onPointerDown={action(() => (this.openPresTransitions = !this.openPresTransitions))} style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}> <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> Transitions <div className="propertiesView-presTrails-title-icon"> <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" color="white" /> @@ -1830,12 +1825,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { )} {!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? null : ( <div className="propertiesView-presTrails"> - <div - className="propertiesView-presTrails-title" - onPointerDown={action(() => { - this.openSlideOptions = !this.openSlideOptions; - })} - style={{ backgroundColor: this.openSlideOptions ? 'black' : '' }}> + <div className="propertiesView-presTrails-title" onPointerDown={action(() => (this.openSlideOptions = !this.openSlideOptions))} style={{ backgroundColor: this.openSlideOptions ? 'black' : '' }}> <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={type === DocumentType.AUDIO ? 'file-audio' : 'file-video'} /> {type === DocumentType.AUDIO ? 'Audio Options' : 'Video Options'} <div className="propertiesView-presTrails-title-icon"> <FontAwesomeIcon icon={this.openSlideOptions ? 'caret-down' : 'caret-right'} size="lg" color="white" /> diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 6d06bbbf6..b9af28413 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -88,6 +88,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> { }; }); + if (!anchor.text) Doc.GetProto(anchor).text = '-selection-'; const textLines: any = [ { type: 'paragraph', @@ -121,16 +122,18 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> { content: taggedContent, }; if (taggedContent.length) textLines.push(metadatatext); - Doc.GetProto(target).text = new RichTextField( - JSON.stringify({ - doc: { - type: 'doc', - content: textLines, - }, - selection: { type: 'text', anchor: 4, head: 4 }, // set selection to middle paragraph - }), - '' - ); + if (textLines.length) { + Doc.GetProto(target).text = new RichTextField( + JSON.stringify({ + doc: { + type: 'doc', + content: textLines, + }, + selection: { type: 'text', anchor: 4, head: 4 }, // set selection to middle paragraph + }), + '' + ); + } this.addDocument(target); setTimeout(() => this._stackRef.current?.focusDocument(target, {})); return target; @@ -233,7 +236,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> { isAnnotationOverlay={false} select={emptyFunction} NativeDimScaling={returnOne} - childShowTitle={this.showTitle} + //childShowTitle={this.showTitle} childDocumentsActive={this.props.isContentActive} whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} childHideDecorationTitle={returnTrue} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 3cb920ba0..817baeae6 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -19,6 +19,7 @@ import { SliderBox } from './nodes/SliderBox'; import './StyleProvider.scss'; import React = require('react'); import { Shadows } from 'browndash-components'; +import { SelectionManager } from '../util/SelectionManager'; export enum StyleProp { TreeViewIcon = 'treeViewIcon', @@ -116,7 +117,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps const excludeTypes = [DocumentType.FONTICON]; let highlighting = !props?.disableDocBrushing && highlightIndex && !excludeTypes.includes(doc.type as any) && doc._viewType !== CollectionViewType.Linear; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way if (highlighting && props?.focus !== emptyFunction && StrCast(doc.title) !== '[pres element template]') { - return { highlightStyle, highlightColor, highlightIndex, highlightStroke: doc.type === DocumentType.INK }; + return { highlightStyle, highlightColor: SelectionManager.Views().some(dv => dv.rootDoc === doc) ? 'black' : highlightColor, highlightIndex, highlightStroke: doc.type === DocumentType.INK }; } } return undefined; diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index e2594b6ae..17f02711d 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -15,6 +15,7 @@ import { RichTextField } from '../../../fields/RichTextField'; import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; +import { GestureUtils } from '../../../pen-gestures/GestureUtils'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; @@ -40,7 +41,7 @@ import { CollectionLinearView } from './collectionLinear'; import './CollectionMenu.scss'; import { COLLECTION_BORDER_WIDTH } from './CollectionView'; import { TabDocView } from './TabDocView'; -import { GestureUtils } from '../../../pen-gestures/GestureUtils'; +import { CollectionFreeFormView } from './collectionFreeForm'; interface CollectionMenuProps { panelHeight: () => number; @@ -733,7 +734,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView doc._currentFrame = 0; CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); } - CollectionFreeFormDocumentView.updateKeyframe(undefined, childDocs, currentFrame || 0); + CollectionFreeFormView.updateKeyframe(undefined, [...childDocs, doc], currentFrame || 0); doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame); } } @@ -745,9 +746,9 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView const currentFrame = Cast(this.document._currentFrame, 'number', null); if (currentFrame === undefined) { this.document._currentFrame = 0; - CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); + CollectionFreeFormDocumentView.setupKeyframes(this.c hildDocs, 0); } - this._keyTimer = CollectionFreeFormDocumentView.updateKeyframe(this._keyTimer, this.childDocs, currentFrame || 0); + this._keyTimer = CollectionFreeFormView.updateKeyframe(this._keyTimer, [...this.childDocs, this.document], currentFrame || 0); this.document._currentFrame = Math.max(0, (currentFrame || 0) + 1); this.document.lastFrame = Math.max(NumCast(this.document._currentFrame), NumCast(this.document.lastFrame)); }; @@ -759,7 +760,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView this.document._currentFrame = 0; CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); } - this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, this.childDocs.slice()); + this._keyTimer = CollectionFreeFormView.gotoKeyframe(this._keyTimer, [...this.childDocs, this.document], 1000); this.document._currentFrame = Math.max(0, (currentFrame || 0) - 1); }; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 2f495d55c..6314b4529 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -129,6 +129,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection TraceMobx(); // appears that we are going to reset the _docXfs. TODO: what is Xfs? this._docXfs.length = 0; + this._renderCount < docs.length && setTimeout(action(() => (this._renderCount = Math.min(docs.length, this._renderCount + 5)))); return docs.map((d, i) => { const height = () => this.getDocHeight(d); const width = () => this.getDocWidth(d); @@ -139,7 +140,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection // So we're choosing whether we're going to render a column or a masonry doc return ( <div className={`collectionStackingView-${this.isStackingView ? 'columnDoc' : 'masonryDoc'}`} key={d[Id]} style={style}> - {this.getDisplayDoc(d, width)} + {this.getDisplayDoc(d, width, i)} </div> ); }); @@ -297,12 +298,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection }; isContentActive = () => (this.props.isSelected() || this.props.isContentActive() ? true : this.props.isSelected() === false || this.props.isContentActive() === false ? false : undefined); + @observable _renderCount = 5; isChildContentActive = () => this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined; isChildButtonContentActive = () => (this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined); // this is what renders the document that you see on the screen // called in Children: this actually adds a document to our children list - getDisplayDoc(doc: Doc, width: () => number) { + getDisplayDoc(doc: Doc, width: () => number, count: number) { const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); @@ -310,7 +312,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection const stackedDocTransform = () => this.getDocTransform(doc, dref); this._docXfs.push({ stackedDocTransform, width, height }); //DocumentView is how the node will be rendered - return ( + return count > this._renderCount ? null : ( <DocumentView ref={r => (dref = r || undefined)} Document={doc} diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 1a265af4a..330e116d1 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -259,11 +259,13 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); }; headerFields = () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields); + @observable _renderCount = 1; @computed get treeViewElements() { TraceMobx(); const dropAction = StrCast(this.doc.childDropAction) as dropActionType; const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before); const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument?.(d, target, addDoc) || false; + if (this._renderCount < this.treeChildren.length) setTimeout(action(() => (this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20)))); return TreeView.GetChildElements( this.treeChildren, this, @@ -296,7 +298,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree //TODO: [AL] add these this.props.AddToMap, this.props.RemFromMap, - this.props.hierarchyIndex + this.props.hierarchyIndex, + this._renderCount ); } @computed get titleBar() { diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index d6bc0a4b2..fa648eb44 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -248,7 +248,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { return; } const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false); - const pinDoc = Doc.MakeDelegate(anchorDoc ?? doc); + const pinDoc = anchorDoc && anchorDoc !== doc ? anchorDoc : Doc.MakeDelegate(doc); pinDoc.presentationTargetDoc = anchorDoc ?? 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 diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 2398d8f58..0d2968ba1 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -32,6 +32,7 @@ import { CollectionTreeView, TreeViewType } from './CollectionTreeView'; import { CollectionView } from './CollectionView'; import './TreeView.scss'; import React = require('react'); +import { render } from 'react-dom'; export interface TreeViewProps { treeView: CollectionTreeView; @@ -95,7 +96,7 @@ export class TreeView extends React.Component<TreeViewProps> { private _header: React.RefObject<HTMLDivElement> = React.createRef(); private _tref = React.createRef<HTMLDivElement>(); @observable _docRef: Opt<DocumentView>; - private _selDisposer: Opt<IReactionDisposer>; + private _disposers: { [name: string]: IReactionDisposer } = {}; private _editTitleScript: (() => ScriptField) | undefined; private _openScript: (() => ScriptField) | undefined; private _treedropDisposer?: DragManager.DragDropDisposer; @@ -212,14 +213,14 @@ export class TreeView extends React.Component<TreeViewProps> { }; @action setEditTitle = (docView?: DocumentView) => { - this._selDisposer?.(); + this._disposers.selection?.(); if (!docView) { this._editTitle = false; } else if (docView.isSelected()) { const doc = docView.Document; SelectionManager.SelectSchemaViewDoc(doc); this._editTitle = true; - this._selDisposer = reaction( + this._disposers.selection = reaction( () => SelectionManager.SelectedSchemaDoc(), seldoc => seldoc !== doc && this.setEditTitle(undefined) ); @@ -259,7 +260,8 @@ export class TreeView extends React.Component<TreeViewProps> { }; componentWillUnmount() { - this._selDisposer?.(); + this._renderTimer && clearTimeout(this._renderTimer); + Object.values(this._disposers).forEach(disposer => disposer?.()); this._treeEle && this.props.unobserveHeight(this._treeEle); document.removeEventListener('pointermove', this.onDragMove, true); document.removeEventListener('pointermove', this.onDragUp, true); @@ -268,6 +270,10 @@ export class TreeView extends React.Component<TreeViewProps> { } componentDidUpdate() { + this._disposers.opening = reaction( + () => this.treeViewOpen, + open => !open && (this._renderCount = 20) + ); this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex); } @@ -512,6 +518,8 @@ export class TreeView extends React.Component<TreeViewProps> { return rows; } + _renderTimer: any; + @observable _renderCount = 1; @computed get renderContent() { TraceMobx(); const expandKey = this.treeViewExpandedView; @@ -543,6 +551,14 @@ export class TreeView extends React.Component<TreeViewProps> { const docs = expandKey === 'aliases' ? this.childAliases : expandKey === 'links' ? this.childLinks : expandKey === 'annotations' ? this.childAnnos : this.childDocs; let downX = 0, downY = 0; + if (docs?.length && this._renderCount < docs?.length) { + this._renderTimer && clearTimeout(this._renderTimer); + this._renderTimer = setTimeout( + action(() => { + this._renderCount = Math.min(docs!.length, this._renderCount + 20); + }) + ); + } return ( <> {!docs?.length || this.props.AddToMap /* hack to identify pres box trees */ ? null : ( @@ -599,7 +615,8 @@ export class TreeView extends React.Component<TreeViewProps> { // TODO: [AL] add these this.props.AddToMap, this.props.RemFromMap, - this.props.hierarchyIndex + this.props.hierarchyIndex, + this._renderCount )} </ul> </> @@ -651,7 +668,7 @@ export class TreeView extends React.Component<TreeViewProps> { return ( <div className={`bullet${this.props.treeView.outlineMode ? '-outline' : ''}`} - key={'bullet'} + key="bullet" title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : 'view fields'} onClick={this.bulletClick} style={ @@ -1131,7 +1148,8 @@ export class TreeView extends React.Component<TreeViewProps> { // TODO: [AL] add these AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[], RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[], - hierarchyIndex?: number[] + hierarchyIndex?: number[], + renderCount?: number ) { const viewSpecScript = Cast(containerCollection.viewSpecScript, ScriptField); if (viewSpecScript) { @@ -1144,6 +1162,7 @@ export class TreeView extends React.Component<TreeViewProps> { return docs .filter(child => child instanceof Doc) .map((child, i) => { + if (renderCount && i > renderCount) return null; const pair = Doc.GetLayoutDataDocPair(containerCollection, dataDoc, child); if (!pair.layout || pair.data instanceof Promise) { return null; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d6e95f97f..132538ea4 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -53,6 +53,8 @@ import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCurso import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; import React = require('react'); +import { DocumentDecorations } from '../../DocumentDecorations'; +import { PresEffect } from '../../nodes/trails'; export type collectionFreeformViewProps = { annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox) @@ -149,7 +151,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] } : this.props.contentBounds?.() ?? aggregateBounds( - this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), + this._layoutElements.filter(e => e.bounds && e.bounds.width && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10) ); @@ -184,6 +186,32 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } _keyTimer: NodeJS.Timeout | undefined; + + public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration: number) { + if (timer) clearTimeout(timer); + return DocumentView.SetViewTransition(docs, 'all', duration, undefined, true); + } + public static updateKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], time: number) { + if (timer) clearTimeout(timer); + const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, undefined, true); + const timecode = Math.round(time); + docs.forEach(doc => { + CollectionFreeFormDocumentView.animFields.forEach(val => { + const findexed = Cast(doc[`${val.key}-indexed`], listSpec('number'), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any as number); + }); + CollectionFreeFormDocumentView.animStringFields.forEach(val => { + const findexed = Cast(doc[`${val}-indexed`], listSpec('string'), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any as string); + }); + CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => { + const findexed = Cast(doc[`${val}-indexed`], listSpec(InkField), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any); + }); + }); + return newTimer; + } + changeKeyFrame = (back = false) => { const currentFrame = Cast(this.Document._currentFrame, 'number', null); if (currentFrame === undefined) { @@ -191,10 +219,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); } if (back) { - this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, this.childDocs.slice()); + this._keyTimer = CollectionFreeFormView.gotoKeyframe(this._keyTimer, [...this.childDocs, this.layoutDoc], 1000); this.Document._currentFrame = Math.max(0, (currentFrame || 0) - 1); } else { - this._keyTimer = CollectionFreeFormDocumentView.updateKeyframe(this._keyTimer, this.childDocs, currentFrame || 0); + this._keyTimer = CollectionFreeFormView.updateKeyframe(this._keyTimer, [...this.childDocs, this.layoutDoc], currentFrame || 0); this.Document._currentFrame = Math.max(0, (currentFrame || 0) + 1); this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame)); } @@ -1203,7 +1231,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } // focus on the document in the collection const didMove = !cantTransform && !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale); - const focusSpeed = options?.instant ? 0 : didMove ? options.zoomTime ?? 500 : 0; + const focusSpeed = options?.instant ? 0 : didMove || DocCast(options.effect)?.presEffect !== PresEffect.None ? options.zoomTime ?? 500 : 0; // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active... if (didMove) { options.zoomScale && scale && (this.Document[this.scaleFieldKey] = scale); @@ -1305,7 +1333,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection pointerEvents = () => { const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); const pointerEvents = - this.props.isContentActive() === false ? 'none' : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.()); + this.props.isContentActive() === false || DocumentDecorations.Instance.Interacting + ? 'none' + : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.()); return pointerEvents; }; getChildDocView(entry: PoolData) { @@ -1389,12 +1419,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }); getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData { - const curFrame = Cast(this.Document._currentFrame, 'number'); const childDoc = params.pair.layout; const childDocLayout = Doc.Layout(childDoc); + const layoutFrameNumber = Cast(this.Document._currentFrame, 'number'); // frame number that container is at which determines layout frame values + const contentFrameNumber = Cast(childDocLayout._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed const { z, zIndex } = childDoc; - const { backgroundColor, color } = curFrame === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, curFrame); - const { x, y, opacity } = curFrame === undefined ? { x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } : CollectionFreeFormDocumentView.getValues(childDoc, curFrame); + const { backgroundColor, color } = contentFrameNumber === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, contentFrameNumber); + const { x, y, opacity } = layoutFrameNumber === undefined ? { x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } : CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber); return { x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x), y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y), @@ -1540,13 +1571,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const elements = computedElementData.slice(); Array.from(newPool.entries()) .filter(entry => this.isCurrent(entry[1].pair.layout)) - .forEach((entry, i) => + .forEach((entry, i) => { + const childData: ViewDefBounds = this.childDataProvider(entry[1].pair.layout, entry[1].replica); + const childSize = this.childSizeProvider(entry[1].pair.layout, entry[1].replica); elements.push({ ele: this.getChildDocView(entry[1]), - bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica), + bounds: childData.opacity === 0 ? { ...childData, width: 0, height: 0 } : { ...childData, width: childSize.width, height: childSize.height }, inkMask: BoolCast(entry[1].pair.layout.isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1, - }) - ); + }); + }); if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) { // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar @@ -1579,7 +1612,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; return PresBox.restoreTargetDocView( this.props.DocumentView?.(), // - { pinDocLayout: BoolCast(anchor.presPinDocLayout) }, + { pinDocLayout: BoolCast(anchor.presPinLayout) }, anchor, focusSpeed, { @@ -1985,7 +2018,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection zoomScaling={this.zoomScaling} presPaths={this.showPresPaths} presPinView={BoolCast(this.Document.presPinView)} - transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', null)} + transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.rootDoc._viewTransition, 'string', null))} viewDefDivClick={this.props.viewDefDivClick}> {this.children} </CollectionFreeFormViewPannableContents> @@ -2287,6 +2320,7 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY SelectionManager.DeselectAll(); dv.props.focus(dv.props.Document, { willPanZoom: true, + zoomScale: 0.8, afterFocus: async didMove => { if (!didMove) { const selfFfview = dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index bc3b17cd9..bd33c4d80 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -397,6 +397,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque newCollection.forceActive = makeGroup; newCollection.x = this.Bounds.left; newCollection.y = this.Bounds.top; + newCollection.fitwidth = true; selected.forEach(d => (d.context = newCollection)); this.hideMarquee(); return newCollection; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index f8ef87fb1..ba510e1dc 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -40,6 +40,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF { key: '_rotation', val: 0 }, { key: '_scrollTop' }, { key: 'opacity', val: 1 }, + { key: '_currentFrame' }, + { key: 'viewScale', val: 1 }, { key: 'viewScale', val: 1 }, { key: 'panX' }, { key: 'panY' }, @@ -115,32 +117,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF }); } - public static updateKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], time: number, targetDoc?: Doc) { - if (timer) clearTimeout(timer); - const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, undefined, true); - const timecode = Math.round(time); - docs.forEach(doc => { - CollectionFreeFormDocumentView.animFields.forEach(val => { - const findexed = Cast(doc[`${val.key}-indexed`], listSpec('number'), null); - findexed?.length <= timecode + 1 && findexed.push(undefined as any as number); - }); - CollectionFreeFormDocumentView.animStringFields.forEach(val => { - const findexed = Cast(doc[`${val}-indexed`], listSpec('string'), null); - findexed?.length <= timecode + 1 && findexed.push(undefined as any as string); - }); - CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => { - const findexed = Cast(doc[`${val}-indexed`], listSpec(InkField), null); - findexed?.length <= timecode + 1 && findexed.push(undefined as any); - }); - }); - return newTimer; - } - - public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration = 1000) { - if (timer) clearTimeout(timer); - return DocumentView.SetViewTransition(docs, 'all', duration, undefined, true); - } - public static setupZoom(doc: Doc, targDoc: Doc) { const width = new List<number>(); const height = new List<number>(); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 36c0240f1..c3b0412de 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -17,7 +17,7 @@ import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from import { AudioField } from '../../../fields/URLField'; import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util'; import { MobileInterface } from '../../../mobile/MobileInterface'; -import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, OmitKeys, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils'; +import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, OmitKeys, returnEmptyString, returnFalse, returnNone, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils'; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { DocServer } from '../../DocServer'; import { Docs, DocUtils } from '../../documents/Documents'; @@ -252,6 +252,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps private _disposers: { [name: string]: IReactionDisposer } = {}; private _downX: number = 0; private _downY: number = 0; + private _downTime: number = 0; private _firstX: number = -1; private _firstY: number = -1; private _lastTap: number = 0; @@ -610,7 +611,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps let preventDefault = true; const isScriptBox = () => StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name); (this.rootDoc._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc); - if (this._doubleTap && (this.props.Document.type !== DocumentType.FONTICON || this.onDoubleClickHandler)) { + if (this._doubleTap && (![DocumentType.FONTICON, DocumentType.PRES].includes(this.props.Document.type as any) || this.onDoubleClickHandler)) { // && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click if (this._timeout) { clearTimeout(this._timeout); @@ -664,7 +665,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps ? this.props.select(false) : ''; const clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click')); - if (this.onDoubleClickHandler) { + if (this.onDoubleClickHandler && !this.props.Document.allowClickBeforeDoubleClick) { runInAction(() => (this._pendingDoubleClick = true)); this._timeout = setTimeout(() => { this._timeout = undefined; @@ -718,6 +719,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps ); this._downX = e.clientX; this._downY = e.clientY; + this._downTime = Date.now(); if ((Doc.ActiveTool === InkTool.None || this.props.addDocTab === returnFalse) && !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) { // if this is part of a template, let the event go up to the tempalte root unless right/ctrl clicking if ( @@ -777,10 +779,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps this._cursorTimer && clearTimeout(this._cursorTimer); this._cursorPress = false; + const now = Date.now(); if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log); - } else { - this._doubleTap = Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2; + } else if (now - this._downTime < 300) { + this._doubleTap = now - this._lastTap < 600 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2; // bcz: this is a placeholder. documents, when selected, should stopPropagation on doubleClicks if they want to keep the DocumentView from getting them if (!this.props.isSelected(true) || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this.rootDoc.type) as any)) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected } @@ -1152,7 +1155,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps className="documentView-contentsView" style={{ pointerEvents: - (this.props.pointerEvents?.() as any) ?? this.rootDoc.layoutKey === 'layout_icon' + this.opacity === 0 + ? 'none' + : (this.props.pointerEvents?.() as any) ?? this.rootDoc.layoutKey === 'layout_icon' ? 'none' : (this.props.contentPointerEvents as any) ? (this.props.contentPointerEvents as any) @@ -1179,6 +1184,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps <DocumentContentsView key={1} {...this.props} + pointerEvents={this.opacity === 0 ? returnNone : this.props.pointerEvents} docViewPath={this.props.viewPath} thumbShown={this.thumbShown} isHovering={this.props.isHovering} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 540958941..6e616931d 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -76,7 +76,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; return PresBox.restoreTargetDocView( this.props.DocumentView?.(), // - { pinDocLayout: BoolCast(anchor.presPinDocLayout) }, + { pinDocLayout: BoolCast(anchor.presPinLayout) }, anchor, focusSpeed, !anchor.presPinData @@ -383,7 +383,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp @computed get content() { TraceMobx(); - const col = DashColor(this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor)); + const backAlpha = DashColor(this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor)).alpha(); const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0]; const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement(); const { nativeWidth, nativeHeight, nativeOrientation } = this.nativeSize; @@ -403,7 +403,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp return ( <div className="imageBox-cont" key={this.layoutDoc[Id]} ref={this.createDropTarget} onPointerDown={this.marqueeDown}> - <div className="imageBox-fader" style={{ opacity: col.alpha() }}> + <div className="imageBox-fader" style={{ opacity: backAlpha }}> <img key="paths" src={srcpath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} /> {fadepath === srcpath ? null : ( <div diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index c392f3577..5bbe521a7 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -257,6 +257,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps // this._iframe?.contentDocument?.removeEventListener("pointerup", this.iframeUp); } + private _selectionText: string = ''; + private _selectionContent: DocumentFragment | undefined; + selectionText = () => this._selectionText; + selectionContent = () => this._selectionContent; @action createTextAnnotation = (sel: Selection, selRange: Range | undefined) => { if (this._mainCont.current && selRange) { @@ -276,6 +280,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } } //this._selectionText = selRange.cloneContents().textContent || ""; + this._selectionContent = selRange?.cloneContents(); + this._selectionText = this._selectionContent?.textContent || ''; // clear selection if (sel.empty) sel.empty(); // Chrome @@ -854,8 +860,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return WebBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1); }; @computed get content() { - const interactive = - !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None && !DocumentDecorations.Instance?.Interacting; + const interactive = !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None; return ( <div className={'webBox-cont' + (interactive ? '-interactive' : '')} onKeyDown={e => e.stopPropagation()} style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']) || `100%` : '100%' }}> {this.urlContent} @@ -1014,7 +1019,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps docView={this.props.docViewPath().lastElement()} finishMarquee={this.finishMarquee} savedAnnotations={this.savedAnnotationsCreator} - selectionText={returnEmptyString} + selectionText={this.selectionText} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} /> diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 9d67283a0..93761633c 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -136,7 +136,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() { const setValue = (value: number) => UndoManager.RunInBatch(() => numScript?.script.run({ value, _readOnly_: false }), 'set num value'); // Script for checking the outcome of the toggle - const checkResult: number = numScript?.script.run({ value: 0, _readOnly_: true }).result || 0; + const checkResult = Number(numScript?.script.run({ value: 0, _readOnly_: true }).result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)); const label = !FontIconBox.GetShowLabels() ? null : <div className="fontIconBox-label">{this.label}</div>; @@ -150,7 +150,6 @@ export class FontIconBox extends DocComponent<ButtonProps>() { max={NumCast(this.rootDoc.numBtnMax, 100)} value={checkResult} className={'menu-slider'} - id="slider" onPointerDown={() => (this._batch = UndoManager.StartBatch('presDuration'))} onPointerUp={() => this._batch?.end()} onChange={e => { @@ -180,7 +179,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() { className="list-item" key={`${value}`} style={{ - backgroundColor: value === checkResult ? Colors.LIGHT_BLUE : undefined, + backgroundColor: value.toString() === checkResult ? Colors.LIGHT_BLUE : undefined, }} onClick={() => setValue(value)}> {value} @@ -642,13 +641,7 @@ ScriptingGlobals.add(function setFontHighlight(color?: string, checkResult?: boo if (checkResult) { return (selected ?? Doc.UserDoc())._fontHighlight; } - if (selected) { - selected._fontColor = color; - if (color) { - editorView?.state && RichTextMenu.Instance.setHighlight(color, editorView, editorView?.dispatch); - } - } - Doc.UserDoc()._fontHighlight = color; + color && RichTextMenu.Instance.setHighlight(color); }); // toggle: Set overlay status of selected document @@ -795,7 +788,7 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, GestureOverlay.Instance.KeepPrimitiveMode = keepPrim; } if (Object.values(GestureUtils.Gestures).includes(tool as any)) { - if (GestureOverlay.Instance.InkShape === tool) { + if (GestureOverlay.Instance.InkShape === tool && !keepPrim) { Doc.ActiveTool = InkTool.None; GestureOverlay.Instance.InkShape = undefined; } else { @@ -804,7 +797,7 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, } } else if (tool) { // pen or eraser - if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape) { + if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape && !keepPrim) { Doc.ActiveTool = InkTool.None; } else { Doc.ActiveTool = tool as any; diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 39005a18b..b33529aeb 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -18,12 +18,18 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { NodeSelection } from 'prosemirror-state'; import { OpenWhere } from '../DocumentView'; +import { FormattedTextBoxComment } from './FormattedTextBoxComment'; export class DashFieldView { dom: HTMLDivElement; // container for label and value root: any; + node: any; + tbox: FormattedTextBox; + unclickable = () => !this.tbox.props.isSelected() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview); constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + this.node = node; + this.tbox = tbox; this.dom = document.createElement('div'); this.dom.style.width = node.attrs.width; this.dom.style.height = node.attrs.height; @@ -44,7 +50,18 @@ export class DashFieldView { this.root = ReactDOM.createRoot(this.dom); this.root.render( - <DashFieldViewInternal node={node} getPos={getPos} fieldKey={node.attrs.fieldKey} docid={node.attrs.docid} width={node.attrs.width} height={node.attrs.height} hideKey={node.attrs.hideKey} editable={node.attrs.editable} tbox={tbox} /> + <DashFieldViewInternal + node={node} + unclickable={this.unclickable} + getPos={getPos} + fieldKey={node.attrs.fieldKey} + docid={node.attrs.docid} + width={node.attrs.width} + height={node.attrs.height} + hideKey={node.attrs.hideKey} + editable={node.attrs.editable} + tbox={tbox} + /> ); } destroy() { @@ -68,6 +85,7 @@ interface IDashFieldViewInternal { editable: boolean; node: any; getPos: any; + unclickable: () => boolean; } @observer @@ -125,7 +143,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna return ( <span className="dashFieldView-fieldSpan" - contentEditable={true} + contentEditable={!this.props.unclickable()} style={{ display: strVal.length < 2 ? 'inline-block' : undefined }} suppressContentEditableWarning={true} defaultValue={strVal} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 49acd8a57..0ff09a0ca 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -81,7 +81,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps static _highlightStyleSheet: any = addStyleSheet(); static _bulletStyleSheet: any = addStyleSheet(); static _userStyleSheet: any = addStyleSheet(); - static _canAnnotate = true; static _hadSelection: boolean = false; private _sidebarRef = React.createRef<SidebarAnnos>(); private _ref: React.RefObject<HTMLDivElement> = React.createRef(); @@ -102,7 +101,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps private _rules: RichTextRules | undefined; private _forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason. This flags the 2nd click as happening on a selection which allows bullet points to toggle private _forceDownNode: Node | undefined; - private _downEvent: any; private _downX = 0; private _downY = 0; private _break = true; @@ -118,7 +116,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } @computed get noSidebar() { - return this.props.docViewPath?.()[this.props.docViewPath().length - 2]?.rootDoc.type === DocumentType.RTF || this.props.noSidebar || this.Document._noSidebar; + return this.props.docViewPath().lastElement()?.props.hideDecorationTitle || this.props.noSidebar || this.Document._noSidebar; } @computed get sidebarWidthPercent() { return this._showSidebar ? '20%' : StrCast(this.layoutDoc._sidebarWidthPercent, '0%'); @@ -254,7 +252,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps }); }; AnchorMenu.Instance.Highlight = action((color: string, isLinkButton: boolean) => { - this._editorView?.state && RichTextMenu.Instance.setHighlight(color, this._editorView, this._editorView?.dispatch); + this._editorView?.state && RichTextMenu.Instance.setHighlight(color); return undefined; }); AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true); @@ -788,7 +786,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps ); const uicontrols: ContextMenuProps[] = []; - !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ''} Show Menu on Selections`, event: () => (FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate), icon: 'expand-arrows-alt' }); uicontrols.push({ description: !this.Document._noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle', event: () => (this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar), icon: 'expand-arrows-alt' }); uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' }); !Doc.noviceMode && @@ -1455,7 +1452,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } this._downX = e.clientX; this._downY = e.clientY; - this._downEvent = true; FormattedTextBoxComment.textBox = this; if (e.button === 0 && (this.props.rootSelected(true) || this.props.isSelected(true)) && !e.altKey && !e.ctrlKey && !e.metaKey) { if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { @@ -1477,17 +1473,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps document.removeEventListener('pointermove', this.onSelectMove); }; onPointerUp = (e: React.PointerEvent): void => { - if (!this._editorView?.state.selection.empty && !(this._editorView?.state.selection instanceof NodeSelection) && FormattedTextBox._canAnnotate && !(e.nativeEvent as any).dash) this.setupAnchorMenu(); - if (!this._downEvent) return; - this._downEvent = false; - if (this._editorView?.state.selection.empty && this.props.isContentActive(true) && !(e.nativeEvent as any).dash) { - const editor = this._editorView!; + const editor = this._editorView!; + const state = editor?.state; + if (!state || !editor) return; + if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu(); + else if (this.props.isContentActive(true)) { const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); - !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0)))); + !this.props.isSelected(true) && editor.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(pcords?.pos || 0)))); let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span> while (target && !target.dataset?.targethrefs) target = target.parentElement; FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true'); - if (pcords && pcords.inside > 0 && this._editorView.state.doc.nodeAt(pcords.inside)?.type === this._editorView.state.schema.nodes.dashDoc) { + if (pcords && pcords.inside > 0 && state.doc.nodeAt(pcords.inside)?.type === state.schema.nodes.dashDoc) { return; } } diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index b70da2e5e..f0caa1f4f 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -54,7 +54,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { @observable private _activeFontColor: string = 'black'; @observable private showColorDropdown: boolean = false; - @observable private activeHighlightColor: string = 'transparent'; + @observable private _activeHighlightColor: string = 'transparent'; @observable private showHighlightDropdown: boolean = false; @observable private currentLink: string | undefined = ''; @@ -89,6 +89,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { @computed get fontColor() { return this._activeFontColor; } + @computed get fontHighlight() { + return this._activeHighlightColor; + } @computed get fontFamily() { return this._activeFontFamily; } @@ -138,7 +141,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document.fontFamily, StrCast(Doc.UserDoc().fontFamily, 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0]; this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(Doc.UserDoc().fontColor, 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...'; - this.activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...'; + this._activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...'; // update link in current selection this.getTextLinkTargetTitle().then(targetTitle => this.setCurrentLink(targetTitle)); @@ -356,11 +359,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this.updateMenu(this.view, undefined, this.props); }; - setHighlight(color: String, view: EditorView, dispatch: any) { - const highlightMark = view.state.schema.mark(view.state.schema.marks.marker, { highlight: color }); - if (view.state.selection.empty) return false; - view.focus(); - this.setMark(highlightMark, view.state, dispatch, false); + setHighlight(color: string) { + if (this.view) { + const highlightMark = this.view.state.schema.mark(this.view.state.schema.marks.marker, { highlight: color }); + this.setMark(highlightMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(highlightMark)), true); + this.view.focus(); + } else Doc.UserDoc()._fontHighlight = color; + this.updateMenu(this.view, undefined, this.props); } setColor(color: string) { @@ -563,7 +568,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { } @action setActiveHighlight(color: string) { - this.activeHighlightColor = color; + this._activeHighlightColor = color; } @action setCurrentLink(link: string) { diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index e07517113..929bf1230 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -1,10 +1,10 @@ import React = require('react'); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; -import { action, computed, IReactionDisposer, observable, ObservableSet, observe, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { ColorState, SketchPicker } from 'react-color'; -import { AnimationSym, Doc, DocListCast, FieldResult, HighlightSym, Opt, StrListCast } from '../../../../fields/Doc'; +import { AnimationSym, Doc, DocListCast, FieldResult, Opt, StrListCast } from '../../../../fields/Doc'; import { Copy, Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; @@ -14,7 +14,7 @@ import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Ty import { AudioField } from '../../../../fields/URLField'; import { emptyFunction, emptyPath, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; -import { Docs, DocumentOptions } from '../../../documents/Documents'; +import { Docs } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; @@ -23,19 +23,17 @@ import { SettingsManager } from '../../../util/SettingsManager'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { CollectionDockingView } from '../../collections/CollectionDockingView'; import { CollectionFreeFormView, computeTimelineLayout, MarqueeViewBounds } from '../../collections/collectionFreeForm'; +import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline'; import { CollectionView } from '../../collections/CollectionView'; import { TabDocView } from '../../collections/TabDocView'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; import { LightboxView } from '../../LightboxView'; -import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentView'; import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { ScriptingBox } from '../ScriptingBox'; import './PresBox.scss'; import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums'; -import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline'; -import { PresElementBox } from './PresElementBox'; const { Howl } = require('howler'); export interface PinProps { @@ -296,7 +294,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (context) { const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.ComponentView as CollectionFreeFormView; if (ffview?.childDocs) { - this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, ffview.childDocs.slice(), transTime); + this._keyTimer = CollectionFreeFormView.gotoKeyframe(this._keyTimer, ffview.childDocs, transTime); context._currentFrame = NumCast(activeFrame); } } @@ -598,7 +596,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { tagDoc.opacity = 1; } } - const hidingIndBef = itemIndexes.find(item => item >= this.itemIndex); + const hidingIndBef = itemIndexes.find(item => item >= this.itemIndex) ?? itemIndexes.slice().reverse().lastElement(); if (curDoc.presHideBefore && index === hidingIndBef) { if (index > this.itemIndex) { tagDoc.opacity = 0; @@ -606,10 +604,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { tagDoc.opacity = 1; } } - const hidingIndAft = itemIndexes - .slice() - .reverse() - .find(item => item < this.itemIndex); + const hidingIndAft = + itemIndexes + .slice() + .reverse() + .find(item => item <= this.itemIndex) ?? itemIndexes.lastElement(); if (curDoc.presHideAfter && index === hidingIndAft) { if (index < this.itemIndex) { tagDoc.opacity = 0; @@ -875,7 +874,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @action selectElement = async (doc: Doc, noNav = false) => { CollectionStackedTimeline.CurrentlyPlaying?.map((clip, i) => DocumentManager.Instance.getDocumentView(clip)?.ComponentView?.Pause?.()); - !noNav && this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem); + if (noNav) { + const index = this.childDocs.indexOf(doc); + if (index >= 0 && index < this.childDocs.length) { + this.rootDoc._itemIndex = index; + } + } else this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem); this.updateCurrentPresentation(DocCast(doc.context)); }; @@ -911,19 +915,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { //regular click @action - regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean, selectPres = true, noNav = false) => { + regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, noNav: boolean, selectPres = true) => { this.clearSelectedArray(); this.addToSelectedArray(doc); this._eleArray.splice(0, this._eleArray.length, ref); this._dragArray.splice(0, this._dragArray.length, drag); - focus && this.selectElement(doc, noNav); + this.selectElement(doc, noNav); selectPres && this.selectPres(); }; - modifierSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean, cmdClick: boolean, shiftClick: boolean, noNav: boolean = false) => { + modifierSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, noNav: 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, noNav); + else this.regularSelect(doc, ref, drag, noNav); }; static keyEventsWrapper = (e: KeyboardEvent) => PresBox.Instance?.keyEvents(e); diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index f265c1315..d19b78dbc 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -3,20 +3,17 @@ import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../../fields/Doc'; -import { Copy, Id } from '../../../../fields/FieldSymbols'; -import { InkField } from '../../../../fields/InkField'; +import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; -import { RichTextField } from '../../../../fields/RichTextField'; import { Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils'; import { Docs, DocUtils } from '../../../documents/Documents'; -import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; +import { CollectionViewType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { SettingsManager } from '../../../util/SettingsManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; -import { MarqueeView } from '../../collections/collectionFreeForm'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { EditableView } from '../../EditableView'; import { Colors } from '../../global/globalEnums'; @@ -139,7 +136,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { onClick={e => { e.stopPropagation(); e.preventDefault(); - this.presBoxView?.modifierSelect(doc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey); + this.presBoxView?.modifierSelect(doc, this._itemRef.current!, this._dragRef.current!, e.shiftKey || e.ctrlKey || e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey); this.presExpandDocumentClick(); }}> <div className="presItem-groupNum">{`${ind + 1}.`}</div> @@ -177,22 +174,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { const element = e.target as any; e.stopPropagation(); e.preventDefault(); - if (element && !(e.ctrlKey || e.metaKey)) { - if (this.selectedArray?.has(this.rootDoc)) { - this.selectedArray.size === 1 && this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false); - setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction); - } else { - setupMoveUpEvents( - this, - e, - (e: PointerEvent) => { - this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false); - return this.startDrag(e); - }, - emptyFunction, - emptyFunction - ); - } + if (element && !(e.ctrlKey || e.metaKey || e.button === 2)) { + this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true, false); + setupMoveUpEvents(this, e, this.startDrag, emptyFunction, e => { + e.stopPropagation(); + e.preventDefault(); + this.presBoxView?.modifierSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, e.shiftKey || e.ctrlKey || e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey); + this.presBoxView?.activeItem && this.showRecording(this.presBoxView?.activeItem); + }); } }; @@ -304,60 +293,26 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { */ @undoBatch @action - updateCapturedContainerLayout = (targetDoc: Doc, activeItem: Doc) => { + updateCapturedContainerLayout = (presTargetDoc: Doc, activeItem: Doc) => { + const targetDoc = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc; activeItem.presX = NumCast(targetDoc.x); activeItem.presY = NumCast(targetDoc.y); activeItem.presRot = NumCast(targetDoc.rotation); activeItem.presWidth = NumCast(targetDoc.width); activeItem.presHeight = NumCast(targetDoc.height); + activeItem.presPinLayout = true; }; /** * Method called for updating the view of the currently selected document * - * @param targetDoc + * @param presTargetDoc * @param activeItem */ @undoBatch @action - updateCapturedViewContents = (targetDoc: Doc, activeItem: Doc) => { - switch (targetDoc.type) { - case DocumentType.PDF: - case DocumentType.WEB: - case DocumentType.RTF: - const scroll = targetDoc._scrollTop; - activeItem.presPinViewScroll = scroll; - if (targetDoc.type === DocumentType.RTF) { - activeItem.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof RichTextField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as RichTextField)[Copy]() : targetDoc.text; - } - break; - case DocumentType.INK: - activeItem.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof InkField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as InkField)[Copy]() : targetDoc.data; - break; - case DocumentType.VID: - case DocumentType.AUDIO: - activeItem.presStartTime = targetDoc._currentTimecode; - break; - case DocumentType.COMPARISON: - const clipWidth = targetDoc._clipWidth; - activeItem.presPinClipWidth = clipWidth; - break; - case DocumentType.COL: - activeItem.presPinLayoutData = new List<string>(DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]).map(d => JSON.stringify({ id: d[Id], x: NumCast(d.x), y: NumCast(d.y), w: NumCast(d._width), h: NumCast(d._height) }))); - default: - 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; - } - } + updateCapturedViewContents = (presTargetDoc: Doc, activeItem: Doc) => { + const target = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc; + PresBox.pinDocView(activeItem, { pinDocContent: true, pinData: PresBox.pinDataTypes(target) }, target); }; @computed get recordingIsInOverlay() { @@ -465,24 +420,26 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { const activeItem: Doc = this.rootDoc; const items: JSX.Element[] = []; - if (activeItem.presPinLayout) { - items.push( - <Tooltip key="slide" title={<div className="dash-tooltip">Update captured doc layout</div>}> - <div className="slideButton" onClick={() => this.updateCapturedContainerLayout(targetDoc, activeItem)} style={{ fontWeight: 700, display: 'flex' }}> - L - </div> - </Tooltip> - ); - } - if (activeItem.presPinData || activeItem.presPinView) { - items.push( - <Tooltip key="flex" title={<div className="dash-tooltip">Update captured doc content</div>}> - <div className="slideButton" onClick={() => this.updateCapturedViewContents(targetDoc, activeItem)} style={{ fontWeight: 700, display: 'flex' }}> - C - </div> - </Tooltip> - ); - } + items.push( + <Tooltip key="slide" title={<div className="dash-tooltip">Update captured doc layout</div>}> + <div + className="slideButton" + onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.updateCapturedContainerLayout(targetDoc, activeItem), true)} + style={{ opacity: activeItem.presPinLayout ? 1 : 0.5, fontWeight: 700, display: 'flex' }}> + L + </div> + </Tooltip> + ); + items.push( + <Tooltip key="flex" title={<div className="dash-tooltip">Update captured doc content</div>}> + <div + className="slideButton" + onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.updateCapturedViewContents(targetDoc, activeItem))} + style={{ opacity: activeItem.presPinData || activeItem.presPinView ? 1 : 0.5, fontWeight: 700, display: 'flex' }}> + C + </div> + </Tooltip> + ); if (!Doc.noviceMode) { items.push( <Tooltip key="slash" title={<div className="dash-tooltip">{this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}> @@ -557,15 +514,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { paddingTop: NumCast(this.layoutDoc._yPadding, this.props.yPadding), paddingBottom: NumCast(this.layoutDoc._yPadding, this.props.yPadding), }} - onClick={e => { - e.stopPropagation(); - e.preventDefault(); - this.presBoxView?.modifierSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey); - this.showRecording(activeItem); - }} onDoubleClick={action(e => { this.toggleProperties(); - this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true); + this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false); })} onPointerOver={this.onPointerOver} onPointerLeave={this.onPointerLeave} @@ -599,7 +550,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { onPointerDown={e => { e.stopPropagation(); if (this._itemRef.current && this._dragRef.current) { - this.presBoxView?.modifierSelect(activeItem, this._itemRef.current, this._dragRef.current, false, false, false, true); + this.presBoxView?.modifierSelect(activeItem, this._itemRef.current, this._dragRef.current, true, false, false); } }} onClick={e => e.stopPropagation()}>{`${this.indexInPres + 1}. `}</div> diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index 7e728306c..f2e9be61d 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -54,7 +54,7 @@ export class TopBar extends React.Component { <span style={{ color: Colors.LIGHT_BLUE, fontWeight: 500 }}>dash</span> </div> )} - {Doc.ActiveDashboard && !Doc.noviceMode && ( + {Doc.ActiveDashboard && ( <Button text="Explore" tooltip="Browsing mode for directly navigating to documents" diff --git a/src/fields/util.ts b/src/fields/util.ts index 3a7484cfd..6024705ec 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -1,3 +1,4 @@ +import { forEach } from 'lodash'; import { $mobx, action, observable, runInAction, trace } from 'mobx'; import { computedFn } from 'mobx-utils'; import { DocServer } from '../client/DocServer'; |