aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/util/Scripting.ts3
-rw-r--r--src/client/views/DocumentDecorations.scss44
-rw-r--r--src/client/views/DocumentDecorations.tsx31
-rw-r--r--src/client/views/MetadataEntryMenu.scss10
-rw-r--r--src/client/views/MetadataEntryMenu.tsx74
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx31
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx8
-rw-r--r--src/new_fields/Doc.ts9
8 files changed, 169 insertions, 41 deletions
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 3156c4f43..62c2cfe85 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -33,6 +33,8 @@ export interface CompileError {
errors: any[];
}
+export type CompileResult = CompiledScript | CompileError;
+
export namespace Scripting {
export function addGlobal(global: { name: string }): void;
export function addGlobal(name: string, global: any): void;
@@ -61,7 +63,6 @@ export function scriptingGlobal(constructor: { new(...args: any[]): any }) {
const scriptingGlobals: { [name: string]: any } = {};
-export type CompileResult = CompiledScript | CompileError;
function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult {
const errors = diagnostics.some(diag => diag.category === ts.DiagnosticCategory.Error);
if ((options.typecheck !== false && errors) || !script) {
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 2d430512b..1afc5c147 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -1,6 +1,7 @@
@import "globalCssVariables";
$linkGap : 3px;
+
.documentDecorations {
position: absolute;
}
@@ -52,15 +53,16 @@ $linkGap : 3px;
grid-column-start: 5;
grid-column-end: 7;
}
-
- #documentDecorations-borderRadius{
+
+ #documentDecorations-borderRadius {
grid-column-start: 5;
grid-column-end: 7;
border-radius: 100%;
- .borderRadiusTooltip{
- width:10px;
- height:10px;
- position:absolute;
+
+ .borderRadiusTooltip {
+ width: 10px;
+ height: 10px;
+ position: absolute;
}
}
@@ -68,8 +70,9 @@ $linkGap : 3px;
#documentDecorations-bottomRightResizer {
cursor: nwse-resize;
}
+
#documentDecorations-bottomRightResizer {
- grid-row:4;
+ grid-row: 4;
}
#documentDecorations-topRightResizer,
@@ -86,7 +89,8 @@ $linkGap : 3px;
#documentDecorations-rightResizer {
cursor: ew-resize;
}
- .title{
+
+ .title {
background: $alt-accent;
grid-column-start: 3;
grid-column-end: 4;
@@ -129,7 +133,6 @@ $linkGap : 3px;
.linkFlyout {
grid-column: 2/4;
- margin-top: $linkGap;
}
.linkButton-empty:hover {
@@ -145,6 +148,7 @@ $linkGap : 3px;
}
.link-button-container {
+ margin-top: $linkGap;
grid-column: 1/4;
width: auto;
height: auto;
@@ -152,9 +156,12 @@ $linkGap : 3px;
flex-direction: row;
}
+.linkButtonWrapper {
+ pointer-events: auto;
+ padding-right: 5px;
+}
+
.linkButton-linker {
- margin-left: 5px;
- margin-top: $linkGap;
height: 20px;
width: 20px;
text-align: center;
@@ -169,7 +176,8 @@ $linkGap : 3px;
transform: scale(1.05);
}
-.linkButton-empty, .linkButton-nonempty {
+.linkButton-empty,
+.linkButton-nonempty {
height: 20px;
width: 20px;
border-radius: 50%;
@@ -194,9 +202,6 @@ $linkGap : 3px;
}
.templating-menu {
- position: absolute;
- bottom: 0;
- left: 50px;
pointer-events: auto;
text-transform: uppercase;
letter-spacing: 2px;
@@ -208,15 +213,17 @@ $linkGap : 3px;
align-items: center;
}
-.fa-icon-link {
+.documentdecorations-icon {
margin-top: 3px;
}
-.templating-button {
+
+.templating-button,
+.docDecs-tagButton {
width: 20px;
height: 20px;
border-radius: 50%;
opacity: 0.9;
- font-size:14;
+ font-size: 14;
background-color: $dark-color;
color: $light-color;
text-align: center;
@@ -238,6 +245,7 @@ $linkGap : 3px;
background-color: $light-color-secondary;
padding: 2px 12px;
list-style: none;
+
.templateToggle {
text-align: left;
}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index c24d66ee4..56fbd75a0 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,5 +1,5 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faLink } from '@fortawesome/free-solid-svg-icons';
+import { faLink, faTag } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
@@ -27,11 +27,13 @@ import React = require("react");
import { RichTextField } from '../../new_fields/RichTextField';
import { LinkManager } from '../util/LinkManager';
import { ObjectField } from '../../new_fields/ObjectField';
+import { MetadataEntryMenu } from './MetadataEntryMenu';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
library.add(faLink);
+library.add(faTag);
@observer
export class DocumentDecorations extends React.Component<{}, { value: string }> {
@@ -596,8 +598,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (!canEmbed) return (null);
return (
<div className="linkButtonWrapper">
- <div style={{ paddingTop: 3, marginLeft: 30 }} title="Drag Embed" className="linkButton-linker" ref={this._embedButton} onPointerDown={this.onEmbedButtonDown}>
- <FontAwesomeIcon className="fa-image" icon="image" size="sm" />
+ <div title="Drag Embed" className="linkButton-linker" ref={this._embedButton} onPointerDown={this.onEmbedButtonDown}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="image" size="sm" />
</div>
</div>
);
@@ -610,10 +612,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
this._textDoc = thisDoc;
return (
<div className="tooltipwrapper">
- <div style={{ paddingTop: 3, marginLeft: 30 }} title="Hide Tooltip" className="linkButton-linker" ref={this._tooltipoff} onPointerDown={this.onTooltipOff}>
+ <div title="Hide Tooltip" className="linkButton-linker" ref={this._tooltipoff} onPointerDown={this.onTooltipOff}>
{/* <FontAwesomeIcon className="fa-image" icon="image" size="sm" /> */}
- T
- </div>
+ </div>
</div>
);
@@ -634,6 +635,17 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
}
+ get metadataMenu() {
+ return (
+ <div className="linkButtonWrapper">
+ <Flyout anchorPoint={anchorPoints.TOP_LEFT}
+ content={<MetadataEntryMenu docs={() => SelectionManager.SelectedDocuments().map(dv => dv.props.Document)} />}>{/* tfs: @bcz This might need to be the data document? */}
+ <div className="docDecs-tagButton" title="Add fields"><FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" /></div>
+ </Flyout>
+ </div>
+ );
+ }
+
render() {
var bounds = this.Bounds;
let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
@@ -723,10 +735,13 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
</div>
<div className="linkButtonWrapper">
<div title="Drag Link" className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}>
- <FontAwesomeIcon className="fa-icon-link" icon="link" size="sm" />
+ <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />
</div>
</div>
- <TemplateMenu docs={SelectionManager.ViewsSortedVertically()} templates={templates} />
+ <div className="linkButtonWrapper">
+ <TemplateMenu docs={SelectionManager.ViewsSortedVertically()} templates={templates} />
+ </div>
+ {this.metadataMenu}
{this.considerEmbed()}
{/* {this.considerTooltip()} */}
</div>
diff --git a/src/client/views/MetadataEntryMenu.scss b/src/client/views/MetadataEntryMenu.scss
new file mode 100644
index 000000000..73e5b6a73
--- /dev/null
+++ b/src/client/views/MetadataEntryMenu.scss
@@ -0,0 +1,10 @@
+.metadataEntry-input {
+ width: 40%;
+ margin-left: 5px;
+ margin-right: 5px;
+}
+
+.metadataEntry-outerDiv {
+ display: flex;
+ width: 300px;
+} \ No newline at end of file
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
new file mode 100644
index 000000000..0dc7e0220
--- /dev/null
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -0,0 +1,74 @@
+import * as React from 'react';
+import "./MetadataEntryMenu.scss";
+import { observer } from 'mobx-react';
+import { observable, action } from 'mobx';
+import { KeyValueBox } from './nodes/KeyValueBox';
+import { Doc } from '../../new_fields/Doc';
+
+export type DocLike = Doc | Doc[] | Promise<Doc> | Promise<Doc[]>;
+export interface MetadataEntryProps {
+ docs: DocLike | (() => DocLike);
+ onError?: () => boolean;
+}
+
+@observer
+export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
+ @observable private _currentKey: string = "";
+ @observable private _currentValue: string = "";
+
+ @action
+ onKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._currentKey = e.target.value;
+ }
+
+ @action
+ onValueChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._currentValue = e.target.value;
+ }
+
+ onValueKeyDown = async (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") {
+ const script = KeyValueBox.CompileKVPScript(this._currentValue);
+ if (!script) return;
+ let doc = this.props.docs;
+ if (typeof doc === "function") {
+ doc = doc();
+ }
+ doc = await doc;
+ let success: boolean;
+ if (doc instanceof Doc) {
+ success = KeyValueBox.ApplyKVPScript(doc, this._currentKey, script);
+ } else {
+ success = doc.every(d => KeyValueBox.ApplyKVPScript(d, this._currentKey, script));
+ }
+ if (!success) {
+ if (this.props.onError) {
+ if (this.props.onError()) {
+ this.clearInputs();
+ }
+ } else {
+ this.clearInputs();
+ }
+ } else {
+ this.clearInputs();
+ }
+ }
+ }
+
+ @action
+ clearInputs = () => {
+ this._currentKey = "";
+ this._currentValue = "";
+ }
+
+ render() {
+ return (
+ <div className="metadataEntry-outerDiv">
+ Key:
+ <input className="metadataEntry-input" value={this._currentKey} onChange={this.onKeyChange} />
+ Value:
+ <input className="metadataEntry-input" value={this._currentValue} onChange={this.onValueChange} onKeyDown={this.onValueKeyDown} />
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index eb8abf2c7..c9dd9a64e 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -2,7 +2,7 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import { CompileScript, ScriptOptions } from "../../util/Scripting";
+import { CompileScript, ScriptOptions, CompiledScript } from "../../util/Scripting";
import { FieldView, FieldViewProps } from './FieldView';
import "./KeyValueBox.scss";
import { KeyValuePair } from "./KeyValuePair";
@@ -21,6 +21,12 @@ import { ImageField } from "../../../new_fields/URLField";
import { SelectionManager } from "../../util/SelectionManager";
import { listSpec } from "../../../new_fields/Schema";
+export type KVPScript = {
+ script: CompiledScript;
+ type: "computed" | "script" | false;
+ onDelegate: boolean;
+};
+
@observer
export class KeyValueBox extends React.Component<FieldViewProps> {
private _mainCont = React.createRef<HTMLDivElement>();
@@ -48,22 +54,27 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
}
}
}
- public static SetField(doc: Doc, key: string, value: string) {
+ public static CompileKVPScript(value: string): KVPScript | undefined {
let eq = value.startsWith("=");
- let target = eq ? doc : Doc.GetProto(doc);
value = eq ? value.substr(1) : value;
- let dubEq = value.startsWith(":=") ? 1 : value.startsWith(";=") ? 2 : 0;
+ const dubEq = value.startsWith(":=") ? "computed" : value.startsWith(";=") ? "script" : false;
value = dubEq ? value.substr(2) : value;
let options: ScriptOptions = { addReturn: true, params: { this: "Doc" } };
if (dubEq) options.typecheck = false;
let script = CompileScript(value, options);
if (!script.compiled) {
- return false;
+ return undefined;
}
+ return { script, type: dubEq, onDelegate: eq };
+ }
+
+ public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript): boolean {
+ const { script, type, onDelegate } = kvpScript;
+ const target = onDelegate ? doc : Doc.GetProto(doc);
let field: Field;
- if (dubEq === 1) {
+ if (type === "computed") {
field = new ComputedField(script);
- } else if (dubEq === 2) {
+ } else if (type === "script") {
field = new ScriptField(script);
} else {
let res = script.run({ this: target });
@@ -77,6 +88,12 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
return false;
}
+ public static SetField(doc: Doc, key: string, value: string) {
+ const script = this.CompileKVPScript(value);
+ if (!script) return false;
+ return this.ApplyKVPScript(doc, key, script);
+ }
+
onPointerDown = (e: React.PointerEvent): void => {
if (e.buttons === 1 && this.props.isSelected()) {
e.stopPropagation();
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index b5db52ac7..209782242 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -92,13 +92,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
contents={contents}
height={36}
GetValue={() => {
- const onDelegate = Object.keys(props.Document).includes(props.fieldKey);
-
- let field = FieldValue(props.Document[props.fieldKey]);
- if (Field.IsField(field)) {
- return (onDelegate ? "=" : "") + Field.toScriptString(field);
- }
- return "";
+ return Field.toKeyValueString(props.Document, props.fieldKey);
}}
SetValue={(value: string) =>
KeyValueBox.SetField(props.Document, props.fieldKey, value)}>
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 0e5f8b4d9..c5f9e7adf 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -12,6 +12,15 @@ import { scriptingGlobal } from "../client/util/Scripting";
import { List } from "./List";
export namespace Field {
+ export function toKeyValueString(doc: Doc, key: string): string {
+ const onDelegate = Object.keys(doc).includes(key);
+
+ let field = FieldValue(doc[key]);
+ if (Field.IsField(field)) {
+ return (onDelegate ? "=" : "") + Field.toScriptString(field);
+ }
+ return "";
+ }
export function toScriptString(field: Field): string {
if (typeof field === "string") {
return `"${field}"`;