aboutsummaryrefslogtreecommitdiff
path: root/src/new_fields
diff options
context:
space:
mode:
Diffstat (limited to 'src/new_fields')
-rw-r--r--src/new_fields/Doc.ts46
-rw-r--r--src/new_fields/Proxy.ts4
-rw-r--r--src/new_fields/Schema.ts8
-rw-r--r--src/new_fields/ScriptField.ts94
-rw-r--r--src/new_fields/util.ts46
5 files changed, 178 insertions, 20 deletions
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index db3ec2e5c..b0184dd4e 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -2,14 +2,12 @@ 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";
import { RefField, FieldId } from "./RefField";
import { ToScriptString, SelfProxy, Parent, OnUpdate, Self, HandleUpdate, Update, Id } from "./FieldSymbols";
-import { TooltipLinkingMenu } from "../client/util/TooltipLinkingMenu";
-import { TooltipTextMenu } from "../client/util/TooltipTextMenu";
export namespace Field {
export function toScriptString(field: Field): string {
@@ -158,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);
@@ -168,6 +175,14 @@ export namespace Doc {
export function IsPrototype(doc: Doc) {
return GetT(doc, "isPrototype", "boolean", true);
}
+ export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) {
+ let hasProto = doc.proto instanceof Doc;
+ let onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1;
+ let onProto = hasProto && Object.getOwnPropertyNames(doc.proto).indexOf(key) !== -1;
+ if (onDeleg || !hasProto || (!onProto && !defaultProto)) {
+ doc[key] = value;
+ } else doc.proto![key] = value;
+ }
export async function SetOnPrototype(doc: Doc, key: string, value: Field) {
const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : doc;
@@ -234,6 +249,30 @@ export namespace Doc {
return true;
}
+ //
+ // Resolves a reference to a field by returning 'doc' if o field extension is specified,
+ // otherwise, it returns the extension document stored in doc.<fieldKey>_ext.
+ // This mechanism allows any fields to be extended with an extension document that can
+ // be used to capture field-specific metadata. For example, an image field can be extended
+ // to store annotations, ink, and other data.
+ //
+ export function resolvedFieldDataDoc(doc: Doc, fieldKey: string, fieldExt: string) {
+ return fieldExt && doc[fieldKey + "_ext"] instanceof Doc ? doc[fieldKey + "_ext"] as Doc : doc;
+ }
+
+ export function UpdateDocumentExtensionForField(doc: Doc, fieldKey: string) {
+ if (doc[fieldKey + "_ext"] === undefined) {
+ setTimeout(() => {
+ let docExtensionForField = new Doc(doc[Id] + fieldKey, true);
+ docExtensionForField.title = "Extension of " + doc.title + "'s field:" + fieldKey;
+ let proto: Doc | undefined = doc;
+ while (proto && !Doc.IsPrototype(proto)) {
+ proto = proto.proto;
+ }
+ (proto ? proto : doc)[fieldKey + "_ext"] = docExtensionForField;
+ }, 0);
+ }
+ }
export function MakeAlias(doc: Doc) {
if (!GetT(doc, "isPrototype", "boolean", true)) {
return Doc.MakeCopy(doc);
@@ -259,6 +298,7 @@ export namespace Doc {
}
}
});
+
return copy;
}
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/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts
new file mode 100644
index 000000000..3d56e9374
--- /dev/null
+++ b/src/new_fields/ScriptField.ts
@@ -0,0 +1,94 @@
+import { ObjectField } from "./ObjectField";
+import { CompiledScript, CompileScript } from "../client/util/Scripting";
+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 { Doc } from "../new_fields/Doc";
+
+function optional(propSchema: PropSchema) {
+ return custom(value => {
+ if (value !== undefined) {
+ return propSchema.serializer(value);
+ }
+ return SKIP;
+ }, (jsonValue: any, context: any, oldValue: any, callback: (err: any, result: any) => void) => {
+ if (jsonValue !== undefined) {
+ return propSchema.deserializer(jsonValue, callback, context, oldValue);
+ }
+ return SKIP;
+ });
+}
+
+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.script.originalScript, script.script.options);
+ if (!comp.compiled) {
+ throw new Error("Couldn't compile loaded script");
+ }
+ (script as any).script = comp;
+}
+
+@Deserializable("script", deserializeScript)
+export class ScriptField extends ObjectField {
+ @serializable(object(scriptSchema))
+ readonly script: CompiledScript;
+
+ constructor(script: CompiledScript) {
+ super();
+
+ this.script = script;
+ }
+
+ // init(callback: (res: Field) => any) {
+ // const options = this.options!;
+ // const keys = Object.keys(options.options.capturedIds);
+ // Server.GetFields(keys).then(fields => {
+ // let captured: { [name: string]: Field } = {};
+ // keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]);
+ // const opts: ScriptOptions = {
+ // addReturn: options.options.addReturn,
+ // params: options.options.params,
+ // requiredType: options.options.requiredType,
+ // capturedVariables: captured
+ // };
+ // const script = CompileScript(options.script, opts);
+ // if (!script.compiled) {
+ // throw new Error("Can't compile script");
+ // }
+ // this._script = script;
+ // callback(this);
+ // });
+ // }
+
+ [Copy](): ObjectField {
+ return new ScriptField(this.script);
+ }
+
+ [ToScriptString]() {
+ return "script field";
+ }
+}
+
+@Deserializable("computed", deserializeScript)
+export class ComputedField extends ScriptField {
+ //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;
+ }
+ return undefined;
+ }
+} \ No newline at end of file
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) {