aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/DocumentDecorations.tsx
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2023-04-05 22:44:03 -0400
committerbobzel <zzzman@gmail.com>2023-04-05 22:44:03 -0400
commit9b41da1af16b982ee8ac2fc09f2f8b5d67eac9fb (patch)
treebc3f57cd5b31fd453d272c925f6d5b728ab63bae /src/client/views/DocumentDecorations.tsx
parent9dae453967183b294bf4f7444b948023a1d52d39 (diff)
parent8f7e99641f84ad15f34ba9e4a60b664ac93d2e5d (diff)
Merge branch 'master' into data-visualization-view-naafi
Diffstat (limited to 'src/client/views/DocumentDecorations.tsx')
-rw-r--r--src/client/views/DocumentDecorations.tsx407
1 files changed, 284 insertions, 123 deletions
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3544f74b4..9bc583ce5 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,8 +1,10 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
-import { action, computed, observable, reaction } from 'mobx';
+import { IconButton } from 'browndash-components';
+import { action, computed, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
+import { FaUndo } from 'react-icons/fa';
import { DateField } from '../../fields/DateField';
import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSym } from '../../fields/Doc';
import { Document } from '../../fields/documentSchemas';
@@ -10,7 +12,7 @@ import { InkField } from '../../fields/InkField';
import { ScriptField } from '../../fields/ScriptField';
import { Cast, NumCast, StrCast } from '../../fields/Types';
import { GetEffectiveAcl } from '../../fields/util';
-import { emptyFunction, numberValue, returnFalse, setupMoveUpEvents } from '../../Utils';
+import { emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils';
import { Docs } from '../documents/Documents';
import { DocumentType } from '../documents/DocumentTypes';
import { DragManager } from '../util/DragManager';
@@ -25,10 +27,11 @@ import { Colors } from './global/globalEnums';
import { InkingStroke } from './InkingStroke';
import { InkStrokeProperties } from './InkStrokeProperties';
import { LightboxView } from './LightboxView';
-import { DocumentView } from './nodes/DocumentView';
+import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { ImageBox } from './nodes/ImageBox';
import React = require('react');
+import { RichTextField } from '../../fields/RichTextField';
@observer
export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> {
@@ -64,12 +67,26 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
DocumentDecorations.Instance = this;
reaction(
() => SelectionManager.Views().slice(),
- action(docs => (this._editingTitle = false))
+ action(docs => {
+ this._showNothing = !DocumentView.LongPress && docs.length === 1; // show decorations if multiple docs are selected or we're long pressing
+ this._editingTitle = false;
+ })
+ );
+ document.addEventListener(
+ // show decorations whenever pointer moves outside of selection bounds.
+ 'pointermove',
+ action(e => {
+ if (this.Bounds.x !== Number.MAX_VALUE && (this.Bounds.x > e.clientX || this.Bounds.r < e.clientX || this.Bounds.y > e.clientY || this.Bounds.b < e.clientY)) {
+ this._showNothing = false;
+ }
+ })
);
}
+ @observable overrideBounds = false;
@computed
get Bounds() {
+ if (this.overrideBounds) return { x: 0, y: 0, r: 0, b: 0 };
const views = SelectionManager.Views();
return views
.filter(dv => dv.props.renderDepth > 0)
@@ -110,20 +127,24 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
}
}
//@ts-ignore
- const titleField = +this._accumulatedTitle === this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle;
- Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
+ const titleField = +this._accumulatedTitle == this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle;
- if (d.rootDoc.syncLayoutFieldWithTitle) {
- const title = titleField.toString();
+ if (titleField.toString().startsWith('<this>')) {
+ const title = titleField.toString().replace(/<this>\.?/, '');
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;
- });
+ if (curKey !== title) {
+ if (title) {
+ if (d.dataDoc[title] === undefined || d.dataDoc[title] instanceof RichTextField || typeof d.dataDoc[title] === 'string') {
+ d.rootDoc.layoutKey = `layout_${title}`;
+ d.rootDoc[`layout_${title}`] = FormattedTextBox.LayoutString(title);
+ d.rootDoc[`${title}-nativeWidth`] = d.rootDoc[`${title}-nativeHeight`] = 0;
+ }
+ } else {
+ d.rootDoc.layoutKey = undefined;
+ }
}
+ } else {
+ Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
}
}),
'title blur'
@@ -138,6 +159,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
}
};
+ @action onContainerDown = (e: React.PointerEvent): void => {
+ setupMoveUpEvents(
+ this,
+ e,
+ e => this.onBackgroundMove(true, e),
+ e => {},
+ emptyFunction
+ );
+ };
+
@action onTitleDown = (e: React.PointerEvent): void => {
setupMoveUpEvents(
this,
@@ -165,9 +196,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
);
dragData.offset = dragDocView.props.ScreenToLocalTransform().transformDirection(e.x - left, e.y - top);
dragData.moveDocument = dragDocView.props.moveDocument;
+ dragData.removeDocument = dragDocView.props.removeDocument;
dragData.isDocDecorationMove = true;
dragData.canEmbed = dragTitle;
- this._hidden = this.Interacting = true;
+ this._hidden = true;
DragManager.StartDocumentDrag(
SelectionManager.Views().map(dv => dv.ContentDiv!),
dragData,
@@ -176,7 +208,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
{
dragComplete: action(e => {
dragData.canEmbed && SelectionManager.DeselectAll();
- this._hidden = this.Interacting = false;
+ this._hidden = false;
}),
hideSource: true,
}
@@ -189,7 +221,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
onCloseClick = (forceDeleteOrIconify: boolean | undefined) => {
const views = SelectionManager.Views()
.slice()
- .filter(v => v);
+ .filter(v => v && v.props.renderDepth > 0);
if (forceDeleteOrIconify === false && this._iconifyBatch) return;
this._deleteAfterIconify = forceDeleteOrIconify || this._iconifyBatch ? true : false;
if (!this._iconifyBatch) {
@@ -203,7 +235,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
if (this._deleteAfterIconify) {
views.forEach(iconView => {
Doc.setNativeView(iconView.props.Document);
- iconView.props.removeDocument?.(iconView.props.Document);
+ if (iconView.props.Document.activeFrame) {
+ iconView.props.Document.opacity = 0; // bcz: hacky ... allows inkMasks and other documents to be "turned off" without removing them from the animated collection which allows them to function properly in a presenation.
+ } else {
+ iconView.props.removeDocument?.(iconView.props.Document);
+ }
});
SelectionManager.DeselectAll();
}
@@ -234,29 +270,29 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
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');
+ CollectionDockingView.AddSplit(Doc.BestAlias(selectedDocs[0].props.Document), OpenWhereMod.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');
+ CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: 'Tab for ' + alias.title }), OpenWhereMod.right);
} else if (e.altKey) {
// open same document in new tab
- CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, 'right');
+ CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, OpenWhereMod.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);
}
- LightboxView.SetLightboxDoc(
- openDoc,
- undefined,
- selectedDocs.slice(1).map(view => view.props.Document)
- );
+ selectedDocs[0].props.addDocTab(openDoc, OpenWhere.lightbox);
+ // LightboxView.SetLightboxDoc(
+ // openDoc,
+ // undefined,
+ // selectedDocs.slice(1).map(view => view.props.Document)
+ // );
}
}
SelectionManager.DeselectAll();
@@ -275,7 +311,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
*/
@action
onRadiusDown = (e: React.PointerEvent): void => {
- this._isRounding = true;
+ this._isRounding = DocumentDecorations.Instance.Interacting = true;
this._resizeUndo = UndoManager.StartBatch('DocDecs set radius');
// Call util move event function
setupMoveUpEvents(
@@ -298,73 +334,130 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
return false;
}, // moveEvent
action(e => {
- this._isRounding = false;
+ DocumentDecorations.Instance.Interacting = this._isRounding = false;
this._resizeUndo?.end();
}), // upEvent
- e => {} // clickEvent
+ e => {}, // clickEvent,
+ true
+ );
+ };
+
+ @action
+ onLockDown = (e: React.PointerEvent): void => {
+ // Call util move event function
+ setupMoveUpEvents(
+ this, // target
+ e, // pointerEvent
+ returnFalse, // moveEvent
+ emptyFunction, // upEvent
+ e => {
+ UndoManager.RunInBatch(
+ () =>
+ SelectionManager.Views().map(dv => {
+ dv.rootDoc._lockedPosition = !dv.rootDoc._lockedPosition;
+ dv.rootDoc._pointerEvents = dv.rootDoc._lockedPosition ? 'none' : undefined;
+ }),
+ 'toggleBackground'
+ );
+ } // clickEvent
+ );
+ };
+
+ setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => {
+ const newloccentern = seldocview.props.ScreenToLocalTransform().transformPoint(rotCenter[0], rotCenter[1]);
+ const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2];
+ const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.rootDoc._rotation) / 180) * Math.PI);
+ seldocview.rootDoc.rotateCenterX = final.x / NumCast(seldocview.layoutDoc._width);
+ seldocview.rootDoc.rotateCenterY = final.y / NumCast(seldocview.layoutDoc._height);
+ };
+
+ @action
+ onRotateCenterDown = (e: React.PointerEvent): void => {
+ this._isRotating = true;
+ const seldocview = SelectionManager.Views()[0];
+ setupMoveUpEvents(
+ this,
+ e,
+ action((e: PointerEvent, down: number[], delta: number[]) => {
+ this.setRotateCenter(seldocview, [this.rotCenter[0] + delta[0], this.rotCenter[1] + delta[1]]);
+ return false;
+ }), // moveEvent
+ action(action(() => (this._isRotating = false))), // upEvent
+ action((e, doubleTap) => {
+ if (doubleTap) {
+ seldocview.rootDoc.rotateCenterX = 0.5;
+ seldocview.rootDoc.rotateCenterY = 0.5;
+ }
+ })
);
};
@action
onRotateDown = (e: React.PointerEvent): void => {
this._isRotating = true;
+ const rcScreen = { X: this.rotCenter[0], Y: this.rotCenter[1] };
const rotateUndo = UndoManager.StartBatch('rotatedown');
const selectedInk = SelectionManager.Views().filter(i => i.ComponentView instanceof InkingStroke);
- const centerPoint = { X: (this.Bounds.x + this.Bounds.r) / 2, Y: (this.Bounds.y + this.Bounds.b) / 2 };
+ const centerPoint = this.rotCenter.slice();
+ const infos = new Map<Doc, { unrotatedDocPos: { x: number; y: number }; startRotCtr: { x: number; y: number }; accumRot: number }>();
+ const seldocview = SelectionManager.Views()[0];
+ SelectionManager.Views().forEach(dv => {
+ const accumRot = (NumCast(dv.rootDoc._rotation) / 180) * Math.PI;
+ const localRotCtr = dv.props.ScreenToLocalTransform().transformPoint(rcScreen.X, rcScreen.Y);
+ const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.rootDoc.width) / 2, localRotCtr[1] - NumCast(dv.rootDoc.height) / 2];
+ const startRotCtr = Utils.rotPt(localRotCtrOffset[0], localRotCtrOffset[1], -accumRot);
+ const unrotatedDocPos = { x: NumCast(dv.rootDoc.x) + localRotCtrOffset[0] - startRotCtr.x, y: NumCast(dv.rootDoc.y) + localRotCtrOffset[1] - startRotCtr.y };
+ infos.set(dv.rootDoc, { unrotatedDocPos, startRotCtr, accumRot });
+ });
+ const infoRot = (angle: number, isAbs = false) => {
+ SelectionManager.Views().forEach(
+ action(dv => {
+ const { unrotatedDocPos, startRotCtr, accumRot } = infos.get(dv.rootDoc)!;
+ const endRotCtr = Utils.rotPt(startRotCtr.x, startRotCtr.y, isAbs ? angle : accumRot + angle);
+ infos.set(dv.rootDoc, { unrotatedDocPos, startRotCtr, accumRot: isAbs ? angle : accumRot + angle });
+ dv.rootDoc.x = infos.get(dv.rootDoc)!.unrotatedDocPos.x - (endRotCtr.x - startRotCtr.x);
+ dv.rootDoc.y = infos.get(dv.rootDoc)!.unrotatedDocPos.y - (endRotCtr.y - startRotCtr.y);
+ dv.rootDoc._rotation = ((isAbs ? 0 : NumCast(dv.rootDoc._rotation)) + (angle * 180) / Math.PI) % 360; // Rotation between -360 and 360
+ })
+ );
+ };
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);
+ const deltaAng = InkStrokeProperties.angleChange(movedPoint, previousPoint, rcScreen);
if (selectedInk.length) {
- angle && InkStrokeProperties.Instance.rotateInk(selectedInk, -angle, centerPoint);
+ deltaAng && InkStrokeProperties.Instance.rotateInk(selectedInk, deltaAng, rcScreen);
+ this.setRotateCenter(seldocview, centerPoint);
} else {
- SelectionManager.Views().forEach(dv => {
- const oldRotation = NumCast(dv.rootDoc._jitterRotation);
- // Rotation between -360 and 360
- let newRotation = (oldRotation - (angle * 180) / Math.PI) % 360;
-
- const diff = Math.round(newRotation / 45) - newRotation / 45;
- if (diff < 0.05) {
- console.log('show lines');
- }
- dv.rootDoc._jitterRotation = newRotation;
- });
+ infoRot(deltaAng);
}
return false;
}, // moveEvent
action(() => {
- SelectionManager.Views().forEach(dv => {
- const oldRotation = NumCast(dv.rootDoc._jitterRotation);
- const diff = Math.round(oldRotation / 45) - oldRotation / 45;
- if (diff < 0.05) {
- let newRotation = Math.round(oldRotation / 45) * 45;
- dv.rootDoc._jitterRotation = newRotation;
+ const oldRotation = NumCast(seldocview.rootDoc._rotation);
+ const diff = oldRotation - Math.round(oldRotation / 45) * 45;
+ if (Math.abs(diff) < 5) {
+ if (selectedInk.length) {
+ InkStrokeProperties.Instance.rotateInk(selectedInk, ((Math.round(oldRotation / 45) * 45 - oldRotation) / 180) * Math.PI, rcScreen);
+ } else {
+ infoRot(((Math.round(oldRotation / 45) * 45) / 180) * Math.PI, true);
}
- });
+ }
+ if (selectedInk.length) {
+ this.setRotateCenter(seldocview, centerPoint);
+ }
this._isRotating = false;
rotateUndo?.end();
- UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
}), // upEvent
- emptyFunction
+ action(e => (this._showRotCenter = !this._showRotCenter)) // clickEvent
);
};
@action
onPointerDown = (e: React.PointerEvent): void => {
- DragManager.docsBeingDragged.push(...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) };
- });
-
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;
@@ -388,10 +481,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
if (!first) return false;
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')) {
@@ -472,6 +561,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
if (e.ctrlKey && !Doc.NativeHeight(docView.props.Document)) docView.toggleNativeDimensions();
if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
const doc = Document(docView.rootDoc);
+ if (doc.nativeHeightUnfrozen && !NumCast(doc.nativeHeight)) {
+ doc._nativeHeight = (NumCast(doc._height) / NumCast(doc._width, 1)) * docView.nativeWidth;
+ }
const nwidth = docView.nativeWidth;
const nheight = docView.nativeHeight;
let docheight = doc._height || 0;
@@ -493,7 +585,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
}
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);
+ const preserveNativeDim = doc._nativeHeightUnfrozen === false && doc._nativeDimModifiable === false;
+ const fixedAspect = nwidth && nheight && (!doc._fitWidth || preserveNativeDim || e.ctrlKey || doc.nativeHeightUnfrozen || doc.nativeDimModifiable);
if (fixedAspect) {
if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop) || !modifyNativeDim)) || dragRight) {
if (dragRight && modifyNativeDim) {
@@ -501,7 +594,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
doc._nativeWidth = (actualdW / (doc._width || 1)) * Doc.NativeWidth(doc);
}
} else {
- if (!doc._fitWidth) {
+ if (!doc._fitWidth || preserveNativeDim) {
actualdH = (nheight / nwidth) * actualdW;
doc._height = actualdH;
} else if (!modifyNativeDim || dragBotRight) doc._height = actualdH;
@@ -509,11 +602,13 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
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)
+ // 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) {
+ if (!doc._fitWidth || preserveNativeDim) {
actualdW = (nwidth / nheight) * actualdH;
doc._width = actualdW;
} else if (!modifyNativeDim || dragBotRight) doc._width = actualdW;
@@ -524,10 +619,18 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
} else doc._height = actualdH;
}
} else {
- const maxHeight = 0; //Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling();
+ const rotCtr = [NumCast(doc._width) / 2, NumCast(doc._height) / 2];
+ const tlRotated = Utils.rotPt(-rotCtr[0], -rotCtr[1], (NumCast(doc._rotation) / 180) * Math.PI);
+
+ const maxHeight = doc.nativeHeightUnfrozen || !nheight ? 0 : Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling();
dH && (doc._height = actualdH > maxHeight && maxHeight ? maxHeight : actualdH);
dW && (doc._width = actualdW);
dH && (doc._autoHeight = false);
+
+ const rotCtr2 = [NumCast(doc._width) / 2, NumCast(doc._height) / 2];
+ const tlRotated2 = Utils.rotPt(-rotCtr2[0], -rotCtr2[1], (NumCast(doc._rotation) / 180) * Math.PI);
+ doc.x = NumCast(doc.x) + tlRotated.x + rotCtr[0] - (tlRotated2.x + rotCtr2[0]); // doc shifts by amount topleft moves because rotation is about center of doc
+ doc.y = NumCast(doc.y) + tlRotated.y + rotCtr[1] - (tlRotated2.y + rotCtr2[1]);
}
doc.x = (doc.x || 0) + dX * (actualdW - docwidth);
doc.y = (doc.y || 0) + (dragBottom ? 0 : dY * (actualdH - docheight));
@@ -593,28 +696,53 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
return SelectionManager.Views().some(docView => docView.rootDoc.layoutKey === 'layout_icon');
}
+ @observable _showRotCenter = false;
+ @observable _rotCenter = [0, 0];
+ @computed get rotCenter() {
+ if (SelectionManager.Views().length) {
+ const seldocview = SelectionManager.Views()[0];
+ const loccenter = Utils.rotPt(
+ NumCast(seldocview.rootDoc.rotateCenterX) * NumCast(seldocview.layoutDoc._width),
+ NumCast(seldocview.rootDoc.rotateCenterY) * NumCast(seldocview.layoutDoc._height),
+ (NumCast(seldocview.rootDoc._rotation) / 180) * Math.PI
+ );
+ return seldocview.props
+ .ScreenToLocalTransform()
+ .inverse()
+ .transformPoint(loccenter.x + NumCast(seldocview.layoutDoc._width) / 2, loccenter.y + NumCast(seldocview.layoutDoc._height) / 2);
+ }
+ return this._rotCenter;
+ }
+
+ @observable _showNothing = true;
+
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)) {
+ const { b, r, x, y } = this.Bounds;
+ const bounds = { b, r, x, y };
+ const seldocview = SelectionManager.Views().lastElement();
+ if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
+ setTimeout(action(() => (this._showNothing = true)));
return null;
}
// hide the decorations if the parent chooses to hide it or if the document itself hides it
- const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc._isGroup || this._isRounding || this._isRotating;
- const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating;
- const hideDocumentButtonBar = seldoc.props.hideDocumentButtonBar || seldoc.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating;
+ const hideDecorations = seldocview.props.hideDecorations || seldocview.rootDoc.hideDecorations;
+ const hideResizers = hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.hideResizeHandles || seldocview.rootDoc._isGroup || this._isRounding || this._isRotating;
+ const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating;
+ const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating;
// if multiple documents have been opened at the same time, then don't show open button
const hideOpenButton =
- seldoc.props.hideOpenButton ||
- seldoc.rootDoc.hideOpenButton ||
+ hideDecorations ||
+ seldocview.props.hideOpenButton ||
+ seldocview.rootDoc.hideOpenButton ||
SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton) ||
this._isRounding ||
this._isRotating;
const hideDeleteButton =
+ hideDecorations ||
this._isRounding ||
this._isRotating ||
- seldoc.props.hideDeleteButton ||
- seldoc.rootDoc.hideDeleteButton ||
+ seldocview.props.hideDeleteButton ||
+ seldocview.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);
@@ -642,6 +770,29 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
);
const colorScheme = StrCast(Doc.ActiveDashboard?.colorScheme);
+
+ 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 useLock = bounds.r - bounds.x > 135 && seldocview.props.CollectionFreeFormDocumentView;
+ const useRotation = !hideResizers && seldocview.rootDoc.type !== DocumentType.EQUATION && seldocview.props.CollectionFreeFormDocumentView; // when do we want an object to not rotate?
+ const rotation = NumCast(seldocview.rootDoc._rotation);
+
+ const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : '';
+
+ // Radius constants
+ const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView;
+ const borderRadius = numberValue(StrCast(seldocview.rootDoc.borderRounding));
+ const docMax = Math.min(NumCast(seldocview.rootDoc.width) / 2, NumCast(seldocview.rootDoc.height) / 2);
+ const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
+ const radiusHandle = (borderRadius / docMax) * maxDist;
+ const radiusHandleLocation = Math.min(radiusHandle, maxDist);
+
const titleArea = this._editingTitle ? (
<input
ref={this._keyinput}
@@ -653,41 +804,25 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
onBlur={e => !hideTitle && this.titleBlur()}
onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
+ onPointerDown={e => e.stopPropagation()}
/>
) : (
<div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown}>
<span className={`documentDecorations-titleSpan${colorScheme}`}>{`${hideTitle ? '' : this.selectionTitle}`}</span>
+ {!useLock ? null : (
+ <Tooltip key="lock" title={<div className="dash-tooltip">toggle ability to interact with document</div>} placement="top">
+ <div className="documentDecorations-lock" style={{ color: seldocview.rootDoc._lockedPosition ? 'red' : undefined }} onPointerDown={this.onLockDown} onContextMenu={e => e.preventDefault()}>
+ <FontAwesomeIcon size="sm" icon="lock" />
+ </div>
+ </Tooltip>
+ )}
</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));
-
- // Rotation constants: Only allow rotation on ink and images
- const useRotation = seldoc.ComponentView instanceof InkingStroke || seldoc.ComponentView instanceof ImageBox;
- const rotation = NumCast(seldoc.rootDoc._jitterRotation);
-
- const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : '';
-
- // Radius constants
- const useRounding = seldoc.ComponentView instanceof ImageBox || seldoc.ComponentView instanceof FormattedTextBox;
- const borderRadius = numberValue(StrCast(seldoc.rootDoc.borderRounding));
- const docMax = Math.min(NumCast(seldoc.rootDoc.width) / 2, NumCast(seldoc.rootDoc.height) / 2);
- const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
- const radiusHandle = (borderRadius / docMax) * maxDist;
- const radiusHandleLocation = Math.min(radiusHandle, maxDist);
return (
- <div className={`documentDecorations${colorScheme}`}>
+ <div className={`documentDecorations${colorScheme}`} style={{ opacity: this._showNothing ? 0.1 : undefined }}>
<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',
left: bounds.x - this._resizeBorderWidth / 2,
@@ -712,9 +847,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
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)')}
+ <div className="documentDecorations-topbar" onPointerDown={this.onContainerDown}>
+ {hideDeleteButton ? <div /> : topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')}
+ {hideResizers || hideDeleteButton ? <div /> : topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')}
+ {hideTitle ? null : titleArea}
+ {hideOpenButton ? <div /> : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection)')}
+ </div>
{hideResizers ? null : (
<>
<div key="tl" className={`documentDecorations-topLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
@@ -727,23 +865,15 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
<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')}
+ {seldocview.props.renderDepth <= 1 || !seldocview.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>
- )}
-
{useRounding && (
<div
key="rad"
style={{
background: `${this._isRounding ? Colors.MEDIUM_BLUE : undefined}`,
- left: `${radiusHandleLocation + 3}`,
- top: `${radiusHandleLocation + 23}`,
+ transform: `translate(${radiusHandleLocation}px, ${radiusHandleLocation}px)`,
}}
className={`documentDecorations-borderRadius`}
onPointerDown={this.onRadiusDown}
@@ -756,12 +886,43 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
className="link-button-container"
key="links"
style={{
- transform: ` translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
+ transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
}}>
<DocumentButtonBar views={SelectionManager.Views} />
</div>
)}
</div>
+
+ {useRotation && (
+ <>
+ <div
+ style={{
+ position: 'absolute',
+ transform: `rotate(${rotation}deg)`,
+ width: this.Bounds.r - this.Bounds.x + 'px',
+ height: this.Bounds.b - this.Bounds.y + 'px',
+ left: this.Bounds.x,
+ top: this.Bounds.y,
+ pointerEvents: 'none',
+ }}>
+ {this._isRotating ? null : (
+ <Tooltip enterDelay={750} title={<div className="dash-tooltip">tap to set rotate center, drag to rotate</div>}>
+ <div className="documentDecorations-rotation" style={{ pointerEvents: 'all' }} onPointerDown={this.onRotateDown} onContextMenu={e => e.preventDefault()}>
+ <IconButton icon={<FaUndo />} isCircle={true} hoverStyle={'lighten'} backgroundColor={Colors.DARK_GRAY} color={Colors.LIGHT_GRAY} />
+ </div>
+ </Tooltip>
+ )}
+ </div>
+ {!this._showRotCenter ? null : (
+ <div
+ className="documentDecorations-rotationCenter"
+ style={{ transform: `translate(${this.rotCenter[0] - 3}px, ${this.rotCenter[1] - 3}px)` }}
+ onPointerDown={this.onRotateCenterDown}
+ onContextMenu={e => e.preventDefault()}
+ />
+ )}
+ </>
+ )}
</div>
)}
</div>