diff options
24 files changed, 219 insertions, 93 deletions
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<Opt<RefField>> } = {}; - 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<Opt<RefField>> { + const _GetRefFieldImpl = (id: string): Promise<Opt<RefField>> => { // 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<Opt<RefField>> = errorFunc; + + export function GetRefField(id: string): Promise<Opt<RefField>> { + 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<RefField> }> { + const _GetRefFieldsImpl = async (ids: string[]): Promise<{ [id: string]: Opt<RefField> }> => { const requestedIds: string[] = []; const waitingIds: string[] = []; const promises: Promise<Opt<RefField>>[] = []; @@ -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<RefField> }> = 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/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 1afc5c147..0b7411fca 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -159,6 +159,7 @@ $linkGap : 3px; .linkButtonWrapper { pointer-events: auto; padding-right: 5px; + width: 25px; } .linkButton-linker { @@ -202,6 +203,7 @@ $linkGap : 3px; } .templating-menu { + position: absolute; pointer-events: auto; text-transform: uppercase; letter-spacing: 2px; @@ -237,8 +239,8 @@ $linkGap : 3px; #template-list { position: absolute; - top: 0; - left: 30px; + top: 25px; + left: 0px; width: max-content; font-family: $sans-serif; font-size: 12px; diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index d3c689571..f378b6c0c 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -107,15 +107,25 @@ export default class KeyManager { }; }); - private ctrl = action((keyname: string) => { + private ctrl = action((keyname: string, e: KeyboardEvent) => { let stopPropagation = true; let preventDefault = true; switch (keyname) { case "arrowright": + if (document.activeElement) { + if (document.activeElement.tagName === "INPUT" || document.activeElement.tagName === "TEXTAREA") { + return { stopPropagation: false, preventDefault: false }; + } + } MainView.Instance.mainFreeform && CollectionDockingView.Instance.AddRightSplit(MainView.Instance.mainFreeform, undefined); break; case "arrowleft": + if (document.activeElement) { + if (document.activeElement.tagName === "INPUT" || document.activeElement.tagName === "TEXTAREA") { + return { stopPropagation: false, preventDefault: false }; + } + } MainView.Instance.mainFreeform && CollectionDockingView.Instance.CloseRightSplit(MainView.Instance.mainFreeform); break; case "f": 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(<MainView />, document.getElementById('root')); })();
\ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 614b9cce7..76cc7e6d3 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -336,7 +336,7 @@ export class MainView extends React.Component { if (!(sidebar instanceof Doc)) return (null); return <div> <div className="mainView-libraryHandle" - style={{ left: `${this.flyoutWidth - 10}px`, backgroundColor: `${StrCast(sidebar.backgroundColor, "lightGray")}` }} + style={{ cursor: "ew-resize", left: `${this.flyoutWidth - 10}px`, backgroundColor: `${StrCast(sidebar.backgroundColor, "lightGray")}` }} onPointerDown={this.onPointerDown}> <span title="library View Dragger" style={{ width: "100%", height: "100%", position: "absolute" }} /> </div> diff --git a/src/client/views/MetadataEntryMenu.scss b/src/client/views/MetadataEntryMenu.scss index a6df3cd1e..bcfc9a82d 100644 --- a/src/client/views/MetadataEntryMenu.scss +++ b/src/client/views/MetadataEntryMenu.scss @@ -37,6 +37,8 @@ .react-autosuggest__suggestions-container--open { display: block; position: fixed; + overflow-y: auto; + max-height: 400px; width: 180px; border: 1px solid #aaa; background-color: #fff; diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx index 08abb9887..bd5a307b3 100644 --- a/src/client/views/MetadataEntryMenu.tsx +++ b/src/client/views/MetadataEntryMenu.tsx @@ -152,13 +152,13 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{ } render() { - trace(); return ( <div className="metadataEntry-outerDiv"> Key: <Autosuggest inputProps={{ value: this._currentKey, onChange: this.onKeyChange }} getSuggestionValue={this.getSuggestionValue} suggestions={this.suggestions} + alwaysRenderSuggestions renderSuggestion={this.renderSuggestion} onSuggestionsFetchRequested={this.onSuggestionFetch} onSuggestionsClearRequested={this.onSuggestionClear} diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index ef68c4489..e7a5475ed 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -33,10 +33,14 @@ export class PreviewCursor extends React.Component<{}> { onKeyPress = (e: KeyboardEvent) => { // Mixing events between React and Native is finicky. In FormattedTextBox, we set the // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore - // the keyPress here. + // the keyPress here. 112- //if not these keys, make a textbox if preview cursor is active! - if (e.key !== "Escape" && e.key !== "Backspace" && e.key !== "Delete" && + if (e.key !== "Escape" && e.key !== "Backspace" && e.key !== "Delete" && e.key !== "CapsLock" && e.key !== "Alt" && e.key !== "Shift" && e.key !== "Meta" && e.key !== "Control" && + e.key !== "Insert" && e.key !== "Home" && e.key !== "End" && e.key !== "PageUp" && e.key !== "PageDown" && + e.key !== "NumLock" && + (e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys + !e.key.startsWith("Arrow") && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) { if (!e.ctrlKey && !e.metaKey) {// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) { PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index fe8288b28..781bafec0 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -412,8 +412,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp if (doc instanceof Doc) { let theDoc = doc; CollectionDockingView.Instance._removedDocs.push(theDoc); - if (CurrentUserUtils.UserDocument.recentlyClosed instanceof Doc) { - Doc.AddDocToList(CurrentUserUtils.UserDocument.recentlyClosed, "data", doc, undefined, true, true); + + const recent = await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc); + if (recent) { + Doc.AddDocToList(recent, "data", doc, undefined, true, true); } SelectionManager.DeselectAll(); } @@ -442,12 +444,16 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp }); stack.header.controlsContainer.find('.lm_close') //get the close icon .off('click') //unbind the current click handler - .click(action(function () { + .click(action(async function () { //if (confirm('really close this?')) { + const recent = await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc); stack.remove(); - stack.contentItems.map(async (contentItem: any) => { + stack.contentItems.forEach(async (contentItem: any) => { let doc = await DocServer.GetRefField(contentItem.config.props.documentId); if (doc instanceof Doc) { + if (recent) { + Doc.AddDocToList(recent, "data", doc, undefined, true, true); + } let theDoc = doc; CollectionDockingView.Instance._removedDocs.push(theDoc); } diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index c97443785..8ab360984 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -30,13 +30,13 @@ export class CollectionPDFView extends React.Component<FieldViewProps> { () => NumCast(this.props.Document.scrollY), () => { // let transform = this.props.ScreenToLocalTransform(); - if (this._buttonTray.current) { - // console.log(this._buttonTray.current.offsetHeight); - // console.log(NumCast(this.props.Document.scrollY)); - let scale = this.nativeWidth() / this.props.Document[WidthSym](); - this.props.Document.panY = NumCast(this.props.Document.scrollY); - // console.log(scale); - } + // if (this._buttonTray.current) { + // console.log(this._buttonTray.current.offsetHeight); + // console.log(NumCast(this.props.Document.scrollY)); + let scale = this.nativeWidth() / this.props.Document[WidthSym](); + this.props.Document.panY = NumCast(this.props.Document.scrollY); + // console.log(scale); + // } // console.log(this.props.Document[HeightSym]()); }, { fireImmediately: true } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index b75cf7d5e..19e280444 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -485,7 +485,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const script = this.Document[key]; let originalText: string | undefined = undefined; if (script) originalText = script.script.originalScript; - let scriptingBox = <ScriptBox initialText={originalText} onCancel={overlayDisposer} onSave={(text, onError) => { + // tslint:disable-next-line: no-unnecessary-callback-wrapper + let scriptingBox = <ScriptBox initialText={originalText} onCancel={() => overlayDisposer()} onSave={(text, onError) => { const script = CompileScript(text, { params, requiredType, diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index fcb38487d..2c1813482 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -421,6 +421,18 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @undoBatch @action drop = async (e: Event, de: DragManager.DropEvent) => { + if (de.data instanceof DragManager.AnnotationDragData) { + e.stopPropagation(); + let annotationDoc = de.data.annotationDocument; + annotationDoc.linkedToDoc = true; + let targetDoc = this.props.Document; + let annotations = await DocListCastAsync(annotationDoc.annotations); + if (annotations) { + annotations.forEach(anno => { + anno.target = targetDoc; + }); + } + } if (de.data instanceof DragManager.LinkDragData) { let sourceDoc = de.data.linkSourceDocument; let destDoc = this.props.Document; diff --git a/src/client/views/pdf/PDFMenu.scss b/src/client/views/pdf/PDFMenu.scss index a4624b1f6..b06d19c53 100644 --- a/src/client/views/pdf/PDFMenu.scss +++ b/src/client/views/pdf/PDFMenu.scss @@ -21,6 +21,10 @@ .pdfMenu-dragger { height: 100%; transition: width .2s; + background-image: url("https://logodix.com/logo/1020374.png"); + background-size: 90% 100%; + background-repeat: no-repeat; + background-position: left center; } .pdfMenu-addTag { diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index aca8c4e53..01fd1c247 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -450,10 +450,6 @@ export class Viewer extends React.Component<IViewerProps> { @action search = (searchString: string) => { - if (searchString.length === 0) { - return; - } - if (this._pdfViewer._pageViewsReady) { this._pdfFindController.executeCommand('find', { diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index 5ff39c867..1e22aca9e 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -2,7 +2,7 @@ import { observer } from "mobx-react"; import React = require("react"); import { observable, action, runInAction, IReactionDisposer, reaction } from "mobx"; import * as Pdfjs from "pdfjs-dist"; -import { Opt, Doc, FieldResult, Field, DocListCast, WidthSym, HeightSym } from "../../../new_fields/Doc"; +import { Opt, Doc, FieldResult, Field, DocListCast, WidthSym, HeightSym, DocListCastAsync } from "../../../new_fields/Doc"; import "./PDFViewer.scss"; import "pdfjs-dist/web/pdf_viewer.css"; import { PDFBox } from "../nodes/PDFBox"; @@ -10,7 +10,7 @@ import { DragManager } from "../../util/DragManager"; import { Docs, DocUtils } from "../../documents/Documents"; import { List } from "../../../new_fields/List"; import { emptyFunction } from "../../../Utils"; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types"; import { listSpec } from "../../../new_fields/Schema"; import { menuBar } from "prosemirror-menu"; import { AnnotationTypes, PDFViewer, scale } from "./PDFViewer"; @@ -159,13 +159,23 @@ export default class Page extends React.Component<IPageProps> { // document that this annotation is linked to let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "New Annotation" }); targetDoc.targetPage = this.props.page; - let annotationDoc = this.highlight(targetDoc, "red"); + let annotationDoc = this.highlight(undefined, "red"); + annotationDoc.linkedToDoc = false; // create dragData and star tdrag let dragData = new DragManager.AnnotationDragData(thisDoc, annotationDoc, targetDoc); if (this._textLayer.current) { DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, { handlers: { - dragComplete: emptyFunction, + dragComplete: async () => { + if (!(await annotationDoc.linkedToDoc)) { + let annotations = await DocListCastAsync(annotationDoc.annotations); + if (annotations) { + annotations.forEach(anno => { + anno.target = targetDoc; + }); + } + } + } }, hideSource: false }); diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx index 7ea703b74..435ca86e3 100644 --- a/src/client/views/search/FilterBox.tsx +++ b/src/client/views/search/FilterBox.tsx @@ -263,7 +263,7 @@ export class FilterBox extends React.Component { @action.bound handleWordQueryChange = () => { this._basicWordStatus = !this._basicWordStatus; } - @action + @action.bound getBasicWordStatus() { return this._basicWordStatus; } @action.bound diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss index 1ec89ed2b..324ba3063 100644 --- a/src/client/views/search/SearchBox.scss +++ b/src/client/views/search/SearchBox.scss @@ -41,12 +41,13 @@ } .searchBox-results { - margin-left: 27px; + margin-right: 142px; top: 300px; display: flex; flex-direction: column; margin-right: 72px; max-height: 560px; + overflow: hidden; overflow-y: auto; .no-result { diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 67aaa387c..c02db528a 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -290,12 +290,13 @@ export class SearchBox extends React.Component { return ( <div className="searchBox-container"> <div className="searchBox-bar"> - <span className="searchBox-barChild searchBox-collection" onPointerDown={SetupDrag(this.collectionRef, this.startDragCollection)} ref={this.collectionRef}> + <span className="searchBox-barChild searchBox-collection" onPointerDown={SetupDrag(this.collectionRef, this.startDragCollection)} ref={this.collectionRef} title="Drag Results as Collection"> <FontAwesomeIcon icon="object-group" size="lg" /> </span> <input value={this._searchString} onChange={this.onChange} type="text" placeholder="Search..." className="searchBox-barChild searchBox-input" onPointerDown={this.openSearch} onKeyPress={this.enter} style={{ width: this._resultsOpen ? "500px" : "100px" }} /> + <button className="searchBox-barChild searchBox-submit" onClick={this.submitSearch} onPointerDown={FilterBox.Instance.stopProp}>Submit</button> <button className="searchBox-barChild searchBox-filter" onClick={FilterBox.Instance.openFilter} onPointerDown={FilterBox.Instance.stopProp}>Filter</button> </div> <div className="searchBox-results" onScroll={this.resultsScrolled} style={{ diff --git a/src/client/views/search/SearchItem.scss b/src/client/views/search/SearchItem.scss index c384e3bfc..0fb93daad 100644 --- a/src/client/views/search/SearchItem.scss +++ b/src/client/views/search/SearchItem.scss @@ -64,15 +64,19 @@ } .link-extended { + // display: none; + visibility: hidden; opacity: 0; position: relative; z-index: 500; overflow: hidden; - -webkit-transition: opacity 0.2s ease-in-out; - -moz-transition: opacity 0.2s ease-in-out; - -o-transition: opacity 0.2s ease-in-out; - transition: opacity 0.2s ease-in-out; + -webkit-transition: opacity 0.2s ease-in-out .2s, visibility 0s linear 0s; + -moz-transition: opacity 0.2s ease-in-out .2s, visibility 0s linear 0s; + -o-transition: opacity 0.2s ease-in-out .2s, visibility 0s linear 0s; + transition: opacity 0.2s ease-in-out .2s, visibility 0s linear 0s; + // transition-delay: 1s; } + } .link-container.item:hover { @@ -85,6 +89,8 @@ .link-container.item:hover .link-extended { opacity: 1; + visibility: visible; + // display: inline; } .icon-icons { diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 9a9351429..16ad71d16 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -246,8 +246,8 @@ export class SearchItem extends React.Component<SearchItemProps> { <div className="search-title" id="result" >{StrCast(this.props.doc.title)}</div> <div className="search-info" style={{ width: this._useIcons ? "15%" : "400px" }}> <div className={`icon-${this._useIcons ? "icons" : "live"}`}> - <div className="search-type" >{this.DocumentIcon}</div> - <div className="search-label">{this.props.doc.type}</div> + <div className="search-type" title="Click to Preview">{this.DocumentIcon}</div> + <div className="search-label">{this.props.doc.type ? this.props.doc.type : "Other"}</div> </div> <div className="link-container item"> <div className="link-count">{this.linkCount}</div> 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(<Repl />, document.getElementById("root"));
\ No newline at end of file +(async function () { + DocServer.init(window.location.protocol, window.location.hostname, 4321, "repl"); + ReactDOM.render(<Repl />, 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(( - <div style={{ position: "absolute", width: "100%", height: "100%" }}> - <Viewer /> - </div>), - document.getElementById('root') -);
\ No newline at end of file +(async function () { + await DocServer.init(window.location.protocol, window.location.hostname, 4321, "viewer"); + ReactDOM.render(( + <div style={{ position: "absolute", width: "100%", height: "100%" }}> + <Viewer /> + </div>), + 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<any> { - 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 e9ca256fa..58af074aa 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -153,6 +153,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); @@ -454,12 +480,21 @@ interface Map { } let clients: Map = {}; +let socketMap = new Map<SocketIO.Socket, string>(); +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); @@ -489,8 +524,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]) { |