diff options
author | bob <bcz@cs.brown.edu> | 2019-06-25 09:09:28 -0400 |
---|---|---|
committer | bob <bcz@cs.brown.edu> | 2019-06-25 09:09:28 -0400 |
commit | 66acf0237f2e1fe74660e5c2a035757403f79f8b (patch) | |
tree | a4fa797203ee262072c53ba845e6bcce214f151e | |
parent | 038819c6f1490bfa3ef9a5a7404ea8688f2b9fd6 (diff) | |
parent | 45dee8324dc360953c584ba18f5b53220ecfdf61 (diff) |
Merge branch 'templatesMac' of https://github.com/browngraphicslab/Dash-Web into templatesMac
-rw-r--r-- | src/client/util/DragManager.ts | 4 | ||||
-rw-r--r-- | src/client/util/Scripting.ts | 14 | ||||
-rw-r--r-- | src/client/views/collections/CollectionDockingView.tsx | 38 | ||||
-rw-r--r-- | src/client/views/nodes/KeyValueBox.tsx | 8 | ||||
-rw-r--r-- | src/client/views/nodes/KeyValuePair.tsx | 1 | ||||
-rw-r--r-- | src/new_fields/Doc.ts | 11 | ||||
-rw-r--r-- | src/new_fields/Proxy.ts | 4 | ||||
-rw-r--r-- | src/new_fields/Schema.ts | 8 | ||||
-rw-r--r-- | src/new_fields/ScriptField.ts (renamed from src/fields/ScriptField.ts) | 39 | ||||
-rw-r--r-- | src/new_fields/util.ts | 46 |
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) { |