aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/DocumentDecorations.scss49
-rw-r--r--src/client/views/DocumentDecorations.tsx1184
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>
+ );
+ }
+}