aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package.json3
-rw-r--r--src/Utils.ts2
-rw-r--r--src/client/DocServer.ts1
-rw-r--r--src/client/util/ClientDiagnostics.ts34
-rw-r--r--src/client/util/TooltipTextMenu.tsx2
-rw-r--r--src/client/views/Main.tsx2
-rw-r--r--src/client/views/MainView.scss17
-rw-r--r--src/client/views/MainView.tsx106
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx2
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx20
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx6
-rw-r--r--src/client/views/collections/CollectionView.tsx16
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx2
-rw-r--r--src/client/views/nodes/ContentFittingDocumentView.tsx1
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx3
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx7
-rw-r--r--src/client/views/nodes/ImageBox.tsx6
-rw-r--r--src/server/ActionUtilities.ts10
-rw-r--r--src/server/ApiManagers/DiagnosticManager.ts30
-rw-r--r--src/server/ApiManagers/SearchManager.ts36
-rw-r--r--src/server/ApiManagers/UtilManager.ts10
-rw-r--r--src/server/Initialization.ts8
-rw-r--r--src/server/Message.ts1
-rw-r--r--src/server/Websocket/Websocket.ts13
-rw-r--r--src/server/database.ts4
-rw-r--r--src/server/index.ts26
26 files changed, 205 insertions, 163 deletions
diff --git a/package.json b/package.json
index 32344aad4..1f0dd8d65 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
+ "start-release": "cross-env RELEASE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts",
"start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts",
"debug": "cross-env NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --inspect -- src/server/index.ts",
"build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 webpack --env production",
@@ -229,4 +230,4 @@
"xoauth2": "^1.2.0",
"youtube": "^0.1.0"
}
-}
+} \ No newline at end of file
diff --git a/src/Utils.ts b/src/Utils.ts
index 7401ef981..2b15ad0f2 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -362,6 +362,8 @@ export function returnZero() { return 0; }
export function returnEmptyString() { return ""; }
+export let emptyPath = [];
+
export function emptyFunction() { }
export function unimplementedFunction() { throw new Error("This function is not implemented, but should be."); }
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index e4b183715..befe9ea5c 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -82,6 +82,7 @@ export namespace DocServer {
Utils.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate);
Utils.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete);
Utils.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
+ _socket.on("connection_terminated", () => alert("Your connection to the server has been terminated."));
}
function errorFunc(): never {
diff --git a/src/client/util/ClientDiagnostics.ts b/src/client/util/ClientDiagnostics.ts
deleted file mode 100644
index 0a213aa1c..000000000
--- a/src/client/util/ClientDiagnostics.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-export namespace ClientDiagnostics {
-
- export async function start() {
-
- let serverPolls = 0;
- const serverHandle = setInterval(async () => {
- if (++serverPolls === 20) {
- alert("Your connection to the server has been terminated.");
- clearInterval(serverHandle);
- }
- await fetch("/serverHeartbeat");
- serverPolls--;
- }, 1000 * 15);
-
- let executed = false;
- let solrHandle: NodeJS.Timeout | undefined;
- const handler = async () => {
- const response = await fetch("/solrHeartbeat");
- if (!(await response.json()).running) {
- if (!executed) {
- alert("Looks like SOLR is not running on your machine.");
- executed = true;
- solrHandle && clearInterval(solrHandle);
- }
- }
- };
- await handler();
- if (!executed) {
- solrHandle = setInterval(handler, 1000 * 15);
- }
-
- }
-
-} \ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 89037dcb2..01d566831 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -1030,7 +1030,7 @@ export class TooltipTextMenu {
TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMap.set(input.value, TooltipTextMenuManager.Instance._brushMarks);
input.style.background = "lightGray";
}
- }
+ };
const wrapper = document.createElement("div");
wrapper.appendChild(input);
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 9e699978f..b21eb9c8f 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -5,12 +5,10 @@ import * as ReactDOM from 'react-dom';
import * as React from 'react';
import { DocServer } from "../DocServer";
import { AssignAllExtensions } from "../../extensions/General/Extensions";
-import { ClientDiagnostics } from "../util/ClientDiagnostics";
AssignAllExtensions();
(async () => {
- await ClientDiagnostics.start();
const info = await CurrentUserUtils.loadCurrentUser();
DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email);
await Docs.Prototypes.initialize();
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 0ee30f117..a8924c6b1 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -7,11 +7,18 @@
width: 100%;
}
+.mainContent-div {
+ position: relative;
+ width:100%;
+ height:100%;
+}
+
// add nodes menu. Note that the + button is actually an input label, not an actual button.
.mainView-docButtons {
position: absolute;
bottom: 20px;
- left: 250px;
+ left: calc(100% + 5px);
+ z-index: 1;
}
#mainView-container {
@@ -27,13 +34,13 @@
width: 100%;
height: 100%;
position: absolute;
+ display: flex;
}
.mainView-flyoutContainer {
display: flex;
flex-direction: column;
- position: absolute;
- width: 100%;
+ position: relative;
height: 100%;
.documentView-node-topmost {
@@ -59,9 +66,11 @@
.mainView-libraryFlyout {
height: 100%;
+ width:100%;
position: absolute;
display: flex;
flex-direction: column;
+ z-index: 1;
}
.mainView-expandFlyoutButton {
@@ -73,6 +82,7 @@
.mainView-libraryHandle {
width: 20px;
+ left: calc(100% - 10px);
height: 40px;
top: 50%;
border: 1px solid black;
@@ -80,6 +90,7 @@
position: absolute;
z-index: 1;
touch-action: none;
+ cursor: ew-resize;
}
.mainView-workspace {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 29719a6eb..bcdc3a453 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -15,7 +15,7 @@ import { List } from '../../new_fields/List';
import { listSpec } from '../../new_fields/Schema';
import { Cast, FieldValue, StrCast } from '../../new_fields/Types';
import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, returnTrue, Utils } from '../../Utils';
+import { emptyFunction, returnEmptyString, returnFalse, returnOne, returnTrue, Utils, emptyPath } from '../../Utils';
import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
import { DocServer } from '../DocServer';
import { Docs, DocumentOptions } from '../documents/Documents';
@@ -39,6 +39,7 @@ import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsM
import InkSelectDecorations from './InkSelectDecorations';
import { Scripting } from '../util/Scripting';
import { AudioBox } from './nodes/AudioBox';
+import { TraceMobx } from '../../new_fields/util';
@observer
export class MainView extends React.Component {
@@ -56,6 +57,7 @@ export class MainView extends React.Component {
@computed private get userDoc() { return CurrentUserUtils.UserDocument; }
@computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeWorkspace, Doc)) : CurrentUserUtils.GuestWorkspace; }
@computed public get mainFreeform(): Opt<Doc> { return (docs => (docs && docs.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); }
+ @computed public get sidebarButtonsDoc() { return Cast(CurrentUserUtils.UserDocument.sidebarButtons, Doc) as Doc; }
public isPointerDown = false;
@@ -205,7 +207,7 @@ export class MainView extends React.Component {
}
@action
- openWorkspace = async (doc: Doc, fromHistory = false) => {
+ openWorkspace = (doc: Doc, fromHistory = false) => {
CurrentUserUtils.MainDocId = doc[Id];
if (doc) { // this has the side-effect of setting the main container since we're assigning the active/guest workspace
@@ -260,38 +262,40 @@ export class MainView extends React.Component {
getPHeight = () => this._panelHeight;
getContentsHeight = () => this._panelHeight - this._buttonBarHeight;
+ @computed get mainDocView() {
+ return <DocumentView Document={this.mainContainer!}
+ DataDoc={undefined}
+ LibraryPath={emptyPath}
+ addDocument={undefined}
+ addDocTab={this.addDocTabFunc}
+ pinToPres={emptyFunction}
+ onClick={undefined}
+ ruleProvider={undefined}
+ removeDocument={undefined}
+ ScreenToLocalTransform={Transform.Identity}
+ ContentScaling={returnOne}
+ PanelWidth={this.getPWidth}
+ PanelHeight={this.getPHeight}
+ renderDepth={0}
+ backgroundColor={returnEmptyString}
+ focus={emptyFunction}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ zoomToScale={emptyFunction}
+ getScale={returnOne}
+ />
+ }
@computed get dockingContent() {
+ TraceMobx();
const mainContainer = this.mainContainer;
- const flyoutWidth = this.flyoutWidth; // bcz: need to be here because Measure messes with observables.
- const flyoutTranslate = this._flyoutTranslate;
+ const width = this.flyoutWidth;
return <Measure offset onResize={this.onResize}>
{({ measureRef }) =>
- <div ref={measureRef} id="mainContent-div" style={{ width: `calc(100% - ${flyoutTranslate ? flyoutWidth : 0}px`, transform: `translate(${flyoutTranslate ? flyoutWidth : 0}px, 0px)` }} onDrop={this.onDrop}>
- {!mainContainer ? (null) :
- <DocumentView Document={mainContainer}
- DataDoc={undefined}
- LibraryPath={[]}
- addDocument={undefined}
- addDocTab={this.addDocTabFunc}
- pinToPres={emptyFunction}
- onClick={undefined}
- ruleProvider={undefined}
- removeDocument={undefined}
- ScreenToLocalTransform={Transform.Identity}
- ContentScaling={returnOne}
- PanelWidth={this.getPWidth}
- PanelHeight={this.getPHeight}
- renderDepth={0}
- backgroundColor={returnEmptyString}
- focus={emptyFunction}
- parentActive={returnTrue}
- whenActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- zoomToScale={emptyFunction}
- getScale={returnOne}
- />}
+ <div ref={measureRef} className="mainContent-div" onDrop={this.onDrop} style={{ width: `calc(100% - ${width}px)` }}>
+ {!mainContainer ? (null) : this.mainDocView}
</div>
}
</Measure>;
@@ -313,6 +317,7 @@ export class MainView extends React.Component {
pointerOverDragger = () => {
if (this.flyoutWidth === 0) {
this.flyoutWidth = 250;
+ this.sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30;
this._flyoutTranslate = false;
}
}
@@ -328,26 +333,22 @@ export class MainView extends React.Component {
@action
onPointerMove = (e: PointerEvent) => {
this.flyoutWidth = Math.max(e.clientX, 0);
+ this.sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30;
}
@action
onPointerUp = (e: PointerEvent) => {
if (Math.abs(e.clientX - this._flyoutSizeOnDown) < 4) {
this.flyoutWidth = this.flyoutWidth < 5 ? 250 : 0;
+ this.flyoutWidth && (this.sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30);
}
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
}
flyoutWidthFunc = () => this.flyoutWidth;
- addDocTabFunc = (doc: Doc, data: Opt<Doc>, where: string, libraryPath?: Doc[]) => {
- if (where === "close") {
- return CollectionDockingView.CloseRightSplit(doc);
- }
- if (doc.dockingConfig) {
- this.openWorkspace(doc);
- return true;
- } else {
- return CollectionDockingView.AddRightSplit(doc, undefined, undefined, libraryPath);
- }
+ addDocTabFunc = (doc: Doc, data: Opt<Doc>, where: string, libraryPath?: Doc[]): boolean => {
+ return where === "close" ? CollectionDockingView.CloseRightSplit(doc) :
+ doc.dockingConfig ? this.openWorkspace(doc) :
+ CollectionDockingView.AddRightSplit(doc, undefined, undefined, libraryPath);
}
mainContainerXf = () => new Transform(0, -this._buttonBarHeight, 1);
@@ -357,13 +358,12 @@ export class MainView extends React.Component {
return (null);
}
const sidebarButtonsDoc = Cast(CurrentUserUtils.UserDocument.sidebarButtons, Doc) as Doc;
- sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30;
return <div className="mainView-flyoutContainer" >
<div className="mainView-tabButtons" style={{ height: `${this._buttonBarHeight}px` }}>
<DocumentView
Document={sidebarButtonsDoc}
DataDoc={undefined}
- LibraryPath={[]}
+ LibraryPath={emptyPath}
addDocument={undefined}
addDocTab={this.addDocTabFunc}
pinToPres={emptyFunction}
@@ -390,7 +390,7 @@ export class MainView extends React.Component {
<DocumentView
Document={sidebarContent}
DataDoc={undefined}
- LibraryPath={[]}
+ LibraryPath={emptyPath}
addDocument={undefined}
addDocTab={this.addDocTabFunc}
pinToPres={emptyFunction}
@@ -416,6 +416,7 @@ export class MainView extends React.Component {
{CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
</button>
</div>
+ {this.docButtons}
</div>;
}
@@ -423,10 +424,9 @@ export class MainView extends React.Component {
const sidebar = this.userDoc && this.userDoc.sidebarContainer;
return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
<div className="mainView-mainContent" >
- <div className="mainView-flyoutContainer" onPointerLeave={this.pointerLeaveDragger}>
- <div className="mainView-libraryHandle"
- style={{ cursor: "ew-resize", left: `${(this.flyoutWidth * (this._flyoutTranslate ? 1 : 0)) - 10}px`, backgroundColor: `${StrCast(sidebar.backgroundColor, "lightGray")}` }}
- onPointerDown={this.onPointerDown} onPointerOver={this.pointerOverDragger}>
+ <div className="mainView-flyoutContainer" onPointerLeave={this.pointerLeaveDragger} style={{ width: this.flyoutWidth }}>
+ <div className="mainView-libraryHandle" onPointerDown={this.onPointerDown} onPointerOver={this.pointerOverDragger}
+ style={{ backgroundColor: `${StrCast(sidebar.backgroundColor, "lightGray")}` }} >
<span title="library View Dragger" style={{
width: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "3vw",
height: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "100vh",
@@ -435,11 +435,9 @@ export class MainView extends React.Component {
}} />
</div>
<div className="mainView-libraryFlyout" style={{
- width: `${this.flyoutWidth}px`,
- zIndex: 1,
- transformOrigin: this._flyoutTranslate ? "" : "left center",
+ //transformOrigin: this._flyoutTranslate ? "" : "left center",
transition: this._flyoutTranslate ? "" : "width .5s",
- transform: `scale(${this._flyoutTranslate ? 1 : 0.8})`,
+ //transform: `scale(${this._flyoutTranslate ? 1 : 0.8})`,
boxShadow: this._flyoutTranslate ? "" : "rgb(156, 147, 150) 0.2vw 0.2vw 0.8vw"
}}>
{this.flyout}
@@ -453,6 +451,7 @@ export class MainView extends React.Component {
public static expandFlyout = action(() => {
MainView.Instance._flyoutTranslate = true;
MainView.Instance.flyoutWidth = (MainView.Instance.flyoutWidth || 250);
+ MainView.Instance.sidebarButtonsDoc.columnWidth = MainView.Instance.flyoutWidth / 3 - 30;
});
@computed get expandButton() {
@@ -471,12 +470,12 @@ export class MainView extends React.Component {
@computed get docButtons() {
if (CurrentUserUtils.UserDocument?.expandingButtons instanceof Doc) {
return <div className="mainView-docButtons" ref={this._docBtnRef}
- style={{ left: (this._flyoutTranslate ? this.flyoutWidth : 0) + 20, height: !CurrentUserUtils.UserDocument.expandingButtons.isExpanded ? "42px" : undefined }} >
+ style={{ height: !CurrentUserUtils.UserDocument.expandingButtons.isExpanded ? "42px" : undefined }} >
<MainViewNotifs />
<CollectionLinearView
Document={CurrentUserUtils.UserDocument.expandingButtons}
DataDoc={undefined}
- LibraryPath={[]}
+ LibraryPath={emptyPath}
fieldKey={"data"}
annotationsKey={""}
select={emptyFunction}
@@ -515,7 +514,6 @@ export class MainView extends React.Component {
{this.mainContent}
<PreviewCursor />
<ContextMenu />
- {this.docButtons}
<PDFMenu />
<MarqueeOptionsMenu />
<OverlayView />
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 4374cde3c..ffcb3e9ce 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -32,6 +32,7 @@ import React = require("react");
import { ButtonSelector } from './ParentDocumentSelector';
import { DocumentType } from '../../documents/DocumentTypes';
import { ComputedField } from '../../../new_fields/ScriptField';
+import { TraceMobx } from '../../../new_fields/util';
library.add(faFile);
const _global = (window /* browser */ || global /* node */) as any;
@@ -662,6 +663,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}
@computed get docView() {
+ TraceMobx();
if (!this._document) return (null);
const document = this._document;
const resolvedDataDoc = document.layout instanceof Doc ? document : this._dataDoc;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 9f01de08b..ff3417b77 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -24,6 +24,7 @@ import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import { ScriptBox } from "../ScriptBox";
import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow";
+import { TraceMobx } from "../../../new_fields/util";
@observer
export class CollectionStackingView extends CollectionSubView(doc => doc) {
@@ -383,17 +384,22 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
}
+ @computed get renderedSections() {
+ TraceMobx();
+ let sections = [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]];
+ if (this.sectionFilter) {
+ const entries = Array.from(this.Sections.entries());
+ sections = entries.sort(this.sortFunc);
+ }
+ return sections.map(section => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1]));
+ }
render() {
+ TraceMobx();
const editableViewProps = {
GetValue: () => "",
SetValue: this.addGroup,
contents: "+ ADD A GROUP"
};
- let sections = [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]];
- if (this.sectionFilter) {
- const entries = Array.from(this.Sections.entries());
- sections = entries.sort(this.sortFunc);
- }
return (
<div className="collectionStackingMasonry-cont" >
<div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"}
@@ -401,8 +407,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
onScroll={action((e: React.UIEvent<HTMLDivElement>) => this._scroll = e.currentTarget.scrollTop)}
onDrop={this.onDrop.bind(this)}
onContextMenu={this.onContextMenu}
- onWheel={(e: React.WheelEvent) => e.stopPropagation()} >
- {sections.map(section => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1]))}
+ onWheel={e => e.stopPropagation()} >
+ {this.renderedSections}
{!this.showAddAGroup ? (null) :
<div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
style={{ width: !this.isStackingView ? "100%" : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 80dc482af..ca3b93bf8 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -17,6 +17,7 @@ import { anchorPoints, Flyout } from "../DocumentDecorations";
import { EditableView } from "../EditableView";
import { CollectionStackingView } from "./CollectionStackingView";
import "./CollectionStackingView.scss";
+import { TraceMobx } from "../../../new_fields/util";
library.add(faPalette);
@@ -252,13 +253,12 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@observable private collapsed: boolean = false;
- private toggleVisibility = action(() => {
- this.collapsed = !this.collapsed;
- });
+ private toggleVisibility = action(() => this.collapsed = !this.collapsed);
@observable _headingsHack: number = 1;
render() {
+ TraceMobx();
const cols = this.props.cols();
const key = StrCast(this.props.parent.props.Document.sectionFilter);
let templatecols = "";
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 54f5a2c42..411040332 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -32,6 +32,8 @@ import { SelectionManager } from '../../util/SelectionManager';
import './CollectionView.scss';
import { FieldViewProps, FieldView } from '../nodes/FieldView';
import { Touchable } from '../Touchable';
+import { TraceMobx } from '../../../new_fields/util';
+const path = require('path');
library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
export enum CollectionViewType {
@@ -234,15 +236,23 @@ export class CollectionView extends Touchable<FieldViewProps> {
}
lightbox = (images: string[]) => {
+ if (!images.length) return (null);
+ const mainPath = path.extname(images[this._curLightboxImg]);
+ const nextPath = path.extname(images[(this._curLightboxImg + 1) % images.length]);
+ const prevPath = path.extname(images[(this._curLightboxImg + images.length - 1) % images.length]);
+ let main = images[this._curLightboxImg].replace(mainPath, "_o" + mainPath);
+ let next = images[(this._curLightboxImg + 1) % images.length].replace(nextPath, "_o" + nextPath);
+ let prev = images[(this._curLightboxImg + images.length - 1) % images.length].replace(prevPath, "_o" + prevPath);
return !this._isLightboxOpen ? (null) : (<Lightbox key="lightbox"
- mainSrc={images[this._curLightboxImg]}
- nextSrc={images[(this._curLightboxImg + 1) % images.length]}
- prevSrc={images[(this._curLightboxImg + images.length - 1) % images.length]}
+ mainSrc={main}
+ nextSrc={next}
+ prevSrc={prev}
onCloseRequest={action(() => this._isLightboxOpen = false)}
onMovePrevRequest={action(() => this._curLightboxImg = (this._curLightboxImg + images.length - 1) % images.length)}
onMoveNextRequest={action(() => this._curLightboxImg = (this._curLightboxImg + 1) % images.length)} />);
}
render() {
+ TraceMobx();
const props: CollectionRenderProps = {
addDocument: this.addDocument,
removeDocument: this.removeDocument,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 0d3748ded..81d8d467b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -711,7 +711,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
case "pivot": computedElementData = this.doPivotLayout(this._layoutPoolData); break;
default: computedElementData = this.doFreeformLayout(this._layoutPoolData); break;
}
- this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair =>
+ this.childLayoutPairs.filter((pair, i) => this.isCurrent(pair.layout)).forEach(pair =>
computedElementData.elements.push({
ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} dataProvider={this.childDataProvider}
ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 660a5bb7e..6d0ea33fa 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -45,6 +45,7 @@ interface ContentFittingDocumentViewProps {
@observer
export class ContentFittingDocumentView extends React.Component<ContentFittingDocumentViewProps>{
+ public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
private get layoutDoc() { return this.props.Document && Doc.Layout(this.props.Document); }
private get nativeWidth() { return NumCast(this.layoutDoc!.nativeWidth, this.props.PanelWidth()); }
private get nativeHeight() { return NumCast(this.layoutDoc!.nativeHeight, this.props.PanelHeight()); }
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 13fd3cde1..1bbc82119 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -32,6 +32,7 @@ import { VideoBox } from "./VideoBox";
import { WebBox } from "./WebBox";
import { InkingStroke } from "../InkingStroke";
import React = require("react");
+import { TraceMobx } from "../../../new_fields/util";
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
type BindingProps = Without<FieldViewProps, 'fieldKey'>;
@@ -57,6 +58,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
hideOnLeave?: boolean
}> {
@computed get layout(): string {
+ TraceMobx();
if (!this.layoutDoc) return "<p>awaiting layout</p>";
const layout = Cast(this.layoutDoc[this.props.layoutKey], "string");
if (layout === undefined) {
@@ -92,6 +94,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
}
render() {
+ TraceMobx();
return (this.props.renderDepth > 7 || !this.layout) ? (null) :
<ObserverJsxParser
blacklistedAttrs={[]}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 11fececd7..3ed010e8f 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1078,8 +1078,11 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
const nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
const dh = NumCast(this.layoutDoc.height, 0);
- this.layoutDoc.height = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
- this.dataDoc.nativeHeight = nh ? scrollHeight : undefined;
+ let newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
+ if (Math.abs(newHeight - dh) > 1) { // bcz: Argh! without this, we get into a React crash if the same document is opened in a freeform view and in the treeview. no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle
+ this.layoutDoc.height = newHeight;
+ this.dataDoc.nativeHeight = nh ? scrollHeight : undefined;
+ }
}
}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f60888929..4b3da3dae 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -215,6 +215,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
}
_curSuffix = "_m";
+ _resized = false;
resize = (srcpath: string) => {
requestImageSize(srcpath)
.then((size: any) => {
@@ -223,11 +224,12 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
const aspect = realsize.height / realsize.width;
if (this.Document.width && (Math.abs(1 - NumCast(this.Document.height) / NumCast(this.Document.width) / (realsize.height / realsize.width)) > 0.1)) {
setTimeout(action(() => {
+ this._resized = true;
this.Document.height = this.Document[WidthSym]() * aspect;
this.Document.nativeHeight = realsize.height;
this.Document.nativeWidth = realsize.width;
}), 0);
- }
+ } else this._resized = true;
})
.catch((err: any) => console.log(err));
}
@@ -315,7 +317,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
const srcaspect = paths[Math.min(paths.length - 1, (this.Document.curPage || 0))][1] as number;
const fadepath = paths[Math.min(paths.length - 1, 1)][0] as string;
- !this.Document.ignoreAspect && this.resize(srcpath);
+ !this.Document.ignoreAspect && !this._resized && this.resize(srcpath);
return <div className="imageBox-cont" key={this.props.Document[Id]} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
<div className="imageBox-fader" >
diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts
index 4fe7374d1..94008e171 100644
--- a/src/server/ActionUtilities.ts
+++ b/src/server/ActionUtilities.ts
@@ -9,21 +9,21 @@ export const command_line = (command: string, fromDirectory?: string) => {
return new Promise<string>((resolve, reject) => {
const options: ExecOptions = {};
if (fromDirectory) {
- options.cwd = path.join(__dirname, fromDirectory);
+ options.cwd = path.resolve(__dirname, fromDirectory);
}
exec(command, options, (err, stdout) => err ? reject(err) : resolve(stdout));
});
};
export const read_text_file = (relativePath: string) => {
- const target = path.join(__dirname, relativePath);
+ const target = path.resolve(__dirname, relativePath);
return new Promise<string>((resolve, reject) => {
fs.readFile(target, (err, data) => err ? reject(err) : resolve(data.toString()));
});
};
export const write_text_file = (relativePath: string, contents: any) => {
- const target = path.join(__dirname, relativePath);
+ const target = path.resolve(__dirname, relativePath);
return new Promise<void>((resolve, reject) => {
fs.writeFile(target, contents, (err) => err ? reject(err) : resolve());
});
@@ -73,7 +73,3 @@ export async function Prune(rootDirectory: string): Promise<boolean> {
}
export const Destroy = (mediaPath: string) => new Promise<boolean>(resolve => fs.unlink(mediaPath, error => resolve(error === null)));
-
-export function addBeforeExitHandler(handler: NodeJS.BeforeExitListener) {
- process.on("beforeExit", handler);
-}
diff --git a/src/server/ApiManagers/DiagnosticManager.ts b/src/server/ApiManagers/DiagnosticManager.ts
deleted file mode 100644
index 104985481..000000000
--- a/src/server/ApiManagers/DiagnosticManager.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method } from "../RouteManager";
-import request = require('request-promise');
-
-export default class DiagnosticManager extends ApiManager {
-
- protected initialize(register: Registration): void {
-
- register({
- method: Method.GET,
- subscription: "/serverHeartbeat",
- onValidation: ({ res }) => res.send(true)
- });
-
- register({
- method: Method.GET,
- subscription: "/solrHeartbeat",
- onValidation: async ({ res }) => {
- try {
- await request("http://localhost:8983");
- res.send({ running: true });
- } catch (e) {
- res.send({ running: false });
- }
- }
- });
-
- }
-
-} \ No newline at end of file
diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts
index 7afecbb18..ccd0896bd 100644
--- a/src/server/ApiManagers/SearchManager.ts
+++ b/src/server/ApiManagers/SearchManager.ts
@@ -4,13 +4,31 @@ import { Search } from "../Search";
const findInFiles = require('find-in-files');
import * as path from 'path';
import { pathToDirectory, Directory } from "./UploadManager";
+import { command_line } from "../ActionUtilities";
+import request = require('request-promise');
+import { red } from "colors";
+import RouteSubscriber from "../RouteSubscriber";
-export default class SearchManager extends ApiManager {
+export class SearchManager extends ApiManager {
protected initialize(register: Registration): void {
register({
method: Method.GET,
+ subscription: new RouteSubscriber("solr").add("action"),
+ onValidation: async ({ req, res }) => {
+ const { action } = req.params;
+ if (["start", "stop"].includes(action)) {
+ const status = req.params.action === "start";
+ const success = await SolrManager.SetRunning(status);
+ console.log(success ? `Successfully ${status ? "started" : "stopped"} Solr!` : `Uh oh! Check the console for the error that occurred while ${status ? "starting" : "stopping"} Solr`);
+ }
+ res.redirect("/home");
+ }
+ });
+
+ register({
+ method: Method.GET,
subscription: "/textsearch",
onValidation: async ({ req, res }) => {
const q = req.query.q;
@@ -46,4 +64,20 @@ export default class SearchManager extends ApiManager {
}
+}
+
+export namespace SolrManager {
+
+ export async function SetRunning(status: boolean): Promise<boolean> {
+ const args = status ? "start" : "stop -p 8983";
+ try {
+ console.log(`Solr management: trying to ${args}`);
+ console.log(await command_line(`solr.cmd ${args}`, "../../solr-8.1.1/bin"));
+ return true;
+ } catch (e) {
+ console.log(red(`Solr management error: unable to ${args}`));
+ return false;
+ }
+ }
+
} \ No newline at end of file
diff --git a/src/server/ApiManagers/UtilManager.ts b/src/server/ApiManagers/UtilManager.ts
index 601a7d0d0..e959645e0 100644
--- a/src/server/ApiManagers/UtilManager.ts
+++ b/src/server/ApiManagers/UtilManager.ts
@@ -3,6 +3,7 @@ import { Method } from "../RouteManager";
import { exec } from 'child_process';
import { command_line } from "../ActionUtilities";
import RouteSubscriber from "../RouteSubscriber";
+import { red } from "colors";
export default class UtilManager extends ApiManager {
@@ -11,7 +12,14 @@ export default class UtilManager extends ApiManager {
register({
method: Method.GET,
subscription: new RouteSubscriber("environment").add("key"),
- onValidation: ({ req, res }) => res.send(process.env[req.params.key])
+ onValidation: ({ req, res }) => {
+ const { key } = req.params;
+ const value = process.env[key];
+ if (!value) {
+ console.log(red(`process.env.${key} is not defined.`));
+ }
+ return res.send(value);
+ }
});
register({
diff --git a/src/server/Initialization.ts b/src/server/Initialization.ts
index ff2b64317..b58bc3e70 100644
--- a/src/server/Initialization.ts
+++ b/src/server/Initialization.ts
@@ -18,8 +18,8 @@ import * as whm from 'webpack-hot-middleware';
import * as fs from 'fs';
import * as request from 'request';
import RouteSubscriber from './RouteSubscriber';
-import { publicDirectory } from '.';
-import { logPort, addBeforeExitHandler } from './ActionUtilities';
+import { publicDirectory, ExitHandlers } from '.';
+import { logPort, } from './ActionUtilities';
import { timeMap } from './ApiManagers/UserManager';
import { blue, yellow } from 'colors';
@@ -31,6 +31,8 @@ export interface InitializationOptions {
routeSetter: RouteSetter;
}
+export let disconnect: Function;
+
export default async function InitializeServer(options: InitializationOptions) {
const { serverPort, routeSetter } = options;
const app = buildWithMiddleware(express());
@@ -65,7 +67,7 @@ export default async function InitializeServer(options: InitializationOptions) {
logPort("server", serverPort);
console.log();
});
- addBeforeExitHandler(async () => { await new Promise<Error>(resolve => server.close(resolve)); });
+ disconnect = async () => new Promise<Error>(resolve => server.close(resolve));
return isRelease;
}
diff --git a/src/server/Message.ts b/src/server/Message.ts
index aaee143e8..621abfd1e 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -50,6 +50,7 @@ export namespace MessageStore {
export const GetFields = new Message<string[]>("Get Fields"); // send string[] of 'id' get Transferable[] back
export const GetDocument = new Message<string>("Get Document");
export const DeleteAll = new Message<any>("Delete All");
+ export const ConnectionTerminated = new Message<string>("Connection Terminated");
export const GetRefField = new Message<string>("Get Ref Field");
export const GetRefFields = new Message<string[]>("Get Ref Fields");
diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts
index 60c34aa44..5c0bb508b 100644
--- a/src/server/Websocket/Websocket.ts
+++ b/src/server/Websocket/Websocket.ts
@@ -7,14 +7,16 @@ import { Search } from "../Search";
import * as io from 'socket.io';
import YoutubeApi from "../apis/youtube/youtubeApiSample";
import { GoogleCredentialsLoader } from "../credentials/CredentialsLoader";
-import { logPort, addBeforeExitHandler } from "../ActionUtilities";
+import { logPort } from "../ActionUtilities";
import { timeMap } from "../ApiManagers/UserManager";
import { green } from "colors";
+import { SolrManager } from "../ApiManagers/SearchManager";
export namespace WebSocket {
const clients: { [key: string]: Client } = {};
export const socketMap = new Map<SocketIO.Socket, string>();
+ export let disconnect: Function;
export async function start(serverPort: number, isRelease: boolean) {
await preliminaryFunctions();
@@ -52,8 +54,13 @@ export namespace WebSocket {
Utils.AddServerHandler(socket, MessageStore.DeleteFields, ids => DeleteFields(socket, ids));
Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
+
+ disconnect = () => {
+ socket.broadcast.emit("connection_terminated", Date.now());
+ socket.disconnect(true);
+ };
});
- addBeforeExitHandler(async () => { await new Promise<void>(resolve => endpoint.close(resolve)); });
+
endpoint.listen(socketPort);
logPort("websocket", socketPort);
}
@@ -171,7 +178,7 @@ export namespace WebSocket {
function UpdateField(socket: Socket, diff: Diff) {
Database.Instance.update(diff.id, diff.diff,
() => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false, "newDocuments");
- const docfield = diff.diff.$set;
+ const docfield = diff.diff.$set || diff.diff.$unset;
if (!docfield) {
return;
}
diff --git a/src/server/database.ts b/src/server/database.ts
index 5bdf1fc45..6e0771c11 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -6,10 +6,10 @@ import { DashUploadUtils } from './DashUploadUtils';
import { Credentials } from 'google-auth-library';
import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils';
import * as mongoose from 'mongoose';
-import { addBeforeExitHandler } from './ActionUtilities';
export namespace Database {
+ export let disconnect: Function;
const schema = 'Dash';
const port = 27017;
export const url = `mongodb://localhost:${port}/${schema}`;
@@ -25,7 +25,7 @@ export namespace Database {
export async function tryInitializeConnection() {
try {
const { connection } = mongoose;
- addBeforeExitHandler(async () => { await new Promise<any>(resolve => connection.close(resolve)); });
+ disconnect = async () => new Promise<any>(resolve => connection.close(resolve));
if (connection.readyState === ConnectionStates.disconnected) {
await new Promise<void>((resolve, reject) => {
connection.on('error', reject);
diff --git a/src/server/index.ts b/src/server/index.ts
index 551ce3898..6099af83c 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -10,7 +10,7 @@ import initializeServer from './Initialization';
import RouteManager, { Method, _success, _permission_denied, _error, _invalid, OnUnauthenticated } from './RouteManager';
import * as qs from 'query-string';
import UtilManager from './ApiManagers/UtilManager';
-import SearchManager from './ApiManagers/SearchManager';
+import { SearchManager, SolrManager } from './ApiManagers/SearchManager';
import UserManager from './ApiManagers/UserManager';
import { WebSocket } from './Websocket/Websocket';
import DownloadManager from './ApiManagers/DownloadManager';
@@ -21,12 +21,14 @@ import UploadManager from "./ApiManagers/UploadManager";
import { log_execution } from "./ActionUtilities";
import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager";
import GooglePhotosManager from "./ApiManagers/GooglePhotosManager";
-import DiagnosticManager from "./ApiManagers/DiagnosticManager";
import { yellow } from "colors";
+import { disconnect } from "../server/Initialization";
export const publicDirectory = path.resolve(__dirname, "public");
export const filesDirectory = path.resolve(publicDirectory, "files");
+export const ExitHandlers = new Array<() => void>();
+
/**
* These are the functions run before the server starts
* listening. Anything that must be complete
@@ -57,7 +59,6 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
new UserManager(),
new UploadManager(),
new DownloadManager(),
- new DiagnosticManager(),
new SearchManager(),
new PDFManager(),
new DeleteManager(),
@@ -79,6 +80,25 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
onValidation: ({ res }) => res.redirect("/home")
});
+ addSupervisedRoute({
+ method: Method.GET,
+ subscription: "/serverHeartbeat",
+ onValidation: ({ res }) => res.send(true)
+ });
+
+ addSupervisedRoute({
+ method: Method.GET,
+ subscription: "/shutdown",
+ onValidation: async ({ res }) => {
+ WebSocket.disconnect();
+ await disconnect();
+ await Database.disconnect();
+ SolrManager.SetRunning(false);
+ res.send("Server successfully shut down.");
+ process.exit(0);
+ }
+ });
+
const serve: OnUnauthenticated = ({ req, res }) => {
const detector = new mobileDetect(req.headers['user-agent'] || "");
const filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html';