diff options
Diffstat (limited to 'src/client/views/PropertiesView.tsx')
-rw-r--r-- | src/client/views/PropertiesView.tsx | 204 |
1 files changed, 158 insertions, 46 deletions
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index b9f928cba..71d184497 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -2,7 +2,7 @@ import { IconLookup, IconProp } from '@fortawesome/fontawesome-svg-core'; import { faAnchor, faArrowRight, faWindowMaximize } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Checkbox, Tooltip } from '@mui/material'; -import { Colors, EditableText, IconButton, NumberInput, Size, Slider, Type } from 'browndash-components'; +import { Colors, EditableText, IconButton, NumberInput, Size, Slider, Toggle, ToggleType, Type } from 'browndash-components'; import { concat } from 'lodash'; import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; @@ -12,7 +12,7 @@ import * as Icons from 'react-icons/bs'; // {BsCollectionFill, BsFillFileEarmark import ResizeObserver from 'resize-observer-polyfill'; import { ClientUtils, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; -import { Doc, Field, FieldResult, FieldType, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast, returnEmptyDoclist } from '../../fields/Doc'; +import { Doc, DocListCast, Field, FieldResult, FieldType, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast, returnEmptyDoclist } from '../../fields/Doc'; import { AclAdmin, DocAcl, DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { InkField } from '../../fields/InkField'; @@ -23,6 +23,7 @@ import { GetEffectiveAcl, SharingPermissions, normalizeEmail } from '../../field import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { GroupManager } from '../util/GroupManager'; import { LinkManager } from '../util/LinkManager'; +import { SettingsManager } from '../util/SettingsManager'; import { SharingManager } from '../util/SharingManager'; import { SnappingManager } from '../util/SnappingManager'; import { Transform } from '../util/Transform'; @@ -30,6 +31,7 @@ import { UndoManager, undoBatch, undoable } from '../util/UndoManager'; import { EditableView } from './EditableView'; import { FilterPanel } from './FilterPanel'; import { InkStrokeProperties } from './InkStrokeProperties'; +import { InkingStroke } from './InkingStroke'; import { ObservableReactComponent } from './ObservableReactComponent'; import { PropertiesButtons } from './PropertiesButtons'; import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector'; @@ -41,6 +43,7 @@ import { DocumentView } from './nodes/DocumentView'; import { StyleProviderFuncType } from './nodes/FieldView'; import { OpenWhere } from './nodes/OpenWhere'; import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails'; +import { SmartDrawHandler } from './smartdraw/SmartDrawHandler'; interface PropertiesViewProps { width: number; @@ -71,6 +74,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps return 200; } + @computed get containsInkDoc() { + return this.containsInk(this.selectedDoc); + } + @computed get selectedDoc() { return DocumentView.SelectedSchemaDoc() || this.selectedDocumentView?.Document || Doc.ActiveDashboard; } @@ -807,6 +814,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps return Field.toString(this.selectedDoc?.[DocData][key] as FieldType); } + @computed get selectedStrokes() { + return this.containsInkDoc ? DocListCast(this.selectedDoc[DocData].data) : DocumentView.SelectedSchemaDoc() ? [DocumentView.SelectedSchemaDoc()!] : DocumentView.SelectedDocs().filter(doc => doc.layout_isSvg); + } @computed get shapeXps() { return NumCast(this.selectedDoc?.x); } // prettier-ignore set shapeXps(value) { this.selectedDoc && (this.selectedDoc.x = Math.round(value * 100) / 100); } // prettier-ignore @computed get shapeYps() { return NumCast(this.selectedDoc?.y); } // prettier-ignore @@ -815,8 +825,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps set shapeWid(value) { this.selectedDoc && (this.selectedDoc._width = Math.round(value * 100) / 100); } // prettier-ignore @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.selectedDoc?.[DocData].stroke_width); } // prettier-ignore - set strokeThk(value) { this.selectedDoc && (this.selectedDoc[DocData].stroke_width = 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; + }); + } @computed get hgtInput() { return this.inputBoxDuo( @@ -853,10 +867,22 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps private _lastDash: string = '2'; - @computed get colorFil() { return StrCast(this.selectedDoc?.[DocData].fillColor); } // prettier-ignore - set colorFil(value) { this.selectedDoc && (this.selectedDoc[DocData].fillColor = value || undefined); } // prettier-ignore - @computed get colorStk() { return StrCast(this.selectedDoc?.[DocData].color); } // prettier-ignore - set colorStk(value) { this.selectedDoc && (this.selectedDoc[DocData].color = value || undefined); } // prettier-ignore + @computed get colorFil() { return StrCast(this.selectedStrokes.lastElement()?.[DocData].fillColor); } // prettier-ignore + set colorFil(value) { + this.selectedStrokes.forEach(doc => { + const inkStroke = DocumentView.getDocumentView(doc)?.ComponentView as InkingStroke; + const { inkData } = inkStroke.inkScaledData(); + if (InkingStroke.IsClosed(inkData)) { + doc[DocData].fillColor = value || undefined; + } + }); + } + @computed get colorStk() { return StrCast(this.selectedStrokes.lastElement()?.[DocData].color); } // prettier-ignore + set colorStk(value) { + this.selectedStrokes.forEach(doc => { + doc[DocData].color = value || undefined; + }); + } colorButton(value: string, type: string, setter: () => void) { return ( @@ -927,26 +953,93 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps ); } - @computed get dashdStk() { return StrCast(this.selectedDoc?.stroke_dash); } // prettier-ignore + @computed get smoothAndColor() { + const targetDoc = this.selectedLayoutDoc; + const smoothNumber = this.getNumber( + 'Smooth Amount', + '', + 1, + Math.max(10, this.smoothAmt), + this.smoothAmt, + (val: number) => { + !isNaN(val) && (this.smoothAmt = val); + }, + 10, + 1 + ); + return ( + <div> + {!targetDoc.layout_isSvg && this.containsInkDoc && ( + <div className="color"> + <Toggle + text={'Color with GPT'} + color={SettingsManager.userColor} + icon={<FontAwesomeIcon icon="fill-drip" />} + iconPlacement="left" + align="flex-start" + fillWidth + toggleType={ToggleType.BUTTON} + onClick={undoable(() => { + SmartDrawHandler.Instance.colorWithGPT(targetDoc); + }, 'smoothStrokes')} + /> + </div> + )} + <div className="smooth"> + <Toggle + text={'Smooth Ink Strokes'} + color={SettingsManager.userColor} + icon={<FontAwesomeIcon icon="bezier-curve" />} + iconPlacement="left" + align="flex-start" + fillWidth + toggleType={ToggleType.BUTTON} + onClick={undoable(() => { + InkStrokeProperties.Instance.smoothInkStrokes(this.selectedStrokes, this.smoothAmt); + }, 'smoothStrokes')} + /> + </div> + <div className="smooth-slider">{smoothNumber}</div> + </div> + ); + } + + @computed get dashdStk() { return this.selectedStrokes[0]?.stroke_dash || ''; } // prettier-ignore set dashdStk(value) { - value && (this._lastDash = value); - this.selectedDoc && (this.selectedDoc[DocData].stroke_dash = value ? this._lastDash : undefined); + value && (this._lastDash = value as string); + this.selectedStrokes.forEach(doc => { + doc[DocData].stroke_dash = value ? this._lastDash : undefined; + }); } @computed get widthStk() { return this.getField('stroke_width') || '1'; } // prettier-ignore set widthStk(value) { - this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Number(value)); + this.selectedStrokes.forEach(doc => { + doc[DocData].stroke_width = Number(value); + }); } @computed get markScal() { return Number(this.getField('stroke_markerScale') || '1'); } // prettier-ignore set markScal(value) { - this.selectedDoc && (this.selectedDoc[DocData].stroke_markerScale = Number(value)); + this.selectedStrokes.forEach(doc => { + doc[DocData].stroke_markerScale = Number(value); + }); + } + @computed get smoothAmt() { return Number(this.getField('stroke_smoothAmount') || '5'); } // prettier-ignore + set smoothAmt(value) { + this.selectedStrokes.forEach(doc => { + doc[DocData].stroke_smoothAmount = Number(value); + }); } @computed get markHead() { return this.getField('stroke_startMarker') || ''; } // prettier-ignore set markHead(value) { - this.selectedDoc && (this.selectedDoc[DocData].stroke_startMarker = value); + this.selectedStrokes.forEach(doc => { + doc[DocData].stroke_startMarker = value; + }); } @computed get markTail() { return this.getField('stroke_endMarker') || ''; } // prettier-ignore set markTail(value) { - this.selectedDoc && (this.selectedDoc[DocData].stroke_endMarker = value); + this.selectedStrokes.forEach(doc => { + doc[DocData].stroke_endMarker = value; + }); } regInput = (key: string, value: string | number | undefined, setter: (val: string) => void) => ( @@ -1053,44 +1146,51 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps this.dashdStk = this.dashdStk === '2' ? '0' : '2'; }; - @computed get appearanceEditor() { + @computed get inkEditor() { return ( - <div className="appearance-editor"> + <div className="ink-editor"> {this.widthAndDash} {this.strokeAndFill} + {this.smoothAndColor} </div> ); } _sliderBatch: UndoManager.Batch | undefined; + _sliderKey = ''; setFinalNumber = () => { + this._sliderKey = ''; this._sliderBatch?.end(); }; - getNumber = (label: string, unit: string, min: number, max: number, number: number, setNumber: (val: number) => void, autorange?: number, autorangeMinVal?: number) => ( - <div key={label + (this.selectedDoc?.title ?? '')}> - <NumberInput formLabel={label} formLabelPlacement="left" type={Type.SEC} unit={unit} fillWidth color={this.color} number={number} setNumber={setNumber} min={min} max={max} /> - <Slider - key={label} - onPointerDown={() => { - this._sliderBatch = UndoManager.StartBatch('slider ' + label); - }} - multithumb={false} - color={this.color} - size={Size.XSMALL} - min={min} - max={max} - autorangeMinVal={autorangeMinVal} - autorange={autorange} - number={number} - unit={unit} - decimals={1} - setFinalNumber={this.setFinalNumber} - setNumber={setNumber} - fillWidth - /> - </div> - ); + getNumber = (label: string, unit: string, min: number, max: number, number: number, setNumber: (val: number) => void, autorange?: number, autorangeMinVal?: number) => { + const key = this._sliderKey || label + min + max + number; + return ( + <div key={label + (this.selectedDoc?.title ?? '')}> + <NumberInput formLabel={label} formLabelPlacement="left" type={Type.SEC} unit={unit} fillWidth color={this.color} number={number} setNumber={setNumber} min={min} max={max} /> + <Slider + key={key} + onPointerDown={() => { + this._sliderKey = key; + this._sliderBatch = UndoManager.StartBatch('slider ' + label); + }} + multithumb={false} + color={this.color} + size={Size.XSMALL} + min={min} + max={max} + autorangeMinVal={autorangeMinVal} + autorange={autorange} + number={number} + unit={unit} + decimals={1} + setFinalNumber={this.setFinalNumber} + setNumber={setNumber} + fillWidth + /> + </div> + ); + }; setVal = (func: (doc: Doc, val: number) => void) => (val: number) => this.selectedDoc && !isNaN(val) && func(this.selectedDoc, val); @computed get transformEditor() { @@ -1177,29 +1277,41 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps @computed get filtersSubMenu() { return ( - // prettier-ignore <PropertiesSection title="Filters" isOpen={this.openFilters} setIsOpen={action(bool => { this.openFilters = bool; })} onDoubleClick={this.CloseAll}> <div className="propertiesView-content filters" style={{ position: 'relative', height: 'auto' }}> <FilterPanel Document={this.selectedDoc ?? Doc.ActiveDashboard!} /> </div> </PropertiesSection> - ); + ); // prettier-ignore } @computed get inkSubMenu() { return ( - // prettier-ignore <> <PropertiesSection title="Appearance" isOpen={this.openAppearance} setIsOpen={bool => { this.openAppearance = bool; }} onDoubleClick={this.CloseAll}> - {this.selectedLayoutDoc?.layout_isSvg ? this.appearanceEditor : null} + {this.selectedStrokes.length ? this.inkEditor : null} </PropertiesSection> <PropertiesSection title="Transform" isOpen={this.openTransform} setIsOpen={bool => { this.openTransform = bool; }} onDoubleClick={this.CloseAll}> {this.transformEditor} </PropertiesSection> </> - ); + ); // prettier-ignore } + /** + * Determines if a selected collection/group document contains any ink strokes to allow users to edit groups + * of ink strokes in the properties menu. + */ + containsInk = (selectedDoc: Doc) => { + const childDocs: Doc[] = DocListCast(selectedDoc[DocData].data); + for (let i = 0; i < childDocs.length; i++) { + if (DocumentView.getDocumentView(childDocs[i])?.layoutDoc?.layout_isSvg) { + return true; + } + } + return false; + }; + @computed get fieldsSubMenu() { return ( <PropertiesSection |