aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/DocServer.ts4
-rw-r--r--src/client/util/CurrentUserUtils.ts18
-rw-r--r--src/client/util/GroupManager.tsx9
-rw-r--r--src/client/util/SettingsManager.tsx3
-rw-r--r--src/client/util/SharingManager.tsx3
-rw-r--r--src/client/util/TrackMovements.ts141
-rw-r--r--src/client/views/ContextMenu.scss22
-rw-r--r--src/client/views/ContextMenuItem.tsx84
-rw-r--r--src/client/views/DashboardView.tsx2
-rw-r--r--src/client/views/GlobalKeyHandler.ts4
-rw-r--r--src/client/views/Main.tsx51
-rw-r--r--src/client/views/MainView.tsx53
-rw-r--r--src/client/views/PropertiesDocContextSelector.tsx2
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx70
-rw-r--r--src/client/views/collections/CollectionView.tsx2
-rw-r--r--src/client/views/collections/TabDocView.tsx15
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx48
-rw-r--r--src/client/views/nodes/PDFBox.tsx5
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx2
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx2
-rw-r--r--src/client/views/topbar/TopBar.tsx2
-rw-r--r--src/fields/util.ts47
-rw-r--r--src/mobile/MobileInkOverlay.tsx119
-rw-r--r--src/mobile/MobileMain.tsx5
-rw-r--r--src/server/ApiManagers/UserManager.ts77
-rw-r--r--src/server/RouteManager.ts58
-rw-r--r--src/server/authentication/AuthenticationManager.ts31
-rw-r--r--src/server/authentication/DashUserModel.ts106
-rw-r--r--src/server/index.ts108
-rw-r--r--src/server/websocket.ts194
30 files changed, 680 insertions, 607 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 1b0ba6bc3..5a34fcf11 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -77,7 +77,7 @@ export namespace DocServer {
}
export function getFieldWriteMode(field: string) {
- return fieldWriteModes[field] || WriteMode.Default;
+ return Doc.CurrentUserEmail === 'guest' ? WriteMode.LiveReadonly : fieldWriteModes[field] || WriteMode.Default;
}
export function registerDocWithCachedUpdate(doc: Doc, field: string, oldValue: any) {
@@ -178,7 +178,7 @@ export namespace DocServer {
_isReadOnly = true;
_CreateField = field => (_cache[field[Id]] = field);
_UpdateField = emptyFunction;
- _RespondToUpdate = emptyFunction; // bcz: option: don't clear RespondToUpdate to continue to receive updates as others change the DB
+ // _RespondToUpdate = emptyFunction; // bcz: option: don't clear RespondToUpdate to continue to receive updates as others change the DB
}
}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 6c80cf0f4..bbf2ff3f9 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,6 +1,7 @@
import { reaction } from "mobx";
import * as rp from 'request-promise';
import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
+import { Id } from "../../fields/FieldSymbols";
import { List } from "../../fields/List";
import { PrefetchProxy } from "../../fields/Proxy";
import { RichTextField } from "../../fields/RichTextField";
@@ -14,6 +15,7 @@ import { DocServer } from "../DocServer";
import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents";
import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
import { TreeViewType } from "../views/collections/CollectionTreeView";
+import { DashboardView } from "../views/DashboardView";
import { Colors } from "../views/global/globalEnums";
import { MainView } from "../views/MainView";
import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox";
@@ -762,8 +764,6 @@ export class CurrentUserUtils {
doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates", system: true });
}
- // this is equivalent to using PrefetchProxies to make sure all the childClickFuncs have been retrieved.
- PromiseValue(Cast(doc["clickFuncs-child"], Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
if (doc.clickFuncs === undefined) {
const onClick = Docs.Create.ScriptingDocument(undefined, {
@@ -789,7 +789,6 @@ export class CurrentUserUtils {
}, "onCheckedClick");
doc.clickFuncs = Docs.Create.TreeDocument([onClick, onChildClick, onDoubleClick, onCheckedClick], { title: "onClick funcs", system: true });
}
- PromiseValue(Cast(doc.clickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
return doc.clickFuncs as Doc;
}
@@ -886,13 +885,20 @@ export class CurrentUserUtils {
public static async loadUserDocument(id: string) {
await rp.get(Utils.prepend("/getUserDocumentIds")).then(ids => {
const { userDocumentId, sharingDocumentId, linkDatabaseId } = JSON.parse(ids);
- if (userDocumentId !== "guest") {
+ if (userDocumentId) {
return DocServer.GetRefField(userDocumentId).then(async field => {
Docs.newAccount = !(field instanceof Doc);
await Docs.Prototypes.initialize();
const userDoc = Docs.newAccount ? new Doc(userDocumentId, true) : field as Doc;
- Docs.newAccount &&(userDoc.activePage = "home");
- return this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId);
+ this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId);
+ if (Docs.newAccount) {
+ if (Doc.CurrentUserEmail === "guest") {
+ DashboardView.createNewDashboard(undefined, "guest dashboard");
+ } else {
+ userDoc.activePage = "home";
+ }
+ }
+ return userDoc;
});
} else {
throw new Error("There should be a user id! Why does Dash think there isn't one?");
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 59334f6a2..c8b784390 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -14,6 +14,7 @@ import { GroupMemberView } from './GroupMemberView';
import { SharingManager, User } from './SharingManager';
import { listSpec } from '../../fields/Schema';
import { DateField } from '../../fields/DateField';
+import { Id } from '../../fields/FieldSymbols';
/**
* Interface for options for the react-select component
@@ -49,9 +50,11 @@ export class GroupManager extends React.Component<{}> {
* Fetches the list of users stored on the database.
*/
populateUsers = async () => {
- const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
- const raw = JSON.parse(userList) as User[];
- raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email)));
+ if (Doc.UserDoc()[Id] !== '__guest__') {
+ const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
+ const raw = JSON.parse(userList) as User[];
+ raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email)));
+ }
};
/**
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 12d1793af..cf143c5e8 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -4,6 +4,7 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { ColorState, SketchPicker } from 'react-color';
import { Doc } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
import { BoolCast, Cast, StrCast } from '../../fields/Types';
import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils';
import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
@@ -81,7 +82,7 @@ export class SettingsManager extends React.Component<{}> {
if (this.playgroundMode) {
DocServer.Control.makeReadOnly();
addStyleSheetRule(SettingsManager._settingsStyle, 'topbar-inner-container', { background: 'red !important' });
- } else DocServer.Control.makeEditable();
+ } else Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable();
});
@undoBatch
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 793027ea1..895bd3374 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -23,6 +23,7 @@ import { GroupMemberView } from './GroupMemberView';
import { SelectionManager } from './SelectionManager';
import './SharingManager.scss';
import { LinkManager } from './LinkManager';
+import { Id } from '../../fields/FieldSymbols';
export interface User {
email: string;
@@ -136,7 +137,7 @@ export class SharingManager extends React.Component<{}> {
* Populates the list of validated users (this.users) by adding registered users which have a sharingDocument.
*/
populateUsers = async () => {
- if (!this.populating) {
+ if (!this.populating && Doc.UserDoc()[Id] !== '__guest__') {
this.populating = true;
const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
const raw = JSON.parse(userList) as User[];
diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts
index d512e4802..4a2ccd706 100644
--- a/src/client/util/TrackMovements.ts
+++ b/src/client/util/TrackMovements.ts
@@ -1,27 +1,26 @@
-import { IReactionDisposer, observable, observe, reaction } from "mobx";
-import { NumCast } from "../../fields/Types";
-import { Doc, DocListCast } from "../../fields/Doc";
-import { CollectionDockingView } from "../views/collections/CollectionDockingView";
-import { Id } from "../../fields/FieldSymbols";
+import { IReactionDisposer, observable, observe, reaction } from 'mobx';
+import { NumCast } from '../../fields/Types';
+import { Doc, DocListCast } from '../../fields/Doc';
+import { CollectionDockingView } from '../views/collections/CollectionDockingView';
+import { Id } from '../../fields/FieldSymbols';
export type Movement = {
- time: number,
- panX: number,
- panY: number,
- scale: number,
- docId: string,
-}
+ time: number;
+ panX: number;
+ panY: number;
+ scale: number;
+ docId: string;
+};
export type Presentation = {
- movements: Movement[] | null,
- totalTime: number,
- meta: Object | Object[],
-}
+ movements: Movement[] | null;
+ totalTime: number;
+ meta: Object | Object[];
+};
export class TrackMovements {
-
private static get NULL_PRESENTATION(): Presentation {
- return { movements: null, meta: {}, totalTime: -1, }
+ return { movements: null, meta: {}, totalTime: -1 };
}
// instance variables
@@ -32,16 +31,17 @@ export class TrackMovements {
private recordingFFViews: Map<string, IReactionDisposer> | null;
private tabChangeDisposeFunc: IReactionDisposer | null;
-
// create static instance and getter for global use
@observable static _instance: TrackMovements;
- static get Instance(): TrackMovements { return TrackMovements._instance }
+ static get Instance(): TrackMovements {
+ return TrackMovements._instance;
+ }
constructor() {
// init the global instance
TrackMovements._instance = this;
// init the instance variables
- this.currentPresentation = TrackMovements.NULL_PRESENTATION
+ this.currentPresentation = TrackMovements.NULL_PRESENTATION;
this.tracking = false;
this.absoluteStart = -1;
@@ -52,28 +52,37 @@ export class TrackMovements {
// little helper :)
private get nullPresentation(): boolean {
- return this.currentPresentation.movements === null
+ return this.currentPresentation.movements === null;
}
private addRecordingFFView(doc: Doc, key: string = doc[Id]): void {
// console.info('adding dispose func : docId', key, 'doc', doc);
- if (this.recordingFFViews === null) { console.warn('addFFView on null RecordingApi'); return; }
- if (this.recordingFFViews.has(key)) { console.warn('addFFView : key already in map'); return; }
+ if (this.recordingFFViews === null) {
+ console.warn('addFFView on null RecordingApi');
+ return;
+ }
+ if (this.recordingFFViews.has(key)) {
+ console.warn('addFFView : key already in map');
+ return;
+ }
const disposeFunc = reaction(
- () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0)}),
- (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovement(res.x, res.y, key, res.scale),
+ () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0) }),
+ res => res.x !== -1 && res.y !== -1 && this.tracking && this.trackMovement(res.x, res.y, key, res.scale)
);
this.recordingFFViews?.set(key, disposeFunc);
}
private removeRecordingFFView = (key: string) => {
// console.info('removing dispose func : docId', key);
- if (this.recordingFFViews === null) { console.warn('removeFFView on null RecordingApi'); return; }
+ if (this.recordingFFViews === null) {
+ console.warn('removeFFView on null RecordingApi');
+ return;
+ }
this.recordingFFViews.get(key)?.();
this.recordingFFViews.delete(key);
- }
+ };
// in the case where only one tab was changed (updates not across dashboards), set only one to true
private updateRecordingFFViewsFromTabs = (tabbedDocs: Doc[], onlyOne = false) => {
@@ -86,7 +95,6 @@ export class TrackMovements {
if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc[Id]);
}
-
// new tab was added - need to add it
if (tabbedFFViews.size > this.recordingFFViews.size) {
for (const DashDoc of tabbedDocs) {
@@ -103,13 +111,13 @@ export class TrackMovements {
// tab was removed - need to remove it from recordingFFViews
else if (tabbedFFViews.size < this.recordingFFViews.size) {
for (const [key] of this.recordingFFViews) {
- if (!tabbedFFViews.has(key)) {
+ if (!tabbedFFViews.has(key)) {
this.removeRecordingFFView(key);
if (onlyOne) return;
- }
+ }
}
}
- }
+ };
private initTabTracker = () => {
if (this.recordingFFViews === null) {
@@ -117,18 +125,19 @@ export class TrackMovements {
}
// init the dispose funcs on the page
- const docList = DocListCast(CollectionDockingView.Instance.props.Document.data);
+ const docList = DocListCast(CollectionDockingView.Instance?.props.Document.data);
this.updateRecordingFFViewsFromTabs(docList);
// create a reaction to monitor changes in tabs
- this.tabChangeDisposeFunc =
- reaction(() => CollectionDockingView.Instance.props.Document.data,
- (change) => {
- // TODO: consider changing between dashboards
- // console.info('change in tabs', change);
- this.updateRecordingFFViewsFromTabs(DocListCast(change), true);
- });
- }
+ this.tabChangeDisposeFunc = reaction(
+ () => CollectionDockingView.Instance?.props.Document.data,
+ change => {
+ // TODO: consider changing between dashboards
+ // console.info('change in tabs', change);
+ this.updateRecordingFFViewsFromTabs(DocListCast(change), true);
+ }
+ );
+ };
start = (meta?: Object) => {
this.initTabTracker();
@@ -142,12 +151,12 @@ export class TrackMovements {
this.absoluteStart = startDate.getTime();
// (2) assign meta content if it exists
- this.currentPresentation.meta = meta || {}
+ this.currentPresentation.meta = meta || {};
// (3) assign start date to currentPresenation
- this.currentPresentation.movements = []
+ this.currentPresentation.movements = [];
// (4) set tracking true to allow trackMovements
- this.tracking = true
- }
+ this.tracking = true;
+ };
/* stops the video and returns the presentatation; if no presentation, returns undefined */
yieldPresentation(clearData: boolean = true): Presentation | null {
@@ -157,7 +166,7 @@ export class TrackMovements {
// set the previus recording view to the play view
// this.playFFView = this.recordingFFView;
- // ensure we add the endTime now that they are done recording
+ // ensure we add the endTime now that they are done recording
const cpy = { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart };
// reset the current presentation
@@ -169,10 +178,10 @@ export class TrackMovements {
finish = (): void => {
// make is tracking false
- this.tracking = false
+ this.tracking = false;
// reset the RecordingApi instance
this.clear();
- }
+ };
private clear = (): void => {
// clear the disposeFunc if we are done (not tracking)
@@ -187,44 +196,46 @@ export class TrackMovements {
}
// clear presenation data
- this.currentPresentation = TrackMovements.NULL_PRESENTATION
+ this.currentPresentation = TrackMovements.NULL_PRESENTATION;
// clear absoluteStart
- this.absoluteStart = -1
- }
+ this.absoluteStart = -1;
+ };
removeAllRecordingFFViews = () => {
- if (this.recordingFFViews === null) { console.warn('removeAllFFViews on null RecordingApi'); return; }
+ if (this.recordingFFViews === null) {
+ console.warn('removeAllFFViews on null RecordingApi');
+ return;
+ }
for (const [id, disposeFunc] of this.recordingFFViews) {
// console.info('calling dispose func : docId', id);
disposeFunc();
this.recordingFFViews.delete(id);
}
- }
+ };
private trackMovement = (panX: number, panY: number, docId: string, scale: number = 0) => {
// ensure we are recording to track
if (!this.tracking) {
- console.error('[recordingApi.ts] trackMovements(): tracking is false')
+ console.error('[recordingApi.ts] trackMovements(): tracking is false');
return;
}
// check to see if the presetation is init - if not, we are between segments
// TODO: make this more clear - tracking should be "live tracking", not always true when the recording api being used (between start and yieldPres)
- // bacuse tracking should be false inbetween segments high key
+ // bacuse tracking should be false inbetween segments high key
if (this.nullPresentation) {
- console.warn('[recordingApi.ts] trackMovements(): trying to store movemetns between segments')
+ console.warn('[recordingApi.ts] trackMovements(): trying to store movemetns between segments');
return;
}
// get the time
- const time = new Date().getTime() - this.absoluteStart
+ const time = new Date().getTime() - this.absoluteStart;
// make new movement object
- const movement: Movement = { time, panX, panY, scale, docId }
+ const movement: Movement = { time, panX, panY, scale, docId };
// add that movement to the current presentation data's movement array
- this.currentPresentation.movements && this.currentPresentation.movements.push(movement)
- }
-
+ this.currentPresentation.movements && this.currentPresentation.movements.push(movement);
+ };
// method that concatenates an array of presentatations into one
public concatPresentations = (presentations: Presentation[]): Presentation => {
@@ -233,13 +244,15 @@ export class TrackMovements {
let sumTime = 0;
let combinedMetas: any[] = [];
- presentations.forEach((presentation) => {
+ presentations.forEach(presentation => {
const { movements, totalTime, meta } = presentation;
// update movements if they had one
if (movements) {
// add the summed time to the movements
- const addedTimeMovements = movements.map(move => { return { ...move, time: move.time + sumTime } });
+ const addedTimeMovements = movements.map(move => {
+ return { ...move, time: move.time + sumTime };
+ });
// concat the movements already in the combined presentation with these new ones
combinedMovements.push(...addedTimeMovements);
}
@@ -252,6 +265,6 @@ export class TrackMovements {
});
// return the combined presentation with the updated total summed time
- return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas };
- }
+ return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas };
+ };
}
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index ea24dbf6d..1e6a377de 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -1,10 +1,10 @@
-@import "global/globalCssVariables";
+@import 'global/globalCssVariables';
.contextMenu-cont {
position: absolute;
display: flex;
z-index: 100000;
- box-shadow: 0px 3px 4px rgba(0,0,0,30%);
+ box-shadow: 0px 3px 4px rgba(0, 0, 0, 30%);
flex-direction: column;
background: whitesmoke;
border-radius: 3px;
@@ -28,9 +28,9 @@
position: absolute;
display: flex;
z-index: 1000;
- box-shadow: #AAAAAA .2vw .2vw .4vw;
+ box-shadow: #aaaaaa 0.2vw 0.2vw 0.4vw;
flex-direction: column;
- border: 1px solid #BBBBBBBB;
+ border: 1px solid #bbbbbbbb;
border-radius: 15px;
padding-top: 10px;
padding-bottom: 10px;
@@ -49,7 +49,7 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- transition: all .1s;
+ transition: all 0.1s;
border-style: none;
// padding: 10px 0px 10px 0px;
white-space: nowrap;
@@ -58,7 +58,7 @@
text-transform: uppercase;
padding-right: 30px;
- .icon-background {
+ .contextMenu-item-icon-background {
pointer-events: all;
background-color: transparent;
width: 35px;
@@ -78,7 +78,7 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- transition: all .1s;
+ transition: all 0.1s;
border-style: none;
// padding: 10px 0px 10px 0px;
white-space: nowrap;
@@ -89,7 +89,7 @@
}
.contextMenu-item:hover {
- border-width: .11px;
+ border-width: 0.11px;
border-style: none;
border-color: $medium-gray; // rgb(187, 186, 186);
border-bottom-style: solid;
@@ -116,8 +116,8 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- transition: all .1s;
- border-width: .11px;
+ transition: all 0.1s;
+ border-width: 0.11px;
border-style: none;
border-color: $medium-gray; // rgb(187, 186, 186);
// padding: 10px 0px 10px 0px;
@@ -152,4 +152,4 @@
padding-left: 10px;
border: solid black 1px;
border-radius: 5px;
-} \ No newline at end of file
+}
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 30073e21f..dc9c2eb6c 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -1,9 +1,9 @@
-import React = require("react");
-import { observable, action, runInAction } from "mobx";
-import { observer } from "mobx-react";
+import React = require('react');
+import { observable, action, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { UndoManager } from "../util/UndoManager";
+import { UndoManager } from '../util/UndoManager';
export interface OriginalMenuProps {
description: string;
@@ -37,7 +37,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
}
handleEvent = async (e: React.MouseEvent<HTMLDivElement>) => {
- if ("event" in this.props) {
+ if ('event' in this.props) {
this.props.closeMenu?.();
let batch: UndoManager.Batch | undefined;
if (this.props.undoable !== false) {
@@ -46,7 +46,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
await this.props.event({ x: e.clientX, y: e.clientY });
batch?.end();
}
- }
+ };
currentTimeout?: any;
static readonly timeout = 300;
@@ -62,8 +62,11 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
}
this._overPosY = e.clientY;
this._overPosX = e.clientX;
- this.currentTimeout = setTimeout(action(() => this.overItem = true), ContextMenuItem.timeout);
- }
+ this.currentTimeout = setTimeout(
+ action(() => (this.overItem = true)),
+ ContextMenuItem.timeout
+ );
+ };
onPointerLeave = () => {
if (this.currentTimeout) {
@@ -73,57 +76,68 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
if (!this.overItem) {
return;
}
- this.currentTimeout = setTimeout(action(() => this.overItem = false), ContextMenuItem.timeout);
- }
+ this.currentTimeout = setTimeout(
+ action(() => (this.overItem = false)),
+ ContextMenuItem.timeout
+ );
+ };
render() {
- if ("event" in this.props) {
+ if ('event' in this.props) {
return (
- <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} onPointerDown={this.handleEvent}>
+ <div className={'contextMenu-item' + (this.props.selected ? ' contextMenu-itemSelected' : '')} onPointerDown={this.handleEvent}>
{this.props.icon ? (
- <span className="icon-background">
+ <span className="contextMenu-item-icon-background">
<FontAwesomeIcon icon={this.props.icon} size="sm" />
</span>
) : null}
- <div className="contextMenu-description">
- {this.props.description.replace(":", "")}
- </div>
+ <div className="contextMenu-description">{this.props.description.replace(':', '')}</div>
</div>
);
- } else if ("subitems" in this.props) {
- const where = !this.overItem ? "" : this._overPosY < window.innerHeight / 3 ? "flex-start" : this._overPosY > window.innerHeight * 2 / 3 ? "flex-end" : "center";
- const marginTop = !this.overItem ? "" : this._overPosY < window.innerHeight / 3 ? "20px" : this._overPosY > window.innerHeight * 2 / 3 ? "-20px" : "";
+ } else if ('subitems' in this.props) {
+ const where = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? 'flex-start' : this._overPosY > (window.innerHeight * 2) / 3 ? 'flex-end' : 'center';
+ const marginTop = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? '20px' : this._overPosY > (window.innerHeight * 2) / 3 ? '-20px' : '';
// here
- const submenu = !this.overItem ? (null) :
- <div className="contextMenu-subMenu-cont"
+ const submenu = !this.overItem ? null : (
+ <div
+ className="contextMenu-subMenu-cont"
style={{
- marginLeft: window.innerHeight - this._overPosX - 50 > 0 ? "90%" : "20%", marginTop
+ marginLeft: window.innerHeight - this._overPosX - 50 > 0 ? '90%' : '20%',
+ marginTop,
}}>
- {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
- </div>;
+ {this._items.map(prop => (
+ <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />
+ ))}
+ </div>
+ );
if (!(this.props as SubmenuProps).noexpand) {
- return <div className="contextMenu-inlineMenu">
- {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
- </div>;
+ return (
+ <div className="contextMenu-inlineMenu">
+ {this._items.map(prop => (
+ <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />
+ ))}
+ </div>
+ );
}
return (
- <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")}
- style={{ alignItems: where, borderTop: this.props.addDivider ? "solid 1px" : undefined }}
- onMouseLeave={this.onPointerLeave} onMouseEnter={this.onPointerEnter}>
+ <div
+ className={'contextMenu-item' + (this.props.selected ? ' contextMenu-itemSelected' : '')}
+ style={{ alignItems: where, borderTop: this.props.addDivider ? 'solid 1px' : undefined }}
+ onMouseLeave={this.onPointerLeave}
+ onMouseEnter={this.onPointerEnter}>
{this.props.icon ? (
- <span className="icon-background" onMouseEnter={this.onPointerLeave} style={{ alignItems: "center", alignSelf: "center" }}>
+ <span className="contextMenu-item-icon-background" onMouseEnter={this.onPointerLeave} style={{ alignItems: 'center', alignSelf: 'center' }}>
<FontAwesomeIcon icon={this.props.icon} size="sm" />
</span>
) : null}
- <div className="contextMenu-description" onMouseEnter={this.onPointerEnter}
- style={{ alignItems: "center", alignSelf: "center" }} >
+ <div className="contextMenu-description" onMouseEnter={this.onPointerEnter} style={{ alignItems: 'center', alignSelf: 'center' }}>
{this.props.description}
- <FontAwesomeIcon icon={"angle-right"} size="lg" style={{ position: "absolute", right: "10px" }} />
+ <FontAwesomeIcon icon={'angle-right'} size="lg" style={{ position: 'absolute', right: '10px' }} />
</div>
{submenu}
</div>
);
}
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 526a32f5a..9ea9128bd 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -238,7 +238,7 @@ export class DashboardView extends React.Component {
} else if (doc.readOnly) {
DocServer.Control.makeReadOnly();
} else {
- DocServer.Control.makeEditable();
+ Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable();
}
}
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 73e0c9933..85f579975 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -116,8 +116,8 @@ export class KeyManager {
var doDeselect = true;
if (SnappingManager.GetIsDragging()) {
DragManager.AbortDrag();
- } else if (CollectionDockingView.Instance.HasFullScreen) {
- CollectionDockingView.Instance.CloseFullScreen();
+ } else if (CollectionDockingView.Instance?.HasFullScreen) {
+ CollectionDockingView.Instance?.CloseFullScreen();
} else if (CollectionStackedTimeline.SelectingRegion) {
CollectionStackedTimeline.SelectingRegion = undefined;
doDeselect = false;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index e998f1fb9..4cb1183d0 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -5,6 +5,8 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { AssignAllExtensions } from '../../extensions/General/Extensions';
+import { Utils } from '../../Utils';
+import { DocServer } from '../DocServer';
import { Docs } from '../documents/Documents';
import { CurrentUserUtils } from '../util/CurrentUserUtils';
import { LinkManager } from '../util/LinkManager'; // this must come before importing Docs and CurrentUserUtils
@@ -19,31 +21,28 @@ AssignAllExtensions();
MainView.Live = window.location.search.includes('live');
window.location.search.includes('safe') && CollectionView.SetSafeMode(true);
const info = await CurrentUserUtils.loadCurrentUser();
- if (info.id !== '__guest__') {
- // a guest will not have an id registered
- await CurrentUserUtils.loadUserDocument(info.id);
- } else {
- await Docs.Prototypes.initialize();
+ if (info.email === 'guest') DocServer.Control.makeReadOnly();
+ await CurrentUserUtils.loadUserDocument(info.id);
+ setTimeout(() => {
+ document.getElementById('root')!.addEventListener(
+ 'wheel',
+ event => {
+ if (event.ctrlKey) {
+ event.preventDefault();
+ }
+ },
+ true
+ );
+ const startload = (document as any).startLoad;
+ const loading = Date.now() - (startload ? Number(startload) : Date.now() - 3000);
+ console.log('Loading Time = ' + loading);
+ const d = new Date();
+ d.setTime(d.getTime() + 100 * 24 * 60 * 60 * 1000);
+ const expires = 'expires=' + d.toUTCString();
+ document.cookie = `loadtime=${loading};${expires};path=/`;
new LinkManager();
- }
- document.getElementById('root')!.addEventListener(
- 'wheel',
- event => {
- if (event.ctrlKey) {
- event.preventDefault();
- }
- },
- true
- );
- const startload = (document as any).startLoad;
- const loading = Date.now() - (startload ? Number(startload) : Date.now() - 3000);
- console.log('Loading Time = ' + loading);
- const d = new Date();
- d.setTime(d.getTime() + 100 * 24 * 60 * 60 * 1000);
- const expires = 'expires=' + d.toUTCString();
- document.cookie = `loadtime=${loading};${expires};path=/`;
- new LinkManager();
- new TrackMovements();
- new ReplayMovements();
- ReactDOM.render(<MainView />, document.getElementById('root'));
+ new TrackMovements();
+ new ReplayMovements();
+ ReactDOM.render(<MainView />, document.getElementById('root'));
+ }, 0);
})();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b61cd3409..7e032af5e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -3,7 +3,7 @@ import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons';
import * as far from '@fortawesome/free-regular-svg-icons';
import * as fa from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, configure, observable, reaction } from 'mobx';
+import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
@@ -77,12 +77,15 @@ export class MainView extends React.Component {
@observable private _panelContent: string = 'none';
@observable private _sidebarContent: any = Doc.MyLeftSidebarPanel;
@observable private _leftMenuFlyoutWidth: number = 0;
+ @computed get _hideUI() {
+ return this.mainDoc && this.mainDoc._viewType !== CollectionViewType.Docking;
+ }
@computed private get dashboardTabHeight() {
- return 27;
+ return this._hideUI ? 0 : 27;
} // 27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js
@computed private get topOfDashUI() {
- return Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', ''));
+ return this._hideUI ? 0 : Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', ''));
}
@computed private get topOfHeaderBarDoc() {
return this.topOfDashUI;
@@ -105,7 +108,12 @@ export class MainView extends React.Component {
@computed private get colorScheme() {
return StrCast(Doc.ActiveDashboard?.colorScheme);
}
+ @observable mainDoc: Opt<Doc>;
@computed private get mainContainer() {
+ if (window.location.pathname.startsWith('/doc/')) {
+ DocServer.GetRefField(window.location.pathname.substring('/doc/'.length)).then(main => runInAction(() => (this.mainDoc = main as Doc)));
+ return this.mainDoc;
+ }
return this.userDoc ? Doc.ActiveDashboard : Doc.GuestDashboard;
}
@computed private get headerBarDoc() {
@@ -116,10 +124,10 @@ export class MainView extends React.Component {
}
headerBarDocWidth = () => this.mainDocViewWidth();
- headerBarDocHeight = () => SettingsManager.headerBarHeight ?? 0;
- topMenuHeight = () => 35;
+ headerBarDocHeight = () => (this._hideUI ? 0 : SettingsManager.headerBarHeight ?? 0);
+ topMenuHeight = () => (this._hideUI ? 0 : 35);
topMenuWidth = returnZero; // value is ignored ...
- leftMenuWidth = () => Number(LEFT_MENU_WIDTH.replace('px', ''));
+ leftMenuWidth = () => (this._hideUI ? 0 : Number(LEFT_MENU_WIDTH.replace('px', '')));
leftMenuHeight = () => this._dashUIHeight;
leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth;
leftMenuFlyoutHeight = () => this._dashUIHeight;
@@ -212,7 +220,8 @@ export class MainView extends React.Component {
const pathname = window.location.pathname.substr(1).split('/');
if (pathname.length > 1 && pathname[0] === 'doc') {
Doc.MainDocId = pathname[1];
- !this.userDoc && DocServer.GetRefField(pathname[1]).then(action(field => field instanceof Doc && (Doc.GuestTarget = field)));
+ //!this.userDoc &&
+ DocServer.GetRefField(pathname[1]).then(action(field => field instanceof Doc && (Doc.GuestTarget = field)));
}
}
@@ -455,7 +464,11 @@ export class MainView extends React.Component {
AudioBox.Enabled = true;
const targets = document.elementsFromPoint(e.x, e.y);
if (targets.length) {
- const targClass = targets[0].className.toString();
+ let targClass = targets[0].className.toString();
+ for (let i = 0; i < targets.length - 1; i++) {
+ if (typeof targets[i].className === 'object') targClass = targets[i + 1].className.toString();
+ else break;
+ }
!targClass.includes('contextMenu') && ContextMenu.Instance.closeMenu();
!['timeline-menu-desc', 'timeline-menu-item', 'timeline-menu-input'].includes(targClass) && TimelineMenu.Instance.closeMenu();
}
@@ -517,7 +530,7 @@ export class MainView extends React.Component {
return () => (this._exploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
}
headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1);
-
+ mainScreenToLocalXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.topOfMainDocContent, 1);
@computed get headerBarDocView() {
return (
<div className="mainView-headerBar" style={{ height: this.headerBarDocHeight() }}>
@@ -557,7 +570,7 @@ export class MainView extends React.Component {
@computed get mainDocView() {
return (
<>
- {this.headerBarDocView}
+ {this._hideUI ? null : this.headerBarDocView}
<DocumentView
key="main"
Document={this.mainContainer!}
@@ -566,11 +579,11 @@ export class MainView extends React.Component {
addDocTab={this.addDocTabFunc}
pinToPres={emptyFunction}
docViewPath={returnEmptyDoclist}
- styleProvider={undefined}
+ styleProvider={this._hideUI ? DefaultStyleProvider : undefined}
rootSelected={returnTrue}
isContentActive={returnTrue}
removeDocument={undefined}
- ScreenToLocalTransform={Transform.Identity}
+ ScreenToLocalTransform={this._hideUI ? this.mainScreenToLocalXf : Transform.Identity}
PanelWidth={this.mainDocViewWidth}
PanelHeight={this.mainDocViewHeight}
focus={DocUtils.DefaultFocus}
@@ -582,7 +595,7 @@ export class MainView extends React.Component {
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
suppressSetHeight={true}
- renderDepth={-1}
+ renderDepth={this._hideUI ? 0 : -1}
/>
</>
);
@@ -750,7 +763,7 @@ export class MainView extends React.Component {
const width = this.propertiesWidth() + leftMenuFlyoutWidth;
return (
<>
- {this.leftMenuPanel}
+ {this._hideUI ? null : this.leftMenuPanel}
<div key="inner" className={`mainView-innerContent${this.colorScheme}`}>
{this.flyout}
<div className="mainView-libraryHandle" style={{ left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? 'none' : undefined }} onPointerDown={this.onFlyoutPointerDown}>
@@ -759,9 +772,11 @@ export class MainView extends React.Component {
<div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)` }}>
{this.dockingContent}
- <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1 }}>
- <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? 'chevron-left' : 'chevron-right'} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" />
- </div>
+ {this._hideUI ? null : (
+ <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1 }}>
+ <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? 'chevron-left' : 'chevron-right'} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" />
+ </div>
+ )}
<div className="properties-container" style={{ width: this.propertiesWidth() }}>
{this.propertiesWidth() < 10 ? null : <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={this.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
</div>
@@ -962,7 +977,7 @@ export class MainView extends React.Component {
<GoogleAuthenticationManager />
<DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfHeaderBarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
<ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
- <TopBar />
+ {this._hideUI ? null : <TopBar />}
{LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
{DocumentLinksButton.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocumentLinksButton.LinkEditorDocView = undefined))} docView={DocumentLinksButton.LinkEditorDocView} /> : null}
{LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : null}
@@ -973,7 +988,7 @@ export class MainView extends React.Component {
default:
return (
<>
- <div style={{ position: 'relative', display: LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 1999 }}>
+ <div style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 1999 }}>
<CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
</div>
{this.mainDashboardArea}
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index 0f63ebc1d..9d89ee036 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -40,7 +40,7 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
.keys()
);
return doclayouts
- .filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document))
+ .filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance?.props.Document))
.filter(doc => !Doc.IsSystem(doc))
.map(doc => ({ col: doc, target }));
}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 42f9bb981..ed9054107 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -29,7 +29,7 @@ const _global = (window /* browser */ || global) /* node */ as any;
@observer
export class CollectionDockingView extends CollectionSubView() {
- @observable public static Instance: CollectionDockingView;
+ @observable public static Instance: CollectionDockingView | undefined;
public static makeDocumentConfig(document: Doc, panelName?: string, width?: number) {
return {
type: 'react-component',
@@ -103,12 +103,14 @@ export class CollectionDockingView extends CollectionSubView() {
@undoBatch
public static CloseSplit(document: Opt<Doc>, panelName?: string): boolean {
- const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tab => (panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document));
- if (tab) {
- const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
- if (j !== -1) {
- tab.header.parent.contentItems[j].remove();
- return CollectionDockingView.Instance.layoutChanged();
+ if (CollectionDockingView.Instance) {
+ const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tab => (panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document));
+ if (tab) {
+ const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
+ if (j !== -1) {
+ tab.header.parent.contentItems[j].remove();
+ return CollectionDockingView.Instance.layoutChanged();
+ }
}
}
@@ -119,19 +121,21 @@ export class CollectionDockingView extends CollectionSubView() {
public static OpenFullScreen(doc: Doc) {
SelectionManager.DeselectAll();
const instance = CollectionDockingView.Instance;
- if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === 'layout') {
- return DashboardView.openDashboard(doc);
+ if (instance) {
+ if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === 'layout') {
+ return DashboardView.openDashboard(doc);
+ }
+ const newItemStackConfig = {
+ type: 'stack',
+ content: [CollectionDockingView.makeDocumentConfig(Doc.MakeAlias(doc))],
+ };
+ const docconfig = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
+ instance._goldenLayout.root.contentItems[0].addChild(docconfig);
+ docconfig.callDownwards('_$init');
+ instance._goldenLayout._$maximiseItem(docconfig);
+ instance._goldenLayout.emit('stateChanged');
+ instance.stateChanged();
}
- const newItemStackConfig = {
- type: 'stack',
- content: [CollectionDockingView.makeDocumentConfig(Doc.MakeAlias(doc))],
- };
- const docconfig = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
- instance._goldenLayout.root.contentItems[0].addChild(docconfig);
- docconfig.callDownwards('_$init');
- instance._goldenLayout._$maximiseItem(docconfig);
- instance._goldenLayout.emit('stateChanged');
- instance.stateChanged();
return true;
}
@@ -146,21 +150,23 @@ export class CollectionDockingView extends CollectionSubView() {
const newContentItem = stack.layoutManager.createContentItem(newConfig, instance._goldenLayout);
stack.addChild(newContentItem.contentItems[0], undefined);
stack.contentItems[activeContentItemIndex].remove();
- return CollectionDockingView.Instance.layoutChanged();
+ return instance.layoutChanged();
}
- const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tab => tab.contentItem.config.props.panelName === panelName);
+ const tab = Array.from(instance.tabMap.keys()).find(tab => tab.contentItem.config.props.panelName === panelName);
if (tab) {
tab.header.parent.addChild(newConfig, undefined);
const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
!addToSplit && j !== -1 && tab.header.parent.contentItems[j].remove();
- return CollectionDockingView.Instance.layoutChanged();
+ return instance.layoutChanged();
}
return CollectionDockingView.AddSplit(document, panelName, stack, panelName);
}
@undoBatch
public static ToggleSplit(doc: Doc, location: string, stack?: any, panelName?: string) {
- return Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex(tab => tab.DashDoc === doc) !== -1 ? CollectionDockingView.CloseSplit(doc) : CollectionDockingView.AddSplit(doc, location, stack, panelName);
+ return CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex(tab => tab.DashDoc === doc) !== -1
+ ? CollectionDockingView.CloseSplit(doc)
+ : CollectionDockingView.AddSplit(doc, location, stack, panelName);
}
//
@@ -170,7 +176,7 @@ export class CollectionDockingView extends CollectionSubView() {
@action
public static AddSplit(document: Doc, pullSide: string, stack?: any, panelName?: string) {
if (document._viewType === CollectionViewType.Docking) return DashboardView.openDashboard(document);
-
+ if (!CollectionDockingView.Instance) return false;
const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document);
if (tab) {
tab.header.parent.setActiveContentItem(tab.contentItem);
@@ -453,13 +459,15 @@ export class CollectionDockingView extends CollectionSubView() {
Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc);
Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
}
- const dview = CollectionDockingView.Instance.props.Document;
- const fieldKey = CollectionDockingView.Instance.props.fieldKey;
- Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc);
- this.tabMap.delete(tab);
- tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
- tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
- this.stateChanged();
+ if (CollectionDockingView.Instance) {
+ const dview = CollectionDockingView.Instance.props.Document;
+ const fieldKey = CollectionDockingView.Instance.props.fieldKey;
+ Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc);
+ this.tabMap.delete(tab);
+ tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
+ tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
+ this.stateChanged();
+ }
};
tabCreated = (tab: any) => {
this.tabMap.add(tab);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 2ab5f6247..f0cb23eab 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -225,7 +225,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
},
})
);
- DocListCast(Cast(Doc.UserDoc()['clickFuncs-child'], Doc, null).data).forEach(childClick =>
+ DocListCast(Cast(Doc.UserDoc()['clickFuncs-child'], Doc, null)?.data).forEach(childClick =>
onClicks.push({
description: `Set child ${childClick.title}`,
icon: 'edit',
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index b8aaea622..2d08b1c09 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -302,6 +302,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array
});
if (
+ CollectionDockingView.Instance &&
!Array.from(CollectionDockingView.Instance.tabMap)
.map(d => d.DashDoc)
.includes(curPres)
@@ -420,7 +421,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
ScreenToLocalTransform = () => {
this._forceInvalidateScreenToLocal;
const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
- return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY);
+ return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY) ?? Transform.Identity();
};
PanelWidth = () => this._panelWidth;
PanelHeight = () => this._panelHeight;
@@ -449,9 +450,9 @@ export class TabDocView extends React.Component<TabDocViewProps> {
PanelWidth={this.PanelWidth}
PanelHeight={this.PanelHeight}
styleProvider={DefaultStyleProvider}
- docFilters={CollectionDockingView.Instance.childDocFilters}
- docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters}
- searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
+ docFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
+ docRangeFilters={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
+ searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
addDocument={undefined}
removeDocument={this.remDocTab}
addDocTab={this.addDocTab}
@@ -624,9 +625,9 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
styleProvider={TabMinimapView.miniStyleProvider}
addDocTab={this.props.addDocTab}
pinToPres={TabDocView.PinDoc}
- docFilters={CollectionDockingView.Instance.childDocFilters}
- docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters}
- searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
+ docFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
+ docRangeFilters={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
+ searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
fitContentsToBox={returnTrue}
/>
<div className="miniOverlay" onPointerDown={this.miniDown}>
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index bf4c029b2..c4b42301e 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Autocomplete, GoogleMap, GoogleMapProps, Marker } from '@react-google-maps/api';
-import { action, computed, IReactionDisposer, observable, ObservableMap } from 'mobx';
+import { action, computed, IReactionDisposer, observable, ObservableMap, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, Opt, WidthSym } from '../../../../fields/Doc';
@@ -12,6 +12,7 @@ import { emptyFunction, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Uti
import { Docs } from '../../../documents/Documents';
import { DragManager } from '../../../util/DragManager';
import { SnappingManager } from '../../../util/SnappingManager';
+import { UndoManager } from '../../../util/UndoManager';
import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
import { Colors } from '../../global/globalEnums';
@@ -368,23 +369,27 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
setupMoveUpEvents(
this,
e,
- (e, down, delta) => {
- const localDelta = this.props
- .ScreenToLocalTransform()
- .scale(this.props.scaling?.() || 1)
- .transformDirection(delta[0], delta[1]);
- const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']);
- const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
- const ratio = (curNativeWidth + localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth;
- if (ratio >= 1) {
- this.layoutDoc.nativeWidth = nativeWidth * ratio;
- this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0];
- this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
- }
- return false;
- },
+ (e, down, delta) =>
+ runInAction(() => {
+ const localDelta = this.props
+ .ScreenToLocalTransform()
+ .scale(this.props.scaling?.() || 1)
+ .transformDirection(delta[0], delta[1]);
+ const fullWidth = this.layoutDoc[WidthSym]();
+ const mapWidth = fullWidth - this.sidebarWidth();
+ if (this.sidebarWidth() + localDelta[0] > 0) {
+ this._showSidebar = true;
+ this.layoutDoc._width = fullWidth + localDelta[0];
+ this.layoutDoc._sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%';
+ } else {
+ this._showSidebar = false;
+ this.layoutDoc._width = mapWidth;
+ this.layoutDoc._sidebarWidthPercent = '0%';
+ }
+ return false;
+ }),
emptyFunction,
- this.toggleSidebar
+ () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map')
);
};
@@ -470,13 +475,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
// TODO: Adding highlight box layer to Maps
@action
toggleSidebar = () => {
+ //1.2 * w * ? = .2 * w .2/1.2
const prevWidth = this.sidebarWidth();
- this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%';
- this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
+ this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? `${(100 * 0.2) / 1.2}%` : '0%') !== '0%';
+ this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
};
sidebarDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false);
+ setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
};
sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
const bounds = this._ref.current!.getBoundingClientRect();
@@ -605,7 +611,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
{this.annotationLayer}
<GoogleMap mapContainerStyle={mapContainerStyle} onZoomChanged={this.zoomChanged} onCenterChanged={this.centered} onLoad={this.loadHandler} options={mapOptions}>
<Autocomplete onLoad={this.setSearchBox} onPlaceChanged={this.handlePlaceChanged}>
- <input className="mapBox-input" ref={this.inputRef} type="text" placeholder="Enter location" />
+ <input className="mapBox-input" ref={this.inputRef} type="text" onKeyDown={e => e.stopPropagation()} placeholder="Enter location" />
</Autocomplete>
{this.renderMarkers()}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 5b9d90780..f41f6a1ad 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -13,20 +13,19 @@ import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { KeyCodes } from '../../util/KeyCodes';
import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
import { Colors } from '../global/globalEnums';
import { CreateImage } from '../nodes/WebBoxRenderer';
-import { AnchorMenu } from '../pdf/AnchorMenu';
import { PDFViewer } from '../pdf/PDFViewer';
import { SidebarAnnos } from '../SidebarAnnos';
import { FieldView, FieldViewProps } from './FieldView';
import { ImageBox } from './ImageBox';
-import './PDFBox.scss';
import { VideoBox } from './VideoBox';
+import './PDFBox.scss';
import React = require('react');
-import { CollectionFreeFormView } from '../collections/collectionFreeForm';
@observer
export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index cfaa428f9..e945920da 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -640,7 +640,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
};
sidebarDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false);
+ setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
};
sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
const bounds = this._ref.current!.getBoundingClientRect();
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 292c187e4..6e5eb3300 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -399,7 +399,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const presCollection = Cast(this.layoutDoc.presCollection, Doc, null);
const collectionDocView = presCollection ? DocumentManager.Instance.getDocumentView(presCollection) : undefined;
const includesDoc: boolean = DocListCast(presCollection?.data).includes(targetDoc);
- const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === srcContext);
+ const tab = CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === srcContext);
this.turnOffEdit();
// Handles the setting of presCollection
if (includesDoc) {
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 0bf1575fb..3fc0a237e 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -26,7 +26,7 @@ import './TopBar.scss';
@observer
export class TopBar extends React.Component {
navigateToHome = () => {
- CollectionDockingView.Instance.CaptureThumbnail()?.then(() => {
+ CollectionDockingView.Instance?.CaptureThumbnail()?.then(() => {
Doc.ActivePage = 'home';
DashboardView.closeActiveDashboard(); // bcz: if we do this, we need some other way to keep track, for user convenience, of the last dashboard in use
});
diff --git a/src/fields/util.ts b/src/fields/util.ts
index cbb560114..41e723119 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,41 +1,40 @@
+import { action, observable, runInAction, trace } from 'mobx';
+import { computedFn } from 'mobx-utils';
+import { DocServer } from '../client/DocServer';
+import { SerializationHelper } from '../client/util/SerializationHelper';
import { UndoManager } from '../client/util/UndoManager';
+import { returnZero } from '../Utils';
+import CursorField from './CursorField';
import {
- Doc,
- FieldResult,
- UpdatingFromServer,
- LayoutSym,
- AclPrivate,
+ AclAdmin,
+ AclAugment,
AclEdit,
+ AclPrivate,
AclReadonly,
- AclAugment,
+ AclSelfEdit,
AclSym,
+ AclUnset,
DataSym,
+ Doc,
DocListCast,
- AclAdmin,
- HeightSym,
- WidthSym,
- updateCachedAcls,
- AclUnset,
DocListCastAsync,
+ FieldResult,
ForceServerWrite,
+ HeightSym,
Initializing,
- AclSelfEdit,
+ LayoutSym,
+ updateCachedAcls,
+ UpdatingFromServer,
+ WidthSym,
} from './Doc';
-import { SerializationHelper } from '../client/util/SerializationHelper';
-import { ProxyField, PrefetchProxy } from './Proxy';
-import { RefField } from './RefField';
+import { Id, OnUpdate, Parent, Self, SelfProxy, Update } from './FieldSymbols';
+import { List } from './List';
import { ObjectField } from './ObjectField';
-import { action, observable, runInAction, trace } from 'mobx';
-import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from './FieldSymbols';
-import { DocServer } from '../client/DocServer';
+import { PrefetchProxy, ProxyField } from './Proxy';
+import { RefField } from './RefField';
+import { RichTextField } from './RichTextField';
import { ComputedField } from './ScriptField';
import { ScriptCast, StrCast } from './Types';
-import { returnZero } from '../Utils';
-import CursorField from './CursorField';
-import { List } from './List';
-import { SnappingManager } from '../client/util/SnappingManager';
-import { computedFn } from 'mobx-utils';
-import { RichTextField } from './RichTextField';
function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx
index d668d134e..6415099fd 100644
--- a/src/mobile/MobileInkOverlay.tsx
+++ b/src/mobile/MobileInkOverlay.tsx
@@ -1,13 +1,12 @@
import React = require('react');
-import { observer } from "mobx-react";
-import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "../server/Message";
-import { observable, action } from "mobx";
-import { GestureUtils } from "../pen-gestures/GestureUtils";
-import "./MobileInkOverlay.scss";
-import { DragManager } from "../client/util/DragManager";
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
import { DocServer } from '../client/DocServer';
+import { DragManager } from '../client/util/DragManager';
import { Doc } from '../fields/Doc';
-
+import { GestureUtils } from '../pen-gestures/GestureUtils';
+import { GestureContent, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent } from '../server/Message';
+import './MobileInkOverlay.scss';
@observer
export default class MobileInkOverlay extends React.Component {
@@ -18,7 +17,7 @@ export default class MobileInkOverlay extends React.Component {
@observable private _height: number = 0;
@observable private _x: number = -300;
@observable private _y: number = -300;
- @observable private _text: string = "";
+ @observable private _text: string = '';
@observable private _offsetX: number = 0;
@observable private _offsetY: number = 0;
@@ -49,7 +48,7 @@ export default class MobileInkOverlay extends React.Component {
this._scale = scaledSize.scale;
this._x = 300; // TODO: center on screen
this._y = 25; // TODO: center on screen
- this._text = text ? text : "";
+ this._text = text ? text : '';
}
@action
@@ -64,31 +63,29 @@ export default class MobileInkOverlay extends React.Component {
// TODO: figure out why strokes drawn in corner of mobile interface dont get inserted
const { points, bounds } = content;
- console.log("received points", points, bounds);
+ console.log('received points', points, bounds);
const B = {
- right: (bounds.right * this._scale) + this._x,
- left: (bounds.left * this._scale) + this._x, // TODO: scale
- bottom: (bounds.bottom * this._scale) + this._y,
- top: (bounds.top * this._scale) + this._y, // TODO: scale
+ right: bounds.right * this._scale + this._x,
+ left: bounds.left * this._scale + this._x, // TODO: scale
+ bottom: bounds.bottom * this._scale + this._y,
+ top: bounds.top * this._scale + this._y, // TODO: scale
width: bounds.width * this._scale,
height: bounds.height * this._scale,
};
const target = document.elementFromPoint(this._x + 10, this._y + 10);
target?.dispatchEvent(
- new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
- {
- bubbles: true,
- detail: {
- points: points,
- gesture: GestureUtils.Gestures.Stroke,
- bounds: B
- }
- }
- )
+ new CustomEvent<GestureUtils.GestureEvent>('dashOnGesture', {
+ bubbles: true,
+ detail: {
+ points: points,
+ gesture: GestureUtils.Gestures.Stroke,
+ bounds: B,
+ },
+ })
);
- }
+ };
uploadDocument = async (content: MobileDocumentUploadContent) => {
const { docId } = content;
@@ -100,36 +97,34 @@ export default class MobileInkOverlay extends React.Component {
const complete = new DragManager.DragCompleteEvent(false, dragData);
if (target) {
- console.log("dispatching upload doc!!!!", target, doc);
+ console.log('dispatching upload doc!!!!', target, doc);
target.dispatchEvent(
- new CustomEvent<DragManager.DropEvent>("dashOnDrop",
- {
- bubbles: true,
- detail: {
- x: this._x,
- y: this._y,
- complete: complete,
- altKey: false,
- metaKey: false,
- ctrlKey: false,
- shiftKey: false,
- embedKey: false
- }
- }
- )
+ new CustomEvent<DragManager.DropEvent>('dashOnDrop', {
+ bubbles: true,
+ detail: {
+ x: this._x,
+ y: this._y,
+ complete: complete,
+ altKey: false,
+ metaKey: false,
+ ctrlKey: false,
+ shiftKey: false,
+ embedKey: false,
+ },
+ })
);
} else {
- alert("TARGET IS UNDEFINED");
+ alert('TARGET IS UNDEFINED');
}
}
- }
+ };
@action
dragStart = (e: React.PointerEvent) => {
- document.removeEventListener("pointermove", this.dragging);
- document.removeEventListener("pointerup", this.dragEnd);
- document.addEventListener("pointermove", this.dragging);
- document.addEventListener("pointerup", this.dragEnd);
+ document.removeEventListener('pointermove', this.dragging);
+ document.removeEventListener('pointerup', this.dragEnd);
+ document.addEventListener('pointermove', this.dragging);
+ document.addEventListener('pointerup', this.dragEnd);
this._isDragging = true;
this._offsetX = e.pageX - this._mainCont.current!.getBoundingClientRect().left;
@@ -137,7 +132,7 @@ export default class MobileInkOverlay extends React.Component {
e.preventDefault();
e.stopPropagation();
- }
+ };
@action
dragging = (e: PointerEvent) => {
@@ -150,41 +145,39 @@ export default class MobileInkOverlay extends React.Component {
e.preventDefault();
e.stopPropagation();
- }
+ };
@action
dragEnd = (e: PointerEvent) => {
- document.removeEventListener("pointermove", this.dragging);
- document.removeEventListener("pointerup", this.dragEnd);
+ document.removeEventListener('pointermove', this.dragging);
+ document.removeEventListener('pointerup', this.dragEnd);
this._isDragging = false;
e.preventDefault();
e.stopPropagation();
- }
+ };
render() {
-
return (
- <div className="mobileInkOverlay"
+ <div
+ className="mobileInkOverlay"
style={{
width: this._width,
height: this._height,
- position: "absolute",
+ position: 'absolute',
transform: `translate(${this._x}px, ${this._y}px)`,
zIndex: 30000,
- pointerEvents: "none",
- borderStyle: this._isDragging ? "solid" : "dashed",
- }
- }
- ref={this._mainCont}
- >
+ pointerEvents: 'none',
+ borderStyle: this._isDragging ? 'solid' : 'dashed',
+ }}
+ ref={this._mainCont}>
<p>{this._text}</p>
<div className="mobileInkOverlay-border top" onPointerDown={this.dragStart}></div>
<div className="mobileInkOverlay-border bottom" onPointerDown={this.dragStart}></div>
<div className="mobileInkOverlay-border left" onPointerDown={this.dragStart}></div>
<div className="mobileInkOverlay-border right" onPointerDown={this.dragStart}></div>
- </div >
+ </div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/mobile/MobileMain.tsx b/src/mobile/MobileMain.tsx
index f85f05f53..6cbf86f77 100644
--- a/src/mobile/MobileMain.tsx
+++ b/src/mobile/MobileMain.tsx
@@ -12,10 +12,7 @@ AssignAllExtensions();
const info = await CurrentUserUtils.loadCurrentUser();
DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email + ' (mobile)');
await Docs.Prototypes.initialize();
- if (info.id !== '__guest__') {
- // a guest will not have an id registered
- await CurrentUserUtils.loadUserDocument(info.id);
- }
+ await CurrentUserUtils.loadUserDocument(info.id);
document.getElementById('root')!.addEventListener(
'wheel',
event => {
diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts
index 7be8a1e9f..53e55c1c3 100644
--- a/src/server/ApiManagers/UserManager.ts
+++ b/src/server/ApiManagers/UserManager.ts
@@ -1,10 +1,10 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method } from "../RouteManager";
-import { Database } from "../database";
-import { msToTime } from "../ActionUtilities";
-import * as bcrypt from "bcrypt-nodejs";
-import { Opt } from "../../fields/Doc";
-import { WebSocket } from "../websocket";
+import ApiManager, { Registration } from './ApiManager';
+import { Method } from '../RouteManager';
+import { Database } from '../database';
+import { msToTime } from '../ActionUtilities';
+import * as bcrypt from 'bcrypt-nodejs';
+import { Opt } from '../../fields/Doc';
+import { WebSocket } from '../websocket';
export const timeMap: { [id: string]: number } = {};
interface ActivityUnit {
@@ -13,28 +13,26 @@ interface ActivityUnit {
}
export default class UserManager extends ApiManager {
-
protected initialize(register: Registration): void {
-
register({
method: Method.GET,
- subscription: "/getUsers",
+ subscription: '/getUsers',
secureHandler: async ({ res }) => {
- const cursor = await Database.Instance.query({}, { email: 1, linkDatabaseId: 1, sharingDocumentId: 1 }, "users");
+ const cursor = await Database.Instance.query({}, { email: 1, linkDatabaseId: 1, sharingDocumentId: 1 }, 'users');
const results = await cursor.toArray();
res.send(results.map((user: any) => ({ email: user.email, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId })));
- }
+ },
});
register({
method: Method.POST,
- subscription: "/setCacheDocumentIds",
+ subscription: '/setCacheDocumentIds',
secureHandler: async ({ user, req, res }) => {
const result: any = {};
user.cacheDocumentIds = req.body.cacheDocumentIds;
user.save(err => {
if (err) {
- result.error = [{ msg: "Error while caching documents" }];
+ result.error = [{ msg: 'Error while caching documents' }];
}
});
@@ -42,32 +40,35 @@ export default class UserManager extends ApiManager {
// console.log(e);
// });
res.send(result);
- }
+ },
});
register({
method: Method.GET,
- subscription: "/getUserDocumentIds",
- secureHandler: ({ res, user }) => res.send({ userDocumentId: user.userDocumentId, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId })
+ subscription: '/getUserDocumentIds',
+ secureHandler: ({ res, user }) => res.send({ userDocumentId: user.userDocumentId, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId }),
+ publicHandler: ({ res }) => res.send({ userDocumentId: '__guest__', linkDatabaseId: 3, sharingDocumentId: 2 }),
});
register({
method: Method.GET,
- subscription: "/getSharingDocumentId",
- secureHandler: ({ res, user }) => res.send(user.sharingDocumentId)
+ subscription: '/getSharingDocumentId',
+ secureHandler: ({ res, user }) => res.send(user.sharingDocumentId),
+ publicHandler: ({ res }) => res.send(2),
});
register({
method: Method.GET,
- subscription: "/getLinkDatabaseId",
- secureHandler: ({ res, user }) => res.send(user.linkDatabaseId)
+ subscription: '/getLinkDatabaseId',
+ secureHandler: ({ res, user }) => res.send(user.linkDatabaseId),
+ publicHandler: ({ res }) => res.send(3),
});
register({
method: Method.GET,
- subscription: "/getCurrentUser",
+ subscription: '/getCurrentUser',
secureHandler: ({ res, user: { _id, email, cacheDocumentIds } }) => res.send(JSON.stringify({ id: _id, email, cacheDocumentIds })),
- publicHandler: ({ res }) => res.send(JSON.stringify({ id: "__guest__", email: "" }))
+ publicHandler: ({ res }) => res.send(JSON.stringify({ id: '__guest__', email: 'guest' })),
});
register({
@@ -80,7 +81,7 @@ export default class UserManager extends ApiManager {
const validated = await new Promise<Opt<boolean>>(resolve => {
bcrypt.compare(curr_pass, user.password, (err, passwords_match) => {
if (err || !passwords_match) {
- result.error = [{ msg: "Incorrect current password" }];
+ result.error = [{ msg: 'Incorrect current password' }];
res.send(result);
resolve(undefined);
} else {
@@ -93,10 +94,10 @@ export default class UserManager extends ApiManager {
return;
}
- req.assert("new_pass", "Password must be at least 4 characters long").len({ min: 4 });
- req.assert("new_confirm", "Passwords do not match").equals(new_pass);
+ req.assert('new_pass', 'Password must be at least 4 characters long').len({ min: 4 });
+ req.assert('new_confirm', 'Passwords do not match').equals(new_pass);
if (curr_pass === new_pass) {
- result.error = [{ msg: "Current and new password are the same" }];
+ result.error = [{ msg: 'Current and new password are the same' }];
}
// was there error in validating new passwords?
if (req.validationErrors()) {
@@ -113,17 +114,17 @@ export default class UserManager extends ApiManager {
user.save(err => {
if (err) {
- result.error = [{ msg: "Error while saving new password" }];
+ result.error = [{ msg: 'Error while saving new password' }];
}
});
res.send(result);
- }
+ },
});
register({
method: Method.GET,
- subscription: "/activity",
+ subscription: '/activity',
secureHandler: ({ res }) => {
const now = Date.now();
@@ -135,25 +136,23 @@ export default class UserManager extends ApiManager {
const socketPair = Array.from(WebSocket.socketMap).find(pair => pair[1] === user);
if (socketPair && !socketPair[0].disconnected) {
const duration = now - time;
- const target = (duration / 1000) < (60 * 5) ? activeTimes : inactiveTimes;
+ const target = duration / 1000 < 60 * 5 ? activeTimes : inactiveTimes;
target.push({ user, duration });
}
}
- const process = (target: { user: string, duration: number }[]) => {
+ const process = (target: { user: string; duration: number }[]) => {
const comparator = (first: ActivityUnit, second: ActivityUnit) => first.duration - second.duration;
const sorted = target.sort(comparator);
return sorted.map(({ user, duration }) => `${user} (${msToTime(duration)})`);
};
- res.render("user_activity.pug", {
- title: "User Activity",
+ res.render('user_activity.pug', {
+ title: 'User Activity',
active: process(activeTimes),
- inactive: process(inactiveTimes)
+ inactive: process(inactiveTimes),
});
- }
+ },
});
-
}
-
-} \ No newline at end of file
+}
diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts
index aa9bfcfa7..5683cd539 100644
--- a/src/server/RouteManager.ts
+++ b/src/server/RouteManager.ts
@@ -1,12 +1,12 @@
import { cyan, green, red } from 'colors';
import { Express, Request, Response } from 'express';
-import { AdminPriviliges } from ".";
-import { DashUserModel } from "./authentication/DashUserModel";
-import RouteSubscriber from "./RouteSubscriber";
+import { AdminPriviliges } from '.';
+import { DashUserModel } from './authentication/DashUserModel';
+import RouteSubscriber from './RouteSubscriber';
export enum Method {
GET,
- POST
+ POST,
}
export interface CoreArguments {
@@ -33,13 +33,13 @@ const registered = new Map<string, Set<Method>>();
enum RegistrationError {
Malformed,
- Duplicate
+ Duplicate,
}
export default class RouteManager {
private server: Express;
private _isRelease: boolean;
- private failedRegistrations: { route: string, reason: RegistrationError }[] = [];
+ private failedRegistrations: { route: string; reason: RegistrationError }[] = [];
public get isRelease() {
return this._isRelease;
@@ -74,39 +74,42 @@ export default class RouteManager {
}
process.exit(1);
} else {
- console.log(green("all server routes have been successfully registered:"));
- Array.from(registered.keys()).sort().forEach(route => console.log(cyan(route)));
+ console.log(green('all server routes have been successfully registered:'));
+ Array.from(registered.keys())
+ .sort()
+ .forEach(route => console.log(cyan(route)));
console.log();
}
- }
+ };
static routes: string[] = [];
/**
- *
- * @param initializer
+ *
+ * @param initializer
*/
addSupervisedRoute = (initializer: RouteInitializer): void => {
const { method, subscription, secureHandler, publicHandler, errorHandler, requireAdminInRelease: requireAdmin } = initializer;
- typeof (initializer.subscription) === "string" && RouteManager.routes.push(initializer.subscription);
+ typeof initializer.subscription === 'string' && RouteManager.routes.push(initializer.subscription);
initializer.subscription instanceof RouteSubscriber && RouteManager.routes.push(initializer.subscription.root);
- initializer.subscription instanceof Array && initializer.subscription.map(sub => {
- typeof (sub) === "string" && RouteManager.routes.push(sub);
- sub instanceof RouteSubscriber && RouteManager.routes.push(sub.root);
- });
+ initializer.subscription instanceof Array &&
+ initializer.subscription.map(sub => {
+ typeof sub === 'string' && RouteManager.routes.push(sub);
+ sub instanceof RouteSubscriber && RouteManager.routes.push(sub.root);
+ });
const isRelease = this._isRelease;
const supervised = async (req: Request, res: Response) => {
let user = req.user as Partial<DashUserModel> | undefined;
const { originalUrl: target } = req;
- if (process.env.DB === "MEM" && !user) {
- user = { id: "guest", email: "", userDocumentId: "guestDocId" };
+ if (process.env.DB === 'MEM' && !user) {
+ user = { id: 'guest', email: 'guest', userDocumentId: '__guest__' };
}
const core = { req, res, isRelease };
const tryExecute = async (toExecute: (args: any) => any | Promise<any>, args: any) => {
try {
await toExecute(args);
} catch (e) {
- console.log(red(target), user && ("email" in user) ? "<user logged out>" : undefined);
+ console.log(red(target), user && 'email' in user ? '<user logged out>' : undefined);
if (errorHandler) {
errorHandler({ ...core, error: e });
} else {
@@ -119,7 +122,7 @@ export default class RouteManager {
if (AdminPriviliges.get(user.id)) {
AdminPriviliges.delete(user.id);
} else {
- return res.redirect(`/admin/${req.originalUrl.substring(1).replace("/", ":")}`);
+ return res.redirect(`/admin/${req.originalUrl.substring(1).replace('/', ':')}`);
}
}
await tryExecute(secureHandler, { ...core, user });
@@ -128,10 +131,10 @@ export default class RouteManager {
if (publicHandler) {
await tryExecute(publicHandler, core);
if (!res.headersSent) {
- res.redirect("/login");
+ // res.redirect("/login");
}
} else {
- res.redirect("/login");
+ res.redirect('/login');
}
}
setTimeout(() => {
@@ -144,7 +147,7 @@ export default class RouteManager {
};
const subscribe = (subscriber: RouteSubscriber | string) => {
let route: string;
- if (typeof subscriber === "string") {
+ if (typeof subscriber === 'string') {
route = subscriber;
} else {
route = subscriber.build;
@@ -152,7 +155,7 @@ export default class RouteManager {
if (!/^\/$|^\/[A-Za-z\*]+(\/\:[A-Za-z?_\*]+)*$/g.test(route)) {
this.failedRegistrations.push({
reason: RegistrationError.Malformed,
- route
+ route,
});
} else {
const existing = registered.get(route);
@@ -160,7 +163,7 @@ export default class RouteManager {
if (existing.has(method)) {
this.failedRegistrations.push({
reason: RegistrationError.Duplicate,
- route
+ route,
});
return;
}
@@ -184,15 +187,14 @@ export default class RouteManager {
} else {
subscribe(subscription);
}
- }
-
+ };
}
export const STATUS = {
OK: 200,
BAD_REQUEST: 400,
EXECUTION_ERROR: 500,
- PERMISSION_DENIED: 403
+ PERMISSION_DENIED: 403,
};
export function _error(res: Response, message: string, error?: any) {
diff --git a/src/server/authentication/AuthenticationManager.ts b/src/server/authentication/AuthenticationManager.ts
index 3622be4c5..52d876e95 100644
--- a/src/server/authentication/AuthenticationManager.ts
+++ b/src/server/authentication/AuthenticationManager.ts
@@ -1,4 +1,4 @@
-import { default as User, DashUserModel } from './DashUserModel';
+import { default as User, DashUserModel, initializeGuest } from './DashUserModel';
import { Request, Response, NextFunction } from 'express';
import * as passport from 'passport';
import { IVerifyOptions } from 'passport-local';
@@ -30,6 +30,7 @@ export let getSignup = (req: Request, res: Response) => {
* Create a new local account.
*/
export let postSignup = (req: Request, res: Response, next: NextFunction) => {
+ const email = req.body.email as String;
req.assert('email', 'Email is not valid').isEmail();
req.assert('password', 'Password must be at least 4 characters long').len({ min: 4 });
req.assert('confirmPassword', 'Passwords do not match').equals(req.body.password);
@@ -41,15 +42,14 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => {
return res.redirect('/signup');
}
- const email = req.body.email as String;
const password = req.body.password;
const model = {
email,
password,
- userDocumentId: Utils.GenerateGuid(),
- sharingDocumentId: Utils.GenerateGuid(),
- linkDatabaseId: Utils.GenerateGuid(),
+ userDocumentId: email === 'guest' ? '__guest__' : Utils.GenerateGuid(),
+ sharingDocumentId: email === 'guest' ? 2 : Utils.GenerateGuid(),
+ linkDatabaseId: email === 'guest' ? 3 : Utils.GenerateGuid(),
cacheDocumentIds: '',
} as Partial<DashUserModel>;
@@ -106,18 +106,22 @@ export let getLogin = (req: Request, res: Response) => {
* On failure, redirect to signup page
*/
export let postLogin = (req: Request, res: Response, next: NextFunction) => {
- req.assert('email', 'Email is not valid').isEmail();
- req.assert('password', 'Password cannot be blank').notEmpty();
- req.sanitize('email').normalizeEmail({ gmail_remove_dots: false });
-
- const errors = req.validationErrors();
+ if (req.body.email === '') {
+ User.findOne({ email: 'guest' }, (err: any, user: DashUserModel) => !user && initializeGuest());
+ req.body.email = 'guest';
+ req.body.password = 'guest';
+ } else {
+ req.assert('email', 'Email is not valid').isEmail();
+ req.assert('password', 'Password cannot be blank').notEmpty();
+ req.sanitize('email').normalizeEmail({ gmail_remove_dots: false });
+ }
- if (errors) {
+ if (req.validationErrors()) {
req.flash('errors', 'Unable to login at this time. Please try again.');
return res.redirect('/signup');
}
- passport.authenticate('local', (err: Error, user: DashUserModel, _info: IVerifyOptions) => {
+ const callback = (err: Error, user: DashUserModel, _info: IVerifyOptions) => {
if (err) {
next(err);
return;
@@ -132,7 +136,8 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => {
}
tryRedirectToTarget(req, res);
});
- })(req, res, next);
+ };
+ setTimeout(() => passport.authenticate('local', callback)(req, res, next), 500);
};
/**
diff --git a/src/server/authentication/DashUserModel.ts b/src/server/authentication/DashUserModel.ts
index bee28b96d..a1883beab 100644
--- a/src/server/authentication/DashUserModel.ts
+++ b/src/server/authentication/DashUserModel.ts
@@ -1,13 +1,13 @@
//@ts-ignore
-import * as bcrypt from "bcrypt-nodejs";
+import * as bcrypt from 'bcrypt-nodejs';
//@ts-ignore
import * as mongoose from 'mongoose';
export type DashUserModel = mongoose.Document & {
- email: String,
- password: string,
- passwordResetToken?: string,
- passwordResetExpires?: Date,
+ email: String;
+ password: string;
+ passwordResetToken?: string;
+ passwordResetExpires?: Date;
userDocumentId: string;
sharingDocumentId: string;
@@ -15,66 +15,74 @@ export type DashUserModel = mongoose.Document & {
cacheDocumentIds: string;
profile: {
- name: string,
- gender: string,
- location: string,
- website: string,
- picture: string
- },
+ name: string;
+ gender: string;
+ location: string;
+ website: string;
+ picture: string;
+ };
- comparePassword: comparePasswordFunction,
+ comparePassword: comparePasswordFunction;
};
type comparePasswordFunction = (candidatePassword: string, cb: (err: any, isMatch: any) => {}) => void;
export type AuthToken = {
- accessToken: string,
- kind: string
+ accessToken: string;
+ kind: string;
};
-const userSchema = new mongoose.Schema({
- email: String,
- password: String,
- passwordResetToken: String,
- passwordResetExpires: Date,
+const userSchema = new mongoose.Schema(
+ {
+ email: String,
+ password: String,
+ passwordResetToken: String,
+ passwordResetExpires: Date,
- userDocumentId: String, // id that identifies a document which hosts all of a user's account data
- sharingDocumentId: String, // id that identifies a document that stores documents shared to a user, their user color, and any additional info needed to communicate between users
- linkDatabaseId: String,
- cacheDocumentIds: String, // set of document ids to retreive on startup
+ userDocumentId: String, // id that identifies a document which hosts all of a user's account data
+ sharingDocumentId: String, // id that identifies a document that stores documents shared to a user, their user color, and any additional info needed to communicate between users
+ linkDatabaseId: String,
+ cacheDocumentIds: String, // set of document ids to retreive on startup
- facebook: String,
- twitter: String,
- google: String,
+ facebook: String,
+ twitter: String,
+ google: String,
- profile: {
- name: String,
- gender: String,
- location: String,
- website: String,
- picture: String
- }
-}, { timestamps: true });
+ profile: {
+ name: String,
+ gender: String,
+ location: String,
+ website: String,
+ picture: String,
+ },
+ },
+ { timestamps: true }
+);
/**
* Password hash middleware.
*/
-userSchema.pre("save", function save(next) {
+userSchema.pre('save', function save(next) {
const user = this as DashUserModel;
- if (!user.isModified("password")) {
+ if (!user.isModified('password')) {
return next();
}
bcrypt.genSalt(10, (err: any, salt: string) => {
if (err) {
return next(err);
}
- bcrypt.hash(user.password, salt, () => void {}, (err: mongoose.Error, hash: string) => {
- if (err) {
- return next(err);
+ bcrypt.hash(
+ user.password,
+ salt,
+ () => void {},
+ (err: mongoose.Error, hash: string) => {
+ if (err) {
+ return next(err);
+ }
+ user.password = hash;
+ next();
}
- user.password = hash;
- next();
- });
+ );
});
});
@@ -88,5 +96,15 @@ const comparePassword: comparePasswordFunction = function (this: DashUserModel,
userSchema.methods.comparePassword = comparePassword;
-const User = mongoose.model("User", userSchema);
-export default User; \ No newline at end of file
+const User = mongoose.model('User', userSchema);
+export function initializeGuest() {
+ new User({
+ email: 'guest',
+ password: 'guest',
+ userDocumentId: '__guest__',
+ sharingDocumentId: '2',
+ linkDatabaseId: '3',
+ cacheDocumentIds: '',
+ }).save();
+}
+export default User;
diff --git a/src/server/index.ts b/src/server/index.ts
index f8c32103b..6e6bde3cb 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,35 +1,35 @@
require('dotenv').config();
-import { yellow } from "colors";
+import { yellow } from 'colors';
import * as mobileDetect from 'mobile-detect';
import * as path from 'path';
import * as qs from 'query-string';
-import { log_execution } from "./ActionUtilities";
-import DeleteManager from "./ApiManagers/DeleteManager";
+import { log_execution } from './ActionUtilities';
+import DeleteManager from './ApiManagers/DeleteManager';
import DownloadManager from './ApiManagers/DownloadManager';
-import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager";
-import GooglePhotosManager from "./ApiManagers/GooglePhotosManager";
-import PDFManager from "./ApiManagers/PDFManager";
+import GeneralGoogleManager from './ApiManagers/GeneralGoogleManager';
+import GooglePhotosManager from './ApiManagers/GooglePhotosManager';
+import PDFManager from './ApiManagers/PDFManager';
import { SearchManager } from './ApiManagers/SearchManager';
-import SessionManager from "./ApiManagers/SessionManager";
-import UploadManager from "./ApiManagers/UploadManager";
+import SessionManager from './ApiManagers/SessionManager';
+import UploadManager from './ApiManagers/UploadManager';
import UserManager from './ApiManagers/UserManager';
import UtilManager from './ApiManagers/UtilManager';
import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader';
-import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils";
-import { DashSessionAgent } from "./DashSession/DashSessionAgent";
-import { AppliedSessionAgent } from "./DashSession/Session/agents/applied_session_agent";
+import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils';
+import { DashSessionAgent } from './DashSession/DashSessionAgent';
+import { AppliedSessionAgent } from './DashSession/Session/agents/applied_session_agent';
import { DashUploadUtils } from './DashUploadUtils';
import { Database } from './database';
-import { Logger } from "./ProcessFactory";
+import { Logger } from './ProcessFactory';
import RouteManager, { Method, PublicHandler } from './RouteManager';
import RouteSubscriber from './RouteSubscriber';
import initializeServer, { resolvedPorts } from './server_Initialization';
export const AdminPriviliges: Map<string, boolean> = new Map();
-export const onWindows = process.platform === "win32";
+export const onWindows = process.platform === 'win32';
export let sessionAgent: AppliedSessionAgent;
-export const publicDirectory = path.resolve(__dirname, "public");
-export const filesDirectory = path.resolve(publicDirectory, "files");
+export const publicDirectory = path.resolve(__dirname, 'public');
+export const filesDirectory = path.resolve(publicDirectory, 'files');
/**
* These are the functions run before the server starts
@@ -43,11 +43,11 @@ async function preliminaryFunctions() {
await GoogleCredentialsLoader.loadCredentials();
SSL.loadCredentials();
GoogleApiServerUtils.processProjectCredentials();
- if (process.env.DB !== "MEM") {
+ if (process.env.DB !== 'MEM') {
await log_execution({
- startMessage: "attempting to initialize mongodb connection",
- endMessage: "connection outcome determined",
- action: Database.tryInitializeConnection
+ startMessage: 'attempting to initialize mongodb connection',
+ endMessage: 'connection outcome determined',
+ action: Database.tryInitializeConnection,
});
}
}
@@ -56,27 +56,16 @@ async function preliminaryFunctions() {
* Either clustered together as an API manager
* or individually referenced below, by the completion
* of this function's execution, all routes will
- * be registered on the server
+ * be registered on the server
* @param router the instance of the route manager
* that will manage the registration of new routes
* with the server
*/
function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: RouteManager) {
- const managers = [
- new SessionManager(),
- new UserManager(),
- new UploadManager(),
- new DownloadManager(),
- new SearchManager(),
- new PDFManager(),
- new DeleteManager(),
- new UtilManager(),
- new GeneralGoogleManager(),
- new GooglePhotosManager(),
- ];
+ const managers = [new SessionManager(), new UserManager(), new UploadManager(), new DownloadManager(), new SearchManager(), new PDFManager(), new DeleteManager(), new UtilManager(), new GeneralGoogleManager(), new GooglePhotosManager()];
// initialize API Managers
- console.log(yellow("\nregistering server routes..."));
+ console.log(yellow('\nregistering server routes...'));
managers.forEach(manager => manager.register(addSupervisedRoute));
/**
@@ -84,88 +73,87 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
*/
addSupervisedRoute({
method: Method.GET,
- subscription: "/",
- secureHandler: ({ res }) => res.redirect("/home")
+ subscription: '/',
+ secureHandler: ({ res }) => res.redirect('/home'),
});
-
addSupervisedRoute({
method: Method.GET,
- subscription: "/serverHeartbeat",
- secureHandler: ({ res }) => res.send(true)
+ subscription: '/serverHeartbeat',
+ secureHandler: ({ res }) => res.send(true),
});
addSupervisedRoute({
method: Method.GET,
- subscription: "/resolvedPorts",
- secureHandler: ({ res }) => res.send(resolvedPorts)
+ subscription: '/resolvedPorts',
+ secureHandler: ({ res }) => res.send(resolvedPorts),
+ publicHandler: ({ res }) => res.send(resolvedPorts),
});
const serve: PublicHandler = ({ req, res }) => {
- const detector = new mobileDetect(req.headers['user-agent'] || "");
+ const detector = new mobileDetect(req.headers['user-agent'] || '');
const filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html';
res.sendFile(path.join(__dirname, '../../deploy/' + filename));
};
/**
- * Serves a simple password input box for any
+ * Serves a simple password input box for any
*/
addSupervisedRoute({
method: Method.GET,
- subscription: new RouteSubscriber("admin").add("previous_target"),
+ subscription: new RouteSubscriber('admin').add('previous_target'),
secureHandler: ({ res, isRelease }) => {
const { PASSWORD } = process.env;
if (!(isRelease && PASSWORD)) {
- return res.redirect("/home");
+ return res.redirect('/home');
}
- res.render("admin.pug", { title: "Enter Administrator Password" });
- }
+ res.render('admin.pug', { title: 'Enter Administrator Password' });
+ },
});
addSupervisedRoute({
method: Method.POST,
- subscription: new RouteSubscriber("admin").add("previous_target"),
+ subscription: new RouteSubscriber('admin').add('previous_target'),
secureHandler: async ({ req, res, isRelease, user: { id } }) => {
const { PASSWORD } = process.env;
if (!(isRelease && PASSWORD)) {
- return res.redirect("/home");
+ return res.redirect('/home');
}
const { password } = req.body;
const { previous_target } = req.params;
let redirect: string;
if (password === PASSWORD) {
AdminPriviliges.set(id, true);
- redirect = `/${previous_target.replace(":", "/")}`;
+ redirect = `/${previous_target.replace(':', '/')}`;
} else {
redirect = `/admin/${previous_target}`;
}
res.redirect(redirect);
- }
+ },
});
addSupervisedRoute({
method: Method.GET,
- subscription: ["/home", new RouteSubscriber("doc").add("docId")],
+ subscription: ['/home', new RouteSubscriber('doc').add('docId')],
secureHandler: serve,
publicHandler: ({ req, res, ...remaining }) => {
const { originalUrl: target } = req;
- const sharing = qs.parse(qs.extract(req.originalUrl), { sort: false }).sharing === "true";
- const docAccess = target.startsWith("/doc/");
+ const sharing = qs.parse(qs.extract(req.originalUrl), { sort: false }).sharing === 'true';
+ const docAccess = target.startsWith('/doc/');
// since this is the public handler, there's no meaning of '/home' to speak of
// since there's no user logged in, so the only viable operation
// for a guest is to look at a shared document
- if (sharing && docAccess) {
+ if (docAccess) {
serve({ req, res, ...remaining });
} else {
- res.redirect("/login");
+ res.redirect('/login');
}
- }
+ },
});
logRegistrationOutcome();
}
-
/**
* This function can be used in two different ways. If not in release mode,
* this is simply the logic that is invoked to start the server. In release mode,
@@ -174,9 +162,9 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
*/
export async function launchServer() {
await log_execution({
- startMessage: "\nstarting execution of preliminary functions",
- endMessage: "completed preliminary functions\n",
- action: preliminaryFunctions
+ startMessage: '\nstarting execution of preliminary functions',
+ endMessage: 'completed preliminary functions\n',
+ action: preliminaryFunctions,
});
await initializeServer(routeSetter);
}
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index 1b7f5919f..9b91a35a6 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -1,24 +1,24 @@
-import * as express from "express";
-import { blue, green } from "colors";
-import { createServer, Server } from "https";
-import { networkInterfaces } from "os";
+import { blue } from 'colors';
+import * as express from 'express';
+import { createServer, Server } from 'https';
+import { networkInterfaces } from 'os';
import * as sio from 'socket.io';
-import { Socket } from "socket.io";
-import { Utils } from "../Utils";
+import { Socket } from 'socket.io';
+import { Opt } from '../fields/Doc';
+import { Utils } from '../Utils';
import { logPort } from './ActionUtilities';
-import { timeMap } from "./ApiManagers/UserManager";
-import { GoogleCredentialsLoader, SSL } from "./apis/google/CredentialsLoader";
-import YoutubeApi from "./apis/youtube/youtubeApiSample";
-import { Client } from "./Client";
-import { Database } from "./database";
-import { DocumentsCollection } from "./IDatabase";
-import { Diff, GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, Transferable, Types, UpdateMobileInkOverlayPositionContent, YoutubeQueryInput, YoutubeQueryTypes } from "./Message";
-import { Search } from "./Search";
+import { timeMap } from './ApiManagers/UserManager';
+import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader';
+import YoutubeApi from './apis/youtube/youtubeApiSample';
+import { initializeGuest } from './authentication/DashUserModel';
+import { Client } from './Client';
+import { Database } from './database';
+import { DocumentsCollection } from './IDatabase';
+import { Diff, GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, Transferable, Types, UpdateMobileInkOverlayPositionContent, YoutubeQueryInput, YoutubeQueryTypes } from './Message';
+import { Search } from './Search';
import { resolvedPorts } from './server_Initialization';
-import { Opt } from "../fields/Doc";
export namespace WebSocket {
-
export let _socket: Socket;
const clients: { [key: string]: Client } = {};
export const socketMap = new Map<SocketIO.Socket, string>();
@@ -32,14 +32,14 @@ export namespace WebSocket {
resolvedPorts.socket = Number(socketPort);
}
let socketEndpoint: Opt<Server>;
- await new Promise<void>(resolve => socketEndpoint = createServer(SSL.Credentials, app).listen(resolvedPorts.socket, resolve));
+ await new Promise<void>(resolve => (socketEndpoint = createServer(SSL.Credentials, app).listen(resolvedPorts.socket, resolve)));
io = sio(socketEndpoint!, SSL.Credentials as any);
} else {
io = sio().listen(resolvedPorts.socket);
}
- logPort("websocket", resolvedPorts.socket);
+ logPort('websocket', resolvedPorts.socket);
- io.on("connection", function (socket: Socket) {
+ io.on('connection', function (socket: Socket) {
_socket = socket;
socket.use((_packet, next) => {
const userEmail = socketMap.get(socket);
@@ -70,14 +70,14 @@ export namespace WebSocket {
socket.join(room);
console.log('Client ID ' + socket.id + ' created room ' + room);
socket.emit('created', room, socket.id);
-
} else if (numClients === 1) {
console.log('Client ID ' + socket.id + ' joined room ' + room);
socket.in(room).emit('join', room);
socket.join(room);
socket.emit('joined', room, socket.id);
socket.in(room).emit('ready');
- } else { // max two clients
+ } else {
+ // max two clients
socket.emit('full', room);
}
});
@@ -97,10 +97,10 @@ export namespace WebSocket {
console.log('received bye');
});
- Utils.Emit(socket, MessageStore.Foo, "handshooken");
+ Utils.Emit(socket, MessageStore.Foo, 'handshooken');
Utils.AddServerHandler(socket, MessageStore.Bar, guid => barReceived(socket, guid));
- Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args));
+ Utils.AddServerHandler(socket, MessageStore.SetField, args => setField(socket, args));
Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField);
Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields);
if (isRelease) {
@@ -126,26 +126,26 @@ export namespace WebSocket {
*/
disconnect = () => {
- socket.broadcast.emit("connection_terminated", Date.now());
+ socket.broadcast.emit('connection_terminated', Date.now());
socket.disconnect(true);
};
});
}
function processGesturePoints(socket: Socket, content: GestureContent) {
- socket.broadcast.emit("receiveGesturePoints", content);
+ socket.broadcast.emit('receiveGesturePoints', content);
}
function processOverlayTrigger(socket: Socket, content: MobileInkOverlayContent) {
- socket.broadcast.emit("receiveOverlayTrigger", content);
+ socket.broadcast.emit('receiveOverlayTrigger', content);
}
function processUpdateOverlayPosition(socket: Socket, content: UpdateMobileInkOverlayPositionContent) {
- socket.broadcast.emit("receiveUpdateOverlayPosition", content);
+ socket.broadcast.emit('receiveUpdateOverlayPosition', content);
}
function processMobileDocumentUpload(socket: Socket, content: MobileDocumentUploadContent) {
- socket.broadcast.emit("receiveMobileDocumentUpload", content);
+ socket.broadcast.emit('receiveMobileDocumentUpload', content);
}
function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any[]) => void]) {
@@ -165,27 +165,22 @@ export namespace WebSocket {
const target: string[] = [];
onlyFields && target.push(DocumentsCollection);
await Database.Instance.dropSchema(...target);
- if (process.env.DISABLE_SEARCH !== "true") {
+ if (process.env.DISABLE_SEARCH !== 'true') {
await Search.clear();
}
+ initializeGuest();
}
function barReceived(socket: SocketIO.Socket, userEmail: string) {
clients[userEmail] = new Client(userEmail.toString());
const currentdate = new Date();
- const datetime = currentdate.getDate() + "/"
- + (currentdate.getMonth() + 1) + "/"
- + currentdate.getFullYear() + " @ "
- + currentdate.getHours() + ":"
- + currentdate.getMinutes() + ":"
- + currentdate.getSeconds();
+ const datetime = currentdate.getDate() + '/' + (currentdate.getMonth() + 1) + '/' + currentdate.getFullYear() + ' @ ' + currentdate.getHours() + ':' + currentdate.getMinutes() + ':' + currentdate.getSeconds();
console.log(blue(`user ${userEmail} has connected to the web socket at: ${datetime}`));
- socketMap.set(socket, userEmail + " at " + datetime);
+ socketMap.set(socket, userEmail + ' at ' + datetime);
}
function getField([id, callback]: [string, (result?: Transferable) => void]) {
- Database.Instance.getDocument(id, (result?: Transferable) =>
- callback(result ? result : undefined));
+ Database.Instance.getDocument(id, (result?: Transferable) => callback(result ? result : undefined));
}
function getFields([ids, callback]: [string[], (result: Transferable[]) => void]) {
@@ -193,9 +188,9 @@ export namespace WebSocket {
}
function setField(socket: Socket, newValue: Transferable) {
- Database.Instance.update(newValue.id, newValue, () =>
- socket.broadcast.emit(MessageStore.SetField.Message, newValue)); // broadcast set value to all other clients
- if (newValue.type === Types.Text) { // if the newValue has sring type, then it's suitable for searching -- pass it to SOLR
+ Database.Instance.update(newValue.id, newValue, () => socket.broadcast.emit(MessageStore.SetField.Message, newValue)); // broadcast set value to all other clients
+ if (newValue.type === Types.Text) {
+ // if the newValue has sring type, then it's suitable for searching -- pass it to SOLR
Search.updateDocument({ id: newValue.id, data: { set: (newValue as any).data } });
}
}
@@ -213,34 +208,36 @@ export namespace WebSocket {
Database.Instance.getDocuments(ids, callback);
}
- const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = {
- "number": "_n",
- "string": "_t",
- "boolean": "_b",
- "image": ["_t", "url"],
- "video": ["_t", "url"],
- "pdf": ["_t", "url"],
- "audio": ["_t", "url"],
- "web": ["_t", "url"],
- "map": ["_t", "url"],
- "script": ["_t", value => value.script.originalScript],
- "RichTextField": ["_t", value => value.Text],
- "date": ["_d", value => new Date(value.date).toISOString()],
- "proxy": ["_i", "fieldId"],
- "list": ["_l", list => {
- const results = [];
- for (const value of list.fields) {
- const term = ToSearchTerm(value);
- if (term) {
- results.push(term.value);
+ const suffixMap: { [type: string]: string | [string, string | ((json: any) => any)] } = {
+ number: '_n',
+ string: '_t',
+ boolean: '_b',
+ image: ['_t', 'url'],
+ video: ['_t', 'url'],
+ pdf: ['_t', 'url'],
+ audio: ['_t', 'url'],
+ web: ['_t', 'url'],
+ map: ['_t', 'url'],
+ script: ['_t', value => value.script.originalScript],
+ RichTextField: ['_t', value => value.Text],
+ date: ['_d', value => new Date(value.date).toISOString()],
+ proxy: ['_i', 'fieldId'],
+ list: [
+ '_l',
+ list => {
+ const results = [];
+ for (const value of list.fields) {
+ const term = ToSearchTerm(value);
+ if (term) {
+ results.push(term.value);
+ }
}
- }
- return results.length ? results : null;
- }]
+ return results.length ? results : null;
+ },
+ ],
};
- function ToSearchTerm(val: any): { suffix: string, value: any } | undefined {
-
+ function ToSearchTerm(val: any): { suffix: string; value: any } | undefined {
if (val === null || val === undefined) {
return;
}
@@ -252,69 +249,79 @@ export namespace WebSocket {
}
if (Array.isArray(suffix)) {
const accessor = suffix[1];
- if (typeof accessor === "function") {
+ if (typeof accessor === 'function') {
val = accessor(val);
} else {
val = val[accessor];
-
}
suffix = suffix[0];
-
}
return { suffix, value: val };
}
function getSuffix(value: string | [string, any]): string {
- return typeof value === "string" ? value : value[0];
+ return typeof value === 'string' ? value : value[0];
}
function addToListField(socket: Socket, diff: Diff, curListItems?: Transferable): void {
- diff.diff.$set = diff.diff.$addToSet; delete diff.diff.$addToSet;// convert add to set to a query of the current fields, and then a set of the composition of the new fields with the old ones
+ diff.diff.$set = diff.diff.$addToSet;
+ delete diff.diff.$addToSet; // convert add to set to a query of the current fields, and then a set of the composition of the new fields with the old ones
const updatefield = Array.from(Object.keys(diff.diff.$set))[0];
const newListItems = diff.diff.$set[updatefield]?.fields;
if (!newListItems) {
- console.log("Error: addToListField - no new list items");
+ console.log('Error: addToListField - no new list items');
return;
}
- const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields.filter((item: any) => item !== undefined) || [];
- diff.diff.$set[updatefield].fields = [...curList, ...newListItems];//, ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))];
+ const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((item: any) => item !== undefined) || [];
+ diff.diff.$set[updatefield].fields = [...curList, ...newListItems]; //, ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))];
const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length;
delete diff.diff.length;
- Database.Instance.update(diff.id, diff.diff,
+ Database.Instance.update(
+ diff.id,
+ diff.diff,
() => {
if (sendBack) {
- console.log("Warning: list modified during update. Composite list is being returned.");
+ console.log('Warning: list modified during update. Composite list is being returned.');
const id = socket.id;
- socket.id = "";
+ socket.id = '';
socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
socket.id = id;
} else socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
dispatchNextOp(diff.id);
- }, false);
+ },
+ false
+ );
}
function remFromListField(socket: Socket, diff: Diff, curListItems?: Transferable): void {
- diff.diff.$set = diff.diff.$remFromSet; delete diff.diff.$remFromSet;
+ diff.diff.$set = diff.diff.$remFromSet;
+ delete diff.diff.$remFromSet;
const updatefield = Array.from(Object.keys(diff.diff.$set))[0];
const remListItems = diff.diff.$set[updatefield].fields;
- const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields.filter((f: any) => f !== null) || [];
- diff.diff.$set[updatefield].fields = curList?.filter((curItem: any) => !remListItems.some((remItem: any) => remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : remItem === curItem));
+ const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((f: any) => f !== null) || [];
+ diff.diff.$set[updatefield].fields = curList?.filter(
+ (curItem: any) => !remListItems.some((remItem: any) => (remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : remItem === curItem))
+ );
const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length;
delete diff.diff.length;
- Database.Instance.update(diff.id, diff.diff,
+ Database.Instance.update(
+ diff.id,
+ diff.diff,
() => {
if (sendBack) {
- console.log("SEND BACK");
+ console.log('SEND BACK');
const id = socket.id;
- socket.id = "";
+ socket.id = '';
socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
socket.id = id;
} else socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
dispatchNextOp(diff.id);
- }, false);
+ },
+ false
+ );
}
- const pendingOps = new Map<string, { diff: Diff, socket: Socket }[]>();
+ const pendingOps = new Map<string, { diff: Diff; socket: Socket }[]>();
function dispatchNextOp(id: string) {
const next = pendingOps.get(id)!.shift();
@@ -341,7 +348,7 @@ export namespace WebSocket {
function UpdateField(socket: Socket, diff: Diff) {
if (CurUser !== socketMap.get(socket)) {
CurUser = socketMap.get(socket);
- console.log("Switch User: " + CurUser);
+ console.log('Switch User: ' + CurUser);
}
if (pendingOps.has(diff.id)) {
pendingOps.get(diff.id)!.push({ diff, socket });
@@ -357,24 +364,25 @@ export namespace WebSocket {
return GetRefFieldLocal([diff.id, (result?: Transferable) => SetField(socket, diff, result)]);
}
function SetField(socket: Socket, diff: Diff, curListItems?: Transferable) {
- Database.Instance.update(diff.id, diff.diff,
- () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false);
+ Database.Instance.update(diff.id, diff.diff, () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false);
const docfield = diff.diff.$set || diff.diff.$unset;
if (docfield) {
const update: any = { id: diff.id };
let dynfield = false;
for (let key in docfield) {
- if (!key.startsWith("fields.")) continue;
+ if (!key.startsWith('fields.')) continue;
dynfield = true;
const val = docfield[key];
key = key.substring(7);
- Object.values(suffixMap).forEach(suf => { update[key + getSuffix(suf)] = { set: null }; });
+ Object.values(suffixMap).forEach(suf => {
+ update[key + getSuffix(suf)] = { set: null };
+ });
const term = ToSearchTerm(val);
if (term !== undefined) {
const { suffix, value } = term;
update[key + suffix] = { set: value };
if (key.endsWith('lastModified')) {
- update["lastModified" + suffix] = value;
+ update['lastModified' + suffix] = value;
}
}
}
@@ -403,6 +411,4 @@ export namespace WebSocket {
function CreateField(newValue: any) {
Database.Instance.insert(newValue);
}
-
}
-