From d2c9550f23c4e5654822ac01b973bb965e3f6dec Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Fri, 14 Jun 2019 20:49:12 -0400 Subject: cleaned up Docs namespace and thoroughly documented DocServer.GetRefFields --- src/client/views/Main.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/Main.tsx') diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 3d9750a85..98b14f9c8 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -5,7 +5,7 @@ import * as ReactDOM from 'react-dom'; import * as React from 'react'; (async () => { - await Docs.initProtos(); + await Docs.Prototypes.initialize(); await CurrentUserUtils.loadCurrentUser(); ReactDOM.render(, document.getElementById('root')); })(); -- cgit v1.2.3-70-g09d2 From 87a3063fa0d0726334125209e74930ebf8422a44 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 6 Jul 2019 03:50:33 -0400 Subject: fixed formatting, reset and document persistence --- .../util/Import & Export/DirectoryImportBox.tsx | 129 ++++++++++++++------- .../util/Import & Export/ImportMetadataEntry.tsx | 42 ++++++- src/client/views/Main.tsx | 2 +- 3 files changed, 125 insertions(+), 48 deletions(-) (limited to 'src/client/views/Main.tsx') diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 88ce9c0e9..b75944745 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -1,9 +1,9 @@ import "fs"; import React = require("react"); -import { Doc, Opt } from "../../../new_fields/Doc"; +import { Doc, Opt, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; import { DocServer } from "../../DocServer"; import { RouteStore } from "../../../server/RouteStore"; -import { action, observable, autorun, runInAction } from "mobx"; +import { action, observable, autorun, runInAction, computed } from "mobx"; import { FieldViewProps, FieldView } from "../../views/nodes/FieldView"; import Measure, { ContentRect } from "react-measure"; import { library } from '@fortawesome/fontawesome-svg-core'; @@ -11,9 +11,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faArrowUp, faTag, faPlus } from '@fortawesome/free-solid-svg-icons'; import { Docs, DocumentOptions } from "../../documents/Documents"; import { observer } from "mobx-react"; -import ImportMetadataEntry from "./ImportMetadataEntry"; +import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from "./ImportMetadataEntry"; import { Utils } from "../../../Utils"; import { DocumentManager } from "../DocumentManager"; +import { Id } from "../../../new_fields/FieldSymbols"; +import { List } from "../../../new_fields/List"; +import { Cast, BoolCast, NumCast } from "../../../new_fields/Types"; +import { listSpec } from "../../../new_fields/Schema"; @observer export default class DirectoryImportBox extends React.Component { @@ -22,27 +26,45 @@ export default class DirectoryImportBox extends React.Component @observable private left = 0; private dimensions = 50; - @observable private editingMetadata = false; - @observable private metadata_guids: string[] = []; @observable private entries: ImportMetadataEntry[] = []; @observable private quota = 1; @observable private remaining = 1; - @observable private uploadBegun = false; + @observable private uploading = false; @observable private removeHover = false; - @observable private shouldKeep = false; public static LayoutString() { return FieldView.LayoutString(DirectoryImportBox); } constructor(props: FieldViewProps) { super(props); library.add(faArrowUp, faTag, faPlus); + let doc = this.props.Document; + this.editingMetadata = this.editingMetadata || false; + this.persistent = this.persistent || false; + !Cast(doc.data, listSpec(Doc)) && (doc.data = new List()); + } + + @computed + private get editingMetadata() { + return BoolCast(this.props.Document.editingMetadata); + } + + private set editingMetadata(value: boolean) { + this.props.Document.editingMetadata = value; + } + + @computed + private get persistent() { + return BoolCast(this.props.Document.persistent); + } + + private set persistent(value: boolean) { + this.props.Document.persistent = value; } - @action handleSelection = async (e: React.ChangeEvent) => { - this.uploadBegun = true; + runInAction(() => this.uploading = true); let promises: Promise[] = []; let docs: Doc[] = []; @@ -58,7 +80,7 @@ export default class DirectoryImportBox extends React.Component file && validated.push(file); } - this.quota = validated.length; + runInAction(() => this.quota = validated.length); for (let uploaded_file of validated) { if (!uploaded_file) { @@ -70,15 +92,14 @@ export default class DirectoryImportBox extends React.Component let dropFileName = uploaded_file ? uploaded_file.name : "-empty-"; let type = uploaded_file.type; - this.remaining++; + runInAction(() => this.remaining++); let prom = fetch(DocServer.prepend(RouteStore.upload), { method: 'POST', body: formData }).then(async (res: Response) => { (await res.json()).map(action((file: any) => { - let path = DocServer.prepend(file); - let docPromise = Docs.getDocumentFromType(type, path, { nativeWidth: 300, width: 300, title: dropFileName }); + let docPromise = Docs.getDocumentFromType(type, DocServer.prepend(file), { nativeWidth: 300, width: 300, title: dropFileName }); docPromise.then(doc => { doc && docs.push(doc) && runInAction(() => this.remaining--); }); @@ -97,24 +118,30 @@ export default class DirectoryImportBox extends React.Component }); let doc = this.props.Document; + let height: number = NumCast(doc.height) || 0; + let offset: number = this.persistent ? (height === 0 ? 0 : height + 30) : 0; let options: DocumentOptions = { title: `Import of ${directory}`, width: 1105, height: 500, - x: Doc.GetT(doc, "x", "number"), - y: Doc.GetT(doc, "y", "number") + x: NumCast(doc.x), + y: NumCast(doc.y) + offset }; let parent = this.props.ContainingCollectionView; if (parent) { let importContainer = Docs.StackingDocument(docs, options); importContainer.singleColumn = false; Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer); - !this.shouldKeep && this.props.removeDocument && this.props.removeDocument(doc); + !this.persistent && this.props.removeDocument && this.props.removeDocument(doc); DocumentManager.Instance.jumpToDocument(importContainer, true); - this.uploadBegun = false; + + } + + runInAction(() => { + this.uploading = false; this.quota = 1; this.remaining = 1; - } + }); } componentDidMount() { @@ -134,30 +161,43 @@ export default class DirectoryImportBox extends React.Component } @action - addMetadataEntry = () => { - this.metadata_guids.push(Utils.GenerateGuid()); + addMetadataEntry = async () => { + let entryDoc = new Doc(); + entryDoc.checked = false; + entryDoc.key = keyPlaceholder; + entryDoc.value = valuePlaceholder; + Doc.AddDocToList(this.props.Document, "data", entryDoc); } @action - remove = (entry: ImportMetadataEntry) => { - let index = this.entries.indexOf(entry); - let key = entry.key; - this.entries.splice(index, 1); - this.metadata_guids.splice(this.metadata_guids.indexOf(key), 1); + remove = async (entry: ImportMetadataEntry) => { + let metadata = await DocListCastAsync(this.props.Document.data); + if (metadata) { + let index = this.entries.indexOf(entry); + if (index !== -1) { + runInAction(() => this.entries.splice(index, 1)); + index = metadata.indexOf(entry.props.Document); + if (index !== -1) { + metadata.splice(index, 1); + } + } + + } } render() { let dimensions = 50; - let guids = this.metadata_guids.map(el => el); + let entries = DocListCast(this.props.Document.data); let isEditing = this.editingMetadata; let remaining = this.remaining; let quota = this.quota; - let percent = `${100 - (remaining / quota * 100)}`; - let uploadBegun = this.uploadBegun; + let uploading = this.uploading; let showRemoveLabel = this.removeHover; - let keep = this.shouldKeep; + let persistent = this.persistent; + let percent = `${100 - (remaining / quota * 100)}`; percent = percent.split(".")[0]; percent = percent.startsWith("100") ? "99" : percent; + let marginOffset = (percent.length === 1 ? 5 : 0) - 1.6; return ( {({ measureRef }) => @@ -192,7 +232,7 @@ export default class DirectoryImportBox extends React.Component position: "absolute", left: this.left + 12.6, top: this.top + 11, - opacity: uploadBegun ? 0 : 1, + opacity: uploading ? 0 : 1, transition: "0.4s opacity ease" }}> @@ -202,7 +242,7 @@ export default class DirectoryImportBox extends React.Component width: 80, height: 80, transition: "0.4s opacity ease", - opacity: uploadBegun ? 0.7 : 0, + opacity: uploading ? 0.7 : 0, position: "absolute", top: this.top - 15, left: this.left - 15 @@ -211,16 +251,17 @@ export default class DirectoryImportBox extends React.Component runInAction(() => this.shouldKeep = e.target.checked)} + onChange={e => runInAction(() => this.persistent = e.target.checked)} style={{ margin: 0, position: "absolute", left: 10, bottom: 10, - opacity: isEditing ? 0 : 1, + opacity: isEditing || uploading ? 0 : 1, transition: "0.4s opacity ease", - pointerEvents: isEditing ? "none" : "all" + pointerEvents: isEditing || uploading ? "none" : "all" }} + checked={this.persistent} onPointerEnter={action(() => this.removeHover = true)} onPointerLeave={action(() => this.removeHover = false)} /> @@ -232,18 +273,18 @@ export default class DirectoryImportBox extends React.Component fontSize: 12, opacity: showRemoveLabel ? 1 : 0, transition: "0.4s opacity ease" - }}>Template will be {keep ? "kept" : "removed"} after upload

+ }}>Template will be {persistent ? "kept" : "removed"} after upload

{percent}%
borderRadius: "50%", width: 25, height: 25, - background: "black" + background: "black", + pointerEvents: uploading ? "none" : "all", + opacity: uploading ? 0 : 1, + transition: "0.4s opacity ease" }} title={isEditing ? "Back to Upload" : "Add Metadata"} onClick={action(() => this.editingMetadata = !this.editingMetadata)} @@ -263,7 +307,9 @@ export default class DirectoryImportBox extends React.Component pointerEvents: "none", position: "absolute", right: isEditing ? 16.3 : 14.5, - top: isEditing ? 15.4 : 16 + top: isEditing ? 15.4 : 16, + opacity: uploading ? 0 : 1, + transition: "0.4s opacity ease" }} icon={isEditing ? faArrowUp : faTag} color="#FFFFFF" @@ -304,9 +350,10 @@ export default class DirectoryImportBox extends React.Component

Add metadata to your import...


- {guids.map(guid => + {entries.map(doc => { if (el) this.entries.push(el); }} next={this.addMetadataEntry} diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx index 3b2a6ebb5..f5198c39b 100644 --- a/src/client/util/Import & Export/ImportMetadataEntry.tsx +++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx @@ -5,20 +5,20 @@ import { observable, action, computed } from "mobx"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { library } from '@fortawesome/fontawesome-svg-core'; -import { Opt } from "../../../new_fields/Doc"; +import { Opt, Doc } from "../../../new_fields/Doc"; +import { StrCast, BoolCast } from "../../../new_fields/Types"; interface KeyValueProps { + Document: Doc; remove: (self: ImportMetadataEntry) => void; next: () => void; } -const keyPlaceholder = "Key"; -const valuePlaceholder = "Value"; +export const keyPlaceholder = "Key"; +export const valuePlaceholder = "Value"; @observer export default class ImportMetadataEntry extends React.Component { - @observable public key = keyPlaceholder; - @observable public value = valuePlaceholder; private keyRef = React.createRef(); private valueRef = React.createRef(); @@ -34,8 +34,36 @@ export default class ImportMetadataEntry extends React.Component return (this.key.length > 0 && this.key !== keyPlaceholder) && (this.value.length > 0 && this.value !== valuePlaceholder); } + @computed + private get backing() { + return this.props.Document; + } + + @computed public get onDataDoc() { - return this.checkRef.current && this.checkRef.current.checked; + return BoolCast(this.backing.checked); + } + + public set onDataDoc(value: boolean) { + this.backing.checked = value; + } + + @computed + public get key() { + return StrCast(this.backing.key); + } + + public set key(value: string) { + this.backing.key = value; + } + + @computed + public get value() { + return StrCast(this.backing.value); + } + + public set value(value: string) { + this.backing.value = value; } @action @@ -75,10 +103,12 @@ export default class ImportMetadataEntry extends React.Component }} > this.onDataDoc = e.target.checked} ref={this.checkRef} style={{ margin: "0 10px 0 15px" }} type="checkbox" title={"Add to Data Document?"} + checked={this.onDataDoc} />
, document.getElementById('root')); -})(); +})(); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 3daca894b6eaf1eb8590f54b1a5bf5feca663a08 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Fri, 12 Jul 2019 22:34:09 -0400 Subject: links now go between users --- src/client/documents/Documents.ts | 18 +++++++++++++++++- src/client/util/LinkManager.ts | 5 ++++- src/client/views/Main.tsx | 23 +++++++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) (limited to 'src/client/views/Main.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2a3827782..638ba287f 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -54,7 +54,8 @@ export enum DocumentType { PDF = "pdf", ICON = "icon", IMPORT = "import", - LINK = "link" + LINK = "link", + LINKDOC = "linkdoc" } export interface DocumentOptions { @@ -85,6 +86,12 @@ export interface DocumentOptions { // [key: string]: Opt; } +class EmptyBox { + public static LayoutString() { + return ""; + } +} + export namespace Docs { export namespace Prototypes { @@ -148,6 +155,11 @@ export namespace Docs { [DocumentType.IMPORT, { layout: { view: DirectoryImportBox }, options: { height: 150 } + }], + [DocumentType.LINKDOC, { + data: new List(), + layout: { view: EmptyBox }, + options: {} }] ]); @@ -195,6 +207,10 @@ export namespace Docs { return PrototypeMap.get(type)!; } + export function MainLinkDocument() { + return Prototypes.get(DocumentType.LINKDOC); + } + /** * This is a convenience method that is used to initialize * prototype documents for the first time. diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 1ed040aa4..a647f22c1 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -5,6 +5,7 @@ import { listSpec } from "../../new_fields/Schema"; import { List } from "../../new_fields/List"; import { Id } from "../../new_fields/FieldSymbols"; import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; +import { Docs } from "../documents/Documents"; /* @@ -35,7 +36,9 @@ export class LinkManager { // the linkmanagerdoc stores a list of docs representing all linkdocs in 'allLinks' and a list of strings representing all group types in 'allGroupTypes' // lists of strings representing the metadata keys for each group type is stored under a key that is the same as the group type public get LinkManagerDoc(): Doc | undefined { - return FieldValue(Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc)); + // return FieldValue(Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc)); + + return Docs.Prototypes.MainLinkDocument(); } public getAllLinks(): Doc[] { diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 932a6375f..1b9a45f0b 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -3,9 +3,32 @@ import { Docs } from "../documents/Documents"; import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; import * as ReactDOM from 'react-dom'; import * as React from 'react'; +import { Cast } from "../../new_fields/Types"; +import { Doc, DocListCastAsync } from "../../new_fields/Doc"; +import { List } from "../../new_fields/List"; + +let swapDocs = async () => { + let oldDoc = await Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc); + Docs.Prototypes.MainLinkDocument().allLinks = new List(); + if (oldDoc) { + let links = await DocListCastAsync(oldDoc.allLinks); + // if (links && DocListCast(links)) { + if (links && links.length) { + let data = await DocListCastAsync(Docs.Prototypes.MainLinkDocument().allLinks); + if (data) { + data.push(...links); + } + else { + Docs.Prototypes.MainLinkDocument().allLinks = new List(links); + } + } + CurrentUserUtils.UserDocument.LinkManagerDoc = undefined; + } +} (async () => { await Docs.Prototypes.initialize(); await CurrentUserUtils.loadCurrentUser(); + await swapDocs(); ReactDOM.render(, document.getElementById('root')); })(); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 3c6c4e6da942ef4c1e7faebdc165eb4fcaa7bee4 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Sat, 13 Jul 2019 16:34:42 -0400 Subject: oops --- src/client/views/Main.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/Main.tsx') diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 1b9a45f0b..971658473 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -9,7 +9,7 @@ import { List } from "../../new_fields/List"; let swapDocs = async () => { let oldDoc = await Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc); - Docs.Prototypes.MainLinkDocument().allLinks = new List(); + // Docs.Prototypes.MainLinkDocument().allLinks = new List(); if (oldDoc) { let links = await DocListCastAsync(oldDoc.allLinks); // if (links && DocListCast(links)) { -- cgit v1.2.3-70-g09d2 From 4390106eb59a90283395ae5a18a0451e43166889 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Sat, 13 Jul 2019 17:42:26 -0400 Subject: oops duplicates --- src/client/views/Main.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/client/views/Main.tsx') diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 971658473..589542806 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -16,13 +16,14 @@ let swapDocs = async () => { if (links && links.length) { let data = await DocListCastAsync(Docs.Prototypes.MainLinkDocument().allLinks); if (data) { - data.push(...links); + data.push(...links.filter(i => data!.indexOf(i) === -1)); + Docs.Prototypes.MainLinkDocument().allLinks = new List(data.filter((i, idx) => data!.indexOf(i) === idx)); } else { Docs.Prototypes.MainLinkDocument().allLinks = new List(links); } } - CurrentUserUtils.UserDocument.LinkManagerDoc = undefined; + CurrentUserUtils.UserDocument.linkManagerDoc = undefined; } } -- cgit v1.2.3-70-g09d2 From e302a00b20ae0f44393548c7da27af60fd56c92b Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Sun, 14 Jul 2019 18:49:42 -0400 Subject: Added whosOnline route to server --- src/client/DocServer.ts | 97 +++++++++++++--------- src/client/views/Main.tsx | 5 +- src/client/views/MetadataEntryMenu.tsx | 1 - src/debug/Repl.tsx | 6 +- src/debug/Viewer.tsx | 15 ++-- .../authentication/models/current_user_utils.ts | 17 ++-- src/server/index.ts | 45 +++++++++- 7 files changed, 128 insertions(+), 58 deletions(-) (limited to 'src/client/views/Main.tsx') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index d05793ea2..6737657c8 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -5,6 +5,7 @@ import { Utils, emptyFunction } from '../Utils'; import { SerializationHelper } from './util/SerializationHelper'; import { RefField } from '../new_fields/RefField'; import { Id, HandleUpdate } from '../new_fields/FieldSymbols'; +import { CurrentUserUtils } from '../server/authentication/models/current_user_utils'; /** * This class encapsulates the transfer and cross-client synchronization of @@ -21,12 +22,31 @@ import { Id, HandleUpdate } from '../new_fields/FieldSymbols'; */ export namespace DocServer { let _cache: { [id: string]: RefField | Promise> } = {}; - const _socket = OpenSocket(`${window.location.protocol}//${window.location.hostname}:4321`); + let _socket: SocketIOClient.Socket; // this client's distinct GUID created at initialization - const GUID: string = Utils.GenerateGuid(); + let GUID: string; // indicates whether or not a document is currently being udpated, and, if so, its id let updatingId: string | undefined; + export function init(protocol: string, hostname: string, port: number, identifier: string) { + _cache = {}; + GUID = identifier; + _socket = OpenSocket(`${protocol}//${hostname}:${port}`); + + _GetRefField = _GetRefFieldImpl; + _GetRefFields = _GetRefFieldsImpl; + _CreateField = _CreateFieldImpl; + _UpdateField = _UpdateFieldImpl; + + /** + * Whenever the server sends us its handshake message on our + * websocket, we use the above function to return the handshake. + */ + Utils.AddServerHandler(_socket, MessageStore.Foo, onConnection); + Utils.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate); + Utils.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete); + Utils.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete); + } /** * A convenience method. Prepends the full path (i.e. http://localhost:1050) to the * requested extension @@ -36,6 +56,10 @@ export namespace DocServer { return window.location.origin + extension; } + function errorFunc(): never { + throw new Error("Can't use DocServer without calling init first"); + } + export namespace Control { let _isReadOnly = false; @@ -63,22 +87,16 @@ export namespace DocServer { } - export namespace Util { - - /** - * Whenever the server sends us its handshake message on our - * websocket, we use the above function to return the handshake. - */ - Utils.AddServerHandler(_socket, MessageStore.Foo, onConnection); + /** + * This function emits a message (with this client's + * unique GUID) to the server + * indicating that this client has connected + */ + function onConnection() { + _socket.emit(MessageStore.Bar.Message, GUID); + } - /** - * This function emits a message (with this client's - * unique GUID) to the server - * indicating that this client has connected - */ - function onConnection() { - _socket.emit(MessageStore.Bar.Message, GUID); - } + export namespace Util { /** * Emits a message to the server that wipes @@ -98,7 +116,7 @@ export namespace DocServer { * the server if the document has not been cached. * @param id the id of the requested document */ - export async function GetRefField(id: string): Promise> { + const _GetRefFieldImpl = (id: string): Promise> => { // an initial pass through the cache to determine whether the document needs to be fetched, // is already in the process of being fetched or already exists in the // cache @@ -139,8 +157,14 @@ export namespace DocServer { return cached; } else { // CACHED => great, let's just return the cached field we have - return cached; + return Promise.resolve(cached); } + }; + + let _GetRefField: (id: string) => Promise> = errorFunc; + + export function GetRefField(id: string): Promise> { + return _GetRefField(id); } /** @@ -149,7 +173,7 @@ export namespace DocServer { * the server if the document has not been cached. * @param ids the ids that map to the reqested documents */ - export async function GetRefFields(ids: string[]): Promise<{ [id: string]: Opt }> { + const _GetRefFieldsImpl = async (ids: string[]): Promise<{ [id: string]: Opt }> => { const requestedIds: string[] = []; const waitingIds: string[] = []; const promises: Promise>[] = []; @@ -245,16 +269,13 @@ export namespace DocServer { // argument to the caller's promise (i.e. GetRefFields(["_id1_", "_id2_", "_id3_"]).then(map => //do something with map...)) // or it is the direct return result if the promise is awaited (i.e. let fields = await GetRefFields(["_id1_", "_id2_", "_id3_"])). return map; - } + }; - function _UpdateFieldImpl(id: string, diff: any) { - if (id === updatingId) { - return; - } - Utils.Emit(_socket, MessageStore.UpdateField, { id, diff }); - } + let _GetRefFields: (ids: string[]) => Promise<{ [id: string]: Opt }> = errorFunc; - let _UpdateField = _UpdateFieldImpl; + export function GetRefFields(ids: string[]) { + return _GetRefFields(ids); + } // WRITE A NEW DOCUMENT TO THE SERVER @@ -274,7 +295,7 @@ export namespace DocServer { Utils.Emit(_socket, MessageStore.CreateField, initialState); } - let _CreateField = _CreateFieldImpl; + let _CreateField: (field: RefField) => void = errorFunc; // NOTIFY THE SERVER OF AN UPDATE TO A DOC'S STATE @@ -290,6 +311,15 @@ export namespace DocServer { _UpdateField(id, updatedState); } + function _UpdateFieldImpl(id: string, diff: any) { + if (id === updatingId) { + return; + } + Utils.Emit(_socket, MessageStore.UpdateField, { id, diff }); + } + + let _UpdateField: (id: string, diff: any) => void = errorFunc; + function _respondToUpdateImpl(diff: any) { const id = diff.id; // to be valid, the Diff object must reference @@ -355,13 +385,4 @@ export namespace DocServer { function respondToDelete(ids: string | string[]) { _respondToDelete(ids); } - - function connected() { - _socket.emit(MessageStore.Bar.Message, GUID); - } - - Utils.AddServerHandler(_socket, MessageStore.Foo, connected); - Utils.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate); - Utils.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete); - Utils.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete); } \ No newline at end of file diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 589542806..80399e24b 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -6,6 +6,7 @@ import * as React from 'react'; import { Cast } from "../../new_fields/Types"; import { Doc, DocListCastAsync } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; +import { DocServer } from "../DocServer"; let swapDocs = async () => { let oldDoc = await Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc); @@ -28,8 +29,10 @@ let swapDocs = async () => { } (async () => { + const info = await CurrentUserUtils.loadCurrentUser(); + DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email); await Docs.Prototypes.initialize(); - await CurrentUserUtils.loadCurrentUser(); + await CurrentUserUtils.loadUserDocument(info); await swapDocs(); ReactDOM.render(, document.getElementById('root')); })(); \ No newline at end of file diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx index 08abb9887..5ee661944 100644 --- a/src/client/views/MetadataEntryMenu.tsx +++ b/src/client/views/MetadataEntryMenu.tsx @@ -152,7 +152,6 @@ export class MetadataEntryMenu extends React.Component{ } render() { - trace(); return (
Key: diff --git a/src/debug/Repl.tsx b/src/debug/Repl.tsx index 91b711c79..4f4db13d2 100644 --- a/src/debug/Repl.tsx +++ b/src/debug/Repl.tsx @@ -6,6 +6,7 @@ import { CompileScript } from '../client/util/Scripting'; import { makeInterface } from '../new_fields/Schema'; import { ObjectField } from '../new_fields/ObjectField'; import { RefField } from '../new_fields/RefField'; +import { DocServer } from '../client/DocServer'; @observer class Repl extends React.Component { @@ -63,4 +64,7 @@ class Repl extends React.Component { } } -ReactDOM.render(, document.getElementById("root")); \ No newline at end of file +(async function () { + DocServer.init(window.location.protocol, window.location.hostname, 4321, "repl"); + ReactDOM.render(, document.getElementById("root")); +})(); \ No newline at end of file diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx index f48eb696c..2b3eed154 100644 --- a/src/debug/Viewer.tsx +++ b/src/debug/Viewer.tsx @@ -178,9 +178,12 @@ class Viewer extends React.Component { } } -ReactDOM.render(( -
- -
), - document.getElementById('root') -); \ No newline at end of file +(async function () { + await DocServer.init(window.location.protocol, window.location.hostname, 4321, "viewer"); + ReactDOM.render(( +
+ +
), + document.getElementById('root') + ); +})(); \ No newline at end of file diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 384c579de..e796ccb43 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -73,17 +73,21 @@ export class CurrentUserUtils { } - public static async loadCurrentUser(): Promise { - let userPromise = rp.get(DocServer.prepend(RouteStore.getCurrUser)).then(response => { + public static loadCurrentUser() { + return rp.get(DocServer.prepend(RouteStore.getCurrUser)).then(response => { if (response) { - let obj = JSON.parse(response); - CurrentUserUtils.curr_id = obj.id as string; - CurrentUserUtils.curr_email = obj.email as string; + const result: { id: string, email: string } = JSON.parse(response); + return result; } else { throw new Error("There should be a user! Why does Dash think there isn't one?"); } }); - let userDocPromise = await rp.get(DocServer.prepend(RouteStore.getUserDocumentId)).then(id => { + } + + public static async loadUserDocument({ id, email }: { id: string, email: string }) { + this.curr_id = id; + this.curr_email = email; + await rp.get(DocServer.prepend(RouteStore.getUserDocumentId)).then(id => { if (id) { return DocServer.GetRefField(id).then(async field => { if (field instanceof Doc) { @@ -108,7 +112,6 @@ export class CurrentUserUtils { } catch (e) { } - return Promise.all([userPromise, userDocPromise]); } /* Northstar catalog ... really just for testing so this should eventually go away */ diff --git a/src/server/index.ts b/src/server/index.ts index 21adff9e5..9cb43bf4e 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -149,6 +149,32 @@ app.get("/search", async (req, res) => { res.send(results); }); +function msToTime(duration: number) { + let milliseconds = Math.floor((duration % 1000) / 100), + seconds = Math.floor((duration / 1000) % 60), + minutes = Math.floor((duration / (1000 * 60)) % 60), + hours = Math.floor((duration / (1000 * 60 * 60)) % 24); + + let hoursS = (hours < 10) ? "0" + hours : hours; + let minutesS = (minutes < 10) ? "0" + minutes : minutes; + let secondsS = (seconds < 10) ? "0" + seconds : seconds; + + return hoursS + ":" + minutesS + ":" + secondsS + "." + milliseconds; +} + +app.get("/whosOnline", (req, res) => { + let users: any = { active: {}, inactive: {} }; + const now = Date.now(); + + for (const user in timeMap) { + const time = timeMap[user]; + const key = ((now - time) / 1000) < (60 * 5) ? "active" : "inactive"; + users[key][user] = `Last active ${msToTime(now - time)} ago`; + } + + res.send(users); +}); + app.get("/thumbnail/:filename", (req, res) => { let filename = req.params.filename; let noExt = filename.substring(0, filename.length - ".png".length); @@ -450,12 +476,21 @@ interface Map { } let clients: Map = {}; +let socketMap = new Map(); +let timeMap: { [id: string]: number } = {}; + server.on("connection", function (socket: Socket) { - console.log("a user has connected"); + socket.use((packet, next) => { + let id = socketMap.get(socket); + if (id) { + timeMap[id] = Date.now(); + } + next(); + }); Utils.Emit(socket, MessageStore.Foo, "handshooken"); - Utils.AddServerHandler(socket, MessageStore.Bar, barReceived); + Utils.AddServerHandler(socket, MessageStore.Bar, guid => barReceived(socket, guid)); Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args)); Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField); Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields); @@ -485,8 +520,10 @@ async function deleteAll() { await Search.Instance.clear(); } -function barReceived(guid: String) { - clients[guid.toString()] = new Client(guid.toString()); +function barReceived(socket: SocketIO.Socket, guid: string) { + clients[guid] = new Client(guid.toString()); + console.log(`User ${guid} has connected`); + socketMap.set(socket, guid); } function getField([id, callback]: [string, (result?: Transferable) => void]) { -- cgit v1.2.3-70-g09d2