From a170b1ac4083cd322cc0d4b1e08087cb45867e55 Mon Sep 17 00:00:00 2001 From: Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com> Date: Fri, 1 Jul 2022 12:44:13 -0700 Subject: added resize handlers and linted document decorations --- src/client/views/DocumentDecorations.scss | 49 +- src/client/views/DocumentDecorations.tsx | 1184 ++++++++++++++++------------- 2 files changed, 674 insertions(+), 559 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 481b90249..135d6d001 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -1,4 +1,4 @@ -@import "global/globalCssVariables"; +@import 'global/globalCssVariables'; $linkGap: 3px; @@ -41,7 +41,7 @@ $linkGap: 3px; opacity: 1; transform: translate(10px, 10px); grid-row: 4; - grid-column: 3 + grid-column: 3; } .documentDecorations-topLeftResizer, @@ -60,8 +60,7 @@ $linkGap: 3px; } } - .documentDecorations-resizer-Dark - { + .documentDecorations-resizer-Dark { background: $light-gray; opacity: 0.2; } @@ -69,7 +68,7 @@ $linkGap: 3px; .documentDecorations-topLeftResizer, .documentDecorations-leftResizer, .documentDecorations-bottomLeftResizer { - grid-column: 1 + grid-column: 1; } .documentDecorations-topResizer, @@ -108,7 +107,6 @@ $linkGap: 3px; right: -15; } - .documentDecorations-topLeftResizer, .documentDecorations-bottomRightResizer { cursor: nwse-resize; @@ -145,7 +143,7 @@ $linkGap: 3px; .documentDecorations-bottomRightResizer, .documentDecorations-topRightResizer, .documentDecorations-rightResizer { - grid-column: 3 + grid-column: 3; } .documentDecorations-rotation, @@ -215,20 +213,25 @@ $linkGap: 3px; border-bottom: 2px solid; } -.documentDecorations-topRightResizer:hover, -.documentDecorations-bottomLeftResizer:hover { - cursor: nesw-resize; - background: black; - opacity: 1; -} + .documentDecorations-topRightResizer:hover, + .documentDecorations-bottomLeftResizer:hover { + cursor: nesw-resize; + background: black; + opacity: 1; + } -.documentDecorations-topResizer, -.documentDecorations-bottomResizer { - cursor: ns-resize; -} + .documentDecorations-topResizer, + .documentDecorations-bottomResizer { + cursor: ns-resize; + } -.documentDecorations-title-Dark, -.documentDecorations-title { + .documentDecorations-leftResizer, + .documentDecorations-rightResizer { + cursor: ew-resize; + } + + .documentDecorations-title-Dark, + .documentDecorations-title { opacity: 1; width: calc(100% - 8px); // = margin-left + margin-right grid-column: 2; @@ -242,11 +245,11 @@ $linkGap: 3px; height: 20px; position: absolute; border-radius: 8px; - background: rgba(159,159,159,0.1); + background: rgba(159, 159, 159, 0.1); - .documentDecorations-titleSpan, - .documentDecorations-titleSpan-Dark { - width: 100% ; + .documentDecorations-titleSpan, + .documentDecorations-titleSpan-Dark { + width: 100%; border-radius: 8px; background: #ffffffa0; position: absolute; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 669718e81..17e135689 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,23 +1,23 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; -import { action, computed, observable, reaction } from "mobx"; -import { observer } from "mobx-react"; +import { action, computed, observable, reaction } from 'mobx'; +import { observer } from 'mobx-react'; import { DateField } from '../../fields/DateField'; -import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSym } from "../../fields/Doc"; +import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSym } from '../../fields/Doc'; import { Document } from '../../fields/documentSchemas'; -import { InkField } from "../../fields/InkField"; +import { InkField } from '../../fields/InkField'; import { ComputedField, ScriptField } from '../../fields/ScriptField'; -import { Cast, FieldValue, NumCast, StrCast } from "../../fields/Types"; +import { Cast, FieldValue, NumCast, StrCast } from '../../fields/Types'; import { GetEffectiveAcl } from '../../fields/util'; -import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils"; -import { Docs } from "../documents/Documents"; +import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../Utils'; +import { Docs } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; -import { DragManager } from "../util/DragManager"; -import { SelectionManager } from "../util/SelectionManager"; +import { DragManager } from '../util/DragManager'; +import { SelectionManager } from '../util/SelectionManager'; import { SnappingManager } from '../util/SnappingManager'; -import { undoBatch, UndoManager } from "../util/UndoManager"; +import { undoBatch, UndoManager } from '../util/UndoManager'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { DocumentButtonBar } from './DocumentButtonBar'; @@ -26,557 +26,669 @@ import { KeyManager } from './GlobalKeyHandler'; import { InkingStroke } from './InkingStroke'; import { InkStrokeProperties } from './InkStrokeProperties'; import { LightboxView } from './LightboxView'; -import { DocumentView } from "./nodes/DocumentView"; +import { DocumentView } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { ImageBox } from './nodes/ImageBox'; -import React = require("react"); +import React = require('react'); @observer -export class DocumentDecorations extends React.Component<{ PanelWidth: number, PanelHeight: number, boundsLeft: number, boundsTop: number }, { value: string }> { - static Instance: DocumentDecorations; - private _resizeHdlId = ""; - private _keyinput = React.createRef(); - private _resizeBorderWidth = 16; - private _linkBoxHeight = 20 + 3; // link button height + margin - private _titleHeight = 20; - private _resizeUndo?: UndoManager.Batch; - private _offX = 0; _offY = 0; // offset from click pt to inner edge of resize border - private _snapX = 0; _snapY = 0; // last snapped location of resize border - private _dragHeights = new Map(); - private _inkDragDocs: { doc: Doc, x: number, y: number, width: number, height: number }[] = []; - - @observable private _accumulatedTitle = ""; - @observable private _titleControlString: string = "#title"; - @observable private _edtingTitle = false; - @observable private _hidden = false; - @observable public AddToSelection = false; // if Shift is pressed, then this should be set so that clicking on the selection background is ignored so overlapped documents can be added to the selection set. - @observable public Interacting = false; - @observable public pushIcon: IconProp = "arrow-alt-circle-up"; - @observable public pullIcon: IconProp = "arrow-alt-circle-down"; - @observable public pullColor: string = "white"; - - constructor(props: any) { - super(props); - DocumentDecorations.Instance = this; - reaction(() => SelectionManager.Views().slice(), action(docs => this._edtingTitle = false)); - } - - @computed - get Bounds() { - const views = SelectionManager.Views(); - return views.filter(dv => dv.props.renderDepth > 0).map(dv => dv.getBounds()).reduce((bounds, rect) => - !rect ? bounds : - { +export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> { + static Instance: DocumentDecorations; + private _resizeHdlId = ''; + private _keyinput = React.createRef(); + private _resizeBorderWidth = 16; + private _linkBoxHeight = 20 + 3; // link button height + margin + private _titleHeight = 20; + private _resizeUndo?: UndoManager.Batch; + private _offX = 0; + _offY = 0; // offset from click pt to inner edge of resize border + private _snapX = 0; + _snapY = 0; // last snapped location of resize border + private _dragHeights = new Map(); + private _inkDragDocs: { doc: Doc; x: number; y: number; width: number; height: number }[] = []; + + @observable private _accumulatedTitle = ''; + @observable private _titleControlString: string = '#title'; + @observable private _edtingTitle = false; + @observable private _hidden = false; + @observable public AddToSelection = false; // if Shift is pressed, then this should be set so that clicking on the selection background is ignored so overlapped documents can be added to the selection set. + @observable public Interacting = false; + @observable public pushIcon: IconProp = 'arrow-alt-circle-up'; + @observable public pullIcon: IconProp = 'arrow-alt-circle-down'; + @observable public pullColor: string = 'white'; + + constructor(props: any) { + super(props); + DocumentDecorations.Instance = this; + reaction( + () => SelectionManager.Views().slice(), + action(docs => (this._edtingTitle = false)) + ); + } + + @computed + get Bounds() { + const views = SelectionManager.Views(); + return views + .filter(dv => dv.props.renderDepth > 0) + .map(dv => dv.getBounds()) + .reduce( + (bounds, rect) => + !rect + ? bounds + : { x: Math.min(rect.left, bounds.x), y: Math.min(rect.top, bounds.y), r: Math.max(rect.right, bounds.r), b: Math.max(rect.bottom, bounds.b), - c: views.length === 1 ? rect.center : undefined - }, - { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE, c: undefined as ({ X: number, Y: number } | undefined) }); - } - - @action - titleBlur = () => { - this._edtingTitle = false; - if (this._accumulatedTitle.startsWith("#") || this._accumulatedTitle.startsWith("=")) { - this._titleControlString = this._accumulatedTitle; - } else if (this._titleControlString.startsWith("#")) { - const titleFieldKey = this._titleControlString.substring(1); - UndoManager.RunInBatch(() => titleFieldKey && SelectionManager.Views().forEach(d => { - if (titleFieldKey === "title") { - d.dataDoc["title-custom"] = !this._accumulatedTitle.startsWith("-"); - if (StrCast(d.rootDoc.title).startsWith("@") && !this._accumulatedTitle.startsWith("@")) { - Doc.RemoveDocFromList(CurrentUserUtils.MyPublishedDocs, undefined, d.rootDoc); - } - if (!StrCast(d.rootDoc.title).startsWith("@") && this._accumulatedTitle.startsWith("@")) { - Doc.AddDocToList(CurrentUserUtils.MyPublishedDocs, undefined, d.rootDoc); - } + c: views.length === 1 ? rect.center : undefined, + }, + { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE, c: undefined as { X: number; Y: number } | undefined } + ); + } + + @action + titleBlur = () => { + this._edtingTitle = false; + if (this._accumulatedTitle.startsWith('#') || this._accumulatedTitle.startsWith('=')) { + this._titleControlString = this._accumulatedTitle; + } else if (this._titleControlString.startsWith('#')) { + const titleFieldKey = this._titleControlString.substring(1); + UndoManager.RunInBatch( + () => + titleFieldKey && + SelectionManager.Views().forEach(d => { + if (titleFieldKey === 'title') { + d.dataDoc['title-custom'] = !this._accumulatedTitle.startsWith('-'); + if (StrCast(d.rootDoc.title).startsWith('@') && !this._accumulatedTitle.startsWith('@')) { + Doc.RemoveDocFromList(CurrentUserUtils.MyPublishedDocs, undefined, d.rootDoc); + } + if (!StrCast(d.rootDoc.title).startsWith('@') && this._accumulatedTitle.startsWith('@')) { + Doc.AddDocToList(CurrentUserUtils.MyPublishedDocs, undefined, d.rootDoc); + } } //@ts-ignore - const titleField = (+this._accumulatedTitle === this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle); + const titleField = +this._accumulatedTitle === this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle; Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true); if (d.rootDoc.syncLayoutFieldWithTitle) { - const title = titleField.toString(); - const curKey = Doc.LayoutFieldKey(d.rootDoc); - if (curKey !== title && d.dataDoc[title] === undefined) { - d.rootDoc.layout = FormattedTextBox.LayoutString(title); - setTimeout(() => { - const val = d.dataDoc[curKey]; - d.dataDoc[curKey] = undefined; - d.dataDoc[title] = val; - }); - } + const title = titleField.toString(); + const curKey = Doc.LayoutFieldKey(d.rootDoc); + if (curKey !== title && d.dataDoc[title] === undefined) { + d.rootDoc.layout = FormattedTextBox.LayoutString(title); + setTimeout(() => { + const val = d.dataDoc[curKey]; + d.dataDoc[curKey] = undefined; + d.dataDoc[title] = val; + }); + } } - }), "title blur"); + }), + 'title blur' + ); + } + }; + + titleEntered = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + e.stopPropagation(); + (e.target as any).blur(); + } + }; + + @action onTitleDown = (e: React.PointerEvent): void => { + setupMoveUpEvents( + this, + e, + e => this.onBackgroundMove(true, e), + e => {}, + action(e => { + !this._edtingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('#') ? this.selectionTitle : this._titleControlString); + this._edtingTitle = true; + this._keyinput.current && setTimeout(this._keyinput.current.focus); + }) + ); + }; + + onBackgroundDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, e => this.onBackgroundMove(false, e), emptyFunction, emptyFunction); + + @action + onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => { + const dragDocView = SelectionManager.Views()[0]; + const { left, top } = dragDocView.getBounds() || { left: 0, top: 0 }; + const dragData = new DragManager.DocumentDragData( + SelectionManager.Views().map(dv => dv.props.Document), + dragDocView.props.dropAction + ); + dragData.offset = dragDocView.props.ScreenToLocalTransform().transformDirection(e.x - left, e.y - top); + dragData.moveDocument = dragDocView.props.moveDocument; + dragData.isDocDecorationMove = true; + dragData.canEmbed = dragTitle; + this._hidden = this.Interacting = true; + DragManager.StartDocumentDrag( + SelectionManager.Views().map(dv => dv.ContentDiv!), + dragData, + e.x, + e.y, + { + dragComplete: action(e => { + dragData.canEmbed && SelectionManager.DeselectAll(); + this._hidden = this.Interacting = false; + }), + hideSource: true, } - } - - titleEntered = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { - e.stopPropagation(); - (e.target as any).blur(); + ); + return true; + }; + + _deleteAfterIconify = false; + _iconifyBatch: UndoManager.Batch | undefined; + onCloseClick = (forceDeleteOrIconify: boolean | undefined) => { + const views = SelectionManager.Views() + .slice() + .filter(v => v); + if (forceDeleteOrIconify === false && this._iconifyBatch) return; + this._deleteAfterIconify = forceDeleteOrIconify || this._iconifyBatch ? true : false; + if (!this._iconifyBatch) { + this._iconifyBatch = UndoManager.StartBatch('iconifying'); + } else { + forceDeleteOrIconify = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes + } + var iconifyingCount = views.length; + const finished = action((force?: boolean) => { + if ((force || --iconifyingCount === 0) && this._iconifyBatch) { + if (this._deleteAfterIconify) { + views.forEach(iconView => iconView.props.removeDocument?.(iconView.props.Document)); + SelectionManager.DeselectAll(); + } + this._iconifyBatch?.end(); + this._iconifyBatch = undefined; } - } - - @action onTitleDown = (e: React.PointerEvent): void => { - setupMoveUpEvents(this, e, e => this.onBackgroundMove(true, e), (e) => { }, action((e) => { - !this._edtingTitle && (this._accumulatedTitle = this._titleControlString.startsWith("#") ? this.selectionTitle : this._titleControlString); - this._edtingTitle = true; - this._keyinput.current && setTimeout(this._keyinput.current.focus); - })); - } - - onBackgroundDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, e => this.onBackgroundMove(false, e), emptyFunction, emptyFunction); - - @action - onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => { - const dragDocView = SelectionManager.Views()[0]; - const { left, top } = dragDocView.getBounds() || { left: 0, top: 0 }; - const dragData = new DragManager.DocumentDragData(SelectionManager.Views().map(dv => dv.props.Document), dragDocView.props.dropAction); - dragData.offset = dragDocView.props.ScreenToLocalTransform().transformDirection(e.x - left, e.y - top); - dragData.moveDocument = dragDocView.props.moveDocument; - dragData.isDocDecorationMove = true; - dragData.canEmbed = dragTitle; - this._hidden = this.Interacting = true; - DragManager.StartDocumentDrag(SelectionManager.Views().map(dv => dv.ContentDiv!), dragData, e.x, e.y, { - dragComplete: action(e => { - dragData.canEmbed && SelectionManager.DeselectAll(); - this._hidden = this.Interacting = false; - }), - hideSource: true - }); - return true; - } - - _deleteAfterIconify = false; - _iconifyBatch: UndoManager.Batch | undefined; - onCloseClick = (forceDeleteOrIconify: boolean | undefined) => { - - const views = SelectionManager.Views().slice().filter(v => v); - if (forceDeleteOrIconify === false && this._iconifyBatch) return; - this._deleteAfterIconify = forceDeleteOrIconify || this._iconifyBatch ? true : false; - if (!this._iconifyBatch) { - this._iconifyBatch = UndoManager.StartBatch("iconifying"); + }); + if (forceDeleteOrIconify) finished(forceDeleteOrIconify); + else if (!this._deleteAfterIconify) views.forEach(dv => dv.iconify(finished)); + }; + onMaximizeDown = (e: React.PointerEvent) => { + setupMoveUpEvents( + this, + e, + () => { + DragManager.StartWindowDrag?.(e, [SelectionManager.Views().slice(-1)[0].rootDoc]); + return true; + }, + emptyFunction, + this.onMaximizeClick, + false, + false + ); + }; + + onMaximizeClick = (e: any): void => { + const selectedDocs = SelectionManager.Views(); + if (selectedDocs.length) { + if (e.ctrlKey) { + // open an alias in a new tab with Ctrl Key + const bestAlias = DocListCast(selectedDocs[0].props.Document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail); + CollectionDockingView.AddSplit(bestAlias ?? Doc.MakeAlias(selectedDocs[0].props.Document), 'right'); + } else if (e.shiftKey) { + // open centered in a new workspace with Shift Key + const alias = Doc.MakeAlias(selectedDocs[0].props.Document); + alias.context = undefined; + alias.x = -alias[WidthSym]() / 2; + alias.y = -alias[HeightSym]() / 2; + CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: 'Tab for ' + alias.title }), 'right'); + } else if (e.altKey) { + // open same document in new tab + CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, 'right'); } else { - forceDeleteOrIconify = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes + var openDoc = selectedDocs[0].props.Document; + if (openDoc.layoutKey === 'layout_icon') { + openDoc = DocListCast(openDoc.aliases).find(alias => !alias.context) ?? Doc.MakeAlias(openDoc); + Doc.deiconifyView(openDoc); + } + LightboxView.SetLightboxDoc( + openDoc, + undefined, + selectedDocs.slice(1).map(view => view.props.Document) + ); } - var iconifyingCount = views.length; - const finished = action((force?: boolean) => { - if ((force || --iconifyingCount === 0) && this._iconifyBatch) { - if (this._deleteAfterIconify) { - views.forEach(iconView => iconView.props.removeDocument?.(iconView.props.Document)); - SelectionManager.DeselectAll(); - } - this._iconifyBatch?.end(); - this._iconifyBatch = undefined; - } + } + SelectionManager.DeselectAll(); + }; + + onIconifyClick = (): void => { + SelectionManager.Views().forEach(dv => dv?.iconify()); + SelectionManager.DeselectAll(); + }; + + onSelectorClick = () => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false); + + onRadiusDown = (e: React.PointerEvent): void => { + this._resizeUndo = UndoManager.StartBatch('DocDecs set radius'); + setupMoveUpEvents( + this, + e, + (e, down) => { + const dist = Math.sqrt((e.clientX - down[0]) * (e.clientX - down[0]) + (e.clientY - down[1]) * (e.clientY - down[1])); + SelectionManager.Views() + .map(dv => dv.props.Document) + .map(doc => (doc.layout instanceof Doc ? doc.layout : doc.isTemplateForField ? doc : Doc.GetProto(doc))) + .map(d => (d.borderRounding = `${Math.max(0, dist < 3 ? 0 : dist)}px`)); + return false; + }, + e => this._resizeUndo?.end(), + e => {} + ); + }; + + @action + onRotateDown = (e: React.PointerEvent): void => { + const rotateUndo = UndoManager.StartBatch('rotatedown'); + const selectedInk = SelectionManager.Views().filter(i => i.ComponentView instanceof InkingStroke); + const centerPoint = !selectedInk.length ? { X: this.Bounds.x, Y: this.Bounds.y } : { X: this.Bounds.c?.X ?? (this.Bounds.x + this.Bounds.r) / 2, Y: this.Bounds.c?.Y ?? (this.Bounds.y + this.Bounds.b) / 2 }; + setupMoveUpEvents( + this, + e, + (e: PointerEvent, down: number[], delta: number[]) => { + const previousPoint = { X: e.clientX, Y: e.clientY }; + const movedPoint = { X: e.clientX - delta[0], Y: e.clientY - delta[1] }; + const angle = InkStrokeProperties.angleChange(previousPoint, movedPoint, centerPoint); + if (selectedInk.length) { + angle && InkStrokeProperties.Instance.rotateInk(selectedInk, -angle, centerPoint); + } else { + SelectionManager.Views().forEach(dv => (dv.rootDoc._jitterRotation = NumCast(dv.rootDoc._jitterRotation) - (angle * 180) / Math.PI)); + } + return false; + }, + () => { + rotateUndo?.end(); + UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']); + }, + emptyFunction + ); + }; + + @action + onPointerDown = (e: React.PointerEvent): void => { + DragManager.docsBeingDragged = SelectionManager.Views().map(dv => dv.rootDoc); + this._inkDragDocs = DragManager.docsBeingDragged + .filter(doc => doc.type === DocumentType.INK) + .map(doc => { + if (InkStrokeProperties.Instance._lock) { + Doc.SetNativeHeight(doc, NumCast(doc._height)); + Doc.SetNativeWidth(doc, NumCast(doc._width)); + } + return { doc, x: NumCast(doc.x), y: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) }; }); - if (forceDeleteOrIconify) finished(forceDeleteOrIconify); - else if (!this._deleteAfterIconify) views.forEach(dv => dv.iconify(finished)); - - } - onMaximizeDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, () => { - DragManager.StartWindowDrag?.(e, [SelectionManager.Views().slice(-1)[0].rootDoc]); - return true; - }, emptyFunction, this.onMaximizeClick, false, false); - } - - onMaximizeClick = (e: any): void => { - const selectedDocs = SelectionManager.Views(); - if (selectedDocs.length) { - if (e.ctrlKey) { // open an alias in a new tab with Ctrl Key - const bestAlias = DocListCast(selectedDocs[0].props.Document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail); - CollectionDockingView.AddSplit(bestAlias ?? Doc.MakeAlias(selectedDocs[0].props.Document), "right"); - } else if (e.shiftKey) { // open centered in a new workspace with Shift Key - const alias = Doc.MakeAlias(selectedDocs[0].props.Document); - alias.context = undefined; - alias.x = -alias[WidthSym]() / 2; - alias.y = -alias[HeightSym]() / 2; - CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: "Tab for " + alias.title }), "right"); - } else if (e.altKey) { // open same document in new tab - CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, "right"); - } else { - var openDoc = selectedDocs[0].props.Document; - if (openDoc.layoutKey === "layout_icon") { - openDoc = DocListCast(openDoc.aliases).find(alias => !alias.context) ?? Doc.MakeAlias(openDoc); - Doc.deiconifyView(openDoc); + + setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); + this.Interacting = true; // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them + this._resizeHdlId = e.currentTarget.className; + const bounds = e.currentTarget.getBoundingClientRect(); + this._offX = this._resizeHdlId.toLowerCase().includes('left') ? bounds.right - e.clientX : bounds.left - e.clientX; + this._offY = this._resizeHdlId.toLowerCase().includes('top') ? bounds.bottom - e.clientY : bounds.top - e.clientY; + this._resizeUndo = UndoManager.StartBatch('DocDecs resize'); + this._snapX = e.pageX; + this._snapY = e.pageY; + const ffviewSet = new Set(); + SelectionManager.Views().forEach(docView => { + const ffview = docView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + ffview && ffviewSet.add(ffview); + this._dragHeights.set(docView.layoutDoc, { start: NumCast(docView.rootDoc._height), lowest: NumCast(docView.rootDoc._height) }); + }); + Array.from(ffviewSet).map(ffview => ffview.setupDragLines(false)); + }; + + onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => { + const first = SelectionManager.Views()[0]; + let thisPt = { x: e.clientX - this._offX, y: e.clientY - this._offY }; + var fixedAspect = Doc.NativeAspect(first.layoutDoc); + InkStrokeProperties.Instance._lock && + SelectionManager.Views() + .filter(dv => dv.rootDoc.type === DocumentType.INK) + .forEach(dv => (fixedAspect = Doc.NativeAspect(dv.rootDoc))); + + const resizeHdl = this._resizeHdlId.split(' ')[0]; + if (fixedAspect && (resizeHdl === 'documentDecorations-bottomRightResizer' || resizeHdl === 'documentDecorations-topLeftResizer')) { + // need to generalize for bl and tr drag handles + const project = (p: number[], a: number[], b: number[]) => { + const atob = [b[0] - a[0], b[1] - a[1]]; + const atop = [p[0] - a[0], p[1] - a[1]]; + const len = atob[0] * atob[0] + atob[1] * atob[1]; + let dot = atop[0] * atob[0] + atop[1] * atob[1]; + const t = dot / len; + dot = (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]); + return [a[0] + atob[0] * t, a[1] + atob[1] * t]; + }; + const tl = first.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); + const drag = project([e.clientX + this._offX, e.clientY + this._offY], tl, [tl[0] + fixedAspect, tl[1] + 1]); + thisPt = DragManager.snapDragAspect(drag, fixedAspect); + } else { + thisPt = DragManager.snapDrag(e, -this._offX, -this._offY, this._offX, this._offY); + } + + move[0] = thisPt.x - this._snapX; + move[1] = thisPt.y - this._snapY; + this._snapX = thisPt.x; + this._snapY = thisPt.y; + let dragBottom = false, + dragRight = false, + dragBotRight = false, + dragTop = false; + let dX = 0, + dY = 0, + dW = 0, + dH = 0; + switch (this._resizeHdlId.split(' ')[0]) { + case '': + break; + case 'documentDecorations-topLeftResizer': + dX = -1; + dY = -1; + dW = -move[0]; + dH = -move[1]; + break; + case 'documentDecorations-topRightResizer': + dW = move[0]; + dY = -1; + dH = -move[1]; + break; + case 'documentDecorations-topResizer': + dY = -1; + dH = -move[1]; + dragTop = true; + break; + case 'documentDecorations-bottomLeftResizer': + dX = -1; + dW = -move[0]; + dH = move[1]; + break; + case 'documentDecorations-bottomRightResizer': + dW = move[0]; + dH = move[1]; + dragBotRight = true; + break; + case 'documentDecorations-bottomResizer': + dH = move[1]; + dragBottom = true; + break; + case 'documentDecorations-leftResizer': + dX = -1; + dW = -move[0]; + break; + case 'documentDecorations-rightResizer': + dW = move[0]; + dragRight = true; + break; + } + + SelectionManager.Views().forEach( + action((docView: DocumentView) => { + if (e.ctrlKey && !Doc.NativeHeight(docView.props.Document)) docView.toggleNativeDimensions(); + if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { + const doc = Document(docView.rootDoc); + const nwidth = docView.nativeWidth; + const nheight = docView.nativeHeight; + let docheight = doc._height || 0; + let docwidth = doc._width || 0; + const width = docwidth; + let height = docheight || (nheight / nwidth) * width; + height = !height || isNaN(height) ? 20 : height; + const scale = docView.props.ScreenToLocalTransform().Scale; + const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable && ((!dragBottom && !dragTop) || e.ctrlKey || doc.nativeHeightUnfrozen); + if (nwidth && nheight) { + if (nwidth / nheight !== width / height && !dragBottom && !dragTop) { + height = (nheight / nwidth) * width; } - LightboxView.SetLightboxDoc(openDoc, undefined, selectedDocs.slice(1).map(view => view.props.Document)); - } - } - SelectionManager.DeselectAll(); - } - - onIconifyClick = (): void => { - SelectionManager.Views().forEach(dv => dv?.iconify()); - SelectionManager.DeselectAll(); - } - - onSelectorClick = () => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false); - - onRadiusDown = (e: React.PointerEvent): void => { - this._resizeUndo = UndoManager.StartBatch("DocDecs set radius"); - setupMoveUpEvents(this, e, (e, down) => { - const dist = Math.sqrt((e.clientX - down[0]) * (e.clientX - down[0]) + (e.clientY - down[1]) * (e.clientY - down[1])); - SelectionManager.Views().map(dv => dv.props.Document).map(doc => doc.layout instanceof Doc ? doc.layout : doc.isTemplateForField ? doc : Doc.GetProto(doc)). - map(d => d.borderRounding = `${Math.max(0, dist < 3 ? 0 : dist)}px`); - return false; - }, (e) => this._resizeUndo?.end(), (e) => { }); - } - - @action - onRotateDown = (e: React.PointerEvent): void => { - const rotateUndo = UndoManager.StartBatch("rotatedown"); - const selectedInk = SelectionManager.Views().filter(i => i.ComponentView instanceof InkingStroke); - const centerPoint = !selectedInk.length ? { X: this.Bounds.x, Y: this.Bounds.y } : { X: this.Bounds.c?.X ?? (this.Bounds.x + this.Bounds.r) / 2, Y: this.Bounds.c?.Y ?? (this.Bounds.y + this.Bounds.b) / 2 }; - setupMoveUpEvents(this, e, - (e: PointerEvent, down: number[], delta: number[]) => { - const previousPoint = { X: e.clientX, Y: e.clientY }; - const movedPoint = { X: e.clientX - delta[0], Y: e.clientY - delta[1] }; - const angle = InkStrokeProperties.angleChange(previousPoint, movedPoint, centerPoint); - if (selectedInk.length) { - angle && InkStrokeProperties.Instance.rotateInk(selectedInk, -angle, centerPoint); - } else { - SelectionManager.Views().forEach(dv => dv.rootDoc._jitterRotation = NumCast(dv.rootDoc._jitterRotation) - angle * 180 / Math.PI); + if (modifyNativeDim && !dragBottom && !dragTop) { + // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction + if (Math.abs(dW) > Math.abs(dH)) dH = (dW * nheight) / nwidth; + else dW = (dH * nwidth) / nheight; } - return false; - }, - () => { - rotateUndo?.end(); - UndoManager.FilterBatches(["data", "x", "y", "width", "height"]); - }, - emptyFunction); - } - - @action - onPointerDown = (e: React.PointerEvent): void => { - DragManager.docsBeingDragged = SelectionManager.Views().map(dv => dv.rootDoc); - this._inkDragDocs = DragManager.docsBeingDragged - .filter(doc => doc.type === DocumentType.INK) - .map(doc => { - if (InkStrokeProperties.Instance._lock) { - Doc.SetNativeHeight(doc, NumCast(doc._height)); - Doc.SetNativeWidth(doc, NumCast(doc._width)); + } + let actualdW = Math.max(width + dW * scale, 20); + let actualdH = Math.max(height + dH * scale, 20); + const fixedAspect = nwidth && nheight && (!doc._fitWidth || e.ctrlKey || doc.nativeHeightUnfrozen); + console.log(fixedAspect); + if (fixedAspect) { + if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop) || !modifyNativeDim)) || dragRight) { + if (dragRight && modifyNativeDim) { + doc._nativeWidth = (actualdW / (doc._width || 1)) * Doc.NativeWidth(doc); + } else { + if (!doc._fitWidth) { + actualdH = (nheight / nwidth) * actualdW; + doc._height = actualdH; + } else if (!modifyNativeDim || dragBotRight) doc._height = actualdH; + } + doc._width = actualdW; + } else { + if ((dragBottom || dragTop) && (modifyNativeDim || (docView.layoutDoc.nativeHeightUnfrozen && docView.layoutDoc._fitWidth))) { + // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height. But they are marked to allow their nativeHeight to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used) + doc._nativeHeight = (actualdH / (doc._height || 1)) * Doc.NativeHeight(doc); + doc._autoHeight = false; + } else { + if (!doc._fitWidth) { + actualdW = (nwidth / nheight) * actualdH; + doc._width = actualdW; + } else if (!modifyNativeDim || dragBotRight) doc._width = actualdW; + } + if (!modifyNativeDim) { + actualdH = Math.min((nheight / nwidth) * NumCast(doc._width), actualdH); + doc._height = actualdH; + } else doc._height = actualdH; } - return ({ doc, x: NumCast(doc.x), y: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) }); - }); - - setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); - this.Interacting = true; // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them - this._resizeHdlId = e.currentTarget.className; - const bounds = e.currentTarget.getBoundingClientRect(); - this._offX = this._resizeHdlId.toLowerCase().includes("left") ? bounds.right - e.clientX : bounds.left - e.clientX; - this._offY = this._resizeHdlId.toLowerCase().includes("top") ? bounds.bottom - e.clientY : bounds.top - e.clientY; - this._resizeUndo = UndoManager.StartBatch("DocDecs resize"); - this._snapX = e.pageX; - this._snapY = e.pageY; - const ffviewSet = new Set(); - SelectionManager.Views().forEach(docView => { - const ffview = docView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; - ffview && ffviewSet.add(ffview); - this._dragHeights.set(docView.layoutDoc, { start: NumCast(docView.rootDoc._height), lowest: NumCast(docView.rootDoc._height) }); + } else { + dH && (doc._height = actualdH); + dW && (doc._width = actualdW); + dH && (doc._autoHeight = false); + } + doc.x = (doc.x || 0) + dX * (actualdW - docwidth); + doc.y = (doc.y || 0) + (dragBottom ? 0 : dY * (actualdH - docheight)); + doc._lastModified = new DateField(); + } + const val = this._dragHeights.get(docView.layoutDoc); + if (val) this._dragHeights.set(docView.layoutDoc, { start: val.start, lowest: Math.min(val.lowest, NumCast(docView.layoutDoc._height)) }); + }) + ); + return false; + }; + + @action + onPointerUp = (e: PointerEvent): void => { + this._resizeHdlId = ''; + this.Interacting = false; + this._resizeUndo?.end(); + SnappingManager.clearSnapLines(); + + // detect autoHeight gesture and apply + SelectionManager.Views() + .map(docView => ({ doc: docView.layoutDoc, hgts: this._dragHeights.get(docView.layoutDoc) })) + .filter(pair => pair.hgts && pair.hgts.lowest < pair.hgts.start && pair.hgts.lowest <= 20) + .forEach(pair => (pair.doc._autoHeight = true)); + //need to change points for resize, or else rotation/control points will fail. + this._inkDragDocs + .map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] })) + .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => { + Doc.GetProto(doc).data = new InkField( + inkPts.map( + ( + ipt // (new x — oldx) + newWidth * (oldxpoint /oldWidth) + ) => ({ + X: NumCast(doc.x) - x + (NumCast(doc.width) * ipt.X) / width, + Y: NumCast(doc.y) - y + (NumCast(doc.height) * ipt.Y) / height, + }) + ) + ); + Doc.SetNativeWidth(doc, undefined); + Doc.SetNativeHeight(doc, undefined); }); - Array.from(ffviewSet).map(ffview => ffview.setupDragLines(false)); - } - - onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => { - const first = SelectionManager.Views()[0]; - let thisPt = { x: e.clientX - this._offX, y: e.clientY - this._offY }; - var fixedAspect = Doc.NativeAspect(first.layoutDoc); - InkStrokeProperties.Instance._lock && SelectionManager.Views().filter(dv => dv.rootDoc.type === DocumentType.INK) - .forEach(dv => fixedAspect = Doc.NativeAspect(dv.rootDoc)); - - const resizeHdl = this._resizeHdlId.split(" ")[0]; - if (fixedAspect && (resizeHdl === "documentDecorations-bottomRightResizer" || resizeHdl === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles - const project = (p: number[], a: number[], b: number[]) => { - const atob = [b[0] - a[0], b[1] - a[1]]; - const atop = [p[0] - a[0], p[1] - a[1]]; - const len = atob[0] * atob[0] + atob[1] * atob[1]; - let dot = atop[0] * atob[0] + atop[1] * atob[1]; - const t = dot / len; - dot = (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]); - return [a[0] + atob[0] * t, a[1] + atob[1] * t]; - }; - const tl = first.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); - const drag = project([e.clientX + this._offX, e.clientY + this._offY], tl, [tl[0] + fixedAspect, tl[1] + 1]); - thisPt = DragManager.snapDragAspect(drag, fixedAspect); - } else { - thisPt = DragManager.snapDrag(e, -this._offX, -this._offY, this._offX, this._offY); + }; + + @computed + get selectionTitle(): string { + if (SelectionManager.Views().length === 1) { + const selected = SelectionManager.Views()[0]; + if (selected.ComponentView?.getTitle?.()) { + return selected.ComponentView.getTitle(); } - - move[0] = thisPt.x - this._snapX; - move[1] = thisPt.y - this._snapY; - this._snapX = thisPt.x; - this._snapY = thisPt.y; - let dragBottom = false, dragRight = false, dragBotRight = false, dragTop = false; - let dX = 0, dY = 0, dW = 0, dH = 0; - switch (this._resizeHdlId.split(" ")[0]) { - case "": break; - case "documentDecorations-topLeftResizer": - dX = -1; - dY = -1; - dW = -move[0]; - dH = -move[1]; - break; - case "documentDecorations-topRightResizer": - dW = move[0]; - dY = -1; - dH = -move[1]; - break; - case "documentDecorations-topResizer": - dY = -1; - dH = -move[1]; - dragTop = true; - break; - case "documentDecorations-bottomLeftResizer": - dX = -1; - dW = -move[0]; - dH = move[1]; - break; - case "documentDecorations-bottomRightResizer": - dW = move[0]; - dH = move[1]; - dragBotRight = true; - break; - case "documentDecorations-bottomResizer": - dH = move[1]; - dragBottom = true; - break; - case "documentDecorations-leftResizer": - dX = -1; - dW = -move[0]; - break; - case "documentDecorations-rightResizer": - dW = move[0]; - dragRight = true; - break; + if (this._titleControlString.startsWith('=')) { + return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.rootDoc, this: selected.layoutDoc }, console.log).result?.toString() || ''; } - - SelectionManager.Views().forEach(action((docView: DocumentView) => { - if (e.ctrlKey && !Doc.NativeHeight(docView.props.Document)) docView.toggleNativeDimensions(); - if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { - const doc = Document(docView.rootDoc); - const nwidth = docView.nativeWidth; - const nheight = docView.nativeHeight; - let docheight = doc._height || 0; - let docwidth = doc._width || 0; - const width = docwidth; - let height = (docheight || (nheight / nwidth * width)); - height = !height || isNaN(height) ? 20 : height; - const scale = docView.props.ScreenToLocalTransform().Scale; - const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable && ((!dragBottom && !dragTop) || e.ctrlKey || doc.nativeHeightUnfrozen); - if (nwidth && nheight) { - if (nwidth / nheight !== width / height && !dragBottom && !dragTop) { - height = nheight / nwidth * width; - } - if (modifyNativeDim && !dragBottom && !dragTop) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction - if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth; - else dW = dH * nwidth / nheight; - } - } - let actualdW = Math.max(width + (dW * scale), 20); - let actualdH = Math.max(height + (dH * scale), 20); - const fixedAspect = (nwidth && nheight && (!doc._fitWidth || e.ctrlKey || doc.nativeHeightUnfrozen)); - console.log(fixedAspect); - if (fixedAspect) { - if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop)|| !modifyNativeDim)) || dragRight) { - if (dragRight && modifyNativeDim) { - doc._nativeWidth = actualdW / (doc._width || 1) * Doc.NativeWidth(doc); - } else { - if (!doc._fitWidth) { - actualdH = nheight / nwidth * actualdW; - doc._height = actualdH; - } - else if (!modifyNativeDim || dragBotRight) doc._height = actualdH; - } - doc._width = actualdW; - } - else { - if ((dragBottom|| dragTop) && (modifyNativeDim || - (docView.layoutDoc.nativeHeightUnfrozen && docView.layoutDoc._fitWidth))) { // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height. But they are marked to allow their nativeHeight to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used) - doc._nativeHeight = actualdH / (doc._height || 1) * Doc.NativeHeight(doc); - doc._autoHeight = false; - } else { - if (!doc._fitWidth) { - actualdW = nwidth / nheight * actualdH; - doc._width = actualdW; - } - else if (!modifyNativeDim || dragBotRight) doc._width = actualdW; - } - if (!modifyNativeDim) { - actualdH = Math.min(nheight / nwidth * NumCast(doc._width), actualdH); - doc._height = actualdH; - } - else doc._height = actualdH; - } - } else { - dH && (doc._height = actualdH); - dW && (doc._width = actualdW); - dH && (doc._autoHeight = false); - } - doc.x = (doc.x || 0) + dX * (actualdW - docwidth); - doc.y = (doc.y || 0) + (dragBottom ? 0: dY * (actualdH - docheight)); - doc._lastModified = new DateField(); - } - const val = this._dragHeights.get(docView.layoutDoc); - if (val) this._dragHeights.set(docView.layoutDoc, { start: val.start, lowest: Math.min(val.lowest, NumCast(docView.layoutDoc._height)) }); - })); - return false; - } - - @action - onPointerUp = (e: PointerEvent): void => { - this._resizeHdlId = ""; - this.Interacting = false; - this._resizeUndo?.end(); - SnappingManager.clearSnapLines(); - - // detect autoHeight gesture and apply - SelectionManager.Views().map(docView => ({ doc: docView.layoutDoc, hgts: this._dragHeights.get(docView.layoutDoc) })) - .filter(pair => pair.hgts && pair.hgts.lowest < pair.hgts.start && pair.hgts.lowest <= 20) - .forEach(pair => pair.doc._autoHeight = true); - //need to change points for resize, or else rotation/control points will fail. - this._inkDragDocs.map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] })) - .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => { - Doc.GetProto(doc).data = new InkField(inkPts.map(ipt => // (new x — oldx) + newWidth * (oldxpoint /oldWidth) - ({ - X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width, - Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height - }))); - Doc.SetNativeWidth(doc, undefined); - Doc.SetNativeHeight(doc, undefined); - }); - } - - @computed - get selectionTitle(): string { - if (SelectionManager.Views().length === 1) { - const selected = SelectionManager.Views()[0]; - if (selected.ComponentView?.getTitle?.()) { - return selected.ComponentView.getTitle(); - } - if (this._titleControlString.startsWith("=")) { - return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.rootDoc, this: selected.layoutDoc }, console.log).result?.toString() || ""; - } - if (this._titleControlString.startsWith("#")) { - return Field.toString(selected.props.Document[this._titleControlString.substring(1)] as Field) || "-unset-"; - } - return this._accumulatedTitle; + if (this._titleControlString.startsWith('#')) { + return Field.toString(selected.props.Document[this._titleControlString.substring(1)] as Field) || '-unset-'; } - return SelectionManager.Views().length > 1 ? "-multiple-" : "-unset-"; - } - - @computed get hasIcons() { - return SelectionManager.Views().some(docView => docView.rootDoc.layoutKey === "layout_icon"); - } - - render() { - const bounds = this.Bounds; - const seldoc = SelectionManager.Views().slice(-1)[0]; - if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { - return (null); - } - // hide the decorations if the parent chooses to hide it or if the document itself hides it - const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc._isGroup; - const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle; - const hideDocumentButtonBar = seldoc.props.hideDocumentButtonBar || seldoc.rootDoc.hideDocumentButtonBar; - // if multiple documents have been opened at the same time, then don't show open button - const hideOpenButton = seldoc.props.hideOpenButton || seldoc.rootDoc.hideOpenButton || - SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton); - const hideDeleteButton = seldoc.props.hideDeleteButton || seldoc.rootDoc.hideDeleteButton || - SelectionManager.Views().some(docView => { - const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit; - return docView.rootDoc.stayInCollection || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin); - }); - const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => ( - {title}} placement="top"> -
e.preventDefault()} - onPointerDown={pointerDown ?? (e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(e => click!(e))))} > - -
-
); - - const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); - const titleArea = hideTitle ? (null) : - this._edtingTitle ? - this.titleBlur()} - onChange={action(e => this._accumulatedTitle = e.target.value)} - onKeyDown={this.titleEntered} /> : -
- {`${this.selectionTitle}`} -
; - - - const leftBounds = this.props.boundsLeft; - const topBounds = LightboxView.LightboxDoc ? 0 : this.props.boundsTop; - bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2; - bounds.y = Math.max(topBounds, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight; - const borderRadiusDraggerWidth = 15; - bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth / 2) - this._resizeBorderWidth / 2 - borderRadiusDraggerWidth)); - bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight)); - - const useRotation = seldoc.ComponentView instanceof InkingStroke || seldoc.ComponentView instanceof ImageBox; - const resizerScheme = colorScheme ? "documentDecorations-resizer" + colorScheme : ""; - - const rotation = NumCast(seldoc.rootDoc._jitterRotation); - - return (
-
1 ? '-multiple-' : '-unset-'; + } + + @computed get hasIcons() { + return SelectionManager.Views().some(docView => docView.rootDoc.layoutKey === 'layout_icon'); + } + + render() { + const bounds = this.Bounds; + const seldoc = SelectionManager.Views().slice(-1)[0]; + if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { + return null; + } + // hide the decorations if the parent chooses to hide it or if the document itself hides it + const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc._isGroup; + const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle; + const hideDocumentButtonBar = seldoc.props.hideDocumentButtonBar || seldoc.rootDoc.hideDocumentButtonBar; + // if multiple documents have been opened at the same time, then don't show open button + const hideOpenButton = + seldoc.props.hideOpenButton || seldoc.rootDoc.hideOpenButton || SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton); + const hideDeleteButton = + seldoc.props.hideDeleteButton || + seldoc.rootDoc.hideDeleteButton || + SelectionManager.Views().some(docView => { + const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit; + return docView.rootDoc.stayInCollection || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin); + }); + const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => ( + {title}
} placement="top"> +
e.preventDefault()} + onPointerDown={ + pointerDown ?? + (e => + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + undoBatch(e => click!(e)) + )) + }> + +
+ + ); + + const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); + const titleArea = hideTitle ? null : this._edtingTitle ? ( + this.titleBlur()} + onChange={action(e => (this._accumulatedTitle = e.target.value))} + onKeyDown={this.titleEntered} + /> + ) : ( +
+ {`${this.selectionTitle}`} +
+ ); + + const leftBounds = this.props.boundsLeft; + const topBounds = LightboxView.LightboxDoc ? 0 : this.props.boundsTop; + bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2; + bounds.y = Math.max(topBounds, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight; + const borderRadiusDraggerWidth = 15; + bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth / 2) - this._resizeBorderWidth / 2 - borderRadiusDraggerWidth)); + bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight)); + + const useRotation = seldoc.ComponentView instanceof InkingStroke || seldoc.ComponentView instanceof ImageBox; + const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : ''; + + const rotation = NumCast(seldoc.rootDoc._jitterRotation); + + return ( +
+
{ e.preventDefault(); e.stopPropagation(); }} /> - {bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? (null) : <> -
- {hideDeleteButton ?
: topBtn("close", this.hasIcons ? "times" : "window-maximize", undefined, e => this.onCloseClick(this.hasIcons ? true : undefined), "Close")} - {titleArea} - {hideOpenButton ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")} - {hideResizers ? (null) : - <> -
e.preventDefault()} /> -
e.preventDefault()} /> -
e.preventDefault()} /> -
e.preventDefault()} /> -
-
e.preventDefault()} /> -
e.preventDefault()} /> -
e.preventDefault()} /> -
e.preventDefault()} /> - - {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) : - topBtn("selector", "arrow-alt-circle-up", undefined, this.onSelectorClick, "tap to select containing document")} -
e.preventDefault()}>{useRotation && "⟲"}
- - } - - {hideDocumentButtonBar ? (null) : -
- -
} -
- } -
- ); - } -} \ No newline at end of file + pointerEvents: DocumentDecorations.Instance.AddToSelection || this.Interacting ? 'none' : 'all', + display: SelectionManager.Views().length <= 1 ? 'none' : undefined, + }} + onPointerDown={this.onBackgroundDown} + onContextMenu={e => { + e.preventDefault(); + e.stopPropagation(); + }} + /> + {bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? null : ( + <> +
+ {hideDeleteButton ?
: topBtn('close', this.hasIcons ? 'times' : 'window-maximize', undefined, e => this.onCloseClick(this.hasIcons ? true : undefined), 'Close')} + {titleArea} + {hideOpenButton ? null : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Tab (ctrl: as alias, shift: in new collection)')} + {hideResizers ? null : ( + <> +
e.preventDefault()} /> +
e.preventDefault()} /> +
e.preventDefault()} /> +
e.preventDefault()} /> +
+
e.preventDefault()} /> +
e.preventDefault()} /> +
e.preventDefault()} /> +
e.preventDefault()} /> + + {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectorClick, 'tap to select containing document')} + {useRotation && ( +
e.preventDefault()}> + {'⟲'} +
+ )} +
e.preventDefault()} /> + + )} + + {hideDocumentButtonBar ? null : ( +
+ +
+ )} +
+ + )} +
+ ); + } +} -- cgit v1.2.3-70-g09d2 From 16703dd7b46a7c0f7ded58cc482e9fda10b64f35 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 3 Jul 2022 13:55:01 -0400 Subject: fixed worker version for pdf to match npm version # --- src/client/views/pdf/PDFViewer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 2869d4f2d..510c5c385 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -30,7 +30,7 @@ const _global = (window /* browser */ || global /* node */) as any; //pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`; // The workerSrc property shall be specified. -pdfjsLib.GlobalWorkerOptions.workerSrc = "https://unpkg.com/pdfjs-dist@2.13.216/build/pdf.worker.js"; +pdfjsLib.GlobalWorkerOptions.workerSrc = "https://unpkg.com/pdfjs-dist@2.14.305/build/pdf.worker.js"; interface IViewerProps extends FieldViewProps { Document: Doc; -- cgit v1.2.3-70-g09d2 From ed099a3990d8047c32b614f1ca2d13af67910998 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 3 Jul 2022 14:02:26 -0400 Subject: fixed clicking on empty parts of equation box to start editing --- src/client/views/nodes/EquationBox.scss | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/client/views/nodes/EquationBox.scss b/src/client/views/nodes/EquationBox.scss index c6a497831..9714e1bd0 100644 --- a/src/client/views/nodes/EquationBox.scss +++ b/src/client/views/nodes/EquationBox.scss @@ -2,4 +2,7 @@ .equationBox-cont { transform-origin: top left; + > span { + width: 100%; + } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 4710ba982c5fd974e7edf06b35eb9199584eb033 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 3 Jul 2022 14:21:41 -0400 Subject: fixed contextMenu to not trigger on a collection when the contextMenu event has already been handled. --- src/client/views/collections/CollectionView.tsx | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index b432104a1..83f5198a9 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -173,6 +173,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent { const cm = ContextMenu.Instance; + if (e.nativeEvent.cancelBubble) return; if (cm && !e.isPropagationStopped() && this.rootDoc[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 this.setupViewTypes("UI Controls...", vtype => { const newRendition = Doc.MakeAlias(this.rootDoc); -- cgit v1.2.3-70-g09d2 From 3419d46a569da7ae8899588251426b82996ca523 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 3 Jul 2022 21:02:48 -0400 Subject: from last --- src/client/views/collections/CollectionView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 83f5198a9..2ae0c01ef 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -173,7 +173,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent { const cm = ContextMenu.Instance; - if (e.nativeEvent.cancelBubble) return; + if (e.nativeEvent.cancelBubble) return; // nested calls to React to render can cause the same event to trigger in the outer view even if the inner view has handled it. This avoid CollectionDockingView menu options from being added when the event has been handled by a sub-document. if (cm && !e.isPropagationStopped() && this.rootDoc[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 this.setupViewTypes("UI Controls...", vtype => { const newRendition = Doc.MakeAlias(this.rootDoc); -- cgit v1.2.3-70-g09d2