diff options
| author | Naafiyan Ahmed <naafiyan@gmail.com> | 2022-07-12 12:08:49 -0400 |
|---|---|---|
| committer | Naafiyan Ahmed <naafiyan@gmail.com> | 2022-07-12 12:08:49 -0400 |
| commit | 9ad978eac113cf3559d885c62a9368a68f6333ec (patch) | |
| tree | 8daa3a5663379b29d8121aaa39e80cfd5e7fd9ed /src/client/views/DocumentDecorations.tsx | |
| parent | 31041d7a5b2c3699518ebb33ccab016af0acd579 (diff) | |
| parent | 5628b585fa6356d66cf2e7454be20e3b847ad22e (diff) | |
merged master
Diffstat (limited to 'src/client/views/DocumentDecorations.tsx')
| -rw-r--r-- | src/client/views/DocumentDecorations.tsx | 157 |
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> ); |
