diff options
Diffstat (limited to 'src')
20 files changed, 233 insertions, 86 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 12fed3e46..d793b56af 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -272,7 +272,7 @@ export namespace DocServer { const fieldMap: { [id: string]: RefField } = {}; const proms: Promise<void>[] = []; for (const field of fields) { - if (field !== undefined) { + if (field !== undefined && field !== null) { // deserialize const prom = SerializationHelper.Deserialize(field).then(deserialized => { fieldMap[field.id] = deserialized; @@ -439,4 +439,4 @@ export namespace DocServer { function respondToDelete(ids: string | string[]) { _respondToDelete(ids); } -}
\ No newline at end of file +} diff --git a/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts b/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts index 3e9145a1b..6b36ffc9e 100644 --- a/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts +++ b/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts @@ -3,7 +3,7 @@ import { AttributeTransformationModel } from "../../northstar/core/attribute/Att import { ChartType } from '../../northstar/model/binRanges/VisualBinRange'; import { AggregateFunction, Bin, Brush, DoubleValueAggregateResult, HistogramResult, MarginAggregateParameters, MarginAggregateResult } from "../../northstar/model/idea/idea"; import { ModelHelpers } from "../../northstar/model/ModelHelpers"; -import { LABColor } from '../../northstar/utils/LABcolor'; +import { LABColor } from '../../northstar/utils/LABColor'; import { PIXIRectangle } from "../../northstar/utils/MathUtil"; import { StyleConstants } from "../../northstar/utils/StyleContants"; import { HistogramBox } from "./HistogramBox"; @@ -237,4 +237,4 @@ export class HistogramBinPrimitiveCollection { // } return StyleConstants.HIGHLIGHT_COLOR; } -}
\ No newline at end of file +} diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx index 5a16b3782..66d91cc1d 100644 --- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx +++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx @@ -5,7 +5,7 @@ import { Utils as DashUtils, emptyFunction } from '../../../Utils'; import { FilterModel } from "../../northstar/core/filter/FilterModel"; import { ModelHelpers } from "../../northstar/model/ModelHelpers"; import { ArrayUtil } from "../../northstar/utils/ArrayUtil"; -import { LABColor } from '../../northstar/utils/LABcolor'; +import { LABColor } from '../../northstar/utils/LABColor'; import { PIXIRectangle } from "../../northstar/utils/MathUtil"; import { StyleConstants } from "../../northstar/utils/StyleContants"; import { HistogramBinPrimitiveCollection, HistogramBinPrimitive } from "./HistogramBinPrimitiveCollection"; @@ -119,4 +119,4 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP </svg> </div>; } -}
\ No newline at end of file +} diff --git a/src/client/util/ProseMirrorEditorView.tsx b/src/client/util/ProseMirrorEditorView.tsx index 3e5fd0604..b42adfbb4 100644 --- a/src/client/util/ProseMirrorEditorView.tsx +++ b/src/client/util/ProseMirrorEditorView.tsx @@ -18,23 +18,23 @@ export class ProseMirrorEditorView extends React.Component<ProseMirrorEditorView private _editorView?: EditorView; _createEditorView = (element: HTMLDivElement | null) => { - if (element != null) { + if (element !== null) { this._editorView = new EditorView(element, { state: this.props.editorState, dispatchTransaction: this.dispatchTransaction, }); } - }; + } dispatchTransaction = (tx: any) => { // In case EditorView makes any modification to a state we funnel those // modifications up to the parent and apply to the EditorView itself. const editorState = this.props.editorState.apply(tx); - if (this._editorView != null) { + if (this._editorView) { this._editorView.updateState(editorState); } this.props.onEditorState(editorState); - }; + } focus() { if (this._editorView) { diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx index 639772faa..419d7caf9 100644 --- a/src/client/util/RichTextMenu.tsx +++ b/src/client/util/RichTextMenu.tsx @@ -175,7 +175,7 @@ export default class RichTextMenu extends AntimodeMenu { setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => { if (mark) { const node = (state.selection as NodeSelection).node; - if (node ?.type === schema.nodes.ordered_list) { + if (node?.type === schema.nodes.ordered_list) { let attrs = node.attrs; if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family }; if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize }; @@ -294,8 +294,8 @@ export default class RichTextMenu extends AntimodeMenu { e.preventDefault(); e.stopPropagation(); self.view && self.view.focus(); - self.view && command && command(self.view!.state, self.view!.dispatch, self.view); - self.view && onclick && onclick(self.view!.state, self.view!.dispatch, self.view); + self.view && command && command(self.view.state, self.view.dispatch, self.view); + self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view); self.setActiveMarkButtons(self.getActiveMarksOnSelection()); } @@ -602,7 +602,7 @@ export default class RichTextMenu extends AntimodeMenu { const link = this.currentLink ? this.currentLink : ""; - const button = <FontAwesomeIcon icon="link" size="lg" /> + const button = <FontAwesomeIcon icon="link" size="lg" />; const dropdownContent = <div className="dropdown link-menu"> @@ -684,8 +684,9 @@ export default class RichTextMenu extends AntimodeMenu { } } else { if (node) { - const extension = this.linkExtend(this.view!.state.selection.$anchor, href); - this.view!.dispatch(this.view!.state.tr.removeMark(extension.from, extension.to, this.view!.state.schema.marks.link)); + const { tr, schema, selection } = this.view.state; + const extension = this.linkExtend(selection.$anchor, href); + this.view.dispatch(tr.removeMark(extension.from, extension.to, schema.marks.link)); } } } diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 54def38b5..faf02b946 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -36,7 +36,7 @@ export interface EditableProps { resetValue: () => void; value: string, onChange: (e: React.ChangeEvent, { newValue }: { newValue: string }) => void, - autosuggestProps: Autosuggest.AutosuggestProps<string> + autosuggestProps: Autosuggest.AutosuggestProps<string, any> }; oneLine?: boolean; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 936c4413f..e3780261d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -988,8 +988,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { </MarqueeView>; } @computed get contentScaling() { - let hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1; - let wscale = this.nativeWidth ? this.props.PanelWidth() / this.nativeWidth : 1; + const hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1; + const wscale = this.nativeWidth ? this.props.PanelWidth() / this.nativeWidth : 1; return wscale < hscale ? wscale : hscale; } render() { diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index abd17ec4d..ace9a9e4c 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -47,7 +47,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> { document.removeEventListener("pointerup", this.onLinkButtonUp); const targets = this.props.group.map(l => LinkManager.Instance.getOppositeAnchor(l, this.props.sourceDoc)).filter(d => d) as Doc[]; - DragManager.StartLinkTargetsDrag(this._drag.current!, e.x, e.y, this.props.sourceDoc, targets); + DragManager.StartLinkTargetsDrag(this._drag.current, e.x, e.y, this.props.sourceDoc, targets); } e.stopPropagation(); } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 2e598a14d..f79496ab7 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,4 +1,4 @@ -import { random } from "animejs"; +import anime from "animejs"; import { computed, IReactionDisposer, observable, reaction, trace } from "mobx"; import { observer } from "mobx-react"; import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc"; @@ -26,7 +26,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) { _disposer: IReactionDisposer | undefined = undefined; get displayName() { return "CollectionFreeFormDocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive - get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; } + get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${anime.random(-1, 1) * this.props.jitterRotation}deg)`; } get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); } get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); } get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.dataProvider && this.dataProvider ? this.dataProvider.width : this.layoutDoc[WidthSym](); } @@ -124,4 +124,4 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF /> */} </div>; } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 10d2e2b3e..ba35366ff 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -196,7 +196,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY); } else if (this.props.Document.isButton === "Selector") { // this should be moved to an OnClick script FormattedTextBoxComment.Hide(); - this.Document.links?.[0] instanceof Doc && (Doc.UserDoc().SelectedDocs = new List([Doc.LinkOtherAnchor(this.Document.links[0]!, this.props.Document)])); + this.Document.links?.[0] instanceof Doc && (Doc.UserDoc().SelectedDocs = new List([Doc.LinkOtherAnchor(this.Document.links[0], this.props.Document)])); } else if (this.Document.isButton) { SelectionManager.SelectDoc(this, e.ctrlKey); // don't think this should happen if a button action is actually triggered. this.buttonClick(e.altKey, e.ctrlKey); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 8e28cf928..8b5c24878 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -906,15 +906,16 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & this.tryUpdateHeight(); // see if we need to preserve the insertion point - const prosediv = this.ProseRef ?.children ?.[0] as any; - const keeplocation = prosediv ?.keeplocation; + const prosediv = this.ProseRef?.children?.[0] as any; + const keeplocation = prosediv?.keeplocation; prosediv && (prosediv.keeplocation = undefined); - const pos = this._editorView ?.state.selection.$from.pos || 1; - keeplocation && setTimeout(() => this._editorView ?.dispatch(this._editorView ?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos)))); + const pos = this._editorView?.state.selection.$from.pos || 1; + keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos)))); // jump rich text menu to this textbox - if (this._ref.current) { - const x = Math.min(Math.max(this._ref.current!.getBoundingClientRect().left, 0), window.innerWidth - RichTextMenu.Instance.width); + const { current } = this._ref; + if (current) { + const x = Math.min(Math.max(current.getBoundingClientRect().left, 0), window.innerWidth - RichTextMenu.Instance.width); const y = this._ref.current!.getBoundingClientRect().top - RichTextMenu.Instance.height - 50; RichTextMenu.Instance.jumpTo(x, y); } @@ -933,7 +934,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text. const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text) - if (pcords && node ?.type === this._editorView!.state.schema.nodes.dashComment) { + if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) { this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2))); e.preventDefault(); } @@ -996,7 +997,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & for (let off = 1; off < 100; off++) { const pos = this._editorView!.posAtCoords({ left: x + off, top: y }); const node = pos && this._editorView!.state.doc.nodeAt(pos.pos); - if (node ?.type === schema.nodes.list_item) { + if (node?.type === schema.nodes.list_item) { list_node = node; break; } @@ -1087,7 +1088,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } if (e.key === "Escape") { this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); - (document.activeElement as any).blur ?.(); + (document.activeElement as any).blur?.(); SelectionManager.DeselectAll(); } e.stopPropagation(); @@ -1109,7 +1110,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & @action tryUpdateHeight(limitHeight?: number) { - let scrollHeight = this._ref.current ?.scrollHeight; + let scrollHeight = this._ref.current?.scrollHeight; if (!this.layoutDoc.animateToPos && this.layoutDoc.autoHeight && scrollHeight && getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation if (limitHeight && scrollHeight > limitHeight) { @@ -1171,7 +1172,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & {this.props.Document.hideSidebar ? (null) : this.sidebarWidthPercent === "0%" ? <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> : <div className={"formattedTextBox-sidebar" + (InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : "")} - style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${StrCast(this.extensionDoc ?.backgroundColor, "transparent")}` }}> + style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${StrCast(this.extensionDoc?.backgroundColor, "transparent")}` }}> <CollectionFreeFormView {...this.props} PanelHeight={this.props.PanelHeight} PanelWidth={() => this.sidebarWidth} diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts index 872c524d6..ef5ca38c6 100644 --- a/src/pen-gestures/ndollar.ts +++ b/src/pen-gestures/ndollar.ts @@ -257,16 +257,16 @@ export class NDollarRecognizer { { if (!requireSameNoOfStrokes || strokes.length === this.Multistrokes[i].NumStrokes) // optional -- only attempt match when same # of component strokes { - for (var j = 0; j < this.Multistrokes[i].Unistrokes.length; j++) // for each unistroke within this multistroke + for (const unistroke of this.Multistrokes[i].Unistrokes) // for each unistroke within this multistroke { - if (AngleBetweenUnitVectors(candidate.StartUnitVector, this.Multistrokes[i].Unistrokes[j].StartUnitVector) <= AngleSimilarityThreshold) // strokes start in the same direction + if (AngleBetweenUnitVectors(candidate.StartUnitVector, unistroke.StartUnitVector) <= AngleSimilarityThreshold) // strokes start in the same direction { var d; if (useProtractor) { - d = OptimalCosineDistance(this.Multistrokes[i].Unistrokes[j].Vector, candidate.Vector); // Protractor + d = OptimalCosineDistance(unistroke.Vector, candidate.Vector); // Protractor } else { - d = DistanceAtBestAngle(candidate.Points, this.Multistrokes[i].Unistrokes[j], -AngleRange, +AngleRange, AnglePrecision); // Golden Section Search (original $N) + d = DistanceAtBestAngle(candidate.Points, unistroke, -AngleRange, +AngleRange, AnglePrecision); // Golden Section Search (original $N) } if (d < b) { b = d; // best (least) distance @@ -283,8 +283,8 @@ export class NDollarRecognizer { AddGesture = (name: string, useBoundedRotationInvariance: boolean, strokes: any[]) => { this.Multistrokes[this.Multistrokes.length] = new Multistroke(name, useBoundedRotationInvariance, strokes); var num = 0; - for (var i = 0; i < this.Multistrokes.length; i++) { - if (this.Multistrokes[i].Name === name) { + for (const multistroke of this.Multistrokes) { + if (multistroke.Name === name) { num++; } } @@ -322,20 +322,20 @@ function HeapPermute(n: number, order: any[], /*out*/ orders: any[]) { function MakeUnistrokes(strokes: any, orders: any) { const unistrokes = new Array(); // array of point arrays - for (var r = 0; r < orders.length; r++) { - for (var b = 0; b < Math.pow(2, orders[r].length); b++) // use b's bits for directions + for (const order of orders) { + for (var b = 0; b < Math.pow(2, order.length); b++) // use b's bits for directions { const unistroke = new Array(); // array of points - for (var i = 0; i < orders[r].length; i++) { + for (var i = 0; i < order.length; i++) { var pts; if (((b >> i) & 1) === 1) {// is b's bit at index i on? - pts = strokes[orders[r][i]].slice().reverse(); // copy and reverse + pts = strokes[order[i]].slice().reverse(); // copy and reverse } else { - pts = strokes[orders[r][i]].slice(); // copy + pts = strokes[order[i]].slice(); // copy } - for (var p = 0; p < pts.length; p++) { - unistroke[unistroke.length] = pts[p]; // append points + for (const point of pts) { + unistroke[unistroke.length] = point; // append points } } unistrokes[unistrokes.length] = unistroke; // add one unistroke to set @@ -346,9 +346,9 @@ function MakeUnistrokes(strokes: any, orders: any) { function CombineStrokes(strokes: any) { const points = new Array(); - for (var s = 0; s < strokes.length; s++) { - for (var p = 0; p < strokes[s].length; p++) { - points[points.length] = new Point(strokes[s][p].X, strokes[s][p].Y); + for (const stroke of strokes) { + for (const { X, Y } of stroke) { + points[points.length] = new Point(X, Y); } } return points; @@ -384,9 +384,9 @@ function RotateBy(points: any, radians: any) // rotates points around centroid const cos = Math.cos(radians); const sin = Math.sin(radians); const newpoints = new Array(); - for (var i = 0; i < points.length; i++) { - const qx = (points[i].X - c.X) * cos - (points[i].Y - c.Y) * sin + c.X; - const qy = (points[i].X - c.X) * sin + (points[i].Y - c.Y) * cos + c.Y; + for (const point of points) { + const qx = (point.X - c.X) * cos - (point.Y - c.Y) * sin + c.X; + const qy = (point.X - c.X) * sin + (point.Y - c.Y) * cos + c.Y; newpoints[newpoints.length] = new Point(qx, qy); } return newpoints; @@ -396,9 +396,9 @@ function ScaleDimTo(points: any, size: any, ratio1D: any) // scales bbox uniform const B = BoundingBox(points); const uniformly = Math.min(B.Width / B.Height, B.Height / B.Width) <= ratio1D; // 1D or 2D gesture test const newpoints = new Array(); - for (var i = 0; i < points.length; i++) { - const qx = uniformly ? points[i].X * (size / Math.max(B.Width, B.Height)) : points[i].X * (size / B.Width); - const qy = uniformly ? points[i].Y * (size / Math.max(B.Width, B.Height)) : points[i].Y * (size / B.Height); + for (const { X, Y } of points) { + const qx = uniformly ? X * (size / Math.max(B.Width, B.Height)) : X * (size / B.Width); + const qy = uniformly ? Y * (size / Math.max(B.Width, B.Height)) : Y * (size / B.Height); newpoints[newpoints.length] = new Point(qx, qy); } return newpoints; @@ -407,9 +407,9 @@ function TranslateTo(points: any, pt: any) // translates points' centroid { const c = Centroid(points); const newpoints = new Array(); - for (var i = 0; i < points.length; i++) { - const qx = points[i].X + pt.X - c.X; - const qy = points[i].Y + pt.Y - c.Y; + for (const { X, Y } of points) { + const qx = X + pt.X - c.X; + const qy = Y + pt.Y - c.Y; newpoints[newpoints.length] = new Point(qx, qy); } return newpoints; @@ -478,9 +478,9 @@ function DistanceAtAngle(points: any, T: any, radians: any) { } function Centroid(points: any) { var x = 0.0, y = 0.0; - for (var i = 0; i < points.length; i++) { - x += points[i].X; - y += points[i].Y; + for (const point of points) { + x += point.X; + y += point.Y; } x /= points.length; y /= points.length; @@ -488,11 +488,11 @@ function Centroid(points: any) { } function BoundingBox(points: any) { var minX = +Infinity, maxX = -Infinity, minY = +Infinity, maxY = -Infinity; - for (var i = 0; i < points.length; i++) { - minX = Math.min(minX, points[i].X); - minY = Math.min(minY, points[i].Y); - maxX = Math.max(maxX, points[i].X); - maxY = Math.max(maxY, points[i].Y); + for (const { X, Y } of points) { + minX = Math.min(minX, X); + minY = Math.min(minY, Y); + maxX = Math.max(maxX, X); + maxY = Math.max(maxY, Y); } return new Rectangle(minX, minY, maxX - minX, maxY - minY); } diff --git a/src/server/IDatabase.ts b/src/server/IDatabase.ts new file mode 100644 index 000000000..6a63df485 --- /dev/null +++ b/src/server/IDatabase.ts @@ -0,0 +1,24 @@ +import * as mongodb from 'mongodb'; +import { Transferable } from './Message'; + +export const DocumentsCollection = 'documents'; +export const NewDocumentsCollection = 'newDocuments'; +export interface IDatabase { + update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert?: boolean, collectionName?: string): Promise<void>; + updateMany(query: any, update: any, collectionName?: string): Promise<mongodb.WriteOpResult>; + + replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert?: boolean, collectionName?: string): void; + + delete(query: any, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>; + delete(id: string, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>; + + deleteAll(collectionName?: string, persist?: boolean): Promise<any>; + + insert(value: any, collectionName?: string): Promise<void>; + + getDocument(id: string, fn: (result?: Transferable) => void, collectionName?: string): void; + getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName?: string): void; + visit(ids: string[], fn: (result: any) => string[] | Promise<string[]>, collectionName?: string): Promise<void>; + + query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName?: string): Promise<mongodb.Cursor>; +} diff --git a/src/server/MemoryDatabase.ts b/src/server/MemoryDatabase.ts new file mode 100644 index 000000000..543f96e7f --- /dev/null +++ b/src/server/MemoryDatabase.ts @@ -0,0 +1,100 @@ +import { IDatabase, DocumentsCollection, NewDocumentsCollection } from './IDatabase'; +import { Transferable } from './Message'; +import * as mongodb from 'mongodb'; + +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] = {}; + } + } + + public update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, _upsert?: boolean, collectionName = DocumentsCollection): Promise<void> { + const collection = this.getCollection(collectionName); + 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("."); + for (let i = 0; i < keys.length - 1; i++) { + const k = keys[i]; + if (typeof currentVal[k] === "object") { + currentVal = currentVal[k]; + } else { + currentVal[k] = {}; + currentVal = currentVal[k]; + } + } + currentVal[keys[keys.length - 1]] = val[key]; + } + } else { + collection[id] = value; + } + callback(null as any, {} as any); + return Promise.resolve(undefined); + } + + public updateMany(query: any, update: any, collectionName = NewDocumentsCollection): Promise<mongodb.WriteOpResult> { + throw new Error("Can't updateMany a MemoryDatabase"); + } + + public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert?: boolean, collectionName = DocumentsCollection): void { + this.update(id, value, callback, upsert, collectionName); + } + + public delete(query: any, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>; + public delete(id: string, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>; + public delete(id: any, collectionName = DocumentsCollection): Promise<mongodb.DeleteWriteOpResultObject> { + const i = id.id ?? id; + delete this.getCollection(collectionName)[i]; + + return Promise.resolve({} as any); + } + + public deleteAll(collectionName = DocumentsCollection, _persist = true): Promise<any> { + delete this.db[collectionName]; + return Promise.resolve(); + } + + public insert(value: any, collectionName = DocumentsCollection): Promise<void> { + const id = value.id; + this.getCollection(collectionName)[id] = value; + return Promise.resolve(); + } + + public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = NewDocumentsCollection): void { + fn(this.getCollection(collectionName)[id]); + } + public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = DocumentsCollection): void { + fn(ids.map(id => this.getCollection(collectionName)[id])); + } + + public async visit(ids: string[], fn: (result: any) => string[] | Promise<string[]>, collectionName = NewDocumentsCollection): Promise<void> { + const visited = new Set<string>(); + while (ids.length) { + 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))); + } + } + } + + public query(): Promise<mongodb.Cursor> { + throw new Error("Can't query a MemoryDatabase"); + } +} diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts index b146d7b72..5afd607fd 100644 --- a/src/server/RouteManager.ts +++ b/src/server/RouteManager.ts @@ -86,7 +86,11 @@ export default class RouteManager { const { method, subscription, secureHandler: onValidation, publicHandler: onUnauthenticated, errorHandler: onError } = initializer; const isRelease = this._isRelease; const supervised = async (req: express.Request, res: express.Response) => { - const { user, originalUrl: target } = req; + let { user } = req; + const { originalUrl: target } = req; + if (process.env.DB === "MEM" && !user) { + user = { id: "guest", email: "", userDocumentId: "guestDocId" }; + } const core = { req, res, isRelease }; const tryExecute = async (toExecute: (args: any) => any | Promise<any>, args: any) => { try { diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts index 578147d60..6dda6956e 100644 --- a/src/server/Websocket/Websocket.ts +++ b/src/server/Websocket/Websocket.ts @@ -28,7 +28,7 @@ export namespace WebSocket { function initialize(isRelease: boolean) { const endpoint = io(); - endpoint.on("connection", function (socket: Socket) { + endpoint.on("connection", function(socket: Socket) { _socket = socket; socket.use((_packet, next) => { @@ -83,7 +83,9 @@ export namespace WebSocket { export async function deleteFields() { await Database.Instance.deleteAll(); - await Search.clear(); + if (process.env.DISABLE_SEARCH !== "true") { + await Search.clear(); + } await Database.Instance.deleteAll('newDocuments'); } @@ -92,7 +94,9 @@ export namespace WebSocket { await Database.Instance.deleteAll('newDocuments'); await Database.Instance.deleteAll('sessions'); await Database.Instance.deleteAll('users'); - await Search.clear(); + if (process.env.DISABLE_SEARCH !== "true") { + await Search.clear(); + } } function barReceived(socket: SocketIO.Socket, userEmail: string) { diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 220c37e2b..36d4cd2f2 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -322,4 +322,4 @@ export class CurrentUserUtils { }; return recurs([] as Attribute[], schema ? schema.rootAttributeGroup : undefined); } -}
\ No newline at end of file +} diff --git a/src/server/database.ts b/src/server/database.ts index 6e0771c11..83ce865c6 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -5,6 +5,8 @@ import { Utils, emptyFunction } from '../Utils'; import { DashUploadUtils } from './DashUploadUtils'; import { Credentials } from 'google-auth-library'; import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils'; +import { IDatabase } from './IDatabase'; +import { MemoryDatabase } from './MemoryDatabase'; import * as mongoose from 'mongoose'; export namespace Database { @@ -44,7 +46,7 @@ export namespace Database { } } - class Database { + class Database implements IDatabase { public static DocumentsCollection = 'documents'; private MongoClient = mongodb.MongoClient; private currentWrites: { [id: string]: Promise<void> } = {}; @@ -215,7 +217,7 @@ export namespace Database { if (!fetchIds.length) { continue; } - const docs = await new Promise<{ [key: string]: any }[]>(res => Instance.getDocuments(fetchIds, res, "newDocuments")); + 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); @@ -262,7 +264,16 @@ export namespace Database { } } - export const Instance = new Database(); + function getDatabase() { + switch (process.env.DB) { + case "MEM": + return new MemoryDatabase(); + default: + return new Database(); + } + } + + export const Instance: IDatabase = getDatabase(); export namespace Auxiliary { @@ -331,4 +342,4 @@ export namespace Database { } -}
\ No newline at end of file +} diff --git a/src/server/index.ts b/src/server/index.ts index 058935bac..313a2f0e2 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -5,7 +5,7 @@ import * as path from 'path'; import { Database } from './database'; import { DashUploadUtils } from './DashUploadUtils'; import RouteSubscriber from './RouteSubscriber'; -import initializeServer from './server_initialization'; +import initializeServer from './server_Initialization'; import RouteManager, { Method, _success, _permission_denied, _error, _invalid, PublicHandler } from './RouteManager'; import * as qs from 'query-string'; import UtilManager from './ApiManagers/UtilManager'; @@ -41,11 +41,13 @@ async function preliminaryFunctions() { await GoogleCredentialsLoader.loadCredentials(); GoogleApiServerUtils.processProjectCredentials(); await DashUploadUtils.buildFileDirectories(); - await log_execution({ - startMessage: "attempting to initialize mongodb connection", - endMessage: "connection outcome determined", - action: Database.tryInitializeConnection - }); + if (process.env.DB !== "MEM") { + await log_execution({ + startMessage: "attempting to initialize mongodb connection", + endMessage: "connection outcome determined", + action: Database.tryInitializeConnection + }); + } } /** @@ -142,4 +144,4 @@ if (process.env.RELEASE) { (sessionAgent = new DashSessionAgent()).launch(); } else { launchServer(); -}
\ No newline at end of file +} diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts index cbe070293..9f67c1dda 100644 --- a/src/server/server_Initialization.ts +++ b/src/server/server_Initialization.ts @@ -86,7 +86,7 @@ function buildWithMiddleware(server: express.Express) { resave: true, cookie: { maxAge: week }, saveUninitialized: true, - store: new MongoStore({ url: Database.url }) + store: process.env.DB === "MEM" ? new session.MemoryStore() : new MongoStore({ url: Database.url }) }), flash(), expressFlash(), @@ -152,4 +152,4 @@ function registerCorsProxy(server: express.Express) { }); }).pipe(res); }); -}
\ No newline at end of file +} |