diff options
Diffstat (limited to 'src')
110 files changed, 1913 insertions, 1891 deletions
diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx index 855f48f7e..5269f763b 100644 --- a/src/client/apis/GoogleAuthenticationManager.tsx +++ b/src/client/apis/GoogleAuthenticationManager.tsx @@ -1,14 +1,14 @@ -import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { Opt } from "../../fields/Doc"; -import { Networking } from "../Network"; -import { ScriptingGlobals } from "../util/ScriptingGlobals"; -import { MainViewModal } from "../views/MainViewModal"; -import "./GoogleAuthenticationManager.scss"; +import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Opt } from '../../fields/Doc'; +import { Networking } from '../Network'; +import { ScriptingGlobals } from '../util/ScriptingGlobals'; +import { MainViewModal } from '../views/MainViewModal'; +import './GoogleAuthenticationManager.scss'; -const AuthenticationUrl = "https://accounts.google.com/o/oauth2/v2/auth"; -const prompt = "Paste authorization code here..."; +const AuthenticationUrl = 'https://accounts.google.com/o/oauth2/v2/auth'; +const prompt = 'Paste authorization code here...'; @observer export class GoogleAuthenticationManager extends React.Component<{}> { @@ -23,11 +23,11 @@ export class GoogleAuthenticationManager extends React.Component<{}> { private disposer: Opt<IReactionDisposer>; private set isOpen(value: boolean) { - runInAction(() => this.openState = value); + runInAction(() => (this.openState = value)); } private set shouldShowPasteTarget(value: boolean) { - runInAction(() => this.showPasteTargetState = value); + runInAction(() => (this.showPasteTargetState = value)); } public cancel() { @@ -35,7 +35,7 @@ export class GoogleAuthenticationManager extends React.Component<{}> { } public fetchOrGenerateAccessToken = async (displayIfFound = false) => { - let response: any = await Networking.FetchFromServer("/readGoogleAccessToken"); + let response: any = await Networking.FetchFromServer('/readGoogleAccessToken'); // if this is an authentication url, activate the UI to register the new access token if (new RegExp(AuthenticationUrl).test(response)) { this.isOpen = true; @@ -47,7 +47,7 @@ export class GoogleAuthenticationManager extends React.Component<{}> { async authenticationCode => { if (authenticationCode && /\d{1}\/[\w-]{55}/.test(authenticationCode)) { this.disposer?.(); - const response = await Networking.PostToServer("/writeGoogleAccessToken", { authenticationCode }); + const response = await Networking.PostToServer('/writeGoogleAccessToken', { authenticationCode }); runInAction(() => { this.success = true; this.credentials = response; @@ -71,7 +71,7 @@ export class GoogleAuthenticationManager extends React.Component<{}> { this.isOpen = true; } return response.access_token; - } + }; resetState = action((visibleForMS: number = 3000, fadesOutInMS: number = 500) => { if (!visibleForMS && !fadesOutInMS) { @@ -89,14 +89,20 @@ export class GoogleAuthenticationManager extends React.Component<{}> { this.displayLauncher = false; this.shouldShowPasteTarget = false; if (visibleForMS > 0 && fadesOutInMS > 0) { - setTimeout(action(() => { - this.isOpen = false; - setTimeout(action(() => { - this.success = undefined; - this.displayLauncher = true; - this.credentials = undefined; - }), fadesOutInMS); - }), visibleForMS); + setTimeout( + action(() => { + this.isOpen = false; + setTimeout( + action(() => { + this.success = undefined; + this.displayLauncher = true; + this.credentials = undefined; + }), + fadesOutInMS + ); + }), + visibleForMS + ); } }); @@ -108,61 +114,44 @@ export class GoogleAuthenticationManager extends React.Component<{}> { private get renderPrompt() { return ( <div className={'authorize-container'}> - - {this.displayLauncher ? <button - className={"dispatch"} - onClick={() => { - window.open(this.authenticationLink); - setTimeout(() => this.shouldShowPasteTarget = true, 500); - }} - style={{ marginBottom: this.showPasteTargetState ? 15 : 0 }} - >Authorize a Google account...</button> : (null)} - {this.showPasteTargetState ? <input - className={'paste-target'} - onChange={action(e => this.authenticationCode = e.currentTarget.value)} - placeholder={prompt} - /> : (null)} - {this.credentials ? + {this.displayLauncher ? ( + <button + className={'dispatch'} + onClick={() => { + window.open(this.authenticationLink); + setTimeout(() => (this.shouldShowPasteTarget = true), 500); + }} + style={{ marginBottom: this.showPasteTargetState ? 15 : 0 }}> + Authorize a Google account... + </button> + ) : null} + {this.showPasteTargetState ? <input className={'paste-target'} onChange={action(e => (this.authenticationCode = e.currentTarget.value))} placeholder={prompt} /> : null} + {this.credentials ? ( <> - <img - className={'avatar'} - src={this.credentials.userInfo.picture} - /> - <span - className={'welcome'} - >Welcome to Dash, {this.credentials.userInfo.name} - </span> + <img className={'avatar'} src={this.credentials.userInfo.picture} /> + <span className={'welcome'}>Welcome to Dash, {this.credentials.userInfo.name}</span> <div className={'disconnect'} onClick={async () => { - await Networking.FetchFromServer("/revokeGoogleAccessToken"); + await Networking.FetchFromServer('/revokeGoogleAccessToken'); this.resetState(0, 0); - }} - >Disconnect Account</div> - </> : (null)} + }}> + Disconnect Account + </div> + </> + ) : null} </div> ); } private get dialogueBoxStyle() { - const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red"; - return { borderColor, transition: "0.2s borderColor ease", zIndex: 1002 }; + const borderColor = this.success === undefined ? 'black' : this.success ? 'green' : 'red'; + return { borderColor, transition: '0.2s borderColor ease', zIndex: 1002 }; } render() { - return ( - <MainViewModal - isDisplayed={this.openState} - interactive={true} - contents={this.renderPrompt} - // overlayDisplayedOpacity={0.9} - dialogueBoxStyle={this.dialogueBoxStyle} - overlayStyle={{ zIndex: 1001 }} - closeOnExternalClick={action(() => this.isOpen = false)} - /> - ); + return <MainViewModal isDisplayed={this.openState} interactive={true} contents={this.renderPrompt} dialogueBoxStyle={this.dialogueBoxStyle} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={action(() => (this.isOpen = false))} />; } - } -ScriptingGlobals.add("GoogleAuthenticationManager", GoogleAuthenticationManager);
\ No newline at end of file +ScriptingGlobals.add('GoogleAuthenticationManager', GoogleAuthenticationManager); diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts index 07a2708ec..1eb977813 100644 --- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts +++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts @@ -291,7 +291,7 @@ export namespace GooglePhotos { response.results.map( item => new Promise<string>(resolve => { - Query.GetImage(item.mediaItem.id).then(item => resolve(item.baseUrl)); + Query.GetImage(item.mediaItem.id).then(itm => resolve(itm.baseUrl)); }) ) ); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f827ea81c..cf397e85a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,9 +1,13 @@ +/* eslint-disable prefer-destructuring */ +/* eslint-disable default-param-last */ +/* eslint-disable no-use-before-define */ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { saveAs } from 'file-saver'; import * as JSZip from 'jszip'; import { action, reaction, runInAction } from 'mobx'; import { basename } from 'path'; import { ClientUtils, OmitKeys } from '../../ClientUtils'; +// eslint-disable-next-line import/extensions import * as JSZipUtils from '../../JSZipUtils'; import { decycle } from '../../decycler/decycler'; import { DateField } from '../../fields/DateField'; @@ -81,6 +85,7 @@ export enum FInfoFieldType { string = 'string', boolean = 'boolean', number = 'number', + // eslint-disable-next-line @typescript-eslint/no-shadow Doc = 'Doc', enumeration = 'enum', date = 'date', @@ -1360,9 +1365,9 @@ export namespace DocUtils { if (key === LinkedTo) { // links are not a field value, so handled here. value is an expression of form ([field=]idToDoc("...")) const allLinks = LinkManager.Instance.getAllRelatedLinks(doc); - const matchLink = (value: string, anchor: Doc) => { - const linkedToExp = (value ?? '').split('='); - if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value; + const matchLink = (val: string, anchor: Doc) => { + const linkedToExp = (val ?? '').split('='); + if (linkedToExp.length === 1) return Field.toScriptString(anchor) === val; return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1]; }; // prettier-ignore @@ -1493,17 +1498,17 @@ export namespace DocUtils { if (!linkSettings.link_relationship) linkSettings.link_relationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link'; if (target.doc === Doc.UserDoc()) return undefined; - const makeLink = action((linkDoc: Doc, showPopup?: number[]) => { - if (showPopup) { + const makeLink = action((linkDoc: Doc, showAt?: number[]) => { + if (showAt) { LinkManager.Instance.currentLink = linkDoc; TaskCompletionBox.textDisplayed = 'Link Created'; - TaskCompletionBox.popupX = showPopup[0]; - TaskCompletionBox.popupY = showPopup[1] - 33; + TaskCompletionBox.popupX = showAt[0]; + TaskCompletionBox.popupY = showAt[1] - 33; TaskCompletionBox.taskCompleted = true; - LinkDescriptionPopup.Instance.popupX = showPopup[0]; - LinkDescriptionPopup.Instance.popupY = showPopup[1]; + LinkDescriptionPopup.Instance.popupX = showAt[0]; + LinkDescriptionPopup.Instance.popupY = showAt[1]; LinkDescriptionPopup.Instance.display = true; const rect = document.body.getBoundingClientRect(); @@ -1811,7 +1816,7 @@ export namespace DocUtils { batch.end(); return doc; } - export function findTemplate(templateName: string, type: string, signature: string) { + export function findTemplate(templateName: string, type: string) { let docLayoutTemplate: Opt<Doc>; const iconViews = DocListCast(Cast(Doc.UserDoc().template_icons, Doc, null)?.data); const templBtns = DocListCast(Cast(Doc.UserDoc().template_buttons, Doc, null)?.data); @@ -1841,7 +1846,7 @@ export namespace DocUtils { const templateName = templateSignature.replace(/\(.*\)/, ''); doc.layout_fieldKey = 'layout_' + (templateSignature || (docLayoutTemplate?.title ?? '')); // eslint-disable-next-line no-param-reassign - docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type), templateSignature); + docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type)); const customName = 'layout_' + templateSignature; const _width = NumCast(doc._width); @@ -2166,12 +2171,16 @@ export namespace DocUtils { const docs: { [id: string]: any } = {}; const links: { [id: string]: any } = {}; - Array.from(map.entries()).forEach(f => (docs[f[0]] = f[1])); - Array.from(linkMap.entries()).forEach(l => (links[l[0]] = l[1])); + Array.from(map.entries()).forEach(f => { + docs[f[0]] = f[1]; + }); + Array.from(linkMap.entries()).forEach(l => { + links[l[0]] = l[1]; + }); const jsonDocs = JSON.stringify({ id: clone[Id], docs, links }, decycle(replacer)); const zip = new JSZip(); - var count = 0; + let count = 0; const promArr = Array.from(proms) .filter(url => url?.startsWith('/files')) .map(url => url.replace('/', '')); // window.location.origin)); 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']; diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index f03e316a2..57498f39f 100644 --- a/src/client/views/animationtimeline/Timeline.tsx +++ b/src/client/views/animationtimeline/Timeline.tsx @@ -47,13 +47,13 @@ import { Track } from './Track'; @observer export class Timeline extends ObservableReactComponent<FieldViewProps> { - //readonly constants + // readonly constants private readonly DEFAULT_TICK_SPACING: number = 50; private readonly MAX_TITLE_HEIGHT = 75; private readonly MAX_CONTAINER_HEIGHT: number = 800; private readonly DEFAULT_TICK_INCREMENT: number = 1000; - //height variables + // height variables private DEFAULT_CONTAINER_HEIGHT: number = 330; private MIN_CONTAINER_HEIGHT: number = 205; @@ -62,7 +62,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> { makeObservable(this); } - //react refs + // react refs @observable private _trackbox = React.createRef<HTMLDivElement>(); @observable private _titleContainer = React.createRef<HTMLDivElement>(); @observable private _timelineContainer = React.createRef<HTMLDivElement>(); @@ -70,7 +70,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> { @observable private _roundToggleRef = React.createRef<HTMLDivElement>(); @observable private _roundToggleContainerRef = React.createRef<HTMLDivElement>(); - //boolean vars and instance vars + // boolean vars and instance vars @observable private _currentBarX: number = 0; @observable private _windSpeed: number = 1; @observable private _totalLength: number = 0; @@ -79,11 +79,11 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> { @observable private _containerHeight: number = this.DEFAULT_CONTAINER_HEIGHT; @observable private _tickSpacing = this.DEFAULT_TICK_SPACING; @observable private _tickIncrement = this.DEFAULT_TICK_INCREMENT; - @observable private _time = 100000; //DEFAULT + @observable private _time = 100000; // DEFAULT @observable private _playButton = faPlayCircle; @observable private _titleHeight = 0; - @observable public IsPlaying: boolean = false; //scrubber playing + @observable public IsPlaying: boolean = false; // scrubber playing /** * collection get method. Basically defines what defines collection's children. These will be tracked in the timeline. Do not edit. @@ -97,30 +97,30 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> { return DocListCast(this._props.Document[this._props.fieldKey]); } - /////////lifecycle functions//////////// + /// //////lifecycle functions//////////// @action componentDidMount() { - const relativeHeight = window.innerHeight / 20; //sets height to arbitrary size, relative to innerHeight - this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; //check if relHeight is less than Maxheight. Else, just set relheight to max - this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; //offset - this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; //twice the titleheight + offset + const relativeHeight = window.innerHeight / 20; // sets height to arbitrary size, relative to innerHeight + this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; // check if relHeight is less than Maxheight. Else, just set relheight to max + this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; // offset + this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; // twice the titleheight + offset if (!this._props.Document.AnimationLength) { - //if animation length did not exist - this._props.Document.AnimationLength = this._time; //set it to default time + // if animation length did not exist + this._props.Document.AnimationLength = this._time; // set it to default time } else { - this._time = NumCast(this._props.Document.AnimationLength); //else, set time to animationlength stored from before + this._time = NumCast(this._props.Document.AnimationLength); // else, set time to animationlength stored from before } - this._totalLength = this._tickSpacing * (this._time / this._tickIncrement); //the entire length of the timeline div (actual div part itself) - this._visibleLength = this._infoContainer.current!.getBoundingClientRect().width; //the visible length of the timeline (the length that you current see) - this._visibleStart = this._infoContainer.current!.scrollLeft; //where the div starts - this._props.Document.isATOn = !this._props.Document.isATOn; //turns the boolean on, saying AT (animation timeline) is on + this._totalLength = this._tickSpacing * (this._time / this._tickIncrement); // the entire length of the timeline div (actual div part itself) + this._visibleLength = this._infoContainer.current!.getBoundingClientRect().width; // the visible length of the timeline (the length that you current see) + this._visibleStart = this._infoContainer.current!.scrollLeft; // where the div starts + this._props.Document.isATOn = !this._props.Document.isATOn; // turns the boolean on, saying AT (animation timeline) is on this.toggleHandle(); } componentWillUnmount() { - this._props.Document.AnimationLength = this._time; //save animation length + this._props.Document.AnimationLength = this._time; // save animation length } - ///////////////////////////////////////////////// + /// ////////////////////////////////////////////// /** * React Functional Component @@ -148,7 +148,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> { pixel <= 0 ? (this._currentBarX = 0) : pixel >= this._totalLength ? (this._currentBarX = this._totalLength) : (this._currentBarX = pixel); }; - //for playing + // for playing onPlay = (e: React.MouseEvent) => { e.stopPropagation(); this.play(); @@ -181,7 +181,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> { e.preventDefault(); e.stopPropagation(); if (this._windSpeed < 64) { - //max speed is 32 + // max speed is 32 this._windSpeed = this._windSpeed * 2; } }; @@ -215,7 +215,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> { const scrubberbox = this._infoContainer.current!; const left = scrubberbox.getBoundingClientRect().left; const offsetX = Math.round(e.clientX - left) * this._props.ScreenToLocalTransform().Scale; - this.changeCurrentBarX(offsetX + this._visibleStart); //changes scrubber to clicked scrubber position + this.changeCurrentBarX(offsetX + this._visibleStart); // changes scrubber to clicked scrubber position return false; }; @@ -355,12 +355,12 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> { * tool box includes the toggle buttons at the top of the timeline (both editing mode and play mode) */ private timelineToolBox = (scale: number, totalTime: number) => { - const size = 40 * scale; //50 is default + const size = 40 * scale; // 50 is default const iconSize = 25; const width: number = this._props.PanelWidth(); const modeType = this._props.Document.isATOn ? 'Author' : 'Play'; - //decides if information should be omitted because the timeline is very small + // decides if information should be omitted because the timeline is very small // if its less than 950 pixels then it's going to be overlapping let modeString = modeType, overviewString = '', @@ -469,7 +469,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> { this._props.Document.isATOn = !this._props.Document.isATOn; if (!BoolCast(this._props.Document.isATOn)) { - //turning on playmode... + // turning on playmode... roundToggle.style.transform = 'translate(0px, 0px)'; roundToggle.style.animationName = 'turnoff'; roundToggleContainer.style.animationName = 'turnoff'; @@ -477,7 +477,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> { timelineContainer.style.top = `${-this._containerHeight}px`; this.toPlay(); } else { - //turning on authoring mode... + // turning on authoring mode... roundToggle.style.transform = 'translate(20px, 0px)'; roundToggle.style.animationName = 'turnon'; roundToggleContainer.style.animationName = 'turnon'; @@ -490,8 +490,8 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> { @action.bound changeLengths() { if (this._infoContainer.current) { - this._visibleLength = this._infoContainer.current.getBoundingClientRect().width; //the visible length of the timeline (the length that you current see) - this._visibleStart = this._infoContainer.current.scrollLeft; //where the div starts + this._visibleLength = this._infoContainer.current.getBoundingClientRect().width; // the visible length of the timeline (the length that you current see) + this._visibleStart = this._infoContainer.current.scrollLeft; // where the div starts } } @@ -515,7 +515,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> { } } } else { - //TODO: remove undefineds and duplicates + // TODO: remove undefineds and duplicates } }); return longestTime; diff --git a/src/client/views/animationtimeline/TimelineOverview.tsx b/src/client/views/animationtimeline/TimelineOverview.tsx index 489c4dcde..7bf685c9e 100644 --- a/src/client/views/animationtimeline/TimelineOverview.tsx +++ b/src/client/views/animationtimeline/TimelineOverview.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/no-unused-prop-types */ import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -32,7 +33,6 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> { @observable private visibleTime: number = 0; @observable private currentX: number = 0; @observable private visibleStart: number = 0; - private readonly DEFAULT_HEIGHT = 50; private readonly DEFAULT_WIDTH = 300; componentDidMount() { @@ -50,9 +50,9 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> { ); } - componentWillUnmount = () => { + componentWillUnmount() { this._authoringReaction && this._authoringReaction(); - }; + } @action setOverviewWidth() { @@ -109,7 +109,7 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> { e.preventDefault(); e.stopPropagation(); const scrubberRef = this._scrubberRef.current!; - const left = scrubberRef.getBoundingClientRect().left; + const { left } = scrubberRef.getBoundingClientRect(); const offsetX = Math.round(e.clientX - left); this.props.changeCurrentBarX((offsetX / this.activeOverviewWidth) * this.props.totalLength + this.props.currentBarX); }; @@ -152,17 +152,17 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> { const timeline = this.props.isAuthoring ? [ <div key="timeline-overview-container" className="timeline-overview-container overviewBar" id="timelineOverview" ref={this.authoringContainer}> - <div ref={this._visibleRef} key="1" className="timeline-overview-visible" style={{ left: `${barStart}px`, width: `${visibleBarWidth}px` }} onPointerDown={this.onPointerDown}></div>, + <div ref={this._visibleRef} key="1" className="timeline-overview-visible" style={{ left: `${barStart}px`, width: `${visibleBarWidth}px` }} onPointerDown={this.onPointerDown} />, <div ref={this._scrubberRef} key="2" className="timeline-overview-scrubber-container" style={{ left: `${scrubberStart}px` }} onPointerDown={this.onScrubberDown}> - <div key="timeline-overview-scrubber-head" className="timeline-overview-scrubber-head"></div> + <div key="timeline-overview-scrubber-head" className="timeline-overview-scrubber-head" /> </div> </div>, ] : [ <div key="1" className="timeline-play-bar overviewBar" id="timelinePlay" ref={this.playbackContainer}> - <div ref={this._scrubberRef} className="timeline-play-head" style={{ left: `${scrubberStart}px` }} onPointerDown={this.onScrubberDown}></div> + <div ref={this._scrubberRef} className="timeline-play-head" style={{ left: `${scrubberStart}px` }} onPointerDown={this.onScrubberDown} /> </div>, - <div key="2" className="timeline-play-tail" style={{ width: `${playWidth}px` }}></div>, + <div key="2" className="timeline-play-tail" style={{ width: `${playWidth}px` }} />, ]; return ( <div className="timeline-flex"> diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx index 578ce77a5..43e5a68eb 100644 --- a/src/client/views/collections/CollectionCalendarView.tsx +++ b/src/client/views/collections/CollectionCalendarView.tsx @@ -25,16 +25,13 @@ export class CollectionCalendarView extends CollectionSubView() { removeCalendar = () => {}; - addCalendar = (doc: Doc | Doc[], annotationKey?: string | undefined): boolean => { + addCalendar = (/* doc: Doc | Doc[], annotationKey?: string | undefined */): boolean => // bring up calendar modal with option to create a calendar - return true; - }; + true; _stackRef = React.createRef<CollectionStackingView>(); - panelHeight = () => { - return this._props.PanelHeight() - 40; // this should be the height of the stacking view. For now, it's the hieight of the calendar view minus 40 to allow for a title - }; + panelHeight = () => this._props.PanelHeight() - 40; // this should be the height of the stacking view. For now, it's the hieight of the calendar view minus 40 to allow for a title // most recent calendar should come first sortByMostRecentDate = (calendarA: Doc, calendarB: Doc) => { @@ -46,18 +43,18 @@ export class CollectionCalendarView extends CollectionSubView() { if (aFromDate > bFromDate) { return -1; // a comes first - } else if (aFromDate < bFromDate) { + } + if (aFromDate < bFromDate) { + return 1; // b comes first + } + // start dates are the same + if (aToDate > bToDate) { + return -1; // a comes first + } + if (aToDate < bToDate) { return 1; // b comes first - } else { - // start dates are the same - if (aToDate > bToDate) { - return -1; // a comes first - } else if (aToDate < bToDate) { - return 1; // b comes first - } else { - return 0; // same start and end dates - } } + return 0; // same start and end dates }; screenToLocalTransform = () => @@ -74,6 +71,7 @@ export class CollectionCalendarView extends CollectionSubView() { return ( <div className="collectionCalendarView"> <CollectionStackingView + // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} setContentViewBox={emptyFunction} ref={this._stackRef} diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 6dda6e545..51352d3e2 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -1,3 +1,6 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable react/jsx-props-no-spreading */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; @@ -107,10 +110,10 @@ export class CollectionCarouselView extends CollectionSubView() { return ( <> <div key="back" className="carouselView-back" onClick={this.goback}> - <FontAwesomeIcon icon={'chevron-left'} size={'2x'} /> + <FontAwesomeIcon icon="chevron-left" size="2x" /> </div> <div key="fwd" className="carouselView-fwd" onClick={this.advance}> - <FontAwesomeIcon icon={'chevron-right'} size={'2x'} /> + <FontAwesomeIcon icon="chevron-right" size="2x" /> </div> </> ); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 9a1781b93..fefaf6591 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -120,7 +120,7 @@ export class CollectionDockingView extends CollectionSubView() { @undoBatch public static CloseSplit(document: Opt<Doc>, panelName?: string): boolean { if (CollectionDockingView.Instance) { - const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tab => (panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document)); + const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tabView => (panelName ? tabView.contentItem.config.props.panelName === panelName : tabView.DashDoc === document)); if (tab) { const j = tab.header.parent.contentItems.indexOf(tab.contentItem); if (j !== -1) { @@ -147,7 +147,7 @@ export class CollectionDockingView extends CollectionSubView() { stack.contentItems[activeContentItemIndex].remove(); return instance.layoutChanged(); } - const tab = Array.from(instance.tabMap.keys()).find(tab => tab.contentItem.config.props.panelName === panelName); + const tab = Array.from(instance.tabMap.keys()).find(tabView => tabView.contentItem.config.props.panelName === panelName); if (tab) { const j = tab.header.parent.contentItems.indexOf(tab.contentItem); if (newConfig.props.documentId !== tab.header.parent.contentItems[j].config.props.documentId) { @@ -172,7 +172,7 @@ export class CollectionDockingView extends CollectionSubView() { public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) { if (document?._type_collection === CollectionViewType.Docking && !keyValue) return DashboardView.openDashboard(document); if (!CollectionDockingView.Instance) return false; - const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document && !tab.contentItem.config.props.keyValue && !keyValue); + const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tabView => tabView.DashDoc === document && !tabView.contentItem.config.props.keyValue && !keyValue); if (tab) { tab.header.parent.setActiveContentItem(tab.contentItem); return true; diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx index e00fdb50c..95aecc7d0 100644 --- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx @@ -1,3 +1,4 @@ +/* eslint-disable jsx-a11y/control-has-associated-label */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; @@ -14,7 +15,7 @@ import { DocUtils, Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; -import { undoBatch } from '../../util/UndoManager'; +import { undoBatch, undoable } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from '../EditableView'; @@ -113,13 +114,15 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV }; @action - headingChanged = (value: string, shiftDown?: boolean) => { + headingChanged = (value: string /* , shiftDown?: boolean */) => { const castedValue = this.getValue(value); if (castedValue) { if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) { return false; } - this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue)); + this._props.docList.forEach(d => { + d[this._props.pivotField] = castedValue; + }); if (this._props.headingObject) { this._props.headingObject.setHeading(castedValue.toString()); this._heading = this._props.headingObject.heading; @@ -129,10 +132,14 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV return false; }; - @action pointerEntered = () => (this._hover = true); - @action pointerLeave = () => (this._hover = false); - @undoBatch - addTextNote = (char: string) => this.addNewTextDoc('-typed text-', false, true); + @action pointerEntered = () => { + this._hover = true; + }; + @action pointerLeave = () => { + this._hover = false; + }; + + addTextNote = undoable(() => this.addNewTextDoc('-typed text-', false, true), 'add text note'); // addNewTextDoc is called when a user starts typing in a column to create a new node @action @@ -195,6 +202,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV } return this._props.addDocument?.(created); } + return undefined; }, icon: 'compress-arrows-alt', }) @@ -214,6 +222,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV } return this._props.addDocument?.(created) || false; } + return undefined; }, icon: 'compress-arrows-alt', }) @@ -238,7 +247,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV const key = this._props.pivotField; const heading = this._heading; const columnYMargin = this._props.headingObject ? 0 : this._props.yMargin; - const evContents = heading ? heading : '25'; + const evContents = heading || '25'; const headingView = this._props.headingObject ? ( <div key={heading} @@ -252,17 +261,16 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV className="collectionNoteTakingView-sectionHeader-subCont" title={evContents === `No Value` ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ''} style={{ background: evContents !== `No Value` ? this._color : 'inherit' }}> - <EditableView GetValue={() => evContents} isEditingCallback={isEditing => isEditing && this._props.select(false)} SetValue={this.headingChanged} contents={evContents} oneLine={true} /> + <EditableView GetValue={() => evContents} isEditingCallback={isEditing => isEditing && this._props.select(false)} SetValue={this.headingChanged} contents={evContents} oneLine /> </div> {(this._props.colHeaderData?.length ?? 0) > 1 && ( - <button className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}> + <button type="button" className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}> <FontAwesomeIcon icon="trash" size="lg" /> </button> )} </div> ) : null; const templatecols = this.columnWidth; - const type = this._props.Document.type; return ( <> {headingView} @@ -270,7 +278,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV <div className="collectionNoteTakingView-columnStack"> <div key={`${heading}-stack`} - className={`collectionNoteTakingView-Nodes`} + className="collectionNoteTakingView-Nodes" style={{ padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`, gridGap: this._props.gridGap, @@ -282,10 +290,13 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV {!this._props.chromeHidden ? ( <div className="collectionNoteTakingView-DocumentButtons" style={{ display: this._props.isContentActive() ? 'flex' : 'none', marginBottom: 10 }}> <div className="collectionNoteTakingView-addDocumentButton" style={{ color: lightOrDark(this._props.backgroundColor?.()) }}> - <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.addTextNote} placeholder={"Type ':' for commands"} contents={'+ Node'} menuCallback={this.menuCallback} /> + <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.addTextNote} placeholder={"Type ':' for commands"} contents="+ Node" menuCallback={this.menuCallback} /> </div> <div className="collectionNoteTakingView-addDocumentButton" style={{ color: lightOrDark(this._props.backgroundColor?.()) }}> - <EditableView {...this._props.editableViewProps()} /> + { + // eslint-disable-next-line react/jsx-props-no-spreading + <EditableView {...this._props.editableViewProps()} /> + } </div> </div> ) : null} diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx index 67070b24d..ddd4b8137 100644 --- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx @@ -28,7 +28,7 @@ export class CollectionNoteTakingViewDivider extends ObservableReactComponent<Di setupMoveUpEvents( this, e, - (e, down, delta) => { + (moveEv, down, delta) => { if (!batch) batch = UndoManager.StartBatch('resizing'); this._props.setColumnStartXCoords(delta[0], this._props.index); return false; diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 35f330b44..cb463077c 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -157,7 +157,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< const key = this._props.pivotField; const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true }); key && (newDoc[key] = this.getValue(this._props.heading)); - const maxHeading = this._props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0); + const maxHeading = this._props.docList.reduce((prevHeading, doc) => (NumCast(doc.heading) > prevHeading ? NumCast(doc.heading) : prevHeading), 0); const heading = maxHeading === 0 || this._props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3; newDoc.heading = heading; FormattedTextBox.SetSelectOnLoad(newDoc); @@ -220,9 +220,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< renderMenu = () => ( <div className="collectionStackingView-optionPicker"> <div className="optionOptions"> - <div className={'optionPicker' + (true ? ' active' : '')} onClick={action(() => {})}> - Add options here - </div> + <div className="optionPicker active">Add options here</div> </div> </div> ); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 538a6fd5e..bf81bdc7f 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -177,24 +177,24 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, -this._headerHeight); @action - remove = (doc: Doc | Doc[]): boolean => { - const docs = doc instanceof Doc ? [doc] : doc; + remove = (docIn: Doc | Doc[]): boolean => { + const docs = docIn instanceof Doc ? [docIn] : docIn; const targetDataDoc = this.Document[DocData]; const value = DocListCast(targetDataDoc[this._props.fieldKey]); const result = value.filter(v => !docs.includes(v)); - if ((doc instanceof Doc ? [doc] : doc).some(doc => SelectionManager.Views.some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll(); + if (docs.some(doc => SelectionManager.Views.some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll(); if (result.length !== value.length) { - if (doc instanceof Doc) { - const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(doc); + if (docIn instanceof Doc) { + const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(docIn); const prev = ind && DocListCast(targetDataDoc[this._props.fieldKey])[ind - 1]; - this._props.removeDocument?.(doc); + this._props.removeDocument?.(docIn); if (ind > 0 && prev) { FormattedTextBox.SetSelectOnLoad(prev); DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false); } return true; } - return this._props.removeDocument?.(doc) ?? false; + return this._props.removeDocument?.(docIn) ?? false; } return false; }; @@ -299,7 +299,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree @computed get treeViewElements() { TraceMobx(); const dragAction = StrCast(this.Document.childDragAction) as any as dropActionType; - const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before); + const treeAddDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before); const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this._props.moveDocument?.(d, target, addDoc) || false; if (this._renderCount < this.treeChildren.length) setTimeout( @@ -315,7 +315,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree this._props.TemplateDataDocument, undefined, undefined, - addDoc, + treeAddDoc, this.remove, moveDoc, dragAction, @@ -419,8 +419,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree addAnnotationDocument = (doc: Doc | Doc[]) => this.addDocument(doc, `${this._props.fieldKey}_annotations`) || false; remAnnotationDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, `${this._props.fieldKey}_annotations`) || false; - moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => - this.moveDocument(doc, targetCollection, addDocument, `${this._props.fieldKey}_annotations`) || false; + moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => this.moveDocument(doc, targetCollection, addDocument) || false; @observable _headerHeight = 0; @computed get content() { diff --git a/src/client/views/collections/KeyRestrictionRow.tsx b/src/client/views/collections/KeyRestrictionRow.tsx index 4523a4f1e..7dc08389b 100644 --- a/src/client/views/collections/KeyRestrictionRow.tsx +++ b/src/client/views/collections/KeyRestrictionRow.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/button-has-type */ import { observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -35,11 +36,36 @@ export default class KeyRestrictionRow extends React.Component<IKeyRestrictionPr return ( <div className="collectionViewBaseChrome-viewSpecsMenu-row"> - <input className="collectionViewBaseChrome-viewSpecsMenu-rowLeft" value={this._key} onChange={e => runInAction(() => (this._key = e.target.value))} placeholder="KEY" /> - <button className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle" style={{ background: this._contains ? '#77dd77' : '#ff6961' }} onClick={() => runInAction(() => (this._contains = !this._contains))}> + <input + className="collectionViewBaseChrome-viewSpecsMenu-rowLeft" + value={this._key} + onChange={e => + runInAction(() => { + this._key = e.target.value; + }) + } + placeholder="KEY" + /> + <button + className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle" + style={{ background: this._contains ? '#77dd77' : '#ff6961' }} + onClick={() => + runInAction(() => { + this._contains = !this._contains; + }) + }> {this._contains ? 'CONTAINS' : 'DOES NOT CONTAIN'} </button> - <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight" value={this._value} onChange={e => runInAction(() => (this._value = e.target.value))} placeholder="VALUE" /> + <input + className="collectionViewBaseChrome-viewSpecsMenu-rowRight" + value={this._value} + onChange={e => + runInAction(() => { + this._value = e.target.value; + }) + } + placeholder="VALUE" + /> </div> ); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx index 0acc99360..d2ce17f99 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx @@ -10,6 +10,7 @@ export interface CollectionFreeFormViewBackgroundGridProps { PanelWidth: () => number; PanelHeight: () => number; color: () => string; + // eslint-disable-next-line react/require-default-props isAnnotationOverlay?: boolean; nativeDimScaling: () => number; zoomScaling: () => number; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx index 73dd7fea3..fc39cafaa 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx @@ -5,13 +5,13 @@ import * as React from 'react'; import { SettingsManager } from '../../../util/SettingsManager'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import './CollectionFreeFormView.scss'; -import { Doc } from '../../../../fields/Doc'; /** * An Fsa Arc. The first array element is a test condition function that will be observed. * The second array element is a function that will be invoked when the first test function * returns a truthy value */ +// eslint-disable-next-line no-use-before-define export type infoArc = [() => any, (res?: any) => infoState]; export const StateMessage = Symbol('StateMessage'); @@ -46,6 +46,7 @@ export function InfoState( gif?: string, entryFunc?: () => any ) { + // eslint-disable-next-line new-cap return new infoState(msg, arcs, gif, entryFunc); } @@ -73,14 +74,15 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec } clearState = () => this._disposers.map(disposer => disposer()); - initState = () => (this._disposers = - this.Arcs.map(arc => ({ test: arc[0], act: arc[1] })).map( - arc => reaction( - arc.test, - res => res && this._props.next(arc.act(res)), - { fireImmediately: true } - ) - )); // prettier-ignore + initState = () => { + this._disposers = this.Arcs + .map(arc => ({ test: arc[0], act: arc[1] })) + .map(arc => reaction( + arc.test, + res => res && this._props.next(arc.act(res)), + { fireImmediately: true } + ) + )}; // prettier-ignore componentDidMount() { this.initState(); @@ -97,10 +99,15 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec render() { const gif = this.State?.[StateMessageGIF]; return ( - <div className={'collectionFreeform-infoUI'}> + <div className="collectionFreeform-infoUI"> <p className="collectionFreeform-infoUI-msg"> {this.State?.[StateMessage]} - <button className={'collectionFreeform-' + (!gif ? 'hidden' : 'infoUI-button')} onClick={action(() => (this._expanded = !this._expanded))}> + <button + type="button" + className={'collectionFreeform-' + (!gif ? 'hidden' : 'infoUI-button')} + onClick={action(() => { + this._expanded = !this._expanded; + })}> {this._expanded ? 'Less...' : 'More...'} </button> </p> @@ -108,7 +115,7 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec <img src={`/assets/${gif}`} alt="state message gif" /> </div> <div className="collectionFreeform-infoUI-close"> - <IconButton icon="x" color={SettingsManager.userColor} size={Size.XSMALL} type={Type.TERT} background={SettingsManager.userBackgroundColor} onClick={action(e => this.props.close())} /> + <IconButton icon="x" color={SettingsManager.userColor} size={Size.XSMALL} type={Type.TERT} background={SettingsManager.userBackgroundColor} onClick={action(() => this.props.close())} /> </div> </div> ); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 3970c6807..a4496a417 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -147,10 +147,10 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do } const val = Field.toString(pair.layout[pivotFieldKey] as FieldType); if (listValue) { - listValue.forEach((val, i) => { - !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] }); - pivotColumnGroups.get(val)!.docs.push(pair.layout); - pivotColumnGroups.get(val)!.replicas.push(i.toString()); + listValue.forEach((lval, i) => { + !pivotColumnGroups.get(lval) && pivotColumnGroups.set(lval, { docs: [], filters: [lval], replicas: [] }); + pivotColumnGroups.get(lval)!.docs.push(pair.layout); + pivotColumnGroups.get(lval)!.replicas.push(i.toString()); }); } else if (val) { !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] }); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx index 35394016d..f64c6715b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -4,9 +4,7 @@ import * as mobxUtils from 'mobx-utils'; import * as React from 'react'; import * as uuid from 'uuid'; import CursorField from '../../../../fields/CursorField'; -import { Doc, FieldResult } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; -import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { Cast } from '../../../../fields/Types'; import { CollectionViewProps } from '../CollectionSubView'; @@ -16,14 +14,12 @@ import './CollectionFreeFormView.scss'; export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> { @computed protected get cursors(): CursorField[] { const { Document } = this.props; - - let cursors: FieldResult<List<CursorField>>; - const id = Doc.UserDoc()[Id]; - if (!id || !(cursors = Cast(Document.cursors, listSpec(CursorField)))) { + const cursors = Cast(Document.cursors, listSpec(CursorField)); + if (!cursors) { return []; } const now = mobxUtils.now(); - return (cursors || []).filter(({ data: { metadata } }) => metadata.id !== id && now - metadata.timestamp < 1000); + return (cursors || []).filter(({ data: { metadata } }) => metadata.id !== Document[Id] && now - metadata.timestamp < 1000); } @computed get renderedCursors() { @@ -33,46 +29,44 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV metadata, position: { x, y }, }, - }) => { - return ( - <div key={metadata.id} className="collectionFreeFormRemoteCursors-cont" style={{ transform: `translate(${x - 10}px, ${y - 10}px)` }}> - <canvas - className="collectionFreeFormRemoteCursors-canvas" - ref={el => { - if (el) { - const ctx = el.getContext('2d'); - if (ctx) { - ctx.fillStyle = '#' + uuid.v5(metadata.id, uuid.v5.URL).substring(0, 6).toUpperCase() + '22'; - ctx.fillRect(0, 0, 20, 20); + }) => ( + <div key={metadata.id} className="collectionFreeFormRemoteCursors-cont" style={{ transform: `translate(${x - 10}px, ${y - 10}px)` }}> + <canvas + className="collectionFreeFormRemoteCursors-canvas" + ref={el => { + if (el) { + const ctx = el.getContext('2d'); + if (ctx) { + ctx.fillStyle = '#' + uuid.v5(metadata.id, uuid.v5.URL).substring(0, 6).toUpperCase() + '22'; + ctx.fillRect(0, 0, 20, 20); - ctx.fillStyle = 'black'; - ctx.lineWidth = 0.5; + ctx.fillStyle = 'black'; + ctx.lineWidth = 0.5; - ctx.beginPath(); + ctx.beginPath(); - ctx.moveTo(10, 0); - ctx.lineTo(10, 8); + ctx.moveTo(10, 0); + ctx.lineTo(10, 8); - ctx.moveTo(10, 20); - ctx.lineTo(10, 12); + ctx.moveTo(10, 20); + ctx.lineTo(10, 12); - ctx.moveTo(0, 10); - ctx.lineTo(8, 10); + ctx.moveTo(0, 10); + ctx.lineTo(8, 10); - ctx.moveTo(20, 10); - ctx.lineTo(12, 10); + ctx.moveTo(20, 10); + ctx.lineTo(12, 10); - ctx.stroke(); - } + ctx.stroke(); } - }} - width={20} - height={20} - /> - <p className="collectionFreeFormRemoteCursors-symbol">{metadata.identifier[0].toUpperCase()}</p> - </div> - ); - } + } + }} + width={20} + height={20} + /> + <p className="collectionFreeFormRemoteCursors-symbol">{metadata.identifier[0].toUpperCase()}</p> + </div> + ) ); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index 79cc534dc..adac5a102 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -9,6 +9,7 @@ import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; @observer export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { + // eslint-disable-next-line no-use-before-define static Instance: MarqueeOptionsMenu; public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => void = unimplementedFunction; @@ -29,14 +30,13 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { } render() { - const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ width: 19 }} />; const buttons = ( <> - <IconButton tooltip={'Create a Collection'} onPointerDown={this.createCollection} icon={<FontAwesomeIcon icon="object-group" />} color={this.userColor} /> - <IconButton tooltip={'Create a Grouping'} onPointerDown={e => this.createCollection(e, true)} icon={<FontAwesomeIcon icon="layer-group" />} color={this.userColor} /> - <IconButton tooltip={'Summarize Documents'} onPointerDown={this.summarize} icon={<FontAwesomeIcon icon="compress-arrows-alt" />} color={this.userColor} /> - <IconButton tooltip={'Delete Documents'} onPointerDown={this.delete} icon={<FontAwesomeIcon icon="trash-alt" />} color={this.userColor} /> - <IconButton tooltip={'Pin selected region'} onPointerDown={this.pinWithView} icon={<FontAwesomeIcon icon="map-pin" />} color={this.userColor} /> + <IconButton tooltip="Create a Collection" onPointerDown={this.createCollection} icon={<FontAwesomeIcon icon="object-group" />} color={this.userColor} /> + <IconButton tooltip="Create a Grouping" onPointerDown={e => this.createCollection(e, true)} icon={<FontAwesomeIcon icon="layer-group" />} color={this.userColor} /> + <IconButton tooltip="Summarize Documents" onPointerDown={this.summarize} icon={<FontAwesomeIcon icon="compress-arrows-alt" />} color={this.userColor} /> + <IconButton tooltip="Delete Documents" onPointerDown={this.delete} icon={<FontAwesomeIcon icon="trash-alt" />} color={this.userColor} /> + <IconButton tooltip="Pin selected region" onPointerDown={this.pinWithView} icon={<FontAwesomeIcon icon="map-pin" />} color={this.userColor} /> </> ); return this.getElement(buttons); diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index ab6788e6f..1634daaf7 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -17,6 +17,7 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { CollectionSubView } from '../CollectionSubView'; import './CollectionGridView.scss'; import Grid, { Layout } from './Grid'; + @observer export class CollectionGridView extends CollectionSubView() { private _containerRef: React.RefObject<HTMLDivElement> = React.createRef(); @@ -77,15 +78,13 @@ export class CollectionGridView extends CollectionSubView() { pairs.forEach((pair, i) => { const existing = oldLayouts.find(l => l.i === pair.layout[Id]); if (existing) newLayouts.push(existing); - else { - if (Object.keys(this.dropLocation).length) { - // external drop - this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.dropLocation as { x: number; y: number }, !this.flexGrid)); - this.dropLocation = {}; - } else { - // internal drop - this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid)); - } + else if (Object.keys(this.dropLocation).length) { + // external drop + this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.dropLocation as { x: number; y: number }, !this.flexGrid)); + this.dropLocation = {}; + } else { + // internal drop + this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid)); } }); pairs?.length && this.setLayoutList(newLayouts); @@ -140,9 +139,7 @@ export class CollectionGridView extends CollectionSubView() { /** * Creates a layout object for a grid item */ - makeLayoutItem = (doc: Doc, pos: { x: number; y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => { - return { i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static }; - }; + makeLayoutItem = (doc: Doc, pos: { x: number; y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => ({ i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static }); /** * Adds a layout to the list of layouts. @@ -190,6 +187,7 @@ export class CollectionGridView extends CollectionSubView() { getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) { return ( <DocumentView + // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} NativeWidth={returnZero} NativeHeight={returnZero} @@ -219,7 +217,7 @@ export class CollectionGridView extends CollectionSubView() { if (this.flexGrid) { const savedLayouts = this.savedLayoutList; this.childLayoutPairs.forEach(({ layout: doc }) => { - const gridLayout = savedLayouts.find(gridLayout => gridLayout.i === doc[Id]); + const gridLayout = savedLayouts.find(layout => layout.i === doc[Id]); if (gridLayout) Object.assign(gridLayout, layoutArray.find(layout => layout.i === doc[Id]) || gridLayout); }); @@ -318,7 +316,9 @@ export class CollectionGridView extends CollectionSubView() { e, returnFalse, action(() => { - undoBatch(() => (this.Document.gridRowHeight = this._rowHeight))(); + undoBatch(() => { + this.Document.gridRowHeight = this._rowHeight; + })(); this._rowHeight = undefined; }), emptyFunction, @@ -332,8 +332,20 @@ export class CollectionGridView extends CollectionSubView() { */ onContextMenu = () => { const displayOptionsMenu: ContextMenuProps[] = []; - displayOptionsMenu.push({ description: 'Toggle Content Display Style', event: () => (this.Document.display = this.Document.display ? undefined : 'contents'), icon: 'copy' }); - displayOptionsMenu.push({ description: 'Toggle Vertical Centering', event: () => (this.Document.centerY = !this.Document.centerY), icon: 'copy' }); + displayOptionsMenu.push({ + description: 'Toggle Content Display Style', + event: () => { + this.Document.display = this.Document.display ? undefined : 'contents'; + }, + icon: 'copy', + }); + displayOptionsMenu.push({ + description: 'Toggle Vertical Centering', + event: () => { + this.Document.centerY = !this.Document.centerY; + }, + icon: 'copy', + }); ContextMenu.Instance.addItem({ description: 'Display', subitems: displayOptionsMenu, icon: 'tv' }); }; @@ -347,14 +359,14 @@ export class CollectionGridView extends CollectionSubView() { e, returnFalse, returnFalse, - (e: PointerEvent, doubleTap?: boolean) => { - if (doubleTap && !e.button) { + (clickEv: PointerEvent, doubleTap?: boolean) => { + if (doubleTap && !clickEv.button) { undoBatch( action(() => { const text = Docs.Create.TextDocument('', { _width: 150, _height: 50 }); FormattedTextBox.SetSelectOnLoad(text); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed Doc.AddDocToList(this.Document, this._props.fieldKey, text); - this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(e.clientX, e.clientY)))); + this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(clickEv.clientX, clickEv.clientY)))); }) )(); } @@ -387,7 +399,7 @@ export class CollectionGridView extends CollectionSubView() { width={this._props.PanelWidth()} nodeList={this.contents.length ? this.contents : null} layout={this.contents.length ? this.renderedLayoutList : undefined} - childrenDraggable={this._props.isSelected() ? true : false} + childrenDraggable={!!this._props.isSelected()} numCols={this.numCols} rowHeight={this.rowHeight} setLayout={this.setLayout} diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 6635ab222..e68ed0e17 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -15,7 +15,8 @@ import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../ import { CollectionViewType } from '../../../documents/DocumentTypes'; import { BranchingTrailManager } from '../../../util/BranchingTrailManager'; import { DocumentManager } from '../../../util/DocumentManager'; -import { DragManager, dropActionType } from '../../../util/DragManager'; +import { DragManager } from '../../../util/DragManager'; +import { dropActionType } from '../../../util/DropActionTypes'; import { SettingsManager } from '../../../util/SettingsManager'; import { Transform } from '../../../util/Transform'; import { UndoStack } from '../../UndoStack'; diff --git a/src/client/views/collections/collectionLinear/index.ts b/src/client/views/collections/collectionLinear/index.ts index ff73e14ae..ab3b4b0b5 100644 --- a/src/client/views/collections/collectionLinear/index.ts +++ b/src/client/views/collections/collectionLinear/index.ts @@ -1 +1 @@ -export * from "./CollectionLinearView";
\ No newline at end of file +export * from './CollectionLinearView'; diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 125dd2781..b8509a005 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.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 { Tooltip } from '@mui/material'; import { Button, IconButton } from 'browndash-components'; @@ -7,7 +9,7 @@ import * as React from 'react'; import { FaChevronRight } from 'react-icons/fa'; import { Doc, DocListCast } from '../../../../fields/Doc'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { DragManager, dropActionType } from '../../../util/DragManager'; +import { DragManager } from '../../../util/DragManager'; import { SettingsManager } from '../../../util/SettingsManager'; import { Transform } from '../../../util/Transform'; import { undoBatch, undoable } from '../../../util/UndoManager'; @@ -16,6 +18,7 @@ import { CollectionSubView } from '../CollectionSubView'; import './CollectionMulticolumnView.scss'; import ResizeBar from './MulticolumnResizer'; import WidthLabel from './MulticolumnWidthLabel'; +import { dropActionType } from '../../../util/DropActionTypes'; interface WidthSpecifier { magnitude: number; @@ -80,7 +83,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { private get resolvedLayoutInformation(): LayoutData { let starSum = 0; const widthSpecifiers: WidthSpecifier[] = []; - this.childLayouts.map(layout => { + this.childLayouts.forEach(layout => { const unit = StrCast(layout._dimUnit, '*'); const magnitude = NumCast(layout._dimMagnitude, this.minimumDim); if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) { @@ -140,6 +143,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) { return this._props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1)) - 2 * NumCast(this.Document._xMargin); } + return undefined; } /** @@ -159,6 +163,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { if (this.resolvedLayoutInformation && this.totalRatioAllocation !== undefined) { return this.totalRatioAllocation / this.resolvedLayoutInformation.starSum; } + return undefined; } /** @@ -175,7 +180,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { * or the ratio width evaluated to a pixel value */ private lookupPixels = (layout: Doc): number => { - const columnUnitLength = this.columnUnitLength; + const { columnUnitLength } = this; if (columnUnitLength === undefined) { return 0; // we're still waiting on promises to resolve } @@ -193,19 +198,19 @@ export class CollectionMulticolumnView extends CollectionSubView() { * documents before the target. */ private lookupIndividualTransform = (layout: Doc) => { - const columnUnitLength = this.columnUnitLength; + const { columnUnitLength } = this; if (columnUnitLength === undefined) { return Transform.Identity(); // we're still waiting on promises to resolve } let offset = 0; - var xf = Transform.Identity(); - this.childLayouts.map(candidate => { + // eslint-disable-next-line no-restricted-syntax + for (const { layout: candidate } of this.childLayoutPairs) { if (candidate === layout) { - return (xf = this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0)); + return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1)); } offset += this.lookupPixels(candidate) + resizerWidth; - }); - return xf; + } + return Transform.Identity(); }; @undoBatch @@ -213,7 +218,9 @@ export class CollectionMulticolumnView extends CollectionSubView() { let dropInd = -1; if (de.complete.docDragData && this._mainCont) { let curInd = -1; - de.complete.docDragData?.droppedDocuments.forEach(d => (curInd = this.childDocs.indexOf(d))); + de.complete.docDragData?.droppedDocuments.forEach(d => { + curInd = this.childDocs.indexOf(d); + }); Array.from(this._mainCont.children).forEach((child, index) => { const brect = child.getBoundingClientRect(); if (brect.x < de.x && brect.x + brect.width > de.x) { @@ -265,7 +272,6 @@ export class CollectionMulticolumnView extends CollectionSubView() { this.lookupIndividualTransform(childLayout) .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin)) .scale(this._props.NativeDimScaling?.() || 1); - const shouldNotScale = () => this._props.fitContentsToBox?.() || BoolCast(childLayout.freeform_fitContentsToBox); return ( <DocumentView Document={childLayout} @@ -281,11 +287,11 @@ export class CollectionMulticolumnView extends CollectionSubView() { dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType} onClickScript={this.onChildClickHandler} onDoubleClickScript={this.onChildDoubleClickHandler} - suppressSetHeight={true} + suppressSetHeight ScreenToLocalTransform={dxf} isContentActive={this.isChildContentActive} isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} - hideResizeHandles={childLayout.layout_fitWidth || this._props.childHideResizeHandles ? true : false} + hideResizeHandles={!!(childLayout.layout_fitWidth || this._props.childHideResizeHandles)} hideDecorationTitle={this._props.childHideDecorationTitle} fitContentsToBox={this._props.fitContentsToBox} focus={this._props.focus} @@ -312,17 +318,19 @@ export class CollectionMulticolumnView extends CollectionSubView() { const collector: JSX.Element[] = []; this.childLayouts.forEach((layout, i) => { collector.push( + // eslint-disable-next-line react/no-array-index-key <Tooltip title={'Tab: ' + StrCast(layout.title)} key={'wrapper' + i}> <div className="document-wrapper" style={{ flexDirection: 'column', width: this.lookupPixels(layout) }}> {this.getDisplayDoc(layout)} {this.layoutDoc._chromeHidden ? null : ( - <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(e => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} /> + <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(() => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} /> )} <WidthLabel layout={layout} collectionDoc={this.Document} /> </div> </Tooltip>, <ResizeBar width={resizerWidth} + // eslint-disable-next-line react/no-array-index-key key={'resizer' + i} styleProvider={this._props.styleProvider} isContentActive={this._props.isContentActive} @@ -353,14 +361,29 @@ export class CollectionMulticolumnView extends CollectionSubView() { {this.contents} {!this._startIndex ? null : ( <Tooltip title="scroll back"> - <div style={{ position: 'absolute', bottom: 0, left: 0, background: SettingsManager.userVariantColor }} onClick={action(e => (this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown)))}> - <Button tooltip="Scroll back" icon={<FontAwesomeIcon icon="chevron-left" size="lg" />} onClick={action(e => (this._startIndex = Math.max(0, this._startIndex - this.maxShown)))} color={SettingsManager.userColor} /> + <div + style={{ position: 'absolute', bottom: 0, left: 0, background: SettingsManager.userVariantColor }} + onClick={action(() => { + this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown); + })}> + <Button + tooltip="Scroll back" + icon={<FontAwesomeIcon icon="chevron-left" size="lg" />} + onClick={action(() => { + this._startIndex = Math.max(0, this._startIndex - this.maxShown); + })} + color={SettingsManager.userColor} + /> </div> </Tooltip> )} {this._startIndex > this.childLayoutPairs.length - 1 || !this.maxShown ? null : ( <Tooltip title="scroll forward"> - <div style={{ position: 'absolute', bottom: 0, right: 0, background: SettingsManager.userVariantColor }} onClick={action(e => (this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown)))}> + <div + style={{ position: 'absolute', bottom: 0, right: 0, background: SettingsManager.userVariantColor }} + onClick={action(() => { + this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown); + })}> <IconButton icon={<FaChevronRight />} color={SettingsManager.userColor} /> </div> </Tooltip> diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 17bf3e50c..3fe3d5343 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -3,7 +3,8 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast } from '../../../../fields/Doc'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { DragManager, dropActionType } from '../../../util/DragManager'; +import { DragManager } from '../../../util/DragManager'; +import { dropActionType } from '../../../util/DropActionTypes'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; import { DocumentView } from '../../nodes/DocumentView'; @@ -11,6 +12,7 @@ import { CollectionSubView } from '../CollectionSubView'; import './CollectionMultirowView.scss'; import HeightLabel from './MultirowHeightLabel'; import ResizeBar from './MultirowResizer'; + interface HeightSpecifier { magnitude: number; unit: string; @@ -63,7 +65,7 @@ export class CollectionMultirowView extends CollectionSubView() { private get resolvedLayoutInformation(): LayoutData { let starSum = 0; const heightSpecifiers: HeightSpecifier[] = []; - this.childLayoutPairs.map(pair => { + this.childLayoutPairs.forEach(pair => { const unit = StrCast(pair.layout._dimUnit, '*'); const magnitude = NumCast(pair.layout._dimMagnitude, this.minimumDim); if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) { @@ -123,6 +125,7 @@ export class CollectionMultirowView extends CollectionSubView() { if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) { return this._props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this.Document._yMargin); } + return undefined; } /** @@ -142,6 +145,7 @@ export class CollectionMultirowView extends CollectionSubView() { if (this.resolvedLayoutInformation && this.totalRatioAllocation !== undefined) { return this.totalRatioAllocation / this.resolvedLayoutInformation.starSum; } + return undefined; } /** @@ -158,13 +162,12 @@ export class CollectionMultirowView extends CollectionSubView() { * or the ratio width evaluated to a pixel value */ private lookupPixels = (layout: Doc): number => { - const rowUnitLength = this.rowUnitLength; - if (rowUnitLength === undefined) { + if (this.rowUnitLength === undefined) { return 0; // we're still waiting on promises to resolve } let height = NumCast(layout._dimMagnitude, this.minimumDim); if (StrCast(layout._dimUnit, '*') === DimUnit.Ratio) { - height *= rowUnitLength; + height *= this.rowUnitLength; } return height; }; @@ -176,11 +179,11 @@ export class CollectionMultirowView extends CollectionSubView() { * documents before the target. */ private lookupIndividualTransform = (layout: Doc) => { - const rowUnitLength = this.rowUnitLength; - if (rowUnitLength === undefined) { + if (this.rowUnitLength === undefined) { return Transform.Identity(); // we're still waiting on promises to resolve } let offset = 0; + // eslint-disable-next-line no-restricted-syntax for (const { layout: candidate } of this.childLayoutPairs) { if (candidate === layout) { return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1)); @@ -195,7 +198,9 @@ export class CollectionMultirowView extends CollectionSubView() { let dropInd = -1; if (de.complete.docDragData && this._mainCont) { let curInd = -1; - de.complete.docDragData?.droppedDocuments.forEach(d => (curInd = this.childDocs.indexOf(d))); + de.complete.docDragData?.droppedDocuments.forEach(d => { + curInd = this.childDocs.indexOf(d); + }); Array.from(this._mainCont.children).forEach((child, index) => { const brect = child.getBoundingClientRect(); if (brect.y < de.y && brect.y + brect.height > de.y) { @@ -247,7 +252,6 @@ export class CollectionMultirowView extends CollectionSubView() { this.lookupIndividualTransform(layout) .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin)) .scale(this._props.NativeDimScaling?.() || 1); - const shouldNotScale = () => this._props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox); return ( <DocumentView Document={layout} @@ -266,7 +270,7 @@ export class CollectionMultirowView extends CollectionSubView() { ScreenToLocalTransform={dxf} isContentActive={this.isChildContentActive} isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} - hideResizeHandles={layout.layout_fitWidth || this._props.childHideResizeHandles ? true : false} + hideResizeHandles={!!(layout.layout_fitWidth || this._props.childHideResizeHandles)} hideDecorationTitle={this._props.childHideDecorationTitle} fitContentsToBox={this._props.fitContentsToBox} focus={this._props.focus} diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx index d580d9c52..49ba85524 100644 --- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx +++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/require-default-props */ import { action } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -52,27 +53,6 @@ export default class ResizeBar extends React.Component<ResizerProps> { } }; - private get isActivated() { - const { toLeft, toRight } = this.props; - if (toLeft && toRight) { - if (StrCast(toLeft._dimUnit, '*') === DimUnit.Pixel && StrCast(toRight._dimUnit, '*') === DimUnit.Pixel) { - return false; - } - return true; - } else if (toLeft) { - if (StrCast(toLeft._dimUnit, '*') === DimUnit.Pixel) { - return false; - } - return true; - } else if (toRight) { - if (StrCast(toRight._dimUnit, '*') === DimUnit.Pixel) { - return false; - } - return true; - } - return false; - } - @action private onPointerUp = () => { window.removeEventListener('pointermove', this.onPointerMove); @@ -90,7 +70,7 @@ export default class ResizeBar extends React.Component<ResizerProps> { width: this.props.width, backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor), }}> - <div className={'multiColumnResizer-hdl'} onPointerDown={e => this.registerResizing(e)} /> + <div className="multiColumnResizer-hdl" onPointerDown={e => this.registerResizing(e)} /> </div> ); } diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx index a9579d931..a7a0b3457 100644 --- a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx +++ b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx @@ -19,7 +19,7 @@ export default class WidthLabel extends React.Component<WidthLabelProps> { const getUnit = () => StrCast(layout.dimUnit); const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(3)); return ( - <div className={'label-wrapper'}> + <div className="label-wrapper"> <EditableView GetValue={getMagnitude} SetValue={value => { diff --git a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx index 878c7ff3c..66215f109 100644 --- a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx +++ b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/require-default-props */ import { computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -20,7 +21,7 @@ export default class HeightLabel extends React.Component<HeightLabelProps> { const getUnit = () => StrCast(layout.dimUnit); const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(decimals ?? 3)); return ( - <div className={'label-wrapper'}> + <div className="label-wrapper"> <EditableView GetValue={getMagnitude} SetValue={value => { diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx index 73d08d5ef..ad77c327d 100644 --- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx +++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/require-default-props */ import { action } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -33,7 +34,7 @@ export default class ResizeBar extends React.Component<ResizerProps> { }; private onPointerMove = ({ movementY }: PointerEvent) => { - const { toTop: toTop, toBottom: toBottom, columnUnitLength } = this.props; + const { toTop, toBottom, columnUnitLength } = this.props; const movingDown = movementY > 0; const toNarrow = movingDown ? toBottom : toTop; const toWiden = movingDown ? toTop : toBottom; @@ -50,27 +51,6 @@ export default class ResizeBar extends React.Component<ResizerProps> { } }; - private get isActivated() { - const { toTop, toBottom } = this.props; - if (toTop && toBottom) { - if (StrCast(toTop._dimUnit, '*') === DimUnit.Pixel && StrCast(toBottom._dimUnit, '*') === DimUnit.Pixel) { - return false; - } - return true; - } else if (toTop) { - if (StrCast(toTop._dimUnit, '*') === DimUnit.Pixel) { - return false; - } - return true; - } else if (toBottom) { - if (StrCast(toBottom._dimUnit, '*') === DimUnit.Pixel) { - return false; - } - return true; - } - return false; - } - @action private onPointerUp = () => { window.removeEventListener('pointermove', this.onPointerMove); @@ -88,7 +68,7 @@ export default class ResizeBar extends React.Component<ResizerProps> { height: this.props.height, backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor), }}> - <div className={'multiRowResizer-hdl'} onPointerDown={e => this.registerResizing(e)} /> + <div className="multiRowResizer-hdl" onPointerDown={e => this.registerResizing(e)} /> </div> ); } diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx index 2823e1936..389fc66b3 100644 --- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx +++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx @@ -1,5 +1,6 @@ +/* eslint-disable react/no-unused-prop-types */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, observable } from 'mobx'; +import { action } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { setupMoveUpEvents } from '../../../../ClientUtils'; @@ -25,8 +26,6 @@ export interface SchemaColumnHeaderProps { @observer export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> { - @observable _ref: HTMLDivElement | null = null; - get fieldKey() { return this.props.columnKeys[this.props.columnIndex]; } @@ -35,9 +34,9 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> sortClicked = (e: React.PointerEvent) => { e.stopPropagation(); e.preventDefault(); - if (this.props.sortField == this.fieldKey && this.props.sortDesc) { + if (this.props.sortField === this.fieldKey && this.props.sortDesc) { this.props.setSort(undefined); - } else if (this.props.sortField == this.fieldKey) { + } else if (this.props.sortField === this.fieldKey) { this.props.setSort(this.fieldKey, true); } else { this.props.setSort(this.fieldKey, false); @@ -46,7 +45,7 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> @action onPointerDown = (e: React.PointerEvent) => { - this.props.isContentActive(true) && setupMoveUpEvents(this, e, e => this.props.dragColumn(e, this.props.columnIndex), emptyFunction, emptyFunction, false); + this.props.isContentActive(true) && setupMoveUpEvents(this, e, moveEv => this.props.dragColumn(moveEv, this.props.columnIndex), emptyFunction, emptyFunction, false); }; render() { @@ -59,19 +58,18 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> onPointerDown={this.onPointerDown} ref={col => { if (col) { - this._ref = col; this.props.setColRef(this.props.columnIndex, col); } }}> - <div className="schema-column-resizer left" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex)}></div> + <div className="schema-column-resizer left" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex)} /> <div className="schema-column-title">{this.fieldKey}</div> <div className="schema-header-menu"> <div className="schema-header-button" onPointerDown={e => this.props.openContextMenu(e.clientX, e.clientY, this.props.columnIndex)}> <FontAwesomeIcon icon="ellipsis-h" /> </div> - <div className="schema-sort-button" onPointerDown={this.sortClicked} style={this.props.sortField == this.fieldKey ? { backgroundColor: Colors.MEDIUM_BLUE } : {}}> - <FontAwesomeIcon icon="caret-right" style={this.props.sortField == this.fieldKey ? { transform: `rotate(${this.props.sortDesc ? '270deg' : '90deg'})` } : {}} /> + <div className="schema-sort-button" onPointerDown={this.sortClicked} style={this.props.sortField === this.fieldKey ? { backgroundColor: Colors.MEDIUM_BLUE } : {}}> + <FontAwesomeIcon icon="caret-right" style={this.props.sortField === this.fieldKey ? { transform: `rotate(${this.props.sortDesc ? '270deg' : '90deg'})` } : {}} /> </div> </div> </div> diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 27a4493cb..61afe08cf 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -63,7 +63,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() { } }; - onPointerEnter = (e: any) => { + onPointerEnter = () => { if (SnappingManager.IsDragging && this._props.isContentActive()) { document.removeEventListener('pointermove', this.onPointerMove); document.addEventListener('pointermove', this.onPointerMove); @@ -75,7 +75,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() { if (this._ref && dragIsRow) { const rect = this._ref.getBoundingClientRect(); - const y = e.clientY - rect.top; //y position within the element. + const y = e.clientY - rect.top; // y position within the element. const height = this._ref.clientHeight; const halfLine = height / 2; if (y <= halfLine) { @@ -90,7 +90,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() { } }; - onPointerLeave = (e: any) => { + onPointerLeave = () => { if (this._ref) { this._ref.style.borderTop = '0px'; this._ref.style.borderBottom = '0px'; @@ -131,15 +131,16 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() { e, returnFalse, emptyFunction, - undoable(e => { - e.stopPropagation(); + undoable(clickEv => { + clickEv.stopPropagation(); Doc.toggleLockedPosition(this.Document); }, 'Delete Row') ) - }></IconButton> + } + /> <IconButton tooltip="close" - icon={<CgClose size={'16px'} />} + icon={<CgClose size="16px" />} size={Size.XSMALL} onPointerDown={e => setupMoveUpEvents( @@ -147,8 +148,8 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() { e, returnFalse, emptyFunction, - undoable(e => { - e.stopPropagation(); + undoable(clickEv => { + clickEv.stopPropagation(); this._props.removeDocument?.(this.Document); }, 'Delete Row') ) @@ -164,8 +165,8 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() { e, returnFalse, emptyFunction, - undoable(e => { - e.stopPropagation(); + undoable(clickEv => { + clickEv.stopPropagation(); this._props.addDocTab(this.Document, OpenWhere.addRight); }, 'Open schema Doc preview') ) diff --git a/src/client/views/global/globalEnums.tsx b/src/client/views/global/globalEnums.tsx index 610c2b102..2cf9e4162 100644 --- a/src/client/views/global/globalEnums.tsx +++ b/src/client/views/global/globalEnums.tsx @@ -1,44 +1,44 @@ export enum Colors { - BLACK = "#000000", - DARK_GRAY = "#323232", - MEDIUM_GRAY = "#9F9F9F", - LIGHT_GRAY = "#DFDFDF", - WHITE = "#FFFFFF", - MEDIUM_BLUE = "#4476F7", - MEDIUM_BLUE_ALT = "#4476f73d", // REDUCED OPACITY - LIGHT_BLUE = "#BDDDF5", - PINK = "#E0217D", - ERROR_RED = "#ff0033", - YELLOW = "#F5D747", - DROP_SHADOW = "#32323215", + BLACK = '#000000', + DARK_GRAY = '#323232', + MEDIUM_GRAY = '#9F9F9F', + LIGHT_GRAY = '#DFDFDF', + WHITE = '#FFFFFF', + MEDIUM_BLUE = '#4476F7', + MEDIUM_BLUE_ALT = '#4476f73d', // REDUCED OPACITY + LIGHT_BLUE = '#BDDDF5', + PINK = '#E0217D', + ERROR_RED = '#ff0033', + YELLOW = '#F5D747', + DROP_SHADOW = '#32323215', } export enum FontSizes { - //Bolded - LARGE_HEADER = "16px", + // Bolded + LARGE_HEADER = '16px', - //Bolded or unbolded - BODY_TEXT = "12px", + // Bolded or unbolded + BODY_TEXT = '12px', - //Bolded - SMALL_TEXT = "9px", + // Bolded + SMALL_TEXT = '9px', } export enum Padding { - MINIMUM_PADDING = "4px", - SMALL_PADDING = "8px", - MEDIUM_PADDING = "16px", - LARGE_PADDING = "32px", + MINIMUM_PADDING = '4px', + SMALL_PADDING = '8px', + MEDIUM_PADDING = '16px', + LARGE_PADDING = '32px', } export enum IconSizes { - ICON_SIZE = "28px", + ICON_SIZE = '28px', } export enum Borders { - STANDARD = "solid 1px #9F9F9F" + STANDARD = 'solid 1px #9F9F9F', } export enum Shadows { - STANDARD_SHADOW = "0px 3px 4px rgba(0, 0, 0, 0.3)" -}
\ No newline at end of file + STANDARD_SHADOW = '0px 3px 4px rgba(0, 0, 0, 0.3)', +} diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index 60def5d45..f99a18db2 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -1,14 +1,17 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable react/require-default-props */ +import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; -import { observable, action } from 'mobx'; +import * as React from 'react'; import { Doc, StrListCast } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { Cast, DocCast } from '../../../fields/Types'; +import { DocumentType } from '../../documents/DocumentTypes'; import { LinkManager } from '../../util/LinkManager'; import { DocumentView } from '../nodes/DocumentView'; import './LinkMenu.scss'; import { LinkMenuItem } from './LinkMenuItem'; -import * as React from 'react'; -import { DocumentType } from '../../documents/DocumentTypes'; interface LinkMenuGroupProps { sourceDoc: Doc; @@ -22,25 +25,24 @@ interface LinkMenuGroupProps { @observer export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> { private _menuRef = React.createRef<HTMLDivElement>(); + @observable _collapsed = false; getBackgroundColor = (): string | undefined => { - const link_relationshipList = StrListCast(Doc.UserDoc().link_relationshipList); + const linkRelationshipList = StrListCast(Doc.UserDoc().link_relationshipList); const linkColorList = StrListCast(Doc.UserDoc().link_ColorList); let color: string | undefined; // if this link's relationship property is not default "link", set its color - if (link_relationshipList) { - const relationshipIndex = link_relationshipList.indexOf(this.props.groupType); + if (linkRelationshipList) { + const relationshipIndex = linkRelationshipList.indexOf(this.props.groupType); const RGBcolor: string = linkColorList[relationshipIndex]; if (RGBcolor) { - //set opacity to 0.25 by modifiying the rgb string + // set opacity to 0.25 by modifiying the rgb string color = RGBcolor.slice(0, RGBcolor.length - 1) + ', 0.25)'; } } return color; }; - @observable _collapsed = false; - render() { const set = new Set<Doc>(this.props.group); const groupItems = Array.from(set.keys()).map(linkDoc => { @@ -76,7 +78,12 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> { return ( <div className="linkMenu-group" ref={this._menuRef}> - <div className="linkMenu-group-name" onClick={action(() => (this._collapsed = !this._collapsed))} style={{ background: this.getBackgroundColor() }}> + <div + className="linkMenu-group-name" + onClick={action(() => { + this._collapsed = !this._collapsed; + })} + style={{ background: this.getBackgroundColor() }}> <p className={this.props.groupType === '*' || this.props.groupType === '' ? '' : 'expand-one'}> {this.props.groupType}:</p> </div> {this._collapsed ? null : <div className="linkMenu-group-wrapper">{groupItems}</div>} diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 9be78a6cb..303ff4b98 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -92,10 +92,10 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> { setupMoveUpEvents( this, e, - e => { + moveEv => { const dragData = new DragManager.DocumentDragData([this._props.linkDoc], dropActionType.embed); dragData.dropPropertiesToRemove = ['hidden']; - DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y); + DragManager.StartDocumentDrag([this._editRef.current!], dragData, moveEv.x, moveEv.y); return true; }, emptyFunction, @@ -125,10 +125,10 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> { setupMoveUpEvents( this, e, - e => { + moveEv => { const eleClone: any = this._drag.current!.cloneNode(true); - eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`; - StartLinkTargetsDrag(eleClone, this._props.docView, e.x, e.y, this._props.sourceDoc, [this._props.linkDoc]); + eleClone.style.transform = `translate(${moveEv.x}px, ${moveEv.y}px)`; + StartLinkTargetsDrag(eleClone, this._props.docView, moveEv.x, moveEv.y, this._props.sourceDoc, [this._props.linkDoc]); this._props.clearLinkEditor?.(); return true; }, diff --git a/src/client/views/linking/LinkRelationshipSearch.tsx b/src/client/views/linking/LinkRelationshipSearch.tsx deleted file mode 100644 index 0902d53b2..000000000 --- a/src/client/views/linking/LinkRelationshipSearch.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { observer } from 'mobx-react'; -import * as React from 'react'; -import './LinkEditor.scss'; - -interface link_relationshipSearchProps { - results: string[] | undefined; - display: string; - //callback fn to set rel + hide dropdown upon setting - handleRelationshipSearchChange: (result: string) => void; - toggleSearch: () => void; -} -@observer -export class link_relationshipSearch extends React.Component<link_relationshipSearchProps> { - handleResultClick = (e: React.MouseEvent) => { - const relationship = (e.target as HTMLParagraphElement).textContent; - if (relationship) { - this.props.handleRelationshipSearchChange(relationship); - } - }; - - handleMouseEnter = () => { - this.props.toggleSearch(); - }; - - handleMouseLeave = () => { - this.props.toggleSearch(); - }; - - /** - * Render an empty div to increase the height of LinkEditor to accommodate 2+ results - */ - emptyDiv = () => { - if (this.props.results && this.props.results.length > 2 && this.props.display === 'block') { - return <div style={{ height: '50px' }} />; - } - }; - - render() { - return ( - <div className="linkEditor-relationship-dropdown-container"> - <div className="linkEditor-relationship-dropdown" style={{ display: this.props.display }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> - { - // return a dropdown of relationship results if there exist results - this.props.results ? ( - this.props.results.map(result => { - return ( - <p key={result} onClick={this.handleResultClick}> - {result} - </p> - ); - }) - ) : ( - <p>No matching relationships</p> - ) - } - </div> - - {/*Render an empty div to increase the height of LinkEditor to accommodate 2+ results */} - {this.emptyDiv()} - </div> - ); - } -} diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index c37466914..9251dca6d 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -532,7 +532,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { e, returnFalse, returnFalse, - action((e: PointerEvent, doubleTap?: boolean) => { + action((moveEv: PointerEvent, doubleTap?: boolean) => { if (doubleTap) { this.startTrim(TrimScope.All); } else if (this.timeline) { @@ -572,14 +572,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { setupTimelineDrop = (r: HTMLDivElement | null) => { if (r && this.timeline) { this._dropDisposer?.(); - this._dropDisposer = DragManager.MakeDropTarget( - r, - (e, de) => { - const [xp] = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y); - de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData, xp); - }, - this.layoutDoc - ); + this._dropDisposer = DragManager.MakeDropTarget(r, (e, de) => de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData), this.layoutDoc); } }; diff --git a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx index 0084d7394..60bc8df18 100644 --- a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx +++ b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx @@ -1,3 +1,5 @@ +/* eslint-disable jsx-a11y/label-has-associated-control */ +/* eslint-disable jsx-a11y/alt-text */ import { IconButton } from 'browndash-components'; import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; @@ -15,56 +17,52 @@ interface SchemaCSVPopUpProps {} @observer export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> { + // eslint-disable-next-line no-use-before-define static Instance: SchemaCSVPopUp; - @observable - public dataVizDoc: Doc | undefined = undefined; + @observable public dataVizDoc: Doc | undefined = undefined; + @observable public view: DocumentView | undefined = undefined; + @observable public target: Doc | undefined = undefined; + @observable public visible: boolean = false; + + constructor(props: SchemaCSVPopUpProps) { + super(props); + makeObservable(this); + SchemaCSVPopUp.Instance = this; + } + @action public setDataVizDoc = (doc: Doc) => { this.dataVizDoc = doc; }; - @observable - public view: DocumentView | undefined = undefined; @action public setView = (docView: DocumentView) => { this.view = docView; }; - @observable - public target: Doc | undefined = undefined; @action public setTarget = (doc: Doc) => { this.target = doc; }; - @observable - public visible: boolean = false; @action public setVisible = (vis: boolean) => { this.visible = vis; }; - constructor(props: SchemaCSVPopUpProps) { - super(props); - makeObservable(this); - SchemaCSVPopUp.Instance = this; - } - - dataBox = () => { - return ( - <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}> - {this.heading('Schema Table as Data Visualization Doc')} - <div className="image-content-wrapper"> - <div className="img-wrapper"> - <div className="img-container" onPointerDown={e => this.drag(e)}> - <img width={150} height={150} src={'/assets/dataVizBox.png'} /> - </div> + dataBox = () => ( + <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}> + {this.heading('Schema Table as Data Visualization Doc')} + <div className="image-content-wrapper"> + <div className="img-wrapper"> + <div className="img-container" onPointerDown={e => this.drag(e)}> + <img width={150} height={150} src="/assets/dataVizBox.png" /> </div> </div> </div> - ); - }; + </div> + ); heading = (headingText: string) => ( <div className="summary-heading"> @@ -79,24 +77,22 @@ export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> { setupMoveUpEvents( {}, e, - e => { + moveEv => { const sourceAnchorCreator = () => this.dataVizDoc!; - const targetCreator = (annotationOn: Doc | undefined) => { + const targetCreator = () => { const embedding = Doc.MakeEmbedding(this.dataVizDoc!); return embedding; }; - if (this.view && sourceAnchorCreator && !ClientUtils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) { - DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this.view, sourceAnchorCreator, targetCreator), downX, downY, { - dragComplete: e => { - this.setVisible(false); - }, + if (this.view && sourceAnchorCreator && !ClientUtils.isClick(moveEv.clientX, moveEv.clientY, downX, downY, Date.now())) { + DragManager.StartAnchorAnnoDrag(moveEv.target instanceof HTMLElement ? [moveEv.target] : [], new DragManager.AnchorAnnoDragData(this.view, sourceAnchorCreator, targetCreator), downX, downY, { + dragComplete: () => this.setVisible(false), }); return true; } return false; }, emptyFunction, - action(e => {}) + action(() => {}) ); }; diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 2735a40d5..910504e7c 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -75,8 +75,8 @@ export class PieChart extends ObservableReactComponent<PieChartProps> { } @computed get defaultGraphTitle() { - var ax0 = this._props.axes[0]; - var ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined; + const ax0 = this._props.axes[0]; + const ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined; if (this._props.axes.length < 2 || !/\d/.test(this._props.records[0][ax0]) || !ax1) { return ax0 + ' Pie Chart'; } @@ -101,8 +101,6 @@ export class PieChart extends ObservableReactComponent<PieChartProps> { ); } - @action - restoreView = (data: Doc) => {}; // create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc) getAnchor = (pinProps?: PinProps) => { const anchor = Docs.Create.ConfigDocument({ @@ -130,23 +128,23 @@ export class PieChart extends ObservableReactComponent<PieChartProps> { : validData.map((d: { [x: string]: any }) => this.byCategory ? d[field] // - : +d[field].replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') + : +d[field].replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') ); }; // outlines the slice selected / hovered over highlightSelectedSlice = (changeSelectedVariables: boolean, svg: any, arc: any, radius: any, pointer: any, pieDataSet: any) => { - var index = -1; - var sameAsCurrent: boolean; + let index = -1; + let sameAsCurrent: boolean; const selected = svg.selectAll('.slice').filter((d: any) => { index++; - var p1 = [0, 0]; // center of pie - var p3 = [arc.centroid(d)[0] * 2, arc.centroid(d)[1] * 2]; // outward peak of arc - var p2 = [radius * Math.sin(d.startAngle), -radius * Math.cos(d.startAngle)]; // start of arc - var p4 = [radius * Math.sin(d.endAngle), -radius * Math.cos(d.endAngle)]; // end of arc + const p1 = [0, 0]; // center of pie + const p3 = [arc.centroid(d)[0] * 2, arc.centroid(d)[1] * 2]; // outward peak of arc + const p2 = [radius * Math.sin(d.startAngle), -radius * Math.cos(d.startAngle)]; // start of arc + const p4 = [radius * Math.sin(d.endAngle), -radius * Math.cos(d.endAngle)]; // end of arc // draw an imaginary horizontal line from the pointer to see how many times it crosses a slice edge - var lineCrossCount = 0; + let lineCrossCount = 0; // if for all 4 lines if (Math.min(p1[1], p2[1]) <= pointer[1] && pointer[1] <= Math.max(p1[1], p2[1])) { // within y bounds @@ -161,13 +159,13 @@ export class PieChart extends ObservableReactComponent<PieChartProps> { if (Math.min(p4[1], p1[1]) <= pointer[1] && pointer[1] <= Math.max(p4[1], p1[1])) { if (pointer[0] <= ((pointer[1] - p4[1]) * (p1[0] - p4[0])) / (p1[1] - p4[1]) + p4[0]) lineCrossCount++; } - if (lineCrossCount % 2 != 0) { + if (lineCrossCount % 2 !== 0) { // inside the slice of it crosses an odd number of edges - var showSelected = this.byCategory ? pieDataSet[index] : this._pieChartData[index]; + const showSelected = this.byCategory ? pieDataSet[index] : this._pieChartData[index]; if (changeSelectedVariables) { // for when a bar is selected - not just hovered over sameAsCurrent = this._currSelected - ? showSelected[Object.keys(showSelected)[0]] == this._currSelected![Object.keys(showSelected)[0]] && showSelected[Object.keys(showSelected)[1]] == this._currSelected![Object.keys(showSelected)[1]] + ? showSelected[Object.keys(showSelected)[0]] === this._currSelected![Object.keys(showSelected)[0]] && showSelected[Object.keys(showSelected)[1]] === this._currSelected![Object.keys(showSelected)[1]] : this._currSelected === showSelected; this._currSelected = sameAsCurrent ? undefined : showSelected; this.selectedData = sameAsCurrent ? undefined : d; @@ -187,104 +185,100 @@ export class PieChart extends ObservableReactComponent<PieChartProps> { d3.select(this._piechartRef.current).select('svg').remove(); d3.select(this._piechartRef.current).select('.tooltip').remove(); - var percentField = Object.keys(dataSet[0])[0]; - var descriptionField = Object.keys(dataSet[0])[1]!; - var radius = Math.min(width, height - this._props.margin.top - this._props.margin.bottom) / 2; + let percentField = Object.keys(dataSet[0])[0]; + let descriptionField = Object.keys(dataSet[0])[1]!; + const radius = Math.min(width, height - this._props.margin.top - this._props.margin.bottom) / 2; // converts data into Objects - var data = this.data(dataSet); - var pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key]))); + let data = this.data(dataSet); + let pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key]))); if (this.byCategory) { - let uniqueCategories = [...new Set(data)]; - var pieStringDataSet: { frequency: number }[] = []; + const uniqueCategories = [...new Set(data)]; + const pieStringDataSet: { frequency: number }[] = []; for (let i = 0; i < uniqueCategories.length; i++) { pieStringDataSet.push({ frequency: 0, [percentField]: uniqueCategories[i] }); } for (let i = 0; i < data.length; i++) { - let sliceData = pieStringDataSet.filter((each: any) => each[percentField] == data[i]); - sliceData[0].frequency = sliceData[0].frequency + 1; + // eslint-disable-next-line no-loop-func + const sliceData = pieStringDataSet.filter((each: any) => each[percentField] === data[i]); + sliceData[0].frequency += 1; } pieDataSet = pieStringDataSet; - percentField = Object.keys(pieDataSet[0])[0]; - descriptionField = Object.keys(pieDataSet[0])[1]!; + [percentField, descriptionField] = Object.keys(pieDataSet[0]); data = this.data(pieStringDataSet); } - var trackDuplicates: { [key: string]: any } = {}; - data.forEach((eachData: any) => (!trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null)); + let trackDuplicates: { [key: string]: any } = {}; + data.forEach((eachData: any) => { + !trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null; + }); // initial chart - var svg = (this._piechartSvg = d3 + const svg = (this._piechartSvg = d3 .select(this._piechartRef.current) .append('svg') .attr('class', 'graph') .attr('width', width + this._props.margin.right + this._props.margin.left) .attr('height', height + this._props.margin.top + this._props.margin.bottom) .append('g')); - let g = svg.append('g').attr('transform', 'translate(' + (width / 2 + this._props.margin.left) + ',' + height / 2 + ')'); - var pie = d3.pie(); - var arc = d3.arc().innerRadius(0).outerRadius(radius); + const g = svg.append('g').attr('transform', 'translate(' + (width / 2 + this._props.margin.left) + ',' + height / 2 + ')'); + const pie = d3.pie(); + const arc = d3.arc().innerRadius(0).outerRadius(radius); + const updateHighlights = () => { + const hoverOverSlice = this.hoverOverData; + const { selectedData } = this; + svg.selectAll('path').attr('class', (d: any) => + (selectedData && d.startAngle === selectedData.startAngle && d.endAngle === selectedData.endAngle) || (hoverOverSlice && d.startAngle === hoverOverSlice.startAngle && d.endAngle === hoverOverSlice.endAngle) ? 'slice hover' : 'slice' + ); + }; // click/hover const onPointClick = action((e: any) => this.highlightSelectedSlice(true, svg, arc, radius, d3.pointer(e), pieDataSet)); const onHover = action((e: any) => { this.highlightSelectedSlice(false, svg, arc, radius, d3.pointer(e), pieDataSet); updateHighlights(); }); - const mouseOut = action((e: any) => { + const mouseOut = action(() => { this.hoverOverData = undefined; updateHighlights(); }); - const updateHighlights = () => { - const hoverOverSlice = this.hoverOverData; - const selectedData = this.selectedData; - svg.selectAll('path').attr('class', function (d: any) { - return (selectedData && d.startAngle == selectedData.startAngle && d.endAngle == selectedData.endAngle) || (hoverOverSlice && d.startAngle == hoverOverSlice.startAngle && d.endAngle == hoverOverSlice.endAngle) - ? 'slice hover' - : 'slice'; - }); - }; // drawing the slices - var selected = this.selectedData; - var arcs = g.selectAll('arc').data(pie(data)).enter().append('g'); + const selected = this.selectedData; + const arcs = g.selectAll('arc').data(pie(data)).enter().append('g'); const possibleDataPointVals: { [x: string]: any }[] = []; pieDataSet.forEach((each: { [x: string]: any | { valueOf(): number } }) => { - var dataPointVal: { [x: string]: any } = {}; + const dataPointVal: { [x: string]: any } = {}; dataPointVal[percentField] = each[percentField]; if (descriptionField) dataPointVal[descriptionField] = each[descriptionField]; try { - dataPointVal[percentField] = Number(dataPointVal[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '')); - } catch (error) {} + dataPointVal[percentField] = Number(dataPointVal[percentField].replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '')); + } catch (error) { + /* empty */ + } possibleDataPointVals.push(dataPointVal); }); const sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::')); arcs.append('path') .attr('fill', (d, i) => { - var dataPoint; + let dataPoint; const possibleDataPoints = possibleDataPointVals.filter((pval: any) => pval[percentField] === Number(d.data)); - if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0]; + if (possibleDataPoints.length === 1) [dataPoint] = possibleDataPoints; else { dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]]; trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1; } - var sliceColor; + let sliceColor; if (dataPoint) { const sliceTitle = dataPoint[this._props.axes[0]]; - const accessByName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle; - sliceColors.forEach(each => each[0] == accessByName && (sliceColor = each[1])); + const accessByName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') : sliceTitle; + sliceColors.forEach(each => { + // eslint-disable-next-line prefer-destructuring + each[0] === accessByName && (sliceColor = each[1]); + }); } return sliceColor ? StrCast(sliceColor) : d3.schemeSet3[i] ? d3.schemeSet3[i] : d3.schemeSet3[i % d3.schemeSet3.length]; }) - .attr( - 'class', - selected - ? function (d) { - return selected && d.startAngle == selected.startAngle && d.endAngle == selected.endAngle ? 'slice hover' : 'slice'; - } - : function (d) { - return 'slice'; - } - ) + .attr('class', selected ? d => (selected && d.startAngle === selected.startAngle && d.endAngle === selected.endAngle ? 'slice hover' : 'slice') : () => 'slice') // @ts-ignore .attr('d', arc) .on('click', onPointClick) @@ -293,20 +287,22 @@ export class PieChart extends ObservableReactComponent<PieChartProps> { // adding labels trackDuplicates = {}; - data.forEach((eachData: any) => (!trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null)); + data.forEach((eachData: any) => { + !trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null; + }); arcs.size() < 100 && arcs .append('text') - .attr('transform', function (d) { - var centroid = arc.centroid(d as unknown as d3.DefaultArcObject); - var heightOffset = (centroid[1] / radius) * Math.abs(centroid[1]); + .attr('transform', d => { + const centroid = arc.centroid(d as unknown as d3.DefaultArcObject); + const heightOffset = (centroid[1] / radius) * Math.abs(centroid[1]); return 'translate(' + (centroid[0] + centroid[0] / (radius * 0.02)) + ',' + (centroid[1] + heightOffset) + ')'; }) .attr('text-anchor', 'middle') - .text(function (d) { - var dataPoint; + .text(d => { + let dataPoint; const possibleDataPoints = possibleDataPointVals.filter((pval: any) => pval[percentField] === Number(d.data)); - if (possibleDataPoints.length == 1) dataPoint = pieDataSet[possibleDataPointVals.indexOf(possibleDataPoints[0])]; + if (possibleDataPoints.length === 1) dataPoint = pieDataSet[possibleDataPointVals.indexOf(possibleDataPoints[0])]; else { dataPoint = pieDataSet[possibleDataPointVals.indexOf(possibleDataPoints[trackDuplicates[d.data.toString()]])]; trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1; @@ -318,11 +314,11 @@ export class PieChart extends ObservableReactComponent<PieChartProps> { @action changeSelectedColor = (color: string) => { this.curSliceSelected.attr('fill', color); const sliceTitle = this._currSelected[this._props.axes[0]]; - const sliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle; + const sliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') : sliceTitle; const sliceColors = Cast(this._props.layoutDoc.dataViz_pie_sliceColors, listSpec('string'), null); - sliceColors.map(each => { - if (each.split('::')[0] == sliceName) sliceColors.splice(sliceColors.indexOf(each), 1); + sliceColors.forEach(each => { + if (each.split('::')[0] === sliceName) sliceColors.splice(sliceColors.indexOf(each), 1); }); sliceColors.push(StrCast(sliceName + '::' + color)); }; @@ -333,38 +329,39 @@ export class PieChart extends ObservableReactComponent<PieChartProps> { }; render() { - var titleAccessor: any = 'dataViz_pie_title'; - if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1]; - else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0]; + let titleAccessor: any = 'dataViz_pie_title'; + if (this._props.axes.length === 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1]; + else if (this._props.axes.length > 0) titleAccessor += this._props.axes[0]; if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle; if (!this._props.layoutDoc.dataViz_pie_sliceColors) this._props.layoutDoc.dataViz_pie_sliceColors = new List<string>(); - var selected: string; - var curSelectedSliceName = ''; + let selected: string; + let curSelectedSliceName = ''; if (this._currSelected) { selected = '{ '; const sliceTitle = this._currSelected[this._props.axes[0]]; - curSelectedSliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle; - Object.keys(this._currSelected).map(key => { - key != '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : ''; + curSelectedSliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') : sliceTitle; + Object.keys(this._currSelected).forEach(key => { + key !== '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : ''; }); selected = selected.substring(0, selected.length - 2); selected += ' }'; - if (this._props.titleCol != '' && (!this._currSelected['frequency'] || this._currSelected['frequency'] < 10)) { + if (this._props.titleCol !== '' && (!this._currSelected.frequency || this._currSelected.frequency < 10)) { selected += '\n' + this._props.titleCol + ': '; this._tableData.forEach(each => { - if (this._currSelected[this._props.axes[0]] == each[this._props.axes[0]]) { + if (this._currSelected[this._props.axes[0]] === each[this._props.axes[0]]) { if (this._props.axes[1]) { - if (this._currSelected[this._props.axes[1]] == each[this._props.axes[1]]) selected += each[this._props.titleCol] + ', '; + if (this._currSelected[this._props.axes[1]] === each[this._props.axes[1]]) selected += each[this._props.titleCol] + ', '; } else selected += each[this._props.titleCol] + ', '; } }); selected = selected.slice(0, -1).slice(0, -1); } } else selected = 'none'; - var selectedSliceColor; - var sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::')); + let selectedSliceColor; + const sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::')); sliceColors.forEach(each => { - if (each[0] == curSelectedSliceName!) selectedSliceColor = each[1]; + // eslint-disable-next-line prefer-destructuring + if (each[0] === curSelectedSliceName!) selectedSliceColor = each[1]; }); if (this._pieChartData.length > 0 || !this.parentViz) { @@ -374,30 +371,32 @@ export class PieChart extends ObservableReactComponent<PieChartProps> { <EditableText val={StrCast(this._props.layoutDoc[titleAccessor])} setVal={undoable( - action(val => (this._props.layoutDoc[titleAccessor] = val as string)), + action(val => { + this._props.layoutDoc[titleAccessor] = val as string; + }), 'Change Graph Title' )} - color={'black'} + color="black" size={Size.LARGE} fillWidth /> </div> {this._props.axes.length === 1 && /\d/.test(this._props.records[0][this._props.axes[0]]) ? ( - <div className={'asHistogram-checkBox'} style={{ width: this._props.width }}> + <div className="asHistogram-checkBox" style={{ width: this._props.width }}> <Checkbox color="primary" onChange={this.changeHistogramCheckBox} checked={this._props.layoutDoc.dataViz_pie_asHistogram as boolean} /> Organize data as histogram </div> ) : null} <div ref={this._piechartRef} /> - {selected != 'none' ? ( - <div className={'selected-data'}> + {selected !== 'none' ? ( + <div className="selected-data"> Selected: {selected} <ColorPicker - tooltip={'Change Slice Color'} + tooltip="Change Slice Color" type={Type.SEC} icon={<FaFillDrip />} - selectedColor={selectedSliceColor ? selectedSliceColor : this.curSliceSelected.attr('fill')} + selectedColor={selectedSliceColor || this.curSliceSelected.attr('fill')} setFinalColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Slice Color')} setSelectedColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Slice Color')} size={Size.XSMALL} @@ -406,12 +405,12 @@ export class PieChart extends ObservableReactComponent<PieChartProps> { ) : null} </div> ) : ( - <span className="chart-container"> {'first use table view to select a column to graph'}</span> - ); - } else - return ( - // when it is a brushed table and the incoming table doesn't have any rows selected - <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div> + <span className="chart-container"> first use table view to select a column to graph</span> ); + } + return ( + // when it is a brushed table and the incoming table doesn't have any rows selected + <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div> + ); } } diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts index 336935d23..be05c3529 100644 --- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts +++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts @@ -5,11 +5,11 @@ import { DataPoint } from '../components/LineChart'; export const minMaxRange = (dataPts: DataPoint[][]) => { // find the max and min of all the data points - const yMin = d3.min(dataPts, d => d3.min(d, d => Number(d.y))); - const yMax = d3.max(dataPts, d => d3.max(d, d => Number(d.y))); + const yMin = d3.min(dataPts, d => d3.min(d, m => Number(m.y))); + const yMax = d3.max(dataPts, d => d3.max(d, m => Number(m.y))); - const xMin = d3.min(dataPts, d => d3.min(d, d => Number(d.x))); - const xMax = d3.max(dataPts, d => d3.max(d, d => Number(d.x))); + const xMin = d3.min(dataPts, d => d3.min(d, m => Number(m.x))); + const xMax = d3.max(dataPts, d => d3.max(d, m => Number(m.x))); return { xMin, xMax, yMin, yMax }; }; @@ -20,18 +20,15 @@ export const scaleCreatorCategorical = (labels: string[], range: number[]) => { return scale; }; -export const scaleCreatorNumerical = (domA: number, domB: number, rangeA: number, rangeB: number) => { - return d3.scaleLinear().domain([domA, domB]).range([rangeA, rangeB]); -}; +export const scaleCreatorNumerical = (domA: number, domB: number, rangeA: number, rangeB: number) => d3.scaleLinear().domain([domA, domB]).range([rangeA, rangeB]); -export const createLineGenerator = (xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) => { +export const createLineGenerator = (xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) => // TODO: nda - look into the different types of curves - return d3 + d3 .line<DataPoint>() .x(d => xScale(d.x)) .y(d => yScale(d.y)) .curve(d3.curveMonotoneX); -}; export const xAxisCreator = (g: d3.Selection<SVGGElement, unknown, null, undefined>, height: number, xScale: d3.ScaleLinear<number, number, never>) => { g.attr('class', 'x-axis').attr('transform', `translate(0,${height})`).call(d3.axisBottom(xScale).tickSize(15)); @@ -48,7 +45,7 @@ export const xGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, he d3 .axisBottom(scale) .tickSize(-height) - .tickFormat((a, b) => '') + .tickFormat((/* a, b */) => '') ); }; @@ -57,10 +54,16 @@ export const yGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, wi d3 .axisLeft(scale) .tickSize(-width) - .tickFormat((a, b) => '') + .tickFormat((/* a, b */) => '') ); }; export const drawLine = (p: d3.Selection<SVGPathElement, unknown, null, undefined>, dataPts: DataPoint[], lineGen: d3.Line<DataPoint>, extra: boolean) => { - p.datum(dataPts).attr('fill', 'none').attr('stroke', 'rgba(53, 162, 235, 0.5)').attr('stroke-width', 2).attr('stroke', extra? 'blue' : 'black').attr('class', 'line').attr('d', lineGen); + p.datum(dataPts) + .attr('fill', 'none') + .attr('stroke', 'rgba(53, 162, 235, 0.5)') + .attr('stroke-width', 2) + .attr('stroke', extra ? 'blue' : 'black') + .attr('class', 'line') + .attr('d', lineGen); }; diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index a029b3761..9b4e36509 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -92,7 +92,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB e, this.onLinkButtonMoved, emptyFunction, - action((e, doubleTap) => { + action((clickEv, doubleTap) => { doubleTap && DocumentView.showBackLinks(this._props.View.Document); }), undefined, @@ -109,7 +109,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB e, this.onLinkButtonMoved, emptyFunction, - action((e, doubleTap) => { + action((clickEv, doubleTap) => { if (doubleTap && this._props.InMenu && this._props.StartLink) { // action(() => Doc.BrushDoc(this._props.View.Document)); if (DocumentLinksButton.StartLink === this._props.View.Document) { @@ -146,7 +146,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB e, returnFalse, emptyFunction, - action(e => DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this._props.View.Document, true, this._props.View)) + action(clickEv => DocumentLinksButton.finishLinkClick(clickEv.clientX, clickEv.clientY, DocumentLinksButton.StartLink, this._props.View.Document, true, this._props.View)) ); }; diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index 9be66ba4a..9c216cba4 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -1,3 +1,4 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ import { action, makeObservable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -42,7 +43,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() { this._ref.current!.mathField.latex(text); } } - //{ fireImmediately: true } + // { fireImmediately: true } ); reaction( () => this._props.isSelected(), @@ -88,7 +89,9 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() { if (e.key === 'Backspace' && !this.dataDoc.text) this._props.removeDocument?.(this.Document); }; @undoBatch - onChange = (str: string) => (this.dataDoc.text = str); + onChange = (str: string) => { + this.dataDoc.text = str; + }; updateSize = () => { const style = this._ref.current && getComputedStyle(this._ref.current.element.current); @@ -111,7 +114,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() { const scale = (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1); return ( <div - ref={r => this.updateSize()} + ref={() => this.updateSize()} className="equationBox-cont" onPointerDown={e => !e.ctrlKey && e.stopPropagation()} style={{ @@ -122,7 +125,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() { fontSize: StrCast(this.layoutDoc._text_fontSize), }} onKeyDown={e => e.stopPropagation()}> - <EquationEditor ref={this._ref} value={StrCast(this.dataDoc.text, 'x')} spaceBehavesLikeTab={true} onChange={this.onChange} autoCommands="pi theta sqrt sum prod alpha beta gamma rho" autoOperatorNames="sin cos tan" /> + <EquationEditor ref={this._ref} value={StrCast(this.dataDoc.text, 'x')} spaceBehavesLikeTab onChange={this.onChange} autoCommands="pi theta sqrt sum prod alpha beta gamma rho" autoOperatorNames="sin cos tan" /> </div> ); } diff --git a/src/client/views/nodes/FaceRectangle.tsx b/src/client/views/nodes/FaceRectangle.tsx index 46bc6eb03..2b66b83fe 100644 --- a/src/client/views/nodes/FaceRectangle.tsx +++ b/src/client/views/nodes/FaceRectangle.tsx @@ -8,11 +8,17 @@ export default class FaceRectangle extends React.Component<{ rectangle: Rectangl @observable private opacity = 0; componentDidMount() { - setTimeout(() => runInAction(() => (this.opacity = 1)), 500); + setTimeout( + () => + runInAction(() => { + this.opacity = 1; + }), + 500 + ); } render() { - const rectangle = this.props.rectangle; + const { rectangle } = this.props; return ( <div style={{ diff --git a/src/client/views/nodes/FontIconBox/ButtonInterface.ts b/src/client/views/nodes/FontIconBox/ButtonInterface.ts index 1c034bfbe..0d0d7b1c3 100644 --- a/src/client/views/nodes/FontIconBox/ButtonInterface.ts +++ b/src/client/views/nodes/FontIconBox/ButtonInterface.ts @@ -1,5 +1,5 @@ -import { Doc } from '../../../../fields/Doc'; import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { Doc } from '../../../../fields/Doc'; import { ButtonType } from './FontIconBox'; export interface IButtonProps { diff --git a/src/client/views/nodes/FontIconBox/TrailsIcon.tsx b/src/client/views/nodes/FontIconBox/TrailsIcon.tsx index 09fd6e3ae..76f00b2f4 100644 --- a/src/client/views/nodes/FontIconBox/TrailsIcon.tsx +++ b/src/client/views/nodes/FontIconBox/TrailsIcon.tsx @@ -1,10 +1,11 @@ import * as React from 'react'; -const TrailsIcon = (fill: string) => ( - <svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 1080.000000 1080.000000" preserveAspectRatio="xMidYMid meet"> - <g transform="translate(0.000000,1080.000000) scale(0.100000,-0.100000)" fill={fill} stroke="none"> - <path - d="M665 9253 c-74 -10 -157 -38 -240 -81 -74 -37 -107 -63 -186 -141 +function TrailsIcon(fill: string) { + return ( + <svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 1080.000000 1080.000000" preserveAspectRatio="xMidYMid meet"> + <g transform="translate(0.000000,1080.000000) scale(0.100000,-0.100000)" fill={fill} stroke="none"> + <path + d="M665 9253 c-74 -10 -157 -38 -240 -81 -74 -37 -107 -63 -186 -141 -104 -104 -156 -191 -201 -334 l-23 -72 0 -3215 c0 -3072 1 -3218 18 -3280 10 -36 39 -108 64 -160 40 -82 59 -107 142 -190 81 -81 111 -103 191 -143 52 -26 122 -55 155 -65 57 -16 322 -17 4775 -20 3250 -2 4736 1 4784 8 256 39 486 @@ -14,68 +15,69 @@ const TrailsIcon = (fill: string) => ( -62 -101 -108 -126 l-42 -22 -4435 -3 c-3954 -2 -4440 0 -4481 13 -26 9 -63 33 -87 56 -79 79 -72 -205 -72 3012 0 2156 3 2889 12 2918 20 70 91 136 168 160 14 4 2010 8 4436 8 3710 1 4418 -1 4456 -13z" - /> - <path - d="M7692 7839 c-46 -14 -109 -80 -122 -128 -7 -27 -9 -472 -8 -1443 l3 + /> + <path + d="M7692 7839 c-46 -14 -109 -80 -122 -128 -7 -27 -9 -472 -8 -1443 l3 -1403 24 -38 c13 -21 42 -50 64 -65 l41 -27 816 0 816 0 41 27 c22 15 51 44 64 65 l24 38 0 1425 0 1425 -24 38 c-13 21 -42 50 -64 65 l-41 27 -800 2 c-488 1 -814 -2 -834 -8z" - /> - <path - d="M1982 7699 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -308 -8 -893 l3 + /> + <path + d="M1982 7699 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -308 -8 -893 l3 -853 24 -38 c13 -21 42 -50 64 -65 l41 -27 1386 0 1386 0 41 27 c22 15 51 44 64 65 l24 38 0 876 0 875 -27 41 c-15 22 -44 51 -65 64 l-38 24 -1370 2 c-847 1 -1383 -2 -1403 -8z" - /> - <path - d="M6413 7093 c-13 -2 -23 -9 -23 -15 0 -24 21 -307 26 -343 l5 -40 182 + /> + <path + d="M6413 7093 c-13 -2 -23 -9 -23 -15 0 -24 21 -307 26 -343 l5 -40 182 -1 c200 -1 307 -15 484 -65 57 -16 107 -29 112 -29 5 0 36 75 69 168 33 92 63 175 67 184 6 14 -10 22 -92 48 -126 39 -308 76 -447 89 -106 11 -337 13 -383 4z" - /> - <path - d="M5840 7033 c-63 -8 -238 -29 -388 -47 -150 -18 -274 -35 -276 -37 -2 + /> + <path + d="M5840 7033 c-63 -8 -238 -29 -388 -47 -150 -18 -274 -35 -276 -37 -2 -2 8 -89 23 -194 22 -163 29 -190 44 -193 10 -2 91 6 180 17 89 12 258 32 376 46 118 14 216 27 218 28 7 8 -43 391 -52 392 -5 1 -62 -4 -125 -12z" - /> - <path - d="M4762 4789 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -323 -8 -943 l3 + /> + <path + d="M4762 4789 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -323 -8 -943 l3 -903 24 -38 c13 -21 42 -50 64 -65 l41 -27 926 0 926 0 41 27 c22 15 51 44 64 65 l24 38 0 926 0 925 -27 41 c-15 22 -44 51 -65 64 l-38 24 -910 2 c-557 1 -923 -2 -943 -8z" - /> - <path - d="M8487 4297 c-26 -215 -161 -474 -307 -585 -27 -20 -49 -40 -49 -44 + /> + <path + d="M8487 4297 c-26 -215 -161 -474 -307 -585 -27 -20 -49 -40 -49 -44 -1 -3 49 -79 110 -167 l110 -161 44 31 c176 126 333 350 418 594 30 86 77 282 77 320 0 8 -57 19 -167 34 -93 13 -182 25 -199 28 -31 5 -31 5 -37 -50z" - /> - <path - d="M3965 4233 c-106 -9 -348 -36 -415 -47 -55 -8 -75 -15 -74 -26 1 -20 + /> + <path + d="M3965 4233 c-106 -9 -348 -36 -415 -47 -55 -8 -75 -15 -74 -26 1 -20 56 -374 59 -377 1 -2 46 4 101 12 159 24 409 45 526 45 l108 0 0 200 0 200 -132 -2 c-73 -1 -151 -3 -173 -5z" - /> - <path - d="M3020 4079 c-85 -23 -292 -94 -368 -125 -97 -40 -298 -140 -305 -151 + /> + <path + d="M3020 4079 c-85 -23 -292 -94 -368 -125 -97 -40 -298 -140 -305 -151 -5 -7 172 -315 192 -336 4 -4 41 10 82 32 103 55 272 123 414 165 66 20 125 38 132 41 11 4 -4 70 -78 348 -10 39 -14 41 -69 26z" - /> - <path - d="M6955 3538 c-21 -91 -74 -362 -72 -364 7 -7 260 -44 367 -54 146 -13 + /> + <path + d="M6955 3538 c-21 -91 -74 -362 -72 -364 7 -7 260 -44 367 -54 146 -13 359 -13 475 0 49 6 90 12 91 13 2 1 -12 90 -29 197 -26 155 -36 194 -47 192 -8 -2 -85 -6 -170 -9 -160 -6 -357 7 -505 33 -103 18 -104 18 -110 -8z" - /> - <path - d="M1993 3513 c-52 -67 -71 -106 -98 -198 -35 -122 -44 -284 -21 -415 9 + /> + <path + d="M1993 3513 c-52 -67 -71 -106 -98 -198 -35 -122 -44 -284 -21 -415 9 -51 18 -96 21 -98 4 -5 360 79 375 88 7 4 7 24 0 60 -21 109 -7 244 31 307 l20 31 -146 131 c-80 72 -147 131 -149 131 -2 0 -17 -17 -33 -37z" - /> - <path - d="M2210 2519 c-91 -50 -166 -92 -168 -94 -2 -1 11 -26 28 -54 l32 -51 + /> + <path + d="M2210 2519 c-91 -50 -166 -92 -168 -94 -2 -1 11 -26 28 -54 l32 -51 244 0 c134 0 244 2 244 5 0 3 -23 33 -51 67 -28 35 -72 98 -97 140 -26 43 -51 77 -57 77 -5 0 -84 -41 -175 -90z" - /> - </g> - </svg> -); + /> + </g> + </svg> + ); +} export default TrailsIcon; diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index bff6d53da..d43241de0 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -45,7 +45,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() { onPointerDown = (e: React.PointerEvent) => { const { linkSource } = this; linkSource && - setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => { + setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (clickEv, doubleTap) => { if (doubleTap) LinkFollower.FollowLink(this.Document, linkSource, false); else this._props.select(false); }); diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 2593491cc..f01905ee1 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -50,15 +51,19 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { this._props.setContentViewBox?.(this); this._disposers.deleting = reaction( () => !this.anchor1 && !this.anchor2 && this.DocumentView?.() && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView!())), - empty => empty && ((this._hackToSeeIfDeleted = setTimeout(() => - (!this.anchor1 && !this.anchor2) && this._props.removeDocument?.(this.Document) - )), 1000) // prettier-ignore + empty => { + if (empty) { + this._hackToSeeIfDeleted = setTimeout(() => { + !this.anchor1 && !this.anchor2 && this._props.removeDocument?.(this.Document); + }, 1000); + } + } ); this._disposers.dragging = reaction( () => SnappingManager.IsDragging, () => setTimeout( action(() => {// need to wait for drag manager to set 'hidden' flag on dragged DOM elements - const a = this.anchor1, - b = this.anchor2; + const a = this.anchor1; + const b = this.anchor2; let a1 = a && document.getElementById(a.ViewGuid); let a2 = b && document.getElementById(b.ViewGuid); // test whether the anchors themselves are hidden,... @@ -67,7 +72,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { // .. or whether any of their DOM parents are hidden for (; a1 && !a1.hidden; a1 = a1.parentElement); for (; a2 && !a2.hidden; a2 = a2.parentElement); - this._hide = a1 || a2 ? true : false; + this._hide = !!(a1 || a2); } })) // prettier-ignore ); @@ -92,24 +97,25 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { a.Document[DocCss]; b.Document[DocCss]; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const axf = a.screenToViewTransform(); // these force re-render when a or b moves (so do NOT remove) const bxf = b.screenToViewTransform(); const scale = docView?.screenToViewTransform().Scale ?? 1; const at = a.getBounds?.transition; // these force re-render when a or b change size and at the end of an animated transition const bt = b.getBounds?.transition; // inquring getBounds() also causes text anchors to update whether or not they reflow (any size change triggers an invalidation) - var foundParent = false; + let foundParent = false; const getAnchor = (field: FieldResult): Element[] => { const docField = DocCast(field); const doc = docField?.layout_unrendered ? DocCast(docField.annotationOn, docField) : docField; const ele = document.getElementById(DocumentView.UniquifyId(LightboxView.Contains(this.DocumentView?.()), doc[Id])); if (ele?.className === 'linkBox-label') foundParent = true; if (ele?.getBoundingClientRect().width) return [ele]; - const eles = Array.from(document.getElementsByClassName(doc[Id])).filter(ele => ele?.getBoundingClientRect().width); + const eles = Array.from(document.getElementsByClassName(doc[Id])).filter(el => el?.getBoundingClientRect().width); const annoOn = DocCast(doc.annotationOn); if (eles.length || !annoOn) return eles; const pareles = getAnchor(annoOn); - foundParent = pareles.length ? true : false; + foundParent = !!pareles.length; return pareles; }; // if there's an element in the DOM with a classname containing a link anchor's id (eg a hypertext <a>), @@ -122,26 +128,38 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { const aid = targetAhyperlinks?.find(alink => container?.contains(alink))?.id ?? targetAhyperlinks?.lastElement()?.id; const bid = targetBhyperlinks?.find(blink => container?.contains(blink))?.id ?? targetBhyperlinks?.lastElement()?.id; if (!aid || !bid) { - setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); + setTimeout( + action(() => { + this._forceAnimate += 0.01; + }) + ); return null; } if (foundParent) { setTimeout( - action(() => (this._forceAnimate = this._forceAnimate + 0.01)), + action(() => { + this._forceAnimate += 0.01; + }), 1 ); } - if (at || bt) setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); // this forces an update during a transition animation + if (at || bt) + setTimeout( + action(() => { + this._forceAnimate += 0.01; + }) + ); // this forces an update during a transition animation const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting); const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined; const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color); const fontFamily = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily); const fontSize = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize); const fontColor = (c => (c !== 'transparent' ? c : undefined))(StrCast(this.layoutDoc.link_fontColor)); - const { stroke_markerScale, stroke_width, stroke_startMarker, stroke_endMarker, stroke_dash } = this.Document; + // eslint-disable-next-line camelcase + const { stroke_markerScale: strokeMarkerScale, stroke_width: strokeRawWidth, stroke_startMarker: strokeStartMarker, stroke_endMarker: strokeEndMarker, stroke_dash: strokeDash } = this.Document; - const strokeWidth = NumCast(stroke_width, 4); + const strokeWidth = NumCast(strokeRawWidth, 4); const linkDesc = StrCast(this.dataDoc.link_description) || ' '; const labelText = linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : ''); return ( @@ -152,12 +170,12 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { start={aid} end={bid} // strokeWidth={strokeWidth + Math.max(2, strokeWidth * 0.1)} - showHead={stroke_startMarker ? true : false} - showTail={stroke_endMarker ? true : false} - headSize={NumCast(stroke_markerScale, 3)} - tailSize={NumCast(stroke_markerScale, 3)} - tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'} - headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'} + showHead={!!strokeStartMarker} + showTail={!!strokeEndMarker} + headSize={NumCast(strokeMarkerScale, 3)} + tailSize={NumCast(strokeMarkerScale, 3)} + tailShape={strokeEndMarker === 'dot' ? 'circle' : 'arrow1'} + headShape={strokeStartMarker === 'dot' ? 'circle' : 'arrow1'} color={highlightColor} /> )} @@ -166,23 +184,23 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { start={aid} end={bid} // strokeWidth={strokeWidth} - dashness={Number(stroke_dash) ? true : false} - showHead={stroke_startMarker ? true : false} - showTail={stroke_endMarker ? true : false} - headSize={NumCast(stroke_markerScale, 3)} - tailSize={NumCast(stroke_markerScale, 3)} - tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'} - headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'} + dashness={!!Number(strokeDash)} + showHead={!!strokeStartMarker} + showTail={!!strokeEndMarker} + headSize={NumCast(strokeMarkerScale, 3)} + tailSize={NumCast(strokeMarkerScale, 3)} + tailShape={strokeEndMarker === 'dot' ? 'circle' : 'arrow1'} + headShape={strokeStartMarker === 'dot' ? 'circle' : 'arrow1'} color={color} labels={ <div id={this.DocumentView?.().DocUniqueId} - className={'linkBox-label'} + className="linkBox-label" style={{ borderRadius: '8px', pointerEvents: this._props.isDocumentActive?.() ? 'all' : undefined, fontSize, - fontFamily /*, fontStyle: 'italic'*/, + fontFamily /* , fontStyle: 'italic' */, color: fontColor || lightOrDark(DashColor(color).fade(0.5).toString()), paddingLeft: 4, paddingRight: 4, @@ -223,16 +241,19 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { } setTimeout( - action(() => (this._forceAnimate = this._forceAnimate + 1)), + action(() => { + this._forceAnimate += 1; + }), 2 ); return ( <div className={`linkBox-container${this._props.isContentActive() ? '-interactive' : ''}`} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }}> <ComparisonBox + // eslint-disable-next-line react/jsx-props-no-spreading {...this.props} // fieldKey="link_anchor" setHeight={emptyFunction} - dontRegisterView={true} + dontRegisterView renderDepth={this._props.renderDepth + 1} addDocument={returnFalse} removeDocument={returnFalse} diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx index 2a96ce458..23cb25962 100644 --- a/src/client/views/nodes/LinkDescriptionPopup.tsx +++ b/src/client/views/nodes/LinkDescriptionPopup.tsx @@ -9,6 +9,7 @@ import { StrCast } from '../../../fields/Types'; @observer export class LinkDescriptionPopup extends React.Component<{}> { + // eslint-disable-next-line no-use-before-define public static Instance: LinkDescriptionPopup; @observable public display: boolean = false; @observable public showDescriptions: string = 'ON'; @@ -23,6 +24,20 @@ export class LinkDescriptionPopup extends React.Component<{}> { LinkDescriptionPopup.Instance = this; } + componentDidMount() { + document.addEventListener('pointerdown', this.onClick, true); + reaction( + () => this.display, + display => { + display && (this.description = StrCast(LinkManager.Instance.currentLink?.link_description)); + } + ); + } + + componentWillUnmount() { + document.removeEventListener('pointerdown', this.onClick, true); + } + @action descriptionChanged = (e: React.ChangeEvent<HTMLInputElement>) => { this.description = e.currentTarget.value; @@ -39,25 +54,13 @@ export class LinkDescriptionPopup extends React.Component<{}> { @action onClick = (e: PointerEvent) => { - if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) { + if (this.popupRef && !this.popupRef.current?.contains(e.target as any)) { this.display = false; this.description = ''; TaskCompletionBox.taskCompleted = false; } }; - componentDidMount() { - document.addEventListener('pointerdown', this.onClick, true); - reaction( - () => this.display, - display => display && (this.description = StrCast(LinkManager.Instance.currentLink?.link_description)) - ); - } - - componentWillUnmount() { - document.removeEventListener('pointerdown', this.onClick, true); - } - render() { return !this.display ? null : ( <div @@ -78,11 +81,11 @@ export class LinkDescriptionPopup extends React.Component<{}> { onChange={e => this.descriptionChanged(e)} /> <div className="linkDescriptionPopup-btn"> - <div className="linkDescriptionPopup-btn-dismiss" onPointerDown={e => this.onDismiss(false)}> + <div className="linkDescriptionPopup-btn-dismiss" onPointerDown={() => this.onDismiss(false)}> {' '} Dismiss{' '} </div> - <div className="linkDescriptionPopup-btn-add" onPointerDown={e => this.onDismiss(true)}> + <div className="linkDescriptionPopup-btn-add" onPointerDown={() => this.onDismiss(true)}> {' '} Add{' '} </div> diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 539daf0bd..a9cfe6c0e 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -23,35 +23,41 @@ import { DocumentView, OpenWhere } from './DocumentView'; import { StyleProviderFuncType } from './FieldView'; import './LinkDocPreview.scss'; +interface LinkDocPreviewProps { + linkDoc?: Doc; + linkSrc?: Doc; + DocumentView?: () => DocumentView; + styleProvider?: StyleProviderFuncType; + location: number[]; + hrefs?: string[]; + showHeader?: boolean; + noPreview?: boolean; +} export class LinkInfo { + // eslint-disable-next-line no-use-before-define private static _instance: Opt<LinkInfo>; constructor() { LinkInfo._instance = this; makeObservable(this); } + // eslint-disable-next-line no-use-before-define @observable public LinkInfo: Opt<LinkDocPreviewProps> = undefined; public static get Instance() { return LinkInfo._instance ?? new LinkInfo(); } public static Clear() { - runInAction(() => LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = undefined)); + runInAction(() => { + LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = undefined); + }); } public static SetLinkInfo(info?: LinkDocPreviewProps) { - runInAction(() => LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = info)); + runInAction(() => { + LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = info); + }); } } -interface LinkDocPreviewProps { - linkDoc?: Doc; - linkSrc?: Doc; - DocumentView?: () => DocumentView; - styleProvider?: StyleProviderFuncType; - location: number[]; - hrefs?: string[]; - showHeader?: boolean; - noPreview?: boolean; -} @observer export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps> { _infoRef = React.createRef<HTMLDivElement>(); @@ -69,13 +75,13 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps @action init() { - var linkTarget = this._props.linkDoc; + let linkTarget = this._props.linkDoc; this._linkSrc = this._props.linkSrc; this._linkDoc = this._props.linkDoc; - const link_anchor_1 = DocCast(this._linkDoc?.link_anchor_1); - const link_anchor_2 = DocCast(this._linkDoc?.link_anchor_2); - if (link_anchor_1 && link_anchor_2) { - linkTarget = Doc.AreProtosEqual(link_anchor_1, this._linkSrc) || Doc.AreProtosEqual(link_anchor_1?.annotationOn as Doc, this._linkSrc) ? link_anchor_2 : link_anchor_1; + const linkAnchor1 = DocCast(this._linkDoc?.link_anchor_1); + const linkAnchor2 = DocCast(this._linkDoc?.link_anchor_2); + if (linkAnchor1 && linkAnchor2) { + linkTarget = Doc.AreProtosEqual(linkAnchor1, this._linkSrc) || Doc.AreProtosEqual(linkAnchor1?.annotationOn as Doc, this._linkSrc) ? linkAnchor2 : linkAnchor1; } if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) { linkTarget = DocCast(linkTarget.annotationOn); // want to show annotation embedContainer document if annotation is not text @@ -111,7 +117,13 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps if (href.startsWith('https://en.wikipedia.org/wiki/')) { wiki() .page(href.replace('https://en.wikipedia.org/wiki/', '')) - .then(page => page.summary().then(action(summary => (this._toolTipText = summary.substring(0, 500))))); + .then(page => + page.summary().then( + action(summary => { + this._toolTipText = summary.substring(0, 500); + }) + ) + ); } else { this._toolTipText = 'url => ' + href; } @@ -133,7 +145,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps this._linkSrc = anchor; const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); this._markerTargetDoc = linkTarget; - this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget; + this._targetDoc = /* linkTarget?.type === DocumentType.MARKER && */ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget; } if (LinkInfo.Instance?.LinkInfo?.noPreview || this._linkSrc?.followLinkToggle || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink(); } @@ -194,7 +206,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps willPan: true, zoomTime: 500, }); - //this._props.docProps?.addDocTab(webDoc, OpenWhere.lightbox); + // this._props.docProps?.addDocTab(webDoc, OpenWhere.lightbox); } }; @@ -249,9 +261,9 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps setupMoveUpEvents( this, e, - (e, down, delta) => { - if (Math.abs(e.clientX - down[0]) + Math.abs(e.clientY - down[1]) > 100) { - DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY); + (moveEv, down) => { + if (Math.abs(moveEv.clientX - down[0]) + Math.abs(moveEv.clientY - down[1]) > 100) { + DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), moveEv.pageX, moveEv.pageY); LinkInfo.Clear(); return true; } @@ -284,18 +296,18 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps removeDocument={returnFalse} addDocTab={returnFalse} pinToPres={returnFalse} - dontRegisterView={true} + dontRegisterView childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} renderDepth={0} - suppressSetHeight={true} + suppressSetHeight PanelWidth={this.width} PanelHeight={this.height} pointerEvents={returnNone} focus={emptyFunction} whenChildContentsActiveChanged={returnFalse} - ignoreAutoHeight={true} // need to ignore layout_autoHeight otherwise layout_autoHeight text boxes will expand beyond the preview panel size. + ignoreAutoHeight // need to ignore layout_autoHeight otherwise layout_autoHeight text boxes will expand beyond the preview panel size. NativeWidth={Doc.NativeWidth(this._targetDoc) ? () => Doc.NativeWidth(this._targetDoc) : undefined} NativeHeight={Doc.NativeHeight(this._targetDoc) ? () => Doc.NativeHeight(this._targetDoc) : undefined} /> diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx index adccc9db6..501831bca 100644 --- a/src/client/views/nodes/LoadingBox.tsx +++ b/src/client/views/nodes/LoadingBox.tsx @@ -60,7 +60,9 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { } else { const updateFunc = async () => { const result = await Networking.QueryYoutubeProgress(StrCast(this.Document[Id])); // We use the guid of the overwriteDoc to track file uploads. - runInAction(() => (this.progress = result.progress)); + runInAction(() => { + this.progress = result.progress; + }); !this.Document.loadingError && this._timer && (this._timer = setTimeout(updateFunc, 1000)); }; this._timer = setTimeout(updateFunc, 1000); diff --git a/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx b/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx index d54a175b2..f4ece627f 100644 --- a/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx +++ b/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx @@ -1,35 +1,44 @@ -import * as React from "react"; +import * as React from 'react'; export const slowSpeedIcon: JSX.Element = ( <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 435.62"> <defs> - <style type="text/css"> - {` + <style type="text/css"> + {` .fil0 { fill: black; fill-rule: nonzero; } .fil1 { fill: #FE0000; fill-rule: nonzero; } `} - </style> + </style> </defs> - <path className="fil0" d="M174.84 343.06c-7.31,-13.12 -13.03,-27.28 -16.89,-42.18 -3.76,-14.56 -5.77,-29.71 -5.77,-45.17 0,-11.94 1.19,-23.66 3.43,-35.03 2.29,-11.57 5.74,-22.83 10.2,-33.63 13.7,-33.14 37.01,-61.29 66.42,-80.96 25.38,-16.96 55.28,-27.66 87.45,-29.87l0 -30.17c0,-0.46 0.02,-0.92 0.06,-1.37l-33.7 0c-5.53,0 -10.05,-4.52 -10.05,-10.04l0 -24.59c0,-5.53 4.52,-10.05 10.05,-10.05l101.27 0c5.53,0 10.05,4.52 10.05,10.05l0 24.59c0,5.52 -4.52,10.04 -10.05,10.04l-33.69 0c0.03,0.45 0.05,0.91 0.05,1.37l0 31.03 -0.1 0c41.1,4.89 77.94,23.63 105.73,51.42 32.56,32.55 52.7,77.54 52.7,127.21 0,49.67 -20.14,94.66 -52.7,127.21 -32.55,32.55 -77.54,52.7 -127.21,52.7 -33.16,0 -64.29,-9.04 -91.05,-24.78 -27.66,-16.27 -50.59,-39.73 -66.2,-67.78zm148.42 -36.62l-80.33 0 0 -25.71 28.6 0 0 -42.57 -28.6 1.93 0 -25.71 36.95 -8.35 25.38 0 0 74.7 18 0 0 25.71zm44.34 -100.41l11.08 26.83 1.61 0 11.09 -26.83 34.86 0 -22.33 48.52 22.33 51.89 -35.67 0 -12.05 -28.92 -1.44 0 -11.89 28.92 -34.06 0 21.85 -50.93 -21.85 -49.48 36.47 0zm126.08 -74.6c6.98,-16.66 6.15,-34.13 -3.84,-45.82 -12,-14.03 -33.67,-15.64 -53.8,-5.77 21.32,14.62 40.68,31.63 57.64,51.59zm-323.17 0c-6.98,-16.66 -6.16,-34.13 3.84,-45.82 11.99,-14.03 33.67,-15.64 53.79,-5.77 -21.32,14.62 -40.68,31.63 -57.63,51.59zm15.31 162.23c3.23,12.5 8.04,24.39 14.18,35.42 13.13,23.58 32.39,43.29 55.6,56.94 22.37,13.16 48.52,20.71 76.49,20.71 41.71,0 79.47,-16.9 106.8,-44.23 27.32,-27.32 44.23,-65.08 44.23,-106.79 0,-41.71 -16.91,-79.47 -44.23,-106.8 -27.33,-27.32 -65.09,-44.23 -106.8,-44.23 -31.07,0 -59.91,9.34 -83.84,25.33 -24.74,16.54 -44.33,40.19 -55.82,67.98 -3.68,8.91 -6.56,18.35 -8.5,28.22 -1.87,9.49 -2.86,19.36 -2.86,29.5 0,13.24 1.65,25.96 4.75,37.95z"/> - <path className="fil1" d="M55.23 188.52c-7.98,0 -14.45,-6.47 -14.45,-14.44 0,-7.98 6.47,-14.45 14.45,-14.45l63.94 0c7.98,0 14.45,6.47 14.45,14.45 0,7.97 -6.47,14.44 -14.45,14.44l-63.94 0zm0.72 167.68c-7.97,0 -14.44,-6.47 -14.44,-14.45 0,-7.97 6.47,-14.45 14.44,-14.45l64.58 0c7.97,0 14.45,6.48 14.45,14.45 0,7.98 -6.48,14.45 -14.45,14.45l-64.58 0zm-41.5 -84.94c-7.98,0 -14.45,-6.47 -14.45,-14.45 0,-7.97 6.47,-14.44 14.45,-14.44l89.12 0c7.98,0 14.45,6.47 14.45,14.44 0,7.98 -6.47,14.45 -14.45,14.45l-89.12 0z"/> + <path + className="fil0" + d="M174.84 343.06c-7.31,-13.12 -13.03,-27.28 -16.89,-42.18 -3.76,-14.56 -5.77,-29.71 -5.77,-45.17 0,-11.94 1.19,-23.66 3.43,-35.03 2.29,-11.57 5.74,-22.83 10.2,-33.63 13.7,-33.14 37.01,-61.29 66.42,-80.96 25.38,-16.96 55.28,-27.66 87.45,-29.87l0 -30.17c0,-0.46 0.02,-0.92 0.06,-1.37l-33.7 0c-5.53,0 -10.05,-4.52 -10.05,-10.04l0 -24.59c0,-5.53 4.52,-10.05 10.05,-10.05l101.27 0c5.53,0 10.05,4.52 10.05,10.05l0 24.59c0,5.52 -4.52,10.04 -10.05,10.04l-33.69 0c0.03,0.45 0.05,0.91 0.05,1.37l0 31.03 -0.1 0c41.1,4.89 77.94,23.63 105.73,51.42 32.56,32.55 52.7,77.54 52.7,127.21 0,49.67 -20.14,94.66 -52.7,127.21 -32.55,32.55 -77.54,52.7 -127.21,52.7 -33.16,0 -64.29,-9.04 -91.05,-24.78 -27.66,-16.27 -50.59,-39.73 -66.2,-67.78zm148.42 -36.62l-80.33 0 0 -25.71 28.6 0 0 -42.57 -28.6 1.93 0 -25.71 36.95 -8.35 25.38 0 0 74.7 18 0 0 25.71zm44.34 -100.41l11.08 26.83 1.61 0 11.09 -26.83 34.86 0 -22.33 48.52 22.33 51.89 -35.67 0 -12.05 -28.92 -1.44 0 -11.89 28.92 -34.06 0 21.85 -50.93 -21.85 -49.48 36.47 0zm126.08 -74.6c6.98,-16.66 6.15,-34.13 -3.84,-45.82 -12,-14.03 -33.67,-15.64 -53.8,-5.77 21.32,14.62 40.68,31.63 57.64,51.59zm-323.17 0c-6.98,-16.66 -6.16,-34.13 3.84,-45.82 11.99,-14.03 33.67,-15.64 53.79,-5.77 -21.32,14.62 -40.68,31.63 -57.63,51.59zm15.31 162.23c3.23,12.5 8.04,24.39 14.18,35.42 13.13,23.58 32.39,43.29 55.6,56.94 22.37,13.16 48.52,20.71 76.49,20.71 41.71,0 79.47,-16.9 106.8,-44.23 27.32,-27.32 44.23,-65.08 44.23,-106.79 0,-41.71 -16.91,-79.47 -44.23,-106.8 -27.33,-27.32 -65.09,-44.23 -106.8,-44.23 -31.07,0 -59.91,9.34 -83.84,25.33 -24.74,16.54 -44.33,40.19 -55.82,67.98 -3.68,8.91 -6.56,18.35 -8.5,28.22 -1.87,9.49 -2.86,19.36 -2.86,29.5 0,13.24 1.65,25.96 4.75,37.95z" + /> + <path + className="fil1" + d="M55.23 188.52c-7.98,0 -14.45,-6.47 -14.45,-14.44 0,-7.98 6.47,-14.45 14.45,-14.45l63.94 0c7.98,0 14.45,6.47 14.45,14.45 0,7.97 -6.47,14.44 -14.45,14.44l-63.94 0zm0.72 167.68c-7.97,0 -14.44,-6.47 -14.44,-14.45 0,-7.97 6.47,-14.45 14.44,-14.45l64.58 0c7.97,0 14.45,6.48 14.45,14.45 0,7.98 -6.48,14.45 -14.45,14.45l-64.58 0zm-41.5 -84.94c-7.98,0 -14.45,-6.47 -14.45,-14.45 0,-7.97 6.47,-14.44 14.45,-14.44l89.12 0c7.98,0 14.45,6.47 14.45,14.44 0,7.98 -6.47,14.45 -14.45,14.45l-89.12 0z" + /> </svg> ); export const mediumSpeedIcon: JSX.Element = ( <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 122.88 104.55"> - <defs><style>{`.cls-1{fill:#fe0000;}`}</style></defs> - <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.45l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.09l5.36,12.45H93.74L90.85,66.6H90.5l-2.85,6.94H79.47l5.25-12.22L79.47,49.45ZM58.65,56.08l-1-5.75a33.58,33.58,0,0,1,9.68-1.46c1.28,0,2.35,0,3.22.11a11.77,11.77,0,0,1,2.67.58,5.41,5.41,0,0,1,2.2,1.28c1.24,1.23,1.85,3.12,1.85,5.66s-.72,4.42-2.16,5.63S70.64,64.73,66,66.3v1.08H76.89v6.16H57.11V68.72a10.73,10.73,0,0,1,.81-4.12,8.4,8.4,0,0,1,2.43-2.7,12.13,12.13,0,0,1,2.79-1.7l3.32-1.52c1-.47,1.88-.87,2.52-1.17V55.42a28.59,28.59,0,0,0-3.2-.19,30.66,30.66,0,0,0-7.13.85Zm59.83-24.54c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z"/> - <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Z"/> - <path className="cls-1" d="M3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z"/> - <path className="cls-1" d="M13.43,85.49a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94Z"/> + <defs> + <style>{`.cls-1{fill:#fe0000;}`}</style> + </defs> + <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.45l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.09l5.36,12.45H93.74L90.85,66.6H90.5l-2.85,6.94H79.47l5.25-12.22L79.47,49.45ZM58.65,56.08l-1-5.75a33.58,33.58,0,0,1,9.68-1.46c1.28,0,2.35,0,3.22.11a11.77,11.77,0,0,1,2.67.58,5.41,5.41,0,0,1,2.2,1.28c1.24,1.23,1.85,3.12,1.85,5.66s-.72,4.42-2.16,5.63S70.64,64.73,66,66.3v1.08H76.89v6.16H57.11V68.72a10.73,10.73,0,0,1,.81-4.12,8.4,8.4,0,0,1,2.43-2.7,12.13,12.13,0,0,1,2.79-1.7l3.32-1.52c1-.47,1.88-.87,2.52-1.17V55.42a28.59,28.59,0,0,0-3.2-.19,30.66,30.66,0,0,0-7.13.85Zm59.83-24.54c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z" /> + <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Z" /> + <path className="cls-1" d="M3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z" /> + <path className="cls-1" d="M13.43,85.49a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94Z" /> </svg> ); export const fastSpeedIcon: JSX.Element = ( <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 122.88 104.55"> - <defs><style>{`.cls-1{fill:#fe0000;`}</style></defs> - <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.61l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.26l5.36,12.45H93.74l-2.9-6.94H90.5l-2.86,6.94H79.47l5.24-12.22L79.47,49.61Zm-19,8.48v-2.5a24.92,24.92,0,0,0-3.74-.2A33.25,33.25,0,0,0,59,56.2l-1-5.7A30.47,30.47,0,0,1,67.13,49a22.86,22.86,0,0,1,5.48.47,6.91,6.91,0,0,1,2.5,1.11,5.62,5.62,0,0,1,1.78,4.55,5.84,5.84,0,0,1-3.2,5.56v.19a5.73,5.73,0,0,1,3.81,5.74,8.67,8.67,0,0,1-.63,3.49,6,6,0,0,1-1.6,2.24,7.15,7.15,0,0,1-2.55,1.25,25.64,25.64,0,0,1-6.61.66,37.78,37.78,0,0,1-8.54-1l1.08-6.37a27.22,27.22,0,0,0,6.21.89,35.79,35.79,0,0,0,4.35-.23V65.11l-6.63-.65V58.87l6.63-.78Zm49.27-26.55c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z"/> - <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Zm.18,40.24a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94ZM3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z"/> + <defs> + <style>{`.cls-1{fill:#fe0000;`}</style> + </defs> + <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.61l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.26l5.36,12.45H93.74l-2.9-6.94H90.5l-2.86,6.94H79.47l5.24-12.22L79.47,49.61Zm-19,8.48v-2.5a24.92,24.92,0,0,0-3.74-.2A33.25,33.25,0,0,0,59,56.2l-1-5.7A30.47,30.47,0,0,1,67.13,49a22.86,22.86,0,0,1,5.48.47,6.91,6.91,0,0,1,2.5,1.11,5.62,5.62,0,0,1,1.78,4.55,5.84,5.84,0,0,1-3.2,5.56v.19a5.73,5.73,0,0,1,3.81,5.74,8.67,8.67,0,0,1-.63,3.49,6,6,0,0,1-1.6,2.24,7.15,7.15,0,0,1-2.55,1.25,25.64,25.64,0,0,1-6.61.66,37.78,37.78,0,0,1-8.54-1l1.08-6.37a27.22,27.22,0,0,0,6.21.89,35.79,35.79,0,0,0,4.35-.23V65.11l-6.63-.65V58.87l6.63-.78Zm49.27-26.55c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z" /> + <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Zm.18,40.24a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94ZM3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z" /> </svg> ); - diff --git a/src/client/views/nodes/MapBox/AnimationUtility.ts b/src/client/views/nodes/MapBox/AnimationUtility.ts index 35153f439..cf5315da3 100644 --- a/src/client/views/nodes/MapBox/AnimationUtility.ts +++ b/src/client/views/nodes/MapBox/AnimationUtility.ts @@ -87,25 +87,24 @@ export class AnimationUtility { @computed get currentPitch(): number { if (!this.isStreetViewAnimation) return 50; if (!this.terrainDisplayed) return 80; - else { - // const groundElevation = 0; - const heightAboveGround = this.currentAnimationAltitude; - const horizontalDistance = 500; - - let pitch; - if (heightAboveGround >= 0) { - pitch = 90 - Math.atan(heightAboveGround / horizontalDistance) * (180 / Math.PI); - } else { - pitch = 80; - } - console.log(Math.max(50, Math.min(pitch, 85))); + // const groundElevation = 0; + const heightAboveGround = this.currentAnimationAltitude; + const horizontalDistance = 500; - if (this.previousPitch) { - return this.lerp(Math.max(50, Math.min(pitch, 85)), this.previousPitch, 0.02); - } - return Math.max(50, Math.min(pitch, 85)); + let pitch; + if (heightAboveGround >= 0) { + pitch = 90 - Math.atan(heightAboveGround / horizontalDistance) * (180 / Math.PI); + } else { + pitch = 80; + } + + console.log(Math.max(50, Math.min(pitch, 85))); + + if (this.previousPitch) { + return this.lerp(Math.max(50, Math.min(pitch, 85)), this.previousPitch, 0.02); } + return Math.max(50, Math.min(pitch, 85)); } @computed get flyInEndPitch() { @@ -214,8 +213,8 @@ export class AnimationUtility { currentAnimationPhase: number; updateAnimationPhase: (newAnimationPhase: number) => void; updateFrameId: (newFrameId: number) => void; - }) => { - return new Promise<void>(async resolve => { + }) => + new Promise<void>(async resolve => { let startTime: number | null = null; const frame = async (currentTime: number) => { @@ -257,7 +256,7 @@ export class AnimationUtility { updateAnimationPhase(animationPhase); // compute corrected camera ground position, so that he leading edge of the path is in view - var correctedPosition = this.computeCameraPosition( + const correctedPosition = this.computeCameraPosition( this.isStreetViewAnimation, this.currentPitch, bearing, @@ -277,7 +276,7 @@ export class AnimationUtility { map.setFreeCameraOptions(camera); this.previousAltitude = this.currentAnimationAltitude; - this.previousPitch = this.previousPitch; + // this.previousPitch = this.previousPitch; // repeat! const innerFrameId = await window.requestAnimationFrame(frame); @@ -287,15 +286,14 @@ export class AnimationUtility { const outerFrameId = await window.requestAnimationFrame(frame); updateFrameId(outerFrameId); }); - }; - public flyInAndRotate = async ({ map, updateFrameId }: { map: MapRef; updateFrameId: (newFrameId: number) => void }) => { - return new Promise<{ bearing: number; altitude: number }>(async resolve => { + public flyInAndRotate = async ({ map, updateFrameId }: { map: MapRef; updateFrameId: (newFrameId: number) => void }) => + new Promise<{ bearing: number; altitude: number }>(async resolve => { let start: number | null; - var currentAltitude; - var currentBearing; - var currentPitch; + let currentAltitude; + let currentBearing; + let currentPitch; // the animation frame will run as many times as necessary until the duration has been reached const frame = async (time: number) => { @@ -319,7 +317,7 @@ export class AnimationUtility { currentPitch = this.FLY_IN_START_PITCH + (this.flyInEndPitch - this.FLY_IN_START_PITCH) * d3.easeCubicOut(animationPhase); // compute corrected camera ground position, so the start of the path is always in view - var correctedPosition = this.computeCameraPosition(false, currentPitch, currentBearing, this.FIRST_LNG_LAT, currentAltitude); + const correctedPosition = this.computeCameraPosition(false, currentPitch, currentBearing, this.FIRST_LNG_LAT, currentAltitude); // set the pitch and bearing of the camera const camera = map.getFreeCameraOptions(); @@ -349,13 +347,10 @@ export class AnimationUtility { const outerFrameId = await window.requestAnimationFrame(frame); updateFrameId(outerFrameId); }); - }; previousCameraPosition: { lng: number; lat: number } | null = null; - lerp = (start: number, end: number, amt: number) => { - return (1 - amt) * start + amt * end; - }; + lerp = (start: number, end: number, amt: number) => (1 - amt) * start + amt * end; computeCameraPosition = (isStreetViewAnimation: boolean, pitch: number, bearing: number, targetPosition: { lng: number; lat: number }, altitude: number, smooth = false) => { const bearingInRadian = (bearing * Math.PI) / 180; diff --git a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx index fe7f8d8b3..f176317d2 100644 --- a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx +++ b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx @@ -14,6 +14,7 @@ import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; @observer export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { + // eslint-disable-next-line no-use-before-define static Instance: DirectionsAnchorMenu; private _disposer: IReactionDisposer | undefined; @@ -24,8 +25,8 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { public OnClick: (e: PointerEvent) => void = unimplementedFunction; // public OnAudio: (e: PointerEvent) => void = unimplementedFunction; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; - public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined; - public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined; + public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = () => undefined; + public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined; public Delete: () => void = unimplementedFunction; // public MakeTargetToggle: () => void = unimplementedFunction; // public ShowTargetTrail: () => void = unimplementedFunction; @@ -56,7 +57,7 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { componentDidMount() { this._disposer = reaction( () => SelectionManager.Views.slice(), - sel => DirectionsAnchorMenu.Instance.fadeOut(true) + () => DirectionsAnchorMenu.Instance.fadeOut(true) ); } // audioDown = (e: React.PointerEvent) => { @@ -104,8 +105,8 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { color={SettingsManager.userColor} /> - <IconButton tooltip="Animate route" onPointerDown={this.Delete} /**TODO: fix */ icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} /> - <IconButton tooltip="Add to calendar" onPointerDown={this.Delete} /**TODO: fix */ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} /> + <IconButton tooltip="Animate route" onPointerDown={this.Delete} /* *TODO: fix */ icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} /> + <IconButton tooltip="Add to calendar" onPointerDown={this.Delete} /* *TODO: fix */ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} /> </div> ); diff --git a/src/client/views/nodes/MapBox/GeocoderControl.tsx b/src/client/views/nodes/MapBox/GeocoderControl.tsx index e4ba51316..e118c57d9 100644 --- a/src/client/views/nodes/MapBox/GeocoderControl.tsx +++ b/src/client/views/nodes/MapBox/GeocoderControl.tsx @@ -3,8 +3,6 @@ // import { ControlPosition, MarkerProps, useControl } from "react-map-gl"; // import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css' - - // export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & { // mapboxAccessToken: string; // marker?: Omit<MarkerProps, 'longitude' | 'latitude'>; @@ -31,7 +29,6 @@ // ctrl.on('results', props.onResults); // ctrl.on('result', evt => { // props.onResult(evt); - // // const {result} = evt; // // const location = // // result && @@ -49,8 +46,6 @@ // position: props.position // } // ); - - // // @ts-ignore (TS2339) private member // if (geocoder._map) { // if (geocoder.getProximity() !== props.proximity && props.proximity !== undefined) { @@ -104,4 +99,4 @@ // onLoading: noop, // onResults: noop, // onError: noop -// };
\ No newline at end of file +// }; diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx index d17c4298c..174511e1a 100644 --- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx +++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/button-has-type */ import { IconLookup, faAdd, faArrowDown, faArrowLeft, faArrowsRotate, faBicycle, faCalendarDays, faCar, faDiamondTurnRight, faEdit, faPersonWalking, faRoute } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material'; @@ -24,6 +25,7 @@ type MapAnchorMenuType = 'standard' | 'routeCreation' | 'calendar' | 'customize' @observer export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { + // eslint-disable-next-line no-use-before-define static Instance: MapAnchorMenu; private _disposer: IReactionDisposer | undefined; @@ -36,8 +38,8 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { public OnClick: (e: PointerEvent) => void = unimplementedFunction; // public OnAudio: (e: PointerEvent) => void = unimplementedFunction; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; - public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined; - public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined; + public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = () => undefined; + public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined; public Delete: () => void = unimplementedFunction; // public MakeTargetToggle: () => void = unimplementedFunction; // public ShowTargetTrail: () => void = unimplementedFunction; @@ -125,7 +127,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { componentDidMount() { this._disposer = reaction( () => SelectionManager.Views.slice(), - sel => MapAnchorMenu.Instance.fadeOut(true) + () => MapAnchorMenu.Instance.fadeOut(true) ); } // audioDown = (e: React.PointerEvent) => { @@ -148,12 +150,12 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { setupMoveUpEvents( this, e, - (e: PointerEvent) => { - this.StartDrag(e, this._commentRef.current!); + moveEv => { + this.StartDrag(moveEv, this._commentRef.current!); return true; }, returnFalse, - e => this.OnClick(e) + clickEv => this.OnClick(clickEv) ); }; @@ -275,7 +277,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { HandleAddRouteClick = () => { if (this.currentRouteInfoMap && this.selectedTransportationType && this.selectedDestinationFeature) { - const coordinates = this.currentRouteInfoMap[this.selectedTransportationType].coordinates; + const { coordinates } = this.currentRouteInfoMap[this.selectedTransportationType]; console.log(coordinates); console.log(this.selectedDestinationFeature); this.AddNewRouteToMap(coordinates, this.title ?? '', this.selectedDestinationFeature, this.createPinForDestination); @@ -294,34 +296,30 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { getDirectionsButton: JSX.Element = (<IconButton tooltip="Get directions" onPointerDown={this.DirectionsClick} icon={<FontAwesomeIcon icon={faDiamondTurnRight as IconLookup} />} color={SettingsManager.userColor} />); - getAddToCalendarButton = (docType: string): JSX.Element => { - return ( - <IconButton - tooltip="Add to calendar" - onPointerDown={() => { - CalendarManager.Instance.open(undefined, docType === 'pin' ? this.pinDoc : this.routeDoc); - }} - icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} - color={SettingsManager.userColor} - /> - ); - }; + getAddToCalendarButton = (docType: string): JSX.Element => ( + <IconButton + tooltip="Add to calendar" + onPointerDown={() => { + CalendarManager.Instance.open(undefined, docType === 'pin' ? this.pinDoc : this.routeDoc); + }} + icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} + color={SettingsManager.userColor} + /> + ); addToCalendarButton: JSX.Element = ( <IconButton tooltip="Add to calendar" onPointerDown={() => CalendarManager.Instance.open(undefined, this.pinDoc)} icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} /> ); - getLinkNoteToDocButton = (docType: string): JSX.Element => { - return ( - <div ref={this._commentRef}> - <IconButton - tooltip={`Link Note to ${docType === 'pin' ? 'Pin' : 'Route'}`} // - onPointerDown={this.notePointerDown} - icon={<FontAwesomeIcon icon="sticky-note" />} - color={SettingsManager.userColor} - /> - </div> - ); - }; + getLinkNoteToDocButton = (docType: string): JSX.Element => ( + <div ref={this._commentRef}> + <IconButton + tooltip={`Link Note to ${docType === 'pin' ? 'Pin' : 'Route'}`} // + onPointerDown={this.notePointerDown} + icon={<FontAwesomeIcon icon="sticky-note" />} + color={SettingsManager.userColor} + /> + </div> + ); linkNoteToPinOrRoutenButton: JSX.Element = ( <div ref={this._commentRef}> @@ -363,16 +361,14 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { /> ); - getDeleteButton = (type: string) => { - return ( - <IconButton - tooltip={`Delete ${type === 'pin' ? 'Pin' : 'Route'}`} // - onPointerDown={this.Delete} - icon={<FontAwesomeIcon icon="trash-alt" />} - color={SettingsManager.userColor} - /> - ); - }; + getDeleteButton = (type: string) => ( + <IconButton + tooltip={`Delete ${type === 'pin' ? 'Pin' : 'Route'}`} // + onPointerDown={this.Delete} + icon={<FontAwesomeIcon icon="trash-alt" />} + color={SettingsManager.userColor} + /> + ); animateRouteButton: JSX.Element = (<IconButton tooltip="Animate route" onPointerDown={() => this.OpenAnimationPanel(this.routeDoc)} icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />); @@ -453,18 +449,17 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { }} options={this.destinationFeatures.filter(feature => feature.place_name).map(feature => feature)} getOptionLabel={(feature: any) => feature.place_name} + // eslint-disable-next-line react/jsx-props-no-spreading renderInput={(params: any) => <TextField {...params} placeholder="Enter a destination" />} /> - {this.selectedDestinationFeature && ( - <> - {!this.allMapPinDocs.some(pinDoc => pinDoc.title === this.selectedDestinationFeature.place_name) && ( - <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '5px' }}> - <FormControlLabel label="Create pin for destination?" control={<Checkbox color="success" checked={this.createPinForDestination} onChange={this.toggleCreatePinForDestinationCheckbox} />} /> - </div> - )} - </> - )} - <button id="get-routes-button" disabled={this.selectedDestinationFeature ? false : true} onClick={() => this.getRoutes(this.selectedDestinationFeature)}> + {!this.selectedDestinationFeature + ? null + : !this.allMapPinDocs.some(pinDoc => pinDoc.title === this.selectedDestinationFeature.place_name) && ( + <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '5px' }}> + <FormControlLabel label="Create pin for destination?" control={<Checkbox color="success" checked={this.createPinForDestination} onChange={this.toggleCreatePinForDestinationCheckbox} />} /> + </div> + )} + <button id="get-routes-button" disabled={!this.selectedDestinationFeature} onClick={() => this.getRoutes(this.selectedDestinationFeature)}> Get routes </button> @@ -517,7 +512,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { </div> ))} </div> - <div style={{ width: '100%', height: '3px', color: 'white' }}></div> + <div style={{ width: '100%', height: '3px', color: 'white' }} /> </div> )} {this.menuType === 'route' && this.routeDoc && <div>{StrCast(this.routeDoc.title)}</div>} diff --git a/src/client/views/nodes/MapBox/MapboxApiUtility.ts b/src/client/views/nodes/MapBox/MapboxApiUtility.ts index 592330ac2..5c5192372 100644 --- a/src/client/views/nodes/MapBox/MapboxApiUtility.ts +++ b/src/client/views/nodes/MapBox/MapboxApiUtility.ts @@ -1,4 +1,3 @@ - const MAPBOX_FORWARD_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/'; const MAPBOX_REVERSE_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/'; const MAPBOX_DIRECTIONS_BASE_URL = 'https://api.mapbox.com/directions/v5/mapbox'; @@ -7,92 +6,79 @@ const MAPBOX_ACCESS_TOKEN = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNscHgwNDd1MDA3 export type TransportationType = 'driving' | 'cycling' | 'walking'; export class MapboxApiUtility { - static forwardGeocodeForFeatures = async (searchText: string) => { try { - const url = MAPBOX_FORWARD_GEOCODE_BASE_URL + encodeURI(searchText) +'.json' +`?access_token=${MAPBOX_ACCESS_TOKEN}`; + const url = MAPBOX_FORWARD_GEOCODE_BASE_URL + encodeURI(searchText) + `.json?access_token=${MAPBOX_ACCESS_TOKEN}`; const response = await fetch(url); const data = await response.json(); return data.features; - } catch (error: any){ - // TODO: handle error in better way + } catch (error: any) { + // TODO: handle error in better way return null; } - } + }; static reverseGeocodeForFeatures = async (longitude: number, latitude: number) => { try { - const url = MAPBOX_REVERSE_GEOCODE_BASE_URL + encodeURI(longitude.toString() + "," + latitude.toString()) + '.json' + - `?access_token=${MAPBOX_ACCESS_TOKEN}`; + const url = MAPBOX_REVERSE_GEOCODE_BASE_URL + encodeURI(longitude.toString() + ',' + latitude.toString()) + `.json?access_token=${MAPBOX_ACCESS_TOKEN}`; const response = await fetch(url); const data = await response.json(); return data.features; - } catch (error: any){ + } catch (error: any) { return null; } - } + }; static getDirections = async (origin: number[], destination: number[]): Promise<Record<TransportationType, any> | undefined> => { try { - const directionsPromises: Promise<any>[] = []; const transportationTypes: TransportationType[] = ['driving', 'cycling', 'walking']; - transportationTypes.forEach((type) => { - directionsPromises.push( - fetch( - `${MAPBOX_DIRECTIONS_BASE_URL}/${type}/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}` - ).then((response) => response.json()) - ); - }); + transportationTypes.forEach(type => { + directionsPromises.push(fetch(`${MAPBOX_DIRECTIONS_BASE_URL}/${type}/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`).then(response => response.json())); + }); const results = await Promise.all(directionsPromises); const routeInfoMap: Record<TransportationType, any> = { - 'driving': {}, - 'cycling': {}, - 'walking': {}, + driving: {}, + cycling: {}, + walking: {}, }; transportationTypes.forEach((type, index) => { const routeData = results[index].routes[0]; if (routeData) { - const geometry = routeData.geometry; - const coordinates = geometry.coordinates; - - routeInfoMap[type] = { - duration: this.secondsToMinutesHours(routeData.duration), - distance: this.metersToMiles(routeData.distance), - coordinates: coordinates, - }; + const { geometry } = routeData; + const { coordinates } = geometry; + + routeInfoMap[type] = { + duration: this.secondsToMinutesHours(routeData.duration), + distance: this.metersToMiles(routeData.distance), + coordinates: coordinates, + }; } - }); + }); return routeInfoMap; - // return current route info, and the temporary route - - } catch (error: any){ + // return current route info, and the temporary route + } catch (error: any) { return undefined; - console.log("Error: ", error); } - } + }; private static secondsToMinutesHours = (seconds: number) => { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60).toFixed(2); - if (hours === 0){ - return `${minutes} min` - } else { - return `${hours} hr ${minutes} min` + if (hours === 0) { + return `${minutes} min`; } - } - - private static metersToMiles = (meters: number) => { - return `${parseFloat((meters/1609.34).toFixed(2))} mi`; - } + return `${hours} hr ${minutes} min`; + }; + private static metersToMiles = (meters: number) => `${parseFloat((meters / 1609.34).toFixed(2))} mi`; } // const drivingQuery = await fetch( @@ -136,4 +122,4 @@ export class MapboxApiUtility { // distance: this.metersToMiles(routeData.distance), // coordinates: coordinates // } -// })
\ No newline at end of file +// }) diff --git a/src/client/views/nodes/MapBox/MarkerIcons.tsx b/src/client/views/nodes/MapBox/MarkerIcons.tsx index a580fcaa0..087472112 100644 --- a/src/client/views/nodes/MapBox/MarkerIcons.tsx +++ b/src/client/views/nodes/MapBox/MarkerIcons.tsx @@ -17,8 +17,6 @@ import { faHouse, faLandmark, faLocationDot, - faLocationPin, - faMapPin, faMasksTheater, faMugSaucer, faPersonHiking, @@ -65,6 +63,7 @@ export class MarkerIcons { iconProps.color = color; } + // eslint-disable-next-line react/jsx-props-no-spreading return <FontAwesomeIcon {...iconProps} size={size} />; } diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx index 189e2105d..fce52ef4b 100644 --- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx +++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx @@ -118,9 +118,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> * @param sidebarKey * @returns */ - sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => { + sidebarAddDocument = (docsIn: Doc | Doc[], sidebarKey?: string) => { if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar(); - const docs = doc instanceof Doc ? [doc] : doc; + const docs = docsIn instanceof Doc ? [docsIn] : docsIn; docs.forEach(doc => { let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this.selectedPin; if (doc.latitude !== undefined && doc.longitude !== undefined && !existingPin) { @@ -139,7 +139,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> } }); // add to annotation list - return this.addDocument(doc, sidebarKey); // add to sidebar list + return this.addDocument(docs, sidebarKey); // add to sidebar list }; removeMapDocument = (doc: Doc | Doc[], annotationKey?: string) => { @@ -168,7 +168,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> setupMoveUpEvents( this, e, - (e, down, delta) => + (moveEv, down, delta) => runInAction(() => { const localDelta = this._props .ScreenToLocalTransform() @@ -243,10 +243,10 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> const docView = this.DocumentView?.(); docView && DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, { - dragComplete: e => { - if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) { - e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.Document; - e.annoDragData.linkSourceDoc.followLinkZoom = false; + dragComplete: dragEv => { + if (!dragEv.aborted && dragEv.annoDragData && dragEv.annoDragData.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) { + dragEv.annoDragData.linkSourceDoc.followLinkToggle = dragEv.annoDragData.dropDocument.annotationOn === this.Document; + dragEv.annoDragData.linkSourceDoc.followLinkZoom = false; } }, }); @@ -680,23 +680,23 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> setupMoveUpEvents( e, e, - e => { + moveEv => { if (!dragClone) { dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement; dragClone.style.position = 'absolute'; dragClone.style.zIndex = '10000'; DragManager.Root().appendChild(dragClone); } - dragClone.style.transform = `translate(${e.clientX - 15}px, ${e.clientY - 15}px)`; + dragClone.style.transform = `translate(${moveEv.clientX - 15}px, ${moveEv.clientY - 15}px)`; return false; }, - e => { + upEv => { if (!dragClone) return; DragManager.Root().removeChild(dragClone); - let target = document.elementFromPoint(e.x, e.y); + let target = document.elementFromPoint(upEv.x, upEv.y); while (target) { if (target === this._ref.current) { - const cpt = this.ScreenToLocalBoxXf().transformPoint(e.clientX, e.clientY); + const cpt = this.ScreenToLocalBoxXf().transformPoint(upEv.clientX, upEv.clientY); const x = cpt[0] - (this._props.PanelWidth() - this.sidebarWidth()) / 2; const y = cpt[1] - 32 /* height of search bar */ - this._props.PanelHeight() / 2; const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y)); @@ -706,7 +706,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> target = target.parentElement; } }, - e => { + () => { const createPin = () => this.createPushpin(this.Document.latitude, this.Document.longitude, this.Document.map); if (this.bingSearchBarContents) { this.bingSearch().then(createPin); diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx index 16450c359..48da4937a 100644 --- a/src/client/views/nodes/RadialMenu.tsx +++ b/src/client/views/nodes/RadialMenu.tsx @@ -6,22 +6,57 @@ import { RadialMenuItem, RadialMenuProps } from './RadialMenuItem'; @observer export class RadialMenu extends React.Component { + // eslint-disable-next-line no-use-before-define static Instance: RadialMenu; static readonly buffer = 20; + @observable private _mouseX: number = -1; + @observable private _mouseY: number = -1; + @observable private _shouldDisplay: boolean = false; + @observable private _mouseDown: boolean = false; + @observable private _closest: number = -1; + @observable private _pageX: number = 0; + @observable private _pageY: number = 0; + @observable _display: boolean = false; + @observable private _yRelativeToTop: boolean = true; + @observable private _items: Array<RadialMenuProps> = []; + private _reactionDisposer?: IReactionDisposer; + constructor(props: any) { super(props); makeObservable(this); RadialMenu.Instance = this; } - @observable private _mouseX: number = -1; - @observable private _mouseY: number = -1; - @observable private _shouldDisplay: boolean = false; - @observable private _mouseDown: boolean = false; - private _reactionDisposer?: IReactionDisposer; + componentDidMount() { + document.addEventListener('pointerdown', this.onPointerDown); + document.addEventListener('pointerup', this.onPointerUp); + this.previewcircle(); + this._reactionDisposer = reaction( + () => this._shouldDisplay, + () => + this._shouldDisplay && + !this._mouseDown && + runInAction(() => { + this._display = true; + }) + ); + } - public used: boolean = false; + componentDidUpdate() { + this.previewcircle(); + } + componentWillUnmount() { + document.removeEventListener('pointerdown', this.onPointerDown); + + document.removeEventListener('pointerup', this.onPointerUp); + this._reactionDisposer && this._reactionDisposer(); + } + + @computed get menuItems() { + // eslint-disable-next-line react/jsx-props-no-spreading + return this._items.map((item, index) => <RadialMenuItem {...item} key={item.description} closeMenu={this.closeMenu} max={this._items.length} min={index} selected={this._closest} />); + } catchTouch = (te: React.TouchEvent) => { te.stopPropagation(); @@ -33,13 +68,9 @@ export class RadialMenu extends React.Component { this._mouseDown = true; this._mouseX = e.clientX; this._mouseY = e.clientY; - this.used = false; document.addEventListener('pointermove', this.onPointerMove); }; - @observable - private _closest: number = -1; - @action onPointerMove = (e: PointerEvent) => { const curX = e.clientX; @@ -65,7 +96,6 @@ export class RadialMenu extends React.Component { }; @action onPointerUp = (e: PointerEvent) => { - this.used = true; this._mouseDown = false; const curX = e.clientX; const curY = e.clientY; @@ -78,86 +108,6 @@ export class RadialMenu extends React.Component { this._items[this._closest].event(); } }; - componentWillUnmount() { - document.removeEventListener('pointerdown', this.onPointerDown); - - document.removeEventListener('pointerup', this.onPointerUp); - this._reactionDisposer && this._reactionDisposer(); - } - - @action - componentDidMount() { - document.addEventListener('pointerdown', this.onPointerDown); - document.addEventListener('pointerup', this.onPointerUp); - this.previewcircle(); - this._reactionDisposer = reaction( - () => this._shouldDisplay, - () => this._shouldDisplay && !this._mouseDown && runInAction(() => (this._display = true)) - ); - } - - componentDidUpdate = () => { - this.previewcircle(); - }; - - @observable private _pageX: number = 0; - @observable private _pageY: number = 0; - @observable _display: boolean = false; - @observable private _yRelativeToTop: boolean = true; - - @observable private _width: number = 0; - @observable private _height: number = 0; - - getItems() { - return this._items; - } - - @action - addItem(item: RadialMenuProps) { - if (this._items.indexOf(item) === -1) { - this._items.push(item); - } - } - - @observable - private _items: Array<RadialMenuProps> = []; - - @action - displayMenu = (x: number, y: number) => { - //maxX and maxY will change if the UI/font size changes, but will work for any amount - //of items added to the menu - this._mouseX = x; - this._mouseY = y; - this._shouldDisplay = true; - }; - // @computed - // get pageX() { - // const x = this._pageX; - // if (x < 0) { - // return 0; - // } - // const width = this._width; - // if (x + width > window.innerWidth - RadialMenu.buffer) { - // return window.innerWidth - RadialMenu.buffer - width; - // } - // return x; - // } - // @computed - // get pageY() { - // const y = this._pageY; - // if (y < 0) { - // return 0; - // } - // const height = this._height; - // if (y + height > window.innerHeight - RadialMenu.buffer) { - // return window.innerHeight - RadialMenu.buffer - height; - // } - // return y; - // } - - @computed get menuItems() { - return this._items.map((item, index) => <RadialMenuItem {...item} key={item.description} closeMenu={this.closeMenu} max={this._items.length} min={index} selected={this._closest} />); - } @action closeMenu = () => { @@ -167,14 +117,6 @@ export class RadialMenu extends React.Component { }; @action - openMenu = (x: number, y: number) => { - this._pageX = x; - this._pageY = y; - this._shouldDisplay; - this._display = true; - }; - - @action clearItems() { this._items = []; } diff --git a/src/client/views/nodes/RadialMenuItem.tsx b/src/client/views/nodes/RadialMenuItem.tsx index 91dc37d34..6f10e7b65 100644 --- a/src/client/views/nodes/RadialMenuItem.tsx +++ b/src/client/views/nodes/RadialMenuItem.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/require-default-props */ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { observer } from 'mobx-react'; @@ -53,6 +54,7 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> { case 2: color = 'lightgray'; break; + default: } if (circlemax % 3 === 1 && circlemin === circlemax - 1) { color = '#c2c2c5'; @@ -80,7 +82,6 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> { const avg = (circlemin / circlemax + (circlemin + 1) / circlemax) / 2; const degrees = 360 * avg; const x = 100 * Math.cos((degrees * Math.PI) / 180); - const y = -125 * Math.sin((degrees * Math.PI) / 180); return x; } @@ -91,7 +92,6 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> { this.props.max ? (circlemax = this.props.max) : null; const avg = (circlemin / circlemax + (circlemin + 1) / circlemax) / 2; const degrees = 360 * avg; - const x = 125 * Math.cos((degrees * Math.PI) / 180); const y = -100 * Math.sin((degrees * Math.PI) / 180); return y; } diff --git a/src/client/views/nodes/RecordingBox/ProgressBar.tsx b/src/client/views/nodes/RecordingBox/ProgressBar.tsx index 1bb2b7c84..62798bc2f 100644 --- a/src/client/views/nodes/RecordingBox/ProgressBar.tsx +++ b/src/client/views/nodes/RecordingBox/ProgressBar.tsx @@ -1,31 +1,33 @@ +/* eslint-disable react/no-array-index-key */ +/* eslint-disable react/require-default-props */ import * as React from 'react'; -import { useEffect, useState, useCallback, useRef } from "react" -import "./ProgressBar.scss" +import { useEffect, useState, useRef } from 'react'; +import './ProgressBar.scss'; import { MediaSegment } from './RecordingView'; interface ProgressBarProps { - videos: MediaSegment[], - setVideos: React.Dispatch<React.SetStateAction<MediaSegment[]>>, - orderVideos: boolean, - progress: number, - recording: boolean, - doUndo: boolean, - setCanUndo?: React.Dispatch<React.SetStateAction<boolean>>, + videos: MediaSegment[]; + setVideos: React.Dispatch<React.SetStateAction<MediaSegment[]>>; + orderVideos: boolean; + progress: number; + recording: boolean; + doUndo: boolean; + setCanUndo?: React.Dispatch<React.SetStateAction<boolean>>; } interface SegmentBox { - endTime: number, - startTime: number, - order: number, + endTime: number; + startTime: number; + order: number; } interface CurrentHover { - index: number, - minX: number, - maxX: number + index: number; + minX: number; + maxX: number; } export function ProgressBar(props: ProgressBarProps) { - const progressBarRef = useRef<HTMLDivElement | null>(null) + const progressBarRef = useRef<HTMLDivElement | null>(null); // the actual list of JSX elements rendered as segments const [segments, setSegments] = useState<JSX.Element[]>([]); @@ -47,8 +49,6 @@ export function ProgressBar(props: ProgressBarProps) { // update the canUndo props based on undo stack useEffect(() => props.setCanUndo?.(undoStack.length > 0), [undoStack.length]); - // useEffect for undo - brings back the most recently deleted segment - useEffect(() => handleUndo(), [props.doUndo]) const handleUndo = () => { // get the last element from the undo if it exists if (undoStack.length === 0) return; @@ -59,27 +59,36 @@ export function ProgressBar(props: ProgressBarProps) { // update the removed time and place element back into ordered setTotalRemovedTime(prevRemoved => prevRemoved - (last.endTime - last.startTime)); setOrdered(prevOrdered => [...prevOrdered, last]); - } + }; + // useEffect for undo - brings back the most recently deleted segment + useEffect(() => handleUndo(), [props.doUndo]); // useEffect for recording changes - changes style to disabled and adds the "expanding-segment" useEffect(() => { // get segments segment's html using it's id -> make them appeared disabled (or enabled) - segments.forEach((seg) => document.getElementById(seg.props.id)?.classList.toggle('segment-disabled', props.recording)); + segments.forEach(seg => document.getElementById(seg.props.id)?.classList.toggle('segment-disabled', props.recording)); progressBarRef.current?.classList.toggle('progressbar-disabled', props.recording); if (props.recording) - setSegments(prevSegments => [...prevSegments, <div key='segment-expanding' id='segment-expanding' className='segment segment-expanding blink' style={{ width: 'fit-content' }}>{props.videos.length + 1}</div>]); - }, [props.recording]) - + setSegments(prevSegments => [ + ...prevSegments, + <div key="segment-expanding" id="segment-expanding" className="segment segment-expanding blink" style={{ width: 'fit-content' }}> + {props.videos.length + 1} + </div>, + ]); + }, [props.recording]); // useEffect that updates the segmentsJSX, which is rendered // only updated when ordered is updated or if the user is dragging around a segment useEffect(() => { const totalTime = props.progress * 1000 - totalRemovedTime; - const segmentsJSX = ordered.map((seg, i) => - <div key={`segment-${i}`} id={`segment-${i}`} className={dragged === i ? 'segment-hide' : 'segment'} style={{ width: `${((seg.endTime - seg.startTime) / totalTime) * 100}%` }}>{seg.order + 1}</div>); + const segmentsJSX = ordered.map((seg, i) => ( + <div key={`segment-${i}`} id={`segment-${i}`} className={dragged === i ? 'segment-hide' : 'segment'} style={{ width: `${((seg.endTime - seg.startTime) / totalTime) * 100}%` }}> + {seg.order + 1} + </div> + )); - setSegments(segmentsJSX) + setSegments(segmentsJSX); }, [dragged, ordered]); // useEffect for dragged - update the cursor to be grabbing while grabbing @@ -89,14 +98,14 @@ export function ProgressBar(props: ProgressBarProps) { // to imporve performance, only want to update the CSS width, not re-render the whole JSXList useEffect(() => { - if (!props.recording) return + if (!props.recording) return; const totalTime = props.progress * 1000 - totalRemovedTime; let remainingTime = totalTime; segments.forEach((seg, i) => { // for the last segment, we need to set that directly if (i === segments.length - 1) return; // update remaining time - remainingTime -= (ordered[i].endTime - ordered[i].startTime); + remainingTime -= ordered[i].endTime - ordered[i].startTime; // update the width for this segment const htmlId = seg.props.id; @@ -106,8 +115,7 @@ export function ProgressBar(props: ProgressBarProps) { // update the width of the expanding segment using the remaining time const segExapandHtml = document.getElementById('segment-expanding'); - if (segExapandHtml) - segExapandHtml.style.width = ordered.length === 0 ? '100%' : `${(remainingTime / totalTime) * 100}%`; + if (segExapandHtml) segExapandHtml.style.width = ordered.length === 0 ? '100%' : `${(remainingTime / totalTime) * 100}%`; }, [props.progress]); // useEffect for props.videos - update the ordered array when a new video is added @@ -120,9 +128,7 @@ export function ProgressBar(props: ProgressBarProps) { // in this case, a new video is added -> push it onto ordered if (order >= ordered.length) { const { endTime, startTime } = props.videos.lastElement(); - setOrdered(prevOrdered => { - return [...prevOrdered, { endTime, startTime, order }]; - }); + setOrdered(prevOrdered => [...prevOrdered, { endTime, startTime, order }]); } // in this case, a video is removed @@ -132,7 +138,7 @@ export function ProgressBar(props: ProgressBarProps) { }, [props.videos]); // useEffect for props.orderVideos - matched the order array with the videos array before the export - useEffect(() => props.setVideos(vids => ordered.map((seg) => vids[seg.order])), [props.orderVideos]); + useEffect(() => props.setVideos(vids => ordered.map(seg => vids[seg.order])), [props.orderVideos]); // useEffect for removed - handles logic for removing a segment useEffect(() => { @@ -151,36 +157,68 @@ export function ProgressBar(props: ProgressBarProps) { // returns the new currentHover based on the new index const updateCurrentHover = (segId: number): CurrentHover | null => { // get the segId of the segment that will become the new bounding area - const rect = progressBarRef.current?.children[segId].getBoundingClientRect() - if (rect == null) return null + const rect = progressBarRef.current?.children[segId].getBoundingClientRect(); + if (rect == null) return null; return { index: segId, minX: rect.x, maxX: rect.x + rect.width, - } - } + }; + }; + + const swapSegments = (oldIndex: number, newIndex: number) => { + if (newIndex == null) return; + setOrdered(prevOrdered => { + const temp = { ...prevOrdered[oldIndex] }; + prevOrdered[oldIndex] = prevOrdered[newIndex]; + prevOrdered[newIndex] = temp; + return prevOrdered; + }); + // update visually where the segment is hovering over + setDragged(newIndex); + }; + + // functions for the floating segment that tracks the cursor while grabbing it + const initDetachSegment = (dot: HTMLDivElement, rect: DOMRect) => { + dot.classList.add('segment-selected'); + dot.style.transitionDuration = '0s'; + dot.style.position = 'absolute'; + dot.style.zIndex = '999'; + dot.style.width = `${rect.width}px`; + dot.style.height = `${rect.height}px`; + dot.style.left = `${rect.x}px`; + dot.style.top = `${rect.y}px`; + dot.draggable = false; + document.body.append(dot); + }; + const followCursor = (event: PointerEvent, dot: HTMLDivElement): void => { + // event.stopPropagation() + const { width, height } = dot.getBoundingClientRect(); + dot.style.left = `${event.clientX - width / 2}px`; + dot.style.top = `${event.clientY - height / 2}px`; + }; // pointerdown event for the progress bar - const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => { - // don't move the videobox element - e.stopPropagation(); + const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => { + // don't move the videobox element + e.stopPropagation(); // if recording, do nothing - if (props.recording) return; + if (props.recording) return; // get the segment the user clicked on to be dragged - const clickedSegment = e.target as HTMLDivElement & EventTarget + const clickedSegment = e.target as HTMLDivElement & EventTarget; // get the profess bar ro add event listeners // don't do anything if null - const progressBar = progressBarRef.current - if (progressBar == null || clickedSegment.id === progressBar.id) return + const progressBar = progressBarRef.current; + if (progressBar == null || clickedSegment.id === progressBar.id) return; // if holding shift key, let's remove that segment if (e.shiftKey) { const segId = parseInt(clickedSegment.id.split('-')[1]); setRemoved(segId); - return + return; } // if holding ctrl key and click, let's undo that segment #hiddenfeature lol @@ -192,26 +230,26 @@ export function ProgressBar(props: ProgressBarProps) { // if we're here, the user is dragging a segment around // let the progress bar capture all the pointer events until the user releases (pointerUp) const ptrId = e.pointerId; - progressBar.setPointerCapture(ptrId) + progressBar.setPointerCapture(ptrId); - const rect = clickedSegment.getBoundingClientRect() - // id for segment is like 'segment-1' or 'segment-10', + const rect = clickedSegment.getBoundingClientRect(); + // id for segment is like 'segment-1' or 'segment-10', // so this works to get the id - const segId = parseInt(clickedSegment.id.split('-')[1]) + const segId = parseInt(clickedSegment.id.split('-')[1]); // set the selected segment to be the one dragged - setDragged(segId) + setDragged(segId); - // this is the logic for storing the lower X bound and upper X bound to know + // this is the logic for storing the lower X bound and upper X bound to know // whether a swap is needed between two segments let currentHover: CurrentHover = { index: segId, minX: rect.x, maxX: rect.x + rect.width, - } + }; // create the floating segment that tracks the cursor - const detchedSegment = document.createElement("div") - initDeatchSegment(detchedSegment, rect); + const detchedSegment = document.createElement('div'); + initDetachSegment(detchedSegment, rect); const updateSegmentOrder = (event: PointerEvent): void => { event.stopPropagation(); @@ -219,6 +257,7 @@ export function ProgressBar(props: ProgressBarProps) { // this fixes a bug where pointerup doesn't fire while cursor is upped while being dragged if (!progressBar.hasPointerCapture(ptrId)) { + // eslint-disable-next-line no-use-before-define placeSegmentandCleanup(); return; } @@ -228,24 +267,23 @@ export function ProgressBar(props: ProgressBarProps) { const curX = event.clientX; // handle the left bound if (curX < currentHover.minX && currentHover.index > 0) { - swapSegments(currentHover.index, currentHover.index - 1) - currentHover = updateCurrentHover(currentHover.index - 1) ?? currentHover + swapSegments(currentHover.index, currentHover.index - 1); + currentHover = updateCurrentHover(currentHover.index - 1) ?? currentHover; } // handle the right bound else if (curX > currentHover.maxX && currentHover.index < segments.length - 1) { - swapSegments(currentHover.index, currentHover.index + 1) - currentHover = updateCurrentHover(currentHover.index + 1) ?? currentHover + swapSegments(currentHover.index, currentHover.index + 1); + currentHover = updateCurrentHover(currentHover.index + 1) ?? currentHover; } - } + }; // handles when the user is done dragging the segment (pointerUp) const placeSegmentandCleanup = (event?: PointerEvent): void => { event?.stopPropagation(); event?.preventDefault(); // if they put the segment outside of the bounds, remove it - if (event && (event.clientX < 0 || event.clientX > document.body.clientWidth || event.clientY < 0 || event.clientY > document.body.clientHeight)) - setRemoved(currentHover.index); - + if (event && (event.clientX < 0 || event.clientX > document.body.clientWidth || event.clientY < 0 || event.clientY > document.body.clientHeight)) setRemoved(currentHover.index); + // remove the update event listener for pointermove progressBar.removeEventListener('pointermove', updateSegmentOrder); // remove the floating segment from the DOM @@ -253,49 +291,16 @@ export function ProgressBar(props: ProgressBarProps) { // dragged is -1 is equiv to nothing being dragged, so the normal state // so this will place the segment in it's location and update the segment bar setDragged(-1); - } + }; // event listeners that allow the user to drag and release the floating segment progressBar.addEventListener('pointermove', updateSegmentOrder); progressBar.addEventListener('pointerup', placeSegmentandCleanup, { once: true }); - } - - const swapSegments = (oldIndex: number, newIndex: number) => { - if (newIndex == null) return; - setOrdered(prevOrdered => { - const temp = { ...prevOrdered[oldIndex] } - prevOrdered[oldIndex] = prevOrdered[newIndex] - prevOrdered[newIndex] = temp - return prevOrdered - }); - // update visually where the segment is hovering over - setDragged(newIndex); - } - - // functions for the floating segment that tracks the cursor while grabbing it - const initDeatchSegment = (dot: HTMLDivElement, rect: DOMRect) => { - dot.classList.add("segment-selected"); - dot.style.transitionDuration = '0s'; - dot.style.position = 'absolute'; - dot.style.zIndex = '999'; - dot.style.width = `${rect.width}px`; - dot.style.height = `${rect.height}px`; - dot.style.left = `${rect.x}px`; - dot.style.top = `${rect.y}px`; - dot.draggable = false; - document.body.append(dot); - } - const followCursor = (event: PointerEvent, dot: HTMLDivElement): void => { - // event.stopPropagation() - const { width, height } = dot.getBoundingClientRect(); - dot.style.left = `${event.clientX - width / 2}px`; - dot.style.top = `${event.clientY - height / 2}px`; - } - + }; return ( <div className="progressbar" id="progressbar" onPointerDown={onPointerDown} ref={progressBarRef}> {segments} </div> - ) -}
\ No newline at end of file + ); +} diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx index 7d123d90c..2bc749e1b 100644 --- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx @@ -44,18 +44,11 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() { } @observable result: Upload.AccessPathInfo | undefined = undefined; - @observable videoDuration: number | undefined = undefined; - - @action - setVideoDuration = (duration: number) => { - this.videoDuration = duration; - }; @action setResult = (info: Upload.AccessPathInfo, presentation?: Presentation) => { this.result = info; this.dataDoc.type = DocumentType.VID; - this.dataDoc[this.fieldKey + '_duration'] = this.videoDuration; this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey); this.dataDoc[this._props.fieldKey] = new VideoField(this.result.accessPaths.client); @@ -194,15 +187,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() { render() { return ( <div className="recordingBox" style={{ width: '100%' }} ref={this._ref}> - {!this.result && ( - <RecordingView - forceTrackScreen={BoolCast(this.layoutDoc[this.fieldKey + '_trackScreen'])} - getControls={this.getControls} - setResult={this.setResult} - setDuration={this.setVideoDuration} - id={DocCast(this.Document.proto)?.[Id] || ''} - /> - )} + {!this.result && <RecordingView forceTrackScreen={BoolCast(this.layoutDoc[this.fieldKey + '_trackScreen'])} getControls={this.getControls} setResult={this.setResult} id={DocCast(this.Document.proto)?.[Id] || ''} />} </div> ); } diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index 9c05a3e94..b8451fe60 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -1,3 +1,6 @@ +/* eslint-disable jsx-a11y/label-has-associated-control */ +/* eslint-disable react/button-has-type */ +/* eslint-disable jsx-a11y/control-has-associated-label */ import * as React from 'react'; import { useEffect, useRef, useState } from 'react'; import { IconContext } from 'react-icons'; @@ -19,19 +22,18 @@ export interface MediaSegment { interface IRecordingViewProps { setResult: (info: Upload.AccessPathInfo, presentation?: Presentation) => void; - setDuration: (seconds: number) => void; id: string; getControls: (record: () => void, pause: () => void, finish: () => void) => void; forceTrackScreen: boolean; } const MAXTIME = 100000; +const iconVals = { color: '#cc1c08', className: 'video-edit-buttons' }; export function RecordingView(props: IRecordingViewProps) { const [recording, setRecording] = useState(false); const recordingTimerRef = useRef<number>(0); const [recordingTimer, setRecordingTimer] = useState(0); // unit is 0.01 second - const [playing, setPlaying] = useState(false); const [progress, setProgress] = useState(0); // acts as a "refresh state" to tell progressBar when to undo @@ -62,7 +64,7 @@ export function RecordingView(props: IRecordingViewProps) { useEffect(() => { if (finished) { // make the total presentation that'll match the concatted video - let concatPres = (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.concatPresentations(videos.map(v => v.presentation as Presentation)); + const concatPres = (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.concatPresentations(videos.map(v => v.presentation as Presentation)); // this async function uses the server to create the concatted video and then sets the result to it's accessPaths (async () => { @@ -100,16 +102,16 @@ export function RecordingView(props: IRecordingViewProps) { return () => clearInterval(interval); }, [recording]); + const setVideoProgressHelper = (curProgrss: number) => { + const newProgress = (curProgrss / MAXTIME) * 100; + setProgress(newProgress); + }; + useEffect(() => { setVideoProgressHelper(recordingTimer); recordingTimerRef.current = recordingTimer; }, [recordingTimer]); - const setVideoProgressHelper = (progress: number) => { - const newProgress = (progress / MAXTIME) * 100; - setProgress(newProgress); - }; - const startShowingStream = async (mediaConstraints = DEFAULT_MEDIA_CONSTRAINTS) => { const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints); @@ -131,7 +133,7 @@ export function RecordingView(props: IRecordingViewProps) { if (event.data.size > 0) videoChunks.push(event.data); }; - videoRecorder.current.onstart = (event: any) => { + videoRecorder.current.onstart = () => { setRecording(true); // start the recording api when the video recorder starts (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.start(); @@ -149,7 +151,7 @@ export function RecordingView(props: IRecordingViewProps) { // depending on if a presenation exists, add it to the video const presentation = TrackMovements.Instance.yieldPresentation(); - setVideos(videos => [...videos, presentation != null && (trackScreen || props.forceTrackScreen) ? { ...nextVideo, presentation } : nextVideo]); + setVideos(theVideos => [...theVideos, presentation != null && (trackScreen || props.forceTrackScreen) ? { ...nextVideo, presentation } : nextVideo]); } // reset the temporary chunks @@ -186,7 +188,7 @@ export function RecordingView(props: IRecordingViewProps) { e, returnTrue, returnFalse, - e => { + () => { // start recording if not already recording if (!videoRecorder.current || videoRecorder.current.state === 'inactive') record(); @@ -202,14 +204,8 @@ export function RecordingView(props: IRecordingViewProps) { setDoUndo(prev => !prev); }; - const handleOnTimeUpdate = () => { - playing && setVideoProgressHelper(videoElementRef.current!.currentTime); - }; - const millisecondToMinuteSecond = (milliseconds: number) => { - const toTwoDigit = (digit: number) => { - return String(digit).length == 1 ? '0' + digit : digit; - }; + const toTwoDigit = (digit: number) => (String(digit).length === 1 ? '0' + digit : digit); const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000); return toTwoDigit(minutes) + ' : ' + toTwoDigit(seconds); @@ -219,10 +215,11 @@ export function RecordingView(props: IRecordingViewProps) { props.getControls(record, pause, finish); }, []); + const iconUndoVals = React.useMemo(() => ({ color: 'grey', className: 'video-edit-buttons', style: { display: canUndo ? 'inherit' : 'none' } }), []); return ( <div className="recording-container"> <div className="video-wrapper"> - <video id={`video-${props.id}`} autoPlay muted onTimeUpdate={() => handleOnTimeUpdate()} ref={videoElementRef} /> + <video id={`video-${props.id}`} autoPlay muted ref={videoElementRef} /> <div className="recording-sign"> <span className="dot" /> <p className="timer">{millisecondToMinuteSecond(recordingTimer * 10)}</p> @@ -246,10 +243,10 @@ export function RecordingView(props: IRecordingViewProps) { {!recording && (videos.length > 0 ? ( <div className="options-wrapper video-edit-wrapper"> - <IconContext.Provider value={{ color: 'grey', className: 'video-edit-buttons', style: { display: canUndo ? 'inherit' : 'none' } }}> + <IconContext.Provider value={iconUndoVals}> <MdBackspace onPointerDown={undoPrevious} /> </IconContext.Provider> - <IconContext.Provider value={{ color: '#cc1c08', className: 'video-edit-buttons' }}> + <IconContext.Provider value={iconVals}> <FaCheckCircle onPointerDown={e => { e.stopPropagation(); @@ -268,7 +265,7 @@ export function RecordingView(props: IRecordingViewProps) { setTrackScreen(e.target.checked); }} /> - <span className="checkmark"></span> + <span className="checkmark" /> Track Screen </label> </div> diff --git a/src/client/views/nodes/RecordingBox/index.ts b/src/client/views/nodes/RecordingBox/index.ts index ff21eaed6..e4f9b5e55 100644 --- a/src/client/views/nodes/RecordingBox/index.ts +++ b/src/client/views/nodes/RecordingBox/index.ts @@ -1,2 +1,2 @@ -export * from './RecordingView' -export * from './RecordingBox'
\ No newline at end of file +export * from './RecordingView'; +export * from './RecordingBox'; diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx index 60d7e4b00..5ebc50a1b 100644 --- a/src/client/views/nodes/ScriptingBox.tsx +++ b/src/client/views/nodes/ScriptingBox.tsx @@ -1,4 +1,5 @@ -let ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete').default; +/* eslint-disable react/button-has-type */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -17,10 +18,12 @@ import { ContextMenu } from '../ContextMenu'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { EditableView } from '../EditableView'; import { OverlayView } from '../OverlayView'; -import { FieldView, FieldViewProps } from '../nodes/FieldView'; +import { FieldView, FieldViewProps } from './FieldView'; import { DocumentIconContainer } from './DocumentIcon'; import './ScriptingBox.scss'; + const _global = (window /* browser */ || global) /* node */ as any; +const ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete').default; @observer export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @@ -79,26 +82,24 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() @computed({ keepAlive: true }) get rawScript() { return ScriptCast(this.dataDoc[this.fieldKey])?.script.originalScript ?? ''; } - @computed({ keepAlive: true }) get functionName() { - return StrCast(this.dataDoc[this.fieldKey + '-functionName'], ''); - } - @computed({ keepAlive: true }) get functionDescription() { - return StrCast(this.dataDoc[this.fieldKey + '-functionDescription'], ''); - } - @computed({ keepAlive: true }) get compileParams() { - return Cast(this.dataDoc[this.fieldKey + '-params'], listSpec('string'), []); - } - set rawScript(value) { this.dataDoc[this.fieldKey] = new ScriptField(undefined, undefined, value); } + @computed({ keepAlive: true }) get functionName() { + return StrCast(this.dataDoc[this.fieldKey + '-functionName'], ''); + } set functionName(value) { this.dataDoc[this.fieldKey + '-functionName'] = value; } + @computed({ keepAlive: true }) get functionDescription() { + return StrCast(this.dataDoc[this.fieldKey + '-functionDescription'], ''); + } set functionDescription(value) { this.dataDoc[this.fieldKey + '-functionDescription'] = value; } - + @computed({ keepAlive: true }) get compileParams() { + return Cast(this.dataDoc[this.fieldKey + '-params'], listSpec('string'), []); + } set compileParams(value) { this.dataDoc[this.fieldKey + '-params'] = new List<string>(value); } @@ -107,9 +108,8 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() if (typeof result === 'object') { const text = descrip ? result[1] : result[2]; return text !== undefined ? text : ''; - } else { - return ''; } + return ''; } onClickScriptDisable = returnAlways; @@ -118,19 +118,18 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() componentDidMount() { this._props.setContentViewBox?.(this); this.rawText = this.rawScript; - const observer = new _global.ResizeObserver( - action((entries: any) => { + const resizeObserver = new _global.ResizeObserver( + action(() => { const area = document.querySelector('textarea'); if (area) { - for (const {} of entries) { - const getCaretCoordinates = require('textarea-caret'); - const caret = getCaretCoordinates(area, this._selection); - this.resetSuggestionPos(caret); - } + // eslint-disable-next-line global-require + const getCaretCoordinates = require('textarea-caret'); + const caret = getCaretCoordinates(area, this._selection); + this.resetSuggestionPos(caret); } }) ); - observer.observe(document.getElementsByClassName('scriptingBox-outerDiv')[0]); + resizeObserver.observe(document.getElementsByClassName('scriptingBox-outerDiv')[0]); } @action @@ -138,12 +137,12 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() if (!this._suggestionRef.current || !this._scriptTextRef.current) return; const suggestionWidth = this._suggestionRef.current.offsetWidth; const scriptWidth = this._scriptTextRef.current.offsetWidth; - const top = caret.top; - const x = this.dataDoc.x; - let left = caret.left; + const { top } = caret; + const { x } = this.dataDoc; + let { left } = caret; if (left + suggestionWidth > x + scriptWidth) { const diff = left + suggestionWidth - (x + scriptWidth); - left = left - diff; + left -= diff; } this._suggestionBoxX = left; @@ -155,7 +154,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() } protected createDashEventsTarget = (ele: HTMLDivElement, dropFunc: (e: Event, de: DragManager.DropEvent) => void) => { - //used for stacking and masonry view + // used for stacking and masonry view if (ele) { this.dropDisposer?.(); this.dropDisposer = DragManager.MakeDropTarget(ele, dropFunc, this.layoutDoc); @@ -164,7 +163,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() // only included in buttons, transforms scripting UI to a button @action - onFinish = () => (this.layoutDoc.layout_fieldKey = 'layout'); + onFinish = () => { + this.layoutDoc.layout_fieldKey = 'layout'; + }; // displays error message @action @@ -176,7 +177,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() @action onCompile = () => { const params: ScriptParam = {}; - this.compileParams.forEach(p => (params[p.split(':')[0].trim()] = p.split(':')[1].trim())); + this.compileParams.forEach(p => { + params[p.split(':')[0].trim()] = p.split(':')[1].trim(); + }); const result = !this.rawText.trim() ? ({ compiled: false, errors: undefined } as any) @@ -196,7 +199,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() onRun = () => { if (this.onCompile()) { const bindings: { [name: string]: any } = {}; - this.paramsNames.forEach(key => (bindings[key] = this.dataDoc[key])); + this.paramsNames.forEach(key => { + bindings[key] = this.dataDoc[key]; + }); // binds vars so user doesnt have to refer to everything as this.<var> ScriptCast(this.dataDoc[this.fieldKey], null)?.script.run({ ...bindings, this: this.Document }, this.onError); } @@ -247,7 +252,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() this.dataDoc.name = this.functionName; this.dataDoc.description = this.functionDescription; - //this.dataDoc.parameters = this.compileParams; + // this.dataDoc.parameters = this.compileParams; this.dataDoc.script = this.rawScript; ScriptManager.Instance.addScript(this.dataDoc); @@ -255,6 +260,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() this._scriptKeys = ScriptingGlobals.getGlobals(); this._scriptingDescriptions = ScriptingGlobals.getDescriptions(); this._scriptingParams = ScriptingGlobals.getParameters(); + return undefined; }; // overlays document numbers (ex. d32) over all documents when clicked on @@ -267,7 +273,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() @action onDrop = (e: Event, de: DragManager.DropEvent, fieldKey: string) => { if (de.complete.docDragData) { - de.complete.docDragData.droppedDocuments.forEach(doc => (this.dataDoc[fieldKey] = doc)); + de.complete.docDragData.droppedDocuments.forEach(doc => { + this.dataDoc[fieldKey] = doc; + }); e.stopPropagation(); return true; } @@ -285,8 +293,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() // sets field of the param name to the selected value in drop down box @action viewChanged = (e: React.ChangeEvent, name: string) => { - //@ts-ignore - const val = e.target.selectedOptions[0].value; + const val = (e.target as any).selectedOptions[0].value; this.dataDoc[name] = val[0] === 'S' ? val.substring(1) : val[0] === 'N' ? parseInt(val.substring(1)) : val.substring(1) === 'true'; }; @@ -306,8 +313,26 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() }; renderFunctionInputs() { - const descriptionInput = <textarea className="scriptingBox-textarea-inputs" onChange={e => (this.functionDescription = e.target.value)} placeholder="enter description here" value={this.functionDescription} />; - const nameInput = <textarea className="scriptingBox-textarea-inputs" onChange={e => (this.functionName = e.target.value)} placeholder="enter name here" value={this.functionName} />; + const descriptionInput = ( + <textarea + className="scriptingBox-textarea-inputs" + onChange={e => { + this.functionDescription = e.target.value; + }} + placeholder="enter description here" + value={this.functionDescription} + /> + ); + const nameInput = ( + <textarea + className="scriptingBox-textarea-inputs" + onChange={e => { + this.functionName = e.target.value; + }} + placeholder="enter name here" + value={this.functionName} + /> + ); return ( <div className="scriptingBox-inputDiv" onPointerDown={e => this._props.isSelected() && e.stopPropagation()}> @@ -339,7 +364,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() return ( <div className="scriptingBox-paramInputs" onFocus={this.onFocus} onBlur={() => this._overlayDisposer?.()} ref={ele => ele && this.createDashEventsTarget(ele, (e, de) => this.onDrop(e, de, parameter))}> <EditableView - display={'block'} + display="block" maxHeight={72} height={35} fontSize={14} @@ -371,7 +396,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() return ( <div className="scriptingBox-paramInputs" style={{ overflowY: 'hidden' }}> <EditableView - display={'block'} + display="block" maxHeight={72} height={35} fontSize={14} @@ -404,6 +429,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() onChange={e => this.viewChanged(e, parameter)} value={typeof this.dataDoc[parameter] === 'string' ? 'S' + StrCast(this.dataDoc[parameter]) : typeof this.dataDoc[parameter] === 'number' ? 'N' + NumCast(this.dataDoc[parameter]) : 'B' + BoolCast(this.dataDoc[parameter])}> {types.map((type, i) => ( + // eslint-disable-next-line react/no-array-index-key <option key={i} className="scriptingBox-viewOption" value={(typeof type === 'string' ? 'S' : typeof type === 'number' ? 'N' : 'B') + type}> {' '} {type.toString()}{' '} @@ -496,6 +522,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() @action suggestionPos = () => { + // eslint-disable-next-line global-require const getCaretCoordinates = require('textarea-caret'); const This = this; document.querySelector('textarea')?.addEventListener('input', function () { @@ -509,7 +536,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() keyHandler(e: any, pos: number) { e.stopPropagation(); if (this._lastChar === 'Enter') { - this.rawText = this.rawText + ' '; + this.rawText += ' '; } if (e.key === '(') { this.suggestionPos(); @@ -524,20 +551,18 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() } } else if (e.key === ')') { this._paramSuggestion = false; - } else { - if (e.key === 'Backspace') { - if (this._lastChar === '(') { - this._paramSuggestion = false; - } else if (this._lastChar === ')') { - if (this.rawText.slice(0, this.rawText.length - 1).split('(').length - 1 > this.rawText.slice(0, this.rawText.length - 1).split(')').length - 1) { - if (this._scriptParamsText.length > 0) { - this._paramSuggestion = true; - } + } else if (e.key === 'Backspace') { + if (this._lastChar === '(') { + this._paramSuggestion = false; + } else if (this._lastChar === ')') { + if (this.rawText.slice(0, this.rawText.length - 1).split('(').length - 1 > this.rawText.slice(0, this.rawText.length - 1).split(')').length - 1) { + if (this._scriptParamsText.length > 0) { + this._paramSuggestion = true; } } - } else if (this.rawText.split('(').length - 1 <= this.rawText.split(')').length - 1) { - this._paramSuggestion = false; } + } else if (this.rawText.split('(').length - 1 <= this.rawText.split(')').length - 1) { + this._paramSuggestion = false; } this._lastChar = e.key === 'Backspace' ? this.rawText[this.rawText.length - 2] : e.key; @@ -559,9 +584,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() parameters.forEach((element: string, i: number) => { if (i < numEntered - 1) { - first = first + element; + first += element; } else if (i > numEntered - 1) { - last = last + element; + last += element; } }); @@ -598,16 +623,16 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() placeholder="write your script here" onFocus={this.onFocus} onBlur={() => this._overlayDisposer?.()} - onChange={action((e: any) => (this.rawText = e.target.value))} + onChange={action((e: any) => { + this.rawText = e.target.value; + })} value={this.rawText} - movePopupAsYouType={true} + movePopupAsYouType loadingComponent={() => <span>Loading</span>} trigger={{ ' ': { dataProvider: (token: any) => this.handleToken(token), - component: (blob: any) => { - return this.renderFuncListElement(blob.entity); - }, + component: (blob: any) => this.renderFuncListElement(blob.entity), output: (item: any, trigger: any) => { this._spaced = true; return trigger + item.trim(); @@ -615,9 +640,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() }, '.': { dataProvider: (token: any) => this.handleToken(token), - component: (blob: any) => { - return this.renderFuncListElement(blob.entity); - }, + component: (blob: any) => this.renderFuncListElement(blob.entity), output: (item: any, trigger: any) => { this._spaced = true; return trigger + item.trim(); @@ -653,16 +676,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() // params box on bottom const parameterInput = ( <div className="scriptingBox-params"> - <EditableView - display={'block'} - maxHeight={72} - height={35} - fontSize={22} - contents={''} - GetValue={returnEmptyString} - SetValue={value => (value && value !== ' ' ? this.compileParam(value) : false)} - placeholder={'enter parameters here'} - /> + <EditableView display="block" maxHeight={72} height={35} fontSize={22} contents="" GetValue={returnEmptyString} SetValue={value => (value && value !== ' ' ? this.compileParam(value) : false)} placeholder="enter parameters here" /> </div> ); @@ -670,13 +684,14 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() const definedParameters = !this.compileParams.length ? null : ( <div className="scriptingBox-plist" style={{ width: '30%' }}> {this.compileParams.map((parameter, i) => ( + // eslint-disable-next-line react/no-array-index-key <div key={i} className="scriptingBox-pborder" onKeyDown={e => e.key === 'Enter' && this._overlayDisposer?.()}> <EditableView - display={'block'} + display="block" maxHeight={72} height={35} fontSize={12} - background-color={'beige'} + background-color="beige" contents={parameter} GetValue={() => parameter} SetValue={value => (value && value !== ' ' ? this.compileParam(value, i) : this.onDelete(i))} @@ -749,6 +764,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {!this.compileParams.length || !this.paramsNames ? null : ( <div className="scriptingBox-plist"> {this.paramsNames.map((parameter: string, i: number) => ( + // eslint-disable-next-line react/no-array-index-key <div key={i} className="scriptingBox-pborder" onKeyDown={e => e.key === 'Enter' && this._overlayDisposer?.()}> <div className="scriptingBox-wrapper" style={{ maxHeight: '40px' }}> <div className="scriptingBox-paramNames"> {`${parameter}:${this.paramsTypes[i]} = `} </div> diff --git a/src/client/views/nodes/SliderBox-components.tsx b/src/client/views/nodes/SliderBox-components.tsx index e19f28f08..b9f215d77 100644 --- a/src/client/views/nodes/SliderBox-components.tsx +++ b/src/client/views/nodes/SliderBox-components.tsx @@ -1,6 +1,6 @@ -import * as React from "react"; -import { SliderItem } from "react-compound-slider"; -import "./SliderBox-tooltip.css"; +import * as React from 'react'; +import { SliderItem } from 'react-compound-slider'; +import './SliderBox-tooltip.css'; const { Component, Fragment } = React; @@ -8,24 +8,24 @@ const { Component, Fragment } = React; // TOOLTIP RAIL // ******************************************************* const railStyle: React.CSSProperties = { - position: "absolute", - width: "100%", + position: 'absolute', + width: '100%', height: 40, borderRadius: 7, - cursor: "pointer", + cursor: 'pointer', opacity: 0.3, zIndex: 300, - border: "1px solid grey" + border: '1px solid grey', }; const railCenterStyle: React.CSSProperties = { - position: "absolute", - width: "100%", + position: 'absolute', + width: '100%', height: 14, borderRadius: 7, - cursor: "pointer", - pointerEvents: "none", - backgroundColor: "rgb(155,155,155)" + cursor: 'pointer', + pointerEvents: 'none', + backgroundColor: 'rgb(155,155,155)', }; interface TooltipRailProps { @@ -37,21 +37,21 @@ interface TooltipRailProps { export class TooltipRail extends Component<TooltipRailProps> { state = { value: null, - percent: null + percent: null, }; static defaultProps = { - disabled: false + disabled: false, }; onMouseEnter = () => { - document.addEventListener("mousemove", this.onMouseMove); - } + document.addEventListener('mousemove', this.onMouseMove); + }; onMouseLeave = () => { this.setState({ value: null, percent: null }); - document.removeEventListener("mousemove", this.onMouseMove); - } + document.removeEventListener('mousemove', this.onMouseMove); + }; onMouseMove = (e: Event) => { const { activeHandleID, getEventData } = this.props; @@ -61,7 +61,7 @@ export class TooltipRail extends Component<TooltipRailProps> { } else { this.setState(getEventData(e)); } - } + }; render() { const { value, percent } = this.state; @@ -73,11 +73,10 @@ export class TooltipRail extends Component<TooltipRailProps> { <div style={{ left: `${percent}%`, - position: "absolute", - marginLeft: "-11px", - marginTop: "-35px" - }} - > + position: 'absolute', + marginLeft: '-11px', + marginTop: '-35px', + }}> <div className="tooltip"> <span className="tooltiptext">Value: {value}</span> </div> @@ -87,7 +86,7 @@ export class TooltipRail extends Component<TooltipRailProps> { style={railStyle} {...getRailProps({ onMouseEnter: this.onMouseEnter, - onMouseLeave: this.onMouseLeave + onMouseLeave: this.onMouseLeave, })} /> <div style={railCenterStyle} /> @@ -110,20 +109,20 @@ interface HandleProps { export class Handle extends Component<HandleProps> { static defaultProps = { - disabled: false + disabled: false, }; state = { - mouseOver: false + mouseOver: false, }; onMouseEnter = () => { this.setState({ mouseOver: true }); - } + }; onMouseLeave = () => { this.setState({ mouseOver: false }); - } + }; render() { const { @@ -131,7 +130,7 @@ export class Handle extends Component<HandleProps> { handle: { id, value, percent }, isActive, disabled, - getHandleProps + getHandleProps, } = this.props; const { mouseOver } = this.state; @@ -141,11 +140,10 @@ export class Handle extends Component<HandleProps> { <div style={{ left: `${percent}%`, - position: "absolute", - marginLeft: "-11px", - marginTop: "-35px" - }} - > + position: 'absolute', + marginLeft: '-11px', + marginTop: '-35px', + }}> <div className="tooltip"> <span className="tooltiptext">Value: {value}</span> </div> @@ -158,21 +156,21 @@ export class Handle extends Component<HandleProps> { aria-valuenow={value} style={{ left: `${percent}%`, - position: "absolute", - marginLeft: "-11px", - marginTop: "-6px", + position: 'absolute', + marginLeft: '-11px', + marginTop: '-6px', zIndex: 400, width: 24, height: 24, - cursor: "pointer", + cursor: 'pointer', border: 0, - borderRadius: "50%", - boxShadow: "1px 1px 1px 1px rgba(0, 0, 0, 0.4)", - backgroundColor: disabled ? "#666" : "#3e1db3" + borderRadius: '50%', + boxShadow: '1px 1px 1px 1px rgba(0, 0, 0, 0.4)', + backgroundColor: disabled ? '#666' : '#3e1db3', }} {...getHandleProps(id, { onMouseEnter: this.onMouseEnter, - onMouseLeave: this.onMouseLeave + onMouseLeave: this.onMouseLeave, })} /> </Fragment> @@ -190,23 +188,18 @@ interface TrackProps { getTrackProps: () => object; } -export function Track({ - source, - target, - getTrackProps, - disabled = false -}: TrackProps) { +export function Track({ source, target, getTrackProps, disabled = false }: TrackProps) { return ( <div style={{ - position: "absolute", + position: 'absolute', height: 14, zIndex: 1, - backgroundColor: disabled ? "#999" : "#3e1db3", + backgroundColor: disabled ? '#999' : '#3e1db3', borderRadius: 7, - cursor: "pointer", + cursor: 'pointer', left: `${source.percent}%`, - width: `${target.percent - source.percent}%` + width: `${target.percent - source.percent}%`, }} {...getTrackProps()} /> @@ -229,25 +222,24 @@ export function Tick({ tick, count, format = defaultFormat }: TickProps) { <div> <div style={{ - position: "absolute", + position: 'absolute', marginTop: 20, width: 1, height: 5, - backgroundColor: "rgb(200,200,200)", - left: `${tick.percent}%` + backgroundColor: 'rgb(200,200,200)', + left: `${tick.percent}%`, }} /> <div style={{ - position: "absolute", + position: 'absolute', marginTop: 25, fontSize: 10, - textAlign: "center", + textAlign: 'center', marginLeft: `${-(100 / count) / 2}%`, width: `${100 / count}%`, - left: `${tick.percent}%` - }} - > + left: `${tick.percent}%`, + }}> {format(tick.value)} </div> </div> diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 5b3f37993..1121134cf 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -271,7 +271,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl setupMoveUpEvents( e.target, e, - action((e, down, delta) => { + action((moveEv, down, delta) => { if (this._controlsTransform) { this._controlsTransform.X = Math.max(0, Math.min(delta[0] + this._controlsTransform.X, window.innerWidth)); this._controlsTransform.Y = Math.max(0, Math.min(delta[1] + this._controlsTransform.Y, window.innerHeight)); @@ -309,8 +309,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl // convert to desired file format const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' // if you want to preview the captured image, - const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, ''); - const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[\.\/\?\=]/g, '_')); + const retitled = StrCast(this.Document.title).replace(/[ -.:]/g, ''); + const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[./?=]/g, '_')); const filename = basename(encodedFilename); ClientUtils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY)); } @@ -410,7 +410,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl canvas.height = 100; canvas.width = 100; canvas.getContext('2d')?.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, 100, 100); - const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, ''); + const retitled = StrCast(this.Document.title).replace(/[ -.:]/g, ''); const encodedFilename = encodeURIComponent('thumbnail' + retitled + '_' + video.currentTime.toString().replace(/\./, '_')); thumbnailPromises?.push(ClientUtils.convertDataUri(canvas.toDataURL(), basename(encodedFilename), true)); const newTime = video.currentTime + video.duration / (VideoBox.numThumbnails - 1); @@ -486,7 +486,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl }, icon: 'expand-arrows-alt', }); - subitems.push({ description: (this.layoutDoc.dontAutoFollowLinks ? '' : "Don't") + ' follow links when encountered', event: () => (this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks), icon: 'expand-arrows-alt' }); + subitems.push({ + description: (this.layoutDoc.dontAutoFollowLinks ? '' : "Don't") + ' follow links when encountered', + event: () => { + this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks; + }, + icon: 'expand-arrows-alt', + }); subitems.push({ description: (this.layoutDoc.dontAutoPlayFollowedLinks ? '' : "Don't") + ' play when link is selected', event: () => { @@ -494,7 +500,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl }, icon: 'expand-arrows-alt', }); - subitems.push({ description: (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : 'Auto play') + ' anchors onClick', event: () => (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors), icon: 'expand-arrows-alt' }); + subitems.push({ + description: (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : 'Auto play') + ' anchors onClick', + event: () => { + this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors; + }, + icon: 'expand-arrows-alt', + }); // subitems.push({ description: "Start Trim All", event: () => this.startTrim(TrimScope.All), icon: "expand-arrows-alt" }); // subitems.push({ description: "Start Trim Clip", event: () => this.startTrim(TrimScope.Clip), icon: "expand-arrows-alt" }); // subitems.push({ description: "Stop Trim", event: () => this.finishTrim(), icon: "expand-arrows-alt" }); @@ -593,8 +605,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl setupMoveUpEvents( this, e, - e => { - this.Snapshot(e.clientX, e.clientY); + moveEv => { + this.Snapshot(moveEv.clientX, moveEv.clientY); return true; }, emptyFunction, @@ -657,13 +669,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl // for annotating, adds doc with time info @action.bound - addDocWithTimecode(doc: Doc | Doc[]): boolean { - const docs = doc instanceof Doc ? [doc] : doc; + addDocWithTimecode(docIn: Doc | Doc[]): boolean { + const docs = docIn instanceof Doc ? [docIn] : docIn; const curTime = NumCast(this.layoutDoc._layout_currentTimecode); docs.forEach(doc => { doc._timecodeToHide = (doc._timecodeToShow = curTime) + 1; }); - return this.addDocument(doc); + return this.addDocument(docs); } // play back the audio from seekTimeInSeconds, fullPlay tells whether clip is being played to end vs link range @@ -671,7 +683,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl playFrom = (seekTimeInSeconds: number, endTime?: number, fullPlay: boolean = false) => { clearTimeout(this._playRegionTimer); this._playRegionTimer = undefined; - if (isNaN(this.player?.duration)) { + if (this.player?.duration === undefined || isNaN(this.player.duration)) { setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500); } else if (this.player) { // trimBounds override requested playback bounds @@ -723,7 +735,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl e, returnFalse, returnFalse, - action((e: PointerEvent, doubleTap?: boolean) => { + action((clickEv: PointerEvent, doubleTap?: boolean) => { if (doubleTap) { this.startTrim(TrimScope.All); } else if (this.timeline) { @@ -782,9 +794,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl setupMoveUpEvents( this, e, - action(e => { + action(moveEv => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); - this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]); + this._marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]); return true; }), returnFalse, diff --git a/src/client/views/nodes/audio/AudioWaveform.tsx b/src/client/views/nodes/audio/AudioWaveform.tsx index 7fd799952..2d1d3d7db 100644 --- a/src/client/views/nodes/audio/AudioWaveform.tsx +++ b/src/client/views/nodes/audio/AudioWaveform.tsx @@ -7,8 +7,8 @@ import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { Cast } from '../../../../fields/Types'; import { numberRange } from '../../../../Utils'; +import { Colors } from '../../global/globalEnums'; import { ObservableReactComponent } from '../../ObservableReactComponent'; -import { Colors } from './../../global/globalEnums'; import './AudioWaveform.scss'; import { WaveCanvas } from './WaveCanvas'; @@ -62,7 +62,7 @@ export class AudioWaveform extends ObservableReactComponent<AudioWaveformProps> return NumListCast(this._props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)]); } - audioBucketField = (start: number, end: number, zoomFactor: number) => this._props.fieldKey + '_audioBuckets/' + '/' + start.toFixed(2).replace('.', '_') + '/' + end.toFixed(2).replace('.', '_') + '/' + zoomFactor * 10; + audioBucketField = (start: number, end: number, zoomFactor: number) => this._props.fieldKey + '_audioBuckets//' + start.toFixed(2).replace('.', '_') + '/' + end.toFixed(2).replace('.', '_') + '/' + zoomFactor * 10; componentWillUnmount() { this._disposer?.(); @@ -72,7 +72,7 @@ export class AudioWaveform extends ObservableReactComponent<AudioWaveformProps> this._disposer = reaction( () => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor), zoomFactor: this._props.zoomFactor }), ({ clipStart, clipEnd, fieldKey, zoomFactor }) => { - if (!this._props.layoutDoc[fieldKey] && this._props.layoutDoc.layout_fieldKey != 'layout_icon') { + if (!this._props.layoutDoc[fieldKey] && this._props.layoutDoc.layout_fieldKey !== 'layout_icon') { // setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time. const waveform = Cast(this._props.layoutDoc[this.audioBucketField(0, this._props.rawDuration, 1)], listSpec('number')); this._props.layoutDoc[fieldKey] = waveform && new List<number>(waveform.slice((clipStart / this._props.rawDuration) * waveform.length, (clipEnd / this._props.rawDuration) * waveform.length)); @@ -109,7 +109,7 @@ export class AudioWaveform extends ObservableReactComponent<AudioWaveformProps> progressColor={Colors.MEDIUM_BLUE_ALT} progress={this._props.progress ?? 1} barWidth={200 / this.audioBuckets.length} - //gradientColors={this._props.gradientColors} + // gradientColors={this._props.gradientColors} peaks={this.audioBuckets} width={(this._props.PanelWidth ?? 0) * window.devicePixelRatio} height={this.waveHeight * window.devicePixelRatio} diff --git a/src/client/views/nodes/audio/WaveCanvas.tsx b/src/client/views/nodes/audio/WaveCanvas.tsx index d3f5669a2..eacda2d42 100644 --- a/src/client/views/nodes/audio/WaveCanvas.tsx +++ b/src/client/views/nodes/audio/WaveCanvas.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/require-default-props */ import React from 'react'; interface WaveCanvasProps { @@ -14,7 +15,7 @@ interface WaveCanvasProps { export class WaveCanvas extends React.Component<WaveCanvasProps> { // If the first value of peaks is negative, addToIndices will be 1 - posPeaks = (peaks: number[], addToIndices: number) => peaks.filter((_, index) => (index + addToIndices) % 2 == 0); + posPeaks = (peaks: number[], addToIndices: number) => peaks.filter((_, index) => (index + addToIndices) % 2 === 0); drawBars = (waveCanvasCtx: CanvasRenderingContext2D, width: number, halfH: number, peaks: number[]) => { // Bar wave draws the bottom only as a reflection of the top, @@ -47,6 +48,7 @@ export class WaveCanvas extends React.Component<WaveCanvasProps> { // A half-pixel offset makes lines crisp const $ = 0.5 / this.props.pixelRatio; + // eslint-disable-next-line no-bitwise const length = ~~(allPeaks.length / 2); // ~~ is Math.floor for positive numbers. const scale = width / length; @@ -55,14 +57,14 @@ export class WaveCanvas extends React.Component<WaveCanvasProps> { waveCanvasCtx.beginPath(); waveCanvasCtx.moveTo($, halfH); - for (var i = 0; i < length; i++) { - var h = Math.round((allPeaks[2 * i] / absmax) * halfH); + for (let i = 0; i < length; i++) { + const h = Math.round((allPeaks[2 * i] / absmax) * halfH); waveCanvasCtx.lineTo(i * scale + $, halfH - h); } // Draw the bottom edge going backwards, to make a single closed hull to fill. - for (var i = length - 1; i >= 0; i--) { - var h = Math.round((allPeaks[2 * i + 1] / absmax) * halfH); + for (let i = length - 1; i >= 0; i--) { + const h = Math.round((allPeaks[2 * i + 1] / absmax) * halfH); waveCanvasCtx.lineTo(i * scale + $, halfH - h); } diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx index 8577510e3..5893c346f 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.tsx +++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx @@ -57,12 +57,13 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() { docBackgroundColor(type: string): string { // TODO: Return a different color based on the event type + console.log(type); return 'blue'; } get calendarEvents(): EventSourceInput | undefined { if (this.childDocs.length === 0) return undefined; - return this.childDocs.map((doc, idx) => { + return this.childDocs.map(doc => { const docTitle = StrCast(doc.title); const docDateRange = StrCast(doc.date_range); const [startDate, endDate] = dateRangeStrToDates(docDateRange); @@ -85,7 +86,7 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() { }); } - handleEventClick = (arg: EventClickArg) => { + handleEventClick = (/* arg: EventClickArg */) => { // TODO: open popover with event description, option to open CalendarManager and change event date, delete event, etc. }; @@ -113,7 +114,7 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() { render() { return ( <div className="calendar-box-conatiner"> - <div id="calendar-box-v1"></div> + <div id="calendar-box-v1" /> </div> ); } diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx index a72ed1813..3ec49fa27 100644 --- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx +++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx @@ -1,60 +1,11 @@ import { TextSelection } from 'prosemirror-state'; import * as ReactDOM from 'react-dom/client'; -import { Doc } from '../../../../fields/Doc'; -import { DocServer } from '../../../DocServer'; import * as React from 'react'; import { IReactionDisposer, computed, reaction } from 'mobx'; +import { Doc } from '../../../../fields/Doc'; +import { DocServer } from '../../../DocServer'; import { NumCast } from '../../../../fields/Types'; -// creates an inline comment in a note when '>>' is typed. -// the comment sits on the right side of the note and vertically aligns with its anchor in the text. -// the comment can be toggled on/off with the '<-' text anchor. -export class DashDocCommentView { - dom: HTMLDivElement; // container for label and value - root: any; - node: any; - - constructor(node: any, view: any, getPos: any) { - this.node = node; - this.dom = document.createElement('div'); - this.dom.style.width = node.attrs.width; - this.dom.style.height = node.attrs.height; - this.dom.style.fontWeight = 'bold'; - this.dom.style.position = 'relative'; - this.dom.style.display = 'inline-block'; - this.dom.onkeypress = function (e: any) { - e.stopPropagation(); - }; - this.dom.onkeydown = function (e: any) { - e.stopPropagation(); - }; - this.dom.onkeyup = function (e: any) { - e.stopPropagation(); - }; - this.dom.onmousedown = function (e: any) { - e.stopPropagation(); - }; - - this.root = ReactDOM.createRoot(this.dom); - this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} setHeight={this.setHeight} docId={node.attrs.docId} />); - (this as any).dom = this.dom; - } - - setHeight = (hgt: number) => { - !this.node.attrs.reflow && DocServer.GetRefField(this.node.attrs.docId).then(doc => doc instanceof Doc && (this.dom.style.height = hgt + '')); - }; - - destroy() { - this.root.unmount(); - } - deselectNode() { - this.dom.classList.remove('ProseMirror-selectednode'); - } - selectNode() { - this.dom.classList.add('ProseMirror-selectednode'); - } -} - interface IDashDocCommentViewInternal { docId: string; view: any; @@ -65,9 +16,6 @@ interface IDashDocCommentViewInternal { export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal> { _reactionDisposer: IReactionDisposer | undefined; - @computed get _dashDoc() { - return DocServer.GetRefField(this.props.docId); - } constructor(props: any) { super(props); this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this); @@ -77,58 +25,62 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV } componentDidMount(): void { this._reactionDisposer?.(); - this._dashDoc.then( - doc => - doc instanceof Doc && - (this._reactionDisposer = reaction( + this._dashDoc.then(doc => { + if (doc instanceof Doc) { + this._reactionDisposer = reaction( () => NumCast((doc as Doc)._height), hgt => this.props.setHeight(hgt), - { - fireImmediately: true, - } - )) - ); + { fireImmediately: true } + ); + } + }); } componentWillUnmount(): void { this._reactionDisposer?.(); } - onPointerLeaveCollapsed(e: any) { + @computed get _dashDoc() { + return DocServer.GetRefField(this.props.docId); + } + + onPointerLeaveCollapsed = (e: any) => { this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight()); e.preventDefault(); e.stopPropagation(); - } + }; - onPointerEnterCollapsed(e: any) { + onPointerEnterCollapsed = (e: any) => { this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false)); e.preventDefault(); e.stopPropagation(); - } + }; - onPointerUpCollapsed(e: any) { + onPointerUpCollapsed = (e: any) => { const target = this.targetNode(); if (target) { const expand = target.hidden; - const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true }); + const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: !target.node.attrs.hidden }); this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs setTimeout(() => { expand && this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); - } catch (e) {} + } catch (err) { + /* empty */ + } }, 0); } e.stopPropagation(); - } + }; - onPointerDownCollapsed(e: any) { + onPointerDownCollapsed = (e: any) => { e.stopPropagation(); - } + }; targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor - const state = this.props.view.state; + const { state } = this.props.view; for (let i = this.props.getPos() + 1; i < state.doc.content.size; i++) { const m = state.doc.nodeAt(i); if (m && m.type === state.schema.nodes.dashDoc && m.attrs.docId === this.props.docId) { @@ -141,7 +93,9 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV setTimeout(() => { try { this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2))); - } catch (e) {} + } catch (err) { + /* empty */ + } }, 0); return undefined; }; @@ -154,7 +108,60 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV onPointerLeave={this.onPointerLeaveCollapsed} onPointerEnter={this.onPointerEnterCollapsed} onPointerUp={this.onPointerUpCollapsed} - onPointerDown={this.onPointerDownCollapsed}></span> + onPointerDown={this.onPointerDownCollapsed} + /> ); } } + +// creates an inline comment in a note when '>>' is typed. +// the comment sits on the right side of the note and vertically aligns with its anchor in the text. +// the comment can be toggled on/off with the '<-' text anchor. +export class DashDocCommentView { + dom: HTMLDivElement; // container for label and value + root: any; + node: any; + + constructor(node: any, view: any, getPos: any) { + this.node = node; + this.dom = document.createElement('div'); + this.dom.style.width = node.attrs.width; + this.dom.style.height = node.attrs.height; + this.dom.style.fontWeight = 'bold'; + this.dom.style.position = 'relative'; + this.dom.style.display = 'inline-block'; + this.dom.onkeypress = function (e: any) { + e.stopPropagation(); + }; + this.dom.onkeydown = function (e: any) { + e.stopPropagation(); + }; + this.dom.onkeyup = function (e: any) { + e.stopPropagation(); + }; + this.dom.onmousedown = function (e: any) { + e.stopPropagation(); + }; + + this.root = ReactDOM.createRoot(this.dom); + this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} setHeight={this.setHeight} docId={node.attrs.docId} />); + (this as any).dom = this.dom; + } + + setHeight = (hgt: number) => { + !this.node.attrs.reflow && + DocServer.GetRefField(this.node.attrs.docId).then(doc => { + doc instanceof Doc && (this.dom.style.height = hgt + ''); + }); + }; + + destroy() { + this.root.unmount(); + } + deselectNode() { + this.dom.classList.remove('ProseMirror-selectednode'); + } + selectNode() { + this.dom.classList.add('ProseMirror-selectednode'); + } +} diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index dee7d70bb..f311b3cdd 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -1,10 +1,10 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { NodeSelection } from 'prosemirror-state'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; -import { Utils } from '../../../../Utils'; -import { returnFalse } from '../../../../ClientUtils'; +import { ClientUtils, returnFalse } from '../../../../ClientUtils'; import { Doc } from '../../../../fields/Doc'; import { Height, Width } from '../../../../fields/DocSymbols'; import { NumCast } from '../../../../fields/Types'; @@ -16,63 +16,7 @@ import { DocumentView } from '../DocumentView'; import { FocusViewOptions } from '../FieldView'; import { FormattedTextBox } from './FormattedTextBox'; -var horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side. -export class DashDocView { - dom: HTMLSpanElement; // container for label and value - root: any; - - constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { - this.dom = document.createElement('span'); - this.dom.style.position = 'relative'; - this.dom.style.textIndent = '0'; - this.dom.style.width = (+node.attrs.width.toString().replace('px', '') + horizPadding).toString(); - this.dom.style.height = node.attrs.height; - this.dom.style.display = node.attrs.hidden ? 'none' : 'inline-block'; - (this.dom.style as any).float = node.attrs.float; - this.dom.onkeypress = function (e: any) { - e.stopPropagation(); - }; - this.dom.onkeydown = function (e: any) { - e.stopPropagation(); - }; - this.dom.onkeyup = function (e: any) { - e.stopPropagation(); - }; - this.dom.onmousedown = function (e: any) { - e.stopPropagation(); - }; - - this.root = ReactDOM.createRoot(this.dom); - this.root.render( - <DashDocViewInternal - docId={node.attrs.docId} - embedding={node.attrs.embedding} - width={node.attrs.width} - height={node.attrs.height} - hidden={node.attrs.hidden} - fieldKey={node.attrs.fieldKey} - tbox={tbox} - view={view} - node={node} - getPos={getPos} - /> - ); - } - destroy() { - setTimeout(() => { - try { - this.root.unmount(); - } catch {} - }); - } - deselectNode() { - this.dom.style.backgroundColor = ''; - } - selectNode() { - this.dom.style.backgroundColor = 'rgb(141, 182, 247)'; - } -} - +const horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side. interface IDashDocViewInternal { docId: string; embedding: string; @@ -85,6 +29,7 @@ interface IDashDocViewInternal { node: any; getPos: any; } + @observer export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewInternal> { _spanRef = React.createRef<HTMLDivElement>(); @@ -158,7 +103,7 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn getDocTransform = () => { if (!this._spanRef.current) return Transform.Identity(); - const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current); + const { scale, translateX, translateY } = ClientUtils.GetScreenTransform(this._spanRef.current); return new Transform(-translateX, -translateY, 1).scale(1 / scale); }; outerFocus = (target: Doc, options: FocusViewOptions) => this._textBox.focus(target, options); // ideally, this would scroll to show the focus target @@ -227,3 +172,61 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn ); } } + +export class DashDocView { + dom: HTMLSpanElement; // container for label and value + root: any; + + constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + this.dom = document.createElement('span'); + this.dom.style.position = 'relative'; + this.dom.style.textIndent = '0'; + this.dom.style.width = (+node.attrs.width.toString().replace('px', '') + horizPadding).toString(); + this.dom.style.height = node.attrs.height; + this.dom.style.display = node.attrs.hidden ? 'none' : 'inline-block'; + (this.dom.style as any).float = node.attrs.float; + this.dom.onkeypress = function (e: any) { + e.stopPropagation(); + }; + this.dom.onkeydown = function (e: any) { + e.stopPropagation(); + }; + this.dom.onkeyup = function (e: any) { + e.stopPropagation(); + }; + this.dom.onmousedown = function (e: any) { + e.stopPropagation(); + }; + + this.root = ReactDOM.createRoot(this.dom); + this.root.render( + <DashDocViewInternal + docId={node.attrs.docId} + embedding={node.attrs.embedding} + width={node.attrs.width} + height={node.attrs.height} + hidden={node.attrs.hidden} + fieldKey={node.attrs.fieldKey} + tbox={tbox} + view={view} + node={node} + getPos={getPos} + /> + ); + } + destroy() { + setTimeout(() => { + try { + this.root.unmount(); + } catch { + /* empty */ + } + }); + } + deselectNode() { + this.dom.style.backgroundColor = ''; + } + selectNode() { + this.dom.style.backgroundColor = 'rgb(141, 182, 247)'; + } +} diff --git a/src/client/views/nodes/formattedText/EquationEditor.tsx b/src/client/views/nodes/formattedText/EquationEditor.tsx index b4102e08e..d9b1a2cf8 100644 --- a/src/client/views/nodes/formattedText/EquationEditor.tsx +++ b/src/client/views/nodes/formattedText/EquationEditor.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/require-default-props */ import React, { Component, createRef } from 'react'; // Import JQuery, required for the functioning of the equation editor @@ -5,11 +6,9 @@ import $ from 'jquery'; import './EquationEditor.scss'; -// eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore window.jQuery = $; -// eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore require('mathquill/build/mathquill'); diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx index b90653acc..5167c8f2a 100644 --- a/src/client/views/nodes/formattedText/EquationView.tsx +++ b/src/client/views/nodes/formattedText/EquationView.tsx @@ -1,3 +1,4 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ import { IReactionDisposer } from 'mobx'; import { observer } from 'mobx-react'; import { TextSelection } from 'prosemirror-state'; @@ -10,44 +11,6 @@ import EquationEditor from './EquationEditor'; import { FormattedTextBox } from './FormattedTextBox'; import { DocData } from '../../../../fields/DocSymbols'; -export class EquationView { - dom: HTMLDivElement; // container for label and value - root: any; - tbox: FormattedTextBox; - view: any; - constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { - this.tbox = tbox; - this.view = view; - this.dom = document.createElement('div'); - this.dom.style.width = node.attrs.width; - this.dom.style.height = node.attrs.height; - this.dom.style.position = 'relative'; - this.dom.style.display = 'inline-block'; - this.dom.onmousedown = function (e: any) { - e.stopPropagation(); - }; - - this.root = ReactDOM.createRoot(this.dom); - this.root.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} getPos={getPos} setEditor={this.setEditor} tbox={tbox} />); - } - _editor: EquationEditor | undefined; - setEditor = (editor?: EquationEditor) => (this._editor = editor); - destroy() { - this.root.unmount(); - } - setSelection() { - this._editor?.mathField.focus(); - } - selectNode() { - this.tbox._applyingChange = this.tbox.fieldKey; // setting focus will make prosemirror lose focus, which will cause it to change its selection to a text selection, which causes this view to get rebuilt but it's no longer node selected, so the equationview won't have focus - setTimeout(() => { - this._editor?.mathField.focus(); - setTimeout(() => (this.tbox._applyingChange = '')); - }); - } - deselectNode() {} -} - interface IEquationViewInternal { fieldKey: string; tbox: FormattedTextBox; @@ -70,12 +33,12 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal> this._textBoxDoc = props.tbox.Document; } - componentWillUnmount() { - this._reactionDisposer?.(); - } componentDidMount() { this.props.setEditor(this._ref.current ?? undefined); } + componentWillUnmount() { + this._reactionDisposer?.(); + } render() { return ( @@ -100,12 +63,56 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal> <EquationEditor ref={this._ref} value={StrCast(this._textBoxDoc[DocData][this._fieldKey])} - onChange={(str: any) => (this._textBoxDoc[DocData][this._fieldKey] = str)} + onChange={(str: any) => { + this._textBoxDoc[DocData][this._fieldKey] = str; + }} autoCommands="pi theta sqrt sum prod alpha beta gamma rho" autoOperatorNames="sin cos tan" - spaceBehavesLikeTab={true} + spaceBehavesLikeTab /> </div> ); } } + +export class EquationView { + dom: HTMLDivElement; // container for label and value + root: any; + tbox: FormattedTextBox; + view: any; + constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + this.tbox = tbox; + this.view = view; + this.dom = document.createElement('div'); + this.dom.style.width = node.attrs.width; + this.dom.style.height = node.attrs.height; + this.dom.style.position = 'relative'; + this.dom.style.display = 'inline-block'; + this.dom.onmousedown = function (e: any) { + e.stopPropagation(); + }; + + this.root = ReactDOM.createRoot(this.dom); + this.root.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} getPos={getPos} setEditor={this.setEditor} tbox={tbox} />); + } + _editor: EquationEditor | undefined; + setEditor = (editor?: EquationEditor) => { + this._editor = editor; + }; + destroy() { + this.root.unmount(); + } + setSelection() { + this._editor?.mathField.focus(); + } + selectNode() { + this.tbox._applyingChange = this.tbox.fieldKey; // setting focus will make prosemirror lose focus, which will cause it to change its selection to a text selection, which causes this view to get rebuilt but it's no longer node selected, so the equationview won't have focus + setTimeout(() => { + this._editor?.mathField.focus(); + setTimeout(() => { + this.tbox._applyingChange = ''; + }); + }); + } + deselectNode() {} +} diff --git a/src/client/views/nodes/formattedText/FootnoteView.tsx b/src/client/views/nodes/formattedText/FootnoteView.tsx index b327e5137..4641da2e9 100644 --- a/src/client/views/nodes/formattedText/FootnoteView.tsx +++ b/src/client/views/nodes/formattedText/FootnoteView.tsx @@ -2,9 +2,9 @@ import { EditorView } from 'prosemirror-view'; import { EditorState } from 'prosemirror-state'; import { keymap } from 'prosemirror-keymap'; import { baseKeymap, toggleMark } from 'prosemirror-commands'; -import { schema } from './schema_rts'; import { redo, undo } from 'prosemirror-history'; import { StepMap } from 'prosemirror-transform'; +import { schema } from './schema_rts'; export class FootnoteView { innerView: any; @@ -100,8 +100,8 @@ export class FootnoteView { this.innerView.updateState(state); if (!tr.getMeta('fromOutside')) { - const outerTr = this.outerView.state.tr, - offsetMap = StepMap.offset(this.getPos() + 1); + const outerTr = this.outerView.state.tr; + const offsetMap = StepMap.offset(this.getPos() + 1); for (const transaction of transactions) { for (const step of transaction.steps) { outerTr.step(step.map(offsetMap)); @@ -115,7 +115,7 @@ export class FootnoteView { if (!node.sameMarkup(this.node)) return false; this.node = node; if (this.innerView) { - const state = this.innerView.state; + const { state } = this.innerView; const start = node.content.findDiffStart(state.doc.content); if (start !== null) { let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content); diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 52d93ec38..01c46edeb 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -19,20 +19,22 @@ export function findLinkMark(marks: readonly Mark[]): Mark | undefined { return marks.find(m => m.type === schema.marks.autoLinkAnchor || m.type === schema.marks.linkAnchor); } export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) { - let before = 0, - nbef = rpos.nodeBefore; + let before = 0; + let nbef = rpos.nodeBefore; while (nbef && finder(nbef.marks)) { before += nbef.nodeSize; + // eslint-disable-next-line no-param-reassign rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize); rpos && (nbef = rpos.nodeBefore); } return before; } export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) { - let after = 0, - naft = rpos.nodeAfter; + let after = 0; + let naft = rpos.nodeAfter; while (naft && finder(naft.marks)) { after += naft.nodeSize; + // eslint-disable-next-line no-param-reassign rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize); rpos && (naft = rpos.nodeAfter); } @@ -80,10 +82,10 @@ export class FormattedTextBoxComment { } static showCommentbox(view: EditorView, nbef: number) { - const state = view.state; + const { state } = view; // These are in screen coordinates - const start = view.coordsAtPos(state.selection.from - nbef), - end = view.coordsAtPos(state.selection.from - nbef); + const start = view.coordsAtPos(state.selection.from - nbef); + const end = view.coordsAtPos(state.selection.from - nbef); // The box in which the tooltip is positioned, to use as base const box = (document.getElementsByClassName('mainView-container') as any)[0].getBoundingClientRect(); // Find a center-ish x position from the selection endpoints (when crossing lines, end may be more to the left) @@ -110,14 +112,16 @@ export class FormattedTextBoxComment { } static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[], linkDoc?: string, noPreview?: boolean) { - const state = view.state; + const { state } = view; // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date if (state.selection.$from) { const nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark); const noselection = state.selection.$from === state.selection.$to; let child: any = null; - state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); + state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any /* , pos: number, parent: any */) => { + !child && node.marks.length && (child = node); + }); const mark = child && findOtherUserMark(child.marks); if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) { FormattedTextBoxComment.saveMarkRegion(textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark); @@ -132,7 +136,7 @@ export class FormattedTextBoxComment { if (state.selection.$from && hrefs?.length) { const nbef = findStartOfMark(state.selection.$from, view, findLinkMark); const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef; - //nbef && + // nbef && naft && LinkInfo.SetLinkInfo({ DocumentView: textBox.DocumentView, diff --git a/src/client/views/nodes/formattedText/OrderedListView.tsx b/src/client/views/nodes/formattedText/OrderedListView.tsx index c3595e59b..dbc60f7bf 100644 --- a/src/client/views/nodes/formattedText/OrderedListView.tsx +++ b/src/client/views/nodes/formattedText/OrderedListView.tsx @@ -1,8 +1,7 @@ export class OrderedListView { - - update(node: any) { - // if attr's of an ordered_list (e.g., bulletStyle) change, + update() { + // if attr's of an ordered_list (e.g., bulletStyle) change, // return false forces the dom node to be recreated which is necessary for the bullet labels to update - return false; + return false; } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts index 30da91710..6c88a0d29 100644 --- a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts +++ b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts @@ -1,18 +1,19 @@ +/* eslint-disable import/extensions */ +import { Node, DOMOutputSpec } from 'prosemirror-model'; import clamp from '../../../util/clamp'; import convertToCSSPTValue from '../../../util/convertToCSSPTValue'; import toCSSLineSpacing from '../../../util/toCSSLineSpacing'; -import { Node, DOMOutputSpec } from 'prosemirror-model'; -//import type { NodeSpec } from './Types'; +// import type { NodeSpec } from './Types'; type NodeSpec = { - attrs?: { [key: string]: any }, - content?: string, - draggable?: boolean, - group?: string, - inline?: boolean, - name?: string, - parseDOM?: Array<any>, - toDOM?: (node: any) => DOMOutputSpec, + attrs?: { [key: string]: any }; + content?: string; + draggable?: boolean; + group?: string; + inline?: boolean; + name?: string; + parseDOM?: Array<any>; + toDOM?: (node: any) => DOMOutputSpec; }; // This assumes that every 36pt maps to one indent level. @@ -25,41 +26,18 @@ export const EMPTY_CSS_VALUE = new Set(['', '0%', '0pt', '0px']); const ALIGN_PATTERN = /(left|right|center|justify)/; -// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js -// :: NodeSpec A plain paragraph textblock. Represented in the DOM -// as a `<p>` element. -export const ParagraphNodeSpec: NodeSpec = { - attrs: { - align: { default: null }, - color: { default: null }, - id: { default: null }, - indent: { default: null }, - inset: { default: null }, - lineSpacing: { default: null }, - // TODO: Add UI to let user edit / clear padding. - paddingBottom: { default: null }, - // TODO: Add UI to let user edit / clear padding. - paddingTop: { default: null }, - }, - content: 'inline*', - group: 'block', - parseDOM: [{ tag: 'p', getAttrs }], - toDOM, -}; +function convertMarginLeftToIndentValue(marginLeft: string): number { + const ptValue = convertToCSSPTValue(marginLeft); + return clamp(MIN_INDENT_LEVEL, Math.floor(ptValue / INDENT_MARGIN_PT_SIZE), MAX_INDENT_LEVEL); +} function getAttrs(dom: HTMLElement): Object { - const { - lineHeight, - textAlign, - marginLeft, - paddingTop, - paddingBottom, - } = dom.style; + const { lineHeight, textAlign, marginLeft, paddingTop, paddingBottom } = dom.style; let align = dom.getAttribute('align') || textAlign || ''; - align = ALIGN_PATTERN.test(align) ? align : ""; + align = ALIGN_PATTERN.test(align) ? align : ''; - let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || "", 10); + let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || '', 10); if (!indent && marginLeft) { indent = convertMarginLeftToIndentValue(marginLeft); @@ -74,15 +52,7 @@ function getAttrs(dom: HTMLElement): Object { } function toDOM(node: Node): DOMOutputSpec { - const { - align, - indent, - inset, - lineSpacing, - paddingTop, - paddingBottom, - id, - } = node.attrs; + const { align, indent, inset, lineSpacing, paddingTop, paddingBottom, id } = node.attrs; const attrs: { [key: string]: any } | null = {}; let style = ''; @@ -128,16 +98,29 @@ function toDOM(node: Node): DOMOutputSpec { return ['p', attrs, 0]; } +// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js +// :: NodeSpec A plain paragraph textblock. Represented in the DOM +// as a `<p>` element. +export const ParagraphNodeSpec: NodeSpec = { + attrs: { + align: { default: null }, + color: { default: null }, + id: { default: null }, + indent: { default: null }, + inset: { default: null }, + lineSpacing: { default: null }, + // TODO: Add UI to let user edit / clear padding. + paddingBottom: { default: null }, + // TODO: Add UI to let user edit / clear padding. + paddingTop: { default: null }, + }, + content: 'inline*', + group: 'block', + parseDOM: [{ tag: 'p', getAttrs }], + toDOM, +}; + export const toParagraphDOM = toDOM; export const getParagraphNodeAttrs = getAttrs; -export function convertMarginLeftToIndentValue(marginLeft: string): number { - const ptValue = convertToCSSPTValue(marginLeft); - return clamp( - MIN_INDENT_LEVEL, - Math.floor(ptValue / INDENT_MARGIN_PT_SIZE), - MAX_INDENT_LEVEL - ); -} - -export default ParagraphNodeSpec;
\ No newline at end of file +export default ParagraphNodeSpec; diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 88adab66e..c107a6724 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -14,6 +14,7 @@ import { CollectionView } from '../../collections/CollectionView'; import { ContextMenu } from '../../ContextMenu'; import { KeyValueBox } from '../KeyValueBox'; import { FormattedTextBox } from './FormattedTextBox'; +// eslint-disable-next-line import/extensions import { wrappingInputRule } from './prosemirrorPatches'; import { RichTextMenu } from './RichTextMenu'; import { schema } from './schema_rts'; @@ -253,7 +254,7 @@ export class RichTextRules { } if (marks) { const tr = state.tr.deleteRange(start, end); - return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr; + return marks ? Array.from(marks).reduce((tr2, m) => tr2.addStoredMark(m), tr) : tr; } const isValidColor = (strColor: string) => { @@ -283,7 +284,7 @@ export class RichTextRules { return marks ? Array.from(marks) .filter(m => m.type !== state.schema.marks.user_mark) - .reduce((tr, m) => tr.removeStoredMark(m), tr) + .reduce((tr2, m) => tr2.removeStoredMark(m), tr) : tr; }), @@ -309,11 +310,11 @@ export class RichTextRules { teditor.dispatch(tstate.tr.setSelection(new TextSelection(tstate.doc.resolve(selection)))); } }; - const getTitledDoc = (docTitle: string) => { - if (!DocServer.FindDocByTitle(docTitle)) { - Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_fitWidth: true, _layout_autoHeight: true }); + const getTitledDoc = (title: string) => { + if (!DocServer.FindDocByTitle(title)) { + Docs.Create.TextDocument('', { title: title, _width: 400, _layout_fitWidth: true, _layout_autoHeight: true }); } - const titledDoc = DocServer.FindDocByTitle(docTitle); + const titledDoc = DocServer.FindDocByTitle(title); return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc; }; const target = getTitledDoc(docTitle); @@ -336,7 +337,7 @@ export class RichTextRules { const assign = match[4] === ':' ? (match[4] = '') : match[4]; const value = match[5]; const dataDoc = value === undefined ? !fieldKey.startsWith('_') : !assign?.startsWith('='); - const getTitledDoc = (docTitle: string) => DocServer.FindDocByTitle(docTitle); + const getTitledDoc = (title: string) => DocServer.FindDocByTitle(title); // if the value has commas assume its an array (unless it's part of a chat gpt call indicated by '((' ) if (value?.includes(',') && !value.startsWith('((')) { const values = value.split(','); diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx index 7ec296ed2..2c366b49b 100644 --- a/src/client/views/nodes/formattedText/SummaryView.tsx +++ b/src/client/views/nodes/formattedText/SummaryView.tsx @@ -3,6 +3,15 @@ import { Fragment, Node, Slice } from 'prosemirror-model'; import * as ReactDOM from 'react-dom/client'; import * as React from 'react'; +interface ISummaryView {} +// currently nothing needs to be rendered for the internal view of a summary. +// eslint-disable-next-line react/prefer-stateless-function +export class SummaryViewInternal extends React.Component<ISummaryView> { + render() { + return null; + } +} + // an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked. // this node actively edits prosemirror (as opposed to just changing how things are rendered) and thus doesn't // really need a react view. However, it would be cleaner to figure out how to do this just as a react rendering @@ -32,8 +41,8 @@ export class SummaryView { }; const js = node.toJSON; - node.toJSON = function () { - return js.apply(this, arguments); + node.toJSON = function (...args: any[]) { + return js.apply(this, args); }; this.root = ReactDOM.createRoot(this.dom); @@ -54,7 +63,8 @@ export class SummaryView { const visited = new Set(); for (let i: number = start + 1; i < view.state.doc.nodeSize - 1; i++) { let skip = false; - view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => { + // eslint-disable-next-line no-loop-func + view.state.doc.nodesBetween(start, i, (node: Node /* , pos: number, parent: Node, index: number */) => { if (node.isLeaf && !visited.has(node) && !skip) { if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) { visited.add(node); @@ -87,11 +97,3 @@ export class SummaryView { this.dom.className = this.className(visible); }; } - -interface ISummaryView {} -// currently nothing needs to be rendered for the internal view of a summary. -export class SummaryViewInternal extends React.Component<ISummaryView> { - render() { - return <> </>; - } -} diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx index 95eb86720..b195654ce 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx @@ -1,3 +1,4 @@ +/* eslint-disable jsx-a11y/label-has-associated-control */ /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ /* eslint-disable jsx-a11y/img-redundant-alt */ /* eslint-disable jsx-a11y/click-events-have-key-events */ @@ -56,7 +57,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // format: array of [image source, corresponding image Doc] const [edits, setEdits] = useState<(string | Doc)[][]>([]); const [edited, setEdited] = useState(false); - const [brushStyle, setBrushStyle] = useState<BrushStyle>(BrushStyle.ADD); + const [brushStyle] = useState<BrushStyle>(BrushStyle.ADD); const [input, setInput] = useState(''); const [loading, setLoading] = useState(false); const [canvasDims, setCanvasDims] = useState<ImageDimensions>({ @@ -102,8 +103,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD if (!ctx || !currImg.current || !canvasRef.current) return; const target = redoStack.current[redoStack.current.length - 1]; - if (!target) { - } else { + if (target) { undoStack.current = [...undoStack.current, canvasRef.current?.toDataURL()]; const img = new Image(); img.src = target; @@ -139,7 +139,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD }; // stop brushing, push to undo stack - const handlePointerUp = (e: React.PointerEvent) => { + const handlePointerUp = () => { const ctx = ImageUtility.getCanvasContext(canvasBackgroundRef); if (!ctx) return; if (!isBrushing) return; @@ -148,11 +148,11 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // handles brushing on pointer movement useEffect(() => { - if (!isBrushing) return; + if (!isBrushing) return undefined; const canvas = canvasRef.current; - if (!canvas) return; + if (!canvas) return undefined; const ctx = ImageUtility.getCanvasContext(canvasRef); - if (!ctx) return; + if (!ctx) return undefined; const handlePointerMove = (e: PointerEvent) => { const currPoint = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale); @@ -300,6 +300,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); // add the doc to the main freeform + // eslint-disable-next-line no-use-before-define await createNewImgDoc(originalImg.current, true); } } else { @@ -313,12 +314,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImgToCanvasUrl(url, canvasDims.width, canvasDims.height))); const imgRes = await Promise.all( imgUrls.map(async url => { + // eslint-disable-next-line no-use-before-define const saveRes = await onSave(url); return [url, saveRes as Doc]; }) ); setEdits(imgRes); const image = new Image(); + // eslint-disable-next-line prefer-destructuring image.src = imgUrls[0]; ImageUtility.drawImgToCanvas(image, canvasRef, canvasDims.width, canvasDims.height); currImg.current = image; @@ -351,7 +354,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // creates a new image document and returns its reference const createNewImgDoc = async (img: HTMLImageElement, firstDoc: boolean): Promise<Doc | undefined> => { - if (!imageRootDoc) return; + if (!imageRootDoc) return undefined; const { src } = img; const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] }); const source = ClientUtils.prepend(result.accessPaths.agnostic.client); @@ -375,7 +378,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD parentDoc.current = newImg; return newImg; } - if (!parentDoc.current) return; + if (!parentDoc.current) return undefined; const x = NumCast(parentDoc.current.x) + freeformRenderSize + offsetX; const initialY = 0; diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx index 185ba2280..d1f68ee0e 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx @@ -1,9 +1,9 @@ import './GenerativeFillButtons.scss'; import * as React from 'react'; import ReactLoading from 'react-loading'; -import { activeColor } from './generativeFillUtils/generativeFillConstants'; import { Button, IconButton, Type } from 'browndash-components'; import { AiOutlineInfo } from 'react-icons/ai'; +import { activeColor } from './generativeFillUtils/generativeFillConstants'; interface ButtonContainerProps { getEdit: () => Promise<void>; @@ -11,7 +11,7 @@ interface ButtonContainerProps { onReset: () => void; } -const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => { +function Buttons({ loading, getEdit, onReset }: ButtonContainerProps) { return ( <div className="generativeFillBtnContainer"> <Button text="RESET" type={Type.PRIM} color={activeColor} onClick={onReset} /> @@ -20,7 +20,7 @@ const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => { text="GET EDITS" type={Type.TERT} color={activeColor} - icon={<ReactLoading type="spin" color={'#ffffff'} width={20} height={20} />} + icon={<ReactLoading type="spin" color="#ffffff" width={20} height={20} />} iconPlacement="right" onClick={() => { if (!loading) getEdit(); @@ -36,9 +36,9 @@ const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => { }} /> )} - <IconButton type={Type.SEC} color={activeColor} tooltip="Open Documentation" icon={<AiOutlineInfo size={'16px'} />} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/generativeai/#editing', '_blank')} /> + <IconButton type={Type.SEC} color={activeColor} tooltip="Open Documentation" icon={<AiOutlineInfo size="16px" />} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/generativeai/#editing', '_blank')} /> </div> ); -}; +} export default Buttons; diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts index 97e03ff20..6da8c3da0 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts @@ -1,10 +1,6 @@ import { Point } from './generativeFillInterfaces'; export class GenerativeFillMathHelpers { - static distanceBetween = (p1: Point, p2: Point) => { - return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)); - }; - static angleBetween = (p1: Point, p2: Point) => { - return Math.atan2(p2.x - p1.x, p2.y - p1.y); - }; + static distanceBetween = (p1: Point, p2: Point) => Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2); + static angleBetween = (p1: Point, p2: Point) => Math.atan2(p2.x - p1.x, p2.y - p1.y); } diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts index 47a14135f..24dba1778 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts @@ -17,15 +17,14 @@ export class ImageUtility { * @param canvas Canvas to convert * @returns Blob of canvas */ - static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> => { - return new Promise(resolve => { + static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> => + new Promise(resolve => { canvas.toBlob(blob => { if (blob) { resolve(blob); } }, 'image/png'); }); - }; // given a square api image, get the cropped img static getCroppedImg = (img: HTMLImageElement, width: number, height: number): HTMLCanvasElement | undefined => { @@ -48,11 +47,12 @@ export class ImageUtility { } return canvas; } + return undefined; }; // converts an image to a canvas data url - static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise<string> => { - return new Promise<string>((resolve, reject) => { + static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise<string> => + new Promise<string>((resolve, reject) => { const img = new Image(); img.onload = () => { const canvas = this.getCroppedImg(img, width, height); @@ -66,7 +66,6 @@ export class ImageUtility { }; img.src = imageSrc; }); - }; // calls the openai api to get image edits static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise<APISuccess | APIError> => { @@ -91,7 +90,7 @@ export class ImageUtility { console.log(data.data); return { status: 'success', - urls: (data.data as { b64_json: string }[]).map(data => `data:image/png;base64,${data.b64_json}`), + urls: (data.data as { b64_json: string }[]).map(urlData => `data:image/png;base64,${urlData.b64_json}`), }; } catch (err) { console.log(err); @@ -100,12 +99,10 @@ export class ImageUtility { }; // mock api call - static mockGetEdit = async (mockSrc: string): Promise<APISuccess | APIError> => { - return { - status: 'success', - urls: [mockSrc, mockSrc, mockSrc], - }; - }; + static mockGetEdit = async (mockSrc: string): Promise<APISuccess | APIError> => ({ + status: 'success', + urls: [mockSrc, mockSrc, mockSrc], + }); // Gets the canvas rendering context of a canvas static getCanvasContext = (canvasRef: RefObject<HTMLCanvasElement>): CanvasRenderingContext2D | null => { @@ -150,12 +147,12 @@ export class ImageUtility { // Draws the image to the current canvas static drawImgToCanvas = (img: HTMLImageElement, canvasRef: React.RefObject<HTMLCanvasElement>, width: number, height: number) => { - const drawImg = (img: HTMLImageElement) => { + const drawImg = (htmlImg: HTMLImageElement) => { const ctx = this.getCanvasContext(canvasRef); if (!ctx) return; ctx.globalCompositeOperation = 'source-over'; ctx.clearRect(0, 0, width, height); - ctx.drawImage(img, 0, 0, width, height); + ctx.drawImage(htmlImg, 0, 0, width, height); }; if (img.complete) { @@ -173,7 +170,7 @@ export class ImageUtility { canvas.width = canvasSize; canvas.height = canvasSize; const ctx = canvas.getContext('2d'); - if (!ctx) return; + if (!ctx) return undefined; ctx?.clearRect(0, 0, canvasSize, canvasSize); ctx.drawImage(paddedCanvas, 0, 0); @@ -195,7 +192,7 @@ export class ImageUtility { // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas) static drawHorizontalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, xOffset: number) => { const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - const data = imageData.data; + const { data } = imageData; for (let i = 0; i < canvas.height; i++) { for (let j = 0; j < xOffset; j++) { const targetIdx = 4 * (i * canvas.width + j); @@ -224,7 +221,7 @@ export class ImageUtility { // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas) static drawVerticalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, yOffset: number) => { const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - const data = imageData.data; + const { data } = imageData; for (let j = 0; j < canvas.width; j++) { for (let i = 0; i < yOffset; i++) { const targetIdx = 4 * (i * canvas.width + j); @@ -256,7 +253,7 @@ export class ImageUtility { canvas.width = canvasSize; canvas.height = canvasSize; const ctx = canvas.getContext('2d'); - if (!ctx) return; + if (!ctx) return undefined; // fix scaling const scale = Math.min(canvasSize / img.width, canvasSize / img.height); const width = Math.floor(img.width * scale); @@ -310,5 +307,6 @@ export class ImageUtility { } catch (err) { console.error(err); } + return undefined; }; } diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts index 9e620ad11..260923a64 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts @@ -1,15 +1,11 @@ -import { Point } from "./generativeFillInterfaces"; +import { Point } from './generativeFillInterfaces'; export class PointerHandler { - static getPointRelativeToElement = ( - element: HTMLElement, - e: React.PointerEvent | PointerEvent, - scale: number - ): Point => { - const boundingBox = element.getBoundingClientRect(); - return { - x: (e.clientX - boundingBox.x) / scale, - y: (e.clientY - boundingBox.y) / scale, + static getPointRelativeToElement = (element: HTMLElement, e: React.PointerEvent | PointerEvent, scale: number): Point => { + const boundingBox = element.getBoundingClientRect(); + return { + x: (e.clientX - boundingBox.x) / scale, + y: (e.clientY - boundingBox.y) / scale, + }; }; - }; } diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx index dec9a5019..317719032 100644 --- a/src/client/views/nodes/importBox/ImportElementBox.tsx +++ b/src/client/views/nodes/importBox/ImportElementBox.tsx @@ -22,13 +22,14 @@ export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() { return ( <div style={{ backgroundColor: 'pink' }}> <DocumentView + // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} // LayoutTemplateString={undefined} Document={this.Document} isContentActive={returnFalse} addDocument={returnFalse} ScreenToLocalTransform={this.screenToLocalXf} - hideResizeHandles={true} + hideResizeHandles /> </div> ); diff --git a/src/client/views/nodes/trails/index.ts b/src/client/views/nodes/trails/index.ts index 8f3f7b03a..7b18974df 100644 --- a/src/client/views/nodes/trails/index.ts +++ b/src/client/views/nodes/trails/index.ts @@ -1,3 +1,3 @@ -export * from "./PresBox"; -export * from "./PresElementBox"; -export * from "./PresEnums";
\ No newline at end of file +export * from './PresBox'; +export * from './PresElementBox'; +export * from './PresEnums'; diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 02ff661e5..7b959533f 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -95,29 +95,29 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { setupMoveUpEvents( this, e, - (e: PointerEvent) => { - this.StartDrag(e, this._commentRef.current!); + (moveEv: PointerEvent) => { + this.StartDrag(moveEv, this._commentRef.current!); return true; }, returnFalse, - e => this.OnClick?.(e) + clickEv => this.OnClick?.(clickEv) ); }; audioDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, returnFalse, e => this.OnAudio?.(e)); + setupMoveUpEvents(this, e, returnFalse, returnFalse, clickEv => this.OnAudio?.(clickEv)); }; cropDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, e, - (e: PointerEvent) => { - this.StartCropDrag(e, this._cropRef.current!); + (moveEv: PointerEvent) => { + this.StartCropDrag(moveEv, this._cropRef.current!); return true; }, returnFalse, - e => this.OnCrop?.(e) + clickev => this.OnCrop?.(clickev) ); }; diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx index 4fff2a8e7..7a1e35636 100644 --- a/src/mobile/ImageUpload.tsx +++ b/src/mobile/ImageUpload.tsx @@ -30,7 +30,6 @@ export class Uploader extends React.Component<ImageUploadProps> { @observable nm: string = 'Choose files'; // Text of 'Choose Files' button @observable process: string = ''; // Current status of upload @observable private dialogueBoxOpacity = 1; - @observable private overlayOpacity = 0.4; onClick = async () => { try { @@ -167,6 +166,6 @@ export class Uploader extends React.Component<ImageUploadProps> { }; render() { - return <MainViewModal contents={this.uploadInterface} isDisplayed interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.closeUpload} />; + return <MainViewModal contents={this.uploadInterface} isDisplayed interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} closeOnExternalClick={this.closeUpload} />; } } |