aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/DocumentDecorations.tsx
diff options
context:
space:
mode:
authorNaafiyan Ahmed <naafiyan@gmail.com>2022-07-12 12:08:49 -0400
committerNaafiyan Ahmed <naafiyan@gmail.com>2022-07-12 12:08:49 -0400
commit9ad978eac113cf3559d885c62a9368a68f6333ec (patch)
tree8daa3a5663379b29d8121aaa39e80cfd5e7fd9ed /src/client/views/DocumentDecorations.tsx
parent31041d7a5b2c3699518ebb33ccab016af0acd579 (diff)
parent5628b585fa6356d66cf2e7454be20e3b847ad22e (diff)
merged master
Diffstat (limited to 'src/client/views/DocumentDecorations.tsx')
-rw-r--r--src/client/views/DocumentDecorations.tsx157
1 files changed, 113 insertions, 44 deletions
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 9dc02e3f4..c55daca3f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -7,13 +7,12 @@ import { DateField } from '../../fields/DateField';
import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSym } from '../../fields/Doc';
import { Document } from '../../fields/documentSchemas';
import { InkField } from '../../fields/InkField';
-import { ComputedField, ScriptField } from '../../fields/ScriptField';
-import { Cast, FieldValue, NumCast, StrCast } from '../../fields/Types';
+import { ScriptField } from '../../fields/ScriptField';
+import { Cast, NumCast, StrCast } from '../../fields/Types';
import { GetEffectiveAcl } from '../../fields/util';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../Utils';
+import { emptyFunction, numberValue, 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 { SnappingManager } from '../util/SnappingManager';
@@ -22,7 +21,7 @@ import { CollectionDockingView } from './collections/CollectionDockingView';
import { CollectionFreeFormView } from './collections/collectionFreeForm';
import { DocumentButtonBar } from './DocumentButtonBar';
import './DocumentDecorations.scss';
-import { KeyManager } from './GlobalKeyHandler';
+import { Colors } from './global/globalEnums';
import { InkingStroke } from './InkingStroke';
import { InkStrokeProperties } from './InkStrokeProperties';
import { LightboxView } from './LightboxView';
@@ -49,20 +48,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
@observable private _accumulatedTitle = '';
@observable private _titleControlString: string = '#title';
- @observable private _edtingTitle = false;
+ @observable private _editingTitle = 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';
+ @observable private _isRotating: boolean = false;
+ @observable private _isRounding: boolean = false;
+ @observable private _isResizing: boolean = false;
constructor(props: any) {
super(props);
DocumentDecorations.Instance = this;
reaction(
() => SelectionManager.Views().slice(),
- action(docs => (this._edtingTitle = false))
+ action(docs => (this._editingTitle = false))
);
}
@@ -89,7 +91,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
@action
titleBlur = () => {
- this._edtingTitle = false;
+ this._editingTitle = false;
if (this._accumulatedTitle.startsWith('#') || this._accumulatedTitle.startsWith('=')) {
this._titleControlString = this._accumulatedTitle;
} else if (this._titleControlString.startsWith('#')) {
@@ -101,10 +103,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
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);
+ Doc.RemoveDocFromList(Doc.MyPublishedDocs, undefined, d.rootDoc);
}
if (!StrCast(d.rootDoc.title).startsWith('@') && this._accumulatedTitle.startsWith('@')) {
- Doc.AddDocToList(CurrentUserUtils.MyPublishedDocs, undefined, d.rootDoc);
+ Doc.AddDocToList(Doc.MyPublishedDocs, undefined, d.rootDoc);
}
}
//@ts-ignore
@@ -143,8 +145,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
e => this.onBackgroundMove(true, e),
e => {},
action(e => {
- !this._edtingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('#') ? this.selectionTitle : this._titleControlString);
- this._edtingTitle = true;
+ !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('#') ? this.selectionTitle : this._titleControlString);
+ this._editingTitle = true;
this._keyinput.current && setTimeout(this._keyinput.current.focus);
})
);
@@ -263,29 +265,48 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
onSelectorClick = () => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false);
+ /**
+ * Handles setting up events when user clicks on the border radius editor
+ * @param e PointerEvent
+ */
+ @action
onRadiusDown = (e: React.PointerEvent): void => {
+ this._isRounding = true;
this._resizeUndo = UndoManager.StartBatch('DocDecs set radius');
+ // Call util move event function
setupMoveUpEvents(
- this,
- e,
+ this, // target
+ e, // pointerEvent
(e, down) => {
- const dist = Math.sqrt((e.clientX - down[0]) * (e.clientX - down[0]) + (e.clientY - down[1]) * (e.clientY - down[1]));
+ const x = this.Bounds.x + 3;
+ const y = this.Bounds.y + 3;
+ const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
+ let dist = Math.sqrt((e.clientX - x) * (e.clientX - x) + (e.clientY - y) * (e.clientY - y));
+ if (e.clientX < x && e.clientY < y) dist = 0;
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`));
+ .map(doc => {
+ const docMax = Math.min(NumCast(doc.width) / 2, NumCast(doc.height) / 2);
+ const ratio = dist / maxDist;
+ const radius = Math.min(1, ratio) * docMax;
+ doc.borderRounding = `${radius}px`;
+ });
return false;
- },
- e => this._resizeUndo?.end(),
- e => {}
+ }, // moveEvent
+ action(e => {
+ this._isRounding = false;
+ this._resizeUndo?.end();
+ }), // upEvent
+ e => {} // clickEvent
);
};
@action
onRotateDown = (e: React.PointerEvent): void => {
+ this._isRotating = true;
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 };
+ const centerPoint = { X: (this.Bounds.x + this.Bounds.r) / 2, Y: (this.Bounds.y + this.Bounds.b) / 2 };
setupMoveUpEvents(
this,
e,
@@ -296,14 +317,33 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
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));
+ 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;
+ });
}
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;
+ }
+ });
+ this._isRotating = false;
rotateUndo?.end();
UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
- },
+ }), // upEvent
emptyFunction
);
};
@@ -554,19 +594,26 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
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;
+ 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;
// 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);
+ seldoc.props.hideOpenButton ||
+ seldoc.rootDoc.hideOpenButton ||
+ SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton) ||
+ this._isRounding ||
+ this._isRotating;
const hideDeleteButton =
+ this._isRounding ||
+ this._isRotating ||
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
@@ -588,8 +635,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
</Tooltip>
);
- const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme);
- const titleArea = hideTitle ? null : this._edtingTitle ? (
+ const colorScheme = StrCast(Doc.ActiveDashboard?.colorScheme);
+ const titleArea = hideTitle ? null : this._editingTitle ? (
<input
ref={this._keyinput}
className={`documentDecorations-title${colorScheme}`}
@@ -615,11 +662,19 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
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 resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : '';
-
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
@@ -641,18 +696,18 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
}}
/>
{bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? null : (
- <>
+ <div>
<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`,
+ transform: `translate(${bounds.x - this._resizeBorderWidth / 2}px, ${bounds.y - this._resizeBorderWidth / 2 - this._titleHeight}px) rotate(${rotation}deg)`,
+ transformOrigin: `50% calc(50% + 10px)`,
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}
+ {hideTitle ? null : titleArea}
{hideOpenButton ? null : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Tab (ctrl: as alias, shift: in new collection)')}
{hideResizers ? null : (
<>
@@ -667,15 +722,29 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
<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()} />
</>
)}
+ {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}`,
+ }}
+ className={`documentDecorations-borderRadius`}
+ onPointerDown={this.onRadiusDown}
+ onContextMenu={e => e.preventDefault()}
+ />
+ )}
+
{hideDocumentButtonBar ? null : (
<div
className="link-button-container"
@@ -687,7 +756,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
</div>
)}
</div>
- </>
+ </div>
)}
</div>
);