diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/DocumentDecorations.scss | 49 | ||||
-rw-r--r-- | src/client/views/DocumentDecorations.tsx | 1184 | ||||
-rw-r--r-- | src/client/views/collections/CollectionView.tsx | 1 | ||||
-rw-r--r-- | src/client/views/nodes/EquationBox.scss | 3 | ||||
-rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 2 |
5 files changed, 679 insertions, 560 deletions
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<HTMLInputElement>(); - 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<Doc, { start: number, lowest: number }>(); - 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<HTMLInputElement>(); + 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<Doc, { start: number; lowest: number }>(); + 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<CollectionFreeFormView>(); + 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<CollectionFreeFormView>(); - 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) => ( - <Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top"> - <div className={`documentDecorations-${key}Button`} onContextMenu={e => e.preventDefault()} - onPointerDown={pointerDown ?? (e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(e => click!(e))))} > - <FontAwesomeIcon icon={icon as any} /> - </div> - </Tooltip>); - - const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); - const titleArea = hideTitle ? (null) : - this._edtingTitle ? - <input ref={this._keyinput} className={`documentDecorations-title${colorScheme}`} - type="text" name="dynbox" autoComplete="on" - value={this._accumulatedTitle} - onBlur={e => this.titleBlur()} - onChange={action(e => this._accumulatedTitle = e.target.value)} - onKeyDown={this.titleEntered} /> : - <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} > - <span className={`documentDecorations-titleSpan${colorScheme}`}>{`${this.selectionTitle}`}</span> - </div>; - - - 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 (<div className={`documentDecorations${colorScheme}`}> - <div className="documentDecorations-background" style={{ + return this._accumulatedTitle; + } + 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) => ( + <Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top"> + <div + className={`documentDecorations-${key}Button`} + onContextMenu={e => e.preventDefault()} + onPointerDown={ + pointerDown ?? + (e => + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + undoBatch(e => click!(e)) + )) + }> + <FontAwesomeIcon icon={icon as any} /> + </div> + </Tooltip> + ); + + const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); + const titleArea = hideTitle ? null : this._edtingTitle ? ( + <input + ref={this._keyinput} + className={`documentDecorations-title${colorScheme}`} + type="text" + name="dynbox" + autoComplete="on" + value={this._accumulatedTitle} + onBlur={e => this.titleBlur()} + onChange={action(e => (this._accumulatedTitle = e.target.value))} + onKeyDown={this.titleEntered} + /> + ) : ( + <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown}> + <span className={`documentDecorations-titleSpan${colorScheme}`}>{`${this.selectionTitle}`}</span> + </div> + ); + + 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 ( + <div className={`documentDecorations${colorScheme}`}> + <div + className="documentDecorations-background" + style={{ transform: `rotate(${rotation}deg)`, - transformOrigin: "top left", - width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px", - height: (bounds.b - bounds.y + this._resizeBorderWidth) + "px", + transformOrigin: 'top left', + width: bounds.r - bounds.x + this._resizeBorderWidth + 'px', + height: bounds.b - bounds.y + this._resizeBorderWidth + 'px', left: bounds.x - this._resizeBorderWidth / 2, top: bounds.y - this._resizeBorderWidth / 2, - 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) : <> - <div className="documentDecorations-container" key="container" style={{ - transform: ` translate(${bounds.x - this._resizeBorderWidth / 2}px, ${bounds.y - this._resizeBorderWidth / 2 - this._titleHeight}px) rotate(${rotation}deg)`, - transformOrigin: `8px 26px`, - width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px", - height: (bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight) + "px", - }}> - {hideDeleteButton ? <div /> : 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) : - <> - <div key="tl" className={`documentDecorations-topLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} /> - <div key="t" className={`documentDecorations-topResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} /> - <div key="tr" className={`documentDecorations-topRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} /> - <div key="l" className={`documentDecorations-leftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} /> - <div key="c" className={`documentDecorations-centerCont ${resizerScheme}`}></div> - <div key="r" className={`documentDecorations-rightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} /> - <div key="bl" className={`documentDecorations-bottomLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} /> - <div key="b" className={`documentDecorations-bottomResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} /> - <div key="br" className={`documentDecorations-bottomRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} /> - - {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) : - topBtn("selector", "arrow-alt-circle-up", undefined, this.onSelectorClick, "tap to select containing document")} - <div key="rot" className={`documentDecorations-${useRotation ? "rotation" : "borderRadius"}`} - onPointerDown={useRotation ? this.onRotateDown : this.onRadiusDown} - onContextMenu={e => e.preventDefault()}>{useRotation && "⟲"}</div> - </> - } - - {hideDocumentButtonBar ? (null) : - <div className="link-button-container" key="links" - style={{ - transform: ` translate(${- this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `, - }}> - <DocumentButtonBar views={SelectionManager.Views} /> - </div>} - </div > - </>} - </div > - ); - } -}
\ 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 : ( + <> + <div + className="documentDecorations-container" + key="container" + style={{ + transform: ` translate(${bounds.x - this._resizeBorderWidth / 2}px, ${bounds.y - this._resizeBorderWidth / 2 - this._titleHeight}px) rotate(${rotation}deg)`, + transformOrigin: `8px 26px`, + width: bounds.r - bounds.x + this._resizeBorderWidth + 'px', + height: bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight + 'px', + }}> + {hideDeleteButton ? <div /> : 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 : ( + <> + <div key="tl" className={`documentDecorations-topLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} /> + <div key="t" className={`documentDecorations-topResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} /> + <div key="tr" className={`documentDecorations-topRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} /> + <div key="l" className={`documentDecorations-leftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} /> + <div key="c" className={`documentDecorations-centerCont ${resizerScheme}`}></div> + <div key="r" className={`documentDecorations-rightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} /> + <div key="bl" className={`documentDecorations-bottomLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} /> + <div key="b" className={`documentDecorations-bottomResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} /> + <div key="br" className={`documentDecorations-bottomRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => 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 && ( + <div key="rot" className={`documentDecorations-rotation`} onPointerDown={this.onRotateDown} onContextMenu={e => e.preventDefault()}> + {'⟲'} + </div> + )} + <div key="br" className={`documentDecorations-borderRadius`} onPointerDown={this.onRadiusDown} onContextMenu={e => e.preventDefault()} /> + </> + )} + + {hideDocumentButtonBar ? null : ( + <div + className="link-button-container" + key="links" + style={{ + transform: ` translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `, + }}> + <DocumentButtonBar views={SelectionManager.Views} /> + </div> + )} + </div> + </> + )} + </div> + ); + } +} diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index b432104a1..2ae0c01ef 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -173,6 +173,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab onContextMenu = (e: React.MouseEvent): void => { const cm = ContextMenu.Instance; + 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); 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 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; |