aboutsummaryrefslogtreecommitdiff
path: root/src/new_fields
diff options
context:
space:
mode:
Diffstat (limited to 'src/new_fields')
-rw-r--r--src/new_fields/CursorField.ts63
-rw-r--r--src/new_fields/DateField.ts23
-rw-r--r--src/new_fields/Doc.ts157
-rw-r--r--src/new_fields/FieldSymbols.ts10
-rw-r--r--src/new_fields/HtmlField.ts7
-rw-r--r--src/new_fields/IconField.ts7
-rw-r--r--src/new_fields/InkField.ts12
-rw-r--r--src/new_fields/List.ts75
-rw-r--r--src/new_fields/ObjectField.ts12
-rw-r--r--src/new_fields/Proxy.ts9
-rw-r--r--src/new_fields/RefField.ts5
-rw-r--r--src/new_fields/RichTextField.ts7
-rw-r--r--src/new_fields/Schema.ts18
-rw-r--r--src/new_fields/Types.ts17
-rw-r--r--src/new_fields/URLField.ts16
-rw-r--r--src/new_fields/util.ts38
16 files changed, 363 insertions, 113 deletions
diff --git a/src/new_fields/CursorField.ts b/src/new_fields/CursorField.ts
new file mode 100644
index 000000000..fd86031a8
--- /dev/null
+++ b/src/new_fields/CursorField.ts
@@ -0,0 +1,63 @@
+import { ObjectField } from "./ObjectField";
+import { observable } from "mobx";
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, createSimpleSchema, object, date } from "serializr";
+import { OnUpdate, ToScriptString, Copy } from "./FieldSymbols";
+
+export type CursorPosition = {
+ x: number,
+ y: number
+};
+
+export type CursorMetadata = {
+ id: string,
+ identifier: string,
+ timestamp: number
+};
+
+export type CursorData = {
+ metadata: CursorMetadata,
+ position: CursorPosition
+};
+
+const PositionSchema = createSimpleSchema({
+ x: true,
+ y: true
+});
+
+const MetadataSchema = createSimpleSchema({
+ id: true,
+ identifier: true,
+ timestamp: true
+});
+
+const CursorSchema = createSimpleSchema({
+ metadata: object(MetadataSchema),
+ position: object(PositionSchema)
+});
+
+@Deserializable("cursor")
+export default class CursorField extends ObjectField {
+
+ @serializable(object(CursorSchema))
+ readonly data: CursorData;
+
+ constructor(data: CursorData) {
+ super();
+ this.data = data;
+ }
+
+ setPosition(position: CursorPosition) {
+ this.data.position = position;
+ this.data.metadata.timestamp = Date.now();
+ this[OnUpdate]();
+ }
+
+ [Copy]() {
+ return new CursorField(this.data);
+ }
+
+ [ToScriptString]() {
+ return "invalid";
+ }
+} \ No newline at end of file
diff --git a/src/new_fields/DateField.ts b/src/new_fields/DateField.ts
new file mode 100644
index 000000000..fc8abb9d9
--- /dev/null
+++ b/src/new_fields/DateField.ts
@@ -0,0 +1,23 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, date } from "serializr";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString } from "./FieldSymbols";
+
+@Deserializable("date")
+export class DateField extends ObjectField {
+ @serializable(date())
+ readonly date: Date;
+
+ constructor(date: Date = new Date()) {
+ super();
+ this.date = date;
+ }
+
+ [Copy]() {
+ return new DateField(this.date);
+ }
+
+ [ToScriptString]() {
+ return `new DateField(new Date(${this.date.toISOString()}))`;
+ }
+}
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 3055af1bf..7f7263cf1 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -4,28 +4,54 @@ import { autoObject, SerializationHelper, Deserializable } from "../client/util/
import { DocServer } from "../client/DocServer";
import { setter, getter, getField, updateFunction, deleteProperty } from "./util";
import { Cast, ToConstructor, PromiseValue, FieldValue, NumCast } from "./Types";
-import { UndoManager, undoBatch } from "../client/util/UndoManager";
import { listSpec } from "./Schema";
-import { List } from "./List";
-import { ObjectField, Parent, OnUpdate } from "./ObjectField";
-import { RefField, FieldId, Id, HandleUpdate } from "./RefField";
-import { Docs } from "../client/documents/Documents";
-
-export function IsField(field: any): field is Field {
- return (typeof field === "string")
- || (typeof field === "number")
- || (typeof field === "boolean")
- || (field instanceof ObjectField)
- || (field instanceof RefField);
+import { ObjectField } from "./ObjectField";
+import { RefField, FieldId } from "./RefField";
+import { ToScriptString, SelfProxy, Parent, OnUpdate, Self, HandleUpdate, Update, Id } from "./FieldSymbols";
+
+export namespace Field {
+ export function toScriptString(field: Field): string {
+ if (typeof field === "string") {
+ return `"${field}"`;
+ } else if (typeof field === "number" || typeof field === "boolean") {
+ return String(field);
+ } else {
+ return field[ToScriptString]();
+ }
+ }
+ export function IsField(field: any): field is Field;
+ export function IsField(field: any, includeUndefined: true): field is Field | undefined;
+ export function IsField(field: any, includeUndefined: boolean = false): field is Field | undefined {
+ return (typeof field === "string")
+ || (typeof field === "number")
+ || (typeof field === "boolean")
+ || (field instanceof ObjectField)
+ || (field instanceof RefField)
+ || (includeUndefined && field === undefined);
+ }
}
export type Field = number | string | boolean | ObjectField | RefField;
export type Opt<T> = T | undefined;
export type FieldWaiting<T extends RefField = RefField> = T extends undefined ? never : Promise<T | undefined>;
export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract<T, RefField>>;
-export const Update = Symbol("Update");
-export const Self = Symbol("Self");
-const SelfProxy = Symbol("SelfProxy");
+/**
+ * Cast any field to either a List of Docs or undefined if the given field isn't a List of Docs.
+ * If a default value is given, that will be returned instead of undefined.
+ * If a default value is given, the returned value should not be modified as it might be a temporary value.
+ * If no default value is given, and the returned value is not undefined, it can be safely modified.
+ */
+export function DocListCastAsync(field: FieldResult): Promise<Doc[] | undefined>;
+export function DocListCastAsync(field: FieldResult, defaultValue: Doc[]): Promise<Doc[]>;
+export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
+ const list = Cast(field, listSpec(Doc));
+ return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
+}
+
+export function DocListCast(field: FieldResult): Doc[] {
+ return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[];
+}
+
export const WidthSym = Symbol("Width");
export const HeightSym = Symbol("Height");
@@ -36,6 +62,7 @@ export class Doc extends RefField {
const doc = new Proxy<this>(this, {
set: setter,
get: getter,
+ // getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter
has: (target, key) => key in target.__fields,
ownKeys: target => Object.keys(target.__fields),
getOwnPropertyDescriptor: (target, prop) => {
@@ -43,6 +70,7 @@ export class Doc extends RefField {
return {
configurable: true,//TODO Should configurable be true?
enumerable: true,
+ value: target.__fields[prop]
};
}
return Reflect.getOwnPropertyDescriptor(target, prop);
@@ -85,11 +113,14 @@ export class Doc extends RefField {
private [Self] = this;
private [SelfProxy]: any;
- public [WidthSym] = () => NumCast(this.__fields.width); // bcz: is this the right way to access width/height? it didn't work with : this.width
- public [HeightSym] = () => NumCast(this.__fields.height);
+ public [WidthSym] = () => NumCast(this[SelfProxy].width); // bcz: is this the right way to access width/height? it didn't work with : this.width
+ public [HeightSym] = () => NumCast(this[SelfProxy].height);
+
+ [ToScriptString]() {
+ return "invalid";
+ }
public [HandleUpdate](diff: any) {
- console.log(diff);
const set = diff.$set;
if (set) {
for (const key in set) {
@@ -119,11 +150,15 @@ export namespace Doc {
const self = doc[Self];
return getField(self, key, ignoreProto);
}
- export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): T | null | undefined {
- return Cast(Get(doc, key, ignoreProto), ctor) as T | null | undefined;
+ export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> {
+ return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>;
+ }
+ export function IsPrototype(doc: Doc) {
+ return GetT(doc, "isPrototype", "boolean", true);
}
export async function SetOnPrototype(doc: Doc, key: string, value: Field) {
- const proto = doc.proto;
+ const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : doc;
+
if (proto) {
proto[key] = value;
}
@@ -141,23 +176,47 @@ export namespace Doc {
for (const key in fields) {
if (fields.hasOwnProperty(key)) {
const value = fields[key];
- if (value !== undefined) {
- doc[key] = value;
- }
+ // Do we want to filter out undefineds?
+ // if (value !== undefined) {
+ doc[key] = value;
+ // }
}
}
return doc;
}
- export function MakeAlias(doc: Doc) {
- const alias = new Doc;
+ // compare whether documents or their protos match
+ export function AreProtosEqual(doc: Doc, other: Doc) {
+ let r = (doc === other);
+ let r2 = (doc.proto === other);
+ let r3 = (other.proto === doc);
+ let r4 = (doc.proto === other.proto);
+ return r || r2 || r3 || r4;
+ }
- PromiseValue(Cast(doc.proto, Doc)).then(proto => {
- if (proto) {
- alias.proto = proto;
- }
- });
+ // gets the document's prototype or returns the document if it is a prototype
+ export function GetProto(doc: Doc) {
+ return Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : doc.proto!;
+ }
+
+ export function allKeys(doc: Doc): string[] {
+ const results: Set<string> = new Set;
+ let proto: Doc | undefined = doc;
+ while (proto) {
+ Object.keys(proto).forEach(key => results.add(key));
+ proto = proto.proto;
+ }
+
+ return Array.from(results);
+ }
+
+
+ export function MakeAlias(doc: Doc) {
+ const alias = new Doc;
+ if (!GetT(doc, "isPrototype", "boolean", true)) {
+ alias.proto = doc.proto;
+ }
return alias;
}
@@ -182,42 +241,14 @@ export namespace Doc {
return copy;
}
- export function MakeLink(source: Doc, target: Doc) {
- UndoManager.RunInBatch(() => {
- let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1 });
- //let linkDoc = new Doc;
- linkDoc.title = "-link name-";
- linkDoc.linkDescription = "";
- linkDoc.linkTags = "Default";
-
- linkDoc.linkedTo = target;
- linkDoc.linkedFrom = source;
-
- let linkedFrom = Cast(target.linkedFromDocs, listSpec(Doc));
- if (!linkedFrom) {
- target.linkedFromDocs = linkedFrom = new List<Doc>();
- }
- linkedFrom.push(linkDoc);
-
- let linkedTo = Cast(source.linkedToDocs, listSpec(Doc));
- if (!linkedTo) {
- source.linkedToDocs = linkedTo = new List<Doc>();
- }
- linkedTo.push(linkDoc);
- return linkDoc;
- }, "make link");
- }
-
- export function MakeDelegate(doc: Doc): Doc;
- export function MakeDelegate(doc: Opt<Doc>): Opt<Doc>;
- export function MakeDelegate(doc: Opt<Doc>): Opt<Doc> {
+ export function MakeDelegate(doc: Doc, id?: string): Doc;
+ export function MakeDelegate(doc: Opt<Doc>, id?: string): Opt<Doc>;
+ export function MakeDelegate(doc: Opt<Doc>, id?: string): Opt<Doc> {
if (!doc) {
return undefined;
}
- const delegate = new Doc();
- //TODO Does this need to be doc[Self]?
+ const delegate = new Doc(id, true);
delegate.proto = doc;
return delegate;
}
- export const Prototype = Symbol("Prototype");
} \ No newline at end of file
diff --git a/src/new_fields/FieldSymbols.ts b/src/new_fields/FieldSymbols.ts
new file mode 100644
index 000000000..a436dcf2b
--- /dev/null
+++ b/src/new_fields/FieldSymbols.ts
@@ -0,0 +1,10 @@
+
+export const Update = Symbol("Update");
+export const Self = Symbol("Self");
+export const SelfProxy = Symbol("SelfProxy");
+export const HandleUpdate = Symbol("HandleUpdate");
+export const Id = Symbol("Id");
+export const OnUpdate = Symbol("OnUpdate");
+export const Parent = Symbol("Parent");
+export const Copy = Symbol("Copy");
+export const ToScriptString = Symbol("Copy"); \ No newline at end of file
diff --git a/src/new_fields/HtmlField.ts b/src/new_fields/HtmlField.ts
index d998746bb..f952acff9 100644
--- a/src/new_fields/HtmlField.ts
+++ b/src/new_fields/HtmlField.ts
@@ -1,6 +1,7 @@
import { Deserializable } from "../client/util/SerializationHelper";
import { serializable, primitive } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString } from "./FieldSymbols";
@Deserializable("html")
export class HtmlField extends ObjectField {
@@ -15,4 +16,8 @@ export class HtmlField extends ObjectField {
[Copy]() {
return new HtmlField(this.html);
}
+
+ [ToScriptString]() {
+ return "invalid";
+ }
}
diff --git a/src/new_fields/IconField.ts b/src/new_fields/IconField.ts
index 1a928389d..62b2cd254 100644
--- a/src/new_fields/IconField.ts
+++ b/src/new_fields/IconField.ts
@@ -1,6 +1,7 @@
import { Deserializable } from "../client/util/SerializationHelper";
import { serializable, primitive } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString } from "./FieldSymbols";
@Deserializable("icon")
export class IconField extends ObjectField {
@@ -15,4 +16,8 @@ export class IconField extends ObjectField {
[Copy]() {
return new IconField(this.icon);
}
+
+ [ToScriptString]() {
+ return "invalid";
+ }
}
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index 86a8bd18a..4e3b7abe0 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -1,8 +1,7 @@
import { Deserializable } from "../client/util/SerializationHelper";
import { serializable, custom, createSimpleSchema, list, object, map } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
-import { number } from "prop-types";
-import { any } from "bluebird";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString } from "./FieldSymbols";
import { deepCopy } from "../Utils";
export enum InkTool {
@@ -11,6 +10,7 @@ export enum InkTool {
Highlighter,
Eraser
}
+
export interface StrokeData {
pathData: Array<{ x: number, y: number }>;
color: string;
@@ -39,6 +39,10 @@ export class InkField extends ObjectField {
}
[Copy]() {
- return new InkField(deepCopy(this.inkData))
+ return new InkField(deepCopy(this.inkData));
+ }
+
+ [ToScriptString]() {
+ return "invalid";
}
}
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
index 96018dafa..f1e4c4721 100644
--- a/src/new_fields/List.ts
+++ b/src/new_fields/List.ts
@@ -1,11 +1,12 @@
import { Deserializable, autoObject } from "../client/util/SerializationHelper";
-import { Field, Update, Self, FieldResult } from "./Doc";
-import { setter, getter, deleteProperty } from "./util";
+import { Field } from "./Doc";
+import { setter, getter, deleteProperty, updateFunction } from "./util";
import { serializable, alias, list } from "serializr";
import { observable, action } from "mobx";
-import { ObjectField, OnUpdate, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
import { RefField } from "./RefField";
import { ProxyField } from "./Proxy";
+import { Self, Update, Parent, OnUpdate, SelfProxy, ToScriptString, Copy } from "./FieldSymbols";
const listHandlers: any = {
/// Mutator methods
@@ -27,7 +28,17 @@ const listHandlers: any = {
},
push: action(function (this: any, ...items: any[]) {
items = items.map(toObjectField);
- const res = this[Self].__fields.push(...items);
+ const list = this[Self];
+ const length = list.__fields.length;
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ //TODO Error checking to make sure parent doesn't already exist
+ if (item instanceof ObjectField) {
+ item[Parent] = list;
+ item[OnUpdate] = updateFunction(list, i + length, item, this);
+ }
+ }
+ const res = list.__fields.push(...items);
this[Update]();
return res;
}),
@@ -48,12 +59,33 @@ const listHandlers: any = {
},
splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) {
items = items.map(toObjectField);
- const res = this[Self].__fields.splice(start, deleteCount, ...items);
+ const list = this[Self];
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ //TODO Error checking to make sure parent doesn't already exist
+ //TODO Need to change indices of other fields in array
+ if (item instanceof ObjectField) {
+ item[Parent] = list;
+ item[OnUpdate] = updateFunction(list, i + start, item, this);
+ }
+ }
+ const res = list.__fields.splice(start, deleteCount, ...items);
this[Update]();
return res.map(toRealField);
}),
unshift(...items: any[]) {
items = items.map(toObjectField);
+ const list = this[Self];
+ const length = list.__fields.length;
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ //TODO Error checking to make sure parent doesn't already exist
+ //TODO Need to change indices of other fields in array
+ if (item instanceof ObjectField) {
+ item[Parent] = list;
+ item[OnUpdate] = updateFunction(list, i, item, this);
+ }
+ }
const res = this[Self].__fields.unshift(...items);
this[Update]();
return res;
@@ -194,19 +226,32 @@ type StoredType<T extends Field> = T extends RefField ? ProxyField<T> : T;
@Deserializable("list")
class ListImpl<T extends Field> extends ObjectField {
- constructor(fields: T[] = []) {
+ constructor(fields?: T[]) {
super();
const list = new Proxy<this>(this, {
set: setter,
get: listGetter,
+ ownKeys: target => Object.keys(target.__fields),
+ getOwnPropertyDescriptor: (target, prop) => {
+ if (prop in target.__fields) {
+ return {
+ configurable: true,//TODO Should configurable be true?
+ enumerable: true,
+ };
+ }
+ return Reflect.getOwnPropertyDescriptor(target, prop);
+ },
deleteProperty: deleteProperty,
defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
});
- (list as any).push(...fields);
+ this[SelfProxy] = list;
+ if (fields) {
+ (list as any).push(...fields);
+ }
return list;
}
- [key: number]: FieldResult<T>;
+ [key: number]: T | (T extends RefField ? Promise<T> : never);
@serializable(alias("fields", list(autoObject())))
private get __fields() {
@@ -215,6 +260,12 @@ class ListImpl<T extends Field> extends ObjectField {
private set __fields(value) {
this.___fields = value;
+ for (const key in value) {
+ const field = value[key];
+ if (!(field instanceof ObjectField)) continue;
+ (field as ObjectField)[Parent] = this[Self];
+ (field as ObjectField)[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
+ }
}
[Copy]() {
@@ -235,6 +286,12 @@ class ListImpl<T extends Field> extends ObjectField {
}
private [Self] = this;
+ private [SelfProxy]: any;
+
+ [ToScriptString]() {
+ return "invalid";
+ // return `new List([${(this as any).map((field => Field.toScriptString(field))}])`;
+ }
}
-export type List<T extends Field> = ListImpl<T> & T[];
+export type List<T extends Field> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
export const List: { new <T extends Field>(fields?: T[]): List<T> } = ListImpl as any; \ No newline at end of file
diff --git a/src/new_fields/ObjectField.ts b/src/new_fields/ObjectField.ts
index 0f3777af6..5f4a6f8fb 100644
--- a/src/new_fields/ObjectField.ts
+++ b/src/new_fields/ObjectField.ts
@@ -1,13 +1,13 @@
import { Doc } from "./Doc";
-
-export const OnUpdate = Symbol("OnUpdate");
-export const Parent = Symbol("Parent");
-export const Copy = Symbol("Copy");
+import { RefField } from "./RefField";
+import { OnUpdate, Parent, Copy, ToScriptString } from "./FieldSymbols";
export abstract class ObjectField {
- protected [OnUpdate]?: (diff?: any) => void;
- private [Parent]?: Doc;
+ protected [OnUpdate](diff?: any) { }
+ private [Parent]?: RefField | ObjectField;
abstract [Copy](): ObjectField;
+
+ abstract [ToScriptString](): string;
}
export namespace ObjectField {
diff --git a/src/new_fields/Proxy.ts b/src/new_fields/Proxy.ts
index fd99ae1c0..130ec066e 100644
--- a/src/new_fields/Proxy.ts
+++ b/src/new_fields/Proxy.ts
@@ -3,8 +3,9 @@ import { FieldWaiting } from "./Doc";
import { primitive, serializable } from "serializr";
import { observable, action } from "mobx";
import { DocServer } from "../client/DocServer";
-import { RefField, Id } from "./RefField";
-import { ObjectField, Copy } from "./ObjectField";
+import { RefField } from "./RefField";
+import { ObjectField } from "./ObjectField";
+import { Id, Copy, ToScriptString } from "./FieldSymbols";
@Deserializable("proxy")
export class ProxyField<T extends RefField> extends ObjectField {
@@ -26,6 +27,10 @@ export class ProxyField<T extends RefField> extends ObjectField {
return new ProxyField<T>(this.fieldId);
}
+ [ToScriptString]() {
+ return "invalid";
+ }
+
@serializable(primitive())
readonly fieldId: string = "";
diff --git a/src/new_fields/RefField.ts b/src/new_fields/RefField.ts
index 202c65f21..75ce4287f 100644
--- a/src/new_fields/RefField.ts
+++ b/src/new_fields/RefField.ts
@@ -1,9 +1,8 @@
import { serializable, primitive, alias } from "serializr";
import { Utils } from "../Utils";
+import { Id, HandleUpdate, ToScriptString } from "./FieldSymbols";
export type FieldId = string;
-export const HandleUpdate = Symbol("HandleUpdate");
-export const Id = Symbol("Id");
export abstract class RefField {
@serializable(alias("id", primitive()))
private __id: FieldId;
@@ -15,4 +14,6 @@ export abstract class RefField {
}
protected [HandleUpdate]?(diff: any): void;
+
+ abstract [ToScriptString](): string;
}
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index eb30e76de..89d077a47 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -1,6 +1,7 @@
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
import { serializable } from "serializr";
import { Deserializable } from "../client/util/SerializationHelper";
+import { Copy, ToScriptString } from "./FieldSymbols";
@Deserializable("RichTextField")
export class RichTextField extends ObjectField {
@@ -15,4 +16,8 @@ export class RichTextField extends ObjectField {
[Copy]() {
return new RichTextField(this.Data);
}
+
+ [ToScriptString]() {
+ return "invalid";
+ }
} \ No newline at end of file
diff --git a/src/new_fields/Schema.ts b/src/new_fields/Schema.ts
index b821baec9..250f3c975 100644
--- a/src/new_fields/Schema.ts
+++ b/src/new_fields/Schema.ts
@@ -1,4 +1,4 @@
-import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType } from "./Types";
+import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType, DefaultFieldConstructor } from "./Types";
import { Doc, Field } from "./Doc";
type AllToInterface<T extends Interface[]> = {
@@ -10,7 +10,7 @@ export const emptySchema = createSchema({});
export const Document = makeInterface(emptySchema);
export type Document = makeInterface<[typeof emptySchema]>;
-export type makeInterface<T extends Interface[]> = Partial<AllToInterface<T>> & Doc & { proto: Doc | undefined };
+export type makeInterface<T extends Interface[]> = AllToInterface<T> & Doc & { proto: Doc | undefined };
// export function makeInterface<T extends Interface[], U extends Doc>(schemas: T): (doc: U) => All<T, U>;
// export function makeInterface<T extends Interface, U extends Doc>(schema: T): (doc: U) => makeInterface<T, U>;
export function makeInterface<T extends Interface[]>(...schemas: T): (doc?: Doc) => makeInterface<T> {
@@ -24,7 +24,12 @@ export function makeInterface<T extends Interface[]>(...schemas: T): (doc?: Doc)
get(target: any, prop, receiver) {
const field = receiver.doc[prop];
if (prop in schema) {
- return Cast(field, (schema as any)[prop]);
+ const desc = (schema as any)[prop];
+ if (typeof desc === "object" && "defaultVal" in desc && "type" in desc) {
+ return Cast(field, desc.type, desc.defaultVal);
+ } else {
+ return Cast(field, (schema as any)[prop]);
+ }
}
return field;
},
@@ -79,4 +84,11 @@ export function createSchema<T extends Interface>(schema: T): T & { proto: ToCon
export function listSpec<U extends ToConstructor<Field>>(type: U): ListSpec<ToType<U>> {
return { List: type as any };//TODO Types
+}
+
+export function defaultSpec<T extends ToConstructor<Field>>(type: T, defaultVal: ToType<T>): DefaultFieldConstructor<ToType<T>> {
+ return {
+ type: type as any,
+ defaultVal
+ };
} \ No newline at end of file
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
index 60f08dc90..c04dd5e6d 100644
--- a/src/new_fields/Types.ts
+++ b/src/new_fields/Types.ts
@@ -1,12 +1,14 @@
-import { Field, Opt, FieldResult } from "./Doc";
+import { Field, Opt, FieldResult, Doc } from "./Doc";
import { List } from "./List";
+import { RefField } from "./RefField";
-export type ToType<T extends ToConstructor<Field> | ListSpec<Field>> =
+export type ToType<T extends ToConstructor<Field> | ListSpec<Field> | DefaultFieldConstructor<Field>> =
T extends "string" ? string :
T extends "number" ? number :
T extends "boolean" ? boolean :
T extends ListSpec<infer U> ? List<U> :
// T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never;
+ T extends DefaultFieldConstructor<infer _U> ? never :
T extends { new(...args: any[]): List<Field> } ? never :
T extends { new(...args: any[]): infer R } ? R : never;
@@ -18,12 +20,17 @@ export type ToConstructor<T extends Field> =
new (...args: any[]) => T;
export type ToInterface<T extends Interface> = {
- [P in Exclude<keyof T, "proto">]: FieldResult<ToType<T[P]>>;
+ [P in Exclude<keyof T, "proto">]: T[P] extends DefaultFieldConstructor<infer F> ? Exclude<FieldResult<F>, undefined> : FieldResult<ToType<T[P]>>;
};
// type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> };
export type ListSpec<T extends Field> = { List: ToConstructor<T> };
+export type DefaultFieldConstructor<T extends Field> = {
+ type: ToConstructor<T>,
+ defaultVal: T
+};
+
// type ListType<U extends Field[]> = { 0: List<ListType<Tail<U>>>, 1: ToType<Head<U>> }[HasTail<U> extends true ? 0 : 1];
export type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
@@ -33,7 +40,7 @@ export type HasTail<T extends any[]> = T extends ([] | [any]) ? false : true;
//TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial
export interface Interface {
- [key: string]: ToConstructor<Field> | ListSpec<Field>;
+ [key: string]: ToConstructor<Field> | ListSpec<Field> | DefaultFieldConstructor<Field>;
// [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
}
@@ -71,7 +78,7 @@ export function BoolCast(field: FieldResult, defaultVal: boolean | null = null)
return Cast(field, "boolean", defaultVal);
}
-type WithoutList<T extends Field> = T extends List<infer R> ? R[] : T;
+type WithoutList<T extends Field> = T extends List<infer R> ? (R extends RefField ? (R | Promise<R>)[] : R[]) : T;
export function FieldValue<T extends Field, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>;
export function FieldValue<T extends Field>(field: FieldResult<T>): Opt<T>;
diff --git a/src/new_fields/URLField.ts b/src/new_fields/URLField.ts
index d00a95a16..4a2841fb6 100644
--- a/src/new_fields/URLField.ts
+++ b/src/new_fields/URLField.ts
@@ -1,6 +1,7 @@
import { Deserializable } from "../client/util/SerializationHelper";
import { serializable, custom } from "serializr";
-import { ObjectField, Copy } from "./ObjectField";
+import { ObjectField } from "./ObjectField";
+import { ToScriptString, Copy } from "./FieldSymbols";
function url() {
return custom(
@@ -13,15 +14,24 @@ function url() {
);
}
-export class URLField extends ObjectField {
+export abstract class URLField extends ObjectField {
@serializable(url())
readonly url: URL;
- constructor(url: URL) {
+ constructor(url: string);
+ constructor(url: URL);
+ constructor(url: URL | string) {
super();
+ if (typeof url === "string") {
+ url = new URL(url);
+ }
this.url = url;
}
+ [ToScriptString]() {
+ return `new ${this.constructor.name}("${this.url.href}")`;
+ }
+
[Copy](): this {
return new (this.constructor as any)(this.url);
}
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index bbd8157f6..2b304c373 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -1,11 +1,12 @@
import { UndoManager } from "../client/util/UndoManager";
-import { Update, Doc, Field } from "./Doc";
+import { Doc, Field } from "./Doc";
import { SerializationHelper } from "../client/util/SerializationHelper";
import { ProxyField } from "./Proxy";
import { FieldValue } from "./Types";
-import { RefField, Id } from "./RefField";
-import { ObjectField, Parent, OnUpdate } from "./ObjectField";
+import { RefField } from "./RefField";
+import { ObjectField } from "./ObjectField";
import { action } from "mobx";
+import { Parent, OnUpdate, Update, Id } from "./FieldSymbols";
export const setter = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
if (SerializationHelper.IsSerializing()) {
@@ -37,7 +38,11 @@ export const setter = action(function (target: any, prop: string | symbol | numb
delete curValue[Parent];
delete curValue[OnUpdate];
}
- target.__fields[prop] = value;
+ if (value === undefined) {
+ delete target.__fields[prop];
+ } else {
+ target.__fields[prop] = value;
+ }
target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } });
UndoManager.AddEvent({
redo: () => receiver[prop] = value,
@@ -55,23 +60,30 @@ export function getter(target: any, prop: string | symbol | number, receiver: an
}
return getField(target, prop);
}
+function getProtoField(protoField: Doc | undefined, prop: string | number, cb?: (field: Field | undefined) => void) {
+ if (!protoField) return undefined;
+ let field = protoField[prop];
+ if (field instanceof Promise) {
+ cb && field.then(cb);
+ return field;
+ } else {
+ cb && cb(field);
+ return field;
+ }
+}
+//TODO The callback parameter is never being passed in currently, so we should be able to get rid of it.
export function getField(target: any, prop: string | number, ignoreProto: boolean = false, callback?: (field: Field | undefined) => void): any {
const field = target.__fields[prop];
if (field instanceof ProxyField) {
return field.value(callback);
}
- if (field === undefined && !ignoreProto) {
+ if (field === undefined && !ignoreProto && prop !== "proto") {
const proto = getField(target, "proto", true);
if (proto instanceof Doc) {
- let field = proto[prop];
- if (field instanceof Promise) {
- callback && field.then(callback);
- return undefined;
- } else {
- callback && callback(field);
- return field;
- }
+ return getProtoField(proto, prop, callback);
+ } else if (proto instanceof Promise) {
+ return proto.then(async proto => getProtoField(proto, prop, callback));
}
}
callback && callback(field);