import { observable, action } from "mobx"; import { serializable, primitive, map, alias, list } from "serializr"; import { autoObject, SerializationHelper, Deserializable } from "../client/util/SerializationHelper"; import { Utils } from "../Utils"; import { DocServer } from "../client/DocServer"; import { setter, getter, getField } from "./util"; import { Cast, ToConstructor, PromiseValue, FieldValue } from "./Types"; import { UndoManager, undoBatch } from "../client/util/UndoManager"; import { listSpec } from "./Schema"; import { List } from "./List"; import { ObjectField } from "./ObjectField"; import { RefField, FieldId, Id } from "./RefField"; export function IsField(field: any): field is Field { return (typeof field === "string") || (typeof field === "number") || (typeof field === "boolean") || (field instanceof ObjectField) || (field instanceof RefField); } export type Field = number | string | boolean | ObjectField | RefField; export type Opt = T | undefined; export type FieldWaiting = T extends undefined ? never : Promise; export type FieldResult = Opt | FieldWaiting>; export const Update = Symbol("Update"); export const Self = Symbol("Self"); @Deserializable("doc").withFields(["id"]) export class Doc extends RefField { constructor(id?: FieldId, forceSave?: boolean) { super(id); const doc = new Proxy(this, { set: setter, get: getter, ownKeys: target => Object.keys(target.__fields), deleteProperty: () => { throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); }, defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); }, }); if (!id || forceSave) { DocServer.CreateField(SerializationHelper.Serialize(doc)); } return doc; } proto: Opt; [key: string]: FieldResult; @serializable(alias("fields", map(autoObject()))) @observable //{ [key: string]: Field | FieldWaiting | undefined } private __fields: any = {}; private [Update] = (diff: any) => { DocServer.UpdateField(this[Id], diff); } private [Self] = this; } export namespace Doc { // export function GetAsync(doc: Doc, key: string, ignoreProto: boolean = false): Promise { // const self = doc[Self]; // return new Promise(res => getField(self, key, ignoreProto, res)); // } // export function GetTAsync(doc: Doc, key: string, ctor: ToConstructor, ignoreProto: boolean = false): Promise { // return new Promise(async res => { // const field = await GetAsync(doc, key, ignoreProto); // return Cast(field, ctor); // }); // } export function Get(doc: Doc, key: string, ignoreProto: boolean = false): FieldResult { const self = doc[Self]; return getField(self, key, ignoreProto); } export function GetT(doc: Doc, key: string, ctor: ToConstructor, ignoreProto: boolean = false): T | null | undefined { return Cast(Get(doc, key, ignoreProto), ctor) as T | null | undefined; } export async function SetOnPrototype(doc: Doc, key: string, value: Field) { const proto = doc.proto; if (proto) { proto[key] = value; } } export function GetAllPrototypes(doc: Doc): Doc[] { const protos: Doc[] = []; let d: Opt = doc; while (d) { protos.push(d); d = FieldValue(d.proto); } return protos; } export function assign(doc: Doc, fields: Partial>>) { for (const key in fields) { if (fields.hasOwnProperty(key)) { const value = fields[key]; if (value !== undefined) { doc[key] = value; } } } return doc; } export function MakeAlias(doc: Doc) { const alias = new Doc; PromiseValue(Cast(doc.proto, Doc)).then(proto => { if (proto) { alias.proto = proto; } }); return alias; } export function MakeCopy(doc: Doc, copyProto: boolean = false): Doc { const copy = new Doc; Object.keys(doc).forEach(async key => { const field = await doc[key]; if (key === "proto" && copyProto) { if (field instanceof Doc) { copy[key] = Doc.MakeCopy(field); } } else { if (field instanceof RefField) { copy[key] = field; } else if (field instanceof ObjectField) { copy[key] = ObjectField.MakeCopy(field); } else { copy[key] = field; } } }); return copy; } export function MakeLink(source: Doc, target: Doc): Doc { let linkDoc = new Doc; UndoManager.RunInBatch(() => { linkDoc.title = "New Link"; 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(); } linkedFrom.push(linkDoc); let linkedTo = Cast(source.linkedToDocs, listSpec(Doc)); if (!linkedTo) { source.linkedToDocs = linkedTo = new List(); } linkedTo.push(linkDoc); }, "make link"); return linkDoc; } export function MakeDelegate(doc: Doc): Doc; export function MakeDelegate(doc: Opt): Opt; export function MakeDelegate(doc: Opt): Opt { if (!doc) { return undefined; } const delegate = new Doc(); //TODO Does this need to be doc[Self]? delegate.proto = doc; return delegate; } export const Prototype = Symbol("Prototype"); }