diff options
Diffstat (limited to 'src/new_fields')
| -rw-r--r-- | src/new_fields/CursorField.ts | 63 | ||||
| -rw-r--r-- | src/new_fields/DateField.ts | 23 | ||||
| -rw-r--r-- | src/new_fields/Doc.ts | 157 | ||||
| -rw-r--r-- | src/new_fields/FieldSymbols.ts | 10 | ||||
| -rw-r--r-- | src/new_fields/HtmlField.ts | 7 | ||||
| -rw-r--r-- | src/new_fields/IconField.ts | 7 | ||||
| -rw-r--r-- | src/new_fields/InkField.ts | 12 | ||||
| -rw-r--r-- | src/new_fields/List.ts | 75 | ||||
| -rw-r--r-- | src/new_fields/ObjectField.ts | 12 | ||||
| -rw-r--r-- | src/new_fields/Proxy.ts | 9 | ||||
| -rw-r--r-- | src/new_fields/RefField.ts | 5 | ||||
| -rw-r--r-- | src/new_fields/RichTextField.ts | 7 | ||||
| -rw-r--r-- | src/new_fields/Schema.ts | 18 | ||||
| -rw-r--r-- | src/new_fields/Types.ts | 17 | ||||
| -rw-r--r-- | src/new_fields/URLField.ts | 16 | ||||
| -rw-r--r-- | src/new_fields/util.ts | 38 |
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); |
