diff options
Diffstat (limited to 'src/client/views/nodes/KeyValueBox.tsx')
-rw-r--r-- | src/client/views/nodes/KeyValueBox.tsx | 173 |
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 }, +}); |