aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ServerUtils.ts10
-rw-r--r--src/Utils.ts16
-rw-r--r--src/client/views/DocComponent.tsx4
-rw-r--r--src/client/views/StyleProvider.tsx3
-rw-r--r--src/client/views/collections/TabDocView.tsx10
-rw-r--r--src/client/views/collections/TreeView.tsx6
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx3
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx38
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx98
-rw-r--r--src/client/views/nodes/trails/SlideEffect.tsx2
-rw-r--r--src/fields/ObjectField.ts5
-rw-r--r--src/fields/RichTextUtils.ts56
-rw-r--r--src/fields/util.ts4
-rw-r--r--src/server/database.ts2
-rw-r--r--src/server/websocket.ts248
15 files changed, 201 insertions, 304 deletions
diff --git a/src/ServerUtils.ts b/src/ServerUtils.ts
index 7e821cda2..904541fc7 100644
--- a/src/ServerUtils.ts
+++ b/src/ServerUtils.ts
@@ -9,20 +9,20 @@ export namespace ServerUtils {
socket.emit(message.Message, args);
}
- export function AddServerHandler<T>(socket: Socket, message: Message<T>, handler: (args: T) => any) {
+ export function AddServerHandler<T>(socket: Socket, message: Message<T>, handler: (args: T) => void) {
socket.on(message.Message, Utils.loggingCallback('Incoming', handler, message.Name));
}
- export function AddServerHandlerCallback<T>(socket: Socket, message: Message<T>, handler: (args: [T, (res: any) => any]) => any) {
- socket.on(message.Message, (arg: T, fn: (res: any) => any) => {
+ export function AddServerHandlerCallback<T>(socket: Socket, message: Message<T>, handler: (args: [T, (res: unknown) => void]) => void) {
+ socket.on(message.Message, (arg: T, fn: (res: unknown) => void) => {
Utils.log('S receiving', message.Name, arg, true);
handler([arg, Utils.loggingCallback('S sending', fn, message.Name)]);
});
}
- export type RoomHandler = (socket: Socket, room: string) => any;
+ export type RoomHandler = (socket: Socket, room: string) => void;
export type UsedSockets = Socket;
export type RoomMessage = 'create or join' | 'created' | 'joined';
export function AddRoomHandler(socket: Socket, message: RoomMessage, handler: RoomHandler) {
- socket.on(message, (room: any) => handler(socket, room));
+ socket.on(message, room => handler(socket, room));
}
}
diff --git a/src/Utils.ts b/src/Utils.ts
index 23ae38bdb..0590c6930 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-namespace */
import * as uuid from 'uuid';
export function clamp(n: number, lower: number, upper: number) {
@@ -23,6 +24,7 @@ export namespace Utils {
export const loggingEnabled = false;
export const logFilter: number | undefined = undefined;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
export function log(prefixIn: string, messageName: string, messageIn: any, receiving: boolean) {
let prefix = prefixIn;
let message = messageIn;
@@ -38,8 +40,9 @@ export namespace Utils {
console.log(`${prefix}: ${idString}, ${receiving ? 'receiving' : 'sending'} ${messageName} with data ${JSON.stringify(message)} `);
}
- export function loggingCallback(prefix: string, func: (args: any) => any, messageName: string) {
- return (args: any) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ export function loggingCallback(prefix: string, func: (args: any) => void, messageName: string) {
+ return (args: unknown) => {
log(prefix, messageName, args, true);
func(args);
};
@@ -47,7 +50,9 @@ export namespace Utils {
export function TraceConsoleLog() {
['log', 'warn'].forEach(method => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
const old = (console as any)[method];
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
(console as any)[method] = function (...args: any[]) {
let stack = new Error('').stack?.split(/\n/);
// Chrome includes a single "Error" line, FF doesn't.
@@ -158,7 +163,7 @@ export function timenow() {
const now = new Date();
let ampm = 'am';
let h = now.getHours();
- let m: any = now.getMinutes();
+ let m: string | number = now.getMinutes();
if (h >= 12) {
if (h > 12) h -= 12;
ampm = 'pm';
@@ -201,7 +206,7 @@ export function intersectRect(r1: { left: number; top: number; width: number; he
export function stringHash(s?: string) {
// eslint-disable-next-line no-bitwise
- return !s ? undefined : Math.abs(s.split('').reduce((a: any, b: any) => (n => n & n)((a << 5) - a + b.charCodeAt(0)), 0));
+ return !s ? undefined : Math.abs(s.split('').reduce((a, b) => (n => n & n)((a << 5) - a + b.charCodeAt(0)), 0));
}
export function percent2frac(percent: string) {
@@ -224,8 +229,6 @@ export function emptyFunction() {
return undefined;
}
-export const emptyPath: any[] = [];
-
export function unimplementedFunction() {
throw new Error('This function is not implemented, but should be.');
}
@@ -246,6 +249,7 @@ export function DeepCopy<K, V>(source: Map<K, V>, predicate?: Predicate<K, V>) {
export namespace JSONUtils {
export function tryParse(source: string) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
let results: any;
try {
results = JSON.parse(source);
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index e5752dcd2..e351e2dec 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -93,7 +93,7 @@ export function ViewBoxBaseComponent<P extends FieldViewProps>() {
* This is the unique data repository for a dcoument that stores the intrinsic document data
*/
@computed get dataDoc() {
- return this.Document.isTemplateForField || this.Document.isTemplateDoc ? this._props.TemplateDataDocument ?? this.Document[DocData] : this.Document[DocData];
+ return this.Document.isTemplateForField || this.Document.isTemplateDoc ? (this._props.TemplateDataDocument ?? this.Document[DocData]) : this.Document[DocData];
}
/**
@@ -151,7 +151,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
* This is the unique data repository for a dcoument that stores the intrinsic document data
*/
@computed get dataDoc() {
- return this.Document.isTemplateForField || this.Document.isTemplateDoc ? this._props.TemplateDataDocument ?? this.Document[DocData] : this.Document[DocData];
+ return this.Document.isTemplateForField || this.Document.isTemplateDoc ? (this._props.TemplateDataDocument ?? this.Document[DocData]) : this.Document[DocData];
}
/**
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 2792955a0..374f8ca3a 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -13,7 +13,6 @@ import { Id } from '../../fields/FieldSymbols';
import { ScriptField } from '../../fields/ScriptField';
import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
import { AudioAnnoState } from '../../server/SharedMediaTypes';
-import { emptyPath } from '../../Utils';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { IsFollowLinkScript } from '../documents/DocUtils';
import { SnappingManager } from '../util/SnappingManager';
@@ -406,5 +405,5 @@ export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>
}
export function returnEmptyDocViewList() {
- return emptyPath;
+ return [] as DocumentView[];
}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index f168db81b..31b6be927 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -7,7 +7,7 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import ResizeObserver from 'resize-observer-polyfill';
-import { ClientUtils, DashColor, lightOrDark, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils';
+import { ClientUtils, DashColor, lightOrDark, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils';
import { emptyFunction } from '../../../Utils';
import { Doc, Opt, returnEmptyDoclist } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
@@ -157,8 +157,8 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
addDocTab={this._props.addDocTab}
// eslint-disable-next-line no-use-before-define
pinToPres={TabDocView.PinDoc}
- childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDocViewList}
- childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDocViewList}
+ childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyFilter}
+ childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyFilter}
searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
fitContentsToBox={returnTrue}
xPadding={this.xPadding}
@@ -617,8 +617,8 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
PanelWidth={this.PanelWidth}
PanelHeight={this.PanelHeight}
styleProvider={DefaultStyleProvider}
- childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDocViewList}
- childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDocViewList}
+ childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyFilter}
+ childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyFilter}
searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
addDocument={undefined}
removeDocument={this.remDocTab}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index c0fe7a894..b10a521ca 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -6,7 +6,7 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { ClientUtils, lightOrDark, return18, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils';
import { emptyFunction } from '../../../Utils';
-import { Doc, DocListCast, Field, FieldResult, FieldType, Opt, StrListCast, returnEmptyDoclist } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, Opt, StrListCast, returnEmptyDoclist } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
@@ -249,7 +249,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
return [];
}
- const runningChildren: FieldResult[] = [];
+ const runningChildren: Doc[] = [];
childList.forEach(child => {
if (child.runProcess && TreeView.GetRunningChildren.get(child)) {
if (child.runProcess) {
@@ -261,7 +261,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
return runningChildren;
};
- static GetRunningChildren = new Map<Doc, () => FieldResult[]>();
+ static GetRunningChildren = new Map<Doc, () => Doc[]>();
static ToggleChildrenRun = new Map<Doc, () => void>();
constructor(props: TreeViewProps) {
super(props);
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index b178d6554..15baebae1 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -192,6 +192,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
return { bindings, layoutFrame };
}
+ blacklistedAttrs = [];
render() {
TraceMobx();
const { bindings, layoutFrame } = this.renderData;
@@ -199,7 +200,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
return this._props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate ? null : (
<ObserverJsxParser
key={42}
- blacklistedAttrs={emptyPath}
+ blacklistedAttrs={this.blacklistedAttrs}
renderInWrapper={false}
components={DocumentContentsView.Components}
bindings={bindings}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 478039ffa..e21902fdd 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -59,7 +59,7 @@ import { LinkInfo } from '../LinkDocPreview';
import { OpenWhere } from '../OpenWhere';
import './FormattedTextBox.scss';
import { findLinkMark, FormattedTextBoxComment } from './FormattedTextBoxComment';
-import { buildKeymap, KeyMap, updateBullets } from './ProsemirrorExampleTransfer';
+import { buildKeymap, updateBullets } from './ProsemirrorExampleTransfer';
import { removeMarkWithAttrs } from './prosemirrorPatches';
import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
import { RichTextRules } from './RichTextRules';
@@ -75,6 +75,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
public static LayoutString(fieldStr: string) {
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
+ public static MakeConfig(rules?: RichTextRules, props?: FormattedTextBoxProps) {
+ const keymapping = buildKeymap(schema, props ?? {});
+ return {
+ schema,
+ plugins: [
+ inputRules(rules?.inpRules ?? { rules: [] }),
+ ...(props ? [FormattedTextBox.richTextMenuPlugin(props)] : []),
+ history(),
+ keymap(keymapping),
+ keymap(baseKeymap),
+ new Plugin({ props: { attributes: { class: 'ProseMirror-example-setup-style' } } }),
+ new Plugin({ view: () => new FormattedTextBoxComment() }),
+ ],
+ };
+ }
private static nodeViews: (self: FormattedTextBox) => { [key: string]: NodeViewConstructor };
/**
* Initialize the class with all the plugin node view components
@@ -108,7 +123,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
private _recordingStart: number = 0;
private _ignoreScroll = false;
private _focusSpeed: Opt<number>;
- private _keymap: KeyMap | undefined = undefined;
private _rules: RichTextRules | undefined;
private _forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason. This flags the 2nd click as happening on a selection which allows bullet points to toggle
private _break = true;
@@ -128,20 +142,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
@computed get titleHeight() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin) as number || 0; } // prettier-ignore
@computed get layout_autoHeightMargins() { return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins); } // prettier-ignore
@computed get config() {
- this._keymap = buildKeymap(schema, this._props);
this._rules = new RichTextRules(this.Document, this);
- return {
- schema,
- plugins: [
- inputRules(this._rules.inpRules),
- this.richTextMenuPlugin(),
- history(),
- keymap(this._keymap),
- keymap(baseKeymap),
- new Plugin({ props: { attributes: { class: 'ProseMirror-example-setup-style' } } }),
- new Plugin({ view: () => new FormattedTextBoxComment() }),
- ],
- };
+ return FormattedTextBox.MakeConfig(this._rules, this._props);
}
public get EditorView() {
@@ -1380,11 +1382,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return true;
}
- richTextMenuPlugin() {
+ static richTextMenuPlugin(props: FormattedTextBoxProps) {
return new Plugin({
view: action((newView: EditorView) => {
- this._props.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView);
- return new RichTextMenuPlugin({ editorProps: this._props });
+ props?.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView);
+ return new RichTextMenuPlugin({ editorProps: props });
}),
});
}
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 0c73400a9..c403f9415 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1,5 +1,3 @@
-/* eslint-disable jsx-a11y/no-static-element-interactions */
-/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import Slider from '@mui/material/Slider';
@@ -32,7 +30,7 @@ import { dropActionType } from '../../../util/DropActionTypes';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
import { SerializationHelper } from '../../../util/SerializationHelper';
import { SnappingManager } from '../../../util/SnappingManager';
-import { undoBatch, UndoManager } from '../../../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager';
import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
import { CollectionFreeFormPannableContents } from '../../collections/collectionFreeForm/CollectionFreeFormPannableContents';
import { CollectionView } from '../../collections/CollectionView';
@@ -191,7 +189,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed
get isTreeOrStack() {
- return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._type_collection) as any);
+ return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._type_collection) as CollectionViewType);
}
@computed get isTree() {
return this.layoutDoc._type_collection === CollectionViewType.Tree;
@@ -304,7 +302,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// 'Play on next' for audio or video therefore first navigate to the audio/video before it should be played
startTempMedia = (targetDoc: Doc, activeItem: Doc) => {
const duration: number = NumCast(activeItem.config_clipEnd) - NumCast(activeItem.config_clipStart);
- if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) {
+ if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as DocumentType)) {
const targMedia = DocumentView.getDocumentView(targetDoc);
targMedia?.ComponentView?.playFrom?.(NumCast(activeItem.config_clipStart), NumCast(activeItem.config_clipStart) + duration);
}
@@ -312,7 +310,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
stopTempMedia = (targetDocField: FieldResult) => {
const targetDoc = DocCast(DocCast(targetDocField).annotationOn) ?? DocCast(targetDocField);
- if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) {
+ if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as DocumentType)) {
const targMedia = DocumentView.getDocumentView(targetDoc);
targMedia?.ComponentView?.Pause?.();
}
@@ -364,7 +362,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.setIsRecording(false);
this.setIsLoading(true);
- const currSlideProperties: { [key: string]: any } = {};
+ const currSlideProperties: { [key: string]: FieldResult } = {};
gptSlideProperties.forEach(key => {
if (this.activeItem[key]) {
currSlideProperties[key] = this.activeItem[key];
@@ -554,7 +552,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
});
static pinDataTypes(target?: Doc): dataTypes {
- const targetType = target?.type as any;
+ const targetType = target?.type as DocumentType;
const inkable = [DocumentType.INK].includes(targetType);
const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetType) || target?._type_collection === CollectionViewType.Stacking;
const pannable = [DocumentType.IMG, DocumentType.PDF].includes(targetType) || (targetType === DocumentType.COL && target?._type_collection === CollectionViewType.Freeform);
@@ -759,8 +757,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const doc = DocCast(DocServer.GetCachedRefField(data.id));
if (doc) {
transitioned.add(doc);
- const field = !data.data ? undefined : await SerializationHelper.Deserialize(data.data);
- const tfield = !data.text ? undefined : await SerializationHelper.Deserialize(data.text);
+ const field = !data.data ? undefined : ((await SerializationHelper.Deserialize(data.data)) as FieldType);
+ const tfield = !data.text ? undefined : ((await SerializationHelper.Deserialize(data.text)) as FieldType);
doc._dataTransition = `all ${transTime}ms`;
doc.x = data.x;
doc.y = data.y;
@@ -858,7 +856,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
effect: activeItem,
noSelect: true,
openLocation: targetDoc.type === DocumentType.PRES ? ((OpenWhere.replace + ':' + PresBox.PanelName) as OpenWhere) : OpenWhere.addLeft,
- easeFunc: StrCast(activeItem.presentation_easeFunc, 'ease') as any,
+ easeFunc: StrCast(activeItem.presentation_easeFunc, 'ease') as 'linear' | 'ease',
zoomTextSelections: BoolCast(activeItem.presentation_zoomText),
playAudio: BoolCast(activeItem.presentation_playAudio),
playMedia: activeItem.presentation_mediaStart === 'auto',
@@ -1101,7 +1099,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
*/
@undoBatch
viewChanged = action((e: React.ChangeEvent) => {
- const typeCollection = (e.target as any).selectedOptions[0].value as CollectionViewType;
+ const typeCollection = (e.target as HTMLSelectElement).selectedOptions[0].value as CollectionViewType;
this.layoutDoc.presFieldKey = this.fieldKey + (typeCollection === CollectionViewType.Tree ? '-linearized' : '');
// pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
[CollectionViewType.Tree || CollectionViewType.Stacking].includes(typeCollection) && (this.Document._pivotField = undefined);
@@ -1111,30 +1109,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
});
- /**
- * Called when the user changes the view type
- * Either 'List' (stacking) or 'Slides' (carousel)
- */
- // @undoBatch
- mediaStopChanged = action((e: React.ChangeEvent) => {
- const { activeItem } = this;
- const stopDoc = (e.target as any).selectedOptions[0].value as string;
- const stopDocIndex = Number(stopDoc[0]);
- activeItem.mediaStopDoc = stopDocIndex;
- if (this.childDocs[stopDocIndex - 1].mediaStopTriggerList) {
- const list = DocListCast(this.childDocs[stopDocIndex - 1].mediaStopTriggerList);
- list.push(activeItem);
- // this.childDocs[stopDocIndex - 1].mediaStopTriggerList = list;\
- } else {
- this.childDocs[stopDocIndex - 1].mediaStopTriggerList = new List<Doc>();
- const list = DocListCast(this.childDocs[stopDocIndex - 1].mediaStopTriggerList);
- list.push(activeItem);
- // this.childDocs[stopDocIndex - 1].mediaStopTriggerList = list;
- }
- });
-
movementName = action((activeItem: Doc) => {
- if (![PresMovement.Zoom, PresMovement.Pan, PresMovement.Center, PresMovement.Jump, PresMovement.None].includes(StrCast(activeItem.presentation_movement) as any)) {
+ if (![PresMovement.Zoom, PresMovement.Pan, PresMovement.Center, PresMovement.Jump, PresMovement.None].includes(StrCast(activeItem.presentation_movement) as PresMovement)) {
return PresMovement.Zoom;
}
return StrCast(activeItem.presentation_movement);
@@ -1185,7 +1161,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
* Method to get the list of selected items in the order in which they have been selected
*/
@computed get listOfSelected() {
- return Array.from(this.selectedArray).map((doc: Doc, index: any) => {
+ return Array.from(this.selectedArray).map((doc, index) => {
const curDoc = Cast(doc, Doc, null);
const tagDoc = Cast(curDoc.presentation_targetDoc, Doc, null);
if (curDoc && curDoc === this.activeItem)
@@ -1193,7 +1169,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// eslint-disable-next-line react/no-array-index-key
<div key={index} className="selectedList-items">
<b>
- {index + 1}. {curDoc.title}
+ {index + 1}. {StrCast(curDoc.title)})
</b>
</div>
);
@@ -1201,14 +1177,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return (
// eslint-disable-next-line react/no-array-index-key
<div key={index} className="selectedList-items">
- {index + 1}. {curDoc.title}
+ {index + 1}. {StrCast(curDoc.title)}
</div>
);
if (curDoc)
return (
// eslint-disable-next-line react/no-array-index-key
<div key={index} className="selectedList-items">
- {index + 1}. {curDoc.title}
+ {index + 1}. {StrCast(curDoc.title)}
</div>
);
return null;
@@ -1301,13 +1277,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
switch (e.key) {
case 'Backspace':
if (this.layoutDoc.presentation_status === 'edit') {
- undoBatch(
+ undoable(
action(() => {
Array.from(this.selectedArray).forEach(doc => this.removeDocument(doc));
this.clearSelectedArray();
this._eleArray.length = 0;
this._dragArray.length = 0;
- })
+ }),
+ 'delete slides'
)();
handled = true;
}
@@ -1488,7 +1465,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
};
// Converts seconds to ms and updates presentation_transition
- public static SetTransitionTime = (number: String, setter: (timeInMS: number) => void, change?: number) => {
+ public static SetTransitionTime = (number: string, setter: (timeInMS: number) => void, change?: number) => {
let timeInMS = Number(number) * 1000;
if (change) timeInMS += change;
if (timeInMS < 100) timeInMS = 100;
@@ -1497,7 +1474,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
@undoBatch
- updateTransitionTime = (number: String, change?: number) => {
+ updateTransitionTime = (number: string, change?: number) => {
PresBox.SetTransitionTime(
number,
(timeInMS: number) =>
@@ -1510,7 +1487,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// Converts seconds to ms and updates presentation_transition
@undoBatch
- updateZoom = (number: String, change?: number) => {
+ updateZoom = (number: string, change?: number) => {
let scale = Number(number) / 100;
if (change) scale += change;
if (scale < 0.01) scale = 0.01;
@@ -1524,7 +1501,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
* Converts seconds to ms and updates presentation_duration
*/
@undoBatch
- updateDurationTime = (number: String, change?: number) => {
+ updateDurationTime = (number: string, change?: number) => {
let timeInMS = Number(number) * 1000;
if (change) timeInMS += change;
if (timeInMS < 100) timeInMS = 100;
@@ -1608,9 +1585,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
};
- static _sliderBatch: any;
+ static _sliderBatch: UndoManager.Batch | undefined;
static endBatch = () => {
- PresBox._sliderBatch.end();
+ PresBox._sliderBatch?.end();
document.removeEventListener('pointerup', PresBox.endBatch, true);
};
public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?: number) => (
@@ -1704,7 +1681,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
</Tooltip>
</div>
- {[DocumentType.AUDIO, DocumentType.VID].includes(targetType as any as DocumentType) ? null : (
+ {[DocumentType.AUDIO, DocumentType.VID].includes(targetType as DocumentType) ? null : (
<>
<div className="ribbon-doubleButton">
<div className="presBox-subheading">Slide Duration</div>
@@ -1847,7 +1824,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (activeItem && this.targetDoc) {
const transitionSpeed = activeItem.presentation_transition ? NumCast(activeItem.presentation_transition) / 1000 : 0.5;
const zoom = NumCast(activeItem.config_zoom, 1) * 100;
- const effect = StrCast(activeItem.presentation_effect) ? (StrCast(activeItem.presentation_effect) as any as PresEffect) : PresEffect.None;
+ const effect = StrCast(activeItem.presentation_effect) ? (StrCast(activeItem.presentation_effect) as PresEffect) : PresEffect.None;
const direction = StrCast(activeItem.presentation_effectDirection) as PresEffectDirection;
return (
@@ -2660,24 +2637,26 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className={`dropdown-play ${this._presentTools ? 'active' : ''}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
<div
className="dropdown-play-button"
- onClick={undoBatch(
+ onClick={undoable(
action(() => {
this.enterMinimize();
this.turnOffEdit(true);
this.gotoDocument(this.itemIndex, this.activeItem);
- })
+ }),
+ 'minimze presentation'
)}>
Mini-player
</div>
<div
className="dropdown-play-button"
- onClick={undoBatch(
+ onClick={undoable(
action(() => {
this.layoutDoc.presentation_status = 'manual';
this.initializePresState(this.itemIndex);
this.turnOffEdit(true);
this.gotoDocument(this.itemIndex, this.activeItem);
- })
+ }),
+ 'make presentation manual'
)}>
Sidebar player
</div>
@@ -2773,13 +2752,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<span className={`presBox-button ${this.layoutDoc.presentation_status === PresStatus.Edit ? 'present' : ''}`}>
<div
className="presBox-button-left"
- onClick={undoBatch(() => {
+ onClick={undoable(() => {
if (this.childDocs.length) {
this.layoutDoc.presentation_status = 'manual';
this.initializePresState(this.itemIndex);
this.gotoDocument(this.itemIndex, this.activeItem);
}
- })}>
+ }, 'start presentation')}>
<FontAwesomeIcon icon="play-circle" />
<div style={{ display: this._props.PanelWidth() > 200 ? 'inline-flex' : 'none' }}>&nbsp; Present</div>
</div>
@@ -2911,11 +2890,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
{this._props.PanelWidth() > 250 ? (
<div
className="presPanel-button-text"
- onClick={undoBatch(
+ onClick={undoable(
action(() => {
this.layoutDoc.presentation_status = PresStatus.Edit;
clearTimeout(this._presTimer);
- })
+ }),
+ 'edit presetnation'
)}>
EXIT
</div>
@@ -2988,7 +2968,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
sort = (treeViewMap: Map<Doc, number>) => [...treeViewMap.entries()].sort((a: [Doc, number], b: [Doc, number]) => (a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0)).map(kv => kv[0]);
-
+ emptyHierarchy = [];
render() {
// needed to ensure that the childDocs are loaded for looking up fields
this.childDocs.slice();
@@ -3086,7 +3066,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
ScreenToLocalTransform={this.getTransform}
AddToMap={this.AddToMap}
RemFromMap={this.RemFromMap}
- hierarchyIndex={emptyPath}
+ hierarchyIndex={this.emptyHierarchy}
/>
) : null}
</div>
diff --git a/src/client/views/nodes/trails/SlideEffect.tsx b/src/client/views/nodes/trails/SlideEffect.tsx
index 00039e3cb..a114c231f 100644
--- a/src/client/views/nodes/trails/SlideEffect.tsx
+++ b/src/client/views/nodes/trails/SlideEffect.tsx
@@ -103,7 +103,7 @@ export default function SpringAnimation({ doc, dir, springSettings, presEffect,
api.start({ loop: infinite, delay: infinite ? 500 : 0 });
}
}, [inView]);
- const animatedDiv = (style: any) => (
+ const animatedDiv = (style: object) => (
<animated.div ref={ref} style={{ ...style, opacity: to(springs.opacity, val => `${val}`) }}>
{children}
</animated.div>
diff --git a/src/fields/ObjectField.ts b/src/fields/ObjectField.ts
index 5f31208eb..c533cb596 100644
--- a/src/fields/ObjectField.ts
+++ b/src/fields/ObjectField.ts
@@ -12,9 +12,8 @@ export interface serializedDoctype {
export type serverOpType = {
$set?: serializedFieldsType; //
$unset?: { [key: string]: unknown };
- $remFromSet?: { [key: string]: { fields: serializedFieldType[] } | { deleteCount: number; start: number } | undefined; hint?: { deleteCount: number; start: number } };
- $addToSet?: serializedFieldsType;
- length?: number;
+ $remFromSet?: { [key: string]: { fields: serializedFieldType[] } | { deleteCount: number; start: number } | number | undefined; length: number; hint: { deleteCount: number; start: number } | undefined };
+ $addToSet?: { [key: string]: { fields: serializedFieldType[] } | number | undefined; length: number };
};
export abstract class ObjectField {
// prettier-ignore
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index 3763dcd2c..d1316d256 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -1,9 +1,10 @@
+/* eslint-disable @typescript-eslint/no-namespace */
/* eslint-disable no-await-in-loop */
/* eslint-disable no-use-before-define */
import { AssertionError } from 'assert';
import * as Color from 'color';
import { docs_v1 as docsV1 } from 'googleapis';
-import { Fragment, Mark, Node } from 'prosemirror-model';
+import { Fragment, Mark, Node, Schema } from 'prosemirror-model';
import { sinkListItem } from 'prosemirror-schema-list';
import { EditorState, TextSelection, Transaction } from 'prosemirror-state';
import { ClientUtils, DashColor } from '../ClientUtils';
@@ -26,7 +27,7 @@ export namespace RichTextUtils {
const joiner = '';
export const Initialize = (initial?: string) => {
- const content: any[] = [];
+ const content: object[] = [];
const state = {
doc: {
type: 'doc',
@@ -80,8 +81,10 @@ export namespace RichTextUtils {
// Preserve the current state, but re-write the content to be the blocks
const parsed = JSON.parse(oldState ? oldState.Data : Initialize());
parsed.doc.content = elements.map(text => {
- const paragraph: any = { type: 'paragraph' };
- text.length && (paragraph.content = [{ type: 'text', marks: [], text }]); // An empty paragraph gets treated as a line break
+ const paragraph: object = {
+ type: 'paragraph',
+ content: text.length ? [{ type: 'text', marks: [], text }] : undefined, // An empty paragraph gets treated as a line break
+ };
return paragraph;
});
@@ -164,7 +167,7 @@ export namespace RichTextUtils {
const inlineObjectMap = await parseInlineObjects(document);
const title = document.title!;
const { text, paragraphs } = GoogleApiClientUtils.Docs.Utils.extractText(document);
- let state = EditorState.create(new FormattedTextBox({} as any).config);
+ let state = EditorState.create(FormattedTextBox.MakeConfig());
const structured = parseLists(paragraphs);
let position = 3;
@@ -253,17 +256,20 @@ export namespace RichTextUtils {
return groups;
};
- const listItem = (lschema: any, runs: docsV1.Schema$TextRun[]): Node => lschema.node('list_item', null, paragraphNode(lschema, runs));
+ const listItem = (lschema: Schema, runs: docsV1.Schema$TextRun[]): Node => lschema.node('list_item', null, paragraphNode(lschema, runs));
- const list = (lschema: any, items: Node[]): Node => lschema.node('ordered_list', { mapStyle: 'bullet' }, items);
+ const list = (lschema: Schema, items: Node[]): Node => lschema.node('ordered_list', { mapStyle: 'bullet' }, items);
- const paragraphNode = (lschema: any, runs: docsV1.Schema$TextRun[]): Node => {
- const children = runs.map(run => textNode(lschema, run)).filter(child => child !== undefined);
+ const paragraphNode = (lschema: Schema, runs: docsV1.Schema$TextRun[]): Node => {
+ const children = runs
+ .map(run => textNode(lschema, run))
+ .filter(child => child !== undefined)
+ .map(child => child!);
const fragment = children.length ? Fragment.from(children) : undefined;
return lschema.node('paragraph', null, fragment);
};
- const imageNode = (lschema: any, image: ImageTemplate, textNote: Doc) => {
+ const imageNode = (lschema: Schema, image: ImageTemplate, textNote: Doc) => {
const { url: src, width, agnostic } = image;
let docId: string;
const guid = Utils.GenerateDeterministicGuid(agnostic);
@@ -279,7 +285,7 @@ export namespace RichTextUtils {
return lschema.node('image', { src, agnostic, width, docId, float: null });
};
- const textNode = (lschema: any, run: docsV1.Schema$TextRun) => {
+ const textNode = (lschema: Schema, run: docsV1.Schema$TextRun) => {
const text = run.content!.removeTrailingNewlines();
return text.length ? lschema.text(text, styleToMarks(lschema, run.textStyle)) : undefined;
};
@@ -291,29 +297,33 @@ export namespace RichTextUtils {
['fontSize', 'pFontSize'],
]);
- const styleToMarks = (lschema: any, textStyle?: docsV1.Schema$TextStyle) => {
+ const styleToMarks = (lschema: Schema, textStyle?: docsV1.Schema$TextStyle) => {
if (!textStyle) {
return undefined;
}
const marks: Mark[] = [];
Object.keys(textStyle).forEach(key => {
const targeted = key as keyof docsV1.Schema$TextStyle;
- const value = textStyle[targeted] as any;
+ const value = textStyle[targeted];
if (value) {
- const attributes: any = {};
+ const attributes: { [key: string]: number | string } = {};
let converted = StyleToMark.get(targeted) || targeted;
- value.url && (attributes.href = value.url);
- if (value.color) {
- const object = value.color.rgbColor;
- attributes.color = Color.rgb(['red', 'green', 'blue'].map(color => object[color] * 255 || 0)).hex();
+ const urlValue = value as docsV1.Schema$Link;
+ urlValue.url && (attributes.href = urlValue.url);
+ const colValue = value as docsV1.Schema$OptionalColor;
+ const object = colValue.color?.rgbColor;
+ if (object) {
+ attributes.color = Color.rgb(['red', 'green', 'blue'].map(color => (object as { [key: string]: number })[color] * 255 || 0)).hex();
}
- if (value.magnitude) {
- attributes.fontSize = value.magnitude;
+ const magValue = value as docsV1.Schema$Dimension;
+ if (magValue.magnitude) {
+ attributes.fontSize = magValue.magnitude;
}
+ const fontValue = value as docsV1.Schema$WeightedFontFamily;
if (converted === 'weightedFontFamily') {
- converted = ImportFontFamilyMapping.get(value.fontFamily) || 'timesNewRoman';
+ converted = (fontValue.fontFamily && ImportFontFamilyMapping.get(fontValue.fontFamily)) || 'timesNewRoman';
}
const mapped = lschema.marks[converted];
@@ -388,7 +398,7 @@ export namespace RichTextUtils {
continue;
}
let converted = MarkToStyle.get(markName) || (markName as keyof docsV1.Schema$TextStyle);
- let value: any = true;
+ let value: unknown = true;
if (!converted) {
// eslint-disable-next-line no-continue
continue;
@@ -436,7 +446,7 @@ export namespace RichTextUtils {
converted = 'fontSize';
value = { magnitude: parseInt(matches[1].replace('px', '')), unit: 'PT' };
}
- textStyle[converted] = value;
+ textStyle[converted] = value as undefined;
}
if (Object.keys(textStyle).length) {
requests.push(EncodeStyleUpdate(information));
diff --git a/src/fields/util.ts b/src/fields/util.ts
index a5c56607c..60eadcdfd 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -398,9 +398,9 @@ export function containedFieldChangedHandler(container: ListImpl<FieldType> | Do
const serializeItems = () => ({ __type: 'list', fields: diff?.items?.map((item: FieldType) => SerializationHelper.Serialize(item) as serializedFieldType) ?? [] });
// prettier-ignore
const serverOp: serverOpType = diff?.op === '$addToSet'
- ? { $addToSet: { ['fields.' + prop]: serializeItems() }, length: diff.length }
+ ? { $addToSet: { ['fields.' + prop]: serializeItems(), length: diff.length ??0 }}
: diff?.op === '$remFromSet'
- ? { $remFromSet: { ['fields.' + prop]: serializeItems(), hint: diff.hint}, length: diff.length }
+ ? { $remFromSet: { ['fields.' + prop]: serializeItems(), hint: diff.hint, length: diff.length ?? 0 } }
: { $set: { ['fields.' + prop]: SerializationHelper.Serialize(liveContainedField) as {fields: serializedFieldType[]}} };
if (!(container instanceof Doc) || !container[UpdatingFromServer]) {
diff --git a/src/server/database.ts b/src/server/database.ts
index a93117349..975b9eb80 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -32,7 +32,7 @@ export namespace Database {
try {
const { connection } = mongoose;
disconnect = async () =>
- new Promise<any>(resolve => {
+ new Promise<void>(resolve => {
connection.close().then(resolve);
});
if (connection.readyState === ConnectionStates.disconnected) {
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index f588151a5..ccbcb1c5f 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -49,12 +49,12 @@ export namespace WebSocket {
DashStats.logUserLogin(userEmail);
}
- function GetRefFieldLocal([id, callback]: [string, (result?: serializedDoctype) => void]) {
+ function GetRefFieldLocal(id: string, callback: (result?: serializedDoctype | undefined) => void) {
return Database.Instance.getDocument(id, callback);
}
function GetRefField([id, callback]: [string, (result?: serializedDoctype) => void]) {
process.stdout.write(`+`);
- GetRefFieldLocal([id, callback]);
+ GetRefFieldLocal(id, callback);
}
function GetRefFields([ids, callback]: [string[], (result?: serializedDoctype[]) => void]) {
@@ -62,112 +62,46 @@ export namespace WebSocket {
Database.Instance.getDocuments(ids, callback);
}
- const suffixMap: { [type: string]: string | [string, string | ((json: any) => any)] } = {
- number: '_n',
- string: '_t',
- boolean: '_b',
- image: ['_t', 'url'],
- video: ['_t', 'url'],
- pdf: ['_t', 'url'],
- audio: ['_t', 'url'],
- web: ['_t', 'url'],
- map: ['_t', 'url'],
- script: ['_t', value => value.script.originalScript],
- RichTextField: ['_t', value => value.Text],
- date: ['_d', value => new Date(value.date).toISOString()],
- proxy: ['_i', 'fieldId'],
- list: [
- '_l',
- list => {
- const results: any[] = [];
- // eslint-disable-next-line no-use-before-define
- list.fields.forEach((value: any) => ToSearchTerm(value) && results.push(ToSearchTerm(value)!.value));
- return results.length ? results : null;
- },
- ],
- };
-
- function ToSearchTerm(valIn: any): { suffix: string; value: any } | undefined {
- let val = valIn;
- if (val === null || val === undefined) {
- return undefined;
- }
- const type = val.__type || typeof val;
-
- let suffix = suffixMap[type];
- if (!suffix) {
- return undefined;
- }
- if (Array.isArray(suffix)) {
- const accessor = suffix[1];
- if (typeof accessor === 'function') {
- val = accessor(val);
- } else {
- val = val[accessor];
- }
- [suffix] = suffix;
- }
- return { suffix, value: val };
- }
-
- function getSuffix(value: string | [string, any]): string {
- return typeof value === 'string' ? value : value[0];
- }
const pendingOps = new Map<string, { diff: Diff; socket: Socket }[]>();
- function dispatchNextOp(id: string) {
- const next = pendingOps.get(id)!.shift();
+ function dispatchNextOp(id: string): unknown {
+ const next = pendingOps.get(id)?.shift();
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const nextOp = (res: boolean) => dispatchNextOp(id);
if (next) {
const { diff, socket } = next;
- if (diff.diff.$addToSet) {
- // eslint-disable-next-line no-use-before-define
- return GetRefFieldLocal([diff.id, (result?: serializedDoctype) => addToListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own
+ // ideally, we'd call the Database update method for all actions, but for now we handle list insertion/removal on our own
+ switch (diff.diff.$addToSet ? 'add' : diff.diff.$remFromSet ? 'rem' : 'set') {
+ case 'add': return GetRefFieldLocal(id, (result) => addToListField(socket, diff, result, nextOp)); // prettier-ignore
+ case 'rem': return GetRefFieldLocal(id, (result) => remFromListField(socket, diff, result, nextOp)); // prettier-ignore
+ default: return Database.Instance.update(id, diff.diff,
+ () => nextOp(socket.broadcast.emit(MessageStore.UpdateField.Message, diff)),
+ false
+ ); // prettier-ignore
}
- if (diff.diff.$remFromSet) {
- // eslint-disable-next-line no-use-before-define
- return GetRefFieldLocal([diff.id, (result?: serializedDoctype) => remFromListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own
- }
- // eslint-disable-next-line no-use-before-define
- return SetField(socket, diff);
}
- return !pendingOps.get(id)!.length && pendingOps.delete(id);
+ return !pendingOps.get(id)?.length && pendingOps.delete(id);
}
- function addToListField(socket: Socket, diffIn: Diff, listDoc?: serializedDoctype): void {
- const diff = diffIn;
- diff.diff.$set = diff.diff.$addToSet;
- delete diff.diff.$addToSet; // convert add to set to a query of the current fields, and then a set of the composition of the new fields with the old ones
- const updatefield = Array.from(Object.keys(diff.diff.$set ?? {}))[0];
- const newListItems = diff.diff.$set?.[updatefield]?.fields;
- if (!newListItems) {
- console.log('Error: addToListField - no new list items');
- return;
- }
- const listItems = listDoc?.fields?.[updatefield.replace('fields.', '')]?.fields.filter(item => item !== undefined) ?? [];
- if (diff.diff.$set?.[updatefield]?.fields !== undefined) {
+ function addToListField(socket: Socket, diff: Diff, listDoc: serializedDoctype | undefined, cb: (res: boolean) => void): void {
+ const $addToSet = diff.diff.$addToSet as serializedFieldsType;
+ const updatefield = Array.from(Object.keys($addToSet ?? {}))[0];
+ const newListItems = $addToSet?.[updatefield]?.fields;
+
+ if (newListItems) {
+ const length = diff.diff.$addToSet?.length;
+ diff.diff.$set = $addToSet; // convert add to set to a query of the current fields, and then a set of the composition of the new fields with the old ones
+ delete diff.diff.$addToSet; // can't pass $set to Mongo, or it will do that insetead of $addToSet
+ const listItems = listDoc?.fields?.[updatefield.replace('fields.', '')]?.fields.filter(item => item) ?? [];
diff.diff.$set[updatefield]!.fields = [...listItems, ...newListItems]; // , ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))];
- const sendBack = diff.diff.length !== diff.diff.$set[updatefield]!.fields?.length;
- delete diff.diff.length;
- Database.Instance.update(
- diff.id,
- diff.diff,
- () => {
- if (sendBack) {
- console.log('Warning: list modified during update. Composite list is being returned.');
- const { id } = socket;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (socket as any).id = ''; // bcz: HACK to reference private variable. this allows the update message to go back to the client that made the change.
- socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (socket as any).id = id;
- } else {
- socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
- }
- dispatchNextOp(diff.id);
- },
- false
- );
- }
+
+ // if the client's list length is not the same as what we're writing to the server,
+ // then we need to send the server's version back to the client so that they are in synch.
+ // this could happen if another client made a change before the server receives the update from the first client
+ const target = length !== diff.diff.$set[updatefield].fields.length ? socket : socket.broadcast;
+ target === socket && console.log('Warning: SEND BACK: list modified during add update. Composite list is being returned.');
+ Database.Instance.update(diff.id, diff.diff, () => cb(target.emit(MessageStore.UpdateField.Message, diff)), false);
+ } else cb(false);
}
/**
@@ -206,15 +140,17 @@ export namespace WebSocket {
* items to delete)
* @param curListItems the server's current copy of the data
*/
- function remFromListField(socket: Socket, diffIn: Diff, curListItems?: serializedDoctype): void {
- const diff = diffIn;
- diff.diff.$set = diff.diff.$remFromSet as serializedFieldsType;
- const hint = diff.diff.$remFromSet?.hint;
- delete diff.diff.$remFromSet;
- const updatefield = Array.from(Object.keys(diff.diff.$set ?? {}))[0];
- const remListItems = diff.diff.$set[updatefield]?.fields;
- if (diff.diff.$set[updatefield] !== undefined && remListItems) {
- const curList = curListItems?.fields?.[updatefield.replace('fields.', '')]?.fields.filter(f => f !== null) || [];
+ function remFromListField(socket: Socket, diff: Diff, curListItems: serializedDoctype | undefined, cb: (res: boolean) => void): void {
+ const $remFromSet = diff.diff.$remFromSet as serializedFieldsType;
+ const updatefield = Array.from(Object.keys($remFromSet ?? {}))[0];
+ const remListItems = $remFromSet?.[updatefield]?.fields;
+
+ if (remListItems) {
+ const hint = diff.diff.$remFromSet?.hint;
+ const length = diff.diff.$remFromSet?.length;
+ diff.diff.$set = $remFromSet; // convert rem from set to a query of the current fields, and then a set of the old fields minus the removed ones
+ delete diff.diff.$remFromSet; // can't pass $set to Mongo, or it will do that insetead of $remFromSet
+ const curList = curListItems?.fields?.[updatefield.replace('fields.', '')]?.fields.filter(f => f) ?? [];
if (hint) {
// indexesToRemove stores the indexes that we mark for deletion, which is later used to filter the list (delete the elements)
@@ -227,83 +163,51 @@ export namespace WebSocket {
if (closestIndex !== -1) {
indexesToRemove.push(closestIndex);
} else {
- console.log('Item to delete was not found - index = -1');
+ console.log('Item to delete was not found');
}
}
}
diff.diff.$set[updatefield]!.fields = curList.filter((curItem, index) => !indexesToRemove.includes(index));
} else {
- // go back to the original way to delete if we didn't receive
- // a hint from the client
+ // if we didn't get a hint, remove all matching items from the list
diff.diff.$set[updatefield]!.fields = curList?.filter(curItem => !remListItems.some(remItem => (remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : remItem === curItem)));
}
- // if the client and server have different versions of the data after
- // deletion, they will have different lengths and the server will
- // send its version of the data to the client
- const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length;
- delete diff.diff.length;
- Database.Instance.update(
- diff.id,
- diff.diff,
- () => {
- if (sendBack) {
- // the two copies are different, so the server sends its copy.
- console.log('SEND BACK');
- const { id } = socket;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (socket as any).id = ''; // bcz: HACK to access private variable this allows the update message to go back to the client that made the change.
- socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (socket as any).id = id;
- } else {
- socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
- }
- dispatchNextOp(diff.id);
- },
- false
- );
- }
+
+ // if the client's list length is not the same as what we're writing to the server,
+ // then we need to send the server's version back to the client so that they are in synch.
+ // this could happen if another client made a change before the server receives the update from the first client
+ const target = length !== diff.diff.$set[updatefield].fields.length ? socket : socket.broadcast;
+ target === socket && console.log('Warning: SEND BACK: list modified during remove update. Composite list is being returned.');
+ Database.Instance.update(diff.id, diff.diff, () => cb(target.emit(MessageStore.UpdateField.Message, diff)), false);
+ } else cb(false);
}
function UpdateField(socket: Socket, diff: Diff) {
const curUser = socketMap.get(socket);
- if (!curUser) return false;
- const currentUsername = curUser.split(' ')[0];
- userOperations.set(currentUsername, userOperations.get(currentUsername) !== undefined ? userOperations.get(currentUsername)! + 1 : 0);
+ if (curUser) {
+ const currentUsername = curUser.split(' ')[0];
+ userOperations.set(currentUsername, userOperations.get(currentUsername) !== undefined ? userOperations.get(currentUsername)! + 1 : 0);
- if (CurUser !== socketMap.get(socket)) {
- CurUser = socketMap.get(socket);
- console.log('Switch User: ' + CurUser);
- }
- if (pendingOps.has(diff.id)) {
- pendingOps.get(diff.id)!.push({ diff, socket });
- return true;
- }
- pendingOps.set(diff.id, [{ diff, socket }]);
- if (diff.diff.$addToSet) {
- return GetRefFieldLocal([diff.id, (result?: serializedDoctype) => addToListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own
- }
- if (diff.diff.$remFromSet) {
- return GetRefFieldLocal([diff.id, (result?: serializedDoctype) => remFromListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own
+ if (CurUser !== socketMap.get(socket)) {
+ CurUser = socketMap.get(socket);
+ console.log('Switch User: ' + CurUser);
+ }
+ if (pendingOps.has(diff.id)) {
+ pendingOps.get(diff.id)!.push({ diff, socket });
+ return true;
+ }
+ pendingOps.set(diff.id, [{ diff, socket }]);
+ return dispatchNextOp(diff.id);
}
- // eslint-disable-next-line no-use-before-define
- return SetField(socket, diff);
- }
- function SetField(socket: Socket, diff: Diff /* , curListItems?: Transferable */) {
- Database.Instance.update(diff.id, diff.diff, () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false);
- dispatchNextOp(diff.id);
+ return false;
}
function DeleteField(socket: Socket, id: string) {
- Database.Instance.delete({ _id: id }).then(() => {
- socket.broadcast.emit(MessageStore.DeleteField.Message, id);
- });
+ Database.Instance.delete({ _id: id }).then(() => socket.broadcast.emit(MessageStore.DeleteField.Message, id));
}
function DeleteFields(socket: Socket, ids: string[]) {
- Database.Instance.delete({ _id: { $in: ids } }).then(() => {
- socket.broadcast.emit(MessageStore.DeleteFields.Message, ids);
- });
+ Database.Instance.delete({ _id: { $in: ids } }).then(() => socket.broadcast.emit(MessageStore.DeleteFields.Message, ids));
}
function CreateDocField(newValue: serializedDoctype) {
@@ -343,21 +247,19 @@ export namespace WebSocket {
socket.in(room).emit('message', message);
});
- socket.on('ipaddr', () => {
+ socket.on('ipaddr', () =>
networkInterfaces().keys?.forEach(dev => {
if (dev.family === 'IPv4' && dev.address !== '127.0.0.1') {
socket.emit('ipaddr', dev.address);
}
- });
- });
+ })
+ );
- socket.on('bye', () => {
- console.log('received bye');
- });
+ socket.on('bye', () => console.log('received bye'));
socket.on('disconnect', () => {
const currentUser = socketMap.get(socket);
- if (!(currentUser === undefined)) {
+ if (currentUser !== undefined) {
const currentUsername = currentUser.split(' ')[0];
DashStats.logUserLogout(currentUsername);
delete timeMap[currentUsername];