diff options
author | Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com> | 2022-07-01 12:44:13 -0700 |
---|---|---|
committer | Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com> | 2022-07-01 12:44:13 -0700 |
commit | a170b1ac4083cd322cc0d4b1e08087cb45867e55 (patch) | |
tree | 86956e72f5a72f98c332d520e397165bd2de0a3d /src | |
parent | 9da331c1a430625b136acbaa515d33448f96d495 (diff) |
added resize handlers and linted document decorations
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/DocumentDecorations.scss | 49 | ||||
-rw-r--r-- | src/client/views/DocumentDecorations.tsx | 1184 |
2 files changed, 674 insertions, 559 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> + ); + } +} |