aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/KeyValueBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/KeyValueBox.tsx')
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx173
1 files changed, 100 insertions, 73 deletions
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 89a5ac0b8..66e210c03 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -1,26 +1,28 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { returnAlways, returnTrue } from '../../../Utils';
-import { Doc, Field, FieldResult } from '../../../fields/Doc';
+import { returnAlways, returnTrue } from '../../../ClientUtils';
+import { Doc, Field, FieldResult, FieldType } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { RichTextField } from '../../../fields/RichTextField';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { DocCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
+import { DocumentType } from '../../documents/DocumentTypes';
import { Docs } from '../../documents/Documents';
import { SetupDrag } from '../../util/DragManager';
-import { CompileScript, CompiledScript, ScriptOptions } from '../../util/Scripting';
-import { undoBatch } from '../../util/UndoManager';
+import { CompiledScript } from '../../util/Scripting';
+import { undoable } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { ObservableReactComponent } from '../ObservableReactComponent';
+import { ViewBoxBaseComponent } from '../DocComponent';
import { DocumentIconContainer } from './DocumentIcon';
-import { OpenWhere } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { ImageBox } from './ImageBox';
import './KeyValueBox.scss';
import { KeyValuePair } from './KeyValuePair';
+import { OpenWhere } from './OpenWhere';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
export type KVPScript = {
@@ -29,7 +31,7 @@ export type KVPScript = {
onDelegate: boolean;
};
@observer
-export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
+export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString() {
return FieldView.LayoutString(KeyValueBox, 'data');
}
@@ -46,16 +48,16 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
componentDidMount() {
this._props.setContentViewBox?.(this);
}
- isKeyValueBox = returnTrue;
- able = returnAlways;
- layout_fitWidth = returnTrue;
- onClickScriptDisable = returnAlways;
+ // ViewBoxInterface overrides
+ override isUnstyledView = returnTrue; // used by style provider via ViewBoxInterface - ignore opacity, anim effects, titles
+ override dontRegisterView = returnTrue; // don't want to follow links to this view
+ override onClickScriptDisable = returnAlways;
@observable private rows: KeyValuePair[] = [];
@observable _splitPercentage = 50;
get fieldDocToLayout() {
- return this._props.fieldKey ? DocCast(this._props.Document[this._props.fieldKey], DocCast(this._props.Document)) : this._props.Document;
+ return DocCast(this.Document);
}
@action
@@ -71,47 +73,65 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
}
}
};
- public static CompileKVPScript(value: string): KVPScript | undefined {
- const eq = value.startsWith('=');
- value = eq ? value.substring(1) : value;
- const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith('$=') ? 'script' : false;
- value = dubEq ? value.substring(2) : value;
- const options: ScriptOptions = { addReturn: true, typecheck: false, params: { this: Doc.name, self: Doc.name, documentView: 'any', _last_: 'any', _readOnly_: 'boolean' }, editable: true };
- if (dubEq) options.typecheck = false;
- const script = CompileScript(value, { ...options, transformer: DocumentIconContainer.getTransformer() });
- return !script.compiled ? undefined : { script, type: dubEq, onDelegate: eq };
- }
+ /**
+ * this compiles a string as a script after parsing off initial characters that determine script parameters
+ * if the script starts with '=', then it will be stored on the delegate of the Doc, otherise on the data doc
+ * if the script then starts with a ':=', then it will be treated as ComputedField,
+ * '$=', then it will just be a Script
+ * @param value
+ * @returns
+ */
+ public static CompileKVPScript = (rawvalueIn: string): KVPScript | undefined => {
+ let rawvalue = rawvalueIn;
+ const onDelegate = rawvalue.startsWith('=');
+ rawvalue = onDelegate ? rawvalue.substring(1) : rawvalue;
+ const type: 'computed' | 'script' | false = rawvalue.startsWith(':=') ? 'computed' : rawvalue.startsWith('$=') ? 'script' : false;
+ rawvalue = type ? rawvalue.substring(2) : rawvalue;
+ rawvalue = rawvalue.replace(/.*\(\((.*)\)\)/, 'dashCallChat(_setCacheResult_, this, `$1`)');
+ const value = ["'", '"', '`'].includes(rawvalue.length ? rawvalue[0] : '') || !isNaN(rawvalue as any) ? rawvalue : '`' + rawvalue + '`';
- public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean): boolean {
+ let script = ScriptField.CompileScript(rawvalue, {}, true, undefined, DocumentIconContainer.getTransformer());
+ if (!script.compiled) {
+ script = ScriptField.CompileScript(value, {}, true, undefined, DocumentIconContainer.getTransformer());
+ }
+ return !script.compiled ? undefined : { script, type, onDelegate };
+ };
+
+ public static ApplyKVPScript = (doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) => {
const { script, type, onDelegate } = kvpScript;
- //const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates
+ // const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates
const target = forceOnDelegate || onDelegate || key.startsWith('_') ? doc : DocCast(doc.proto, doc);
- let field: Field;
- if (type === 'computed') {
- field = new ComputedField(script);
- } else if (type === 'script') {
- field = new ScriptField(script);
- } else {
- const res = script.run({ this: Doc.Layout(doc), self: doc }, console.log);
- if (!res.success) {
- target[key] = script.originalScript;
- return true;
+ let field: FieldType | undefined;
+ switch (type) {
+ case 'computed': field = new ComputedField(script); break; // prettier-ignore
+ case 'script': field = new ScriptField(script); break; // prettier-ignore
+ default: {
+ const _setCacheResult_ = (value: FieldResult) => {
+ field = value as FieldType;
+ if (setResult) setResult?.(value);
+ else target[key] = field;
+ };
+ const res = script.run({ this: Doc.Layout(doc), _setCacheResult_ }, console.log);
+ if (!res.success) {
+ if (key) target[key] = script.originalScript;
+ return false;
+ }
+ field === undefined && (field = res.result instanceof Array ? new List<any>(res.result) : res.result);
}
- field = res.result;
}
+ if (!key) return false;
if (Field.IsField(field, true) && (key !== 'proto' || field !== target)) {
target[key] = field;
return true;
}
return false;
- }
+ };
- @undoBatch
- public static SetField(doc: Doc, key: string, value: string, forceOnDelegate?: boolean) {
- const script = this.CompileKVPScript(value);
+ public static SetField = undoable((doc: Doc, key: string, value: string, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) => {
+ const script = KeyValueBox.CompileKVPScript(value);
if (!script) return false;
- return this.ApplyKVPScript(doc, key, script, forceOnDelegate);
- }
+ return KeyValueBox.ApplyKVPScript(doc, key, script, forceOnDelegate, setResult);
+ }, 'Set Doc Field');
onPointerDown = (e: React.PointerEvent): void => {
if (e.buttons === 1 && this._props.isSelected()) {
@@ -135,20 +155,20 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
const ids: { [key: string]: string } = {};
const protos = Doc.GetAllPrototypes(doc);
- for (const proto of protos) {
+ protos.forEach(proto => {
Object.keys(proto).forEach(key => {
if (!(key in ids) && realDoc[key] !== ComputedField.undefined) {
ids[key] = key;
}
});
- }
+ });
const rows: JSX.Element[] = [];
let i = 0;
const self = this;
const keys = Object.keys(ids).slice();
- //for (const key of [...keys.filter(id => id !== 'layout' && !id.includes('_')).sort(), ...keys.filter(id => id === 'layout' || id.includes('_')).sort()]) {
- for (const key of keys.sort((a: string, b: string) => {
+ // for (const key of [...keys.filter(id => id !== 'layout' && !id.includes('_')).sort(), ...keys.filter(id => id === 'layout' || id.includes('_')).sort()]) {
+ const sortedKeys = keys.sort((a: string, b: string) => {
const a_ = a.split('_')[0];
const b_ = b.split('_')[0];
if (a_ < b_) return -1;
@@ -156,7 +176,8 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
if (a === a_) return -1;
if (b === b_) return 1;
return a === b ? 0 : a < b ? -1 : 1;
- })) {
+ });
+ sortedKeys.forEach(key => {
rows.push(
<KeyValuePair
doc={realDoc}
@@ -177,7 +198,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
keyName={key}
/>
);
- }
+ });
return rows;
}
@computed get newKeyValue() {
@@ -211,7 +232,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
this._splitPercentage = Math.max(0, 100 - Math.round(((e.clientX - nativeWidth.left) / nativeWidth.width) * 100));
};
@action
- onDividerUp = (e: PointerEvent): void => {
+ onDividerUp = (): void => {
document.removeEventListener('pointermove', this.onDividerMove);
document.removeEventListener('pointerup', this.onDividerUp);
};
@@ -225,35 +246,36 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
getFieldView = () => {
const rows = this.rows.filter(row => row.isChecked);
if (rows.length > 1) {
- const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this._props.Document).title}`, _chromeHidden: true });
- for (const row of rows) {
- const field = this.createFieldView(DocCast(this._props.Document), row);
+ const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this.Document).title}`, _chromeHidden: true });
+ rows.forEach(row => {
+ const field = this.createFieldView(DocCast(this.Document), row);
field && Doc.AddDocToList(parent, 'data', field);
row.uncheck();
- }
+ });
return parent;
}
- return rows.length ? this.createFieldView(DocCast(this._props.Document), rows.lastElement()) : undefined;
+ return rows.length ? this.createFieldView(DocCast(this.Document), rows.lastElement()) : undefined;
};
createFieldView = (templateDoc: Doc, row: KeyValuePair) => {
const metaKey = row._props.keyName;
- const fieldTemplate = Doc.IsDelegateField(templateDoc, metaKey) ? Doc.MakeDelegate(templateDoc) : Doc.MakeEmbedding(templateDoc);
- fieldTemplate.title = metaKey;
- fieldTemplate.layout_fitWidth = true;
- fieldTemplate._xMargin = 10;
- fieldTemplate._yMargin = 10;
- fieldTemplate._width = 100;
- fieldTemplate._height = 40;
- fieldTemplate.layout = this.inferType(templateDoc[metaKey], metaKey);
- return fieldTemplate;
+ const fieldTempDoc = Doc.IsDelegateField(templateDoc, metaKey) ? Doc.MakeDelegate(templateDoc) : Doc.MakeEmbedding(templateDoc);
+ fieldTempDoc.title = metaKey;
+ fieldTempDoc.layout_fitWidth = true;
+ fieldTempDoc._xMargin = 10;
+ fieldTempDoc._yMargin = 10;
+ fieldTempDoc._width = 100;
+ fieldTempDoc._height = 40;
+ fieldTempDoc.layout = this.inferType(templateDoc[metaKey], metaKey);
+ return fieldTempDoc;
};
inferType = (data: FieldResult, metaKey: string) => {
const options = { _width: 300, _height: 300, title: metaKey };
if (data instanceof RichTextField || typeof data === 'string' || typeof data === 'number') {
return FormattedTextBox.LayoutString(metaKey);
- } else if (data instanceof List) {
+ }
+ if (data instanceof List) {
if (data.length === 0) {
return Docs.Create.StackingDocument([], options);
}
@@ -262,28 +284,25 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
return Docs.Create.StackingDocument([], options);
}
switch (first.data.constructor) {
- case RichTextField:
- return Docs.Create.TreeDocument([], options);
- case ImageField:
- return Docs.Create.MasonryDocument([], options);
- default:
- console.log(`Template for ${first.data.constructor} not supported!`);
- return undefined;
- }
+ case RichTextField: return Docs.Create.TreeDocument([], options);
+ case ImageField: return Docs.Create.MasonryDocument([], options);
+ default: console.log(`Template for ${first.data.constructor} not supported!`);
+ return undefined;
+ } // prettier-ignore
} else if (data instanceof ImageField) {
return ImageBox.LayoutString(metaKey);
}
return new Doc();
};
- specificContextMenu = (e: React.MouseEvent): void => {
+ specificContextMenu = (): void => {
const cm = ContextMenu.Instance;
const open = cm.findByDescription('Change Perspective...');
const openItems: ContextMenuProps[] = open && 'subitems' in open ? open.subitems : [];
openItems.push({
description: 'Default Perspective',
event: () => {
- this._props.addDocTab(this._props.Document, OpenWhere.close);
+ this._props.addDocTab(this.Document, OpenWhere.close);
this._props.addDocTab(this.fieldDocToLayout, OpenWhere.addRight);
},
icon: 'image',
@@ -319,4 +338,12 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
</div>
);
}
+ public static Init() {
+ Doc.SetField = KeyValueBox.SetField;
+ }
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.KVP, {
+ layout: { view: KeyValueBox, dataField: 'data' },
+ options: { acl: '', _layout_fitWidth: true, _height: 150 },
+});