diff options
Diffstat (limited to 'src/new_fields')
| -rw-r--r-- | src/new_fields/CursorField.ts | 16 | ||||
| -rw-r--r-- | src/new_fields/DateField.ts | 7 | ||||
| -rw-r--r-- | src/new_fields/Doc.ts | 135 | ||||
| -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 | 7 | ||||
| -rw-r--r-- | src/new_fields/List.ts | 16 | ||||
| -rw-r--r-- | src/new_fields/ObjectField.ts | 9 | ||||
| -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 | 48 | ||||
| -rw-r--r-- | src/new_fields/Types.ts | 16 | ||||
| -rw-r--r-- | src/new_fields/URLField.ts | 16 | ||||
| -rw-r--r-- | src/new_fields/util.ts | 41 |
16 files changed, 234 insertions, 122 deletions
diff --git a/src/new_fields/CursorField.ts b/src/new_fields/CursorField.ts index fc144222c..fd86031a8 100644 --- a/src/new_fields/CursorField.ts +++ b/src/new_fields/CursorField.ts @@ -1,7 +1,8 @@ -import { ObjectField, Copy, OnUpdate } from "./ObjectField"; +import { ObjectField } from "./ObjectField"; import { observable } from "mobx"; import { Deserializable } from "../client/util/SerializationHelper"; -import { serializable, createSimpleSchema, object } from "serializr"; +import { serializable, createSimpleSchema, object, date } from "serializr"; +import { OnUpdate, ToScriptString, Copy } from "./FieldSymbols"; export type CursorPosition = { x: number, @@ -10,7 +11,8 @@ export type CursorPosition = { export type CursorMetadata = { id: string, - identifier: string + identifier: string, + timestamp: number }; export type CursorData = { @@ -25,7 +27,8 @@ const PositionSchema = createSimpleSchema({ const MetadataSchema = createSimpleSchema({ id: true, - identifier: true + identifier: true, + timestamp: true }); const CursorSchema = createSimpleSchema({ @@ -46,10 +49,15 @@ export default class CursorField extends ObjectField { 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 index c0a79f267..fc8abb9d9 100644 --- a/src/new_fields/DateField.ts +++ b/src/new_fields/DateField.ts @@ -1,6 +1,7 @@ import { Deserializable } from "../client/util/SerializationHelper"; import { serializable, date } from "serializr"; -import { ObjectField, Copy } from "./ObjectField"; +import { ObjectField } from "./ObjectField"; +import { Copy, ToScriptString } from "./FieldSymbols"; @Deserializable("date") export class DateField extends ObjectField { @@ -15,4 +16,8 @@ export class DateField extends ObjectField { [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 7b86cd8b1..7e02a5bc5 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -4,31 +4,37 @@ 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"); -export const SelfProxy = Symbol("SelfProxy"); -export const WidthSym = Symbol("Width"); -export const HeightSym = Symbol("Height"); - /** * 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. @@ -42,10 +48,13 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) { return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue); } -export function DocListCast(field: FieldResult) { - return Cast(field, listSpec(Doc), []).filter(d => d && d instanceof Doc).map(d => d as Doc); +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"); + @Deserializable("doc").withFields(["id"]) export class Doc extends RefField { constructor(id?: FieldId, forceSave?: boolean) { @@ -53,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) => { @@ -60,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); @@ -105,6 +116,10 @@ export class Doc extends RefField { 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) { const set = diff.$set; if (set) { @@ -138,8 +153,11 @@ export namespace Doc { 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 = Object.getOwnPropertyNames(doc).indexOf("isPrototype") == -1 ? doc.proto : doc; + const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : doc; if (proto) { proto[key] = value; @@ -169,27 +187,35 @@ export namespace Doc { // compare whether documents or their protos match export function AreProtosEqual(doc: Doc, other: Doc) { - let r = (doc[Id] === other[Id]); - let r2 = (doc.proto && doc.proto.Id === other[Id]); - let r3 = (other.proto && other.proto.Id === doc[Id]); - let r4 = (doc.proto && other.proto && doc.proto[Id] === other.proto[Id]); - return r || r2 || r3 || r4 ? true : false; + 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; } - export function MakeAlias(doc: Doc) { - const alias = new Doc; + // 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!; + } - if (!doc.proto) { - alias.proto = doc; - } else { - PromiseValue(Cast(doc.proto, Doc)).then(proto => { - if (proto) { - alias.proto = 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 alias; + return Array.from(results); + } + + export function MakeAlias(doc: Doc) { + if (!GetT(doc, "isPrototype", "boolean", true)) { + return Doc.MakeCopy(doc); + } + return new Doc; } export function MakeCopy(doc: Doc, copyProto: boolean = false): Doc { @@ -213,41 +239,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.proto!.title = "-link name-"; - linkDoc.proto!.linkDescription = ""; - linkDoc.proto!.linkTags = "Default"; - - linkDoc.proto!.linkedTo = target; - linkDoc.proto!.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(); + 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 2d75f8a19..4e3b7abe0 100644 --- a/src/new_fields/InkField.ts +++ b/src/new_fields/InkField.ts @@ -1,6 +1,7 @@ import { Deserializable } from "../client/util/SerializationHelper"; import { serializable, custom, createSimpleSchema, list, object, map } from "serializr"; -import { ObjectField, Copy } from "./ObjectField"; +import { ObjectField } from "./ObjectField"; +import { Copy, ToScriptString } from "./FieldSymbols"; import { deepCopy } from "../Utils"; export enum InkTool { @@ -40,4 +41,8 @@ export class InkField extends ObjectField { [Copy]() { return new InkField(deepCopy(this.inkData)); } + + [ToScriptString]() { + return "invalid"; + } } diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts index 70e36f911..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, SelfProxy } from "./Doc"; +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, Parent } 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 @@ -225,7 +226,7 @@ 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, @@ -244,7 +245,9 @@ class ListImpl<T extends Field> extends ObjectField { defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); }, }); this[SelfProxy] = list; - (list as any).push(...fields); + if (fields) { + (list as any).push(...fields); + } return list; } @@ -284,6 +287,11 @@ 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 | (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 f276bfa67..5f4a6f8fb 100644 --- a/src/new_fields/ObjectField.ts +++ b/src/new_fields/ObjectField.ts @@ -1,14 +1,13 @@ import { Doc } from "./Doc"; import { RefField } from "./RefField"; - -export const OnUpdate = Symbol("OnUpdate"); -export const Parent = Symbol("Parent"); -export const Copy = Symbol("Copy"); +import { OnUpdate, Parent, Copy, ToScriptString } from "./FieldSymbols"; export abstract class ObjectField { - protected [OnUpdate](diff?: any) { }; + 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..40ffaecd5 100644 --- a/src/new_fields/Schema.ts +++ b/src/new_fields/Schema.ts @@ -1,5 +1,7 @@ -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"; +import { ObjectField } from "./ObjectField"; +import { RefField } from "./RefField"; type AllToInterface<T extends Interface[]> = { 1: ToInterface<Head<T>> & AllToInterface<Tail<T>>, @@ -10,10 +12,16 @@ 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 interface InterfaceFunc<T extends Interface[]> { + (docs: Doc[]): makeInterface<T>[]; + (): makeInterface<T>; + (doc: Doc): makeInterface<T>; +} + +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> { +export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFunc<T> { let schema: Interface = {}; for (const s of schemas) { for (const key in s) { @@ -24,7 +32,21 @@ 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) {//defaultSpec + return Cast(field, desc.type, desc.defaultVal); + } else if (typeof desc === "function" && !ObjectField.isPrototypeOf(desc) && !RefField.isPrototypeOf(desc)) { + const doc = Cast(field, Doc); + if (doc === undefined) { + return undefined; + } else if (doc instanceof Doc) { + return desc(doc); + } else { + return doc.then(doc => doc && desc(doc)); + } + } else { + return Cast(field, desc); + } } return field; }, @@ -33,14 +55,21 @@ export function makeInterface<T extends Interface[]>(...schemas: T): (doc?: Doc) return true; } }); - return function (doc?: Doc) { - doc = doc || new Doc; + const fn = (doc: Doc) => { 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; }; + return function (doc?: Doc | Doc[]) { + doc = doc || new Doc; + if (doc instanceof Doc) { + return fn(doc); + } else { + return doc.map(fn); + } + }; } export type makeStrictInterface<T extends Interface> = Partial<ToInterface<T>>; @@ -79,4 +108,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 4b4c58eb8..8dd893aa4 100644 --- a/src/new_fields/Types.ts +++ b/src/new_fields/Types.ts @@ -2,14 +2,16 @@ 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 InterfaceValue> = 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; + T extends { new(...args: any[]): infer R } ? R : + T extends (doc?: Doc) => infer R ? R : never; export type ToConstructor<T extends Field> = T extends string ? "string" : @@ -19,12 +21,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; @@ -32,9 +39,10 @@ export type Tail<T extends any[]> = ((...t: T) => any) extends ((_: any, ...tail: infer TT) => any) ? TT : []; export type HasTail<T extends any[]> = T extends ([] | [any]) ? false : true; +export type InterfaceValue = ToConstructor<Field> | ListSpec<Field> | DefaultFieldConstructor<Field> | ((doc?: Doc) => any); //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]: InterfaceValue; // [key: string]: ToConstructor<Field> | ListSpec<Field[]>; } 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 a5f5e368b..8cb1db953 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -1,11 +1,13 @@ 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, SelfProxy } from "./FieldSymbols"; +import { ComputedField } from "../fields/ScriptField"; export const setter = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean { if (SerializationHelper.IsSerializing()) { @@ -37,8 +39,13 @@ export const setter = action(function (target: any, prop: string | symbol | numb delete curValue[Parent]; delete curValue[OnUpdate]; } - target.__fields[prop] = value; - target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } }); + if (value === undefined) { + delete target.__fields[prop]; + } else { + target.__fields[prop] = value; + } + if (value === undefined) target[Update]({ '$unset': { ["fields." + prop]: "" } }); + else target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } }); UndoManager.AddEvent({ redo: () => receiver[prop] = value, undo: () => receiver[prop] = curValue @@ -56,26 +63,21 @@ export function getter(target: any, prop: string | symbol | number, receiver: an return getField(target, prop); } -//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 { +export function getField(target: any, prop: string | number, ignoreProto: boolean = false): any { const field = target.__fields[prop]; if (field instanceof ProxyField) { - return field.value(callback); + return field.value(); + } + if (field instanceof ComputedField) { + return field.value; } - 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 proto[prop]; } + return undefined; } - callback && callback(field); return field; } @@ -84,7 +86,8 @@ export function deleteProperty(target: any, prop: string | number | symbol) { delete target[prop]; return true; } - throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); + target[SelfProxy][prop] = undefined; + return true; } export function updateFunction(target: any, prop: any, value: any, receiver: any) { |
