diff options
-rw-r--r-- | src/ClientUtils.ts | 2 | ||||
-rw-r--r-- | src/client/documents/Documents.ts | 2 | ||||
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 36 | ||||
-rw-r--r-- | src/client/util/bezierFit.ts | 9 | ||||
-rw-r--r-- | src/client/views/ComponentDecorations.tsx | 6 | ||||
-rw-r--r-- | src/client/views/DocumentDecorations.tsx | 14 | ||||
-rw-r--r-- | src/client/views/InkingStroke.tsx | 14 | ||||
-rw-r--r-- | src/client/views/PropertiesView.scss | 1 | ||||
-rw-r--r-- | src/client/views/PropertiesView.tsx | 33 | ||||
-rw-r--r-- | src/client/views/TagsView.scss | 1 | ||||
-rw-r--r-- | src/client/views/collections/CollectionCardDeckView.tsx | 20 | ||||
-rw-r--r-- | src/client/views/collections/CollectionCarousel3DView.tsx | 6 | ||||
-rw-r--r-- | src/client/views/collections/CollectionCarouselView.tsx | 6 | ||||
-rw-r--r-- | src/client/views/global/globalScripts.ts | 21 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 6 | ||||
-rw-r--r-- | src/fields/InkField.ts | 8 |
16 files changed, 110 insertions, 75 deletions
diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts index baad9a06c..8f62b9060 100644 --- a/src/ClientUtils.ts +++ b/src/ClientUtils.ts @@ -165,7 +165,7 @@ export namespace ClientUtils { return { scale: 0, translateX: 1, translateY: 1 }; } const rect = ele.getBoundingClientRect(); - const scale = ele.offsetWidth === 0 && rect.width === 0 ? 1 : rect.width / ele.offsetWidth; + const scale = ele.offsetWidth === 0 && rect.width === 0 ? 1 : rect.width / (ele.offsetWidth || 1); const translateX = rect.left; const translateY = rect.top; diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index efd6ce3c9..d898fe0c5 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -926,6 +926,8 @@ export namespace Docs { I.stroke_isInkMask = isInkMask; I.text_align = 'center'; I.rotation = 0; + I.width_min = 1; + I.height_min = 1; I.defaultDoubleClick = 'ignore'; I.author_date = new DateField(); I.acl_Guest = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 98a3e8914..fe23fe062 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -4,7 +4,7 @@ import * as rp from 'request-promise'; import { ClientUtils, OmitKeys } from "../../ClientUtils"; import { Doc, DocListCast, DocListCastAsync, FieldType, Opt } from "../../fields/Doc"; import { DocData } from "../../fields/DocSymbols"; -import { InkEraserTool, InkInkTool, InkTool } from "../../fields/InkField"; +import { InkEraserTool, InkInkTool, InkProperty, InkTool } from "../../fields/InkField"; import { List } from "../../fields/List"; import { PrefetchProxy } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; @@ -759,27 +759,27 @@ pie title Minerals in my tap water static inkTools():Button[] { return [ - { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", toolType: Gestures.Circle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} }, - { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType: Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} }, - { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType: Gestures.Line, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} }, - { title: "Ink", toolTip: "Ink", btnType: ButtonType.MultiToggleButton, toolType: InkTool.Ink, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }, + { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", toolType: Gestures.Circle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} }, + { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType: Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} }, + { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType: Gestures.Line, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} }, + { title: "Ink", toolTip: "Ink", btnType: ButtonType.MultiToggleButton, toolType: InkTool.Ink, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }, subMenu: [ - { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: InkInkTool.Pen, ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }}, - { title: "Highlight",toolTip: "Highlight (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", toolType: InkInkTool.Highlight, ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }}, - { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", toolType: InkInkTool.Write, ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }}, + { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: InkInkTool.Pen, ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }}, + { title: "Highlight",toolTip: "Highlight (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", toolType: InkInkTool.Highlight, ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }}, + { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", toolType: InkInkTool.Write, ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }}, ]}, - { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberSliderButton, toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}, numBtnMin: 1, linearBtnWidth:40}, - { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}}, - { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.MultiToggleButton, toolType: InkTool.Eraser, scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, + { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberSliderButton, toolType: InkProperty.StrokeWidth,ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}, numBtnMin: 1, linearBtnWidth:40}, + { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", toolType: InkProperty.StrokeColor,ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}}, + { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.MultiToggleButton, toolType: InkTool.Eraser, scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, subMenu: [ - { title: "Stroke", toolTip: "Eraser complete strokes",btnType: ButtonType.ToggleButton, icon: "eraser", toolType:InkEraserTool.Stroke, ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}}, - { title: "Segment", toolTip: "Erase segments up to intersections",btnType: ButtonType.ToggleButton,icon: "xmark",toolType:InkEraserTool.Segment,ignoreClick:true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}}, - { title: "Area", toolTip: "Erase like a pencil", btnType: ButtonType.ToggleButton, icon: "circle-xmark",toolType:InkEraserTool.Radius, ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}}, + { title: "Stroke", toolTip: "Eraser complete strokes",btnType: ButtonType.ToggleButton, icon: "eraser", toolType:InkEraserTool.Stroke, ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}}, + { title: "Segment", toolTip: "Erase between intersections",btnType:ButtonType.ToggleButton,icon:"xmark", toolType:InkEraserTool.Segment, ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}}, + { title: "Area", toolTip: "Erase like a pencil", btnType: ButtonType.ToggleButton, icon: "circle-xmark",toolType:InkEraserTool.Radius, ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}}, ]}, - { title: " Size", toolTip: "Size of area pencil eraser", btnType: ButtonType.NumberSliderButton, toolType: "eraserWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"NotRadiusEraser()"}, numBtnMin: 1, linearBtnWidth:40}, - { title: "Mask", toolTip: "Make Stroke a Stencil Mask", btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } }, - { title: "Labels", toolTip: "Show Labels Inside Shapes", btnType: ButtonType.ToggleButton, icon: "text-width", toolType: "labels", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}}, - { title: "Smart Draw", toolTip: "Draw with GPT", btnType: ButtonType.ToggleButton, icon: "user-pen", toolType: "smartdraw", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}'}, funcs: {hidden: "IsNoviceMode()"}}, + { title: " Size", toolTip: "Size of area pencil eraser", btnType: ButtonType.NumberSliderButton, toolType: InkProperty.EraserWidth,ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"NotRadiusEraser()"}, numBtnMin: 1, linearBtnWidth:40}, + { title: "Mask", toolTip: "Make Stroke a Stencil Mask", btnType: ButtonType.ToggleButton, icon: "user-circle", toolType: InkProperty.Mask, scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } }, + { title: "Labels", toolTip: "Show Labels Inside Shapes", btnType: ButtonType.ToggleButton, icon: "text-width", toolType: InkProperty.Labels, scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}}, + { title: "Smart Draw", toolTip: "Draw with GPT", btnType: ButtonType.ToggleButton, icon: "user-pen", toolType: InkTool.SmartDraw, scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}'}, funcs: {hidden: "IsNoviceMode()"}}, ]; } diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts index 4aef28e6b..d52460023 100644 --- a/src/client/util/bezierFit.ts +++ b/src/client/util/bezierFit.ts @@ -691,8 +691,9 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] { } case 'path': { const coordList: Point[] = []; - const startPt = attributes.d.match(/M(-?\d+\.?\d*),(-?\d+\.?\d*)/); - coordList.push({ X: parseInt(startPt[1]), Y: parseInt(startPt[2]) }); + const [startX, startY] = attributes.d.match(/M(-?\d+\.?\d*),(-?\d+\.?\d*)/).slice(1); + const startPt = { X: parseInt(startX), Y: parseInt(startY) }; + coordList.push(startPt); const matches: RegExpMatchArray[] = Array.from( attributes.d.matchAll(/Q(-?\d+\.?\d*),(-?\d+\.?\d*) (-?\d+\.?\d*),(-?\d+\.?\d*)|C(-?\d+\.?\d*),(-?\d+\.?\d*) (-?\d+\.?\d*),(-?\d+\.?\d*) (-?\d+\.?\d*),(-?\d+\.?\d*)|L(-?\d+\.?\d*),(-?\d+\.?\d*)/g) ); @@ -721,8 +722,8 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] { const hasZ = attributes.d.match(/Z/); if (hasZ) { coordList.push(lastPt); - coordList.push({ X: parseInt(startPt[1]), Y: parseInt(startPt[2]) }); - coordList.push({ X: parseInt(startPt[1]), Y: parseInt(startPt[2]) }); + coordList.push(startPt); + coordList.push(startPt); } else { coordList.pop(); } diff --git a/src/client/views/ComponentDecorations.tsx b/src/client/views/ComponentDecorations.tsx index 929b549e0..28e9d9792 100644 --- a/src/client/views/ComponentDecorations.tsx +++ b/src/client/views/ComponentDecorations.tsx @@ -5,11 +5,7 @@ import { DocumentView } from './nodes/DocumentView'; @observer export class ComponentDecorations extends React.Component<{ boundsTop: number; boundsLeft: number }, { value: string }> { - // eslint-disable-next-line no-use-before-define - static Instance: ComponentDecorations; - render() { - const seldoc = DocumentView.Selected().lastElement(); - return seldoc?.ComponentView?.componentUI?.(this.props.boundsLeft, this.props.boundsTop) ?? null; + return DocumentView.Selected().map(seldoc => seldoc?.ComponentView?.componentUI?.(this.props.boundsLeft, this.props.boundsTop) ?? null); } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 5a48b6c62..492c2bda1 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -181,7 +181,13 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora }; onBackgroundDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, moveEv => this.onBackgroundMove(false, moveEv), emptyFunction, emptyFunction); + setupMoveUpEvents( + this, + e, + moveEv => this.onBackgroundMove(false, moveEv), + emptyFunction, + (clickEv, doubleTap) => doubleTap && DocumentView.Selected().some(dv => dv.Document.layout_isSvg) && (InkStrokeProperties.Instance._controlButton = true) + ); e.stopPropagation(); }; @action @@ -559,8 +565,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora if (setData) Doc.SetNativeHeight(doc[DocData], NumCast(doc._nativeHeight)); } - doc._width = Math.max(1, NumCast(doc._width) * scale.x); - doc._height = Math.max(1, NumCast(doc._height) * scale.y); + doc._width = Math.max(NumCast(doc._width_min, 25), NumCast(doc._width) * scale.x); + doc._height = Math.max(NumCast(doc._height_min, 25), NumCast(doc._height) * scale.y); const { deltaX, deltaY } = this.realignRefPt(doc, refCent, initWidth, initHeight); doc.x = NumCast(doc.x) + deltaX; doc.y = NumCast(doc.y) + deltaY; @@ -791,7 +797,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora transformOrigin, background: SnappingManager.ShiftKey ? undefined : 'yellow', pointerEvents: SnappingManager.ShiftKey || SnappingManager.IsResizing ? 'none' : 'all', - display: DocumentView.Selected().length <= 1 || hideDecorations ? 'none' : undefined, + display: DocumentView.Selected().length <= 1 || InkStrokeProperties.Instance._controlButton || hideDecorations ? 'none' : undefined, transform: `rotate(${rotation}deg)`, }} onPointerDown={this.onBackgroundDown} diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 5199eb02b..f847db6c2 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -21,7 +21,7 @@ Most of the operations that can be performed on an InkStroke (eg delete a point, rotate, stretch) are implemented in the InkStrokeProperties helper class */ import { Property } from 'csstype'; -import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { DashColor, returnFalse, setupMoveUpEvents } from '../../ClientUtils'; @@ -66,9 +66,14 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>() private _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated private _disposers: { [key: string]: IReactionDisposer } = {}; + constructor(props: FieldViewProps) { + super(props); + makeObservable(this); + } + @observable _nearestSeg?: number = undefined; // nearest Bezier segment along the ink stroke to the cursor (used for displaying the Add Point highlight) @observable _nearestT?: number = undefined; // nearest t value within the nearest Bezier segment " - @observable _nearestScrPt?: { X: number; Y: number }; // nearst screen point on the ink stroke "" + @observable _nearestScrPt?: { X: number; Y: number } = { X: 0, Y: 0 }; // nearst screen point on the ink stroke "" componentDidMount() { this._props.setContentViewBox?.(this); @@ -155,6 +160,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>() const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex; const isEditing = InkStrokeProperties.Instance._controlButton && this._props.isSelected(); this.controlUndo = undefined; + this._nearestScrPt = undefined; setupMoveUpEvents( this, e, @@ -275,7 +281,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>() .map(p => ({ X: p[0], Y: p[1] })); const { distance, nearestT, nearestSeg, nearestPt } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY }); - if (distance < 40) { + if (distance < 40 && !e.buttons) { this._nearestT = nearestT; this._nearestSeg = nearestSeg; this._nearestScrPt = nearestPt; @@ -427,7 +433,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>() StrCast(this.layoutDoc.stroke_lineJoin) as Property.StrokeLinejoin, StrCast(this.layoutDoc.stroke_lineCap) as Property.StrokeLinecap, StrCast(this.layoutDoc.stroke_bezier), - !closed || !fillColor || DashColor(fillColor).alpha() === 0 ? 'none' : fillColor, + 'none', startMarker, endMarker, markerScale, diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss index a5e60b831..693c75ebf 100644 --- a/src/client/views/PropertiesView.scss +++ b/src/client/views/PropertiesView.scss @@ -508,6 +508,7 @@ display: flex; margin-bottom: 3px; margin-left: 4px; + justify-content: space-evenly; .arrows-head { display: flex; diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index c539b1d0a..10c2a9898 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -3,6 +3,7 @@ import { faAnchor, faArrowRight, faWindowMaximize } from '@fortawesome/free-soli import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Checkbox, Tooltip } from '@mui/material'; import { Colors, EditableText, IconButton, NumberInput, Size, Slider, Toggle, ToggleType, Type } from 'browndash-components'; +import { Property } from 'csstype'; import { concat } from 'lodash'; import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; @@ -830,11 +831,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps @computed get shapeHgt() { return NumCast(this.selectedDoc?._height); } // prettier-ignore set shapeHgt(value) { this.selectedDoc && (this.selectedDoc._height = Math.round(value * 100) / 100); } // prettier-ignore @computed get strokeThk(){ return NumCast(this.selectedStrokes.lastElement()?.[DocData].stroke_width); } // prettier-ignore - set strokeThk(value) { - this.selectedStrokes.forEach(doc => { - doc[DocData].stroke_width = Math.round(value * 100) / 100; - }); - } + set strokeThk(value) { this.selectedStrokes.forEach(doc => { doc[DocData].stroke_width = Math.round(value * 100) / 100; }); } // prettier-ignore @computed get hgtInput() { return this.inputBoxDuo( @@ -1025,7 +1022,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps doc[DocData].stroke_dash = value ? this._lastDash : undefined; }); } - @computed get widthStk() { return this.getField('stroke_width') || '1'; } // prettier-ignore + @computed get lineCapStk() { return (this.getField('stroke_lineCap') || 'round' ) as Property.StrokeLinecap; } // prettier-ignore + set lineCapStk(value) { + this.selectedStrokes.forEach(doc => { + doc[DocData].stroke_lineCap = value; + }); + } + @computed get widthStk() { return this.getField('stroke') || '1'; } // prettier-ignore set widthStk(value) { this.selectedStrokes.forEach(doc => { doc[DocData].stroke_width = Number(value); @@ -1127,7 +1130,6 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps <div className="arrows-head"> <div className="arrows-head-title">Arrow Head: </div> <input - key="markHead" className="arrows-head-input" type="checkbox" checked={this.markHead !== ''} @@ -1137,16 +1139,25 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps <div className="arrows-tail"> <div className="arrows-tail-title">Arrow End: </div> <input - key="markTail" className="arrows-tail-input" type="checkbox" checked={this.markTail !== ''} - onChange={undoable( - action(() => { this.markTail = this.markTail ? '' : 'arrow'; }) ,"change arrow tail" - )} + onChange={undoable(action(() => { this.markTail = this.markTail ? '' : 'arrow'; }) ,"change arrow tail")} /> </div> </div> + <div className="arrows"> + {["butt", "round", "square"].map(cap => + <div className="arrows-tail" key={cap}> + <div className="arrows-tail-title">{cap}</div> + <input + className="arrows-tail-input" + type="checkbox" + checked={this.lineCapStk === cap} + onChange={undoable(action(() => { this.lineCapStk = cap as Property.StrokeLinecap; }), `change lineCap ${cap}`)} + /> + </div>)} + </div> <div className="dashed"> <div className="dashed-title">Dashed Line:</div> <input key="markHead" className="dashed-input" type="checkbox" checked={this.dashdStk === '2'} onChange={this.changeDash} /> diff --git a/src/client/views/TagsView.scss b/src/client/views/TagsView.scss index 24f9e86bc..b741e7bfd 100644 --- a/src/client/views/TagsView.scss +++ b/src/client/views/TagsView.scss @@ -59,6 +59,7 @@ margin: auto; align-self: center; width: 90%; + color: black; } .tagsView-buttons { diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 0ba6b679c..836a5a2c3 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -1,4 +1,4 @@ -import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction } from 'mobx'; +import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, trace } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; @@ -19,7 +19,7 @@ import { dropActionType } from '../../util/DropActionTypes'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoable } from '../../util/UndoManager'; -import { PinDocView } from '../PinFuncs'; +import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; import { TagItem } from '../TagsView'; import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; @@ -113,6 +113,12 @@ export class CollectionCardView extends CollectionSubView() { } } ); + this._disposers.select = reaction( + () => this.childDocs.find(d => this._docRefs.get(d)?.IsSelected), + selected => { + selected && (this.layoutDoc._card_curDoc = selected); + } + ); } componentWillUnmount() { @@ -349,7 +355,7 @@ export class CollectionCardView extends CollectionSubView() { onClickScript={this.curDoc() === doc ? undefined : this._setCurDocScript} dontCenter="y" // Don't center it vertically, because the grid it's in is already doing that and we don't want to do it twice. dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType} - showTags={BoolCast(this.layoutDoc.showChildTags)} + showTags={BoolCast(this.layoutDoc.showChildTags) || BoolCast(this.Document._layout_showTags)} whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} dontHideOnDrag /> @@ -555,7 +561,7 @@ export class CollectionCardView extends CollectionSubView() { const dref = this._docRefs.get(doc); const { translateX, translateY, scale } = ClientUtils.GetScreenTransform(dref?.ContentDiv); - if (!scale) return new Transform(0, 0, 0); + if (!scale) return new Transform(0, 0, 1); return new Transform(-translateX + (dref?.centeringX || 0) * scale, -translateY + (dref?.centeringY || 0) * scale, 1) @@ -608,9 +614,9 @@ export class CollectionCardView extends CollectionSubView() { } return undefined; }); - getAnchor = (addAsAnnotation: boolean) => { + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_card_curDoc: this.curDoc() }); - PinDocView(anchor, { pinData: { collectionType: true, filters: true } }, this.Document); + PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { collectionType: true, filters: true } }, this.Document); addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered return anchor; }; @@ -620,6 +626,7 @@ export class CollectionCardView extends CollectionSubView() { * Actually renders all the cards */ @computed get renderCards() { + trace(); // Map sorted documents to their rendered components return this.sortedDocs.map((doc, index) => { const cardsInRow = this.cardsInRowThatIncludesCardIndex(index); @@ -667,6 +674,7 @@ export class CollectionCardView extends CollectionSubView() { curDoc = () => DocCast(this.layoutDoc._card_curDoc); render() { + trace(); const fitContentScale = this.childCards.length === 0 ? 1 : this.fitContentScale; return ( <div diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index c080ba27e..b7ecf9a2f 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -12,7 +12,7 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { Transform } from '../../util/Transform'; -import { PinDocView } from '../PinFuncs'; +import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; import { DocumentView } from '../nodes/DocumentView'; import { FocusViewOptions } from '../nodes/FocusViewOptions'; @@ -107,9 +107,9 @@ export class CollectionCarousel3DView extends CollectionSubView() { } return undefined; }; - getAnchor = (addAsAnnotation: boolean) => { + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_carousel_index: this.layoutDoc._carousel_index as number }); - PinDocView(anchor, { pinData: { collectionType: true, filters: true } }, this.Document); + PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { collectionType: true, filters: true } }, this.Document); addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered return anchor; }; diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index f714e2a00..87c6e3e5c 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -8,7 +8,7 @@ import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; -import { PinDocView } from '../PinFuncs'; +import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; import { DocumentView } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; @@ -84,9 +84,9 @@ export class CollectionCarouselView extends CollectionSubView() { return undefined; }; - getAnchor = (addAsAnnotation: boolean) => { + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_carousel_index: this.carouselIndex }); - PinDocView(anchor, { pinData: { collectionType: true, filters: true } }, this.Document); + PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { collectionType: true, filters: true } }, this.Document); addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered return anchor; }; diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 40144c4ce..5a17bc8f6 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -3,7 +3,7 @@ import { Colors } from 'browndash-components'; import { runInAction } from 'mobx'; import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; -import { InkEraserTool, InkInkTool, InkTool } from '../../../fields/InkField'; +import { InkEraserTool, InkInkTool, InkProperty, InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; @@ -430,36 +430,31 @@ ScriptingGlobals.add(function activeEraserTool() { // toggle: Set overlay status of selected document // eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor' | 'eraserWidth', value: string | number, checkResult?: boolean) { +ScriptingGlobals.add(function setInkProperty(option: InkProperty, value: string | number, checkResult?: boolean) { const selected = DocumentView.SelectedDocs().lastElement(); // prettier-ignore - const map: Map<'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor' | 'eraserWidth', { checkResult: () => number|boolean|string|undefined; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([ - ['inkMask', { + const map: Map<InkProperty, { checkResult: () => number|boolean|string|undefined; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([ + [InkProperty.Mask, { checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_isInkMask) : ActiveIsInkMask())), setInk: (doc: Doc) => { doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask; }, setMode: () => SetActiveIsInkMask(value ? true : false) }], - ['labels', { + [InkProperty.Labels, { checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_showLabel) : !ActiveHideTextLabels())), setInk: (doc: Doc) => { doc[DocData].stroke_showLabel = value; }, setMode: () => SetactiveHideTextLabels(value? false : true), }], - ['fillColor', { - checkResult: () => (selected?._layout_isSvg ? StrCast(selected[DocData].fillColor) : ActiveInkFillColor() ?? "transparent"), - setInk: (doc: Doc) => { doc[DocData].fillColor = StrCast(value); }, - setMode: () => SetActiveInkFillColor(StrCast(value)), - }], - [ 'strokeWidth', { + [ InkProperty.StrokeWidth, { checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].stroke_width) : ActiveInkWidth()), setInk: (doc: Doc) => { doc[DocData].stroke_width = NumCast(value); }, setMode: () => SetActiveInkWidth(value.toString()), }], - ['strokeColor', { + [InkProperty.StrokeColor, { checkResult: () => (selected?._layout_isSvg? StrCast(selected[DocData].color) : ActiveInkColor()), setInk: (doc: Doc) => { doc[DocData].color = String(value); }, setMode: () => SetActiveInkColor(StrCast(value)) }], - [ 'eraserWidth', { + [ InkProperty.EraserWidth, { checkResult: () => ActiveEraserWidth() === 0 ? 1 : ActiveEraserWidth(), setInk: (doc: Doc) => { }, setMode: () => SetEraserWidth(+value), diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 905c69bb8..35f1ec9ef 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1274,9 +1274,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB ); this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and layout_autoHeight is on - () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layoutAutoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins, tagsHeight: this.tagsHeight }), - ({ sidebarHeight, textHeight, layoutAutoHeight, marginsHeight, tagsHeight }) => { - const newHeight = this.contentScaling * (tagsHeight + marginsHeight + Math.max(sidebarHeight, textHeight)); + () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layoutAutoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }), + ({ sidebarHeight, textHeight, layoutAutoHeight, marginsHeight }) => { + const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight)); if ( (!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this._props.isSelected()) && // layoutAutoHeight && diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index f3ff5f11f..936e45eb2 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -26,6 +26,14 @@ export enum InkEraserTool { Radius = 'Radius', } +export enum InkProperty { + Mask = 'inkMask', + Labels = 'labels', + StrokeWidth = 'strokeWidth', + StrokeColor = 'strokeColor', + EraserWidth = ' eraserWidth', +} + export type Segment = Array<Bezier>; // Defines an ink as an array of points. |