From 71ebece4a97547ac9bec32cd30957d38c96fb755 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 6 Dec 2019 21:19:25 -0500 Subject: performance fixes for sidebar. stop re-rendering docking view when sidebar expands. fixed lightbox. --- src/client/views/MainView.scss | 17 ++++- src/client/views/MainView.tsx | 77 +++++++++++----------- .../views/collections/CollectionDockingView.tsx | 2 + src/client/views/collections/CollectionView.tsx | 14 +++- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../views/nodes/ContentFittingDocumentView.tsx | 1 + 6 files changed, 68 insertions(+), 45 deletions(-) (limited to 'src') 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..175af1abc 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -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 { @@ -260,38 +261,41 @@ export class MainView extends React.Component { getPHeight = () => this._panelHeight; getContentsHeight = () => this._panelHeight - this._buttonBarHeight; + _emptyPath = []; + @computed get mainDocView() { + return + } @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 {({ measureRef }) => -
- {!mainContainer ? (null) : - } +
+ {!mainContainer ? (null) : this.mainDocView}
} ; @@ -416,6 +420,7 @@ export class MainView extends React.Component { {CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
+ {this.docButtons} ; } @@ -423,10 +428,9 @@ export class MainView extends React.Component { const sidebar = this.userDoc && this.userDoc.sidebarContainer; return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
-
-
+
+
{this.flyout} @@ -471,7 +473,7 @@ export class MainView extends React.Component { @computed get docButtons() { if (CurrentUserUtils.UserDocument?.expandingButtons instanceof Doc) { return
+ style={{ height: !CurrentUserUtils.UserDocument.expandingButtons.isExpanded ? "42px" : undefined }} > - {this.docButtons} 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 { } @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/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 54f5a2c42..cd546696f 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -32,6 +32,7 @@ import { SelectionManager } from '../../util/SelectionManager'; import './CollectionView.scss'; import { FieldViewProps, FieldView } from '../nodes/FieldView'; import { Touchable } from '../Touchable'; +const path = require('path'); library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy); export enum CollectionViewType { @@ -234,10 +235,17 @@ export class CollectionView extends Touchable { } 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) : ( this._isLightboxOpen = false)} onMovePrevRequest={action(() => this._curLightboxImg = (this._curLightboxImg + images.length - 1) % images.length)} onMoveNextRequest={action(() => this._curLightboxImg = (this._curLightboxImg + 1) % images.length)} />); 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: { + 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()); } -- cgit v1.2.3-70-g09d2 From 600d06a162b4489fd52d5809eb9f5a69f28618fc Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 6 Dec 2019 23:11:51 -0500 Subject: performance tuning for sidebar --- src/Utils.ts | 2 ++ src/client/views/MainView.tsx | 11 +++++------ .../views/collections/CollectionStackingView.tsx | 20 +++++++++++++------- .../CollectionStackingViewFieldColumn.tsx | 6 +++--- src/client/views/collections/CollectionView.tsx | 2 ++ src/client/views/nodes/DocumentContentsView.tsx | 3 +++ 6 files changed, 28 insertions(+), 16 deletions(-) (limited to 'src') 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/views/MainView.tsx b/src/client/views/MainView.tsx index 175af1abc..dacc9226e 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'; @@ -261,11 +261,10 @@ export class MainView extends React.Component { getPHeight = () => this._panelHeight; getContentsHeight = () => this._panelHeight - this._buttonBarHeight; - _emptyPath = []; @computed get mainDocView() { return 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 (
doc) { onScroll={action((e: React.UIEvent) => 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) :
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 { - 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 cd546696f..411040332 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -32,6 +32,7 @@ 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); @@ -251,6 +252,7 @@ export class CollectionView extends Touchable { 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/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; @@ -57,6 +58,7 @@ export class DocumentContentsView extends React.Component { @computed get layout(): string { + TraceMobx(); if (!this.layoutDoc) return "

awaiting layout

"; const layout = Cast(this.layoutDoc[this.props.layoutKey], "string"); if (layout === undefined) { @@ -92,6 +94,7 @@ export class DocumentContentsView extends React.Component 7 || !this.layout) ? (null) : Date: Sat, 7 Dec 2019 00:30:00 -0500 Subject: only call resize() once after an image is loaded to speed things up. --- src/client/views/nodes/ImageBox.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') 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 { requestImageSize(srcpath) .then((size: any) => { @@ -223,11 +224,12 @@ export class ImageBox extends DocAnnotatableComponent 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
-- cgit v1.2.3-70-g09d2 From 304d7f239ae6e7b854a8da1124a919b7ba093b48 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 7 Dec 2019 18:35:32 -0500 Subject: initial commit --- src/client/DocServer.ts | 1 + src/client/util/ClientDiagnostics.ts | 34 -------------------- src/client/util/TooltipTextMenu.tsx | 2 +- src/client/views/Main.tsx | 2 -- src/server/ActionUtilities.ts | 8 ++--- src/server/ApiManagers/DiagnosticManager.ts | 30 ------------------ src/server/ApiManagers/SearchManager.ts | 49 ++++++++++++++++++++++++++++- src/server/Message.ts | 1 + src/server/Websocket/Websocket.ts | 4 ++- src/server/index.ts | 41 ++++++++++++++++++++++-- 10 files changed, 96 insertions(+), 76 deletions(-) delete mode 100644 src/client/util/ClientDiagnostics.ts delete mode 100644 src/server/ApiManagers/DiagnosticManager.ts (limited to 'src') 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/server/ActionUtilities.ts b/src/server/ActionUtilities.ts index 4fe7374d1..1ebeab203 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((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((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((resolve, reject) => { fs.writeFile(target, contents, (err) => err ? reject(err) : resolve()); }); @@ -75,5 +75,5 @@ export async function Prune(rootDirectory: string): Promise { export const Destroy = (mediaPath: string) => new Promise(resolve => fs.unlink(mediaPath, error => resolve(error === null))); export function addBeforeExitHandler(handler: NodeJS.BeforeExitListener) { - process.on("beforeExit", handler); + // 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..0e794fed6 100644 --- a/src/server/ApiManagers/SearchManager.ts +++ b/src/server/ApiManagers/SearchManager.ts @@ -4,11 +4,26 @@ import { Search } from "../Search"; const findInFiles = require('find-in-files'); import * as path from 'path'; import { pathToDirectory, Directory } from "./UploadManager"; +import { command_line, addBeforeExitHandler } from "../ActionUtilities"; +import request = require('request-promise'); +import { red, green, yellow, cyan } from "colors"; -export default class SearchManager extends ApiManager { +export class SearchManager extends ApiManager { protected initialize(register: Registration): void { + register({ + method: Method.GET, + subscription: "/startSolr", + onValidation: async ({ res }) => res.send((await SolrManager.SetRunning(true)) ? "Successfully started Solr!" : "Uh oh! Check the console for the error that occurred while starting Solr") + }); + + register({ + method: Method.GET, + subscription: "/stopSolr", + onValidation: async ({ res }) => res.send((await SolrManager.SetRunning(false)) ? "Successfully stopped Solr!" : "Uh oh! Check the console for the error that occurred while stopping Solr") + }); + register({ method: Method.GET, subscription: "/textsearch", @@ -46,4 +61,36 @@ export default class SearchManager extends ApiManager { } +} + +export namespace SolrManager { + + export async function initializeSolr() { + console.log(cyan("\nInspecting Solr status...")); + try { + await request("http://localhost:8983"); + console.log(green('Solr already running\n')); + } catch (e) { + console.log(cyan('Initializing Solr...')); + await SolrManager.SetRunning(true); + } finally { + addBeforeExitHandler(async () => SolrManager.SetRunning(false)); + } + } + + export async function SetRunning(status: boolean): Promise { + const args = status ? "start" : "stop -p 8983"; + console.log(`Solr management: trying to ${args}`); + try { + 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}`)); + if (status) { + process.exit(0); + } + return false; + } + } + } \ No newline at end of file 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("Get Fields"); // send string[] of 'id' get Transferable[] back export const GetDocument = new Message("Get Document"); export const DeleteAll = new Message("Delete All"); + export const ConnectionTerminated = new Message("Connection Terminated"); export const GetRefField = new Message("Get Ref Field"); export const GetRefFields = new Message("Get Ref Fields"); diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts index 60c34aa44..de1d5eb25 100644 --- a/src/server/Websocket/Websocket.ts +++ b/src/server/Websocket/Websocket.ts @@ -10,6 +10,7 @@ import { GoogleCredentialsLoader } from "../credentials/CredentialsLoader"; import { logPort, addBeforeExitHandler } from "../ActionUtilities"; import { timeMap } from "../ApiManagers/UserManager"; import { green } from "colors"; +import { ExitHandlers } from ".."; export namespace WebSocket { @@ -52,8 +53,9 @@ export namespace WebSocket { Utils.AddServerHandler(socket, MessageStore.DeleteFields, ids => DeleteFields(socket, ids)); Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField); Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields); + + ExitHandlers.push(() => socket.broadcast.emit("connection_terminated", Date.now())); }); - addBeforeExitHandler(async () => { await new Promise(resolve => endpoint.close(resolve)); }); endpoint.listen(socketPort); logPort("websocket", socketPort); } diff --git a/src/server/index.ts b/src/server/index.ts index 551ce3898..42b4f7ff2 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,18 +21,48 @@ 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"; 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 * before clients can access the server should be run or awaited here. */ async function preliminaryFunctions() { + process.on('SIGINT', () => { + const { stdin, stdout, stderr } = process; + stdin.resume(); + stdout.resume(); + stderr.resume(); + ExitHandlers.forEach(handler => handler()); + console.log("Okay, now we're done..."); + // process.exit(0); + }); + + (process as any).on('cleanup', () => { + console.log("CLEANING UP!"); + }); + + process.on('exit', function () { + (process.emit as Function)('cleanup'); + }); + + //catch uncaught exceptions, trace, then exit normally + process.on('uncaughtException', function (e) { + console.log('Uncaught Exception...'); + process.exit(99); + }); + process.on('unhandledRejection', function (e) { + console.log('Unhandled Rejection...'); + process.exit(99); + }); + + await SolrManager.initializeSolr(); await GoogleCredentialsLoader.loadCredentials(); GoogleApiServerUtils.processProjectCredentials(); await DashUploadUtils.buildFileDirectories(); @@ -57,7 +87,6 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: new UserManager(), new UploadManager(), new DownloadManager(), - new DiagnosticManager(), new SearchManager(), new PDFManager(), new DeleteManager(), @@ -79,6 +108,12 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: onValidation: ({ res }) => res.redirect("/home") }); + addSupervisedRoute({ + method: Method.GET, + subscription: "/serverHeartbeat", + onValidation: ({ res }) => res.send(true) + }); + const serve: OnUnauthenticated = ({ req, res }) => { const detector = new mobileDetect(req.headers['user-agent'] || ""); const filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html'; -- cgit v1.2.3-70-g09d2 From b4bc5f534146968a0923c04927be66130f36f463 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 8 Dec 2019 11:10:05 -0500 Subject: minor exception fix --- src/client/views/MainView.tsx | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index dacc9226e..bcdc3a453 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -57,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 { 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; @@ -206,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 @@ -316,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; } } @@ -331,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, 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, 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); @@ -360,7 +358,6 @@ export class MainView extends React.Component { return (null); } const sidebarButtonsDoc = Cast(CurrentUserUtils.UserDocument.sidebarButtons, Doc) as Doc; - sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30; return
{ MainView.Instance._flyoutTranslate = true; MainView.Instance.flyoutWidth = (MainView.Instance.flyoutWidth || 250); + MainView.Instance.sidebarButtonsDoc.columnWidth = MainView.Instance.flyoutWidth / 3 - 30; }); @computed get expandButton() { -- cgit v1.2.3-70-g09d2 From c06a5948341986e034c8fde10ae692f2774207f4 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 8 Dec 2019 13:22:23 -0500 Subject: semi-fixed solr to "erase" fields that have been deleted --- src/server/Websocket/Websocket.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts index de1d5eb25..f65cc235c 100644 --- a/src/server/Websocket/Websocket.ts +++ b/src/server/Websocket/Websocket.ts @@ -173,7 +173,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; } -- cgit v1.2.3-70-g09d2 From 297c5e428d8702ac7a192c023e081fe556bb5fce Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 8 Dec 2019 15:24:05 -0500 Subject: solr interactions hopefully more stable, untested but safe shutdown route --- src/server/ActionUtilities.ts | 4 --- src/server/ApiManagers/SearchManager.ts | 41 +++++++++++-------------------- src/server/Initialization.ts | 8 +++--- src/server/Websocket/Websocket.ts | 11 ++++++--- src/server/database.ts | 4 +-- src/server/index.ts | 43 +++++++++++---------------------- 6 files changed, 43 insertions(+), 68 deletions(-) (limited to 'src') diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts index 1ebeab203..94008e171 100644 --- a/src/server/ActionUtilities.ts +++ b/src/server/ActionUtilities.ts @@ -73,7 +73,3 @@ export async function Prune(rootDirectory: string): Promise { } export const Destroy = (mediaPath: string) => new Promise(resolve => fs.unlink(mediaPath, error => resolve(error === null))); - -export function addBeforeExitHandler(handler: NodeJS.BeforeExitListener) { - // process.on("beforeExit", handler); -} diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts index 0e794fed6..ccd0896bd 100644 --- a/src/server/ApiManagers/SearchManager.ts +++ b/src/server/ApiManagers/SearchManager.ts @@ -4,9 +4,10 @@ import { Search } from "../Search"; const findInFiles = require('find-in-files'); import * as path from 'path'; import { pathToDirectory, Directory } from "./UploadManager"; -import { command_line, addBeforeExitHandler } from "../ActionUtilities"; +import { command_line } from "../ActionUtilities"; import request = require('request-promise'); -import { red, green, yellow, cyan } from "colors"; +import { red } from "colors"; +import RouteSubscriber from "../RouteSubscriber"; export class SearchManager extends ApiManager { @@ -14,14 +15,16 @@ export class SearchManager extends ApiManager { register({ method: Method.GET, - subscription: "/startSolr", - onValidation: async ({ res }) => res.send((await SolrManager.SetRunning(true)) ? "Successfully started Solr!" : "Uh oh! Check the console for the error that occurred while starting Solr") - }); - - register({ - method: Method.GET, - subscription: "/stopSolr", - onValidation: async ({ res }) => res.send((await SolrManager.SetRunning(false)) ? "Successfully stopped Solr!" : "Uh oh! Check the console for the error that occurred while stopping Solr") + 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({ @@ -65,30 +68,14 @@ export class SearchManager extends ApiManager { export namespace SolrManager { - export async function initializeSolr() { - console.log(cyan("\nInspecting Solr status...")); - try { - await request("http://localhost:8983"); - console.log(green('Solr already running\n')); - } catch (e) { - console.log(cyan('Initializing Solr...')); - await SolrManager.SetRunning(true); - } finally { - addBeforeExitHandler(async () => SolrManager.SetRunning(false)); - } - } - export async function SetRunning(status: boolean): Promise { const args = status ? "start" : "stop -p 8983"; - console.log(`Solr management: trying to ${args}`); 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}`)); - if (status) { - process.exit(0); - } return false; } } 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(resolve => server.close(resolve)); }); + disconnect = async () => new Promise(resolve => server.close(resolve)); return isRelease; } diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts index f65cc235c..5c0bb508b 100644 --- a/src/server/Websocket/Websocket.ts +++ b/src/server/Websocket/Websocket.ts @@ -7,15 +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 { ExitHandlers } from ".."; +import { SolrManager } from "../ApiManagers/SearchManager"; export namespace WebSocket { const clients: { [key: string]: Client } = {}; export const socketMap = new Map(); + export let disconnect: Function; export async function start(serverPort: number, isRelease: boolean) { await preliminaryFunctions(); @@ -54,8 +55,12 @@ export namespace WebSocket { Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField); Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields); - ExitHandlers.push(() => socket.broadcast.emit("connection_terminated", Date.now())); + disconnect = () => { + socket.broadcast.emit("connection_terminated", Date.now()); + socket.disconnect(true); + }; }); + endpoint.listen(socketPort); logPort("websocket", socketPort); } 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(resolve => connection.close(resolve)); }); + disconnect = async () => new Promise(resolve => connection.close(resolve)); if (connection.readyState === ConnectionStates.disconnected) { await new Promise((resolve, reject) => { connection.on('error', reject); diff --git a/src/server/index.ts b/src/server/index.ts index 42b4f7ff2..9337dc1a8 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -22,6 +22,7 @@ import { log_execution } from "./ActionUtilities"; import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager"; import GooglePhotosManager from "./ApiManagers/GooglePhotosManager"; import { yellow } from "colors"; +import { disconnect } from "../server/Initialization"; export const publicDirectory = path.resolve(__dirname, "public"); export const filesDirectory = path.resolve(publicDirectory, "files"); @@ -34,35 +35,6 @@ export const ExitHandlers = new Array<() => void>(); * before clients can access the server should be run or awaited here. */ async function preliminaryFunctions() { - process.on('SIGINT', () => { - const { stdin, stdout, stderr } = process; - stdin.resume(); - stdout.resume(); - stderr.resume(); - ExitHandlers.forEach(handler => handler()); - console.log("Okay, now we're done..."); - // process.exit(0); - }); - - (process as any).on('cleanup', () => { - console.log("CLEANING UP!"); - }); - - process.on('exit', function () { - (process.emit as Function)('cleanup'); - }); - - //catch uncaught exceptions, trace, then exit normally - process.on('uncaughtException', function (e) { - console.log('Uncaught Exception...'); - process.exit(99); - }); - process.on('unhandledRejection', function (e) { - console.log('Unhandled Rejection...'); - process.exit(99); - }); - - await SolrManager.initializeSolr(); await GoogleCredentialsLoader.loadCredentials(); GoogleApiServerUtils.processProjectCredentials(); await DashUploadUtils.buildFileDirectories(); @@ -114,6 +86,19 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: onValidation: ({ res }) => res.send(true) }); + addSupervisedRoute({ + method: Method.GET, + subscription: "/shutdown", + onValidation: async ({ res }) => { + WebSocket.disconnect(); + await disconnect(); + await Database.disconnect(); + await 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'; -- cgit v1.2.3-70-g09d2 From 88072c644ff2b9176507255f5c0db5c9c7862322 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 8 Dec 2019 15:25:51 -0500 Subject: no await solr start --- src/server/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/server/index.ts b/src/server/index.ts index 9337dc1a8..6099af83c 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -93,7 +93,7 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: WebSocket.disconnect(); await disconnect(); await Database.disconnect(); - await SolrManager.SetRunning(false); + SolrManager.SetRunning(false); res.send("Server successfully shut down."); process.exit(0); } -- cgit v1.2.3-70-g09d2 From 21b22e62b6f005dcdbc83983506dc6af38694e24 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 8 Dec 2019 15:34:56 -0500 Subject: logging process.env accesses --- src/server/ApiManagers/UtilManager.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src') 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({ -- cgit v1.2.3-70-g09d2 From 6c28c3f00e5072b75a5d38da49a234cf347c6605 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 8 Dec 2019 22:32:28 -0500 Subject: fixed infinite loop render crash with textboxes and treeview --- src/client/views/nodes/FormattedTextBox.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 3302d2e54..8dadbb668 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1067,8 +1067,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; + } } } -- cgit v1.2.3-70-g09d2