aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2024-04-23 16:20:08 -0400
committerbobzel <zzzman@gmail.com>2024-04-23 16:20:08 -0400
commit9e809f8748d1812bb03ec6471aa6f97467f8f75a (patch)
tree6657f2290e1c1a10456a32d2e1462981f461c8d0 /src
parent939e18624af4252551f38c43335ee8ef0acd144c (diff)
fixes for rich text menu updates and setting parameters on text doc vs within in RTF. Lots of lint cleanup.
Diffstat (limited to 'src')
-rw-r--r--src/client/DocServer.ts15
-rw-r--r--src/client/util/CurrentUserUtils.ts2
-rw-r--r--src/client/util/DocumentManager.ts36
-rw-r--r--src/client/util/request-image-size.ts26
-rw-r--r--src/client/views/ScriptingRepl.tsx191
-rw-r--r--src/client/views/StyleProvider.tsx108
-rw-r--r--src/client/views/collections/CollectionMenu.tsx73
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx181
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx2
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss2
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx53
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts14
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx56
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts19
-rw-r--r--src/client/views/webcam/DashWebRTCVideo.scss82
-rw-r--r--src/client/views/webcam/DashWebRTCVideo.tsx76
-rw-r--r--src/client/views/webcam/WebCamLogic.js292
-rw-r--r--src/fields/Doc.ts3
-rw-r--r--src/fields/util.ts6
-rw-r--r--src/server/ActionUtilities.ts79
-rw-r--r--src/server/ApiManagers/ApiManager.ts4
-rw-r--r--src/server/ApiManagers/DeleteManager.ts14
-rw-r--r--src/server/ApiManagers/DownloadManager.ts6
-rw-r--r--src/server/ApiManagers/MongoStore.js21
-rw-r--r--src/server/ApiManagers/SearchManager.ts9
-rw-r--r--src/server/ApiManagers/SessionManager.ts26
-rw-r--r--src/server/ApiManagers/UtilManager.ts31
-rw-r--r--src/server/DashSession/Session/agents/applied_session_agent.ts23
-rw-r--r--src/server/DashSession/Session/agents/monitor.ts48
-rw-r--r--src/server/DashSession/Session/agents/process_message_router.ts12
-rw-r--r--src/server/DashSession/Session/agents/promisified_ipc_manager.ts51
-rw-r--r--src/server/DashSession/Session/agents/server_worker.ts46
-rw-r--r--src/server/DashSession/Session/utilities/repl.ts76
-rw-r--r--src/server/DashSession/Session/utilities/session_config.ts104
-rw-r--r--src/server/DashSession/Session/utilities/utilities.ts43
-rw-r--r--src/server/DashStats.ts5
-rw-r--r--src/server/DashUploadUtils.ts8
-rw-r--r--src/server/GarbageCollector.ts70
-rw-r--r--src/server/MemoryDatabase.ts37
-rw-r--r--src/server/PdfTypes.ts20
-rw-r--r--src/server/ProcessFactory.ts54
-rw-r--r--src/server/RouteSubscriber.ts5
-rw-r--r--src/server/SharedMediaTypes.ts31
-rw-r--r--src/server/apis/google/CredentialsLoader.ts24
-rw-r--r--src/server/apis/google/GoogleApiServerUtils.ts8
-rw-r--r--src/server/apis/google/SharedTypes.ts13
-rw-r--r--src/server/apis/youtube/youtubeApiSample.d.ts2
-rw-r--r--src/server/authentication/DashUserModel.ts8
-rw-r--r--src/server/authentication/Passport.ts25
-rw-r--r--src/server/database.ts1
-rw-r--r--src/server/index.ts48
-rw-r--r--src/server/updateProtos.ts6
-rw-r--r--src/server/websocket.ts40
53 files changed, 963 insertions, 1272 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index b833d3287..cf7a61d24 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -8,7 +8,7 @@ import { FieldLoader } from '../fields/FieldLoader';
import { HandleUpdate, Id, Parent } from '../fields/FieldSymbols';
import { ObjectField, SetObjGetRefField, SetObjGetRefFields } from '../fields/ObjectField';
import { RefField } from '../fields/RefField';
-import { GestureContent, Message, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from './../server/Message';
+import { GestureContent, Message, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from '../server/Message';
import { SerializationHelper } from './util/SerializationHelper';
/**
@@ -63,7 +63,7 @@ export namespace DocServer {
return foundDocId ? (_cache[foundDocId] as Doc) : undefined;
}
- export let _socket: Socket;
+ let _socket: Socket;
// this client's distinct GUID created at initialization
let USER_ID: string;
// indicates whether or not a document is currently being udpated, and, if so, its id
@@ -317,7 +317,7 @@ export namespace DocServer {
// ii) which are already in the process of being fetched
// iii) which already exist in the cache
// eslint-disable-next-line no-restricted-syntax
- for (const id of ids.filter(id => id)) {
+ for (const id of ids.filter(filterid => filterid)) {
const cached = _cache[id];
if (cached === undefined) {
defaultPromises.push({
@@ -362,6 +362,7 @@ export namespace DocServer {
for (const field of serializedFields) {
processed++;
if (processed % 150 === 0) {
+ // eslint-disable-next-line no-loop-func
runInAction(() => {
FieldLoader.ServerLoadStatus.retrieved = processed;
});
@@ -375,6 +376,7 @@ export namespace DocServer {
// deserialize
// adds to a list of promises that will be awaited asynchronously
promises.push(
+ // eslint-disable-next-line no-loop-func
(_cache[field.id] = SerializationHelper.Deserialize(field).then(deserialized => {
// overwrite or delete any promises (that we inserted as flags
// to indicate that the field was in the process of being fetched). Now everything
@@ -447,7 +449,10 @@ export namespace DocServer {
}
// WRITE A NEW DOCUMENT TO THE SERVER
- export let CacheNeedsUpdate = false;
+ let _cacheNeedsUpdate = false;
+ export function CacheNeedsUpdate() {
+ return _cacheNeedsUpdate;
+ }
/**
* A wrapper around the function local variable _createField.
@@ -456,7 +461,7 @@ export namespace DocServer {
* @param field the [RefField] to be serialized and sent to the server to be stored in the database
*/
export function CreateField(field: RefField) {
- CacheNeedsUpdate = true;
+ _cacheNeedsUpdate = true;
_CreateField(field);
}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 6dba8027d..acbd0c0b9 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -946,7 +946,7 @@ pie title Minerals in my tap water
// eslint-disable-next-line no-new
new LinkManager();
- DocServer.CacheNeedsUpdate && setTimeout(UPDATE_SERVER_CACHE, 2500);
+ DocServer.CacheNeedsUpdate() && setTimeout(UPDATE_SERVER_CACHE, 2500);
setInterval(UPDATE_SERVER_CACHE, 120000);
return doc;
}
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 9a7786125..cca92816f 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -178,11 +178,13 @@ export class DocumentManager {
return containerDocContext;
}
+ static _howl: Howl;
static playAudioAnno(doc: Doc) {
const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations'], listSpec(AudioField), null)?.lastElement();
if (anno) {
+ this._howl?.stop();
if (anno instanceof AudioField) {
- new Howl({
+ this._howl = new Howl({
src: [anno.url.href],
format: ['mp3'],
autoplay: true,
@@ -200,13 +202,13 @@ export class DocumentManager {
static _overlayViews = new ObservableSet<DocumentView>();
public static LinkCommonAncestor(linkDoc: Doc) {
- const anchor = (which: number) => {
+ const getAnchor = (which: number) => {
const anch = DocCast(linkDoc['link_anchor_' + which]);
const anchor = anch?.layout_unrendered ? DocCast(anch.annotationOn) : anch;
return DocumentManager.Instance.getDocumentView(anchor);
};
- const anchor1 = anchor(1);
- const anchor2 = anchor(2);
+ const anchor1 = getAnchor(1);
+ const anchor2 = getAnchor(2);
return anchor1
?.docViewPath()
.reverse()
@@ -275,9 +277,13 @@ export class DocumentManager {
if (rootContextView) {
const target = await this.focusViewsInPath(rootContextView, options, childViewIterator);
- this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc);
- finished?.(target.focused);
- } else finished?.(false);
+ if (target) {
+ this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc);
+ finished?.(target.focused);
+ return;
+ }
+ }
+ finished?.(false);
};
focusViewsInPath = async (
@@ -289,12 +295,15 @@ export class DocumentManager {
let focused = false;
let docView = docViewIn;
const options = optionsIn;
- while (true) {
+ const maxFocusLength = 100; // want to keep focusing until we get to target, but avoid an infinite loop
+ for (let i = 0; i < maxFocusLength; i++) {
if (docView.Document.layout_fieldKey === 'layout_icon') {
- // eslint-disable-next-line no-await-in-loop
- await new Promise<any>(res => {
+ // eslint-disable-next-line no-loop-func
+ const prom = new Promise<void>(res => {
docView.iconify(res);
});
+ // eslint-disable-next-line no-await-in-loop
+ await prom;
options.didMove = true;
}
const nextFocus = docView._props.focus(docView.Document, options); // focus the view within its container
@@ -305,6 +314,7 @@ export class DocumentManager {
contextView = options.anchorDoc?.layout_unrendered && !childDocView.Document.layout_unrendered ? childDocView : docView;
docView = childDocView;
}
+ return undefined;
};
@action
@@ -347,9 +357,9 @@ export function DocFocusOrOpen(docIn: Doc, optionsIn: FocusViewOptions = { willZ
const showDoc = !Doc.IsSystem(container) && !cv ? container : doc;
options.toggleTarget = undefined;
DocumentManager.Instance.showDocument(showDoc, options, () => DocumentManager.Instance.showDocument(doc, { ...options, openLocation: undefined })).then(() => {
- const cv = DocumentManager.Instance.getDocumentView(containingDoc);
- const dv = DocumentManager.Instance.getDocumentView(doc, cv);
- dv && Doc.linkFollowHighlight(dv.Document);
+ const cvFound = DocumentManager.Instance.getDocumentView(containingDoc);
+ const dvFound = DocumentManager.Instance.getDocumentView(doc, cvFound);
+ dvFound && Doc.linkFollowHighlight(dvFound.Document);
});
}
};
diff --git a/src/client/util/request-image-size.ts b/src/client/util/request-image-size.ts
index 57e8516ac..0f98a2710 100644
--- a/src/client/util/request-image-size.ts
+++ b/src/client/util/request-image-size.ts
@@ -14,19 +14,17 @@ const imageSize = require('image-size');
const HttpError = require('standard-http-error');
module.exports = function requestImageSize(options: any) {
- let opts = {
+ let opts: any = {
encoding: null,
};
if (options && typeof options === 'object') {
opts = Object.assign(options, opts);
} else if (options && typeof options === 'string') {
- opts = Object.assign(
- {
- uri: options,
- },
- opts
- );
+ opts = {
+ uri: options,
+ ...opts,
+ };
} else {
return Promise.reject(new Error('You should provide an URI string or a "request" options object.'));
}
@@ -38,7 +36,8 @@ module.exports = function requestImageSize(options: any) {
req.on('response', (res: any) => {
if (res.statusCode >= 400) {
- return reject(new HttpError(res.statusCode, res.statusMessage));
+ reject(new HttpError(res.statusCode, res.statusMessage));
+ return;
}
let buffer = Buffer.from([]);
@@ -51,20 +50,23 @@ module.exports = function requestImageSize(options: any) {
size = imageSize(buffer);
if (size) {
resolve(size);
- return req.abort();
+ req.abort();
}
- } catch (err) {}
+ } catch (err) {
+ /* empty */
+ }
});
res.on('error', reject);
res.on('end', () => {
if (!size) {
- return reject(new Error('Image has no size'));
+ reject(new Error('Image has no size'));
+ return;
}
size.downloaded = buffer.length;
- return resolve(size);
+ resolve(size);
});
});
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index acf0ecff4..ba2e22b3b 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable react/no-array-index-key */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
@@ -12,6 +15,36 @@ import { OverlayView } from './OverlayView';
import './ScriptingRepl.scss';
import { DocumentIconContainer } from './nodes/DocumentIcon';
+interface replValueProps {
+ scrollToBottom: () => void;
+ value: any;
+ name?: string;
+}
+@observer
+export class ScriptingValueDisplay extends ObservableReactComponent<replValueProps> {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
+ render() {
+ const val = this._props.name ? this._props.value[this._props.name] : this._props.value;
+ const title = (name: string) => (
+ <>
+ {this._props.name ? <b>{this._props.name} : </b> : <> </>}
+ {name}
+ </>
+ );
+ if (typeof val === 'object') {
+ // eslint-disable-next-line no-use-before-define
+ return <ScriptingObjectDisplay scrollToBottom={this._props.scrollToBottom} value={val} name={this._props.name} />;
+ }
+ if (typeof val === 'function') {
+ return <div className="scriptingObject-leaf">{title('[Function]')}</div>;
+ }
+ return <div className="scriptingObject-leaf">{title(String(val))}</div>;
+ }
+}
interface ReplProps {
scrollToBottom: () => void;
value: { [key: string]: any };
@@ -37,7 +70,7 @@ export class ScriptingObjectDisplay extends ObservableReactComponent<ReplProps>
const name = (proto && proto.constructor && proto.constructor.name) || String(val);
const title = (
<>
- {this.props.name ? <b>{this._props.name} : </b> : <></>}
+ {this.props.name ? <b>{this._props.name} : </b> : null}
{name}
</>
);
@@ -50,53 +83,23 @@ export class ScriptingObjectDisplay extends ObservableReactComponent<ReplProps>
{title} (+{Object.keys(val).length})
</div>
);
- } else {
- return (
- <div className="scriptingObject-open">
- <div>
- <span onClick={this.toggle} className="scriptingObject-icon">
- <FontAwesomeIcon icon="caret-down" size="sm" />
- </span>
- {title}
- </div>
- <div className="scriptingObject-fields">
- {Object.keys(val).map(key => (
- <ScriptingValueDisplay {...this._props} name={key} />
- ))}
- </div>
- </div>
- );
}
- }
-}
-
-interface replValueProps {
- scrollToBottom: () => void;
- value: any;
- name?: string;
-}
-@observer
-export class ScriptingValueDisplay extends ObservableReactComponent<replValueProps> {
- constructor(props: any) {
- super(props);
- makeObservable(this);
- }
-
- render() {
- const val = this._props.name ? this._props.value[this._props.name] : this._props.value;
- const title = (name: string) => (
- <>
- {this._props.name ? <b>{this._props.name} : </b> : <> </>}
- {name}
- </>
+ return (
+ <div className="scriptingObject-open">
+ <div>
+ <span onClick={this.toggle} className="scriptingObject-icon">
+ <FontAwesomeIcon icon="caret-down" size="sm" />
+ </span>
+ {title}
+ </div>
+ <div className="scriptingObject-fields">
+ {Object.keys(val).map(key => (
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ <ScriptingValueDisplay {...this._props} name={key} />
+ ))}
+ </div>
+ </div>
);
- if (typeof val === 'object') {
- return <ScriptingObjectDisplay scrollToBottom={this._props.scrollToBottom} value={val} name={this._props.name} />;
- } else if (typeof val === 'function') {
- const name = '[Function]';
- return <div className="scriptingObject-leaf">{title('[Function]')}</div>;
- }
- return <div className="scriptingObject-leaf">{title(String(val))}</div>;
}
}
@@ -119,47 +122,45 @@ export class ScriptingRepl extends ObservableReactComponent<{}> {
private args: any = {};
- getTransformer = (): Transformer => {
- return {
- transformer: context => {
- const knownVars: { [name: string]: number } = {};
- const usedDocuments: number[] = [];
- ScriptingGlobals.getGlobals().forEach((global: any) => (knownVars[global] = 1));
- return root => {
- function visit(node: ts.Node) {
- let skip = false;
- if (ts.isIdentifier(node)) {
- if (ts.isParameter(node.parent)) {
- skip = true;
- knownVars[node.text] = 1;
- }
+ getTransformer = (): Transformer => ({
+ transformer: context => {
+ const knownVars: { [name: string]: number } = {};
+ const usedDocuments: number[] = [];
+ ScriptingGlobals.getGlobals().forEach((global: any) => {
+ knownVars[global] = 1;
+ });
+ return root => {
+ function visit(nodeIn: ts.Node) {
+ if (ts.isIdentifier(nodeIn)) {
+ if (ts.isParameter(nodeIn.parent)) {
+ knownVars[nodeIn.text] = 1;
}
- node = ts.visitEachChild(node, visit, context);
+ }
+ const node = ts.visitEachChild(nodeIn, visit, context);
- if (ts.isIdentifier(node)) {
- const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
- const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
- if (ts.isParameter(node.parent)) {
- // delete knownVars[node.text];
- } else if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) {
- const match = node.text.match(/d([0-9]+)/);
- if (match) {
- const m = parseInt(match[1]);
- usedDocuments.push(m);
- } else {
- return ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('args'), node);
- // ts.createPropertyAccess(ts.createIdentifier('args'), node);
- }
+ if (ts.isIdentifier(node)) {
+ const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
+ const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
+ if (ts.isParameter(node.parent)) {
+ // delete knownVars[node.text];
+ } else if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) {
+ const match = node.text.match(/d([0-9]+)/);
+ if (match) {
+ const m = parseInt(match[1]);
+ usedDocuments.push(m);
+ } else {
+ return ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('args'), node);
+ // ts.createPropertyAccess(ts.createIdentifier('args'), node);
}
}
-
- return node;
}
- return ts.visitNode(root, visit);
- };
- },
- };
- };
+
+ return node;
+ }
+ return ts.visitNode(root, visit);
+ };
+ },
+ });
@action
onKeyDown = (e: React.KeyboardEvent) => {
@@ -168,14 +169,16 @@ export class ScriptingRepl extends ObservableReactComponent<{}> {
case 'Enter': {
e.stopPropagation();
const docGlobals: { [name: string]: any } = {};
- DocumentManager.Instance.DocumentViews.forEach((dv, i) => (docGlobals[`d${i}`] = dv.Document));
+ DocumentManager.Instance.DocumentViews.forEach((dv, i) => {
+ docGlobals[`d${i}`] = dv.Document;
+ });
const globals = ScriptingGlobals.makeMutableGlobalsCopy(docGlobals);
const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: 'any' }, transformer: this.getTransformer(), globals });
if (!script.compiled) {
this.commands.push({ command: this.commandString, result: script.errors });
return;
}
- const result = undoable(() => script.run({ args: this.args }, e => this.commands.push({ command: this.commandString, result: e.toString() })), 'run:' + this.commandString)();
+ const result = undoable(() => script.run({ args: this.args }, () => this.commands.push({ command: this.commandString, result: e.toString() })), 'run:' + this.commandString)();
if (result.success) {
this.commands.push({ command: this.commandString, result: result.result });
this.commandsHistory.push(this.commandString);
@@ -260,18 +263,16 @@ export class ScriptingRepl extends ObservableReactComponent<{}> {
return (
<div className="scriptingRepl-outerContainer">
<div className="scriptingRepl-commandsContainer" style={{ background: SettingsManager.userBackgroundColor }} ref={this.commandsRef}>
- {this.commands.map(({ command, result }, i) => {
- return (
- <div className="scriptingRepl-resultContainer" style={{ background: SettingsManager.userBackgroundColor }} key={i}>
- <div className="scriptingRepl-commandString" style={{ background: SettingsManager.userBackgroundColor }}>
- {command || <br />}
- </div>
- <div className="scriptingRepl-commandResult" style={{ background: SettingsManager.userBackgroundColor }}>
- {<ScriptingValueDisplay scrollToBottom={this.maybeScrollToBottom} value={result} />}
- </div>
+ {this.commands.map(({ command, result }, i) => (
+ <div className="scriptingRepl-resultContainer" style={{ background: SettingsManager.userBackgroundColor }} key={i}>
+ <div className="scriptingRepl-commandString" style={{ background: SettingsManager.userBackgroundColor }}>
+ {command || <br />}
+ </div>
+ <div className="scriptingRepl-commandResult" style={{ background: SettingsManager.userBackgroundColor }}>
+ <ScriptingValueDisplay scrollToBottom={this.maybeScrollToBottom} value={result} />
</div>
- );
- })}
+ </div>
+ ))}
</div>
<input
className="scriptingRepl-commandInput"
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 75f1a7d80..3697aa010 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -49,6 +49,7 @@ export enum StyleProp {
TitleHeight = 'titleHeight', // Height of Title area
ShowTitle = 'layout_showTitle', // whether to display a title on a Document (optional :hover suffix)
BorderPath = 'customBorder', // border path for document view
+ FontColor = 'fontColor', // color o tet
FontSize = 'fontSize', // size of text font
FontFamily = 'fontFamily', // font family of text
FontWeight = 'fontWeight', // font weight of text
@@ -109,13 +110,35 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
const layoutDoc = doc ? Doc.Layout(doc) : doc;
const isOpen = property.includes(':open');
const boxBackground = property.includes(':box');
- const fieldKey = props?.fieldKey ? props.fieldKey + '_' : isCaption ? 'caption_' : '';
- const isInk = () => layoutDoc?._layout_isSvg && !props?.LayoutTemplateString;
+ const {
+ fieldKey: fieldKeyProp,
+ styleProvider,
+ pointerEvents,
+ isGroupActive,
+ isDocumentActive,
+ containerViewPath,
+ childFilters,
+ hideCaptions,
+ // eslint-disable-next-line camelcase
+ layout_showTitle,
+ childFiltersByRanges,
+ renderDepth,
+ docViewPath,
+ DocumentView,
+ LayoutTemplateString,
+ disableBrushing,
+ NativeDimScaling,
+ PanelWidth,
+ PanelHeight,
+ } = props || {}; // extract props that are not shared between fieldView and documentView props.
+ const fieldKey = fieldKeyProp ? fieldKeyProp + '_' : isCaption ? 'caption_' : '';
+ const isInk = () => layoutDoc?._layout_isSvg && !LayoutTemplateString;
const lockedPosition = () => doc && BoolCast(doc._lockedPosition);
- const titleHeight = () => props?.styleProvider?.(doc, props, StyleProp.TitleHeight);
- const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor + ':nonTransparent' + (isNonTransparentLevel + 1));
- const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity);
- const layoutShowTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle);
+ const titleHeight = () => styleProvider?.(doc, props, StyleProp.TitleHeight);
+ const backgroundCol = () => styleProvider?.(doc, props, StyleProp.BackgroundColor + ':nonTransparent' + (isNonTransparentLevel + 1));
+ const color = () => styleProvider?.(doc, props, StyleProp.Color);
+ const opacity = () => styleProvider?.(doc, props, StyleProp.Opacity);
+ const layoutShowTitle = () => styleProvider?.(doc, props, StyleProp.ShowTitle);
// prettier-ignore
switch (property.split(':')[0]) {
case StyleProp.TreeViewIcon: {
@@ -137,7 +160,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
}
case StyleProp.Highlighting:
if (doc && (Doc.IsSystem(doc) || doc.type === DocumentType.FONTICON)) return undefined;
- if (doc && !doc.layout_disableBrushing && !props?.disableBrushing) {
+ if (doc && !doc.layout_disableBrushing && !disableBrushing) {
const selected = Array.from(doc?.[DocViews]??[]).filter(dv => dv.IsSelected).length;
const highlightIndex = Doc.GetBrushHighlightStatus(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0);
const highlightColor = ['transparent', 'rgb(68, 118, 247)', selected ? "black" : 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
@@ -152,26 +175,27 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
}
}
return undefined;
- case StyleProp.DocContents:return undefined;
- case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
- case StyleProp.Opacity: return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
- case StyleProp.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(Doc.UserDoc().fontSize));
- case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily));
- case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(Doc.UserDoc().fontWeight));
- case StyleProp.FillColor: return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent')));
- case StyleProp.ShowCaption:return props?.hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
- case StyleProp.TitleHeight:return Math.min(4,(props?.DocumentView?.().screenToViewTransform().Scale ?? 1)) * NumCast(Doc.UserDoc().headerHeight,30);
+ case StyleProp.DocContents: return undefined;
+ case StyleProp.WidgetColor: return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
+ case StyleProp.Opacity: return LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
+ case StyleProp.FontColor: return StrCast(doc?.[fieldKey + 'fontColor'], StrCast(Doc.UserDoc().fontColor, color()));
+ case StyleProp.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(Doc.UserDoc().fontSize));
+ case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily));
+ case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(Doc.UserDoc().fontWeight));
+ case StyleProp.FillColor: return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent')));
+ case StyleProp.ShowCaption: return hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
+ case StyleProp.TitleHeight: return Math.min(4,(DocumentView?.().screenToViewTransform().Scale ?? 1)) * NumCast(Doc.UserDoc().headerHeight,30);
case StyleProp.ShowTitle:
return (
(doc &&
- !(props?.DocumentView?.().ComponentView instanceof CollectionSchemaView) &&
- !props?.LayoutTemplateString &&
+ !(DocumentView?.().ComponentView instanceof CollectionSchemaView) &&
+ !LayoutTemplateString &&
!doc.presentation_targetDoc &&
- !props?.LayoutTemplateString?.includes(KeyValueBox.name) &&
- props?.layout_showTitle?.() !== '' &&
+ !LayoutTemplateString?.includes(KeyValueBox.name) &&
+ layout_showTitle?.() !== '' &&
StrCast(
doc._layout_showTitle,
- props?.layout_showTitle?.() ||
+ layout_showTitle?.() ||
(!Doc.IsSystem(doc) && [DocumentType.COL, DocumentType.FUNCPLOT, DocumentType.LABEL, DocumentType.RTF, DocumentType.IMG, DocumentType.VID].includes(doc.type as any)
? doc.author === ClientUtils.CurrentUserEmail()
? StrCast(Doc.UserDoc().layout_showTitle)
@@ -195,15 +219,15 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
}
case StyleProp.BorderPath: {
const borderPath = Doc.IsComicStyle(doc) &&
- props?.renderDepth &&
- !doc?.layout_isSvg && { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, 0.08), width: 3 };
+ renderDepth &&
+ !doc?.layout_isSvg && { path: wavyBorderPath(PanelWidth?.() || 0, PanelHeight?.() || 0), fill: wavyBorderPath(PanelWidth?.() || 0, PanelHeight?.() || 0, 0.08), width: 3 };
return !borderPath
? null
: {
clipPath: `path('${borderPath.path}')`,
jsx: (
<div key="border2" className="documentView-customBorder" style={{ pointerEvents: 'none' }}>
- <svg style={{ overflow: 'visible', height: '100%' }} viewBox={`0 0 ${props.PanelWidth()} ${props.PanelHeight()}`}>
+ <svg style={{ overflow: 'visible', height: '100%' }} viewBox={`0 0 ${PanelWidth?.()} ${PanelHeight?.()}`}>
<path d={borderPath.path} style={{ stroke: 'black', fill: 'transparent', strokeWidth: borderPath.width }} />
</svg>
</div>
@@ -251,13 +275,13 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
? undefined
: doc?._type_collection === CollectionViewType.Stacking ?
(Colors.DARK_GRAY)
- : Cast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (Colors.MEDIUM_GRAY));
+ : Cast((renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (Colors.MEDIUM_GRAY));
break;
// if (doc._type_collection !== CollectionViewType.Freeform && doc._type_collection !== CollectionViewType.Time) return "rgb(62,62,62)";
default: docColor = docColor || (Colors.WHITE);
}
- if (isNonTransparent && isNonTransparentLevel < 9 && (!docColor || docColor === 'transparent') && doc?.embedContainer && props?.styleProvider) {
- return props.styleProvider(DocCast(doc.embedContainer), props, StyleProp.BackgroundColor+":nonTransparent"+(isNonTransparentLevel+1));
+ if (isNonTransparent && isNonTransparentLevel < 9 && (!docColor || docColor === 'transparent') && doc?.embedContainer && styleProvider) {
+ return styleProvider(DocCast(doc.embedContainer), props, StyleProp.BackgroundColor+":nonTransparent"+(isNonTransparentLevel+1));
}
return (docColor && !doc) ? DashColor(docColor).fade(0.5).toString() : docColor;
}
@@ -271,7 +295,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
doc?.layout_boxShadow,
doc?._type_collection === CollectionViewType.Pile
? '4px 4px 10px 2px'
- : lockedPosition() || doc?.isGroup || props?.LayoutTemplateString
+ : lockedPosition() || doc?.isGroup || LayoutTemplateString
? undefined // groups have no drop shadow -- they're supposed to be "invisible". LayoutString's imply collection is being rendered as something else (e.g., title of a Slide)
: `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}`
);
@@ -282,10 +306,10 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
default:
return doc.z
? `#9c9396 ${StrCast(doc?.layout_boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow
- : props?.containerViewPath?.().lastElement()?.Document._freeform_useClusters
- ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (props?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
+ : containerViewPath?.().lastElement()?.Document._freeform_useClusters
+ ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
: NumCast(doc.group, -1) !== -1
- ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (props?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
+ ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
: lockedPosition()
? undefined // if it's a background & has a cluster color, make the shadow spread really big
: fieldKey.includes('_inline') // if doc is an inline document in a text box
@@ -296,14 +320,14 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
}
}
case StyleProp.PointerEvents:
- if (StrCast(doc?.pointerEvents) && !props?.LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc
- if (props?.LayoutTemplateString?.includes(KeyValueBox.name)) return 'all';
+ if (StrCast(doc?.pointerEvents) && !LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc
+ if (LayoutTemplateString?.includes(KeyValueBox.name)) return 'all';
if (SnappingManager.ExploreMode || doc?.layout_unrendered) return isInk() ? 'visiblePainted' : 'all';
- if (props?.pointerEvents?.() === 'none') return 'none';
+ if (pointerEvents?.() === 'none') return 'none';
if (opacity() === 0) return 'none';
- if (props?.isGroupActive?.() ) return isInk() ? 'visiblePainted': (doc?.
+ if (isGroupActive?.() ) return isInk() ? 'visiblePainted': (doc?.
isGroup )? undefined: 'all'
- if (props?.isDocumentActive?.()) return isInk() ? 'visiblePainted' : 'all';
+ if (isDocumentActive?.()) return isInk() ? 'visiblePainted' : 'all';
return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
case StyleProp.Decorations: {
const lock = () => doc?.pointerEvents !== 'none' ? null : (
@@ -312,7 +336,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
</div>
);
const paint = () => !doc?.onPaint ? null : (
- <div className={`styleProvider-paint${props?.DocumentView?.().IsSelected ? "-selected":""}`} onClick={e => togglePaintView(e, doc, props)}>
+ <div className={`styleProvider-paint${DocumentView?.().IsSelected ? "-selected":""}`} onClick={e => togglePaintView(e, doc, props)}>
<FontAwesomeIcon icon='pen' size="lg" />
</div>
);
@@ -321,7 +345,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
const showFilterIcon =
StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length
? 'green' // #18c718bd' //'hasFilter'
- : props?.childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || props?.childFiltersByRanges().length
+ : childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || childFiltersByRanges?.().length
? 'orange' // 'inheritsFilter'
: undefined;
return !showFilterIcon ? null : (
@@ -353,7 +377,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
"this view inherits filters from one of its parents"}
color={SettingsManager.userColor}
background={showFilterIcon}
- items={[ ...(dashView ? [dashView]: []), ...(props?.docViewPath?.()??[])]
+ items={[ ...(dashView ? [dashView]: []), ...(docViewPath?.()??[])]
.filter(dv => StrListCast(dv?.Document.childFilters).length || StrListCast(dv?.Document.childRangeFilters).length)
.map(dv => ({
text: StrCast(dv?.Document.title),
@@ -365,9 +389,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
);
};
const audio = () => {
- const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, AudioAnnoState.stopped);
- const audioAnnosCount = (doc: Doc) => StrListCast(doc[fieldKey + 'audioAnnotations']).length;
- if (!doc || props?.renderDepth === -1 || !audioAnnosCount(doc)) return null;
+ const audioAnnoState = (audioDoc: Doc) => StrCast(audioDoc.audioAnnoState, AudioAnnoState.stopped);
+ const audioAnnosCount = (audioDoc: Doc) => StrListCast(audioDoc[fieldKey + 'audioAnnotations']).length;
+ if (!doc || renderDepth === -1 || !audioAnnosCount(doc)) return null;
const audioIconColors: { [key: string]: string } = { playing: 'green', stopped: 'blue' };
return (
<Tooltip title={<div>{StrListCast(doc[fieldKey + 'audioAnnotations_text']).lastElement()}</div>}>
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 5a509128d..6dba9e155 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1,3 +1,9 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable jsx-a11y/control-has-associated-label */
+/* eslint-disable react/no-unused-class-component-methods */
+/* eslint-disable react/sort-comp */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { Toggle, ToggleType, Type } from 'browndash-components';
@@ -312,8 +318,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
}
private get _buttonizableCommands() {
switch (this.props.type) {
- default:
- return this._doc_commands;
case CollectionViewType.Freeform:
return this._freeform_commands;
case CollectionViewType.Tree:
@@ -332,6 +336,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
return this._freeform_commands;
case CollectionViewType.Carousel3D:
return this._freeform_commands;
+ default:
+ return this._doc_commands;
}
}
private _commandRef = React.createRef<HTMLInputElement>();
@@ -345,11 +351,13 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
@undoBatch
viewChanged = (e: React.ChangeEvent) => {
const target = this.document !== Doc.MyLeftSidebarPanel ? this.document : DocCast(this.document.proto);
- target._type_collection = e.target.selectedOptions[0].value;
+ target._type_collection = (e.target as any).selectedOptions[0].value;
};
commandChanged = (e: React.ChangeEvent) => {
- runInAction(() => (this._currentKey = e.target.selectedOptions[0].value));
+ runInAction(() => {
+ this._currentKey = (e.target as any).selectedOptions[0].value;
+ });
};
@action closeViewSpecs = () => {
@@ -367,7 +375,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
@undoBatch
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
- const docDragData = de.complete.docDragData;
+ const { docDragData } = de.complete;
if (docDragData?.draggedDocuments.length) {
this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => c.immediate(docDragData.draggedDocuments || []));
e.stopPropagation();
@@ -420,11 +428,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
<div className="collectionViewBaseChrome-template" ref={this.createDropTarget}>
<Tooltip title={<div className="dash-tooltip">drop document to apply or drag to create button</div>} placement="bottom">
<div className="commandEntry-outerDiv" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
- <button className={'antimodeMenu-button'}>
+ <button type="button" className="antimodeMenu-button">
<FontAwesomeIcon icon="bullseye" size="lg" />
</button>
<select className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={'empty'} value={''} />
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key="empty" value="" />
{this._buttonizableCommands?.map(cmd => (
<option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>
{cmd.title}
@@ -471,23 +479,19 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
if (docs instanceof Doc) {
const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_'));
return keys.filter(key => key.toLowerCase().indexOf(val) > -1);
- } else {
- const keys = new Set<string>();
- docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
- const noviceKeys = Array.from(keys).filter(
- key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_')
- );
- return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
}
+ const keys = new Set<string>();
+ docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
+ const noviceKeys = Array.from(keys).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_'));
+ return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
}
if (docs instanceof Doc) {
return Object.keys(docs).filter(key => key.toLowerCase().indexOf(val) > -1);
- } else {
- const keys = new Set<string>();
- docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
- return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1);
}
+ const keys = new Set<string>();
+ docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
+ return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1);
};
@action
@@ -497,9 +501,7 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
getSuggestionValue = (suggestion: string) => suggestion;
- renderSuggestion = (suggestion: string) => {
- return <p>{suggestion}</p>;
- };
+ renderSuggestion = (suggestion: string) => <p>{suggestion}</p>;
onSuggestionFetch = async ({ value }: { value: string }) => {
const sugg = await this.getKeySuggestions(value);
@@ -587,12 +589,16 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
}
componentDidMount() {
- runInAction(() => (this.resize = this.props.docView.props.PanelWidth() < 700));
+ runInAction(() => {
+ this.resize = this.props.docView.props.PanelWidth() < 700;
+ });
// listener to reduce text on chrome resize (panel resize)
this.resizeListenerDisposer = reaction(
() => this.panelWidth,
- newValue => (this.resize = newValue < 700)
+ newValue => {
+ this.resize = newValue < 700;
+ }
);
}
@@ -608,7 +614,10 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
* Sets the value of `numCols` on the grid's Document to the value entered.
*/
onNumColsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- if (e.currentTarget.valueAsNumber > 0) undoBatch(() => (this.document.gridNumCols = e.currentTarget.valueAsNumber))();
+ if (e.currentTarget.valueAsNumber > 0)
+ undoBatch(() => {
+ this.document.gridNumCols = e.currentTarget.valueAsNumber;
+ })();
};
/**
@@ -637,7 +646,9 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
onIncrementButtonClick = () => {
this.clicked = true;
this.entered && (this.document.gridNumCols as number)--;
- undoBatch(() => (this.document.gridNumCols = this.numCols + 1))();
+ undoBatch(() => {
+ this.document.gridNumCols = this.numCols + 1;
+ })();
this.entered = false;
};
@@ -648,7 +659,9 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
this.clicked = true;
if (this.numCols > 1 && !this.decrementLimitReached) {
this.entered && (this.document.gridNumCols as number)++;
- undoBatch(() => (this.document.gridNumCols = this.numCols - 1))();
+ undoBatch(() => {
+ this.document.gridNumCols = this.numCols - 1;
+ })();
if (this.numCols === 1) this.decrementLimitReached = true;
}
this.entered = false;
@@ -741,7 +754,13 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
<label className="flexLabel">{this.resize ? 'Flex' : 'Flexible'}</label>
</span>
- <button onClick={() => (this.document.gridResetLayout = true)}>{!this.resize ? 'Reset' : <FontAwesomeIcon icon="redo-alt" size="1x" />}</button>
+ <button
+ type="button"
+ onClick={() => {
+ this.document.gridResetLayout = true;
+ }}>
+ {!this.resize ? 'Reset' : <FontAwesomeIcon icon="redo-alt" size="1x" />}
+ </button>
</div>
);
}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 46bf56dc8..ee79812a1 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable no-restricted-syntax */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Popup, PopupTrigger, Type } from 'browndash-components';
import { ObservableMap, action, computed, makeObservable, observable, observe } from 'mobx';
@@ -31,6 +32,7 @@ import { CollectionSubView } from '../CollectionSubView';
import './CollectionSchemaView.scss';
import { SchemaColumnHeader } from './SchemaColumnHeader';
import { SchemaRowBox } from './SchemaRowBox';
+
const { SCHEMA_NEW_NODE_HEIGHT } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore
export const FInfotoColType: { [key: string]: ColumnType } = {
@@ -90,12 +92,11 @@ export class CollectionSchemaView extends CollectionSubView() {
@computed get _selectedDocs() {
const selected = SelectionManager.Docs.filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.Document));
if (!selected.length) {
- for (const sel of SelectionManager.Docs) {
- const contextPath = DocumentManager.GetContextPath(sel, true);
- if (contextPath.includes(this.Document)) {
- const parentInd = contextPath.indexOf(this.Document);
- return parentInd < contextPath.length - 1 ? [contextPath[parentInd + 1]] : [];
- }
+ // if no schema doc is directly selected, test if a child of a schema doc is selected (such as in the preview window)
+ const childOfSchemaDoc = SelectionManager.Docs.find(sel => DocumentManager.GetContextPath(sel, true).includes(this.Document));
+ if (childOfSchemaDoc) {
+ const contextPath = DocumentManager.GetContextPath(childOfSchemaDoc, true);
+ return [contextPath[contextPath.indexOf(childOfSchemaDoc) - 1]]; // the schema doc that is "selected" by virtue of one of its children being selected
}
}
return selected;
@@ -158,7 +159,9 @@ export class CollectionSchemaView extends CollectionSubView() {
Object.keys(proto).forEach(action(key => // check if any of its keys are new, and add them
!this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo("-no description-", key === 'author'))))));
break;
- case 'update': //let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list
+ case 'update': // let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list
+ break;
+ default:
}
},
true
@@ -187,7 +190,7 @@ export class CollectionSchemaView extends CollectionSubView() {
if (this._selectedDocs.includes(newDoc)) {
SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
} else {
- this.addDocToSelection(newDoc, e.shiftKey, lastIndex + 1);
+ this.addDocToSelection(newDoc, e.shiftKey);
this._selectedCell && (this._selectedCell[0] = newDoc);
this.scrollToDoc(newDoc, {});
}
@@ -206,7 +209,7 @@ export class CollectionSchemaView extends CollectionSubView() {
const newDoc = this.sortedDocs.docs[firstIndex - 1];
if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
else {
- this.addDocToSelection(newDoc, e.shiftKey, firstIndex - 1);
+ this.addDocToSelection(newDoc, e.shiftKey);
this._selectedCell && (this._selectedCell[0] = newDoc);
this.scrollToDoc(newDoc, {});
}
@@ -235,7 +238,9 @@ export class CollectionSchemaView extends CollectionSubView() {
}
case 'Escape': {
this.deselectCell();
+ break;
}
+ default:
}
}
};
@@ -254,7 +259,7 @@ export class CollectionSchemaView extends CollectionSubView() {
this.addNewKey(newKey, defaultVal);
}
- let currKeys = [...this.columnKeys];
+ const currKeys = [...this.columnKeys];
currKeys[index] = newKey;
this.layoutDoc.schema_columnKeys = new List<string>(currKeys);
};
@@ -271,13 +276,16 @@ export class CollectionSchemaView extends CollectionSubView() {
const newDesiredTableWidth = currWidths.reduce((w, cw) => w + cw, 0);
this.layoutDoc.schema_columnWidths = new List<number>(currWidths.map(w => (w / newDesiredTableWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth)));
- let currKeys = this.columnKeys.slice();
+ const currKeys = this.columnKeys.slice();
currKeys.splice(0, 0, key);
this.layoutDoc.schema_columnKeys = new List<string>(currKeys);
};
@action
- addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[DocData][key] = defaultVal));
+ addNewKey = (key: string, defaultVal: any) =>
+ this.childDocs.forEach(doc => {
+ doc[DocData][key] = defaultVal;
+ });
@undoBatch
removeColumn = (index: number) => {
@@ -287,7 +295,7 @@ export class CollectionSchemaView extends CollectionSubView() {
const newDesiredTableWidth = currWidths.reduce((w, cw) => w + cw, 0);
this.layoutDoc.schema_columnWidths = new List<number>(currWidths.map(w => (w / newDesiredTableWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth)));
- let currKeys = this.columnKeys.slice();
+ const currKeys = this.columnKeys.slice();
currKeys.splice(index, 1);
this.layoutDoc.schema_columnKeys = new List<string>(currKeys);
};
@@ -295,7 +303,7 @@ export class CollectionSchemaView extends CollectionSubView() {
@action
startResize = (e: any, index: number) => {
this._displayColumnWidths = this.storedColumnWidths;
- setupMoveUpEvents(this, e, (e, delta) => this.resizeColumn(e, index), this.finishResize, emptyFunction);
+ setupMoveUpEvents(this, e, moveEv => this.resizeColumn(moveEv, index), this.finishResize, emptyFunction);
};
@action
@@ -334,11 +342,11 @@ export class CollectionSchemaView extends CollectionSubView() {
@undoBatch
moveColumn = (fromIndex: number, toIndex: number) => {
- let currKeys = this.columnKeys.slice();
+ const currKeys = this.columnKeys.slice();
currKeys.splice(toIndex, 0, currKeys.splice(fromIndex, 1)[0]);
this.layoutDoc.schema_columnKeys = new List<string>(currKeys);
- let currWidths = this.storedColumnWidths.slice();
+ const currWidths = this.storedColumnWidths.slice();
currWidths.splice(toIndex, 0, currWidths.splice(fromIndex, 1)[0]);
this.layoutDoc.schema_columnWidths = new List<number>(currWidths);
};
@@ -352,7 +360,7 @@ export class CollectionSchemaView extends CollectionSubView() {
document.removeEventListener('pointermove', this.highlightDropColumn);
document.addEventListener('pointermove', this.highlightDropColumn);
- let stopHighlight = (e: PointerEvent) => {
+ const stopHighlight = () => {
document.removeEventListener('pointermove', this.highlightDropColumn);
document.removeEventListener('pointerup', stopHighlight);
};
@@ -405,7 +413,7 @@ export class CollectionSchemaView extends CollectionSubView() {
};
@action
- addDocToSelection = (doc: Doc, extendSelection: boolean, index: number) => {
+ addDocToSelection = (doc: Doc, extendSelection: boolean) => {
const rowDocView = DocumentManager.Instance.getDocumentView(doc);
if (rowDocView) SelectionManager.SelectView(rowDocView, extendSelection);
};
@@ -420,19 +428,25 @@ export class CollectionSchemaView extends CollectionSubView() {
const endRow = Math.max(lastSelectedRow, index);
for (let i = startRow; i <= endRow; i++) {
const currDoc = this.sortedDocs.docs[i];
- if (!this._selectedDocs.includes(currDoc)) this.addDocToSelection(currDoc, true, i);
+ if (!this._selectedDocs.includes(currDoc)) this.addDocToSelection(currDoc, true);
}
};
@action
- selectCell = (doc: Doc, index: number) => (this._selectedCell = [doc, index]);
+ selectCell = (doc: Doc, index: number) => {
+ this._selectedCell = [doc, index];
+ };
@action
- deselectCell = () => (this._selectedCell = undefined);
+ deselectCell = () => {
+ this._selectedCell = undefined;
+ };
sortedSelectedDocs = () => this.sortedDocs.docs.filter(doc => this._selectedDocs.includes(doc));
- setDropIndex = (index: number) => (this._closestDropIndex = index);
+ setDropIndex = (index: number) => {
+ this._closestDropIndex = index;
+ };
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.columnDragData) {
@@ -476,7 +490,7 @@ export class CollectionSchemaView extends CollectionSubView() {
onDividerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, emptyFunction);
@action
- onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
+ onDividerMove = (e: PointerEvent) => {
const nativeWidth = this._previewRef!.getBoundingClientRect();
const minWidth = 40;
const maxWidth = 1000;
@@ -506,37 +520,76 @@ export class CollectionSchemaView extends CollectionSubView() {
const rect = found.getBoundingClientRect();
const localRect = this.ScreenToLocalBoxXf().transformBounds(rect.left, rect.top, rect.width, rect.height);
if (localRect.y < this.rowHeightFunc() || localRect.y + localRect.height > this._props.PanelHeight()) {
- let focusSpeed = options.zoomTime ?? 50;
+ const focusSpeed = options.zoomTime ?? 50;
smoothScroll(focusSpeed, this._tableContentRef!, localRect.y + this._tableContentRef!.scrollTop - this.rowHeightFunc(), options.easeFunc);
return focusSpeed;
}
}
+ return undefined;
};
@computed get fieldDefaultInput() {
switch (this._newFieldType) {
case ColumnType.Number:
- return <input type="number" name="" id="" value={this._newFieldDefault ?? 0} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.value))} />;
+ return (
+ <input
+ type="number"
+ name=""
+ id=""
+ value={this._newFieldDefault ?? 0}
+ onPointerDown={e => e.stopPropagation()}
+ onChange={action(e => {
+ this._newFieldDefault = e.target.value;
+ })}
+ />
+ );
case ColumnType.Boolean:
return (
<>
- <input type="checkbox" name="" id="" value={this._newFieldDefault} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.checked))} />
+ <input
+ type="checkbox"
+ name=""
+ id=""
+ value={this._newFieldDefault}
+ onPointerDown={e => e.stopPropagation()}
+ onChange={action(e => {
+ this._newFieldDefault = e.target.checked;
+ })}
+ />
{this._newFieldDefault ? 'true' : 'false'}
</>
);
case ColumnType.String:
- return <input type="text" name="" id="" value={this._newFieldDefault ?? ''} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.value))} />;
+ return (
+ <input
+ type="text"
+ name=""
+ id=""
+ value={this._newFieldDefault ?? ''}
+ onPointerDown={e => e.stopPropagation()}
+ onChange={action(e => {
+ this._newFieldDefault = e.target.value;
+ })}
+ />
+ );
+ default:
+ return undefined;
}
}
onSearchKeyDown = (e: React.KeyboardEvent) => {
switch (e.key) {
case 'Enter':
- this._menuKeys.length > 0 && this._menuValue.length > 0 ? this.setKey(this._menuKeys[0]) : action(() => (this._makeNewField = true))();
+ this._menuKeys.length > 0 && this._menuValue.length > 0
+ ? this.setKey(this._menuKeys[0])
+ : action(() => {
+ this._makeNewField = true;
+ })();
break;
case 'Escape':
this.closeColumnMenu();
break;
+ default:
}
};
@@ -568,7 +621,9 @@ export class CollectionSchemaView extends CollectionSubView() {
};
@action
- closeColumnMenu = () => (this._columnMenuIndex = undefined);
+ closeColumnMenu = () => {
+ this._columnMenuIndex = undefined;
+ };
@action
openFilterMenu = (index: number) => {
@@ -577,7 +632,9 @@ export class CollectionSchemaView extends CollectionSubView() {
};
@action
- closeFilterMenu = () => (this._filterColumnIndex = undefined);
+ closeFilterMenu = () => {
+ this._filterColumnIndex = undefined;
+ };
openContextMenu = (x: number, y: number, index: number) => {
this.closeColumnMenu();
@@ -607,7 +664,7 @@ export class CollectionSchemaView extends CollectionSubView() {
this._menuKeys = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase()));
};
- getFieldFilters = (field: string) => StrListCast(this.Document._childFilters).filter(filter => filter.split(Doc.FilterSep)[0] == field);
+ getFieldFilters = (field: string) => StrListCast(this.Document._childFilters).filter(filter => filter.split(Doc.FilterSep)[0] === field);
removeFieldFilters = (field: string) => {
this.getFieldFilters(field).forEach(filter => Doc.setDocFilter(this.Document, field, filter.split(Doc.FilterSep)[1], 'remove'));
@@ -619,11 +676,14 @@ export class CollectionSchemaView extends CollectionSubView() {
case 'Escape':
this.closeFilterMenu();
break;
+ default:
}
};
@action
- updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => (this._filterSearchValue = e.target.value);
+ updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._filterSearchValue = e.target.value;
+ };
@computed get newFieldMenu() {
return (
@@ -632,7 +692,7 @@ export class CollectionSchemaView extends CollectionSubView() {
<input
type="radio"
name="newFieldType"
- checked={this._newFieldType == ColumnType.Number}
+ checked={this._newFieldType === ColumnType.Number}
onChange={action(() => {
this._newFieldType = ColumnType.Number;
this._newFieldDefault = 0;
@@ -644,7 +704,7 @@ export class CollectionSchemaView extends CollectionSubView() {
<input
type="radio"
name="newFieldType"
- checked={this._newFieldType == ColumnType.Boolean}
+ checked={this._newFieldType === ColumnType.Boolean}
onChange={action(() => {
this._newFieldType = ColumnType.Boolean;
this._newFieldDefault = false;
@@ -656,7 +716,7 @@ export class CollectionSchemaView extends CollectionSubView() {
<input
type="radio"
name="newFieldType"
- checked={this._newFieldType == ColumnType.String}
+ checked={this._newFieldType === ColumnType.String}
onChange={action(() => {
this._newFieldType = ColumnType.String;
this._newFieldDefault = '';
@@ -668,7 +728,7 @@ export class CollectionSchemaView extends CollectionSubView() {
<div className="schema-key-warning">{this._newFieldWarning}</div>
<div
className="schema-column-menu-button"
- onPointerDown={action(e => {
+ onPointerDown={action(() => {
if (this.documentKeys.includes(this._menuValue)) {
this._newFieldWarning = 'Field already exists';
} else if (this._menuValue.length === 0) {
@@ -733,7 +793,7 @@ export class CollectionSchemaView extends CollectionSubView() {
}
@computed get renderColumnMenu() {
- const x = this._columnMenuIndex! == -1 ? 0 : this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._columnMenuIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth);
+ const x = this._columnMenuIndex! === -1 ? 0 : this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._columnMenuIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth);
return (
<div className="schema-column-menu" style={{ left: x, minWidth: CollectionSchemaView._minColWidth }}>
<input className="schema-key-search-input" type="text" onKeyDown={this.onSearchKeyDown} onChange={this.updateKeySearch} onPointerDown={e => e.stopPropagation()} />
@@ -817,7 +877,7 @@ export class CollectionSchemaView extends CollectionSubView() {
: [...this.childDocs].sort((docA, docB) => {
const aStr = Field.toString(docA[field] as FieldType);
const bStr = Field.toString(docB[field] as FieldType);
- var out = 0;
+ let out = 0;
if (aStr < bStr) out = -1;
if (aStr > bStr) out = 1;
if (desc) out *= -1;
@@ -835,7 +895,7 @@ export class CollectionSchemaView extends CollectionSubView() {
render() {
return (
<div className="collectionSchemaView" ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} onDrop={this.onExternalDrop.bind(this)}>
- <div ref={this._menuTarget} style={{ background: 'red', top: 0, left: 0, position: 'absolute', zIndex: 10000 }}></div>
+ <div ref={this._menuTarget} style={{ background: 'red', top: 0, left: 0, position: 'absolute', zIndex: 10000 }} />
<div
className="schema-table"
style={{ width: `calc(100% - ${this.previewWidth}px)` }}
@@ -851,7 +911,7 @@ export class CollectionSchemaView extends CollectionSubView() {
placement="right"
background={SettingsManager.userBackgroundColor}
color={SettingsManager.userColor}
- toggle={<FontAwesomeIcon onPointerDown={e => this.openColumnMenu(-1, true)} icon="plus" />}
+ toggle={<FontAwesomeIcon onPointerDown={() => this.openColumnMenu(-1, true)} icon="plus" />}
trigger={PopupTrigger.CLICK}
type={Type.TERT}
isOpen={this._columnMenuIndex !== -1 ? false : undefined}
@@ -860,6 +920,7 @@ export class CollectionSchemaView extends CollectionSubView() {
</div>
{this.columnKeys.map((key, index) => (
<SchemaColumnHeader
+ // eslint-disable-next-line react/no-array-index-key
key={index}
columnIndex={index}
columnKeys={this.columnKeys}
@@ -879,28 +940,42 @@ export class CollectionSchemaView extends CollectionSubView() {
</div>
{this._columnMenuIndex !== undefined && this._columnMenuIndex !== -1 && this.renderColumnMenu}
{this._filterColumnIndex !== undefined && this.renderFilterMenu}
- <CollectionSchemaViewDocs schema={this} childDocs={this.sortedDocsFunc} rowHeight={this.rowHeightFunc} setRef={(ref: HTMLDivElement | null) => (this._tableContentRef = ref)} />
+ {
+ // eslint-disable-next-line no-use-before-define
+ <CollectionSchemaViewDocs
+ schema={this}
+ childDocs={this.sortedDocsFunc}
+ rowHeight={this.rowHeightFunc}
+ setRef={(ref: HTMLDivElement | null) => {
+ this._tableContentRef = ref;
+ }}
+ />
+ }
{this.layoutDoc.chromeHidden ? null : (
<div className="schema-add">
<EditableView
GetValue={returnEmptyString}
SetValue={undoable(value => (value ? this.addRow(Docs.Create.TextDocument(value, { title: value, _layout_autoHeight: true })) : false), 'add text doc')}
placeholder={"Type text to create note or ':' to create specific type"}
- contents={'+ New Node'}
+ contents="+ New Node"
menuCallback={this.menuCallback}
height={CollectionSchemaView._newNodeInputHeight}
/>
</div>
)}
</div>
- {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown}></div>}
+ {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown} />}
{this.previewWidth > 0 && (
- <div style={{ width: `${this.previewWidth}px` }} ref={ref => (this._previewRef = ref)}>
+ <div
+ style={{ width: `${this.previewWidth}px` }}
+ ref={ref => {
+ this._previewRef = ref;
+ }}>
{Array.from(this._selectedDocs).lastElement() && (
<DocumentView
Document={Array.from(this._selectedDocs).lastElement()}
fitContentsToBox={returnTrue}
- dontCenter={'y'}
+ dontCenter="y"
onClickScriptDisable="always"
focus={emptyFunction}
defaultDoubleClick={returnIgnore}
@@ -945,7 +1020,10 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
<div className="schema-table-content" ref={this.props.setRef} style={{ height: `calc(100% - ${CollectionSchemaView._newNodeInputHeight + this.props.rowHeight()}px)` }}>
{this.props.childDocs().docs.map((doc: Doc, index: number) => (
<div key={doc[Id]} className="schema-row-wrapper" style={{ height: this.props.rowHeight() }}>
- <CollectionSchemaViewDoc doc={doc} schema={this.props.schema} index={index} rowHeight={this.props.rowHeight} />
+ {
+ // eslint-disable-next-line no-use-before-define
+ <CollectionSchemaViewDoc doc={doc} schema={this.props.schema} index={index} rowHeight={this.props.rowHeight} />
+ }
</div>
))}
</div>
@@ -977,6 +1055,7 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
return (
<DocumentView
key={this._props.doc[Id]}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props.schema._props}
containerViewPath={this._props.schema.childContainerViewPath}
LayoutTemplate={this._props.schema._props.childLayoutTemplate}
@@ -996,14 +1075,14 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
searchFilterDocs={this._props.schema.searchFilterDocs}
rootSelected={this._props.schema.rootSelected}
ScreenToLocalTransform={this.screenToLocalXf}
- dragWhenActive={true}
+ dragWhenActive
isDocumentActive={this._props.schema._props.childDocumentsActive?.() ? this._props.schema._props.isDocumentActive : this._props.schema.isContentActive}
isContentActive={emptyFunction}
whenChildContentsActiveChanged={this._props.schema._props.whenChildContentsActiveChanged}
- hideDecorations={true}
- hideTitle={true}
- hideDocumentButtonBar={true}
- hideLinkAnchors={true}
+ hideDecorations
+ hideTitle
+ hideDocumentButtonBar
+ hideLinkAnchors
layout_fitWidth={returnTrue}
/>
);
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 832e18b68..cc4b5b67f 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -21,7 +21,6 @@ import { CollectionSchemaView } from '../collections/collectionSchema/Collection
import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox';
import { PresElementBox } from './trails/PresElementBox';
import { SearchBox } from '../search/SearchBox';
-import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo';
import { AudioBox } from './AudioBox';
import { ComparisonBox } from './ComparisonBox';
import { DataVizBox } from './DataVizBox/DataVizBox';
@@ -248,7 +247,6 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
PresElementBox,
SearchBox,
FunctionPlotBox,
- DashWebRTCVideo,
LinkAnchorBox,
InkingStroke,
LinkBox,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 3a1a72910..99b4a84fc 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -349,7 +349,7 @@ footnote::before {
touch-action: none;
span {
font-family: inherit;
- background-color: inherit;
+ // background-color: inherit; // intended to allow texts to inherit background from list container, but this prevents css highlights e.,g highlight text from others
display: inline; // needs to be inline for search highlighting to appear
// display: contents; // BUT needs to be 'contents' to avoid Chrome bug where extra space is added above and <ol> lists when inside a prosemirror span
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 99a2f4ab9..ba37c3265 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -65,6 +65,7 @@ import { FootnoteView } from './FootnoteView';
import './FormattedTextBox.scss';
import { findLinkMark, FormattedTextBoxComment } from './FormattedTextBoxComment';
import { buildKeymap, updateBullets } from './ProsemirrorExampleTransfer';
+// eslint-disable-next-line import/extensions
import { removeMarkWithAttrs } from './prosemirrorPatches';
import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
import { RichTextRules } from './RichTextRules';
@@ -291,7 +292,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
});
};
AnchorMenu.Instance.Highlight = undoable((color: string) => {
- this._editorView?.state && RichTextMenu.Instance?.setHighlight(color);
+ this._editorView?.state && RichTextMenu.Instance?.setFontField(color, 'fontHighlight');
return undefined;
}, 'highlght text');
AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
@@ -637,8 +638,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const pos = view.posAtCoords({ left: de.x, top: de.y })?.pos;
pos && view.dispatch(view.state.tr.insert(pos, node));
added = !!pos; // pos will be null if you don't drop onto an actual text location
- } catch (e) {
- console.log('Drop failed', e);
+ } catch (err) {
+ console.log('Drop failed', err);
added = false;
} finally {
this._inDrop = false;
@@ -778,7 +779,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this,
e,
this.sidebarMove,
- (e, movement, isClick) => !isClick && batch.end(),
+ (moveEv, movement, isClick) => !isClick && batch.end(),
() => {
this.toggleSidebar();
batch.end();
@@ -1301,7 +1302,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._disposers.selected = reaction(
() => this._props.rootSelected?.(),
action(selected => {
- // selected && setTimeout(() => this.prepareForTyping());
+ this.prepareForTyping();
if (FormattedTextBox._globalHighlights.has('Bold Text')) {
// eslint-disable-next-line operator-assignment
this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed
@@ -1498,8 +1499,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
- } else if (curText && !FormattedTextBox.DontSelectInitialText) {
- selectAll(this._editorView.state, this._editorView?.dispatch);
+ } else if (!FormattedTextBox.DontSelectInitialText) {
+ const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) });
+ selectAll(this._editorView.state, (tx: Transaction) => {
+ this._editorView?.dispatch(tx.deleteSelection().addStoredMark(mark));
+ });
this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
} else {
const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
@@ -1526,17 +1530,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
prepareForTyping = () => {
- if (!this._editorView) return;
- const docDefaultMarks = [
- ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
- ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []),
- ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []),
- ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { fontFamily: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily) })] : []),
- ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize) })] : []),
- ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []),
- ...[schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) })],
- ];
- this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks));
+ if (this._editorView) {
+ const { text, paragraph } = schema.nodes;
+ const selNode = this._editorView.state.selection.$anchor.node();
+ if (this._editorView.state.selection.from === 1 && this._editorView.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) {
+ const docDefaultMarks = [schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) })];
+ this._editorView.state.selection.empty && this._editorView.state.selection.from === 1 && this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks).removeStoredMark(schema.marks.pFontColor));
+ }
+ }
};
componentWillUnmount() {
@@ -1601,10 +1602,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const state = this.EditorView?.state;
if (state && this.ProseRef?.children[0].className.includes('-focused') && this._props.isContentActive() && !e.button) {
if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
- let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
- for (let target = e.target as any; target && !target.dataset?.targethrefs; target = target.parentElement);
- while (target && !target.dataset?.targethrefs) target = target.parentElement;
- FormattedTextBoxComment.update(this, this.EditorView!, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
+ let clickTarget = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
+ for (let { target } = e as any; target && !target.dataset?.targethrefs; target = target.parentElement);
+ while (clickTarget && !clickTarget.dataset?.targethrefs) clickTarget = clickTarget.parentElement;
+ FormattedTextBoxComment.update(this, this.EditorView!, undefined, clickTarget?.dataset?.targethrefs, clickTarget?.dataset.linkdoc, clickTarget?.dataset.nopreview === 'true');
}
};
@action
@@ -1724,9 +1725,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (!(this.EditorView?.state.selection instanceof NodeSelection)) {
this.autoLink();
if (this._editorView?.state.tr) {
- const tr = stordMarks?.reduce((tr, m) => {
- tr.addStoredMark(m);
- return tr;
+ const tr = stordMarks?.reduce((tr2, m) => {
+ tr2.addStoredMark(m);
+ return tr2;
}, this._editorView.state.tr);
tr && this._editorView.dispatch(tr);
}
@@ -2038,7 +2039,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
_oldWheel: any;
@computed get fontColor() {
- return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontColor);
}
@computed get fontSize() {
return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 073ed91c3..5d448d40e 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -7,7 +7,7 @@ import { liftTarget } from 'prosemirror-transform';
import { EditorView } from 'prosemirror-view';
import { ClientUtils } from '../../../../ClientUtils';
import { Utils } from '../../../../Utils';
-import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols';
import { GetEffectiveAcl } from '../../../../fields/util';
import { Docs } from '../../../documents/Documents';
import { RTFMarkup } from '../../../util/RTFMarkup';
@@ -48,11 +48,12 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
}
const canEdit = (state: any) => {
- switch (GetEffectiveAcl(props.TemplateDataDocument)) {
+ const permissions = GetEffectiveAcl(props.TemplateDataDocument ?? props.Document[DocData]);
+ switch (permissions) {
case AclAugment:
{
const prevNode = state.selection.$cursor.nodeBefore;
- const prevUser = !prevNode ? ClientUtils.CurrentUserEmail() : prevNode.marks[prevNode.marks.length - 1].attrs.userid;
+ const prevUser = !prevNode ? ClientUtils.CurrentUserEmail() : prevNode.marks.lastElement()?.attrs.userid;
if (prevUser !== ClientUtils.CurrentUserEmail()) {
return false;
}
@@ -278,7 +279,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
dispatch(updateBullets(tx, schema));
if (view.state.selection.$anchor.node(-1)?.type === schema.nodes.list_item) {
// gets rid of an extra paragraph when joining two list items together.
- joinBackward(view.state, (tx: Transaction) => view.dispatch(tx));
+ joinBackward(view.state, (tx2: Transaction) => view.dispatch(tx2));
}
})
) {
@@ -344,7 +345,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
!splitBlockKeepMarks(state, (tx3: Transaction) => {
const tonode = tx3.selection.$to.node();
if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) {
- const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks);
+ const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks).setStoredMarks(marks || []);
dispatch(tx4);
}
@@ -365,7 +366,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
// Command to create a blank space
bind('Space', () => {
- if (props.TemplateDataDocument && ![AclAdmin, AclAugment, AclEdit].includes(GetEffectiveAcl(props.TemplateDataDocument))) return true;
+ const editDoc = props.TemplateDataDocument ?? props.Document[DocData];
+ if (editDoc && ![AclAdmin, AclAugment, AclEdit].includes(GetEffectiveAcl(editDoc))) return true;
return false;
});
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 6108383c2..6c12b9991 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -10,7 +10,6 @@ import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import { Doc } from '../../../../fields/Doc';
import { BoolCast, Cast, StrCast } from '../../../../fields/Types';
-import { numberRange } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { LinkManager } from '../../../util/LinkManager';
import { SelectionManager } from '../../../util/SelectionManager';
@@ -147,12 +146,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const { activeHighlights } = active;
const refDoc = SelectionManager.Views.lastElement()?.layoutDoc ?? Doc.UserDoc();
const refField = (pfx => (pfx ? pfx + '_' : ''))(SelectionManager.Views.lastElement()?.LayoutFieldKey);
+ const refVal = (field: string, dflt: string) => StrCast(refDoc[refField + field], StrCast(Doc.UserDoc()[field], dflt));
this._activeListType = this.getActiveListStyle();
this._activeAlignment = this.getActiveAlignment();
- this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(refDoc[refField + 'fontFamily'], 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
- this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(refDoc[refField + 'fontSize'], '10px')) : activeSizes[0];
- this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(refDoc[refField + 'fontColor'], 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
+ this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, refVal('fontFamily', 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
+ this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, refVal('fontSize', '10px')) : activeSizes[0];
+ this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, refVal('fontColor', 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
this._activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';
// update link in current selection
@@ -161,12 +161,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
setMark = (mark: Mark, state: EditorState, dispatch: any, dontToggle: boolean = false) => {
if (mark) {
- const liFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.list_item);
- const liTo = numberRange(state.selection.$to.depth + 1).find(i => state.selection.$to.node(i)?.type === state.schema.nodes.list_item);
- const olFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.ordered_list);
- const nodeOl = (liFirst && liTo && state.selection.$from.node(liFirst) !== state.selection.$to.node(liTo) && olFirst) || (!liFirst && !liTo && olFirst);
- const fromRange = numberRange(state.selection.from).reverse();
- const newPos = nodeOl ? fromRange.find(i => state.doc.nodeAt(i)?.type === state.schema.nodes.ordered_list) ?? state.selection.from : state.selection.from;
+ const newPos = state.selection.$anchor.node()?.type === schema.nodes.ordered_list ? state.selection.from : state.selection.from;
const node = (state.selection as NodeSelection).node ?? (newPos >= 0 ? state.doc.nodeAt(newPos) : undefined);
if (node?.type === schema.nodes.ordered_list || node?.type === schema.nodes.list_item) {
const hasMark = node.marks.some(m => m.type === mark.type);
@@ -174,18 +169,16 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const addAnyway = node.marks.filter(m => m.type === mark.type && Object.keys(m.attrs).some(akey => m.attrs[akey] !== mark.attrs[akey]));
const markup = state.tr.setNodeMarkup(newPos, node.type, node.attrs, hasMark && !addAnyway ? otherMarks : [...otherMarks, mark]);
dispatch(updateBullets(markup, state.schema));
- } else {
- const state = this.view?.state;
- if (state) {
- const { tr } = state;
- if (dontToggle) {
- tr.addMark(state.selection.from, state.selection.to, mark);
- dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise
- } else {
- toggleMark(mark.type, mark.attrs)(state, dispatch);
- }
+ } else if (state) {
+ const { tr } = state;
+ if (dontToggle) {
+ tr.addMark(state.selection.from, state.selection.to, mark);
+ dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise
+ } else {
+ toggleMark(mark.type, mark.attrs)(state, dispatch);
}
}
+ this.updateMenu(this.view, undefined, undefined, this.layoutDoc);
}
};
@@ -242,7 +235,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
m.type === state.schema.marks.pFontFamily && activeFamilies.add(m.attrs.fontFamily);
m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.fontColor);
m.type === state.schema.marks.pFontSize && activeSizes.add(m.attrs.fontSize);
- m.type === state.schema.marks.pFontHighlight && activeHighlights.add(String(m.attrs.fontHigh));
+ m.type === state.schema.marks.pFontHighlight && activeHighlights.add(String(m.attrs.fontHighlight));
});
} else if (SelectionManager.Views.some(dv => dv.ComponentView instanceof EquationBox)) {
SelectionManager.Views.forEach(dv => StrCast(dv.Document._text_fontSize) && activeSizes.add(StrCast(dv.Document._text_fontSize)));
@@ -359,18 +352,21 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
setFontField = (value: string, fontField: 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => {
if (this.view) {
- if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === value))) {
+ const { text, paragraph } = this.view.state.schema.nodes;
+ const selNode = this.view.state.selection.$anchor.node();
+ if (this.view.state.selection.from === 1 && this.view.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) {
this.TextView.dataDoc[this.TextView.fieldKey + `_${fontField}`] = value;
this.view.focus();
- } else {
- const attrs: { [key: string]: string } = {};
- attrs[fontField] = value;
- const fmark = this.view?.state.schema.marks['pF' + fontField.substring(1)].create(attrs);
- this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
- this.view.focus();
}
- } else Doc.UserDoc()[fontField] = value;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+ const attrs: { [key: string]: string } = {};
+ attrs[fontField] = value;
+ const fmark = this.view?.state.schema.marks['pF' + fontField.substring(1)].create(attrs);
+ this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
+ this.view.focus();
+ } else {
+ Doc.UserDoc()[fontField] = value;
+ this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+ }
};
// TODO: remove doesn't work
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 184487b7d..5bf942218 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -3,6 +3,7 @@ import { listItem, orderedList } from 'prosemirror-schema-list';
import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec';
import { DocServer } from '../../../DocServer';
import { Doc, Field, FieldType } from '../../../../fields/Doc';
+import { schema } from './schema_rts';
const blockquoteDOM: DOMOutputSpec = ['blockquote', 0];
const hrDOM: DOMOutputSpec = ['hr'];
@@ -353,7 +354,7 @@ export const nodes: { [index: string]: NodeSpec } = {
},
{
style: 'list-style-type=disc',
- getAttrs(dom: any) {
+ getAttrs() {
return { mapStyle: 'bullet' };
},
},
@@ -373,10 +374,10 @@ export const nodes: { [index: string]: NodeSpec } = {
],
toDOM(node: Node) {
const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
- const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight);
- const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize);
- const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family);
- const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color);
+ const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontHighlight)?.attrs.fontHighlight);
+ const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontSize)?.attrs.fontSize);
+ const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontFamily)?.attrs.fontFamily);
+ const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontColor)?.attrs.fontColor);
const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : '';
if (node.attrs.mapStyle === 'bullet') {
return [
@@ -421,10 +422,10 @@ export const nodes: { [index: string]: NodeSpec } = {
},
],
toDOM(node: Node) {
- const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight);
- const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize);
- const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family);
- const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color);
+ const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontHighlight)?.attrs.fontHighlight);
+ const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontSize)?.attrs.fontSize);
+ const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontFamily)?.attrs.fontFamily);
+ const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontColor)?.attrs.fontColor);
const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
return [
'li',
diff --git a/src/client/views/webcam/DashWebRTCVideo.scss b/src/client/views/webcam/DashWebRTCVideo.scss
deleted file mode 100644
index 5744ebbcd..000000000
--- a/src/client/views/webcam/DashWebRTCVideo.scss
+++ /dev/null
@@ -1,82 +0,0 @@
-@import '../global/globalCssVariables.module.scss';
-
-.webcam-cont {
- background: whitesmoke;
- color: grey;
- border-radius: 15px;
- box-shadow: #9c9396 0.2vw 0.2vw 0.4vw;
- border: solid #bbbbbbbb 5px;
- pointer-events: all;
- display: flex;
- flex-direction: column;
- overflow: hidden;
-
- .webcam-header {
- height: 50px;
- text-align: center;
- text-transform: uppercase;
- letter-spacing: 2px;
- font-size: 16px;
- width: 100%;
- margin-top: 20px;
- }
-
- .videoContainer {
- position: relative;
- width: calc(100% - 20px);
- height: 100%;
- /* border: 10px solid red; */
- margin-left: 10px;
- }
-
- .buttonContainer {
- display: flex;
- width: calc(100% - 20px);
- height: 50px;
- justify-content: center;
- text-align: center;
- /* border: 1px solid black; */
- margin-left: 10px;
- margin-top: 0;
- margin-bottom: 15px;
- }
-
- #roomName {
- outline: none;
- border-radius: inherit;
- border: 1px solid #bbbbbbbb;
- margin: 10px;
- padding: 10px;
- }
-
- .side {
- width: 25%;
- height: 20%;
- position: absolute;
- /* top: 65%; */
- z-index: 2;
- right: 0px;
- bottom: 18px;
- }
-
- .main {
- position: absolute;
- width: 100%;
- height: 100%;
- /* top: 20%; */
- align-self: center;
- }
-
- .videoButtons {
- border-radius: 50%;
- height: 30px;
- width: 30px;
- display: flex;
- justify-content: center;
- align-items: center;
- justify-self: center;
- align-self: center;
- margin: 5px;
- border: 1px solid black;
- }
-}
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
deleted file mode 100644
index 4e984f3d6..000000000
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { IconLookup } from '@fortawesome/fontawesome-svg-core';
-import { faPhoneSlash, faSync } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Doc } from '../../../fields/Doc';
-import { InkTool } from '../../../fields/InkField';
-import { SnappingManager } from '../../util/SnappingManager';
-import '../../views/nodes/WebBox.scss';
-import { FieldView, FieldViewProps } from '../nodes/FieldView';
-import './DashWebRTCVideo.scss';
-import { hangup, initialize, refreshVideos } from './WebCamLogic';
-
-/**
- * This models the component that will be rendered, that can be used as a doc that will reflect the video cams.
- */
-@observer
-export class DashWebRTCVideo extends React.Component<FieldViewProps> {
- private roomText: HTMLInputElement | undefined;
- @observable remoteVideoAdded: boolean = false;
-
- @action
- changeUILook = () => (this.remoteVideoAdded = true);
-
- /**
- * Function that submits the title entered by user on enter press.
- */
- private onEnterKeyDown = (e: React.KeyboardEvent) => {
- if (e.keyCode === 13) {
- const submittedTitle = this.roomText!.value;
- this.roomText!.value = '';
- this.roomText!.blur();
- initialize(submittedTitle, this.changeUILook);
- }
- };
-
- public static LayoutString(fieldKey: string) {
- return FieldView.LayoutString(DashWebRTCVideo, fieldKey);
- }
-
- onClickRefresh = () => refreshVideos();
-
- onClickHangUp = () => hangup();
-
- render() {
- const content = (
- <div className="webcam-cont" style={{ width: '100%', height: '100%' }}>
- <div className="webcam-header">DashWebRTC</div>
- <input id="roomName" type="text" placeholder="Enter room name" ref={e => (this.roomText = e!)} onKeyDown={this.onEnterKeyDown} />
- <div className="videoContainer">
- <video id="localVideo" className={'RTCVideo' + (this.remoteVideoAdded ? ' side' : ' main')} autoPlay playsInline muted ref={e => {}}></video>
- <video id="remoteVideo" className="RTCVideo main" autoPlay playsInline ref={e => {}}></video>
- </div>
- <div className="buttonContainer">
- <div className="videoButtons" style={{ background: 'red' }} onClick={this.onClickHangUp}>
- <FontAwesomeIcon icon={faPhoneSlash as IconLookup} color="white" />
- </div>
- <div className="videoButtons" style={{ background: 'green' }} onClick={this.onClickRefresh}>
- <FontAwesomeIcon icon={faSync as IconLookup} color="white" />
- </div>
- </div>
- </div>
- );
-
- const frozen = !this.props.isSelected() || SnappingManager.IsResizing;
- const classname = 'webBox-cont' + (this.props.isSelected() && Doc.ActiveTool === InkTool.None && !SnappingManager.IsResizing ? '-interactive' : '');
-
- return (
- <>
- <div className={classname}>{content}</div>
- {!frozen ? null : <div className="webBox-overlay" />}
- </>
- );
- }
-}
diff --git a/src/client/views/webcam/WebCamLogic.js b/src/client/views/webcam/WebCamLogic.js
deleted file mode 100644
index 5f6202bc8..000000000
--- a/src/client/views/webcam/WebCamLogic.js
+++ /dev/null
@@ -1,292 +0,0 @@
-'use strict';
-import io from "socket.io-client";
-
-var socket;
-var isChannelReady = false;
-var isInitiator = false;
-var isStarted = false;
-var localStream;
-var pc;
-var remoteStream;
-var turnReady;
-var room;
-
-export function initialize(roomName, handlerUI) {
-
- var pcConfig = {
- 'iceServers': [{
- 'urls': 'stun:stun.l.google.com:19302'
- }]
- };
-
- // Set up audio and video regardless of what devices are present.
- var sdpConstraints = {
- offerToReceiveAudio: true,
- offerToReceiveVideo: true
- };
-
- /////////////////////////////////////////////
-
- room = roomName;
-
- socket = io.connect(`${window.location.protocol}//${window.location.hostname}:4321`);
-
- if (room !== '') {
- socket.emit('create or join', room);
- console.log('Attempted to create or join room', room);
- }
-
- socket.on('created', function (room) {
- console.log('Created room ' + room);
- isInitiator = true;
- });
-
- socket.on('full', function (room) {
- console.log('Room ' + room + ' is full');
- });
-
- socket.on('join', function (room) {
- console.log('Another peer made a request to join room ' + room);
- console.log('This peer is the initiator of room ' + room + '!');
- isChannelReady = true;
- });
-
- socket.on('joined', function (room) {
- console.log('joined: ' + room);
- isChannelReady = true;
- });
-
- socket.on('log', function (array) {
- console.log.apply(console, array);
- });
-
- ////////////////////////////////////////////////
-
-
- // This client receives a message
- socket.on('message', function (message) {
- console.log('Client received message:', message);
- if (message === 'got user media') {
- maybeStart();
- } else if (message.type === 'offer') {
- if (!isInitiator && !isStarted) {
- maybeStart();
- }
- pc.setRemoteDescription(new RTCSessionDescription(message));
- doAnswer();
- } else if (message.type === 'answer' && isStarted) {
- pc.setRemoteDescription(new RTCSessionDescription(message));
- } else if (message.type === 'candidate' && isStarted) {
- var candidate = new RTCIceCandidate({
- sdpMLineIndex: message.label,
- candidate: message.candidate
- });
- pc.addIceCandidate(candidate);
- } else if (message === 'bye' && isStarted) {
- handleRemoteHangup();
- }
- });
-
- ////////////////////////////////////////////////////
-
- var localVideo = document.querySelector('#localVideo');
- var remoteVideo = document.querySelector('#remoteVideo');
-
- const gotStream = (stream) => {
- console.log('Adding local stream.');
- localStream = stream;
- localVideo.srcObject = stream;
- sendMessage('got user media');
- if (isInitiator) {
- maybeStart();
- }
- }
-
-
- navigator.mediaDevices.getUserMedia({
- audio: true,
- video: true
- })
- .then(gotStream)
- .catch(function (e) {
- alert('getUserMedia() error: ' + e.name);
- });
-
-
-
- var constraints = {
- video: true
- };
-
- console.log('Getting user media with constraints', constraints);
-
- const requestTurn = (turnURL) => {
- var turnExists = false;
- for (var i in pcConfig.iceServers) {
- if (pcConfig.iceServers[i].urls.substr(0, 5) === 'turn:') {
- turnExists = true;
- turnReady = true;
- break;
- }
- }
- if (!turnExists) {
- console.log('Getting TURN server from ', turnURL);
- // No TURN server. Get one from computeengineondemand.appspot.com:
- var xhr = new XMLHttpRequest();
- xhr.onreadystatechange = function () {
- if (xhr.readyState === 4 && xhr.status === 200) {
- var turnServer = JSON.parse(xhr.responseText);
- console.log('Got TURN server: ', turnServer);
- pcConfig.iceServers.push({
- 'urls': 'turn:' + turnServer.username + '@' + turnServer.turn,
- 'credential': turnServer.password
- });
- turnReady = true;
- }
- };
- xhr.open('GET', turnURL, true);
- xhr.send();
- }
- }
-
-
-
-
- if (location.hostname !== 'localhost') {
- requestTurn(
- `${window.location.origin}/corsProxy/${encodeURIComponent("https://computeengineondemand.appspot.com/turn?username=41784574&key=4080218913")}`
- );
- }
-
- const maybeStart = () => {
- console.log('>>>>>>> maybeStart() ', isStarted, localStream, isChannelReady);
- if (!isStarted && typeof localStream !== 'undefined' && isChannelReady) {
- console.log('>>>>>> creating peer connection');
- createPeerConnection();
- pc.addStream(localStream);
- isStarted = true;
- console.log('isInitiator', isInitiator);
- if (isInitiator) {
- doCall();
- }
- }
- };
-
- window.onbeforeunload = function () {
- sendMessage('bye');
- };
-
- /////////////////////////////////////////////////////////
-
- const createPeerConnection = () => {
- try {
- pc = new RTCPeerConnection(null);
- pc.onicecandidate = handleIceCandidate;
- pc.onaddstream = handleRemoteStreamAdded;
- pc.onremovestream = handleRemoteStreamRemoved;
- console.log('Created RTCPeerConnnection');
- } catch (e) {
- console.log('Failed to create PeerConnection, exception: ' + e.message);
- alert('Cannot create RTCPeerConnection object.');
- return;
- }
- }
-
- const handleIceCandidate = (event) => {
- console.log('icecandidate event: ', event);
- if (event.candidate) {
- sendMessage({
- type: 'candidate',
- label: event.candidate.sdpMLineIndex,
- id: event.candidate.sdpMid,
- candidate: event.candidate.candidate
- });
- } else {
- console.log('End of candidates.');
- }
- }
-
- const handleCreateOfferError = (event) => {
- console.log('createOffer() error: ', event);
- }
-
- const doCall = () => {
- console.log('Sending offer to peer');
- pc.createOffer(setLocalAndSendMessage, handleCreateOfferError);
- }
-
- const doAnswer = () => {
- console.log('Sending answer to peer.');
- pc.createAnswer().then(
- setLocalAndSendMessage,
- onCreateSessionDescriptionError
- );
- }
-
- const setLocalAndSendMessage = (sessionDescription) => {
- pc.setLocalDescription(sessionDescription);
- console.log('setLocalAndSendMessage sending message', sessionDescription);
- sendMessage(sessionDescription);
- }
-
- const onCreateSessionDescriptionError = (error) => {
- trace('Failed to create session description: ' + error.toString());
- }
-
-
-
- const handleRemoteStreamAdded = (event) => {
- console.log('Remote stream added.');
- remoteStream = event.stream;
- remoteVideo.srcObject = remoteStream;
- handlerUI();
-
- };
-
- const handleRemoteStreamRemoved = (event) => {
- console.log('Remote stream removed. Event: ', event);
- }
-}
-
-export function hangup() {
- console.log('Hanging up.');
- stop();
- sendMessage('bye');
- if (localStream) {
- localStream.getTracks().forEach(track => track.stop());
- }
-}
-
-function stop() {
- isStarted = false;
- if (pc) {
- pc.close();
- }
- pc = null;
-}
-
-function handleRemoteHangup() {
- console.log('Session terminated.');
- stop();
- isInitiator = false;
- if (localStream) {
- localStream.getTracks().forEach(track => track.stop());
- }
-}
-
-function sendMessage(message) {
- console.log('Client sending message: ', message);
- socket.emit('message', message, room);
-};
-
-export function refreshVideos() {
- var localVideo = document.querySelector('#localVideo');
- var remoteVideo = document.querySelector('#remoteVideo');
- if (localVideo) {
- localVideo.srcObject = localStream;
- }
- if (remoteVideo) {
- remoteVideo.srcObject = remoteStream;
- }
-
-} \ No newline at end of file
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 4512d5c5b..5028e1f8f 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -116,7 +116,9 @@ export type FieldResult<T extends FieldType = FieldType> = Opt<T> | FieldWaiting
* If no default value is given, and the returned value is not undefined, it can be safely modified.
*/
export function DocListCastAsync(field: FieldResult): Promise<Doc[] | undefined>;
+// eslint-disable-next-line no-redeclare
export function DocListCastAsync(field: FieldResult, defaultValue: Doc[]): Promise<Doc[]>;
+// eslint-disable-next-line no-redeclare
export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
const list = Cast(field, listSpec(Doc));
return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
@@ -416,6 +418,7 @@ export class Doc extends RefField {
}
}
+// eslint-disable-next-line no-redeclare
export namespace Doc {
export function SetContainer(doc: Doc, container: Doc) {
if (container !== Doc.MyRecentlyClosed) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index d7268f31a..72b0ef721 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -285,10 +285,10 @@ export function inheritParentAcls(parent: Doc, child: Doc, layoutOnly: boolean)
* sets a callback function to be called whenever a value is assigned to the specified field key.
* For example, this is used to "publish" documents with titles that start with '@'
* @param prop
- * @param setter
+ * @param propSetter
*/
-export function SetPropSetterCb(prop: string, setter: ((target: any, value: any) => void) | undefined) {
- _propSetterCB.set(prop, setter);
+export function SetPropSetterCb(prop: string, propSetter: ((target: any, value: any) => void) | undefined) {
+ _propSetterCB.set(prop, propSetter);
}
//
diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts
index 6f5b9272a..520ebb42e 100644
--- a/src/server/ActionUtilities.ts
+++ b/src/server/ActionUtilities.ts
@@ -16,29 +16,30 @@ export function pathFromRoot(relative?: string) {
return path.resolve(projectRoot, relative);
}
-export async function fileDescriptorFromStream(path: string) {
- const logStream = createWriteStream(path);
- return new Promise<number>(resolve => logStream.on('open', resolve));
+export async function fileDescriptorFromStream(filePath: string) {
+ const logStream = createWriteStream(filePath);
+ return new Promise<number>(resolve => {
+ logStream.on('open', resolve);
+ });
}
-export const command_line = (command: string, fromDirectory?: string) => {
- return new Promise<string>((resolve, reject) => {
+export const commandLine = (command: string, fromDirectory?: string) =>
+ new Promise<string>((resolve, reject) => {
const options: ExecOptions = {};
if (fromDirectory) {
options.cwd = fromDirectory ? path.resolve(projectRoot, fromDirectory) : projectRoot;
}
exec(command, options, (err, stdout) => (err ? reject(err) : resolve(stdout)));
});
-};
-export const read_text_file = (relativePath: string) => {
+export const readTextFile = (relativePath: string) => {
const target = path.resolve(__dirname, relativePath);
return new Promise<string>((resolve, reject) => {
readFile(target, (err, data) => (err ? reject(err) : resolve(data.toString())));
});
};
-export const write_text_file = (relativePath: string, contents: any) => {
+export const writeTextFile = (relativePath: string, contents: any) => {
const target = path.resolve(__dirname, relativePath);
return new Promise<void>((resolve, reject) => {
writeFile(target, contents, err => (err ? reject(err) : resolve()));
@@ -55,39 +56,38 @@ export interface LogData<T> {
color?: Color;
}
+function logHelper(content: string, color: Color | string) {
+ if (typeof color === 'string') {
+ console.log(color, content);
+ } else {
+ console.log(color(content));
+ }
+}
+
let current = Math.ceil(Math.random() * 20);
export async function logExecution<T>({ startMessage, endMessage, action, color }: LogData<T>): Promise<T | undefined> {
- let result: T | undefined = undefined,
- error: Error | null = null;
+ let result: T | undefined;
+ let error: Error | null = null;
const resolvedColor = color || `\x1b[${31 + (++current % 6)}m%s\x1b[0m`;
- log_helper(`${startMessage}...`, resolvedColor);
+ logHelper(`${startMessage}...`, resolvedColor);
try {
result = await action();
} catch (e: any) {
error = e;
} finally {
- log_helper(typeof endMessage === 'string' ? endMessage : endMessage({ result, error }), resolvedColor);
+ logHelper(typeof endMessage === 'string' ? endMessage : endMessage({ result, error }), resolvedColor);
}
return result;
}
-
-function log_helper(content: string, color: Color | string) {
- if (typeof color === 'string') {
- console.log(color, content);
- } else {
- console.log(color(content));
- }
-}
-
export function logPort(listener: string, port: number) {
console.log(`${listener} listening on port ${yellow(String(port))}`);
}
export function msToTime(duration: number) {
- const milliseconds = Math.floor((duration % 1000) / 100),
- seconds = Math.floor((duration / 1000) % 60),
- minutes = Math.floor((duration / (1000 * 60)) % 60),
- hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
+ const milliseconds = Math.floor((duration % 1000) / 100);
+ const seconds = Math.floor((duration / 1000) % 60);
+ const minutes = Math.floor((duration / (1000 * 60)) % 60);
+ const hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
const hoursS = hours < 10 ? '0' + hours : hours;
const minutesS = minutes < 10 ? '0' + minutes : minutes;
@@ -96,21 +96,32 @@ export function msToTime(duration: number) {
return hoursS + ':' + minutesS + ':' + secondsS + '.' + milliseconds;
}
-export const createIfNotExists = async (path: string) => {
- if (await new Promise<boolean>(resolve => exists(path, resolve))) {
+export const createIfNotExists = async (filePath: string) => {
+ if (
+ await new Promise<boolean>(resolve => {
+ exists(filePath, resolve);
+ })
+ ) {
return true;
}
- return new Promise<boolean>(resolve => mkdir(path, error => resolve(error === null)));
+ return new Promise<boolean>(resolve => {
+ mkdir(filePath, error => resolve(error === null));
+ });
};
export async function Prune(rootDirectory: string): Promise<boolean> {
// const error = await new Promise<Error>(resolve => rimraf(rootDirectory).then(resolve));
- await new Promise<void>(resolve => rimraf(rootDirectory).then(() => resolve()));
+ await new Promise<void>(resolve => {
+ rimraf(rootDirectory).then(() => resolve());
+ });
// return error === null;
return true;
}
-export const Destroy = (mediaPath: string) => new Promise<boolean>(resolve => unlink(mediaPath, error => resolve(error === null)));
+export const Destroy = (mediaPath: string) =>
+ new Promise<boolean>(resolve => {
+ unlink(mediaPath, error => resolve(error === null));
+ });
export namespace Email {
const smtpTransport = nodemailer.createTransport({
@@ -137,9 +148,9 @@ export namespace Email {
const failures: DispatchFailure[] = [];
await Promise.all(
to.map(async recipient => {
- let error: Error | null;
const resolved = attachments ? ('length' in attachments ? attachments : [attachments]) : undefined;
- if ((error = await Email.dispatch({ to: recipient, subject, content, attachments: resolved })) !== null) {
+ const error = await Email.dispatch({ to: recipient, subject, content, attachments: resolved });
+ if (error !== null) {
failures.push({
recipient,
error,
@@ -158,6 +169,8 @@ export namespace Email {
text: `Hello ${to.split('@')[0]},\n\n${content}`,
attachments,
} as MailOptions;
- return new Promise<Error | null>(resolve => smtpTransport.sendMail(mailOptions, resolve));
+ return new Promise<Error | null>(resolve => {
+ smtpTransport.sendMail(mailOptions, resolve);
+ });
}
}
diff --git a/src/server/ApiManagers/ApiManager.ts b/src/server/ApiManagers/ApiManager.ts
index 27e9de065..f55495b2e 100644
--- a/src/server/ApiManagers/ApiManager.ts
+++ b/src/server/ApiManagers/ApiManager.ts
@@ -1,4 +1,4 @@
-import { RouteInitializer } from "../RouteManager";
+import { RouteInitializer } from '../RouteManager';
export type Registration = (initializer: RouteInitializer) => void;
@@ -8,4 +8,4 @@ export default abstract class ApiManager {
public register(register: Registration) {
this.initialize(register);
}
-} \ No newline at end of file
+}
diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts
index 9a9b807ae..9ad334c1b 100644
--- a/src/server/ApiManagers/DeleteManager.ts
+++ b/src/server/ApiManagers/DeleteManager.ts
@@ -1,12 +1,12 @@
-import ApiManager, { Registration } from './ApiManager';
-import { Method, _permissionDenied } from '../RouteManager';
-import { WebSocket } from '../websocket';
-import { Database } from '../database';
+import { mkdirSync } from 'fs';
import { rimraf } from 'rimraf';
-import { filesDirectory } from '..';
+import { filesDirectory } from '../SocketData';
import { DashUploadUtils } from '../DashUploadUtils';
-import { mkdirSync } from 'fs';
+import { Method } from '../RouteManager';
import RouteSubscriber from '../RouteSubscriber';
+import { Database } from '../database';
+import { WebSocket } from '../websocket';
+import ApiManager, { Registration } from './ApiManager';
export default class DeleteManager extends ApiManager {
protected initialize(register: Registration): void {
@@ -24,9 +24,11 @@ export default class DeleteManager extends ApiManager {
switch (target) {
case 'all':
all = true;
+ // eslint-disable-next-line no-fallthrough
case 'database':
await WebSocket.doDelete(false);
if (!all) break;
+ // eslint-disable-next-line no-fallthrough
case 'files':
rimraf.sync(filesDirectory);
mkdirSync(filesDirectory);
diff --git a/src/server/ApiManagers/DownloadManager.ts b/src/server/ApiManagers/DownloadManager.ts
index b105c825c..5ee21fb44 100644
--- a/src/server/ApiManagers/DownloadManager.ts
+++ b/src/server/ApiManagers/DownloadManager.ts
@@ -153,7 +153,7 @@ async function writeHierarchyRecursive(file: Archiver.Archiver, hierarchy: Hiera
}
}
-async function getDocs(id: string) {
+async function getDocs(docId: string) {
const files = new Set<string>();
const docs: { [id: string]: any } = {};
const fn = (doc: any): string[] => {
@@ -209,8 +209,8 @@ async function getDocs(id: string) {
}
return ids;
};
- await Database.Instance.visit([id], fn);
- return { id, docs, files };
+ await Database.Instance.visit([docId], fn);
+ return { id: docId, docs, files };
}
export default class DownloadManager extends ApiManager {
diff --git a/src/server/ApiManagers/MongoStore.js b/src/server/ApiManagers/MongoStore.js
index 28515fee4..5d91c2805 100644
--- a/src/server/ApiManagers/MongoStore.js
+++ b/src/server/ApiManagers/MongoStore.js
@@ -1,10 +1,9 @@
-'use strict';
-var __createBinding =
+const __createBinding =
(this && this.__createBinding) ||
(Object.create
? function (o, m, k, k2) {
if (k2 === undefined) k2 = k;
- var desc = Object.getOwnPropertyDescriptor(m, k);
+ let desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ('get' in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = {
enumerable: true,
@@ -19,25 +18,25 @@ var __createBinding =
if (k2 === undefined) k2 = k;
o[k2] = m[k];
});
-var __setModuleDefault =
+const __setModuleDefault =
(this && this.__setModuleDefault) ||
(Object.create
? function (o, v) {
Object.defineProperty(o, 'default', { enumerable: true, value: v });
}
: function (o, v) {
- o['default'] = v;
+ o.default = v;
});
-var __importStar =
+const __importStar =
(this && this.__importStar) ||
function (mod) {
if (mod && mod.__esModule) return mod;
- var result = {};
- if (mod != null) for (var k in mod) if (k !== 'default' && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+ const result = {};
+ if (mod != null) for (const k in mod) if (k !== 'default' && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
-var __importDefault =
+const __importDefault =
(this && this.__importDefault) ||
function (mod) {
return mod && mod.__esModule ? mod : { default: mod };
@@ -246,7 +245,7 @@ class MongoStore extends session.Store {
*/
set(sid, session, callback = noop) {
(async () => {
- var _a;
+ let _a;
try {
debug(`MongoStore#set=${sid}`);
// Removing the lastModified prop from the session object before update
@@ -306,7 +305,7 @@ class MongoStore extends session.Store {
}
touch(sid, session, callback = noop) {
(async () => {
- var _a;
+ let _a;
try {
debug(`MongoStore#touch=${sid}`);
const updateFields = {};
diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts
index 1b1db5809..f43ed6ac9 100644
--- a/src/server/ApiManagers/SearchManager.ts
+++ b/src/server/ApiManagers/SearchManager.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-use-before-define */
import { exec } from 'child_process';
import { cyan, green, red, yellow } from 'colors';
import { logExecution } from '../ActionUtilities';
@@ -17,8 +18,10 @@ export class SearchManager extends ApiManager {
switch (action) {
case 'start':
case 'stop':
- const status = req.params.action === 'start';
- SolrManager.SetRunning(status);
+ {
+ const status = req.params.action === 'start';
+ SolrManager.SetRunning(status);
+ }
break;
case 'update':
await SolrManager.update();
@@ -95,7 +98,7 @@ export namespace SolrManager {
if (doc.__type !== 'Doc') {
return;
}
- const fields = doc.fields;
+ const { fields } = doc;
if (!fields) {
return;
}
diff --git a/src/server/ApiManagers/SessionManager.ts b/src/server/ApiManagers/SessionManager.ts
index c3139896f..bebe50a62 100644
--- a/src/server/ApiManagers/SessionManager.ts
+++ b/src/server/ApiManagers/SessionManager.ts
@@ -9,20 +9,18 @@ const permissionError = 'You are not authorized!';
export default class SessionManager extends ApiManager {
private secureSubscriber = (root: string, ...params: string[]) => new RouteSubscriber(root).add('session_key', ...params);
- private authorizedAction = (handler: SecureHandler) => {
- return (core: AuthorizedCore) => {
- const {
- req: { params },
- res,
- } = core;
- if (!process.env.MONITORED) {
- return res.send('This command only makes sense in the context of a monitored session.');
- }
- if (params.session_key !== process.env.session_key) {
- return _permissionDenied(res, permissionError);
- }
- return handler(core);
- };
+ private authorizedAction = (handler: SecureHandler) => (core: AuthorizedCore) => {
+ const {
+ req: { params },
+ res,
+ } = core;
+ if (!process.env.MONITORED) {
+ return res.send('This command only makes sense in the context of a monitored session.');
+ }
+ if (params.session_key !== process.env.session_key) {
+ return _permissionDenied(res, permissionError);
+ }
+ return handler(core);
};
protected initialize(register: Registration): void {
diff --git a/src/server/ApiManagers/UtilManager.ts b/src/server/ApiManagers/UtilManager.ts
index e657866ce..8ad421a30 100644
--- a/src/server/ApiManagers/UtilManager.ts
+++ b/src/server/ApiManagers/UtilManager.ts
@@ -1,6 +1,7 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method } from "../RouteManager";
import { exec } from 'child_process';
+import ApiManager, { Registration } from './ApiManager';
+import { Method } from '../RouteManager';
+
// import { IBM_Recommender } from "../../client/apis/IBM_Recommender";
// import { Recommender } from "../Recommender";
@@ -8,9 +9,7 @@ import { exec } from 'child_process';
// recommender.testModel();
export default class UtilManager extends ApiManager {
-
protected initialize(register: Registration): void {
-
// register({
// method: Method.POST,
// subscription: "/IBMAnalysis",
@@ -33,26 +32,25 @@ export default class UtilManager extends ApiManager {
register({
method: Method.GET,
- subscription: "/pull",
- secureHandler: async ({ res }) => {
- return new Promise<void>(resolve => {
+ subscription: '/pull',
+ secureHandler: async ({ res }) =>
+ new Promise<void>(resolve => {
exec('"C:\\Program Files\\Git\\git-bash.exe" -c "git pull"', err => {
if (err) {
res.send(err.message);
return;
}
- res.redirect("/");
+ res.redirect('/');
resolve();
});
- });
- }
+ }),
});
register({
method: Method.GET,
- subscription: "/version",
- secureHandler: ({ res }) => {
- return new Promise<void>(resolve => {
+ subscription: '/version',
+ secureHandler: ({ res }) =>
+ new Promise<void>(resolve => {
exec('"C:\\Program Files\\Git\\bin\\git.exe" rev-parse HEAD', (err, stdout) => {
if (err) {
res.send(err.message);
@@ -61,10 +59,7 @@ export default class UtilManager extends ApiManager {
res.send(stdout);
});
resolve();
- });
- }
+ }),
});
-
}
-
-} \ No newline at end of file
+}
diff --git a/src/server/DashSession/Session/agents/applied_session_agent.ts b/src/server/DashSession/Session/agents/applied_session_agent.ts
index 2037e93e5..c42ba95cc 100644
--- a/src/server/DashSession/Session/agents/applied_session_agent.ts
+++ b/src/server/DashSession/Session/agents/applied_session_agent.ts
@@ -1,13 +1,13 @@
-import * as _cluster from "cluster";
-import { Monitor } from "./monitor";
-import { ServerWorker } from "./server_worker";
+import * as _cluster from 'cluster';
+import { Monitor } from './monitor';
+import { ServerWorker } from './server_worker';
+
const cluster = _cluster as any;
const isMaster = cluster.isPrimary;
export type ExitHandler = (reason: Error | boolean) => void | Promise<void>;
export abstract class AppliedSessionAgent {
-
// the following two methods allow the developer to create a custom
// session and use the built in customization options for each thread
protected abstract initializeMonitor(monitor: Monitor): Promise<string>;
@@ -18,15 +18,15 @@ export abstract class AppliedSessionAgent {
public killSession = (reason: string, graceful = true, errorCode = 0) => {
const target = cluster.default.isPrimary ? this.sessionMonitor : this.serverWorker;
target.killSession(reason, graceful, errorCode);
- }
+ };
private sessionMonitorRef: Monitor | undefined;
public get sessionMonitor(): Monitor {
if (!cluster.default.isPrimary) {
- this.serverWorker.emit("kill", {
+ this.serverWorker.emit('kill', {
graceful: false,
- reason: "Cannot access the session monitor directly from the server worker thread.",
- errorCode: 1
+ reason: 'Cannot access the session monitor directly from the server worker thread.',
+ errorCode: 1,
});
throw new Error();
}
@@ -36,7 +36,7 @@ export abstract class AppliedSessionAgent {
private serverWorkerRef: ServerWorker | undefined;
public get serverWorker(): ServerWorker {
if (isMaster) {
- throw new Error("Cannot access the server worker directly from the session monitor thread");
+ throw new Error('Cannot access the server worker directly from the session monitor thread');
}
return this.serverWorkerRef!;
}
@@ -52,8 +52,7 @@ export abstract class AppliedSessionAgent {
this.serverWorkerRef = await this.initializeServerWorker();
}
} else {
- throw new Error("Cannot launch a session thread more than once per process.");
+ throw new Error('Cannot launch a session thread more than once per process.');
}
}
-
-} \ No newline at end of file
+}
diff --git a/src/server/DashSession/Session/agents/monitor.ts b/src/server/DashSession/Session/agents/monitor.ts
index a6fde4356..6cdad46c2 100644
--- a/src/server/DashSession/Session/agents/monitor.ts
+++ b/src/server/DashSession/Session/agents/monitor.ts
@@ -1,21 +1,19 @@
-import { ExitHandler } from './applied_session_agent';
-import { Configuration, configurationSchema, defaultConfig, Identifiers, colorMapping } from '../utilities/session_config';
-import Repl, { ReplAction } from '../utilities/repl';
+import { ExecOptions, exec } from 'child_process';
import * as _cluster from 'cluster';
import { Worker } from 'cluster';
-import { manage, MessageHandler, ErrorLike } from './promisified_ipc_manager';
-import { red, cyan, white, yellow, blue } from 'colors';
-import { exec, ExecOptions } from 'child_process';
-import { validate, ValidationError } from 'jsonschema';
-import { Utilities } from '../utilities/utilities';
+import { blue, cyan, red, white, yellow } from 'colors';
import { readFileSync } from 'fs';
+import { ValidationError, validate } from 'jsonschema';
+import Repl, { ReplAction } from '../utilities/repl';
+import { Configuration, Identifiers, colorMapping, configurationSchema, defaultConfig } from '../utilities/session_config';
+import { Utilities } from '../utilities/utilities';
+import { ExitHandler } from './applied_session_agent';
import IPCMessageReceiver from './process_message_router';
+import { ErrorLike, MessageHandler, manage } from './promisified_ipc_manager';
import { ServerWorker } from './server_worker';
+
const cluster = _cluster as any;
-const isWorker = cluster.isWorker;
-const setupMaster = cluster.setupPrimary;
-const on = cluster.on;
-const fork = cluster.fork;
+const { isWorker, setupMaster, on, fork } = cluster;
/**
* Validates and reads the configuration file, accordingly builds a child process factory
@@ -41,9 +39,8 @@ export class Monitor extends IPCMessageReceiver {
} else if (++Monitor.count > 1) {
console.error(red('cannot create more than one monitor.'));
process.exit(1);
- } else {
- return new Monitor();
}
+ return new Monitor();
}
private constructor() {
@@ -128,25 +125,25 @@ export class Monitor extends IPCMessageReceiver {
this.repl.registerCommand(basename, argPatterns, action);
};
- public exec = (command: string, options?: ExecOptions) => {
- return new Promise<void>(resolve => {
+ public exec = (command: string, options?: ExecOptions) =>
+ new Promise<void>(resolve => {
exec(command, { ...options, encoding: 'utf8' }, (error, stdout, stderr) => {
if (error) {
this.execLog(red(`unable to execute ${white(command)}`));
error.message.split('\n').forEach(line => line.length && this.execLog(red(`(error) ${line}`)));
} else {
- let outLines: string[], errorLines: string[];
- if ((outLines = stdout.split('\n').filter(line => line.length)).length) {
+ const outLines = stdout.split('\n').filter(line => line.length);
+ if (outLines.length) {
outLines.forEach(line => line.length && this.execLog(cyan(`(stdout) ${line}`)));
}
- if ((errorLines = stderr.split('\n').filter(line => line.length)).length) {
+ const errorLines = stderr.split('\n').filter(line => line.length);
+ if (errorLines.length) {
errorLines.forEach(line => line.length && this.execLog(yellow(`(stderr) ${line}`)));
}
}
resolve();
});
});
- };
/**
* Generates a blue UTC string associated with the time
@@ -226,12 +223,10 @@ export class Monitor extends IPCMessageReceiver {
const newPollingIntervalSeconds = Math.floor(Number(args[1]));
if (newPollingIntervalSeconds < 0) {
this.mainLog(red('the polling interval must be a non-negative integer'));
- } else {
- if (newPollingIntervalSeconds !== this.config.polling.intervalSeconds) {
- this.config.polling.intervalSeconds = newPollingIntervalSeconds;
- if (args[2] === 'true') {
- Monitor.IPCManager.emit('updatePollingInterval', { newPollingIntervalSeconds });
- }
+ } else if (newPollingIntervalSeconds !== this.config.polling.intervalSeconds) {
+ this.config.polling.intervalSeconds = newPollingIntervalSeconds;
+ if (args[2] === 'true') {
+ Monitor.IPCManager.emit('updatePollingInterval', { newPollingIntervalSeconds });
}
}
});
@@ -297,6 +292,7 @@ export class Monitor extends IPCMessageReceiver {
};
}
+// eslint-disable-next-line no-redeclare
export namespace Monitor {
export enum IntrinsicEvents {
KeyGenerated = 'key_generated',
diff --git a/src/server/DashSession/Session/agents/process_message_router.ts b/src/server/DashSession/Session/agents/process_message_router.ts
index 0745ea455..3e2b7d8d0 100644
--- a/src/server/DashSession/Session/agents/process_message_router.ts
+++ b/src/server/DashSession/Session/agents/process_message_router.ts
@@ -1,7 +1,6 @@
-import { MessageHandler, PromisifiedIPCManager, HandlerMap } from "./promisified_ipc_manager";
+import { MessageHandler, PromisifiedIPCManager, HandlerMap } from './promisified_ipc_manager';
export default abstract class IPCMessageReceiver {
-
protected static IPCManager: PromisifiedIPCManager;
protected handlers: HandlerMap = {};
@@ -18,7 +17,7 @@ export default abstract class IPCMessageReceiver {
} else {
handlers.push(handler);
}
- }
+ };
/**
* Unregister a given listener at this message.
@@ -31,11 +30,10 @@ export default abstract class IPCMessageReceiver {
handlers.splice(index, 1);
}
}
- }
+ };
- /**
+ /**
* Unregister all listeners at this message.
*/
public clearMessageListeners = (...names: string[]) => names.map(name => delete this.handlers[name]);
-
-} \ No newline at end of file
+}
diff --git a/src/server/DashSession/Session/agents/promisified_ipc_manager.ts b/src/server/DashSession/Session/agents/promisified_ipc_manager.ts
index 76e218977..99b4d4de3 100644
--- a/src/server/DashSession/Session/agents/promisified_ipc_manager.ts
+++ b/src/server/DashSession/Session/agents/promisified_ipc_manager.ts
@@ -1,13 +1,14 @@
-import { Utilities } from '../utilities/utilities';
import { ChildProcess } from 'child_process';
+import { Utilities } from '../utilities/utilities';
/**
- * Convenience constructor
- * @param target the process / worker to which to attach the specialized listeners
+ * Specifies a general message format for this API
*/
-export function manage(target: IPCTarget, handlers?: HandlerMap) {
- return new PromisifiedIPCManager(target, handlers);
-}
+export type Message<T = any> = {
+ name: string;
+ args?: T;
+};
+export type MessageHandler<T = any> = (args: T) => any | Promise<any>;
/**
* Captures the logic to execute upon receiving a message
@@ -22,15 +23,10 @@ export type HandlerMap = { [name: string]: MessageHandler[] };
*/
export type IPCTarget = NodeJS.Process | ChildProcess;
-/**
- * Specifies a general message format for this API
- */
-export type Message<T = any> = {
- name: string;
- args?: T;
-};
-export type MessageHandler<T = any> = (args: T) => any | Promise<any>;
-
+interface Metadata {
+ isResponse: boolean;
+ id: string;
+}
/**
* When a message is emitted, it is embedded with private metadata
* to facilitate the resolution of promises, etc.
@@ -38,10 +34,6 @@ export type MessageHandler<T = any> = (args: T) => any | Promise<any>;
interface InternalMessage extends Message {
metadata: Metadata;
}
-interface Metadata {
- isResponse: boolean;
- id: string;
-}
/**
* Allows for the transmission of the error's key features over IPC.
@@ -95,7 +87,7 @@ export class PromisifiedIPCManager {
}
return new Promise<Response<T>>(resolve => {
const messageId = Utilities.guid();
- type InternalMessageHandler = (message: any /* MessageListener*/) => any | Promise<any>;
+ type InternalMessageHandler = (message: any /* MessageListener */) => any | Promise<any>;
const responseHandler: InternalMessageHandler = ({ metadata: { id, isResponse }, args }) => {
if (isResponse && id === messageId) {
this.target.removeListener('message', responseHandler);
@@ -118,8 +110,8 @@ export class PromisifiedIPCManager {
* completion response for each of the pending messages, allowing their
* promises in the caller to resolve.
*/
- public destroy = () => {
- return new Promise<void>(async resolve => {
+ public destroy = () =>
+ new Promise<void>(async resolve => {
if (this.callerIsTarget) {
this.destroyHelper();
} else {
@@ -127,7 +119,6 @@ export class PromisifiedIPCManager {
}
resolve();
});
- };
/**
* Dispatches the dummy responses and sets the isDestroyed flag to true.
@@ -168,12 +159,20 @@ export class PromisifiedIPCManager {
error = e;
}
if (!this.isDestroyed && this.target.send) {
- const metadata = { id, isResponse: true };
+ const metadataRes = { id, isResponse: true };
const response: Response = { results, error };
- const message = { name, args: response, metadata };
+ const messageRes = { name, args: response, metadata: metadataRes };
delete this.pendingMessages[id];
- this.target.send(message);
+ this.target.send(messageRes);
}
}
};
}
+
+/**
+ * Convenience constructor
+ * @param target the process / worker to which to attach the specialized listeners
+ */
+export function manage(target: IPCTarget, handlers?: HandlerMap) {
+ return new PromisifiedIPCManager(target, handlers);
+}
diff --git a/src/server/DashSession/Session/agents/server_worker.ts b/src/server/DashSession/Session/agents/server_worker.ts
index d8b3ee80b..85e1b31d6 100644
--- a/src/server/DashSession/Session/agents/server_worker.ts
+++ b/src/server/DashSession/Session/agents/server_worker.ts
@@ -1,10 +1,10 @@
-import cluster from "cluster";
-import { green, red, white, yellow } from "colors";
-import { get } from "request-promise";
-import { ExitHandler } from "./applied_session_agent";
-import { Monitor } from "./monitor";
-import IPCMessageReceiver from "./process_message_router";
-import { ErrorLike, manage } from "./promisified_ipc_manager";
+import cluster from 'cluster';
+import { green, red, white, yellow } from 'colors';
+import { get } from 'request-promise';
+import { ExitHandler } from './applied_session_agent';
+import { Monitor } from './monitor';
+import IPCMessageReceiver from './process_message_router';
+import { ErrorLike, manage } from './promisified_ipc_manager';
/**
* Effectively, each worker repairs the connection to the server by reintroducing a consistent state
@@ -23,18 +23,17 @@ export class ServerWorker extends IPCMessageReceiver {
private isInitialized = false;
public static Create(work: Function) {
if (cluster.isPrimary) {
- console.error(red("cannot create a worker on the monitor process."));
+ console.error(red('cannot create a worker on the monitor process.'));
process.exit(1);
} else if (++ServerWorker.count > 1) {
- ServerWorker.IPCManager.emit("kill", {
- reason: "cannot create more than one worker on a given worker process.",
+ ServerWorker.IPCManager.emit('kill', {
+ reason: 'cannot create more than one worker on a given worker process.',
graceful: false,
- errorCode: 1
+ errorCode: 1,
});
process.exit(1);
- } else {
- return new ServerWorker(work);
}
+ return new ServerWorker(work);
}
/**
@@ -48,7 +47,7 @@ export class ServerWorker extends IPCMessageReceiver {
* server worker (child process). This will also kill
* this process (child process).
*/
- public killSession = (reason: string, graceful = true, errorCode = 0) => this.emit<never>("kill", { reason, graceful, errorCode });
+ public killSession = (reason: string, graceful = true, errorCode = 0) => this.emit<never>('kill', { reason, graceful, errorCode });
/**
* A convenience wrapper to tell the session monitor (parent process)
@@ -60,7 +59,7 @@ export class ServerWorker extends IPCMessageReceiver {
super();
this.configureInternalHandlers();
ServerWorker.IPCManager = manage(process, this.handlers);
- this.lifecycleNotification(green(`initializing process... ${white(`[${process.execPath} ${process.execArgv.join(" ")}]`)}`));
+ this.lifecycleNotification(green(`initializing process... ${white(`[${process.execPath} ${process.execArgv.join(' ')}]`)}`));
const { pollingRoute, serverPort, pollingIntervalSeconds, pollingFailureTolerance } = process.env;
this.serverPort = Number(serverPort);
@@ -78,8 +77,10 @@ export class ServerWorker extends IPCMessageReceiver {
*/
protected configureInternalHandlers = () => {
// updates the local values of variables to the those sent from master
- this.on("updatePollingInterval", ({ newPollingIntervalSeconds }) => this.pollingIntervalSeconds = newPollingIntervalSeconds);
- this.on("manualExit", async ({ isSessionEnd }) => {
+ this.on('updatePollingInterval', ({ newPollingIntervalSeconds }) => {
+ this.pollingIntervalSeconds = newPollingIntervalSeconds;
+ });
+ this.on('manualExit', async ({ isSessionEnd }) => {
await ServerWorker.IPCManager.destroy();
await this.executeExitHandlers(isSessionEnd);
process.exit(0);
@@ -91,7 +92,7 @@ export class ServerWorker extends IPCMessageReceiver {
const appropriateError = reason instanceof Error ? reason : new Error(`unhandled rejection: ${reason}`);
this.proactiveUnplannedExit(appropriateError);
});
- }
+ };
/**
* Execute the list of functions registered to be called
@@ -102,7 +103,7 @@ export class ServerWorker extends IPCMessageReceiver {
/**
* Notify master thread (which will log update in the console) of initialization via IPC.
*/
- public lifecycleNotification = (event: string) => this.emit("lifecycle", { event });
+ public lifecycleNotification = (event: string) => this.emit('lifecycle', { event });
/**
* Called whenever the process has a reason to terminate, either through an uncaught exception
@@ -120,11 +121,11 @@ export class ServerWorker extends IPCMessageReceiver {
this.lifecycleNotification(red(error.message));
await ServerWorker.IPCManager.destroy();
process.exit(1);
- }
+ };
/**
* This monitors the health of the server by submitting a get request to whatever port / route specified
- * by the configuration every n seconds, where n is also given by the configuration.
+ * by the configuration every n seconds, where n is also given by the configuration.
*/
private pollServer = async (): Promise<void> => {
await new Promise<void>(resolve => {
@@ -156,6 +157,5 @@ export class ServerWorker extends IPCMessageReceiver {
});
// controlled, asynchronous infinite recursion achieves a persistent poll that does not submit a new request until the previous has completed
this.pollServer();
- }
-
+ };
}
diff --git a/src/server/DashSession/Session/utilities/repl.ts b/src/server/DashSession/Session/utilities/repl.ts
index 643141286..5d9f15e4c 100644
--- a/src/server/DashSession/Session/utilities/repl.ts
+++ b/src/server/DashSession/Session/utilities/repl.ts
@@ -1,5 +1,5 @@
-import { createInterface, Interface } from "readline";
-import { red, green, white } from "colors";
+import { createInterface, Interface } from 'readline';
+import { red, green, white } from 'colors';
export interface Configuration {
identifier: () => string | string;
@@ -32,76 +32,82 @@ export default class Repl {
this.interface = createInterface(process.stdin, process.stdout).on('line', this.considerInput);
}
- private resolvedIdentifier = () => typeof this.identifier === "string" ? this.identifier : this.identifier();
+ private resolvedIdentifier = () => (typeof this.identifier === 'string' ? this.identifier : this.identifier());
private usage = (command: string, validCommand: boolean) => {
if (validCommand) {
const formatted = white(command);
- const patterns = green(this.commandMap.get(command)!.map(({ argPatterns }) => `${formatted} ${argPatterns.join(" ")}`).join('\n'));
+ const patterns = green(
+ this.commandMap
+ .get(command)!
+ .map(({ argPatterns }) => `${formatted} ${argPatterns.join(' ')}`)
+ .join('\n')
+ );
return `${this.resolvedIdentifier()}\nthe given arguments do not match any registered patterns for ${formatted}\nthe list of valid argument patterns is given by:\n${patterns}`;
- } else {
- const resolved = this.keys;
- if (resolved) {
- return resolved;
- }
- const members: string[] = [];
- const keys = this.commandMap.keys();
- let next: IteratorResult<string>;
- while (!(next = keys.next()).done) {
- members.push(next.value);
- }
- return `${this.resolvedIdentifier()} commands: { ${members.sort().join(", ")} }`;
}
- }
+ const resolved = this.keys;
+ if (resolved) {
+ return resolved;
+ }
+ const members: string[] = [];
+ const keys = this.commandMap.keys();
+ let next: IteratorResult<string>;
+ // eslint-disable-next-line no-cond-assign
+ while (!(next = keys.next()).done) {
+ members.push(next.value);
+ }
+ return `${this.resolvedIdentifier()} commands: { ${members.sort().join(', ')} }`;
+ };
private success = (command: string) => `${this.resolvedIdentifier()} completed local execution of ${white(command)}`;
public registerCommand = (basename: string, argPatterns: (RegExp | string)[], action: ReplAction) => {
const existing = this.commandMap.get(basename);
- const converted = argPatterns.map(input => input instanceof RegExp ? input : new RegExp(input));
+ const converted = argPatterns.map(input => (input instanceof RegExp ? input : new RegExp(input)));
const registration = { argPatterns: converted, action };
if (existing) {
existing.push(registration);
} else {
this.commandMap.set(basename, [registration]);
}
- }
+ };
private invalid = (command: string, validCommand: boolean) => {
- console.log(red(typeof this.onInvalid === "string" ? this.onInvalid : this.onInvalid(command, validCommand)));
+ console.log(red(typeof this.onInvalid === 'string' ? this.onInvalid : this.onInvalid(command, validCommand)));
this.busy = false;
- }
+ };
private valid = (command: string) => {
- console.log(green(typeof this.onValid === "string" ? this.onValid : this.onValid(command)));
+ console.log(green(typeof this.onValid === 'string' ? this.onValid : this.onValid(command)));
this.busy = false;
- }
+ };
- private considerInput = async (line: string) => {
+ private considerInput = async (lineIn: string) => {
if (this.busy) {
- console.log(red("Busy"));
+ console.log(red('Busy'));
return;
}
this.busy = true;
- line = line.trim();
+ let line = lineIn.trim();
if (this.isCaseSensitive) {
line = line.toLowerCase();
}
const [command, ...args] = line.split(/\s+/g);
if (!command) {
- return this.invalid(command, false);
+ this.invalid(command, false);
+ return;
}
const registered = this.commandMap.get(command);
if (registered) {
const { length } = args;
const candidates = registered.filter(({ argPatterns: { length: count } }) => count === length);
- for (const { argPatterns, action } of candidates) {
+ candidates.forEach(({ argPatterns, action }: { argPatterns: any; action: any }) => {
const parsed: string[] = [];
let matched = true;
if (length) {
for (let i = 0; i < length; i++) {
- let matches: RegExpExecArray | null;
- if ((matches = argPatterns[i].exec(args[i])) === null) {
+ const matches = argPatterns[i].exec(args[i]);
+ if (matches === null) {
matched = false;
break;
}
@@ -110,19 +116,17 @@ export default class Repl {
}
if (!length || matched) {
const result = action(parsed);
- const resolve = () => this.valid(`${command} ${parsed.join(" ")}`);
+ const resolve = () => this.valid(`${command} ${parsed.join(' ')}`);
if (result instanceof Promise) {
result.then(resolve);
} else {
resolve();
}
- return;
}
- }
+ });
this.invalid(command, true);
} else {
this.invalid(command, false);
}
- }
-
-} \ No newline at end of file
+ };
+}
diff --git a/src/server/DashSession/Session/utilities/session_config.ts b/src/server/DashSession/Session/utilities/session_config.ts
index 266759929..b42c1a3c7 100644
--- a/src/server/DashSession/Session/utilities/session_config.ts
+++ b/src/server/DashSession/Session/utilities/session_config.ts
@@ -1,85 +1,85 @@
-import { Schema } from "jsonschema";
-import { yellow, red, cyan, green, blue, magenta, Color, grey, gray, white, black } from "colors";
+import { Schema } from 'jsonschema';
+import { yellow, red, cyan, green, blue, magenta, Color, grey, gray, white, black } from 'colors';
const colorPattern = /black|red|green|yellow|blue|magenta|cyan|white|gray|grey/;
const identifierProperties: Schema = {
- type: "object",
+ type: 'object',
properties: {
text: {
- type: "string",
- minLength: 1
+ type: 'string',
+ minLength: 1,
},
color: {
- type: "string",
- pattern: colorPattern
- }
- }
+ type: 'string',
+ pattern: colorPattern,
+ },
+ },
};
const portProperties: Schema = {
- type: "number",
+ type: 'number',
minimum: 443,
- maximum: 65535
+ maximum: 65535,
};
export const configurationSchema: Schema = {
- id: "/configuration",
- type: "object",
+ id: '/configuration',
+ type: 'object',
properties: {
- showServerOutput: { type: "boolean" },
+ showServerOutput: { type: 'boolean' },
ports: {
- type: "object",
+ type: 'object',
properties: {
server: portProperties,
- socket: portProperties
+ socket: portProperties,
},
- required: ["server"],
- additionalProperties: true
+ required: ['server'],
+ additionalProperties: true,
},
identifiers: {
- type: "object",
+ type: 'object',
properties: {
master: identifierProperties,
worker: identifierProperties,
- exec: identifierProperties
- }
+ exec: identifierProperties,
+ },
},
polling: {
- type: "object",
+ type: 'object',
additionalProperties: false,
properties: {
intervalSeconds: {
- type: "number",
+ type: 'number',
minimum: 1,
- maximum: 86400
+ maximum: 86400,
},
route: {
- type: "string",
- pattern: /\/[a-zA-Z]*/g
+ type: 'string',
+ pattern: /\/[a-zA-Z]*/g,
},
failureTolerance: {
- type: "number",
+ type: 'number',
minimum: 0,
- }
- }
+ },
+ },
},
- }
+ },
};
-type ColorLabel = "yellow" | "red" | "cyan" | "green" | "blue" | "magenta" | "grey" | "gray" | "white" | "black";
+type ColorLabel = 'yellow' | 'red' | 'cyan' | 'green' | 'blue' | 'magenta' | 'grey' | 'gray' | 'white' | 'black';
export const colorMapping: Map<ColorLabel, Color> = new Map([
- ["yellow", yellow],
- ["red", red],
- ["cyan", cyan],
- ["green", green],
- ["blue", blue],
- ["magenta", magenta],
- ["grey", grey],
- ["gray", gray],
- ["white", white],
- ["black", black]
+ ['yellow', yellow],
+ ['red', red],
+ ['cyan', cyan],
+ ['green', green],
+ ['blue', blue],
+ ['magenta', magenta],
+ ['grey', grey],
+ ['gray', gray],
+ ['white', white],
+ ['black', black],
]);
interface Identifier {
@@ -108,22 +108,22 @@ export const defaultConfig: Configuration = {
showServerOutput: false,
identifiers: {
master: {
- text: "__monitor__",
- color: "yellow"
+ text: '__monitor__',
+ color: 'yellow',
},
worker: {
- text: "__server__",
- color: "magenta"
+ text: '__server__',
+ color: 'magenta',
},
exec: {
- text: "__exec__",
- color: "green"
- }
+ text: '__exec__',
+ color: 'green',
+ },
},
ports: { server: 1050 },
polling: {
- route: "/",
+ route: '/',
intervalSeconds: 30,
- failureTolerance: 0
- }
-}; \ No newline at end of file
+ failureTolerance: 0,
+ },
+};
diff --git a/src/server/DashSession/Session/utilities/utilities.ts b/src/server/DashSession/Session/utilities/utilities.ts
index eb8de9d7e..a2ba29c67 100644
--- a/src/server/DashSession/Session/utilities/utilities.ts
+++ b/src/server/DashSession/Session/utilities/utilities.ts
@@ -1,31 +1,16 @@
-import { v4 } from "uuid";
+import { v4 } from 'uuid';
export namespace Utilities {
-
export function guid() {
return v4();
}
- /**
- * At any arbitrary layer of nesting within the configuration objects, any single value that
- * is not specified by the configuration is given the default counterpart. If, within an object,
- * one peer is given by configuration and two are not, the one is preserved while the two are given
- * the default value.
- * @returns the composition of all of the assigned objects, much like Object.assign(), but with more
- * granularity in the overwriting of nested objects
- */
- export function preciseAssign(target: any, ...sources: any[]): any {
- for (const source of sources) {
- preciseAssignHelper(target, source);
- }
- return target;
- }
-
export function preciseAssignHelper(target: any, source: any) {
- Array.from(new Set([...Object.keys(target), ...Object.keys(source)])).map(property => {
- let targetValue: any, sourceValue: any;
- if (sourceValue = source[property]) {
- if (typeof sourceValue === "object" && typeof (targetValue = target[property]) === "object") {
+ Array.from(new Set([...Object.keys(target), ...Object.keys(source)])).forEach(property => {
+ const targetValue = target[property];
+ const sourceValue = source[property];
+ if (sourceValue) {
+ if (typeof sourceValue === 'object' && typeof targetValue === 'object') {
preciseAssignHelper(targetValue, sourceValue);
} else {
target[property] = sourceValue;
@@ -34,4 +19,18 @@ export namespace Utilities {
});
}
-} \ No newline at end of file
+ /**
+ * At any arbitrary layer of nesting within the configuration objects, any single value that
+ * is not specified by the configuration is given the default counterpart. If, within an object,
+ * one peer is given by configuration and two are not, the one is preserved while the two are given
+ * the default value.
+ * @returns the composition of all of the assigned objects, much like Object.assign(), but with more
+ * granularity in the overwriting of nested objects
+ */
+ export function preciseAssign(target: any, ...sources: any[]): any {
+ sources.forEach(source => {
+ preciseAssignHelper(target, source);
+ });
+ return target;
+ }
+}
diff --git a/src/server/DashStats.ts b/src/server/DashStats.ts
index 485ab9f99..808d2c6f2 100644
--- a/src/server/DashStats.ts
+++ b/src/server/DashStats.ts
@@ -1,7 +1,6 @@
import { cyan, magenta } from 'colors';
import { Response } from 'express';
import * as fs from 'fs';
-import SocketIO from 'socket.io';
import { socketMap, timeMap, userOperations } from './SocketData';
/**
@@ -242,7 +241,7 @@ export namespace DashStats {
* @param username the username in the format of "username@domain.com logged in"
* @param socket the websocket associated with the current connection
*/
- export function logUserLogin(username: string | undefined, socket: SocketIO.Socket) {
+ export function logUserLogin(username: string | undefined) {
if (!(username === undefined)) {
const currentDate = new Date();
console.log(magenta(`User ${username.split(' ')[0]} logged in at: ${currentDate.toISOString()}`));
@@ -267,7 +266,7 @@ export namespace DashStats {
* @param username the username in the format of "username@domain.com logged in"
* @param socket the websocket associated with the current connection.
*/
- export function logUserLogout(username: string | undefined, socket: SocketIO.Socket) {
+ export function logUserLogout(username: string | undefined) {
if (!(username === undefined)) {
const currentDate = new Date();
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 3d8325da9..08cea1de5 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -186,8 +186,8 @@ export namespace DashUploadUtils {
const image = await request.get(source, { encoding: null });
const { /* data, */ error } = await new Promise<{ data: any; error: any }>(resolve => {
// eslint-disable-next-line no-new
- new ExifImage({ image }, (error, data) => {
- const reason = (error as any)?.code;
+ new ExifImage({ image }, (exifError, data) => {
+ const reason = (exifError as any)?.code;
resolve({ data, error: reason });
});
});
@@ -506,8 +506,8 @@ export namespace DashUploadUtils {
if (fExists(fileKey, Directory.pdfs) && fExists(textFilename, Directory.text)) {
fs.unlink(file.filepath, () => {});
return new Promise<Upload.FileResponse>(res => {
- const textFilename = `${fileKey.substring(0, fileKey.length - 4)}.txt`;
- const readStream = createReadStream(serverPathToFile(Directory.text, textFilename));
+ const pdfTextFilename = `${fileKey.substring(0, fileKey.length - 4)}.txt`;
+ const readStream = createReadStream(serverPathToFile(Directory.text, pdfTextFilename));
let rawText = '';
readStream
.on('data', chunk => {
diff --git a/src/server/GarbageCollector.ts b/src/server/GarbageCollector.ts
index 423c719c2..041f65592 100644
--- a/src/server/GarbageCollector.ts
+++ b/src/server/GarbageCollector.ts
@@ -1,11 +1,15 @@
+/* eslint-disable no-await-in-loop */
+/* eslint-disable no-continue */
+/* eslint-disable no-cond-assign */
+/* eslint-disable no-restricted-syntax */
import * as fs from 'fs';
import * as path from 'path';
import { Database } from './database';
import { Search } from './Search';
-
function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
for (const key in doc) {
+ // eslint-disable-next-line no-prototype-builtins
if (!doc.hasOwnProperty(key)) {
continue;
}
@@ -13,22 +17,22 @@ function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
if (field === undefined || field === null) {
continue;
}
- if (field.__type === "proxy" || field.__type === "prefetch_proxy") {
+ if (field.__type === 'proxy' || field.__type === 'prefetch_proxy') {
ids.push(field.fieldId);
- } else if (field.__type === "list") {
+ } else if (field.__type === 'list') {
addDoc(field.fields, ids, files);
- } else if (typeof field === "string") {
- const re = /"(?:dataD|d)ocumentId"\s*:\s*"([\w\-]*)"/g;
+ } else if (typeof field === 'string') {
+ const re = /"(?:dataD|d)ocumentId"\s*:\s*"([\w-]*)"/g;
let match: string[] | null;
while ((match = re.exec(field)) !== null) {
ids.push(match[1]);
}
- } else if (field.__type === "RichTextField") {
+ } else if (field.__type === 'RichTextField') {
const re = /"href"\s*:\s*"(.*?)"/g;
let match: string[] | null;
while ((match = re.exec(field.Data)) !== null) {
const urlString = match[1];
- const split = new URL(urlString).pathname.split("doc/");
+ const split = new URL(urlString).pathname.split('doc/');
if (split.length > 1) {
ids.push(split[split.length - 1]);
}
@@ -36,7 +40,7 @@ function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
const re2 = /"src"\s*:\s*"(.*?)"/g;
while ((match = re2.exec(field.Data)) !== null) {
const urlString = match[1];
- const pathname = new URL(urlString).pathname;
+ const { pathname } = new URL(urlString);
const ext = path.extname(pathname);
const fileName = path.basename(pathname, ext);
let exts = files[fileName];
@@ -45,9 +49,9 @@ function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
}
exts.push(ext);
}
- } else if (["audio", "image", "video", "pdf", "web", "map"].includes(field.__type)) {
+ } else if (['audio', 'image', 'video', 'pdf', 'web', 'map'].includes(field.__type)) {
const url = new URL(field.url);
- const pathname = url.pathname;
+ const { pathname } = url;
const ext = path.extname(pathname);
const fileName = path.basename(pathname, ext);
let exts = files[fileName];
@@ -60,12 +64,12 @@ function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
}
async function GarbageCollect(full: boolean = true) {
- console.log("start GC");
+ console.log('start GC');
const start = Date.now();
// await new Promise(res => setTimeout(res, 3000));
const cursor = await Database.Instance.query({}, { userDocumentId: 1 }, 'users');
const users = await cursor.toArray();
- const ids: string[] = [...users.map((user:any) => user.userDocumentId), ...users.map((user:any) => user.sharingDocumentId), ...users.map((user:any) => user.linkDatabaseId)];
+ const ids: string[] = [...users.map((user: any) => user.userDocumentId), ...users.map((user: any) => user.sharingDocumentId), ...users.map((user: any) => user.linkDatabaseId)];
const visited = new Set<string>();
const files: { [name: string]: string[] } = {};
@@ -76,9 +80,11 @@ async function GarbageCollect(full: boolean = true) {
if (!fetchIds.length) {
continue;
}
- const docs = await new Promise<{ [key: string]: any }[]>(res => Database.Instance.getDocuments(fetchIds, res));
+ const docs = await new Promise<{ [key: string]: any }[]>(res => {
+ Database.Instance.getDocuments(fetchIds, res);
+ });
for (const doc of docs) {
- const id = doc.id;
+ const { id } = doc;
if (doc === undefined) {
console.log(`Couldn't find field with Id ${id}`);
continue;
@@ -95,19 +101,27 @@ async function GarbageCollect(full: boolean = true) {
const notToDelete = Array.from(visited);
const toDeleteCursor = await Database.Instance.query({ _id: { $nin: notToDelete } }, { _id: 1 });
- const toDelete: string[] = (await toDeleteCursor.toArray()).map((doc:any) => doc._id);
+ const toDelete: string[] = (await toDeleteCursor.toArray()).map((doc: any) => doc._id);
toDeleteCursor.close();
if (!full) {
- await Database.Instance.updateMany({ _id: { $nin: notToDelete } }, { $set: { "deleted": true } });
- await Database.Instance.updateMany({ _id: { $in: notToDelete } }, { $unset: { "deleted": true } });
- console.log(await Search.updateDocuments(
- notToDelete.map<any>(id => ({
- id, deleted: { set: null }
- }))
- .concat(toDelete.map(id => ({
- id, deleted: { set: true }
- })))));
- console.log("Done with partial GC");
+ await Database.Instance.updateMany({ _id: { $nin: notToDelete } }, { $set: { deleted: true } });
+ await Database.Instance.updateMany({ _id: { $in: notToDelete } }, { $unset: { deleted: true } });
+ console.log(
+ await Search.updateDocuments(
+ notToDelete
+ .map<any>(id => ({
+ id,
+ deleted: { set: null },
+ }))
+ .concat(
+ toDelete.map(id => ({
+ id,
+ deleted: { set: true },
+ }))
+ )
+ )
+ );
+ console.log('Done with partial GC');
console.log(`Took ${(Date.now() - start) / 1000} seconds`);
} else {
let i = 0;
@@ -123,15 +137,15 @@ async function GarbageCollect(full: boolean = true) {
console.log(`${deleted} documents deleted`);
await Search.deleteDocuments(toDelete);
- console.log("Cleared search documents");
+ console.log('Cleared search documents');
- const folder = "./src/server/public/files/";
+ const folder = './src/server/public/files/';
fs.readdir(folder, (_, fileList) => {
const filesToDelete = fileList.filter(file => {
const ext = path.extname(file);
let base = path.basename(file, ext);
const existsInDb = (base in files || (base = base.substring(0, base.length - 2)) in files) && files[base].includes(ext);
- return file !== ".gitignore" && !existsInDb;
+ return file !== '.gitignore' && !existsInDb;
});
console.log(`Deleting ${filesToDelete.length} files`);
filesToDelete.forEach(file => {
diff --git a/src/server/MemoryDatabase.ts b/src/server/MemoryDatabase.ts
index b74332bf5..1432d91c4 100644
--- a/src/server/MemoryDatabase.ts
+++ b/src/server/MemoryDatabase.ts
@@ -3,16 +3,15 @@ import { DocumentsCollection, IDatabase } from './IDatabase';
import { Transferable } from './Message';
export class MemoryDatabase implements IDatabase {
-
private db: { [collectionName: string]: { [id: string]: any } } = {};
private getCollection(collectionName: string) {
const collection = this.db[collectionName];
if (collection) {
return collection;
- } else {
- return this.db[collectionName] = {};
}
+ this.db[collectionName] = {};
+ return {};
}
public getCollectionNames() {
@@ -21,15 +20,15 @@ export class MemoryDatabase implements IDatabase {
public update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateResult) => void, _upsert?: boolean, collectionName = DocumentsCollection): Promise<void> {
const collection = this.getCollection(collectionName);
- const set = "$set";
+ const set = '$set';
if (set in value) {
let currentVal = collection[id] ?? (collection[id] = {});
const val = value[set];
for (const key in val) {
- const keys = key.split(".");
+ const keys = key.split('.');
for (let i = 0; i < keys.length - 1; i++) {
const k = keys[i];
- if (typeof currentVal[k] === "object") {
+ if (typeof currentVal[k] === 'object') {
currentVal = currentVal[k];
} else {
currentVal[k] = {};
@@ -45,7 +44,7 @@ export class MemoryDatabase implements IDatabase {
return Promise.resolve(undefined);
}
- public updateMany(query: any, update: any, collectionName = DocumentsCollection): Promise<mongodb.UpdateResult> {
+ public updateMany(/* query: any, update: any, collectionName = DocumentsCollection */): Promise<mongodb.UpdateResult> {
throw new Error("Can't updateMany a MemoryDatabase");
}
@@ -54,7 +53,9 @@ export class MemoryDatabase implements IDatabase {
}
public delete(query: any, collectionName?: string): Promise<mongodb.DeleteResult>;
+ // eslint-disable-next-line no-dupe-class-members
public delete(id: string, collectionName?: string): Promise<mongodb.DeleteResult>;
+ // eslint-disable-next-line no-dupe-class-members
public delete(id: any, collectionName = DocumentsCollection): Promise<mongodb.DeleteResult> {
const i = id.id ?? id;
delete this.getCollection(collectionName)[i];
@@ -75,7 +76,7 @@ export class MemoryDatabase implements IDatabase {
}
public insert(value: any, collectionName = DocumentsCollection): Promise<void> {
- const id = value.id;
+ const { id } = value;
this.getCollection(collectionName)[id] = value;
return Promise.resolve();
}
@@ -93,14 +94,18 @@ export class MemoryDatabase implements IDatabase {
const count = Math.min(ids.length, 1000);
const index = ids.length - count;
const fetchIds = ids.splice(index, count).filter(id => !visited.has(id));
- if (!fetchIds.length) {
- continue;
- }
- const docs = await new Promise<{ [key: string]: any }[]>(res => this.getDocuments(fetchIds, res, collectionName));
- for (const doc of docs) {
- const id = doc.id;
- visited.add(id);
- ids.push(...(await fn(doc)));
+ if (fetchIds.length) {
+ // eslint-disable-next-line no-await-in-loop
+ const docs = await new Promise<{ [key: string]: any }[]>(res => {
+ this.getDocuments(fetchIds, res, collectionName);
+ });
+ // eslint-disable-next-line no-restricted-syntax
+ for (const doc of docs) {
+ const { id } = doc;
+ visited.add(id);
+ // eslint-disable-next-line no-await-in-loop
+ ids.push(...(await fn(doc)));
+ }
}
}
}
diff --git a/src/server/PdfTypes.ts b/src/server/PdfTypes.ts
index e87f08e1d..fb435a677 100644
--- a/src/server/PdfTypes.ts
+++ b/src/server/PdfTypes.ts
@@ -1,21 +1,19 @@
-export interface ParsedPDF {
- numpages: number;
- numrender: number;
- info: PDFInfo;
- metadata: PDFMetadata;
- version: string; //https://mozilla.github.io/pdf.js/getting_started/
- text: string;
-}
-
export interface PDFInfo {
PDFFormatVersion: string;
IsAcroFormPresent: boolean;
IsXFAPresent: boolean;
[key: string]: any;
}
-
export interface PDFMetadata {
parse(): void;
get(name: string): string;
has(name: string): boolean;
-} \ No newline at end of file
+}
+export interface ParsedPDF {
+ numpages: number;
+ numrender: number;
+ info: PDFInfo;
+ metadata: PDFMetadata;
+ version: string; // https://mozilla.github.io/pdf.js/getting_started/
+ text: string;
+}
diff --git a/src/server/ProcessFactory.ts b/src/server/ProcessFactory.ts
index f69eda4c3..3791b0e1e 100644
--- a/src/server/ProcessFactory.ts
+++ b/src/server/ProcessFactory.ts
@@ -1,44 +1,42 @@
-import { ChildProcess, spawn, StdioOptions } from "child_process";
-import { existsSync, mkdirSync } from "fs";
-import { Stream } from "stream";
+import { ChildProcess, spawn, StdioOptions } from 'child_process';
+import { existsSync, mkdirSync } from 'fs';
+import { rimraf } from 'rimraf';
+import { Stream } from 'stream';
import { fileDescriptorFromStream, pathFromRoot } from './ActionUtilities';
-import { rimraf } from "rimraf";
-
-export namespace ProcessFactory {
-
- export type Sink = "pipe" | "ipc" | "ignore" | "inherit" | Stream | number | null | undefined;
-
- export async function createWorker(command: string, args?: readonly string[], stdio?: StdioOptions | "logfile", detached = true): Promise<ChildProcess> {
- if (stdio === "logfile") {
- const log_fd = await Logger.create(command, args);
- stdio = ["ignore", log_fd, log_fd];
- }
- const child = spawn(command, args ?? [], { detached, stdio });
- child.unref();
- return child;
- }
-
-}
export namespace Logger {
-
- const logPath = pathFromRoot("./logs");
+ const logPath = pathFromRoot('./logs');
export async function initialize() {
if (existsSync(logPath)) {
if (!process.env.SPAWNED) {
- await new Promise<any>(resolve => rimraf(logPath).then(resolve));
+ await new Promise<any>(resolve => {
+ rimraf(logPath).then(resolve);
+ });
}
}
mkdirSync(logPath);
}
- export async function create(command: string, args?: readonly string[]): Promise<number> {
- return fileDescriptorFromStream(generate_log_path(command, args));
+ function generateLogPath(command: string, args?: readonly string[]) {
+ return pathFromRoot(`./logs/${command}-${args?.length}-${new Date().toUTCString()}.log`);
}
- function generate_log_path(command: string, args?: readonly string[]) {
- return pathFromRoot(`./logs/${command}-${args?.length}-${new Date().toUTCString()}.log`);
+ export async function create(command: string, args?: readonly string[]): Promise<number> {
+ return fileDescriptorFromStream(generateLogPath(command, args));
}
+}
+export namespace ProcessFactory {
+ export type Sink = 'pipe' | 'ipc' | 'ignore' | 'inherit' | Stream | number | null | undefined;
-} \ No newline at end of file
+ export async function createWorker(command: string, args?: readonly string[], stdio?: StdioOptions | 'logfile', detached = true): Promise<ChildProcess> {
+ if (stdio === 'logfile') {
+ const logFd = await Logger.create(command, args);
+ // eslint-disable-next-line no-param-reassign
+ stdio = ['ignore', logFd, logFd];
+ }
+ const child = spawn(command, args ?? [], { detached, stdio });
+ child.unref();
+ return child;
+ }
+}
diff --git a/src/server/RouteSubscriber.ts b/src/server/RouteSubscriber.ts
index a1cf7c1c4..b923805a8 100644
--- a/src/server/RouteSubscriber.ts
+++ b/src/server/RouteSubscriber.ts
@@ -18,9 +18,8 @@ export default class RouteSubscriber {
public get build() {
let output = this._root;
if (this.requestParameters.length) {
- output = `${output}/:${this.requestParameters.join("/:")}`;
+ output = `${output}/:${this.requestParameters.join('/:')}`;
}
return output;
}
-
-} \ No newline at end of file
+}
diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts
index 7db1c2dae..0a961f570 100644
--- a/src/server/SharedMediaTypes.ts
+++ b/src/server/SharedMediaTypes.ts
@@ -22,24 +22,17 @@ export namespace Upload {
return 'duration' in uploadResponse;
}
+ export interface AccessPathInfo {
+ [suffix: string]: { client: string; server: string };
+ }
export interface FileInformation {
accessPaths: AccessPathInfo;
rawText?: string;
duration?: number;
}
-
- export type FileResponse<T extends FileInformation = FileInformation> = { source: File; result: T | Error };
-
- export type ImageInformation = FileInformation & InspectionResults;
-
- export type VideoInformation = FileInformation & VideoResults;
-
- export interface AccessPathInfo {
- [suffix: string]: { client: string; server: string };
- }
-
- export interface VideoResults {
- duration: number;
+ export interface EnrichedExifData {
+ data: ExifData & ExifData['gps'];
+ error?: string;
}
export interface InspectionResults {
source: string;
@@ -51,9 +44,13 @@ export namespace Upload {
nativeHeight: number;
filename?: string;
}
-
- export interface EnrichedExifData {
- data: ExifData & ExifData['gps'];
- error?: string;
+ export interface VideoResults {
+ duration: number;
}
+
+ export type FileResponse<T extends FileInformation = FileInformation> = { source: File; result: T | Error };
+
+ export type ImageInformation = FileInformation & InspectionResults;
+
+ export type VideoInformation = FileInformation & VideoResults;
}
diff --git a/src/server/apis/google/CredentialsLoader.ts b/src/server/apis/google/CredentialsLoader.ts
index ef1f9a91e..46dc00b8a 100644
--- a/src/server/apis/google/CredentialsLoader.ts
+++ b/src/server/apis/google/CredentialsLoader.ts
@@ -1,10 +1,9 @@
-import { readFile, readFileSync } from "fs";
-import { pathFromRoot } from "../../ActionUtilities";
-import { SecureContextOptions } from "tls";
-import { blue, red } from "colors";
+import { readFile, readFileSync } from 'fs';
+import { SecureContextOptions } from 'tls';
+import { blue, red } from 'colors';
+import { pathFromRoot } from '../../ActionUtilities';
export namespace GoogleCredentialsLoader {
-
export interface InstalledCredentials {
client_id: string;
project_id: string;
@@ -28,18 +27,16 @@ export namespace GoogleCredentialsLoader {
});
});
}
-
}
export namespace SSL {
-
export let Credentials: SecureContextOptions = {};
export let Loaded = false;
const suffixes = {
- privateKey: ".key",
- certificate: ".crt",
- caBundle: "-ca.crt"
+ privateKey: '.key',
+ certificate: '.crt',
+ caBundle: '-ca.crt',
};
export async function loadCredentials() {
@@ -57,11 +54,10 @@ export namespace SSL {
}
export function exit() {
- console.log(red("Running this server in release mode requires the following SSL credentials in the project root:"));
- const serverName = process.env.serverName ? process.env.serverName : "{process.env.serverName}";
+ console.log(red('Running this server in release mode requires the following SSL credentials in the project root:'));
+ const serverName = process.env.serverName ? process.env.serverName : '{process.env.serverName}';
Object.values(suffixes).forEach(suffix => console.log(blue(`${serverName}${suffix}`)));
- console.log(red("Please ensure these files exist and restart, or run this in development mode."));
+ console.log(red('Please ensure these files exist and restart, or run this in development mode.'));
process.exit(0);
}
-
}
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts
index 55e5fd7c0..d3acc968b 100644
--- a/src/server/apis/google/GoogleApiServerUtils.ts
+++ b/src/server/apis/google/GoogleApiServerUtils.ts
@@ -57,12 +57,12 @@ export namespace GoogleApiServerUtils {
* global, intentionally unauthenticated worker OAuth2 client instance.
*/
export function processProjectCredentials(): void {
- const { client_secret, client_id, redirect_uris } = GoogleCredentialsLoader.ProjectCredentials;
+ const { client_secret: clientSecret, client_id: clientId, redirect_uris: redirectUris } = GoogleCredentialsLoader.ProjectCredentials;
// initialize the global authorization client
oAuthOptions = {
- clientId: client_id,
- clientSecret: client_secret,
- redirectUri: redirect_uris[0],
+ clientId,
+ clientSecret,
+ redirectUri: redirectUris[0],
};
worker = generateClient();
}
diff --git a/src/server/apis/google/SharedTypes.ts b/src/server/apis/google/SharedTypes.ts
index 9ad6130b6..f5e0e2e2b 100644
--- a/src/server/apis/google/SharedTypes.ts
+++ b/src/server/apis/google/SharedTypes.ts
@@ -1,9 +1,3 @@
-export interface NewMediaItemResult {
- uploadToken: string;
- status: { code: number, message: string };
- mediaItem: MediaItem;
-}
-
export interface MediaItem {
id: string;
description: string;
@@ -17,5 +11,10 @@ export interface MediaItem {
};
filename: string;
}
+export interface NewMediaItemResult {
+ uploadToken: string;
+ status: { code: number; message: string };
+ mediaItem: MediaItem;
+}
-export type MediaItemCreationResult = { newMediaItemResults: NewMediaItemResult[] }; \ No newline at end of file
+export type MediaItemCreationResult = { newMediaItemResults: NewMediaItemResult[] };
diff --git a/src/server/apis/youtube/youtubeApiSample.d.ts b/src/server/apis/youtube/youtubeApiSample.d.ts
index 427f54608..97c3f0b54 100644
--- a/src/server/apis/youtube/youtubeApiSample.d.ts
+++ b/src/server/apis/youtube/youtubeApiSample.d.ts
@@ -1,2 +1,2 @@
declare const YoutubeApi: any;
-export = YoutubeApi; \ No newline at end of file
+export = YoutubeApi;
diff --git a/src/server/authentication/DashUserModel.ts b/src/server/authentication/DashUserModel.ts
index 3bc21ecb6..a288bfeab 100644
--- a/src/server/authentication/DashUserModel.ts
+++ b/src/server/authentication/DashUserModel.ts
@@ -73,9 +73,9 @@ userSchema.pre('save', function save(next) {
user.password,
salt,
() => {},
- (err: mongoose.Error, hash: string) => {
- if (err) {
- return next(err);
+ (cryptErr: mongoose.Error, hash: string) => {
+ if (cryptErr) {
+ return next(cryptErr);
}
user.password = hash;
next();
@@ -97,7 +97,7 @@ const comparePassword: comparePasswordFunction = function (this: DashUserModel,
userSchema.methods.comparePassword = comparePassword;
-const User = mongoose.model('User', userSchema);
+const User: any = mongoose.model('User', userSchema);
export function initializeGuest() {
new User({
email: 'guest',
diff --git a/src/server/authentication/Passport.ts b/src/server/authentication/Passport.ts
index a9cf6698b..a5222e531 100644
--- a/src/server/authentication/Passport.ts
+++ b/src/server/authentication/Passport.ts
@@ -1,6 +1,6 @@
import * as passport from 'passport';
import * as passportLocal from 'passport-local';
-import { DashUserModel, default as User } from './DashUserModel';
+import User, { DashUserModel } from './DashUserModel';
const LocalStrategy = passportLocal.Strategy;
@@ -11,22 +11,25 @@ passport.serializeUser<any, any>((req, user, done) => {
passport.deserializeUser<any, any>((id, done) => {
User.findById(id)
.exec()
- .then(user => done(undefined, user));
+ .then((user: any) => done(undefined, user));
});
// AUTHENTICATE JUST WITH EMAIL AND PASSWORD
passport.use(
new LocalStrategy({ usernameField: 'email', passReqToCallback: true }, (req, email, password, done) => {
User.findOne({ email: email.toLowerCase() })
- .then(user => {
- if (!user) return done(undefined, false, { message: 'Invalid email or password' }); // invalid email
- (user as any as DashUserModel).comparePassword(password, (error: Error, isMatch: boolean) => {
- if (error) return done(error);
- if (!isMatch) return done(undefined, false, { message: 'Invalid email or password' }); // invalid password
- // valid authentication HERE
- return done(undefined, user);
- });
+ .then((user: any) => {
+ if (!user) {
+ done(undefined, false, { message: 'Invalid email or password' }); // invalid email
+ } else {
+ (user as any as DashUserModel).comparePassword(password, (error: Error, isMatch: boolean) => {
+ if (error) return done(error);
+ if (!isMatch) return done(undefined, false, { message: 'Invalid email or password' }); // invalid password
+ // valid authentication HERE
+ return done(undefined, user);
+ });
+ }
})
- .catch(error => done(error));
+ .catch((error: any) => done(error));
})
);
diff --git a/src/server/database.ts b/src/server/database.ts
index 3a28dc87e..ff5635b2c 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -58,6 +58,7 @@ export namespace Database {
}
}
+ // eslint-disable-next-line @typescript-eslint/no-shadow
export class Database implements IDatabase {
private MongoClient = mongodb.MongoClient;
private currentWrites: { [id: string]: Promise<void> } = {};
diff --git a/src/server/index.ts b/src/server/index.ts
index 5a86f36d9..4374a72b7 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,15 +1,14 @@
import { yellow } from 'colors';
+// eslint-disable-next-line import/no-extraneous-dependencies
import * as dotenv from 'dotenv';
import * as mobileDetect from 'mobile-detect';
import * as path from 'path';
-import * as qs from 'query-string';
import { logExecution } from './ActionUtilities';
import { AdminPrivileges, resolvedPorts } from './SocketData';
import DataVizManager from './ApiManagers/DataVizManager';
import DeleteManager from './ApiManagers/DeleteManager';
import DownloadManager from './ApiManagers/DownloadManager';
import GeneralGoogleManager from './ApiManagers/GeneralGoogleManager';
-//import GooglePhotosManager from './ApiManagers/GooglePhotosManager';
import { SearchManager } from './ApiManagers/SearchManager';
import SessionManager from './ApiManagers/SessionManager';
import UploadManager from './ApiManagers/UploadManager';
@@ -26,8 +25,11 @@ import { Logger } from './ProcessFactory';
import RouteManager, { Method, PublicHandler } from './RouteManager';
import RouteSubscriber from './RouteSubscriber';
import initializeServer from './server_Initialization';
+// import GooglePhotosManager from './ApiManagers/GooglePhotosManager';
+
dotenv.config();
export const onWindows = process.platform === 'win32';
+// eslint-disable-next-line import/no-mutable-exports
export let sessionAgent: AppliedSessionAgent;
/**
@@ -60,8 +62,18 @@ async function preliminaryFunctions() {
* 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 DeleteManager(), new UtilManager(), new GeneralGoogleManager(), /* new GooglePhotosManager(),*/ new DataVizManager()];
+function routeSetter({ addSupervisedRoute, logRegistrationOutcome }: RouteManager) {
+ const managers = [
+ new SessionManager(),
+ new UserManager(),
+ new UploadManager(),
+ new DownloadManager(),
+ new SearchManager(),
+ new DeleteManager(),
+ new UtilManager(),
+ new GeneralGoogleManager(),
+ /* new GooglePhotosManager(), */ new DataVizManager(),
+ ];
// initialize API Managers
console.log(yellow('\nregistering server routes...'));
@@ -102,6 +114,7 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
});
const serve: PublicHandler = ({ req, res }) => {
+ // eslint-disable-next-line new-cap
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));
@@ -116,9 +129,8 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
secureHandler: ({ res, isRelease }) => {
const { PASSWORD } = process.env;
if (!(isRelease && PASSWORD)) {
- return res.redirect('/home');
- }
- res.render('admin.pug', { title: 'Enter Administrator Password' });
+ res.redirect('/home');
+ } else res.render('admin.pug', { title: 'Enter Administrator Password' });
},
});
@@ -128,18 +140,19 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
secureHandler: async ({ req, res, isRelease, user: { id } }) => {
const { PASSWORD } = process.env;
if (!(isRelease && PASSWORD)) {
- return res.redirect('/home');
- }
- const { password } = req.body;
- const { previous_target } = req.params;
- let redirect: string;
- if (password === PASSWORD) {
- AdminPrivileges.set(id, true);
- redirect = `/${previous_target.replace(':', '/')}`;
+ res.redirect('/home');
} else {
- redirect = `/admin/${previous_target}`;
+ const { password } = req.body;
+ const { previous_target: previousTarget } = req.params;
+ let redirect: string;
+ if (password === PASSWORD) {
+ AdminPrivileges.set(id, true);
+ redirect = `/${previousTarget.replace(':', '/')}`;
+ } else {
+ redirect = `/admin/${previousTarget}`;
+ }
+ res.redirect(redirect);
}
- res.redirect(redirect);
},
});
@@ -149,7 +162,6 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
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/');
// 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
diff --git a/src/server/updateProtos.ts b/src/server/updateProtos.ts
index 2f3772a77..72a44ebf4 100644
--- a/src/server/updateProtos.ts
+++ b/src/server/updateProtos.ts
@@ -6,15 +6,15 @@ const protos = ['text', 'image', 'web', 'collection', 'kvp', 'video', 'audio', '
await Promise.all(
protos.map(
protoId =>
- new Promise(res =>
+ new Promise(res => {
Database.Instance.update(
protoId,
{
$set: { 'fields.isBaseProto': true },
},
res
- )
- )
+ );
+ })
)
);
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index cb16bce72..cece8a1b7 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -79,7 +79,7 @@ export namespace WebSocket {
timeMap[userEmail] = Date.now();
socketMap.set(socket, userEmail + ' at ' + datetime);
userOperations.set(userEmail, 0);
- DashStats.logUserLogin(userEmail, socket);
+ DashStats.logUserLogin(userEmail);
}
function getField([id, callback]: [string, (result?: Transferable) => void]) {
@@ -417,38 +417,12 @@ export namespace WebSocket {
socket.in(room).emit('message', message);
});
- socket.on('create or join', room => {
- console.log('Received request to create or join room ' + room);
-
- const clientsInRoom = socket.rooms.has(room);
- const numClients = clientsInRoom ? Object.keys(room.sockets).length : 0;
- console.log('Room ' + room + ' now has ' + numClients + ' client(s)');
-
- if (numClients === 0) {
- 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
- socket.emit('full', room);
- }
- });
-
socket.on('ipaddr', () => {
- const ifaces = networkInterfaces();
- for (const dev in ifaces) {
- ifaces[dev]?.forEach(details => {
- if (details.family === 'IPv4' && details.address !== '127.0.0.1') {
- socket.emit('ipaddr', details.address);
- }
- });
- }
+ networkInterfaces().keys?.forEach(dev => {
+ if (dev.family === 'IPv4' && dev.address !== '127.0.0.1') {
+ socket.emit('ipaddr', dev.address);
+ }
+ });
});
socket.on('bye', () => {
@@ -459,7 +433,7 @@ export namespace WebSocket {
const currentUser = socketMap.get(socket);
if (!(currentUser === undefined)) {
const currentUsername = currentUser.split(' ')[0];
- DashStats.logUserLogout(currentUsername, socket);
+ DashStats.logUserLogout(currentUsername);
delete timeMap[currentUsername];
}
});