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.tsx272
1 files changed, 155 insertions, 117 deletions
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 4b1fbaf7d..d9f46509e 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -1,34 +1,37 @@
-
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, Field, FieldResult } from "../../../fields/Doc";
-import { List } from "../../../fields/List";
-import { RichTextField } from "../../../fields/RichTextField";
-import { listSpec } from "../../../fields/Schema";
-import { ComputedField, ScriptField } from "../../../fields/ScriptField";
-import { Cast, FieldValue, NumCast } from "../../../fields/Types";
-import { ImageField } from "../../../fields/URLField";
-import { Docs } from "../../documents/Documents";
-import { SetupDrag } from "../../util/DragManager";
-import { CompiledScript, CompileScript, ScriptOptions } from "../../util/Scripting";
-import { undoBatch } from "../../util/UndoManager";
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, Field, FieldResult } from '../../../fields/Doc';
+import { List } from '../../../fields/List';
+import { RichTextField } from '../../../fields/RichTextField';
+import { listSpec } from '../../../fields/Schema';
+import { ComputedField, ScriptField } from '../../../fields/ScriptField';
+import { Cast, DocCast, FieldValue, NumCast } from '../../../fields/Types';
+import { ImageField } from '../../../fields/URLField';
+import { Docs } from '../../documents/Documents';
+import { SetupDrag } from '../../util/DragManager';
+import { CompiledScript, CompileScript, ScriptOptions } from '../../util/Scripting';
+import { undoBatch } from '../../util/UndoManager';
import { FieldView, FieldViewProps } from './FieldView';
-import "./KeyValueBox.scss";
-import { KeyValuePair } from "./KeyValuePair";
-import React = require("react");
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import e = require("express");
+import './KeyValueBox.scss';
+import { KeyValuePair } from './KeyValuePair';
+import React = require('react');
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import e = require('express');
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
+import { ImageBox } from './ImageBox';
export type KVPScript = {
script: CompiledScript;
- type: "computed" | "script" | false;
+ type: 'computed' | 'script' | false;
onDelegate: boolean;
};
@observer
export class KeyValueBox extends React.Component<FieldViewProps> {
- public static LayoutString(fieldStr: string) { return FieldView.LayoutString(KeyValueBox, fieldStr); }
+ public static LayoutString(fieldStr: string) {
+ return FieldView.LayoutString(KeyValueBox, fieldStr);
+ }
private _mainCont = React.createRef<HTMLDivElement>();
private _keyHeader = React.createRef<HTMLTableHeaderCellElement>();
@@ -37,8 +40,12 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
@observable private rows: KeyValuePair[] = [];
- @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); }
- get fieldDocToLayout() { return this.props.fieldKey ? Cast(this.props.Document[this.props.fieldKey], Doc, null) : this.props.Document; }
+ @computed get splitPercentage() {
+ return NumCast(this.props.Document.schemaSplitPercentage, 50);
+ }
+ get fieldDocToLayout() {
+ return this.props.fieldKey ? Cast(this.props.Document[this.props.fieldKey], Doc, null) : this.props.Document;
+ }
@action
onEnterKey = (e: React.KeyboardEvent): void => {
@@ -46,19 +53,19 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
e.stopPropagation();
if (this._keyInput.current?.value && this._valInput.current?.value && this.fieldDocToLayout) {
if (KeyValueBox.SetField(this.fieldDocToLayout, this._keyInput.current.value, this._valInput.current.value)) {
- this._keyInput.current.value = "";
- this._valInput.current.value = "";
+ this._keyInput.current.value = '';
+ this._valInput.current.value = '';
document.body.focus();
}
}
}
- }
+ };
public static CompileKVPScript(value: string): KVPScript | undefined {
- const eq = value.startsWith("=");
+ const eq = value.startsWith('=');
value = eq ? value.substr(1) : value;
- const dubEq = value.startsWith(":=") ? "computed" : value.startsWith(";=") ? "script" : false;
+ const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith(';=') ? 'script' : false;
value = dubEq ? value.substr(2) : value;
- const options: ScriptOptions = { addReturn: true, params: { this: Doc.name, self: Doc.name, _last_: "any", _readOnly_: "boolean" }, editable: false };
+ const options: ScriptOptions = { addReturn: true, params: { this: Doc.name, self: Doc.name, _last_: 'any', _readOnly_: 'boolean' }, editable: false };
if (dubEq) options.typecheck = false;
const script = CompileScript(value, options);
return !script.compiled ? undefined : { script, type: dubEq, onDelegate: eq };
@@ -67,11 +74,11 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean): boolean {
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 = forceOnDelegate || onDelegate || key.startsWith("_") ? doc : doc.proto || doc;
+ const target = forceOnDelegate || onDelegate || key.startsWith('_') ? doc : doc.proto || doc;
let field: Field;
- if (type === "computed") {
+ if (type === 'computed') {
field = new ComputedField(script);
- } else if (type === "script") {
+ } else if (type === 'script') {
field = new ScriptField(script);
} else {
const res = script.run({ this: target }, console.log);
@@ -96,7 +103,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
if (e.buttons === 1 && this.props.isSelected(true)) {
e.stopPropagation();
}
- }
+ };
onPointerWheel = (e: React.WheelEvent): void => e.stopPropagation();
rowHeight = () => 30;
@@ -104,7 +111,11 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
@computed get createTable() {
const doc = this.fieldDocToLayout;
if (!doc) {
- return <tr><td>Loading...</td></tr>;
+ return (
+ <tr>
+ <td>Loading...</td>
+ </tr>
+ );
}
const realDoc = doc;
@@ -122,83 +133,102 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
let i = 0;
const self = this;
for (const key of Object.keys(ids).slice().sort()) {
- rows.push(<KeyValuePair doc={realDoc} addDocTab={this.props.addDocTab} PanelWidth={this.props.PanelWidth} PanelHeight={this.rowHeight}
- ref={(function () {
- let oldEl: KeyValuePair | undefined;
- return (el: KeyValuePair) => {
- if (oldEl) self.rows.splice(self.rows.indexOf(oldEl), 1);
- oldEl = el;
- if (el) self.rows.push(el);
- };
- })()} keyWidth={100 - this.splitPercentage} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} key={key} keyName={key} />);
+ rows.push(
+ <KeyValuePair
+ doc={realDoc}
+ addDocTab={this.props.addDocTab}
+ PanelWidth={this.props.PanelWidth}
+ PanelHeight={this.rowHeight}
+ ref={(function () {
+ let oldEl: KeyValuePair | undefined;
+ return (el: KeyValuePair) => {
+ if (oldEl) self.rows.splice(self.rows.indexOf(oldEl), 1);
+ oldEl = el;
+ if (el) self.rows.push(el);
+ };
+ })()}
+ keyWidth={100 - this.splitPercentage}
+ rowStyle={'keyValueBox-' + (i++ % 2 ? 'oddRow' : 'evenRow')}
+ key={key}
+ keyName={key}
+ />
+ );
}
return rows;
}
@computed get newKeyValue() {
- return <tr className="keyValueBox-valueRow">
- <td className="keyValueBox-td-key" onClick={(e) => { this._keyInput.current!.select(); e.stopPropagation(); }} style={{ width: `${100 - this.splitPercentage}%` }}>
- <input style={{ width: "100%" }} ref={this._keyInput} type="text" placeholder="Key" />
- </td>
- <td className="keyValueBox-td-value" onClick={(e) => { this._valInput.current!.select(); e.stopPropagation(); }} style={{ width: `${this.splitPercentage}%` }}>
- <input style={{ width: "100%" }} ref={this._valInput} type="text" placeholder="Value" onKeyDown={this.onEnterKey} />
- </td>
- </tr>;
+ return (
+ <tr className="keyValueBox-valueRow">
+ <td
+ className="keyValueBox-td-key"
+ onClick={e => {
+ this._keyInput.current!.select();
+ e.stopPropagation();
+ }}
+ style={{ width: `${100 - this.splitPercentage}%` }}>
+ <input style={{ width: '100%' }} ref={this._keyInput} type="text" placeholder="Key" />
+ </td>
+ <td
+ className="keyValueBox-td-value"
+ onClick={e => {
+ this._valInput.current!.select();
+ e.stopPropagation();
+ }}
+ style={{ width: `${this.splitPercentage}%` }}>
+ <input style={{ width: '100%' }} ref={this._valInput} type="text" placeholder="Value" onKeyDown={this.onEnterKey} />
+ </td>
+ </tr>
+ );
}
@action
onDividerMove = (e: PointerEvent): void => {
const nativeWidth = this._mainCont.current!.getBoundingClientRect();
- this.props.Document.schemaSplitPercentage = Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100));
- }
+ this.props.Document.schemaSplitPercentage = Math.max(0, 100 - Math.round(((e.clientX - nativeWidth.left) / nativeWidth.width) * 100));
+ };
@action
onDividerUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onDividerMove);
+ document.removeEventListener('pointermove', this.onDividerMove);
document.removeEventListener('pointerup', this.onDividerUp);
- }
+ };
onDividerDown = (e: React.PointerEvent) => {
e.stopPropagation();
e.preventDefault();
- document.addEventListener("pointermove", this.onDividerMove);
+ document.addEventListener('pointermove', this.onDividerMove);
document.addEventListener('pointerup', this.onDividerUp);
- }
+ };
- getTemplate = async () => {
- const parent = Docs.Create.StackingDocument([], { _width: 800, _height: 800, title: "Template", _chromeHidden: true });
- parent._columnWidth = 100;
- for (const row of this.rows.filter(row => row.isChecked)) {
- await this.createTemplateField(parent, row);
- row.uncheck();
+ getFieldView = async () => {
+ const rows = this.rows.filter(row => row.isChecked);
+ if (rows.length > 1) {
+ const parent = Docs.Create.StackingDocument([], { _autoHeight: true, _width: 300, title: `field views for ${DocCast(this.props.Document.data).title}`, _chromeHidden: true });
+ for (const row of rows) {
+ const field = this.createFieldView(DocCast(this.props.Document.data), row);
+ field && Doc.AddDocToList(parent, 'data', field);
+ row.uncheck();
+ }
+ return parent;
}
- return parent;
- }
+ return this.createFieldView(DocCast(this.props.Document.data), rows.lastElement());
+ };
- createTemplateField = async (parentStackingDoc: Doc, row: KeyValuePair) => {
+ createFieldView = (templateDoc: Doc, row: KeyValuePair) => {
const metaKey = row.props.keyName;
- const sourceDoc = await Cast(this.props.Document.data, Doc);
- if (!sourceDoc) {
- return;
- }
+ const fieldTemplate = Doc.MakeAlias(templateDoc);
+ fieldTemplate.title = metaKey;
+ fieldTemplate.layout = this.inferType(templateDoc[metaKey], metaKey);
+ return fieldTemplate;
+ };
- const fieldTemplate = await this.inferType(sourceDoc[metaKey], metaKey);
- if (!fieldTemplate) {
- return;
- }
- const previousViewType = fieldTemplate._viewType;
- Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(parentStackingDoc));
- previousViewType && (fieldTemplate._viewType = previousViewType);
-
- Cast(parentStackingDoc.data, listSpec(Doc))!.push(fieldTemplate);
- }
-
- inferType = async (data: FieldResult, metaKey: string) => {
+ inferType = (data: FieldResult, metaKey: string) => {
const options = { _width: 300, _height: 300, title: metaKey };
- if (data instanceof RichTextField || typeof data === "string" || typeof data === "number") {
- return Docs.Create.TextDocument("", options);
+ if (data instanceof RichTextField || typeof data === 'string' || typeof data === 'number') {
+ return FormattedTextBox.LayoutString(metaKey);
} else if (data instanceof List) {
if (data.length === 0) {
return Docs.Create.StackingDocument([], options);
}
- const first = await Cast(data[0], Doc);
+ const first = DocCast(data[0]);
if (!first || !first.data) {
return Docs.Create.StackingDocument([], options);
}
@@ -212,44 +242,52 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
return undefined;
}
} else if (data instanceof ImageField) {
- return Docs.Create.ImageDocument("https://image.flaticon.com/icons/png/512/23/23765.png", options);
+ return ImageBox.LayoutString(metaKey);
}
- return new Doc;
- }
+ return new Doc();
+ };
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
- const open = cm.findByDescription("Change Perspective...");
- const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
+ 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, "close");
- this.props.addDocTab(this.fieldDocToLayout, "add:right");
- }, icon: "image"
+ description: 'Default Perspective',
+ event: () => {
+ this.props.addDocTab(this.props.Document, 'close');
+ this.props.addDocTab(this.fieldDocToLayout, 'add:right');
+ },
+ icon: 'image',
});
- !open && cm.addItem({ description: "Change Perspective...", subitems: openItems, icon: "external-link-alt" });
- }
+ !open && cm.addItem({ description: 'Change Perspective...', subitems: openItems, icon: 'external-link-alt' });
+ };
render() {
- const dividerDragger = this.splitPercentage === 0 ? (null) :
- <div className="keyValueBox-dividerDragger" style={{ transform: `translate(calc(${100 - this.splitPercentage}% - 5px), 0px)` }}>
- <div className="keyValueBox-dividerDraggerThumb" onPointerDown={this.onDividerDown} />
- </div>;
-
- return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel} onContextMenu={this.specificContextMenu} ref={this._mainCont}>
- <table className="keyValueBox-table">
- <tbody className="keyValueBox-tbody">
- <tr className="keyValueBox-header">
- <th className="keyValueBox-key" style={{ width: `${100 - this.splitPercentage}%` }} ref={this._keyHeader}
- onPointerDown={SetupDrag(this._keyHeader, this.getTemplate)}
- >Key</th>
- <th className="keyValueBox-fields" style={{ width: `${this.splitPercentage}%` }}>Fields</th>
- </tr>
- {this.createTable}
- {this.newKeyValue}
- </tbody>
- </table>
- {dividerDragger}
- </div>);
+ const dividerDragger =
+ this.splitPercentage === 0 ? null : (
+ <div className="keyValueBox-dividerDragger" style={{ transform: `translate(calc(${100 - this.splitPercentage}% - 5px), 0px)` }}>
+ <div className="keyValueBox-dividerDraggerThumb" onPointerDown={this.onDividerDown} />
+ </div>
+ );
+
+ return (
+ <div className="keyValueBox-cont" onWheel={this.onPointerWheel} onContextMenu={this.specificContextMenu} ref={this._mainCont}>
+ <table className="keyValueBox-table">
+ <tbody className="keyValueBox-tbody">
+ <tr className="keyValueBox-header">
+ <th className="keyValueBox-key" style={{ width: `${100 - this.splitPercentage}%` }} ref={this._keyHeader} onPointerDown={SetupDrag(this._keyHeader, this.getFieldView)}>
+ Key
+ </th>
+ <th className="keyValueBox-fields" style={{ width: `${this.splitPercentage}%` }}>
+ Fields
+ </th>
+ </tr>
+ {this.createTable}
+ {this.newKeyValue}
+ </tbody>
+ </table>
+ {dividerDragger}
+ </div>
+ );
}
}