aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTyler Schicke <tyler_schicke@brown.edu>2019-04-19 05:40:10 -0400
committerTyler Schicke <tyler_schicke@brown.edu>2019-04-19 05:40:10 -0400
commitecae4ae106be3e07471208cb93ec0965548d2d12 (patch)
tree5debe1190de60955e4bf3b97f1405f2623731860 /src
parentbe5d2d30bdd98dfc32c28a84ad606eb2b4599932 (diff)
Added decent amount of list support
Diffstat (limited to 'src')
-rw-r--r--src/debug/Test.tsx8
-rw-r--r--src/fields/NewDoc.ts190
2 files changed, 111 insertions, 87 deletions
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 033615be6..6a677f80f 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -66,6 +66,14 @@ class Test extends React.Component {
assert(test2.testDoc === undefined);
test2.url = 35;
assert(test2.url === 35);
+ const l = new List<number>();
+ //TODO push, and other array functions don't go through the proxy
+ l.push(1);
+ //TODO currently length, and any other string fields will get serialized
+ l.length = 3;
+ l[2] = 5;
+ console.log(l.slice());
+ console.log(SerializationHelper.Serialize(l));
}
render() {
diff --git a/src/fields/NewDoc.ts b/src/fields/NewDoc.ts
index 7be0d5146..c22df4b70 100644
--- a/src/fields/NewDoc.ts
+++ b/src/fields/NewDoc.ts
@@ -1,6 +1,6 @@
import { observable, action } from "mobx";
import { UndoManager } from "../client/util/UndoManager";
-import { serializable, primitive, map, alias } from "serializr";
+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";
@@ -21,9 +21,10 @@ export abstract class RefField {
}
const Update = Symbol("Update");
+const OnUpdate = Symbol("OnUpdate");
const Parent = Symbol("Parent");
export class ObjectField {
- protected [Update]?: (diff?: any) => void;
+ protected [OnUpdate]?: (diff?: any) => void;
private [Parent]?: Doc;
}
@@ -107,92 +108,112 @@ export const FieldWaiting: FieldWaiting = null;
const Self = Symbol("Self");
-export class List<T extends Field> extends ObjectField {
- [index: number]: T;
+function setter(target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
+ if (SerializationHelper.IsSerializing()) {
+ target[prop] = value;
+ return true;
+ }
+ if (typeof prop === "symbol") {
+ target[prop] = value;
+ return true;
+ }
+ const curValue = target.__fields[prop];
+ if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) {
+ // TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically
+ // curValue should get filled in with value if it isn't already filled in, in case we fetched the referenced field some other way
+ return true;
+ }
+ if (value instanceof RefField) {
+ value = new ProxyField(value);
+ }
+ if (value instanceof ObjectField) {
+ if (value[Parent] && value[Parent] !== target) {
+ throw new Error("Can't put the same object in multiple documents at the same time");
+ }
+ value[Parent] = target;
+ value[OnUpdate] = (diff?: any) => {
+ if (!diff) diff = SerializationHelper.Serialize(value);
+ target[Update]({ [prop]: diff });
+ };
+ }
+ if (curValue instanceof ObjectField) {
+ delete curValue[Parent];
+ delete curValue[OnUpdate];
+ }
+ target.__fields[prop] = value;
+ target[Update]({ ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) });
+ UndoManager.AddEvent({
+ redo: () => receiver[prop] = value,
+ undo: () => receiver[prop] = curValue
+ });
+ return true;
}
-@Deserializable("doc").withFields(["id"])
-export class Doc extends RefField {
-
- private static setter(target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
- if (SerializationHelper.IsSerializing()) {
- target[prop] = value;
- return true;
- }
- if (typeof prop === "symbol") {
- target[prop] = value;
- return true;
- }
- const curValue = target.__fields[prop];
- if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) {
- // TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically
- // curValue should get filled in with value if it isn't already filled in, in case we fetched the referenced field some other way
- return true;
- }
- if (value instanceof RefField) {
- value = new ProxyField(value);
- }
- if (value instanceof ObjectField) {
- if (value[Parent] && value[Parent] !== target) {
- throw new Error("Can't put the same object in multiple documents at the same time");
- }
- value[Parent] = target;
- value[Update] = (diff?: any) => {
- if (!diff) diff = SerializationHelper.Serialize(value);
- target[Update]({ [prop]: diff });
- };
- }
- if (curValue instanceof ObjectField) {
- delete curValue[Parent];
- delete curValue[Update];
- }
- target.__fields[prop] = value;
- target[Update]({ ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) });
- UndoManager.AddEvent({
- redo: () => receiver[prop] = value,
- undo: () => receiver[prop] = curValue
- });
- return true;
+function getter(target: any, prop: string | symbol | number, receiver: any): any {
+ if (typeof prop === "symbol") {
+ return target.__fields[prop] || target[prop];
+ }
+ if (SerializationHelper.IsSerializing()) {
+ return target[prop];
}
+ return getField(target, prop, receiver);
+}
- private static getter(target: any, prop: string | symbol | number, receiver: any): any {
- if (typeof prop === "symbol") {
- return target[prop];
- }
- if (SerializationHelper.IsSerializing()) {
- return target[prop];
+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) {
+ const proto = getField(target, "prototype", true);
+ if (proto instanceof Doc) {
+ let field = proto[prop];
+ callback && callback(field === null ? undefined : field);
+ return field;
}
- return Doc.getField(target, prop, receiver);
}
+ callback && callback(field);
+ return field;
- private static 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) {
- const proto = Doc.getField(target, "prototype", true);
- if (proto instanceof Doc) {
- let field = proto[prop];
- callback && callback(field === null ? undefined : field);
- return field;
- }
- }
- callback && callback(field);
- return field;
+}
+@Deserializable("list")
+class ListImpl<T extends Field> extends ObjectField {
+ constructor() {
+ super();
+ const list = new Proxy<this>(this, {
+ set: function (a, b, c, d) { return setter(a, b, c, d); },
+ get: getter,
+ 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"); },
+ });
+ return list;
}
- static GetAsync(doc: Doc, key: string, ignoreProto: boolean = false): Promise<Field | undefined> {
- const self = doc[Self];
- return new Promise(res => Doc.getField(self, key, ignoreProto, res));
+ [key: number]: T | null | undefined;
+
+ @serializable(alias("fields", list(autoObject())))
+ @observable
+ private __fields: (T | null | undefined)[] = [];
+
+ private [Update] = (diff: any) => {
+ console.log(diff);
+ const update = this[OnUpdate];
+ update && update(diff);
}
+ private [Self] = this;
+}
+export type List<T extends Field> = ListImpl<T> & T[];
+export const List: { new <T extends Field>(): List<T> } = ListImpl as any;
+
+@Deserializable("doc").withFields(["id"])
+export class Doc extends RefField {
constructor(id?: string, forceSave?: boolean) {
super(id);
const doc = new Proxy<this>(this, {
- set: Doc.setter,
- get: Doc.getter,
+ set: setter,
+ get: getter,
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"); },
});
@@ -216,20 +237,15 @@ export class Doc extends RefField {
}
export namespace Doc {
+ export function GetAsync(doc: Doc, key: string, ignoreProto: boolean = false): Promise<Field | undefined> {
+ const self = doc[Self];
+ return new Promise(res => getField(self, key, ignoreProto, res));
+ }
export const Prototype = Symbol("Prototype");
}
export const GetAsync = Doc.GetAsync;
-interface IDoc {
- [key: string]: Field | null | undefined;
-}
-
-interface ImageDocument extends IDoc {
- data: URLField;
- test: number;
-}
-
export type ToType<T> =
T extends "string" ? string :
T extends "number" ? number :
@@ -261,20 +277,20 @@ interface Interface {
// [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
}
-type FieldCtor<T extends Field> = ToConstructor<T> | ListSpec<Field>;
+type FieldCtor<T extends Field> = ToConstructor<T> | ListSpec<T>;
-function Cast<T extends Field>(field: Field | undefined, ctor: FieldCtor<T>): ToType<typeof ctor> | undefined {
+function Cast<T extends FieldCtor<Field>>(field: Field | undefined, ctor: T): ToType<T> | undefined {
if (field !== undefined) {
if (typeof ctor === "string") {
if (typeof field === ctor) {
- return field as ToType<typeof ctor>;
+ return field as ToType<T>;
}
} else if (typeof ctor === "object") {
if (field instanceof List) {
- return field as ToType<typeof ctor>;
+ return field as ToType<T>;
}
} else if (field instanceof (ctor as any)) {
- return field as ToType<typeof ctor>;
+ return field as ToType<T>;
}
}
return undefined;