From c9f3a1934548169ebe3c2e97968680ff339e304e Mon Sep 17 00:00:00 2001
From: Sam Wilkins <abdullah_ahmed@brown.edu>
Date: Sat, 4 May 2019 18:59:30 -0400
Subject: initial commit - todo: use cursor field rather than generic tuple
 field

---
 src/client/views/collections/CollectionSubView.tsx | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

(limited to 'src/client/views/collections/CollectionSubView.tsx')

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 828ac880a..1e8723fc6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -17,6 +17,7 @@ import { Cast, PromiseValue, FieldValue } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
 import { DocServer } from "../../DocServer";
 import { ObjectField } from "../../../new_fields/ObjectField";
+import { TupleField } from "../../../new_fields/TupleField";
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -66,12 +67,12 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 if (!proto) {
                     return;
                 }
-                let cursors = await Cast(proto.cursors, listSpec(ObjectField));
+                let cursors = await Cast(proto!.cursors, listSpec(TupleField));
                 if (!cursors) {
-                    proto.cursors = cursors = new List<ObjectField>();
+                    proto!.cursors = cursors = new List<TupleField<[]>>();
                 }
-                if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.Data[0][0] === id)) > -1) {
-                    cursors[ind].Data[1] = position;
+                if (cursors!.length > 0 && (ind = cursors!.findIndex(entry => entry.data[0][0] === id)) > -1) {
+                    cursors![ind].data[1] = position;
                 } else {
                     let entry = new TupleField<[string, string], [number, number]>([textInfo, position]);
                     cursors.push(entry);
-- 
cgit v1.2.3-70-g09d2


From 086391b7e45ed4b3cb29602a776f5812f142fff2 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <abdullah_ahmed@brown.edu>
Date: Wed, 8 May 2019 22:30:46 -0400
Subject: restoration of cursor functionality: cursor field

---
 src/client/views/collections/CollectionSubView.tsx | 21 +++----
 .../CollectionFreeFormRemoteCursors.tsx            | 66 +++++++++++++---------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +-
 src/new_fields/CursorField.ts                      | 55 ++++++++++++++++++
 src/new_fields/InkField.ts                         |  3 +-
 src/new_fields/List.ts                             | 39 +++++++++++--
 src/new_fields/TupleField.ts                       | 63 ---------------------
 7 files changed, 140 insertions(+), 111 deletions(-)
 create mode 100644 src/new_fields/CursorField.ts
 delete mode 100644 src/new_fields/TupleField.ts

(limited to 'src/client/views/collections/CollectionSubView.tsx')

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 1e8723fc6..232679a59 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -10,14 +10,14 @@ import * as rp from 'request-promise';
 import { CollectionView } from "./CollectionView";
 import { CollectionPDFView } from "./CollectionPDFView";
 import { CollectionVideoView } from "./CollectionVideoView";
-import { Doc, Opt } from "../../../new_fields/Doc";
+import { Doc, Opt, FieldResult } from "../../../new_fields/Doc";
 import { DocComponent } from "../DocComponent";
 import { listSpec } from "../../../new_fields/Schema";
-import { Cast, PromiseValue, FieldValue } from "../../../new_fields/Types";
+import { Cast, PromiseValue, FieldValue, ListSpec } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
 import { DocServer } from "../../DocServer";
 import { ObjectField } from "../../../new_fields/ObjectField";
-import { TupleField } from "../../../new_fields/TupleField";
+import CursorField, { CursorPosition, CursorMetadata } from "../../../new_fields/CursorField";
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -31,8 +31,6 @@ export interface SubCollectionViewProps extends CollectionViewProps {
     CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;
 }
 
-export type CursorEntry = TupleField<[string, string], [number, number]>;
-
 export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
     class CollectionSubView extends DocComponent<SubCollectionViewProps, T>(schemaCtor) {
         private dropDisposer?: DragManager.DragDropDisposer;
@@ -56,25 +54,24 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
 
         @action
         protected async setCursorPosition(position: [number, number]) {
-            return;
             let ind;
             let doc = this.props.Document;
             let id = CurrentUserUtils.id;
             let email = CurrentUserUtils.email;
+            let pos = { x: position[0], y: position[1] };
             if (id && email) {
-                let textInfo: [string, string] = [id, email];
                 const proto = await doc.proto;
                 if (!proto) {
                     return;
                 }
-                let cursors = await Cast(proto!.cursors, listSpec(TupleField));
+                let cursors = Cast(proto.cursors, listSpec(CursorField));
                 if (!cursors) {
-                    proto!.cursors = cursors = new List<TupleField<[]>>();
+                    proto.cursors = cursors = new List<CursorField>();
                 }
-                if (cursors!.length > 0 && (ind = cursors!.findIndex(entry => entry.data[0][0] === id)) > -1) {
-                    cursors![ind].data[1] = position;
+                if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.data.metadata.id === id)) > -1) {
+                    cursors[ind].setPosition(pos);
                 } else {
-                    let entry = new TupleField<[string, string], [number, number]>([textInfo, position]);
+                    let entry = new CursorField({ metadata: { id: id, identifier: email }, position: pos });
                     cursors.push(entry);
                 }
             }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index 036745eca..c22f430ac 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -1,26 +1,38 @@
 import { computed } from "mobx";
 import { observer } from "mobx-react";
-import { CollectionViewProps, CursorEntry } from "../CollectionSubView";
+import { CollectionViewProps } from "../CollectionSubView";
 import "./CollectionFreeFormView.scss";
 import React = require("react");
 import v5 = require("uuid/v5");
 import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
+import CursorField from "../../../../new_fields/CursorField";
+import { List } from "../../../../new_fields/List";
+import { Cast } from "../../../../new_fields/Types";
+import { listSpec } from "../../../../new_fields/Schema";
 
 @observer
 export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
-    protected getCursors(): CursorEntry[] {
+
+    protected getCursors(): CursorField[] {
         let doc = this.props.Document;
+
         let id = CurrentUserUtils.id;
-        let cursors = doc.GetList(KeyStore.Cursors, [] as CursorEntry[]);
-        let notMe = cursors.filter(entry => entry.Data[0][0] !== id);
-        return id ? notMe : [];
+        if (!id) {
+            return [];
+        }
+
+        let cursors = Cast(doc.cursors, listSpec(CursorField));
+        if (!cursors) {
+            doc.cursors = cursors = new List<CursorField>();
+        }
+
+        return cursors.filter(cursor => cursor.data.metadata.id !== id);
     }
 
     private crosshairs?: HTMLCanvasElement;
     drawCrosshairs = (backgroundColor: string) => {
         if (this.crosshairs) {
-            let c = this.crosshairs;
-            let ctx = c.getContext('2d');
+            let ctx = this.crosshairs.getContext('2d');
             if (ctx) {
                 ctx.fillStyle = backgroundColor;
                 ctx.fillRect(0, 0, 20, 20);
@@ -49,29 +61,27 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
             }
         }
     }
+
     @computed
     get sharedCursors() {
-        return this.getCursors().map(entry => {
-            if (entry.Data.length > 0) {
-                let id = entry.Data[0][0];
-                let email = entry.Data[0][1];
-                let point = entry.Data[1];
-                this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22");
-                return (
-                    <div key={id} className="collectionFreeFormRemoteCursors-cont"
-                        style={{ transform: `translate(${point[0] - 10}px, ${point[1] - 10}px)` }}
-                    >
-                        <canvas className="collectionFreeFormRemoteCursors-canvas"
-                            ref={(el) => { if (el) this.crosshairs = el; }}
-                            width={20}
-                            height={20}
-                        />
-                        <p className="collectionFreeFormRemoteCursors-symbol">
-                            {email[0].toUpperCase()}
-                        </p>
-                    </div>
-                );
-            }
+        return this.getCursors().map(c => {
+            let m = c.data.metadata;
+            let l = c.data.position;
+            this.drawCrosshairs("#" + v5(m.id, v5.URL).substring(0, 6).toUpperCase() + "22");
+            return (
+                <div key={m.id} className="collectionFreeFormRemoteCursors-cont"
+                    style={{ transform: `translate(${l.x - 10}px, ${l.y - 10}px)` }}
+                >
+                    <canvas className="collectionFreeFormRemoteCursors-canvas"
+                        ref={(el) => { if (el) this.crosshairs = el; }}
+                        width={20}
+                        height={20}
+                    />
+                    <p className="collectionFreeFormRemoteCursors-symbol">
+                        {m.identifier[0].toUpperCase()}
+                    </p>
+                </div>
+            );
         });
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 17c25c9db..59f7fa442 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -70,7 +70,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
     public getActiveDocuments = () => {
         const curPage = FieldValue(this.Document.curPage, -1);
-        return FieldValue(this.children, [] as Doc[]).filter(doc => {
+        return this.children.filter(doc => {
             var page = NumCast(doc.page, -1);
             return page === curPage || page === -1;
         });
@@ -314,7 +314,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                                 {this.childViews}
                             </InkingCanvas>
                         </CollectionFreeFormLinksView>
-                        {/* <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" /> */}
+                        <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
                     </CollectionFreeFormViewPannableContents>
                     <CollectionFreeFormOverlayView {...this.getDocumentViewProps(this.props.Document)} {...this.props} />
                 </MarqueeView>
diff --git a/src/new_fields/CursorField.ts b/src/new_fields/CursorField.ts
new file mode 100644
index 000000000..7fd326a5f
--- /dev/null
+++ b/src/new_fields/CursorField.ts
@@ -0,0 +1,55 @@
+import { ObjectField, Copy, OnUpdate } from "./ObjectField";
+import { observable } from "mobx";
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, createSimpleSchema, object } from "serializr";
+
+export type CursorPosition = {
+    x: number,
+    y: number
+}
+
+export type CursorMetadata = {
+    id: string,
+    identifier: string
+}
+
+export type CursorData = {
+    metadata: CursorMetadata,
+    position: CursorPosition
+}
+
+const PositionSchema = createSimpleSchema({
+    x: true,
+    y: true
+});
+
+const MetadataSchema = createSimpleSchema({
+    id: true,
+    identifier: 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[OnUpdate]();
+    }
+
+    [Copy]() {
+        return new CursorField(this.data);
+    }
+}
\ No newline at end of file
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index a3157857f..2d75f8a19 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -1,8 +1,6 @@
 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 { deepCopy } from "../Utils";
 
 export enum InkTool {
@@ -11,6 +9,7 @@ export enum InkTool {
     Highlighter,
     Eraser
 }
+
 export interface StrokeData {
     pathData: Array<{ x: number, y: number }>;
     color: string;
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
index 96018dafa..3e5fee646 100644
--- a/src/new_fields/List.ts
+++ b/src/new_fields/List.ts
@@ -1,9 +1,9 @@
 import { Deserializable, autoObject } from "../client/util/SerializationHelper";
 import { Field, Update, Self, FieldResult } from "./Doc";
-import { setter, getter, deleteProperty } from "./util";
+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, OnUpdate, Copy, Parent } from "./ObjectField";
 import { RefField } from "./RefField";
 import { ProxyField } from "./Proxy";
 
@@ -27,7 +27,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 +58,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;
diff --git a/src/new_fields/TupleField.ts b/src/new_fields/TupleField.ts
deleted file mode 100644
index 1ff57fefc..000000000
--- a/src/new_fields/TupleField.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { ObjectField, Copy } from "./ObjectField";
-import { IObservableArray, IArrayChange, IArraySplice, observe, Lambda, observable } from "mobx";
-import { UndoManager } from "../client/util/UndoManager";
-import { Field } from "./Doc";
-import { Deserializable } from "../client/util/SerializationHelper";
-import { serializable, createSimpleSchema, list, object } from "serializr";
-import { array } from "prop-types";
-
-const tupleSchema = createSimpleSchema({
-
-});
-
-@Deserializable("tuple")
-export class TupleField<T, U> extends ObjectField {
-
-
-    @serializable(list(object(tupleSchema)))
-    private Data: [T, U];
-
-    public get data() {
-        return this.Data;
-    }
-
-    constructor(data: [T, U]) {
-        super();
-        this.Data = data;
-        this.observeTuple();
-    }
-
-    private observeDisposer: Lambda | undefined;
-    private observeTuple(): void {
-        this.observeDisposer = observe(this.Data as (T | U)[] as IObservableArray<T | U>, (change: IArrayChange<T | U> | IArraySplice<T | U>) => {
-            if (change.type === "update") {
-                UndoManager.AddEvent({
-                    undo: () => this.Data[change.index] = change.oldValue,
-                    redo: () => this.Data[change.index] = change.newValue
-                });
-            } else {
-                throw new Error("Why are you messing with the length of a tuple, huh?");
-            }
-        });
-    }
-
-    protected setData(value: [T, U]) {
-        if (this.observeDisposer) {
-            this.observeDisposer();
-        }
-        this.Data = observable(value) as (T | U)[] as [T, U];
-        this.observeTuple();
-    }
-
-    UpdateFromServer(values: [T, U]) {
-        this.setData(values);
-    }
-
-    ToScriptString(): string {
-        return `new TupleField([${this.Data[0], this.Data[1]}])`;
-    }
-
-    [Copy]() {
-        return new TupleField<T, U>(this.Data);
-    }
-}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2

