aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTyler Schicke <tyler_schicke@brown.edu>2019-04-17 06:16:50 -0400
committerTyler Schicke <tyler_schicke@brown.edu>2019-04-17 06:16:50 -0400
commit79d8b5c812db5c28f477ace8db0ee4d9e18a84b7 (patch)
treeb43e5504cb7d7cd8128d0238140d5007fb36ee31
parent9c82d21123aaaa745a33e9dfe8775ef1db73c035 (diff)
Started implementing new documents
-rw-r--r--package.json1
-rw-r--r--src/debug/Test.tsx39
-rw-r--r--src/fields/NewDoc.ts190
3 files changed, 212 insertions, 18 deletions
diff --git a/package.json b/package.json
index 2463afa74..1eb546a80 100644
--- a/package.json
+++ b/package.json
@@ -163,6 +163,7 @@
"react-table": "^6.9.2",
"request": "^2.88.0",
"request-promise": "^4.2.4",
+ "serializr": "^1.5.1",
"socket.io": "^2.2.0",
"socket.io-client": "^2.2.0",
"typescript-collections": "^1.3.2",
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 11f2b0c4e..ca093e5b2 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -1,29 +1,32 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
-import JsxParser from 'react-jsx-parser';
+import { serialize, deserialize, map } from 'serializr';
+import { URLField, Doc } from '../fields/NewDoc';
-class Hello extends React.Component<{ firstName: string, lastName: string }> {
- render() {
- return <div>Hello {this.props.firstName} {this.props.lastName}</div>;
+class Test extends React.Component {
+ onClick = () => {
+ const url = new URLField(new URL("http://google.com"));
+ const doc = new Doc("a");
+ const doc2 = new Doc("b");
+ doc.hello = 5;
+ doc.fields = "test";
+ doc.test = "hello doc";
+ doc.url = url;
+ doc.testDoc = doc2;
+
+ console.log(doc.hello);
+ console.log(doc.fields);
+ console.log(doc.test);
+ console.log(doc.url);
+ console.log(doc.testDoc);
}
-}
-class Test extends React.Component {
render() {
- let jsx = "<Hello {...props}/>";
- let bindings = {
- props: {
- firstName: "First",
- lastName: "Last"
- }
- };
- return <JsxParser jsx={jsx} bindings={bindings} components={{ Hello }}></JsxParser>;
+ return <button onClick={this.onClick}>Click me</button>;
}
}
-ReactDOM.render((
- <div style={{ position: "absolute", width: "100%", height: "100%" }}>
- <Test />
- </div>),
+ReactDOM.render(
+ <Test />,
document.getElementById('root')
); \ No newline at end of file
diff --git a/src/fields/NewDoc.ts b/src/fields/NewDoc.ts
new file mode 100644
index 000000000..d0e518306
--- /dev/null
+++ b/src/fields/NewDoc.ts
@@ -0,0 +1,190 @@
+import { observable, action } from "mobx";
+import { Server } from "../client/Server";
+import { UndoManager } from "../client/util/UndoManager";
+import { serialize, deserialize, serializable, primitive } from "serializr";
+
+const Type = Symbol("type");
+
+const Id = Symbol("id");
+export abstract class RefField {
+ readonly [Id]: string;
+
+ constructor(id: string) {
+ this[Id] = id;
+ }
+}
+
+const Update = Symbol("Update");
+const Parent = Symbol("Parent");
+export class ObjectField {
+ protected [Update]?: (diff?: any) => void;
+ private [Parent]?: Doc;
+}
+
+function url() {
+ return {
+ serializer: function (value: URL) {
+ return value.href;
+ },
+ deserializer: function (jsonValue: string, done: (err: any, val: any) => void) {
+ done(undefined, new URL(jsonValue));
+ }
+ };
+}
+export class URLField extends ObjectField {
+ @serializable(url())
+ url: URL;
+
+ constructor(url: URL) {
+ super();
+ this.url = url;
+ }
+}
+
+export class ProxyField<T extends RefField> extends ObjectField {
+ constructor();
+ constructor(value: T);
+ constructor(value?: T) {
+ super();
+ if (value) {
+ this.cache = value;
+ this.fieldId = value[Id];
+ }
+ }
+
+ @serializable(primitive())
+ readonly fieldId: string = "";
+
+ // This getter/setter and nested object thing is
+ // because mobx doesn't play well with observable proxies
+ @observable.ref
+ private _cache: { readonly field: T | undefined } = { field: undefined };
+ private get cache(): T | undefined {
+ return this._cache.field;
+ }
+ private set cache(field: T | undefined) {
+ this._cache = { field };
+ }
+
+ private failed = false;
+ private promise?: Promise<any>;
+
+ value(callback?: ((field: T | undefined) => void)): T | undefined | null {
+ if (this.cache) {
+ callback && callback(this.cache);
+ return this.cache;
+ }
+ if (this.failed) {
+ return undefined;
+ }
+ if (!this.promise) {
+ // this.promise = Server.GetField(this.fieldId).then(action((field: any) => {
+ // this.promise = undefined;
+ // this.cache = field;
+ // if (field === undefined) this.failed = true;
+ // return field;
+ // }));
+ this.promise = new Promise(r => r());
+ }
+ callback && this.promise.then(callback);
+ return null;
+ }
+}
+
+export type Field = number | string | boolean | ObjectField | RefField;
+
+const Self = Symbol("Self");
+export class Doc extends RefField {
+
+ private static setter(target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
+ 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 = serialize(value);
+ target[Update]({ [prop]: diff });
+ };
+ }
+ if (curValue instanceof ObjectField) {
+ delete curValue[Parent];
+ delete curValue[Update];
+ }
+ target.__fields[prop] = value;
+ target[Update]({ [prop]: typeof value === "object" ? serialize(value) : value });
+ UndoManager.AddEvent({
+ redo: () => receiver[prop] = value,
+ undo: () => receiver[prop] = curValue
+ });
+ return true;
+ }
+
+ private static getter(target: any, prop: string | symbol | number, receiver: any): any {
+ if (typeof prop !== "string") {
+ return undefined;
+ }
+ return Doc.getField(target, prop, receiver);
+ }
+
+ private static getField(target: any, prop: string, ignoreProto: boolean = false, callback?: (field: Field | undefined) => void): any {
+ if (typeof prop === "symbol") {
+ return target.__fields[prop];
+ }
+ 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;
+
+ }
+
+ 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));
+ }
+
+ constructor(id: string) {
+ super(id);
+ const doc = new Proxy<this>(this, {
+ set: Doc.setter,
+ get: Doc.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 doc;
+ }
+
+ [key: string]: Field | null | undefined;
+
+ @observable
+ private __fields: { [key: string]: Field | null | undefined } = {};
+
+ private [Update] = (diff?: any) => {
+ console.log(JSON.stringify(diff || this));
+ }
+
+ private [Self] = this;
+}
+
+export namespace Doc {
+ export const Prototype = Symbol("Prototype");
+} \ No newline at end of file