diff options
Diffstat (limited to 'src/client/util')
24 files changed, 318 insertions, 245 deletions
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx index 46aa4d238..8e3936d34 100644 --- a/src/client/util/CalendarManager.tsx +++ b/src/client/util/CalendarManager.tsx @@ -54,7 +54,6 @@ export class CalendarManager extends ObservableReactComponent<{}> { @observable private targetDoc: Doc | undefined = undefined; // the target document @observable private targetDocView: DocumentView | undefined = undefined; // the DocumentView of the target doc @observable private dialogueBoxOpacity = 1; // for the modal - @observable private overlayOpacity = 0.4; // for the modal @observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used @@ -205,14 +204,12 @@ export class CalendarManager extends ObservableReactComponent<{}> { if (docs.length) { docs.forEach(doc => doc && Doc.BrushDoc(doc)); this.dialogueBoxOpacity = 0.1; - this.overlayOpacity = 0.1; } })} onPointerLeave={action(() => { if (docs.length) { docs.forEach(doc => doc && Doc.UnBrushDoc(doc)); this.dialogueBoxOpacity = 1; - this.overlayOpacity = 0.4; } })}> {contents} @@ -352,6 +349,6 @@ export class CalendarManager extends ObservableReactComponent<{}> { } render() { - return <MainViewModal contents={this.calendarInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />; + return <MainViewModal contents={this.calendarInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} closeOnExternalClick={this.close} />; } } diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx index 2939ba581..a03c80f2a 100644 --- a/src/client/util/CaptureManager.tsx +++ b/src/client/util/CaptureManager.tsx @@ -1,3 +1,5 @@ +/* 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, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; @@ -13,18 +15,22 @@ import { SelectionManager } from './SelectionManager'; @observer export class CaptureManager extends React.Component<{}> { + // eslint-disable-next-line no-use-before-define public static Instance: CaptureManager; static _settingsStyle = addStyleSheet(); @observable _document: any = undefined; @observable isOpen: boolean = false; // whether the CaptureManager is to be displayed or not. + // eslint-disable-next-line react/sort-comp constructor(props: {}) { super(props); makeObservable(this); CaptureManager.Instance = this; } - public close = action(() => (this.isOpen = false)); + public close = action(() => { + this.isOpen = false; + }); public open = action((doc: Doc) => { this.isOpen = true; this._document = doc; @@ -99,15 +105,15 @@ export class CaptureManager extends React.Component<{}> { <div className="capture-interface"> <div className="capture-t1"> <div className="recordButtonOutline" style={{}}> - <div className="recordButtonInner" style={{}}></div> + <div className="recordButtonInner" style={{}} /> </div> Conversation Capture </div> - <div className="capture-t2"></div> + <div className="capture-t2" /> {this.visibilityContent} {this.linksContent} <div className="close-button" onClick={this.close}> - <FontAwesomeIcon icon={'times'} color="black" size={'lg'} /> + <FontAwesomeIcon icon="times" color="black" size="lg" /> </div> {this.closeButtons} </div> @@ -119,11 +125,10 @@ export class CaptureManager extends React.Component<{}> { <MainViewModal contents={this.captureInterface} isDisplayed={this.isOpen} - interactive={true} + interactive closeOnExternalClick={this.close} dialogueBoxStyle={{ width: '500px', height: '350px', border: 'none', background: 'whitesmoke' }} overlayStyle={{ background: 'black' }} - overlayDisplayedOpacity={0.6} /> ); } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index acbd0c0b9..65dce34a5 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -67,7 +67,7 @@ export class CurrentUserUtils { // initializes experimental advanced template views - slideView, headerView static setupUserDocumentCreatorButtons(doc: Doc, userDocTemplates: Opt<Doc>) { - const userTemplates = DocListCast(userDocTemplates?.data).filter(doc => !Doc.IsSystem(doc)); + const userTemplates = DocListCast(userDocTemplates?.data).filter(fdoc => !Doc.IsSystem(fdoc)); const reqdOpts:DocumentOptions = { title: "User Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true, hidden: false, _dragOnlyWithinContainer: true, layout_hideContextMenu: true, isSystem: true, _forceActive: true, @@ -87,7 +87,7 @@ export class CurrentUserUtils { { opts: { title: "Open Detail On Right", targetScriptKey: "onChildDoubleClick"}, script: `openDoc(this.doubleClickView.${OpenWhere.addRight})`}]; const reqdClickList = reqdTempOpts.map(opts => { const allOpts = {...reqdClickOpts, ...opts.opts}; - const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined; + const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(fdoc => fdoc.title === opts.opts.title): undefined; return DocUtils.AssignOpts(clickDoc, allOpts) ?? Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script, allOpts),allOpts); }); @@ -110,7 +110,7 @@ export class CurrentUserUtils { const reqdClickList = reqdTempOpts.map(opts => { const title = opts.opts.title?.toString(); const allOpts = {...reqdClickOpts, ...opts.opts}; - const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === title): undefined; + const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(fdoc => fdoc.title === title): undefined; const script = ScriptField.MakeScript(opts.script, {heading:Doc.name, checked:"boolean", containingTreeView:Doc.name}); const scriptDoc = Docs.Create.ScriptingDocument(script, allOpts, title) return DocUtils.AssignOpts(clickDoc, allOpts) ?? MakeTemplate(scriptDoc); @@ -130,7 +130,7 @@ export class CurrentUserUtils { { title: "Topic", backgroundColor: "lightblue", icon: "book-open" , _layout_showTitle: "title"}]; const reqdNoteList = [...reqdTempOpts.map(opts => { const reqdOpts = {...opts, isSystem:true, width:200, layout_autoHeight: true, layout_fitWidth: true}; - const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.title === opts.title): undefined; + const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(fdoc => fdoc.title === opts.title): undefined; return DocUtils.AssignOpts(noteTemp, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts)); }), ... DocListCast(tempNotes?.data).filter(note => !reqdTempOpts.find(reqd => reqd.title === note.title))]; @@ -249,7 +249,7 @@ export class CurrentUserUtils { Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true, _height: 100, isSystem: true, _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), _text_fontSize: StrCast(Doc.UserDoc().fontSize) }) ], {...opts, title: "Slide View Template"})); const plotlyApi = () => { - let plotly = Doc.MyPublishedDocs.find(doc => doc.title === "@plotly"); + let plotly = Doc.MyPublishedDocs.find(fdoc => fdoc.title === "@plotly"); if (!plotly) { plotly = Docs.Create.TextDocument( `await import("https://cdn.plot.ly/plotly-2.27.0.min.js"); @@ -289,7 +289,7 @@ export class CurrentUserUtils { return slide; } const mermaidsApi = () => { - let mermaids = Doc.MyPublishedDocs.find(doc => doc.title === "@mermaids"); + let mermaids = Doc.MyPublishedDocs.find(fdoc => fdoc.title === "@mermaids"); if (!mermaids) { mermaids = Docs.Create.TextDocument( `const mdef = (await import("https://cdn.jsdelivr.net/npm/mermaid@10.8.0/dist/mermaid.esm.min.mjs")).default; @@ -416,7 +416,7 @@ pie title Minerals in my tap water /// Initalizes the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools static setupCreatorButtons(doc: Doc, dragCreatorDoc?:Doc):Doc { const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => { - const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined; + const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(fdoc => fdoc.title === reqdOpts.title): undefined; const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit, _width: 60, _height: 60, _dragOnlyWithinContainer: true, btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, isSystem: true, @@ -460,7 +460,7 @@ pie title Minerals in my tap water this.setupLeftSidebarPanel(doc); const myLeftSidebarMenu = DocCast(doc[field]); const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, toolTip, hidden, scripts, funcs }) => { - const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined; + const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(fdoc => fdoc.title === title) : undefined; const reqdBtnOpts:DocumentOptions = { title, icon, target, toolTip, hidden, btnType: ButtonType.MenuButton, isSystem: true, undoIgnoreFields: new List<string>(['height', 'data_columnHeaders']), dontRegisterView: true, _width: 60, _height: 60, _dragOnlyWithinContainer: true, @@ -614,7 +614,7 @@ pie title Minerals in my tap water static setupDockedButtons(doc: Doc, field="myDockedBtns") { const dockedBtns = DocCast(doc[field]); const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:string}) => - DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ?? + DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(fdoc => fdoc.title === opts.title), opts) ?? CurrentUserUtils.createToolButton(opts), scripts, funcs); const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet @@ -780,7 +780,7 @@ pie title Minerals in my tap water childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: !params.scripts?.onClick, linearView_SubMenu: true, linearView_Expandable: true, embedContainer: menuDoc}; - const items = (menuBtnDoc?:Doc) => !menuBtnDoc ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtnDoc) ); + const items = (menutBtn?:Doc) => !menutBtn ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menutBtn) ); const creator = params.btnType === ButtonType.MultiToggleButton ? this.multiToggleList : this.linearButtonList; const btnDoc = DocUtils.AssignScripts( DocUtils.AssignDocField(menuDoc, StrCast(params.title), (opts) => creator(opts, items(menuBtnDoc)), reqdSubMenuOpts, items(menuBtnDoc)), params.scripts, params.funcs); @@ -802,7 +802,7 @@ pie title Minerals in my tap water Doc.UserDoc().workspaceReplayingState = undefined; const dockedBtns = DocCast(doc[field]); const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:any}) => - DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ?? + DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(fdoc => fdoc.title === opts.title), opts) ?? CurrentUserUtils.createToolButton(opts), scripts, funcs); const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet @@ -964,7 +964,7 @@ pie title Minerals in my tap water case FInfoFieldType.Doc: opts.fieldValues = new List<Doc>(options.values as any); break; default: opts.fieldValues = new List<string>(options.values as any); break;// string, pointerEvents, dimUnit, dropActionType } - DocUtils.AssignDocField(infos, pair[0], opts => Doc.assign(new Doc(), OmitKeys(opts,["values"]).omit), opts); + DocUtils.AssignDocField(infos, pair[0], docOpts => Doc.assign(new Doc(), OmitKeys(docOpts,["values"]).omit), opts); } }); } diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 207d3ea0b..08fd80882 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-use-before-define */ import * as interpreter from 'words-to-numbers'; // @ts-ignore bcz: how are you supposed to include these definitions since dom-speech-recognition isn't a module? import type {} from '@types/dom-speech-recognition'; @@ -6,7 +7,7 @@ import { Doc, Opt } from '../../fields/Doc'; import { List } from '../../fields/List'; import { RichTextField } from '../../fields/RichTextField'; import { listSpec } from '../../fields/Schema'; -import { Cast, CastCtor, DocCast } from '../../fields/Types'; +import { Cast, CastCtor } from '../../fields/Types'; import { AudioField, ImageField } from '../../fields/URLField'; import { DocumentType } from '../documents/DocumentTypes'; import { Docs } from '../documents/Documents'; @@ -14,6 +15,7 @@ import { DictationOverlay } from '../views/DictationOverlay'; import { DocumentView, OpenWhere } from '../views/nodes/DocumentView'; import { SelectionManager } from './SelectionManager'; import { UndoManager } from './UndoManager'; +import { DocData } from '../../fields/DocSymbols'; /** * This namespace provides a singleton instance of a manager that @@ -64,9 +66,10 @@ export namespace DictationManager { export let isListening = false; let isManuallyStopped = false; - let current: string | undefined = undefined; + let current: string | undefined; let sessionResults: string[] = []; + // eslint-disable-next-line new-cap const recognizer: Opt<SpeechRecognition> = webkitSpeechRecognition ? new webkitSpeechRecognition() : undefined; export type InterimResultHandler = (results: string) => any; @@ -87,7 +90,7 @@ export namespace DictationManager { let pendingListen: Promise<string> | string | undefined; export const listen = async (options?: Partial<ListeningOptions>) => { - if (pendingListen instanceof Promise) return pendingListen.then(pl => innerListen(options)); + if (pendingListen instanceof Promise) return pendingListen.then(() => innerListen(options)); return innerListen(options); }; const innerListen = async (options?: Partial<ListeningOptions>) => { @@ -150,29 +153,29 @@ export namespace DictationManager { recognizer.start(); - return new Promise<string>((resolve, reject) => { + return new Promise<string>(resolve => { recognizer.onerror = (e: any) => { // e is SpeechRecognitionError but where is that defined? if (!(indefinite && e.error === 'no-speech')) { recognizer.stop(); resolve(e); - //reject(e); } }; recognizer.onresult = (e: SpeechRecognitionEvent) => { current = synthesize(e, intra); - let matchedTerminator: string | undefined; - if (options?.terminators && (matchedTerminator = options.terminators.find(end => (current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false)))) { + const matchedTerminator = options?.terminators?.find(end => (current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false)); + if (options?.terminators && matchedTerminator) { current = matchedTerminator; recognizer.abort(); return complete(); } !isManuallyStopped && handler?.(current); - //isManuallyStopped && complete(); + // isManuallyStopped && complete() + return undefined; }; - recognizer.onend = (e: Event) => { + recognizer.onend = () => { if (!indefinite || isManuallyStopped) { return complete(); } @@ -182,6 +185,7 @@ export namespace DictationManager { current = undefined; } recognizer.start(); + return undefined; }; const complete = () => { @@ -202,7 +206,7 @@ export namespace DictationManager { }); }; - export const stop = (salvageSession = true) => { + export const stop = (/* salvageSession = true */) => { if (!isListening || !recognizer) { return; } @@ -212,7 +216,7 @@ export namespace DictationManager { }; const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => { - const results = e.results; + const { results } = e; const transcripts: string[] = []; for (let i = 0; i < results.length; i++) { transcripts.push(results.item(i).item(0).transcript.trim()); @@ -233,22 +237,25 @@ export namespace DictationManager { export const RegisterIndependent = (key: string, value: IndependentEntry) => Independent.set(key, value); export const RegisterDependent = (entry: DependentEntry) => Dependent.push(entry); - export const execute = async (phrase: string) => { - return UndoManager.RunInBatch(async () => { + export const execute = async (phrase: string) => + UndoManager.RunInBatch(async () => { console.log('PHRASE: ' + phrase); const targets = SelectionManager.Views; if (!targets || !targets.length) { - return; + return undefined; } + // eslint-disable-next-line no-param-reassign phrase = phrase.toLowerCase(); const entry = Independent.get(phrase); if (entry) { let success = false; - const restrictTo = entry.restrictTo; + const { restrictTo } = entry; + // eslint-disable-next-line no-restricted-syntax for (const target of targets) { if (!restrictTo || validate(target, restrictTo)) { + // eslint-disable-next-line no-await-in-loop await entry.action(target); success = true; } @@ -256,16 +263,19 @@ export namespace DictationManager { return success; } - for (const entry of Dependent) { - const regex = entry.expression; + // eslint-disable-next-line no-restricted-syntax + for (const depEntry of Dependent) { + const regex = depEntry.expression; const matches = regex.exec(phrase); regex.lastIndex = 0; if (matches !== null) { let success = false; - const restrictTo = entry.restrictTo; + const { restrictTo } = depEntry; + // eslint-disable-next-line no-restricted-syntax for (const target of targets) { if (!restrictTo || validate(target, restrictTo)) { - await entry.action(target, matches); + // eslint-disable-next-line no-await-in-loop + await depEntry.action(target, matches); success = true; } } @@ -275,7 +285,6 @@ export namespace DictationManager { return false; }, 'Execute Command'); - }; const ConstructorMap = new Map<DocumentType, CastCtor>([ [DocumentType.COL, listSpec(Doc)], @@ -294,6 +303,7 @@ export namespace DictationManager { }; const validate = (target: DocumentView, types: DocumentType[]) => { + // eslint-disable-next-line no-restricted-syntax for (const type of types) { if (tryCast(target, type)) { return true; @@ -318,7 +328,9 @@ export namespace DictationManager { [ 'clear', { - action: (target: DocumentView) => (Doc.GetProto(target.Document).data = new List()), + action: (target: DocumentView) => { + Doc.GetProto(target.Document).data = new List(); + }, restrictTo: [DocumentType.COL], }, ], @@ -328,7 +340,7 @@ export namespace DictationManager { { action: (target: DocumentView) => { const newBox = Docs.Create.TextDocument('', { _width: 400, _height: 200, title: 'My Outline', _layout_autoHeight: true }); - const proto = DocCast(newBox.proto); + const proto = newBox[DocData]; const prompt = 'Press alt + r to start dictating here...'; const head = 3; const anchor = head + prompt.length; @@ -341,7 +353,7 @@ export namespace DictationManager { ], ]); - const Dependent = new Array<DependentEntry>( + const Dependent = [ { expression: /create (\w+) documents of type (image|nested collection)/g, action: (target: DocumentView, matches: RegExpExecArray) => { @@ -361,6 +373,7 @@ export namespace DictationManager { case 'nested collection': created = Docs.Create.FreeformDocument([], {}); break; + default: } created && Doc.AddDocToList(dataDoc, fieldKey, created); } @@ -375,7 +388,7 @@ export namespace DictationManager { mode && (target.Document._type_collection = mode); }, restrictTo: [DocumentType.COL], - } - ); + }, + ]; } } diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx index 894583711..c703c3f98 100644 --- a/src/client/util/GroupMemberView.tsx +++ b/src/client/util/GroupMemberView.tsx @@ -1,4 +1,7 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Button, IconButton, Size, Type } from 'browndash-components'; import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -8,7 +11,6 @@ import { StrCast } from '../../fields/Types'; import { MainViewModal } from '../views/MainViewModal'; import { GroupManager, UserOptions } from './GroupManager'; import './GroupMemberView.scss'; -import { Button, IconButton, Size, Type } from 'browndash-components'; import { SettingsManager } from './SettingsManager'; interface GroupMemberViewProps { @@ -38,20 +40,23 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> { className="group-title" style={{ marginLeft: !hasEditAccess ? '-14%' : 0 }} value={StrCast(this.group.title || this.group.groupName)} - onChange={e => (this.group.title = e.currentTarget.value)} - disabled={!hasEditAccess}></input> - <div className={'memberView-closeButton'}> - <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={action(this.props.onCloseButtonClick)} color={StrCast(Doc.UserDoc().userColor)} /> + onChange={e => { + this.group.title = e.currentTarget.value; + }} + disabled={!hasEditAccess} + /> + <div className="memberView-closeButton"> + <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={action(this.props.onCloseButtonClick)} color={StrCast(Doc.UserDoc().userColor)} /> </div> {GroupManager.Instance.hasEditAccess(this.group) ? ( <div className="group-buttons"> <div style={{ border: StrCast(Doc.UserDoc().userColor) }}> <Select className="add-member-dropdown" - isSearchable={true} + isSearchable options={options} onChange={selectedOption => GroupManager.Instance.addMemberToGroup(this.group, (selectedOption as UserOptions).value)} - placeholder={'Add members'} + placeholder="Add members" value={null} styles={{ control: () => ({ @@ -80,7 +85,12 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> { </div> </div> ) : null} - <div className="sort-emails" style={{ paddingTop: hasEditAccess ? 0 : 35 }} onClick={action(() => (this.memberSort = this.memberSort === 'ascending' ? 'descending' : this.memberSort === 'descending' ? 'none' : 'ascending'))}> + <div + className="sort-emails" + style={{ paddingTop: hasEditAccess ? 0 : 35 }} + onClick={action(() => { + this.memberSort = this.memberSort === 'ascending' ? 'descending' : this.memberSort === 'descending' ? 'none' : 'ascending'; + })}> Emails {this.memberSort === 'ascending' ? '↑' : this.memberSort === 'descending' ? '↓' : ''} {/* → */} </div> </div> @@ -90,8 +100,8 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> { <div className="editing-row" key={member}> <div className="user-email">{member}</div> {hasEditAccess ? ( - <div className={'remove-button'} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)}> - <IconButton icon={<FontAwesomeIcon icon={'trash-alt'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)} /> + <div className="remove-button" onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)}> + <IconButton icon={<FontAwesomeIcon icon="trash-alt" />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)} /> </div> ) : null} </div> @@ -102,6 +112,6 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> { } render() { - return <MainViewModal isDisplayed={true} interactive={true} contents={this.editingInterface} dialogueBoxStyle={{ width: 400, height: 250 }} closeOnExternalClick={this.props.onCloseButtonClick} />; + return <MainViewModal isDisplayed interactive contents={this.editingInterface} dialogueBoxStyle={{ width: 400, height: 250 }} closeOnExternalClick={this.props.onCloseButtonClick} />; } } diff --git a/src/client/util/History.ts b/src/client/util/History.ts index b500a5af9..52d0223d5 100644 --- a/src/client/util/History.ts +++ b/src/client/util/History.ts @@ -1,3 +1,9 @@ +/* eslint-disable no-use-before-define */ +/* eslint-disable no-empty */ +/* eslint-disable no-continue */ +/* eslint-disable guard-for-in */ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-param-reassign */ import * as qs from 'query-string'; import { Doc } from '../../fields/Doc'; import { OmitKeys, ClientUtils } from '../../ClientUtils'; @@ -32,6 +38,7 @@ export namespace HistoryUtil { case 'doc': onDocUrl(url); break; + default: } } } @@ -124,11 +131,11 @@ export namespace HistoryUtil { const val = customParser(pathname, opts, current); if (val === null) { return undefined; - } else if (val === undefined) { + } + if (val === undefined) { return current; - } else { - return val; } + return val; } return current; }; @@ -142,26 +149,27 @@ export namespace HistoryUtil { } const queryObj = OmitKeys(state, keys).extract; const query: any = {}; - Object.keys(queryObj).forEach(key => (query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key]))); + Object.keys(queryObj).forEach(key => { + query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key]); + }); const queryString = qs.stringify(query); return path + (queryString ? `?${queryString}` : ''); }; } addParser('doc', {}, { readonly: true, initializers: true, nro: true, sharing: true }, (pathname, opts, current) => { - if (pathname.length !== 2) return undefined; - - current.initializers = current.initializers || {}; - const docId = pathname[1]; - current.docId = docId; - }); - addStringifier('doc', ['initializers', 'readonly', 'nro'], (state, current) => { - return `${current}/${state.docId}`; + if (pathname.length === 2) { + current.initializers = current.initializers || {}; + const docId = pathname[1]; + current.docId = docId; + } + return undefined; }); + addStringifier('doc', ['initializers', 'readonly', 'nro'], (state, current) => `${current}/${state.docId}`); export function parseUrl(location: Location | URL): ParsedUrl | undefined { const pathname = location.pathname.substring(1); - const search = location.search; + const { search } = location; const opts = search.length ? qs.parse(search, { sort: false }) : {}; const pathnameSplit = pathname.split('/'); diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx index 58a09b9c9..db1e3d6cd 100644 --- a/src/client/util/Import & Export/ImportMetadataEntry.tsx +++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx @@ -1,10 +1,13 @@ -import * as React from 'react'; -import { observer } from 'mobx-react'; -import { EditableView } from '../../views/EditableView'; -import { action, computed } from 'mobx'; +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable no-use-before-define */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; import { Doc } from '../../../fields/Doc'; -import { StrCast, BoolCast } from '../../../fields/Types'; +import { BoolCast, StrCast } from '../../../fields/Types'; +import { EditableView } from '../../views/EditableView'; interface KeyValueProps { Document: Doc; @@ -93,18 +96,27 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps> alignItems: 'center', alignContent: 'center', }}> - <input onChange={e => (this.onDataDoc = e.target.checked)} ref={this.checkRef} style={{ margin: '0 10px 0 15px' }} type="checkbox" title={'Add to Data Document?'} checked={this.onDataDoc} /> - <div className={'key_container'} style={keyValueStyle}> - <EditableView ref={this.keyRef} contents={this.key} SetValue={this.updateKey} GetValue={() => ''} oneLine={true} /> + <input + onChange={e => { + this.onDataDoc = e.target.checked; + }} + ref={this.checkRef} + style={{ margin: '0 10px 0 15px' }} + type="checkbox" + title="Add to Data Document?" + checked={this.onDataDoc} + /> + <div className="key_container" style={keyValueStyle}> + <EditableView ref={this.keyRef} contents={this.key} SetValue={this.updateKey} GetValue={() => ''} oneLine /> </div> - <div className={'value_container'} style={keyValueStyle}> - <EditableView ref={this.valueRef} contents={this.value} SetValue={this.updateValue} GetValue={() => ''} oneLine={true} /> + <div className="value_container" style={keyValueStyle}> + <EditableView ref={this.valueRef} contents={this.value} SetValue={this.updateValue} GetValue={() => ''} oneLine /> </div> - <div onClick={() => this.props.remove(this)} title={'Delete Entry'}> + <div onClick={() => this.props.remove(this)} title="Delete Entry"> <FontAwesomeIcon - icon={'plus'} - color={'red'} - size={'1x'} + icon="plus" + color="red" + size="1x" style={{ marginLeft: 15, marginRight: 15, diff --git a/src/client/util/KeyCodes.ts b/src/client/util/KeyCodes.ts index de2457a5a..fb21f78ee 100644 --- a/src/client/util/KeyCodes.ts +++ b/src/client/util/KeyCodes.ts @@ -133,4 +133,4 @@ export class KeyCodes { public static NUM_9: number = 57; public static SUBTRACT: number = 189; public static ADD: number = 187; -}
\ No newline at end of file +} diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index 85bada8c9..09262f5ac 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -44,7 +44,7 @@ export class LinkFollower { public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, finished?: () => void, traverseBacklink?: boolean) { const getView = (doc: Doc) => DocumentManager.Instance.getFirstDocumentView(DocCast(doc.layout_unrendered ? doc.annotationOn : doc)); - const isAnchor = (sourceDoc: Doc, anchor: Doc) => Doc.AreProtosEqual(anchor, sourceDoc) || Doc.AreProtosEqual(anchor.annotationOn as Doc, sourceDoc); + const isAnchor = (source: Doc, anchor: Doc) => Doc.AreProtosEqual(anchor, source) || Doc.AreProtosEqual(anchor.annotationOn as Doc, source); const linkDocs = link ? [link] : LinkManager.Links(sourceDoc); const fwdLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_1 as Doc)); // link docs where 'sourceDoc' is link_anchor_1 const backLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_2 as Doc)); // link docs where 'sourceDoc' is link_anchor_2 diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts index 7638e2ce0..e5e69c5ac 100644 --- a/src/client/util/PingManager.ts +++ b/src/client/util/PingManager.ts @@ -1,8 +1,10 @@ import { action, makeObservable, observable, runInAction } from 'mobx'; import { Networking } from '../Network'; import { CurrentUserUtils } from './CurrentUserUtils'; + export class PingManager { // create static instance and getter for global use + // eslint-disable-next-line no-use-before-define @observable static _instance: PingManager; @observable IsBeating = true; static get Instance(): PingManager { @@ -29,7 +31,9 @@ export class PingManager { sendPing = async (): Promise<void> => { try { const res = await Networking.PostToServer('/ping', { date: new Date() }); - runInAction(() => (CurrentUserUtils.ServerVersion = res.message)); + runInAction(() => { + CurrentUserUtils.ServerVersion = res.message; + }); !this.IsBeating && this.setIsBeating(true); } catch { if (this.IsBeating) { diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx index 35b1579df..05fb849fd 100644 --- a/src/client/util/RTFMarkup.tsx +++ b/src/client/util/RTFMarkup.tsx @@ -6,23 +6,16 @@ import { SettingsManager } from './SettingsManager'; @observer export class RTFMarkup extends React.Component<{}> { + // eslint-disable-next-line no-use-before-define static Instance: RTFMarkup; @observable private isOpen = false; // whether the SharingManager modal is open or not - @action - public open = () => (this.isOpen = true); - - @action - public close = () => (this.isOpen = false); - constructor(props: {}) { super(props); makeObservable(this); RTFMarkup.Instance = this; } - @observable _stats: { [key: string]: any } | undefined = undefined; - /** * @returns the main interface of the SharingManager. */ @@ -30,11 +23,11 @@ export class RTFMarkup extends React.Component<{}> { return ( <div style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, textAlign: 'initial', height: '100%' }}> <p> - <b style={{ fontSize: 'larger' }}>{`(@wiki:phrase)`}</b> + <b style={{ fontSize: 'larger' }}>(@wiki:phrase)</b> {` display wikipedia page for entered text (terminate with carriage return)`} </p> <p> - <b style={{ fontSize: 'larger' }}>{`(( any text ))`}</b> + <b style={{ fontSize: 'larger' }}>(( any text ))</b> {` submit text to Chat GPT to have results appended afterward`} </p> <p> @@ -129,13 +122,23 @@ export class RTFMarkup extends React.Component<{}> { ); } + @action + public open = () => { + this.isOpen = true; + }; + + @action + public close = () => { + this.isOpen = false; + }; + render() { return ( <MainViewModal dialogueBoxStyle={{ backgroundColor: SettingsManager.userBackgroundColor, alignContent: 'normal', color: SettingsManager.userColor, padding: '16px' }} contents={this.cheatSheet} isDisplayed={this.isOpen} - interactive={true} + interactive closeOnExternalClick={this.close} /> ); diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts index b881f18b4..2c8fdf483 100644 --- a/src/client/util/ReplayMovements.ts +++ b/src/client/util/ReplayMovements.ts @@ -14,6 +14,7 @@ export class ReplayMovements { private isPlaying: boolean; // create static instance and getter for global use + // eslint-disable-next-line no-use-before-define @observable static _instance: ReplayMovements; static get Instance(): ReplayMovements { return ReplayMovements._instance; @@ -90,7 +91,7 @@ export class ReplayMovements { loadPresentation = (presentation: Presentation) => { const { movements } = presentation; if (movements === null) { - throw '[recordingApi.ts] followMovements() failed: no presentation data'; + throw new Error('[recordingApi.ts] followMovements() failed: no presentation data'); } movements.forEach((movement, i) => { @@ -106,9 +107,7 @@ export class ReplayMovements { // returns undefined if the docView isn't open on the screen getCollectionFFView = (doc: Doc) => { const isInView = DocumentManager.Instance.getDocumentView(doc); - if (isInView) { - return isInView.ComponentView as CollectionFreeFormView; - } + return isInView?.ComponentView as CollectionFreeFormView; }; // will open the doc in a tab then return the CollectionFFView that holds it @@ -136,9 +135,9 @@ export class ReplayMovements { if (movements === null) return new Map(); // generate a set of all unique docIds const docIdtoFirstMove = new Map<Doc, Movement>(); - for (const move of movements) { + movements.forEach(move => { if (!docIdtoFirstMove.has(move.doc)) docIdtoFirstMove.set(move.doc, move); - } + }); return docIdtoFirstMove; }; @@ -151,10 +150,10 @@ export class ReplayMovements { // console.info('playMovements', presentation, timeViewed, docIdtoDoc); if (presentation.movements === null || presentation.movements.length === 0) { - //|| this.playFFView === null) { - return new Error('[recordingApi.ts] followMovements() failed: no presentation data'); + // || this.playFFView === null) { + return '[recordingApi.ts] followMovements() failed: no presentation data'; } - if (this.isPlaying) return; + if (this.isPlaying) return undefined; this.isPlaying = true; Doc.UserDoc().presentationMode = 'watching'; @@ -170,10 +169,10 @@ export class ReplayMovements { // for the open tabs, set it to the first move const docIdtoFirstMove = this.getFirstMovements(filteredMovements); - for (const [doc, firstMove] of docIdtoFirstMove) { + Array.from(docIdtoFirstMove).forEach(([doc, firstMove]) => { const colFFView = this.getCollectionFFView(doc); if (colFFView) this.zoomAndPan(firstMove, colFFView); - } + }); }; handleFirstMovements(); @@ -197,5 +196,6 @@ export class ReplayMovements { } }, timeDiff); }); + return undefined; }; } diff --git a/src/client/util/ScriptManager.ts b/src/client/util/ScriptManager.ts index 87509f2ea..9158f6c0b 100644 --- a/src/client/util/ScriptManager.ts +++ b/src/client/util/ScriptManager.ts @@ -7,8 +7,10 @@ import { ScriptingGlobals } from './ScriptingGlobals'; export class ScriptManager { static _initialized = false; + // eslint-disable-next-line no-use-before-define private static _instance: ScriptManager; public static get Instance(): ScriptManager { + // eslint-disable-next-line no-return-assign return this._instance || (this._instance = new this()); } private constructor() { @@ -58,14 +60,16 @@ export class ScriptManager { const params = Cast(scriptDoc['data-params'], listSpec('string'), []); const paramNames = params.reduce((o: string, p: string) => { + let out = o; if (params.indexOf(p) === params.length - 1) { - o = o + p.split(':')[0].trim(); + out += p.split(':')[0].trim(); } else { - o = o + p.split(':')[0].trim() + ','; + out += p.split(':')[0].trim() + ','; } - return o; + return out; }, '' as string); + // eslint-disable-next-line no-new-func const f = new Function(paramNames, StrCast(scriptDoc.script)); Object.defineProperty(f, 'name', { value: StrCast(scriptDoc.name), writable: false }); diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index de5e8b92e..f7d7ba6a4 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -124,7 +124,7 @@ class ScriptingCompilerHost { return 'node_modules/typescript/lib/lib.d.ts'; // No idea what this means... } writeFile(fileName: string, content: string) { - const file = this.files.find(file => file.fileName === fileName); + const file = this.files.find(f => f.fileName === fileName); if (file) { file.content = content; } else { @@ -147,7 +147,7 @@ class ScriptingCompilerHost { return this.files.some(file => file.fileName === fileName); } readFile(fileName: string): string | undefined { - const file = this.files.find(file => file.fileName === fileName); + const file = this.files.find(f => f.fileName === fileName); if (file) { return file.content; } diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 36b926053..1328e90e9 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -11,6 +11,7 @@ import { ScriptingGlobals } from './ScriptingGlobals'; import { UndoManager } from './UndoManager'; export class SelectionManager { + // eslint-disable-next-line no-use-before-define private static _manager: SelectionManager; private static get Instance() { return SelectionManager._manager ?? new SelectionManager(); @@ -61,7 +62,9 @@ export class SelectionManager { dv.IsSelected = false; dv._props.whenChildContentsActiveChanged(false); }); - runInAction(() => (this.Instance.SelectedViews.length = 0)); + runInAction(() => { + this.Instance.SelectedViews.length = 0; + }); if (found) this.SelectView(found, false); }; @@ -71,17 +74,20 @@ export class SelectionManager { public static get Docs() { return this.Instance.SelectedViews.map(dv => dv.Document).filter(doc => doc?._type_collection !== CollectionViewType.Docking); } // prettier-ignore } +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, expertMode: boolean, checkContext?: boolean) { if (Doc.noviceMode && expertMode) return false; if (type === 'tab') { return SelectionManager.Views.lastElement()?._props.renderDepth === 0; } - let selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc ?? SelectionManager.Docs.lastElement()); + const selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc ?? SelectionManager.Docs.lastElement()); return selected?.type === type || selected?.type_collection === type || !type; }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function deselectAll() { SelectionManager.DeselectAll(); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function undo() { SelectionManager.DeselectAll(); return UndoManager.Undo(); @@ -89,17 +95,19 @@ ScriptingGlobals.add(function undo() { export function ShowUndoStack() { SelectionManager.DeselectAll(); - var buffer = ''; + let buffer = ''; UndoManager.undoStack.forEach((batch, i) => { buffer += 'Batch => ' + UndoManager.undoStackNames[i] + '\n'; - ///batch.forEach(undo => (buffer += ' ' + undo.prop + '\n')); + // /batch.forEach(undo => (buffer += ' ' + undo.prop + '\n')); }); alert(buffer); } +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function redo() { SelectionManager.DeselectAll(); return UndoManager.Redo(); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) { const docs = SelectionManager.Views.map(dv => dv.Document).filter( d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null)) diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx index c8df9182d..891561245 100644 --- a/src/client/util/ServerStats.tsx +++ b/src/client/util/ServerStats.tsx @@ -1,44 +1,28 @@ -import { action, computed, makeObservable, observable } from 'mobx'; +import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { MainViewModal } from '../views/MainViewModal'; import './SharingManager.scss'; import { PingManager } from './PingManager'; -import { StrCast } from '../../fields/Types'; -import { Doc } from '../../fields/Doc'; import { SettingsManager } from './SettingsManager'; @observer export class ServerStats extends React.Component<{}> { + // eslint-disable-next-line no-use-before-define public static Instance: ServerStats; @observable private isOpen = false; // whether the SharingManager modal is open or not + @observable _stats: { [key: string]: any } | undefined = undefined; // private get linkVisible() { // return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false; // } - @action - public open = async () => { - /** - * Populates the list of users. - */ - fetch('/stats').then((res: Response) => res.text().then(action(stats => (this._stats = JSON.parse(stats))))); - - this.isOpen = true; - }; - - public close = action(() => { - this.isOpen = false; - }); - constructor(props: {}) { super(props); makeObservable(this); ServerStats.Instance = this; } - @observable _stats: { [key: string]: any } | undefined = undefined; - /** * @returns the main interface of the SharingManager. */ @@ -63,7 +47,28 @@ export class ServerStats extends React.Component<{}> { ); } + // eslint-disable-next-line react/sort-comp + public close = action(() => { + this.isOpen = false; + }); + public open = async () => { + /** + * Populates the list of users. + */ + fetch('/stats').then((res: Response) => + res.text().then( + action(stats => { + this._stats = JSON.parse(stats); + }) + ) + ); + + runInAction(() => { + this.isOpen = true; + }); + }; + render() { - return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} />; + return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive closeOnExternalClick={this.close} />; } } diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 6676e4e03..5b4ac5aff 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -78,7 +78,6 @@ export class SharingManager extends React.Component<{}> { @observable private targetDocView: DocumentView | undefined = undefined; // the DocumentView of the document being shared // @observable private copied = false; @observable private dialogueBoxOpacity = 1; // for the modal - @observable private overlayOpacity = 0.4; // for the modal @observable private selectedUsers: UserOptions[] | null = null; // users (individuals/groups) selected to share with @observable private permissions: SharingPermissions = SharingPermissions.Edit; // the permission with which to share with other users @observable private individualSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of individuals @@ -661,14 +660,12 @@ export class SharingManager extends React.Component<{}> { if (docs.length) { docs.forEach(doc => doc && Doc.BrushDoc(doc)); this.dialogueBoxOpacity = 0.1; - this.overlayOpacity = 0.1; } })} onPointerLeave={action(() => { if (docs.length) { docs.forEach(doc => doc && Doc.UnBrushDoc(doc)); this.dialogueBoxOpacity = 1; - this.overlayOpacity = 0.4; } })}> {contents} @@ -742,6 +739,6 @@ export class SharingManager extends React.Component<{}> { } render() { - return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />; + return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} closeOnExternalClick={this.close} />; } } diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts index f9c2d522f..25a3c9ad8 100644 --- a/src/client/util/TrackMovements.ts +++ b/src/client/util/TrackMovements.ts @@ -1,8 +1,7 @@ -import { IReactionDisposer, makeObservable, observable, observe, reaction } from 'mobx'; +import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { NumCast } from '../../fields/Types'; import { Doc, DocListCast } from '../../fields/Doc'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; -import { Id } from '../../fields/FieldSymbols'; import { CollectionViewType } from '../documents/DocumentTypes'; export type Movement = { @@ -33,6 +32,7 @@ export class TrackMovements { private tabChangeDisposeFunc: IReactionDisposer | null; // create static instance and getter for global use + // eslint-disable-next-line no-use-before-define @observable static _instance: TrackMovements; static get Instance(): TrackMovements { return TrackMovements._instance; @@ -92,12 +92,13 @@ export class TrackMovements { // so that the size comparisons are correct, we must filter to only the FFViews const isFFView = (doc: Doc) => doc && doc._type_collection === CollectionViewType.Freeform; const tabbedFFViews = new Set<Doc>(); - for (const DashDoc of tabbedDocs) { + tabbedDocs.forEach(DashDoc => { if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc); - } + }); // new tab was added - need to add it if (tabbedFFViews.size > this.recordingFFViews.size) { + // eslint-disable-next-line no-restricted-syntax for (const DashDoc of tabbedDocs) { if (!this.recordingFFViews.has(DashDoc)) { if (isFFView(DashDoc)) { @@ -111,6 +112,7 @@ export class TrackMovements { } // tab was removed - need to remove it from recordingFFViews else if (tabbedFFViews.size < this.recordingFFViews.size) { + // eslint-disable-next-line no-restricted-syntax for (const [doc] of this.recordingFFViews) { if (!tabbedFFViews.has(doc)) { this.removeRecordingFFView(doc); @@ -208,11 +210,11 @@ export class TrackMovements { return; } - for (const [id, disposeFunc] of this.recordingFFViews) { + Array.from(this.recordingFFViews).forEach(([id, disposeFunc]) => { // console.info('calling dispose func : docId', id); disposeFunc(); - this.recordingFFViews.delete(id); - } + this.recordingFFViews?.delete(id); + }); }; private trackMovement = (panX: number, panY: number, doc: Doc, scale: number = 0) => { @@ -241,9 +243,9 @@ export class TrackMovements { // method that concatenates an array of presentatations into one public concatPresentations = (presentations: Presentation[]): Presentation => { // these three will lead to the combined presentation - let combinedMovements: Movement[] = []; + const combinedMovements: Movement[] = []; let sumTime = 0; - let combinedMetas: any[] = []; + const combinedMetas: any[] = []; presentations.forEach(presentation => { const { movements, totalTime, meta } = presentation; @@ -251,9 +253,7 @@ export class TrackMovements { // update movements if they had one if (movements) { // add the summed time to the movements - const addedTimeMovements = movements.map(move => { - return { ...move, time: move.time + sumTime }; - }); + const addedTimeMovements = movements.map(move => ({ ...move, time: move.time + sumTime })); // concat the movements already in the combined presentation with these new ones combinedMovements.push(...addedTimeMovements); } diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts index dca37c960..1a07dd6ae 100644 --- a/src/client/util/Transform.ts +++ b/src/client/util/Transform.ts @@ -116,20 +116,14 @@ export class Transform { preTransformed = (transform: Transform): Transform => this.copy().preTransform(transform); - transformPoint = (x: number, y: number): [number, number] => { - x *= this._scale; - x += this._translateX; - y *= this._scale; - y += this._translateY; - return [x, y]; - }; + transformPoint = (x: number, y: number): [number, number] => [x * this._scale + this._translateX, y * this._scale + this._translateY]; transformDirection = (x: number, y: number): [number, number] => [x * this._scale, y * this._scale]; transformBounds(x: number, y: number, width: number, height: number): { x: number; y: number; width: number; height: number } { - [x, y] = this.transformPoint(x, y); - [width, height] = this.transformDirection(width, height); - return { x, y, width, height }; + const [tx, ty] = this.transformPoint(x, y); + const [twidth, theight] = this.transformDirection(width, height); + return { x: tx, y: ty, width: twidth, height: theight }; } inverse = () => new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale, -this._rotate); diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts index 90fd299c1..9ef2aa8d7 100644 --- a/src/client/util/TypedEvent.ts +++ b/src/client/util/TypedEvent.ts @@ -14,27 +14,27 @@ export class TypedEvent<T> { on = (listener: Listener<T>): Disposable => { this.listeners.push(listener); return { - dispose: () => this.off(listener) + dispose: () => this.off(listener), }; - } + }; once = (listener: Listener<T>): void => { this.listenersOncer.push(listener); - } + }; off = (listener: Listener<T>) => { const callbackIndex = this.listeners.indexOf(listener); if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1); - } + }; emit = (event: T) => { /** Update any general listeners */ - this.listeners.forEach((listener) => listener(event)); + this.listeners.forEach(listener => listener(event)); /** Clear the `once` queue */ - this.listenersOncer.forEach((listener) => listener(event)); + this.listenersOncer.forEach(listener => listener(event)); this.listenersOncer = []; - } + }; - pipe = (te: TypedEvent<T>): Disposable => this.on((e) => te.emit(e)); -}
\ No newline at end of file + pipe = (te: TypedEvent<T>): Disposable => this.on(e => te.emit(e)); +} diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts index 6bbf55e5a..d6f3f2340 100644 --- a/src/client/util/bezierFit.ts +++ b/src/client/util/bezierFit.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-use-before-define */ /* eslint-disable prefer-destructuring */ /* eslint-disable no-param-reassign */ /* eslint-disable camelcase */ @@ -242,6 +243,7 @@ function splitCubic(p: Point[], t: number, left: Point[], right: Point[]) { * is robust: a near-tangential intersection will yield zero or two * intersections. */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars function recursively_intersect(a: Point[], t0: number, t1: number, deptha: number, b: Point[], u0: number, u1: number, depthb: number, parameters: number[][]) { if (deptha > 0) { const a1 = new Array<Point>(4); @@ -538,8 +540,8 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2: for (let i = 0; i < maxIterations; i++) { const uPrime = ReparameterizeBezier(d, first, last, u, bezCurve); // Improved parameter values GenerateBezier(d, first, last, uPrime, tHat1, tHat2, bezCurve); - const { maxError } = ComputeMaxError(d, first, last, bezCurve, uPrime); - if (maxError < error) { + const { maxError: maximumError } = ComputeMaxError(d, first, last, bezCurve, uPrime); + if (maximumError < error) { result.push(bezCurve[1]); result.push(bezCurve[2]); result.push(bezCurve[3]); diff --git a/src/client/util/reportManager/ReportManagerComponents.tsx b/src/client/util/reportManager/ReportManagerComponents.tsx index 1e226bf6d..cecebc648 100644 --- a/src/client/util/reportManager/ReportManagerComponents.tsx +++ b/src/client/util/reportManager/ReportManagerComponents.tsx @@ -1,9 +1,15 @@ +/* eslint-disable react/require-default-props */ +/* eslint-disable prefer-destructuring */ +/* 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 no-use-before-define */ import * as React from 'react'; -import { Issue } from './reportManagerSchema'; -import { darkColors, dashBlue, getLabelColors, isDarkMode, lightColors } from './reportManagerUtils'; import ReactMarkdown from 'react-markdown'; import rehypeRaw from 'rehype-raw'; import remarkGfm from 'remark-gfm'; +import { darkColors, dashBlue, getLabelColors, isDarkMode, lightColors } from './reportManagerUtils'; +import { Issue } from './reportManagerSchema'; import { StrCast } from '../../../fields/Types'; import { Doc } from '../../../fields/Doc'; @@ -18,7 +24,7 @@ interface FilterProps<T> { } // filter ui for issues (horizontal list of tags) -export const Filter = <T extends string>({ items, activeValue, setActiveValue }: FilterProps<T>) => { +export function Filter<T extends string>({ items, activeValue, setActiveValue }: FilterProps<T>) { // establishing theme const darkMode = isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)); const colors = darkMode ? darkColors : lightColors; @@ -28,7 +34,7 @@ export const Filter = <T extends string>({ items, activeValue, setActiveValue }: return ( <div className="issues-filter"> <Tag - text={'All'} + text="All" onClick={() => { setActiveValue(null); }} @@ -38,25 +44,23 @@ export const Filter = <T extends string>({ items, activeValue, setActiveValue }: borderColor={activeValue === null ? StrCast(Doc.UserDoc().userColor) : colors.border} border /> - {items.map(item => { - return ( - <Tag - key={item} - text={item} - onClick={() => { - setActiveValue(item); - }} - fontSize="12px" - backgroundColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : 'transparent'} - color={activeValue === item ? activeTagTextColor : colors.textGrey} - border - borderColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : colors.border} - /> - ); - })} + {items.map(item => ( + <Tag + key={item} + text={item} + onClick={() => { + setActiveValue(item); + }} + fontSize="12px" + backgroundColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : 'transparent'} + color={activeValue === item ? activeTagTextColor : colors.textGrey} + border + borderColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : colors.border} + /> + ))} </div> ); -}; +} interface IssueCardProps { issue: Issue; @@ -64,7 +68,7 @@ interface IssueCardProps { } // Component for the issue cards list on the left -export const IssueCard = ({ issue, onSelect }: IssueCardProps) => { +export function IssueCard({ issue, onSelect }: IssueCardProps) { const [textColor, setTextColor] = React.useState(''); const [bgColor, setBgColor] = React.useState('transparent'); const [borderColor, setBorderColor] = React.useState('transparent'); @@ -103,14 +107,14 @@ export const IssueCard = ({ issue, onSelect }: IssueCardProps) => { <h3 className="issue-title">{issue.title}</h3> </div> ); -}; +} interface IssueViewProps { issue: Issue; } // Detailed issue view that displays on the right -export const IssueView = ({ issue }: IssueViewProps) => { +export function IssueView({ issue }: IssueViewProps) { const [issueBody, setIssueBody] = React.useState(''); // Parses the issue body into a formatted markdown (main functionality is replacing urls with tags) @@ -127,15 +131,16 @@ export const IssueView = ({ issue }: IssueViewProps) => { parts.map(async part => { if (imgTagRegex.test(part) || videoTagRegex.test(part) || audioTagRegex.test(part)) { return `\n${await parseFileTag(part)}\n`; - } else if (fileRegex.test(part)) { + } + if (fileRegex.test(part)) { const tag = await parseDashFiles(part); return tag; - } else if (localRegex.test(part)) { + } + if (localRegex.test(part)) { const tag = await parseLocalFiles(part); return tag; - } else { - return part; } + return part; }) ); @@ -143,7 +148,7 @@ export const IssueView = ({ issue }: IssueViewProps) => { }; // Extracts the src from an image tag and either returns the raw url if not accessible or a new image tag - const parseFileTag = async (tag: string): Promise<string> => { + const parseFileTag = async (tag: string): Promise<string | undefined> => { const regex = /src="([^"]+)"/; let url = ''; const match = tag.match(regex); @@ -160,18 +165,19 @@ export const IssueView = ({ issue }: IssueViewProps) => { case '.png': case '.jpeg': case '.gif': - return await getDisplayedFile(url, 'image'); + return getDisplayedFile(url, 'image'); // video case '.mp4': case '.mpeg': case '.webm': case '.mov': - return await getDisplayedFile(url, 'video'); - //audio + return getDisplayedFile(url, 'video'); + // audio case '.mp3': case '.wav': case '.ogg': - return await getDisplayedFile(url, 'audio'); + return getDisplayedFile(url, 'audio'); + default: } return tag; }; @@ -183,14 +189,15 @@ export const IssueView = ({ issue }: IssueViewProps) => { const dashAudioRegex = /https:\/\/browndash\.com\/files[/\\]audio/; if (dashImgRegex.test(url)) { - return await getDisplayedFile(url, 'image'); - } else if (dashVideoRegex.test(url)) { - return await getDisplayedFile(url, 'video'); - } else if (dashAudioRegex.test(url)) { - return await getDisplayedFile(url, 'audio'); - } else { - return url; + return getDisplayedFile(url, 'image'); } + if (dashVideoRegex.test(url)) { + return getDisplayedFile(url, 'video'); + } + if (dashAudioRegex.test(url)) { + return getDisplayedFile(url, 'audio'); + } + return url; }; // Returns the corresponding HTML tag for a src url @@ -200,31 +207,37 @@ export const IssueView = ({ issue }: IssueViewProps) => { const dashAudioRegex = /http:\/\/localhost:1050\.com\/files[/\\]audio/; if (imgRegex.test(url)) { - return await getDisplayedFile(url, 'image'); - } else if (dashVideoRegex.test(url)) { - return await getDisplayedFile(url, 'video'); - } else if (dashAudioRegex.test(url)) { - return await getDisplayedFile(url, 'audio'); - } else { - return url; + return getDisplayedFile(url, 'image'); + } + if (dashVideoRegex.test(url)) { + return getDisplayedFile(url, 'video'); + } + if (dashAudioRegex.test(url)) { + return getDisplayedFile(url, 'audio'); } + return url; }; - const getDisplayedFile = async (url: string, fileType: 'image' | 'video' | 'audio'): Promise<string> => { + const getDisplayedFile = async (url: string, fileType: 'image' | 'video' | 'audio'): Promise<string | undefined> => { switch (fileType) { - case 'image': + case 'image': { const imgValid = await isImgValid(url); if (!imgValid) return `\n${url} (This image could not be loaded)\n`; return `\n${url}\n<img width="100%" alt="Issue asset" src=${url} />\n`; - case 'video': + } + case 'video': { const videoValid = await isVideoValid(url); if (!videoValid) return `\n${url} (This video could not be loaded)\n`; return `\n${url}\n<video class="report-default-video" width="100%" controls alt="Issue asset" src=${url} />\n`; - case 'audio': + } + case 'audio': { const audioValid = await isAudioValid(url); if (!audioValid) return `\n${url} (This audio could not be loaded)\n`; return `\n${url}\n<audio src=${url} controls />\n`; + } + default: } + return undefined; }; // Loads an image and returns a promise that resolves as whether the image is valid or not @@ -270,7 +283,7 @@ export const IssueView = ({ issue }: IssueViewProps) => { <div className="issue-view"> <span className="issue-label"> Issue{' '} - <a className="issue-link" href={issue.html_url} target="_blank"> + <a className="issue-link" href={issue.html_url} target="_blank" rel="noreferrer"> #{issue.number} </a> </span> @@ -292,7 +305,7 @@ export const IssueView = ({ issue }: IssueViewProps) => { <ReactMarkdown children={issueBody} className="issue-content" remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} /> </div> ); -}; +} interface TagProps { text: string; @@ -305,7 +318,7 @@ interface TagProps { } // Small tag for labels of the issue -export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) => { +export function Tag({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) { return ( <div onClick={onClick ?? (() => {})} @@ -314,14 +327,14 @@ export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColo {text} </div> ); -}; +} interface FormInputProps { value: string; placeholder: string; onChange: (val: string) => void; } -export const FormInput = ({ value, placeholder, onChange }: FormInputProps) => { +export function FormInput({ value, placeholder, onChange }: FormInputProps) { const [inputBorderColor, setInputBorderColor] = React.useState(''); return ( @@ -349,9 +362,9 @@ export const FormInput = ({ value, placeholder, onChange }: FormInputProps) => { }} /> ); -}; +} -export const FormTextArea = ({ value, placeholder, onChange }: FormInputProps) => { +export function FormTextArea({ value, placeholder, onChange }: FormInputProps) { const [textAreaBorderColor, setTextAreaBorderColor] = React.useState(''); return ( @@ -378,4 +391,4 @@ export const FormTextArea = ({ value, placeholder, onChange }: FormInputProps) = }} /> ); -}; +} diff --git a/src/client/util/reportManager/reportManagerSchema.ts b/src/client/util/reportManager/reportManagerSchema.ts index 9a1c7c3e9..171c24393 100644 --- a/src/client/util/reportManager/reportManagerSchema.ts +++ b/src/client/util/reportManager/reportManagerSchema.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-use-before-define */ /** * Issue interface schema from Github. */ diff --git a/src/client/util/reportManager/reportManagerUtils.ts b/src/client/util/reportManager/reportManagerUtils.ts index 22e5eebbb..f14967e0a 100644 --- a/src/client/util/reportManager/reportManagerUtils.ts +++ b/src/client/util/reportManager/reportManagerUtils.ts @@ -63,9 +63,8 @@ export const getAllIssues = async (octokit: Octokit): Promise<any[]> => { // 200 status means success if (res.status === 200) { return res.data; - } else { - throw new Error('Error getting issues'); } + throw new Error('Error getting issues'); }; /** @@ -104,9 +103,7 @@ export const fileLinktoServerLink = (fileLink: string): string => { * @param link response from file upload * @returns server file path */ -export const getServerPath = (link: any): string => { - return link.result.accessPaths.agnostic.server as string; -}; +export const getServerPath = (link: any): string => link.result.accessPaths.agnostic.server as string; /** * Uploads media files to the server. @@ -124,6 +121,7 @@ export const uploadFilesToServer = async (mediaFiles: FileData[]): Promise<strin alert(err); } } + return undefined; }; // helper functions @@ -141,18 +139,16 @@ export const passesTagFilter = (issue: Issue, priorityFilter: string | null, bug passesPriority = issue.labels.some(label => { if (typeof label === 'string') { return label === priorityFilter; - } else { - return label.name === priorityFilter; } + return label.name === priorityFilter; }); } if (bugFilter) { passesBug = issue.labels.some(label => { if (typeof label === 'string') { return label === bugFilter; - } else { - return label.name === bugFilter; } + return label.name === bugFilter; }); } return passesPriority && passesBug; @@ -217,7 +213,8 @@ export const bugColors: { [key: string]: string[] } = { export const getLabelColors = (label: string): string[] => { if (prioritySet.has(label as Priority)) { return priorityColors[label]; - } else if (bugSet.has(label as BugType)) { + } + if (bugSet.has(label as BugType)) { return bugColors[label]; } return ['#0f73f6', '#ffffff']; |
