aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbob <bcz@cs.brown.edu>2019-06-25 09:09:28 -0400
committerbob <bcz@cs.brown.edu>2019-06-25 09:09:28 -0400
commit66acf0237f2e1fe74660e5c2a035757403f79f8b (patch)
treea4fa797203ee262072c53ba845e6bcce214f151e
parent038819c6f1490bfa3ef9a5a7404ea8688f2b9fd6 (diff)
parent45dee8324dc360953c584ba18f5b53220ecfdf61 (diff)
Merge branch 'templatesMac' of https://github.com/browngraphicslab/Dash-Web into templatesMac
-rw-r--r--src/client/util/DragManager.ts4
-rw-r--r--src/client/util/Scripting.ts14
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx38
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx8
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx1
-rw-r--r--src/new_fields/Doc.ts11
-rw-r--r--src/new_fields/Proxy.ts4
-rw-r--r--src/new_fields/Schema.ts8
-rw-r--r--src/new_fields/ScriptField.ts (renamed from src/fields/ScriptField.ts)39
-rw-r--r--src/new_fields/util.ts46
10 files changed, 115 insertions, 58 deletions
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index cc83b7346..3143924fc 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -92,6 +92,8 @@ export namespace DragManager {
handlers: DragHandlers;
hideSource: boolean | (() => boolean);
+
+ withoutShiftDrag?: boolean;
}
export interface DragDropDisposer {
@@ -318,7 +320,7 @@ export namespace DragManager {
if (dragData instanceof DocumentDragData) {
dragData.userDropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
}
- if (e.shiftKey && CollectionDockingView.Instance) {
+ if (((options && !options.withoutShiftDrag) || !options) && e.shiftKey && CollectionDockingView.Instance) {
AbortDrag();
CollectionDockingView.Instance.StartOtherDrag({
pageX: e.pageX,
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 40e2ad6bb..30a05154a 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -12,7 +12,7 @@ import { Doc, Field } from '../../new_fields/Doc';
import { ImageField, PdfField, VideoField, AudioField } from '../../new_fields/URLField';
import { List } from '../../new_fields/List';
import { RichTextField } from '../../new_fields/RichTextField';
-import { ScriptField, ComputedField } from '../../fields/ScriptField';
+import { ScriptField, ComputedField } from '../../new_fields/ScriptField';
export interface ScriptSucccess {
success: true;
@@ -39,7 +39,6 @@ export interface CompileError {
}
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) {
@@ -64,10 +63,20 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
}
}
let thisParam = args.this || capturedVariables.this;
+ let batch: { end(): void } | undefined = undefined;
try {
+ if (!options.editable) {
+ batch = Doc.MakeReadOnly();
+ }
const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray);
+ if (batch) {
+ batch.end();
+ }
return { success: true, result };
} catch (error) {
+ if (batch) {
+ batch.end();
+ }
return { success: false, error };
}
};
@@ -133,6 +142,7 @@ export interface ScriptOptions {
params?: { [name: string]: string };
capturedVariables?: { [name: string]: Field };
typecheck?: boolean;
+ editable?: boolean;
}
export function CompileScript(script: string, options: ScriptOptions = {}): CompileResult {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 9aef17234..82cb5dbbb 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -24,6 +24,10 @@ import { SubCollectionViewProps } from "./CollectionSubView";
import { ParentDocSelector } from './ParentDocumentSelector';
import React = require("react");
import { MainView } from '../MainView';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faFile } from '@fortawesome/free-solid-svg-icons';
+library.add(faFile);
@observer
export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -262,7 +266,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
onPointerDown = (e: React.PointerEvent): void => {
this._isPointerDown = true;
let onPointerUp = action(() => {
- window.removeEventListener("pointerup", onPointerUp)
+ window.removeEventListener("pointerup", onPointerUp);
this._isPointerDown = false;
});
window.addEventListener("pointerup", onPointerUp);
@@ -277,7 +281,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
DocServer.GetRefField(docid).then(action(async (sourceDoc: Opt<Field>) =>
(sourceDoc instanceof Doc) && DragLinksAsDocuments(tab, x, y, sourceDoc)));
} else
- if ((className === "lm_title" || className === "lm_tab lm_active") && !e.shiftKey) {
+ if ((className === "lm_title" || className === "lm_tab lm_active") && e.shiftKey) {
e.stopPropagation();
e.preventDefault();
let x = e.clientX;
@@ -297,7 +301,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
handlers: {
dragComplete: emptyFunction,
},
- hideSource: false
+ hideSource: false,
+ withoutShiftDrag: true
});
}
}));
@@ -345,8 +350,11 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
let doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId) as Doc;
let dataDoc = await DocServer.GetRefField(tab.contentItem.config.props.dataDocumentId) as Doc;
if (doc instanceof Doc && dataDoc instanceof Doc) {
- let counter: any = this.htmlToElement(`<span class="messageCounter">0</div>`);
- tab.element.append(counter);
+ let dragSpan = document.createElement("span");
+ dragSpan.style.position = "relative";
+ dragSpan.style.bottom = "6px";
+ dragSpan.style.paddingLeft = "4px";
+ dragSpan.style.paddingRight = "2px";
let upDiv = document.createElement("span");
const stack = tab.contentItem.parent;
// shifts the focus to this tab when another tab is dragged over it
@@ -358,16 +366,24 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
}
tab.setActive(true);
};
- ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, dataDoc, location) => CollectionDockingView.Instance.AddTab(stack, doc, dataDoc)} />, upDiv);
- tab.reactComponents = [upDiv];
+ ReactDOM.render(<span onPointerDown={
+ e => {
+ e.preventDefault();
+ e.stopPropagation();
+ DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc], [dataDoc]), e.clientX, e.clientY, {
+ handlers: { dragComplete: emptyFunction },
+ hideSource: false
+ });
+ }}><FontAwesomeIcon icon="file" size="lg" /></span>, dragSpan);
+ ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={doc => CollectionDockingView.Instance.AddTab(stack, doc, dataDoc)} />, upDiv);
+ tab.reactComponents = [dragSpan, upDiv];
+ tab.element.append(dragSpan);
tab.element.append(upDiv);
- counter.DashDocId = tab.contentItem.config.props.documentId;
- counter.DashDataDocId = tab.contentItem.config.props.dataDocumentId;
- tab.reactionDisposer = reaction(() => [doc.linkedFromDocs, doc.LinkedToDocs, doc.title],
+ tab.reactionDisposer = reaction(() => [doc.title],
() => {
- counter.innerHTML = DocListCast(doc.linkedFromDocs).length + DocListCast(doc.linkedToDocs).length;
tab.titleElement[0].textContent = doc.title;
}, { fireImmediately: true });
+ //TODO why can't this just be doc instead of the id?
tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
}
}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 3d626eef0..cd65c42bc 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -9,7 +9,7 @@ import { KeyValuePair } from "./KeyValuePair";
import React = require("react");
import { NumCast, Cast, FieldValue } from "../../../new_fields/Types";
import { Doc, Field } from "../../../new_fields/Doc";
-import { ComputedField } from "../../../fields/ScriptField";
+import { ComputedField } from "../../../new_fields/ScriptField";
@observer
export class KeyValueBox extends React.Component<FieldViewProps> {
@@ -38,10 +38,11 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
}
public static SetField(doc: Doc, key: string, value: string) {
let eq = value.startsWith("=");
+ let target = eq ? doc : Doc.GetProto(doc);
value = eq ? value.substr(1) : value;
let dubEq = value.startsWith(":=");
value = dubEq ? value.substr(2) : value;
- let options: ScriptOptions = { addReturn: true };
+ let options: ScriptOptions = { addReturn: true, params: { this: "Doc" } };
if (dubEq) options.typecheck = false;
let script = CompileScript(value, options);
if (!script.compiled) {
@@ -49,12 +50,11 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
}
let field = new ComputedField(script);
if (!dubEq) {
- let res = script.run();
+ let res = script.run({ this: target });
if (!res.success) return false;
field = res.result;
}
if (Field.IsField(field, true)) {
- let target = eq ? doc : Doc.GetProto(doc);
target[key] = field;
return true;
}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index d3bd9c875..3e03e7e75 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -11,7 +11,6 @@ import "./KeyValuePair.scss";
import React = require("react");
import { Doc, Opt, Field } from '../../../new_fields/Doc';
import { FieldValue } from '../../../new_fields/Types';
-import { ComputedField } from '../../../fields/ScriptField';
import { KeyValueBox } from './KeyValueBox';
// Represents one row in a key value plane
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 0a1964dea..e0937c619 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -2,7 +2,7 @@ import { observable, action } from "mobx";
import { serializable, primitive, map, alias, list } from "serializr";
import { autoObject, SerializationHelper, Deserializable } from "../client/util/SerializationHelper";
import { DocServer } from "../client/DocServer";
-import { setter, getter, getField, updateFunction, deleteProperty } from "./util";
+import { setter, getter, getField, updateFunction, deleteProperty, makeEditable, makeReadOnly } from "./util";
import { Cast, ToConstructor, PromiseValue, FieldValue, NumCast } from "./Types";
import { listSpec } from "./Schema";
import { ObjectField } from "./ObjectField";
@@ -156,6 +156,15 @@ export namespace Doc {
// return Cast(field, ctor);
// });
// }
+ export function MakeReadOnly(): { end(): void } {
+ makeReadOnly();
+ return {
+ end() {
+ makeEditable();
+ }
+ };
+ }
+
export function Get(doc: Doc, key: string, ignoreProto: boolean = false): FieldResult {
const self = doc[Self];
return getField(self, key, ignoreProto);
diff --git a/src/new_fields/Proxy.ts b/src/new_fields/Proxy.ts
index 130ec066e..38d874a68 100644
--- a/src/new_fields/Proxy.ts
+++ b/src/new_fields/Proxy.ts
@@ -48,9 +48,8 @@ export class ProxyField<T extends RefField> extends ObjectField {
private failed = false;
private promise?: Promise<any>;
- value(callback?: ((field: T | undefined) => void)): T | undefined | FieldWaiting {
+ value(): T | undefined | FieldWaiting {
if (this.cache) {
- callback && callback(this.cache);
return this.cache;
}
if (this.failed) {
@@ -64,7 +63,6 @@ export class ProxyField<T extends RefField> extends ObjectField {
return field;
}));
}
- callback && this.promise.then(callback);
return this.promise;
}
}
diff --git a/src/new_fields/Schema.ts b/src/new_fields/Schema.ts
index 40ffaecd5..2355304d5 100644
--- a/src/new_fields/Schema.ts
+++ b/src/new_fields/Schema.ts
@@ -2,6 +2,7 @@ import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListS
import { Doc, Field } from "./Doc";
import { ObjectField } from "./ObjectField";
import { RefField } from "./RefField";
+import { SelfProxy } from "./FieldSymbols";
type AllToInterface<T extends Interface[]> = {
1: ToInterface<Head<T>> & AllToInterface<Tail<T>>,
@@ -56,9 +57,10 @@ export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFu
}
});
const fn = (doc: Doc) => {
- if (!(doc instanceof Doc)) {
- throw new Error("Currently wrapping a schema in another schema isn't supported");
- }
+ doc = doc[SelfProxy];
+ // if (!(doc instanceof Doc)) {
+ // throw new Error("Currently wrapping a schema in another schema isn't supported");
+ // }
const obj = Object.create(proto, { doc: { value: doc, writable: false } });
return obj;
};
diff --git a/src/fields/ScriptField.ts b/src/new_fields/ScriptField.ts
index ac46ccf90..3d56e9374 100644
--- a/src/fields/ScriptField.ts
+++ b/src/new_fields/ScriptField.ts
@@ -1,9 +1,9 @@
-import { ObjectField } from "../new_fields/ObjectField";
+import { ObjectField } from "./ObjectField";
import { CompiledScript, CompileScript } from "../client/util/Scripting";
-import { Copy, ToScriptString, Parent, SelfProxy } from "../new_fields/FieldSymbols";
+import { Copy, ToScriptString, Parent, SelfProxy } from "./FieldSymbols";
import { serializable, createSimpleSchema, map, primitive, object, deserialize, PropSchema, custom, SKIP } from "serializr";
import { Deserializable } from "../client/util/SerializationHelper";
-import { computed } from "mobx";
+import { Doc } from "../new_fields/Doc";
function optional(propSchema: PropSchema) {
return custom(value => {
@@ -23,35 +23,32 @@ const optionsSchema = createSimpleSchema({
requiredType: true,
addReturn: true,
typecheck: true,
+ readonly: true,
params: optional(map(primitive()))
});
+const scriptSchema = createSimpleSchema({
+ options: object(optionsSchema),
+ originalScript: true
+});
+
function deserializeScript(script: ScriptField) {
- const comp = CompileScript(script.scriptString, script.options);
+ const comp = CompileScript(script.script.originalScript, script.script.options);
if (!comp.compiled) {
throw new Error("Couldn't compile loaded script");
}
- (script as any)._script = comp;
+ (script as any).script = comp;
}
@Deserializable("script", deserializeScript)
export class ScriptField extends ObjectField {
- protected readonly _script: CompiledScript;
+ @serializable(object(scriptSchema))
+ readonly script: CompiledScript;
constructor(script: CompiledScript) {
super();
- this._script = script;
- }
-
- @serializable(custom(object(optionsSchema).serializer, () => SKIP))
- get options() {
- return this._script && this._script.options;
- }
-
- @serializable(custom(primitive().serializer, () => SKIP))
- get scriptString(): string {
- return this._script && this._script.originalScript;
+ this.script = script;
}
// init(callback: (res: Field) => any) {
@@ -76,7 +73,7 @@ export class ScriptField extends ObjectField {
// }
[Copy](): ObjectField {
- return new ScriptField(this._script);
+ return new ScriptField(this.script);
}
[ToScriptString]() {
@@ -86,9 +83,9 @@ export class ScriptField extends ObjectField {
@Deserializable("computed", deserializeScript)
export class ComputedField extends ScriptField {
- @computed
- get value() {
- const val = this._script.run({ this: (this[Parent] as any)[SelfProxy] });
+ //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc
+ value(doc: Doc) {
+ const val = this.script.run({ this: doc });
if (val.success) {
return val.result;
}
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 8cb1db953..abb777adf 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -6,10 +6,13 @@ import { FieldValue } from "./Types";
import { RefField } from "./RefField";
import { ObjectField } from "./ObjectField";
import { action } from "mobx";
-import { Parent, OnUpdate, Update, Id, SelfProxy } from "./FieldSymbols";
-import { ComputedField } from "../fields/ScriptField";
+import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
+import { ComputedField } from "./ScriptField";
-export const setter = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
+function _readOnlySetter(): never {
+ throw new Error("Documents can't be modified in read-only mode");
+}
+const _setterImpl = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
if (SerializationHelper.IsSerializing()) {
target[prop] = value;
return true;
@@ -18,6 +21,9 @@ export const setter = action(function (target: any, prop: string | symbol | numb
target[prop] = value;
return true;
}
+ if (value !== undefined) {
+ value = value[SelfProxy] || value;
+ }
const curValue = target.__fields[prop];
if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) {
// TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically
@@ -28,11 +34,10 @@ export const setter = action(function (target: any, prop: string | symbol | numb
value = new ProxyField(value);
}
if (value instanceof ObjectField) {
- //TODO Instead of target, maybe use target[Self]
- if (value[Parent] && value[Parent] !== target) {
+ if (value[Parent] && value[Parent] !== receiver) {
throw new Error("Can't put the same object in multiple documents at the same time");
}
- value[Parent] = target;
+ value[Parent] = receiver;
value[OnUpdate] = updateFunction(target, prop, value, receiver);
}
if (curValue instanceof ObjectField) {
@@ -53,6 +58,20 @@ export const setter = action(function (target: any, prop: string | symbol | numb
return true;
});
+let _setter: (target: any, prop: string | symbol | number, value: any, receiver: any) => boolean = _setterImpl;
+
+export function makeReadOnly() {
+ _setter = _readOnlySetter;
+}
+
+export function makeEditable() {
+ _setter = _setterImpl;
+}
+
+export function setter(target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
+ return _setter(target, prop, value, receiver);
+}
+
export function getter(target: any, prop: string | symbol | number, receiver: any): any {
if (typeof prop === "symbol") {
return target.__fields[prop] || target[prop];
@@ -60,25 +79,30 @@ export function getter(target: any, prop: string | symbol | number, receiver: an
if (SerializationHelper.IsSerializing()) {
return target[prop];
}
- return getField(target, prop);
+ return getFieldImpl(target, prop, receiver);
}
-export function getField(target: any, prop: string | number, ignoreProto: boolean = false): any {
+function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreProto: boolean = false): any {
+ receiver = receiver || target[SelfProxy];
const field = target.__fields[prop];
if (field instanceof ProxyField) {
return field.value();
}
if (field instanceof ComputedField) {
- return field.value;
+ return field.value(receiver);
}
if (field === undefined && !ignoreProto && prop !== "proto") {
- const proto = getField(target, "proto", true);
+ const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters
if (proto instanceof Doc) {
- return proto[prop];
+ return getFieldImpl(proto[Self], prop, receiver, ignoreProto);
}
return undefined;
}
return field;
+
+}
+export function getField(target: any, prop: string | number, ignoreProto: boolean = false): any {
+ return getFieldImpl(target, prop, undefined, ignoreProto);
}
export function deleteProperty(target: any, prop: string | number | symbol) {