aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/PropertiesView.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/PropertiesView.tsx')
-rw-r--r--src/client/views/PropertiesView.tsx204
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