diff options
Diffstat (limited to 'src/client/util')
38 files changed, 438 insertions, 829 deletions
diff --git a/src/client/util/BranchingTrailManager.tsx b/src/client/util/BranchingTrailManager.tsx index 119d103c5..65336812d 100644 --- a/src/client/util/BranchingTrailManager.tsx +++ b/src/client/util/BranchingTrailManager.tsx @@ -15,18 +15,18 @@ export class BranchingTrailManager extends React.Component { public static Instance: BranchingTrailManager; // stack of the history - @observable private slideHistoryStack: String[] = []; - @observable private containsSet: Set<String> = new Set<String>(); + @observable private slideHistoryStack: string[] = []; + @observable private containsSet: Set<string> = new Set<string>(); // docId to Doc map - @observable private docIdToDocMap: Map<String, Doc> = new Map<String, Doc>(); + @observable private docIdToDocMap: Map<string, Doc> = new Map<string, Doc>(); // prev pres to copmare with - @observable private prevPresId: String | null = null; - @action setPrevPres = action((newId: String | null) => { + @observable private prevPresId: string | null = null; + @action setPrevPres = action((newId: string | null) => { this.prevPresId = newId; }); - constructor(props: any) { + constructor(props: object) { super(props); makeObservable(this); if (!BranchingTrailManager.Instance) { @@ -48,7 +48,7 @@ export class BranchingTrailManager extends React.Component { // Doc.AddToMyOverlay(hi); }; - @action setSlideHistoryStack = action((newArr: String[]) => { + @action setSlideHistoryStack = action((newArr: string[]) => { this.slideHistoryStack = newArr; }); diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx index 77cf80151..d0cd69273 100644 --- a/src/client/util/CalendarManager.tsx +++ b/src/client/util/CalendarManager.tsx @@ -1,5 +1,3 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ import { DateRangePicker, Provider, defaultTheme } from '@adobe/react-spectrum'; import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -19,6 +17,7 @@ import { DocumentView } from '../views/nodes/DocumentView'; import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; import './CalendarManager.scss'; import { SnappingManager } from './SnappingManager'; +import { CalendarDate, DateValue } from '@internationalized/date'; // import 'react-date-range/dist/styles.css'; // import 'react-date-range/dist/theme/default.css'; @@ -29,7 +28,7 @@ interface CalendarSelectOptions { value: string; } -const formatCalendarDateToString = (calendarDate: any) => { +const formatCalendarDateToString = (calendarDate: DateValue) => { console.log('Formatting the following date: ', calendarDate); const date = new Date(calendarDate.year, calendarDate.month - 1, calendarDate.day); console.log(typeof date); @@ -44,7 +43,7 @@ const formatCalendarDateToString = (calendarDate: any) => { // TODO: For a doc already in a calendar: give option to edit date range, delete from calendar @observer -export class CalendarManager extends ObservableReactComponent<{}> { +export class CalendarManager extends ObservableReactComponent<object> { // eslint-disable-next-line no-use-before-define public static Instance: CalendarManager; @observable private isOpen = false; @@ -101,7 +100,7 @@ export class CalendarManager extends ObservableReactComponent<{}> { this.layoutDocAcls = false; }); - constructor(props: {}) { + constructor(props: object) { super(props); CalendarManager.Instance = this; makeObservable(this); @@ -110,15 +109,6 @@ export class CalendarManager extends ObservableReactComponent<{}> { componentDidMount(): void {} @action - handleSelectChange = (option: any) => { - if (option) { - const selectOpt = option as CalendarSelectOptions; - this.selectedExistingCalendarOption = selectOpt; - this.calendarName = selectOpt.value; // or label - } - }; - - @action handleCalendarTitleChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { console.log('Existing calendars: ', this.existingCalendars); this.calendarName = event.target.value; @@ -212,15 +202,13 @@ export class CalendarManager extends ObservableReactComponent<{}> { }; @observable - selectedDateRange: any = [ - { - start: new Date(), - end: new Date(), - }, - ]; + selectedDateRange: { start: DateValue; end: DateValue } = { + start: new CalendarDate(2024, 1, 1), + end: new CalendarDate(2024, 1, 1), + }; @action - setSelectedDateRange = (range: any) => { + setSelectedDateRange = (range: { start: DateValue; end: DateValue }) => { console.log('Range: ', range); this.selectedDateRange = range; }; @@ -228,14 +216,14 @@ export class CalendarManager extends ObservableReactComponent<{}> { @computed get createButtonActive() { if (this.calendarName.length === 0 || this.errorMessage.length > 0) return false; // disabled if no calendar name - let startDate: Date | undefined; - let endDate: Date | undefined; + let startDate: DateValue | undefined; + let endDate: DateValue | undefined; try { startDate = this.selectedDateRange.start; endDate = this.selectedDateRange.end; console.log(startDate); console.log(endDate); - } catch (e: any) { + } catch (e) { console.log(e); return false; // disabled } @@ -288,7 +276,13 @@ export class CalendarManager extends ObservableReactComponent<{}> { isSearchable options={this.selectOptions} value={this.selectedExistingCalendarOption} - onChange={this.handleSelectChange} + onChange={change => { + if (change) { + const selectOpt = change; + this.selectedExistingCalendarOption = selectOpt; + this.calendarName = selectOpt.value; // or label + } + }} styles={{ control: () => ({ display: 'inline-flex', diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx index 253cdd8b5..47f31612f 100644 --- a/src/client/util/CaptureManager.tsx +++ b/src/client/util/CaptureManager.tsx @@ -1,26 +1,24 @@ -/* 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'; import * as React from 'react'; import { addStyleSheet } from '../../ClientUtils'; -import { Doc } from '../../fields/Doc'; +import { Doc, Opt } from '../../fields/Doc'; import { DocCast, StrCast } from '../../fields/Types'; import { MainViewModal } from '../views/MainViewModal'; import { DocumentView } from '../views/nodes/DocumentView'; import './CaptureManager.scss'; @observer -export class CaptureManager extends React.Component<{}> { +export class CaptureManager extends React.Component<object> { // eslint-disable-next-line no-use-before-define public static Instance: CaptureManager; static _settingsStyle = addStyleSheet(); - @observable _document: any = undefined; + @observable _document: Opt<Doc> = undefined; @observable isOpen: boolean = false; // whether the CaptureManager is to be displayed or not. // eslint-disable-next-line react/sort-comp - constructor(props: {}) { + constructor(props: object) { super(props); makeObservable(this); CaptureManager.Instance = this; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index c712dba21..8a9f20565 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1,7 +1,8 @@ + import { reaction, runInAction } from "mobx"; import * as rp from 'request-promise'; import { ClientUtils, OmitKeys } from "../../ClientUtils"; -import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc"; +import { Doc, DocListCast, DocListCastAsync, FieldType, Opt } from "../../fields/Doc"; import { DocData } from "../../fields/DocSymbols"; import { InkTool } from "../../fields/InkField"; import { List } from "../../fields/List"; @@ -60,11 +61,10 @@ interface Button { // fields that do not correspond to DocumentOption fields scripts?: { script?: string; onClick?: string; onDoubleClick?: string } - funcs?: { [key:string]: any}; + funcs?: { [key:string]: string}; subMenu?: Button[]; } -// eslint-disable-next-line import/no-mutable-exports export let resolvedPorts: { server: number, socket: number }; export class CurrentUserUtils { @@ -95,7 +95,6 @@ export class CurrentUserUtils { }); const reqdOpts:DocumentOptions = { title: "child click editors", _height:75, isSystem: true}; - // eslint-disable-next-line no-return-assign return DocUtils.AssignOpts(tempClicks, reqdOpts, reqdClickList) ?? (doc[field] = Docs.Create.TreeDocument(reqdClickList, reqdOpts)); } @@ -120,7 +119,6 @@ export class CurrentUserUtils { }); const reqdOpts:DocumentOptions = {title: "click editor templates", _height:75, isSystem: true}; - // eslint-disable-next-line no-return-assign return DocUtils.AssignOpts(tempClicks, reqdOpts, reqdClickList) ?? (doc[field] = Docs.Create.TreeDocument(reqdClickList, reqdOpts)); } @@ -138,7 +136,6 @@ export class CurrentUserUtils { }), ... DocListCast(tempNotes?.data).filter(note => !reqdTempOpts.find(reqd => reqd.title === note.title))]; const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, isSystem: true }; - // eslint-disable-next-line no-return-assign return DocUtils.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts)); } static setupUserTemplates(doc: Doc, field="template_user") { @@ -146,7 +143,6 @@ export class CurrentUserUtils { const reqdUserList = DocListCast(tempUsers?.data); const reqdOpts:DocumentOptions = { title: "User Layouts", _height: 75, isSystem: true }; - // eslint-disable-next-line no-return-assign return DocUtils.AssignOpts(tempUsers, reqdOpts, reqdUserList) ?? (doc[field] = Docs.Create.TreeDocument(reqdUserList, reqdOpts)); } @@ -174,8 +170,8 @@ export class CurrentUserUtils { const imageBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.ImageDocument( "http://www.cs.brown.edu/~bcz/noImage.png", { layout:ImageBox.LayoutString(fieldKey), "icon_nativeWidth": 360 / 4, "icon_nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG, _width: 360 / 4, _height: 270 / 4, _layout_showTitle: "title", ...opts }); const fontBox = (opts:DocumentOptions, fieldKey:string) => Docs.Create.FontIconDocument({ layout:FontIconBox.LayoutString(fieldKey), _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts }); - const makeIconTemplate = (type: DocumentType | undefined, templateField: string, opts:DocumentOptions) => { - const title = "icon" + (type ? "_" + type : ""); + const makeIconTemplate = (name: DocumentType | string | undefined, templateField: string, opts:DocumentOptions) => { + const title = "icon" + (name ? "_" + name : ""); const curIcon = DocCast(templateIconsDoc[title]); const creator = (() => { switch (opts.iconTemplate) { case DocumentType.IMG : return imageBox; @@ -183,12 +179,15 @@ export class CurrentUserUtils { default: return labelBox; }})(); const allopts = {isSystem: true, onClickScriptDisable: "never", ...opts, title}; - // eslint-disable-next-line no-return-assign return DocUtils.AssignScripts( (curIcon?.iconTemplate === opts.iconTemplate ? DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[title] = MakeTemplate(creator(allopts, templateField)))), {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)", }); }; const iconTemplates = [ + // see createCustomView for where icon templates are created at run time + // templates defined by a Docs icon_fieldKey (e.g., ink with a transciprtion shows a template of the transcribed text, not miniature ink) + makeIconTemplate("transcription", "text", { iconTemplate:DocumentType.LABEL, backgroundColor: "orange" }), + // templates defined by a Doc's type makeIconTemplate(undefined, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "dimgray"}), makeIconTemplate(DocumentType.AUDIO, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "lightgreen"}), makeIconTemplate(DocumentType.PDF, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "pink"}), @@ -199,10 +198,9 @@ export class CurrentUserUtils { makeIconTemplate(DocumentType.COL, "icon", { iconTemplate:DocumentType.IMG}), makeIconTemplate(DocumentType.VID, "icon", { iconTemplate:DocumentType.IMG}), makeIconTemplate(DocumentType.BUTTON,"title", { iconTemplate:DocumentType.FONTICON}), - // nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now - makeIconTemplate("transcription" as any, "transcription", { iconTemplate:DocumentType.LABEL, backgroundColor: "orange" }), - // makeIconTemplate(DocumentType.PDF, "icon", {iconTemplate:DocumentType.IMG}, (opts) => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", opts)) - ].filter(d => d).map(d => d!); + // makeIconTemplate(DocumentType.PDF, "icon", { iconTemplate:DocumentType.IMG}), + ].filter(d => d).map(d => d!); + DocUtils.AssignOpts(DocCast(doc[field]), {}, iconTemplates); } @@ -320,20 +318,16 @@ export class CurrentUserUtils { const rtfield = new RichTextField(JSON.stringify( {doc: {type:"doc",content:[ {type:"code_block",content:[ - {type:"text",text:"^@mermaids"}, - {type:"text",text:"\n\n"}, - {type:"text",text:"pie "}, - {type:"text",text:"title"}, - {type:"text",text:" "}, - {type:"text",text:"Minerals in my tap water"}, - {type:"text",text:"\n \"Calcium\" : "}, + {type:"text",text:`^@mermaids\n`}, + {type:"text",text:`\n pie title Minerals in my tap water`}, + {type:"text",text:`\n "Calcium" : `}, {type:"dashField",attrs:{fieldKey:"calcium",docId:"",hideKey:true,hideValue:false,editable:true}}, - {type:"text",text:"\n \"Potassium\" : "}, + {type:"text",text:`\n "Potassium" : `}, {type:"dashField",attrs:{fieldKey:"pot",docId:"",hideKey:true,hideValue:false,editable:true}}, - {type:"text",text:"\n \"Magnesium\" : 10.01"} + {type:"text",text:`\n "Magnesium" : 10.01`} ]} ]}, - selection:{type:"text",anchor:109,head:109} + selection:{type:"text",anchor:1,head:1} }), `^@mermaids pie title Minerals in my tap water @@ -350,9 +344,9 @@ pie title Minerals in my tap water plotlyApi(); mermaidsApi(); const emptyThings:{key:string, // the field name where the empty thing will be stored opts:DocumentOptions, // the document options that are required for the empty thing - funcs?:{[key:string]: any}, // computed fields that are rquired for the empth thing - scripts?:{[key:string]: any}, - creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist + funcs?:{[key:string]: string}, // computed fields that are rquired for the empth thing + scripts?:{[key:string]: string}, + creator:(opts:DocumentOptions)=> Doc // how to create the empty thing if it doesn't exist }[] = [ {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true }}, {key: "Flashcard", creator: opts => Docs.Create.ComparisonDocument("", opts), opts: { _layout_isFlashcard: true, _width: 300, _height: 300}}, @@ -403,7 +397,7 @@ pie title Minerals in my tap water { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab)}, { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, clickFactory: DocCast(doc.emptyWebpage)}, { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)}, - { toolTip: "Tap or drag to create a diagram", title: "Diagram", icon: "tree", dragFactory: doc.emptyDiagram as Doc, clickFactory: DocCast(doc.emptyDiagram)}, + { toolTip: "Tap or drag to create a diagram", title: "Diagram", icon: "tree", dragFactory: doc.emptyDiagram as Doc, clickFactory: DocCast(doc.emptyDiagram)}, { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, clickFactory: DocCast(doc.emptyAudio), openFactoryLocation: OpenWhere.overlay}, { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)}, { toolTip: "Tap or drag to create a chat assistant", title: "Assistant Chat", icon: "book",dragFactory: doc.emptyChat as Doc, clickFactory: DocCast(doc.emptyChat)}, @@ -412,11 +406,11 @@ pie title Minerals in my tap water { toolTip: "Tap or drag to create a button", title: "Button", icon: "circle", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)}, { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, clickFactory: DocCast(doc.emptyScript), funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "chart-bar", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)}, - { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "person-chalkboard", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, - { toolTip: "Tap or drag to create a view slide", title: "View Slide", icon: "address-card", dragFactory: doc.emptyViewSlide as Doc,clickFactory: DocCast(doc.emptyViewSlide),openFactoryLocation: OpenWhere.overlay,funcs: { hidden: "IsNoviceMode()"}}, - { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize",dragFactory: doc.emptyHeader as Doc,clickFactory: DocCast(doc.emptyHeader), openFactoryAsDelegate: true, funcs: { hidden: "IsNoviceMode()"} }, - { toolTip: "Toggle a Calculator REPL", title: "replviewer", icon: "calculator", clickFactory: '<ScriptingRepl />' as any, openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script - // { toolTip: "Toggle an UndoStack", title: "undostacker", icon: "calculator", clickFactory: "<UndoStack />" as any, openFactoryLocation: OpenWhere.overlay}, + { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "person-chalkboard",dragFactory: doc.emptySlide as Doc,clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, + { toolTip: "Tap or drag to create a view slide", title: "View Slide", icon: "address-card", dragFactory: doc.emptyViewSlide as Doc,clickFactory: DocCast(doc.emptyViewSlide), openFactoryLocation: OpenWhere.overlay,funcs: { hidden: "IsNoviceMode()"}}, + { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize",dragFactory: doc.emptyHeader as Doc,clickFactory: DocCast(doc.emptyHeader), openFactoryAsDelegate: true, funcs: { hidden: "IsNoviceMode()"} }, + { toolTip: "Toggle a Calculator REPL", title: "replviewer", icon: "calculator", clickFactory: '<ScriptingRepl />' as unknown as Doc, openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script + // { toolTip: "Toggle an UndoStack", title: "undostacker", icon: "calculator", clickFactory: "<UndoStack />" as any, openFactoryLocation: OpenWhere.overlay}, ].map(tuple => ( { openFactoryLocation: OpenWhere.addRight, scripts: { onClick: 'openDoc(copyDragFactory(this.clickFactory,this.openFactoryAsDelegate), this.openFactoryLocation)', @@ -446,7 +440,7 @@ pie title Minerals in my tap water } /// returns descriptions needed to buttons for the left sidebar to open up panes displaying different collections of documents - static leftSidebarMenuBtnDescriptions(doc: Doc):{title:string, target:Doc, icon:string, toolTip: string, scripts:{[key:string]:any}, funcs?:{[key:string]:any}, hidden?: boolean}[] { + static leftSidebarMenuBtnDescriptions(doc: Doc):{title:string, target:Doc, icon:string, toolTip: string, scripts:{[key:string]:undefined|string}, funcs?:{[key:string]:undefined|string}, hidden?: boolean}[] { const badgeValue = "((len) => len && len !== '0' ? len: undefined)(docList(this.target?.data).filter(doc => !docList(this.target.viewed).includes(doc)).length.toString())"; const getActiveDashTrails = "Doc.ActiveDashboard?.myTrails"; return [ @@ -458,13 +452,15 @@ pie title Minerals in my tap water { title: "Closed", toolTip: "Recently Closed", target: this.setupRecentlyClosed(doc, "myRecentlyClosed"), ignoreClick: true, icon: "archive", hidden: true }, // this doc is hidden from the Sidebar, but it's still being used in MyFilesystem which ignores the hidden field { title: "Shared", toolTip: "Shared Docs", target: Doc.MySharedDocs, ignoreClick: true, icon: "users", funcs: {badgeValue: badgeValue}}, { title: "Trails", toolTip: "Trails ⌘R", target: Doc.UserDoc(), ignoreClick: true, icon: "pres-trail", funcs: {target: getActiveDashTrails}}, + { title: "Image Grouper", toolTip: "Image Grouper", target: this.setupImageGrouper(doc, "myImageGrouper"), ignoreClick: true, icon: "folder-open", hidden: false }, + { title: "Faces", toolTip: "Unique Faces", target: this.setupFaceCollection(doc, "myFaceCollection"), ignoreClick: true, icon: "face-smile", hidden: false }, { title: "User Doc", toolTip: "User Doc", target: this.setupUserDocView(doc, "myUserDocView"), ignoreClick: true, icon: "address-card",funcs: {hidden: "IsNoviceMode()"} }, - ].map(tuple => ({...tuple, scripts:{onClick: 'selectMainMenu(this)'}})); + ].map(tuple => ({...tuple, scripts:{onClick: 'selectMainMenu(this)'}})); } /// the empty panel that is filled with whichever left menu button's panel has been selected static setupLeftSidebarPanel(doc: Doc, field="myLeftSidebarPanel") { - DocUtils.AssignDocField(doc, field, (opts) => Doc.assign(new Doc(), opts as any), {title:"leftSidebarPanel", isSystem:true, undoIgnoreFields: new List<string>(['proto'])}); + DocUtils.AssignDocField(doc, field, (opts) => Doc.assign(new Doc(), opts as {[key:string]: FieldType}), {title:"leftSidebarPanel", isSystem:true, undoIgnoreFields: new List<string>(['proto'])}); } /// Initializes the left sidebar menu buttons and the panels they open up @@ -494,6 +490,18 @@ pie title Minerals in my tap water _lockedPosition: true, _type_collection: CollectionViewType.Schema }); } + static setupImageGrouper(doc: Doc, field: string) { + return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.ImageGrouperDocument(opts), { + dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Image Grouper", isSystem: true, childDragAction: dropActionType.embed, + _lockedPosition: true, _type_collection: CollectionViewType.Schema }); + } + + static setupFaceCollection(doc: Doc, field: string) { + return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.FaceCollectionDocument(opts), { + dontRegisterView: true, ignoreClick: true, title: "Face Collection", isSystem: true, childDragAction: dropActionType.embed, + _lockedPosition: true, _type_collection: CollectionViewType.Schema }); + } + /// Initializes the panel of draggable tools that is opened from the left sidebar. static setupToolsBtnPanel(doc: Doc, field:string) { const allTools = DocListCast(DocCast(doc[field])?.data); @@ -524,7 +532,7 @@ pie title Minerals in my tap water const contextMenuLabels = [/* "Create New Dashboard" */] as string[]; const contextMenuIcons = [/* "plus" */] as string[]; const childContextMenuScripts = [`toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(this)`, 'removeDashboard(this)', 'resetDashboard(this)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters - const childContextMenuFilters = ['!IsNoviceMode()', '!IsNoviceMode()', undefined as any, undefined as any, '!IsNoviceMode()'];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts + const childContextMenuFilters = ['!IsNoviceMode()', '!IsNoviceMode()', undefined, undefined, '!IsNoviceMode()'];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts const childContextMenuLabels = ["Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard", "Reset Dashboard"];// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters const childContextMenuIcons = ["tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters const reqdOpts:DocumentOptions = { @@ -546,7 +554,7 @@ pie title Minerals in my tap water myDashboards.childContextMenuScripts = new List<ScriptField>(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); } if (Cast(myDashboards.childContextMenuFilters, listSpec(ScriptField), null)?.length !== childContextMenuFilters.length) { - myDashboards.childContextMenuFilters = new List<ScriptField>(childContextMenuFilters.map(script => !script ? script: ScriptField.MakeFunction(script)!)); + myDashboards.childContextMenuFilters = new List<ScriptField>(childContextMenuFilters.map(script => !script ? script as unknown as ScriptField: ScriptField.MakeFunction(script)!)); } return myDashboards; } @@ -612,7 +620,7 @@ pie title Minerals in my tap water _lockedPosition: true, isSystem: true, flexDirection: "row" }) static multiToggleList = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.FontIconDocument({ - ...opts, data:docs, _gridGap: 0, _xMargin: 5, _yMargin: 5, layout_boxShadow: "0 0", _forceActive: true, + ...opts, data: new List<Doc>(docs), _gridGap: 0, _xMargin: 5, _yMargin: 5, layout_boxShadow: "0 0", _forceActive: true, dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), _lockedPosition: true, isSystem: true, flexDirection: "row" }) @@ -696,8 +704,8 @@ pie title Minerals in my tap water { title: "Fit All", icon: "object-group", toolTip: "Fit Docs to View (double click to make sticky)",btnType: ButtonType.ToggleButton, ignoreClick:true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}', onDoubleClick: '{ return showFreeform(this.toolType, _readOnly_, true);}'}}, // Only when floating document is selected in freeform { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform - { title: "Arrange", icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform - + { title: "Arrange", icon:"arrow-down-short-wide",toolTip:"Auto Arrange", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + ] } static textTools():Button[] { @@ -807,7 +815,7 @@ pie title Minerals in my tap water /// initializes a context menu button for the top bar context menu static setupContextMenuButton(params:Button, btnDoc?:Doc, btnContainer?:Doc) { const reqdOpts:DocumentOptions = { - ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, + ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit as {[key:string]: string|undefined}, color: Colors.WHITE, isSystem: true, _nativeWidth: params.width ?? 30, _width: params.width ?? 30, _height: 30, _nativeHeight: 30, linearBtnWidth: params.linearBtnWidth, @@ -815,7 +823,7 @@ pie title Minerals in my tap water _dragOnlyWithinContainer: true, _lockedPosition: true, _embedContainer: btnContainer }; - const reqdFuncs:{[key:string]:any} = { + const reqdFuncs:{[key:string]:string} = { ...params.funcs, } return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs); @@ -854,14 +862,14 @@ pie title Minerals in my tap water Doc.UserDoc().workspaceRecordingState = undefined; Doc.UserDoc().workspaceReplayingState = undefined; const dockedBtns = DocCast(doc[field]); - const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:any}) => + const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:string|undefined}) => 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 - { opts: { title: "Replicate",icon:"camera",toolTip: "Copy dashboard layout",btnType: ButtonType.ClickButton, expertMode: true}, scripts: { onClick: `snapshotDashboard()`}}, - { opts: { title: "Recordings", toolTip: "Workspace Recordings", btnType: ButtonType.DropdownList,expertMode: false, ignoreClick: true, width: 100}, funcs: {hidden: `false`, btnList:`getWorkspaceRecordings()`}, scripts: { script: `{ return replayWorkspace(value, _readOnly_); }`, onDragScript: `{ return startRecordingDrag(value); }`}}, - { opts: { title: "Stop Rec",icon: "stop", toolTip: "Stop recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `!isWorkspaceRecording()`}, scripts: { onClick: `stopWorkspaceRecording()`}}, + { opts: { title: "Replicate",icon:"camera",toolTip: "Copy dashboard layout",btnType: ButtonType.ClickButton, expertMode: true}, scripts: { onClick: `snapshotDashboard()`}}, + { opts: { title: "Recordings", toolTip: "Workspace Recordings", btnType: ButtonType.DropdownList,expertMode: false, ignoreClick: true, width: 100}, funcs: {hidden: `false`, btnList:`getWorkspaceRecordings()`},scripts: { script: `{ return replayWorkspace(value, _readOnly_); }`, onDragScript: `{ return startRecordingDrag(value); }`}}, + { opts: { title: "Stop Rec",icon: "stop", toolTip: "Stop recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `!isWorkspaceRecording()`}, scripts: { onClick: `stopWorkspaceRecording()`}}, { opts: { title: "Play", icon: "play", toolTip: "Play recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Paused}"`}, scripts: { onClick: `resumeWorkspaceReplaying(getCurrentRecording())`}}, { opts: { title: "Pause", icon: "pause",toolTip: "Pause playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Playing}"`}, scripts: { onClick: `pauseWorkspaceReplaying(getCurrentRecording())`}}, { opts: { title: "Stop", icon: "stop", toolTip: "Stop playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Paused}"`}, scripts: { onClick: `stopWorkspaceReplaying(getCurrentRecording())`}}, @@ -1003,20 +1011,20 @@ pie title Minerals in my tap water return doc; } static setupFieldInfos(doc:Doc, field="fieldInfos") { - const fieldInfoOpts = { title: "Field Infos", isSystem: true}; // bcz: all possible document options have associated field infos which are stored onn the FieldInfos document **except for title and system which are used as part of the definition of the fieldInfos object - const infos = DocUtils.AssignDocField(doc, field, opts => Doc.assign(new Doc(), opts as any), fieldInfoOpts); + const fieldInfoOpts = { title: "Field Infos", isSystem: true}; // bcz: all possible document options have associated field infos which are stored on the FieldInfos document **except for title and system which are used as part of the definition of the fieldInfos object + const infos = DocUtils.AssignDocField(doc, field, opts => Doc.assign(new Doc(), opts as {[key:string]: FieldType}), fieldInfoOpts); const entries = Object.entries(new DocumentOptions()); entries.forEach(pair => { if (!Array.from(Object.keys(fieldInfoOpts)).includes(pair[0])) { const options = pair[1] as FInfo; - const opts:DocumentOptions = { isSystem: true, title: pair[0], ...OmitKeys(options, ["values"]).omit, fieldIsLayout: pair[0].startsWith("_")}; + const opts:DocumentOptions = { isSystem: true, title: pair[0], ...OmitKeys(options, ["values"]).omit}; switch (options.fieldType) { - case FInfoFieldType.boolean: opts.fieldValues = new List<boolean>(options.values as any); break; - case FInfoFieldType.number: opts.fieldValues = new List<number>(options.values as any); break; - 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 + case FInfoFieldType.boolean: opts.fieldValues = new List<boolean>(options.values as boolean[]); break; + case FInfoFieldType.number: opts.fieldValues = new List<number>(options.values as number[]); break; + case FInfoFieldType.Doc: opts.fieldValues = new List<Doc>(options.values as Doc[]); break; + default: opts.fieldValues = new List<FieldType>(options.values); break;// string, pointerEvents, dimUnit, dropActionType } - DocUtils.AssignDocField(infos, pair[0], docOpts => Doc.assign(new Doc(), OmitKeys(docOpts,["values"]).omit), opts); + DocUtils.AssignDocField(infos, pair[0], docOpts => Doc.assign(new Doc(), OmitKeys(docOpts,["values"]).omit as {[key:string]: FieldType}), opts); } }); } @@ -1024,10 +1032,10 @@ pie title Minerals in my tap water public static async loadCurrentUser() { return rp.get(ClientUtils.prepend("/getCurrentUser")).then(async response => { if (response) { - const result: { version: string, userDocumentId: string, sharingDocumentId: string, linkDatabaseId: string, email: string, cacheDocumentIds: string, resolvedPorts: string } = JSON.parse(response); + const result: { version: string, userDocumentId: string, sharingDocumentId: string, linkDatabaseId: string, email: string, cacheDocumentIds: string, resolvedPorts: {server: number, socket: number} } = JSON.parse(response); runInAction(() => { SnappingManager.SetServerVersion(result.version); }); ClientUtils.SetCurrentUserEmail(result.email); - resolvedPorts = result.resolvedPorts as any; + resolvedPorts = result.resolvedPorts; DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts?.socket, result.email); if (result.cacheDocumentIds) { diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index b9a465515..831afe538 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -1,7 +1,5 @@ /* 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'; import { ClientUtils } from '../../ClientUtils'; import { Doc, Opt } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; @@ -33,17 +31,19 @@ import { UndoManager } from './UndoManager'; * In addition to compile-time default commands, you can invoke DictationManager.Commands.Register(Independent|Dependent) * to add new commands as classes or components are constructed. */ + export namespace DictationManager { /** * Some type maneuvering to access Webkit's built-in * speech recognizer. */ + namespace CORE { export interface IWindow extends Window { - webkitSpeechRecognition: any; + webkitSpeechRecognition: { new (): SpeechRecognition }; } } - const { webkitSpeechRecognition }: CORE.IWindow = window as any as CORE.IWindow; + const { webkitSpeechRecognition }: CORE.IWindow = window as unknown as CORE.IWindow; export const placeholder = 'Listening...'; export namespace Controls { @@ -74,7 +74,7 @@ export namespace DictationManager { // eslint-disable-next-line new-cap const recognizer: Opt<SpeechRecognition> = webkitSpeechRecognition ? new webkitSpeechRecognition() : undefined; - export type InterimResultHandler = (results: string) => any; + export type InterimResultHandler = (results: string) => void; export type ContinuityArgs = { indefinite: boolean } | false; export type DelimiterArgs = { inter: string; intra: string }; export type ListeningUIStatus = { interim: boolean } | false; @@ -117,11 +117,11 @@ export namespace DictationManager { } options?.tryExecute && (await DictationManager.Commands.execute(results)); } - } catch (e: any) { + } catch (e) { console.log(e); if (overlay) { DictationOverlay.Instance.isListening = false; - DictationOverlay.Instance.dictatedPhrase = results = `dictation error: ${'error' in e ? e.error : 'unknown error'}`; + DictationOverlay.Instance.dictatedPhrase = results = `dictation error: ${(e as { error: string }).error || 'unknown error'}`; DictationOverlay.Instance.dictationSuccess = false; } } finally { @@ -156,11 +156,11 @@ export namespace DictationManager { recognizer.start(); return new Promise<string>(resolve => { - recognizer.onerror = (e: any) => { + recognizer.onerror = e => { // e is SpeechRecognitionError but where is that defined? if (!(indefinite && e.error === 'no-speech')) { recognizer.stop(); - resolve(e); + resolve(e.message); } }; @@ -230,10 +230,10 @@ export namespace DictationManager { export namespace Commands { export const dictationFadeDuration = 2000; - export type IndependentAction = (target: DocumentView) => any | Promise<any>; + export type IndependentAction = (target: DocumentView) => void | Promise<void>; export type IndependentEntry = { action: IndependentAction; restrictTo?: DocumentType[] }; - export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise<any>; + export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => void | Promise<void>; export type DependentEntry = { expression: RegExp; action: DependentAction; restrictTo?: DocumentType[] }; export const RegisterIndependent = (key: string, value: IndependentEntry) => Independent.set(key, value); @@ -295,7 +295,6 @@ export namespace DictationManager { [DocumentType.COL, listSpec(Doc)], [DocumentType.AUDIO, AudioField], [DocumentType.IMG, ImageField], - [DocumentType.IMPORT, listSpec(Doc)], [DocumentType.RTF, 'string'], ]); @@ -397,8 +396,8 @@ export namespace DictationManager { ]; } export function recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) { - let gumStream: any; - let recorder: any; + let gumStream: MediaStream | undefined; + let recorder: MediaRecorder | undefined; navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => { let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null); if (audioTextAnnos) audioTextAnnos.push(''); @@ -415,8 +414,12 @@ export namespace DictationManager { gumStream = stream; recorder = new MediaRecorder(stream); - recorder.ondataavailable = async (e: any) => { - const [{ result }] = await Networking.UploadFilesToServer({ file: e.data }); + recorder.ondataavailable = async (e: BlobEvent) => { + const file: Blob & { name?: string; lastModified?: number; webkitRelativePath?: string } = e.data; + file.name = ''; + file.lastModified = 0; + file.webkitRelativePath = ''; + const [{ result }] = await Networking.UploadFilesToServer({ file: file as Blob & { name: string; lastModified: number; webkitRelativePath: string } }); if (!(result instanceof Error)) { const audioField = new AudioField(result.accessPaths.agnostic.client); const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null); @@ -426,10 +429,10 @@ export namespace DictationManager { }; recorder.start(); const stopFunc = () => { - recorder.stop(); + recorder?.stop(); DictationManager.Controls.stop(/* false */); dataDoc.audioAnnoState = AudioAnnoState.stopped; - gumStream.getAudioTracks()[0].stop(); + gumStream?.getAudioTracks()[0].stop(); }; if (onRecording) onRecording(stopFunc); else setTimeout(stopFunc, 5000); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 96b8b5657..83b83240e 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -17,7 +17,6 @@ export class DocumentManager { // eslint-disable-next-line no-use-before-define private static _instance: DocumentManager; public static get Instance(): DocumentManager { - // eslint-disable-next-line no-return-assign return this._instance || (this._instance = new this()); } @@ -50,22 +49,23 @@ export class DocumentManager { DocumentView.getLightboxDocumentView = this.getLightboxDocumentView; observe(Doc.CurrentlyLoading, change => { // watch CurrentlyLoading-- when something is loaded, it's removed from the list and we have to update its icon if it were iconified since LoadingBox icons are different than the media they become - switch (change.type as any) { + switch (change.type) { case 'update': break; - case 'remove': - // DocumentManager.Instance.getAllDocumentViews(change as any).forEach(dv => StrCast(dv.Document.layout_fieldKey) === 'layout_icon' && dv.iconify(() => dv.iconify())); - break; case 'splice': - (change as any).removed.forEach((doc: Doc) => DocumentManager.Instance.getAllDocumentViews(doc).forEach(dv => StrCast(dv.Document.layout_fieldKey) === 'layout_icon' && dv.iconify(() => dv.iconify()))); + change.removed.forEach((doc: Doc) => DocumentManager.Instance.getAllDocumentViews(doc).forEach(dv => StrCast(dv.Document.layout_fieldKey) === 'layout_icon' && dv.iconify(() => dv.iconify()))); break; default: } }); } - private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => any }[] = []; - public AddViewRenderedCb = (doc: Opt<Doc>, func: (dv: DocumentView) => any) => { + private _anyViewRenderedCbs: ((dv: DocumentView) => unknown)[] = []; + public AddAnyViewRenderedCB = (func: (dv: DocumentView) => unknown) => { + this._anyViewRenderedCbs.push(func); + }; + private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => unknown }[] = []; + public AddViewRenderedCb = (doc: Opt<Doc>, func: (dv: DocumentView) => unknown) => { if (doc) { const dv = DocumentView.LightboxDoc() ? this.getLightboxDocumentView(doc) : this.getDocumentView(doc); this._viewRenderedCbs.push({ doc, func }); @@ -74,18 +74,20 @@ export class DocumentManager { return true; } } else { + // eslint-disable-next-line @typescript-eslint/no-explicit-any func(undefined as any); } return false; }; callAddViewFuncs = (view: DocumentView) => { - const callFuncs = this._viewRenderedCbs.filter(vc => vc.doc === view.Document); + const docCallFuncs = this._viewRenderedCbs.filter(vc => vc.doc === view.Document); + const callFuncs = docCallFuncs.map(vc => vc.func).concat(this._anyViewRenderedCbs); if (callFuncs.length) { - this._viewRenderedCbs = this._viewRenderedCbs.filter(vc => !callFuncs.includes(vc)); + this._viewRenderedCbs = this._viewRenderedCbs.filter(vc => !docCallFuncs.includes(vc)); const intTimer = setInterval( () => { if (!view.ComponentView?.incrementalRendering?.()) { - callFuncs.forEach(cf => cf.func(view)); + callFuncs.forEach(cf => cf(view)); clearInterval(intTimer); } }, @@ -341,22 +343,24 @@ export class DocumentManager { // if there's an options.effect, it will be handled from linkFollowHighlight. We delay the start of // the highlight so that the target document can be somewhat centered so that the effect/highlight will be seen // bcz: should this delay be an options parameter? - setTimeout(() => { - Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect); - if (options.zoomTextSelections && Doc.IsUnhighlightTimerSet() && contextView && targetDoc.text_html) { - // if the docView is a text anchor, the contextView is the PDF/Web/Text doc - contextView.setTextHtmlOverlay(StrCast(targetDoc.text_html), options.effect); - DocumentManager._overlayViews.add(contextView); - } - Doc.AddUnHighlightWatcher(() => { - docView.Document[Animation] = undefined; - DocumentManager.removeOverlayViews(); - }); - }, (options.zoomTime ?? 0) * 0.5); + setTimeout( + () => { + Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect); + if (options.zoomTextSelections && Doc.IsUnhighlightTimerSet() && contextView && targetDoc.text_html) { + // if the docView is a text anchor, the contextView is the PDF/Web/Text doc + contextView.setTextHtmlOverlay(StrCast(targetDoc.text_html), options.effect); + DocumentManager._overlayViews.add(contextView); + } + Doc.AddUnHighlightWatcher(() => { + docView.Document[Animation] = undefined; + DocumentManager.removeOverlayViews(); + }); + }, + (options.zoomTime ?? 0) * 0.5 + ); if (options.playMedia) docView.ComponentView?.playFrom?.(NumCast(docView.Document._layout_currentTimecode)); if (options.playAudio) DocumentManager.playAudioAnno(docView.Document); if (options.toggleTarget && (!options.didMove || docView.Document.hidden)) docView.Document.hidden = !docView.Document.hidden; - Doc.AddUnHighlightWatcher(() => docView.Document[Animation] = undefined); } } } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index fda505420..7db13689d 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -1,4 +1,3 @@ -/* eslint-disable import/no-mutable-exports */ /* eslint-disable no-use-before-define */ /** * The DragManager handles all dragging interactions that occur entirely within Dash (as opposed to external drag operations from the file system, etc) @@ -23,13 +22,14 @@ import { DocData } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; import { PrefetchProxy } from '../../fields/Proxy'; import { ScriptField } from '../../fields/ScriptField'; -import { ScriptCast } from '../../fields/Types'; +import { ScriptCast, StrCast } from '../../fields/Types'; import { Docs } from '../documents/Documents'; import { DocumentView } from '../views/nodes/DocumentView'; import { dropActionType } from './DropActionTypes'; import { SnappingManager } from './SnappingManager'; import { UndoManager } from './UndoManager'; +// eslint-disable-next-line @typescript-eslint/no-var-requires const { contextMenuZindex } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore /** @@ -78,7 +78,7 @@ export namespace DragManager { export let CompleteWindowDrag: Opt<(aborted: boolean) => void>; export let AbortDrag: () => void = emptyFunction; export const docsBeingDragged: Doc[] = observable([]); - export let DocDragData: DocumentDragData | undefined; + export let DraggedDocs: Doc[] | undefined; export function Root() { const root = document.getElementById('root'); @@ -118,7 +118,7 @@ export namespace DragManager { // event called when the drag operation has completed (aborted or completed a drop) -- this will be after any drop event has been generated export class DragCompleteEvent { - constructor(aborted: boolean, dragData: { [id: string]: any }) { + constructor(aborted: boolean, dragData: DocumentDragData | AnchorAnnoDragData | LinkDragData | ColumnDragData) { this.aborted = aborted; this.docDragData = dragData instanceof DocumentDragData ? dragData : undefined; this.annoDragData = dragData instanceof AnchorAnnoDragData ? dragData : undefined; @@ -167,6 +167,9 @@ export namespace DragManager { linkSourceGetAnchor: () => Doc; linkSourceDoc?: Doc; linkDragView: DocumentView; + get canEmbed() { + return true; + } } export class ColumnDragData { // constructor(colKey: SchemaHeaderField) { @@ -177,6 +180,9 @@ export namespace DragManager { this.colIndex = colIndex; } colIndex: number; + get canEmbed() { + return true; + } } // used by PDFs,Text,Image,Video,Web to conditionally (if the drop completes) create a text annotation when dragging the annotate button from the AnchorMenu when a text/region selection has been made. // this is pretty clunky and should be rethought out using linkDrag or DocumentDrag @@ -191,6 +197,9 @@ export namespace DragManager { offset: number[]; dropAction?: dropActionType; userDropAction?: dropActionType; + get canEmbed() { + return true; + } } const defaultPreDropFunc = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { @@ -208,7 +217,7 @@ export namespace DragManager { const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail); const preDropHandler = (e: Event) => { const de = (e as CustomEvent<DropEvent>).detail; - (preDropFunc ?? defaultPreDropFunc)(e, de, doc.dropAction as any as dropActionType); + (preDropFunc ?? defaultPreDropFunc)(e, de, StrCast(doc.dropAction) as dropActionType); }; element.addEventListener('dashOnDrop', handler); element.addEventListener('dashPreDrop', preDropHandler); @@ -220,8 +229,8 @@ export namespace DragManager { } // drag a document and drop it (or make an embed/copy on drop) - export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, onDropCompleted?: (e?: DragCompleteEvent) => any) { - const addAudioTag = (dropDoc: any) => { + export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, onDropCompleted?: (e?: DragCompleteEvent) => unknown) { + const addAudioTag = (dropDoc: Doc) => { dropDoc && !dropDoc.author_date && (dropDoc.author_date = new DateField()); dropDoc instanceof Doc && CreateLinkToActiveAudio(() => dropDoc); return dropDoc; @@ -236,7 +245,7 @@ export namespace DragManager { await Promise.all( dragData.draggedDocuments.map(async d => !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart) - ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) + ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result as Doc) : docDragData.dropAction === dropActionType.embed ? Doc.BestEmbedding(d) : docDragData.dropAction === dropActionType.add @@ -249,7 +258,7 @@ export namespace DragManager { ) ) ).filter(d => d); - ![dropActionType.same, dropActionType.proto].includes(docDragData.dropAction as any) && + ![dropActionType.same, dropActionType.proto].includes(StrCast(docDragData.dropAction) as dropActionType) && docDragData.droppedDocuments // .filter(drop => !drop.dragOnlyWithinContainer || ['embed', 'copy'].includes(docDragData.dropAction as any)) .forEach((drop: Doc, i: number) => { @@ -376,9 +385,18 @@ export namespace DragManager { options?.dragComplete?.(complete); endDrag?.(); } - export function StartDrag(elesIn: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void, dragUndoName?: string) { - if (dragData.dropAction === 'none' || SnappingManager.ExploreMode) return; - DocDragData = dragData as DocumentDragData; + export function StartDrag( + elesIn: HTMLElement[], + dragData: DocumentDragData | LinkDragData | ColumnDragData | AnchorAnnoDragData, + downX: number, + downY: number, + options?: DragOptions, + finishDrag?: (dropData: DragCompleteEvent) => void, + dragUndoName?: string + ) { + if (SnappingManager.ExploreMode) return; + const docDragData = dragData instanceof DocumentDragData ? dragData : undefined; + DraggedDocs = docDragData?.draggedDocuments; const batch = UndoManager.StartBatch(dragUndoName ?? 'document drag'); const eles = elesIn.filter(e => e); SnappingManager.SetCanEmbed(dragData.canEmbed || false); @@ -437,8 +455,9 @@ export namespace DragManager { next && children.push(...Array.from(next.children)); if (next) { ['marker-start', 'marker-mid', 'marker-end'].forEach(field => { - if (next.localName.startsWith('path') && (next.attributes as any)[field]) { - next.setAttribute(field, (next.attributes as any)[field].value.replace('#', '#X')); + if (next.localName.startsWith('path')) { + const item = next.attributes.getNamedItem(field); + item && next.setAttribute(field, item.value.replace('#', '#X')); } }); if (next.localName.startsWith('marker')) { @@ -495,7 +514,7 @@ export namespace DragManager { .map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0)); } [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))] - .map(dele => (dele as any).style) + .map(dele => (dele as HTMLElement)?.style) .forEach(style => { style && (style.pointerEvents = 'none'); }); @@ -536,34 +555,35 @@ export namespace DragManager { const yFromBottom = elesCont.bottom - downY; let scrollAwaiter: Opt<NodeJS.Timeout>; - let startWindowDragTimer: any; + let startWindowDragTimer: NodeJS.Timeout | undefined; const moveHandler = (e: PointerEvent) => { e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop - if (dragData instanceof DocumentDragData) { - dragData.userDropAction = e.ctrlKey && e.altKey ? dropActionType.copy : e.shiftKey ? dropActionType.move : e.ctrlKey ? dropActionType.embed : dragData.defaultDropAction; - } - if (['lm_tab', 'lm_title_wrap', 'lm_tabs', 'lm_header'].includes(typeof (e.target as any).className === 'string' ? (e.target as any)?.className : '') && dragData.draggedDocuments.length === 1) { - if (!startWindowDragTimer) { - startWindowDragTimer = setTimeout(async () => { - startWindowDragTimer = undefined; - dragData.dropAction = dragData.userDropAction || 'same'; - AbortDrag(); - await finishDrag?.(new DragCompleteEvent(true, dragData)); - DragManager.StartWindowDrag?.(e, dragData.droppedDocuments, aborted => { - if (!aborted && (dragData.dropAction === 'move' || dragData.dropAction === 'same')) { - dragData.removeDocument?.(dragData.draggedDocuments[0]); - } - }); - }, 500); + if (docDragData) { + docDragData.userDropAction = e.ctrlKey && e.altKey ? dropActionType.copy : e.shiftKey ? dropActionType.move : e.ctrlKey ? dropActionType.embed : docDragData.defaultDropAction; + const targClassName = e.target instanceof HTMLElement && typeof e.target.className === 'string' ? e.target.className : ''; + if (['lm_tab', 'lm_title_wrap', 'lm_tabs', 'lm_header'].includes(targClassName) && docDragData.draggedDocuments.length === 1) { + if (!startWindowDragTimer) { + startWindowDragTimer = setTimeout(async () => { + startWindowDragTimer = undefined; + docDragData.dropAction = docDragData.userDropAction || dropActionType.same; + AbortDrag(); + await finishDrag?.(new DragCompleteEvent(true, docDragData)); + DragManager.StartWindowDrag?.(e, docDragData.droppedDocuments, aborted => { + if (!aborted && (docDragData?.dropAction === 'move' || docDragData?.dropAction === 'same')) { + docDragData.removeDocument?.(docDragData?.draggedDocuments[0]); + } + }); + }, 500); + } + } else { + clearTimeout(startWindowDragTimer); + startWindowDragTimer = undefined; } - } else { - clearTimeout(startWindowDragTimer); - startWindowDragTimer = undefined; } const target = document.elementFromPoint(e.x, e.y); - if (target && !Doc.UserDoc()._noAutoscroll && !options?.noAutoscroll && !dragData.draggedDocuments?.some((d: any) => d._freeform_noAutoPan)) { + if (target && !Doc.UserDoc()._noAutoscroll && !options?.noAutoscroll && !(docDragData?.draggedDocuments as Doc[])?.some(d => d._freeform_noAutoPan)) { const autoScrollHandler = () => { target.dispatchEvent( new CustomEvent<React.DragEvent>('dashDragMovePause', { @@ -587,7 +607,7 @@ export namespace DragManager { screenX: e.screenX, screenY: e.screenY, detail: e.detail, - view: e.view ? e.view : (new Window() as any), + view: { ...(e.view ?? new Window()), styleMedia: { type: '', matchMedium: () => false } }, // bcz: Ugh.. this looks wrong nativeEvent: new DragEvent('dashDragMovePause'), currentTarget: target, target: target, @@ -596,10 +616,10 @@ export namespace DragManager { defaultPrevented: true, eventPhase: e.eventPhase, isTrusted: true, - preventDefault: () => 'not implemented for this event' && false, - isDefaultPrevented: () => 'not implemented for this event' && false, - stopPropagation: () => 'not implemented for this event' && false, - isPropagationStopped: () => 'not implemented for this event' && false, + preventDefault: () => 'not implemented for this event', + isDefaultPrevented: () => false, + stopPropagation: () => 'not implemented for this event', + isPropagationStopped: () => false, persist: emptyFunction, timeStamp: e.timeStamp, type: 'dashDragMovePause', diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index 0314af06b..eb2011b77 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -26,9 +26,10 @@ function makeTemplate(doc: Doc, first: boolean = true): boolean { if (layoutDoc.layout instanceof Doc) { return true; // its already a template } - const layout = StrCast(layoutDoc.layout).match(/fieldKey={'[^']*'}/)![0]; - const fieldKey = layout.replace("fieldKey={'", '').replace(/'}$/, ''); - const docs = DocListCast(layoutDoc[fieldKey]); + const layout = StrCast(layoutDoc.layout).match(/fieldKey={'[^']*'}/)?.[0]; + const fieldKey = layout?.replace("fieldKey={'", '').replace(/'}$/, ''); + const docData = fieldKey ? layoutDoc[fieldKey] : undefined; + const docs = DocListCast(docData); let isTemplate = false; docs.forEach(d => { if (!StrCast(d.title).startsWith('-')) { @@ -40,7 +41,7 @@ function makeTemplate(doc: Doc, first: boolean = true): boolean { if (first && !docs.length) { // bcz: feels hacky : if the root level document has items, it's not a field template isTemplate = Doc.MakeMetadataFieldTemplate(doc, layoutDoc[DocData], true) || isTemplate; - } else if (layoutDoc[fieldKey] instanceof RichTextField || layoutDoc[fieldKey] instanceof ImageField) { + } else if (docData instanceof RichTextField || docData instanceof ImageField) { if (!StrCast(layoutDoc.title).startsWith('-')) { isTemplate = Doc.MakeMetadataFieldTemplate(layoutDoc, layoutDoc[DocData], true); } @@ -110,8 +111,8 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) { } ScriptingGlobals.add( // eslint-disable-next-line prefer-arrow-callback - function convertToButtons(dragData: any) { - convertDropDataToButtons(dragData as DragManager.DocumentDragData); + function convertToButtons(dragData: DragManager.DocumentDragData) { + convertDropDataToButtons(dragData); }, 'converts the dropped data to buttons', '(dragData: any)' diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index 5701a22c0..9d0817a06 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -1,8 +1,6 @@ -/* 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, computed, makeObservable, observable } from 'mobx'; +import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import Select from 'react-select'; @@ -31,7 +29,7 @@ export interface UserOptions { } @observer -export class GroupManager extends ObservableReactComponent<{}> { +export class GroupManager extends ObservableReactComponent<object> { // eslint-disable-next-line no-use-before-define static Instance: GroupManager; @observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not. @@ -44,7 +42,7 @@ export class GroupManager extends ObservableReactComponent<{}> { @observable private buttonColour: '#979797' | 'black' = '#979797'; @observable private groupSort: 'ascending' | 'descending' | 'none' = 'none'; - constructor(props: Readonly<{}>) { + constructor(props: Readonly<object>) { super(props); makeObservable(this); GroupManager.Instance = this; @@ -227,15 +225,6 @@ export class GroupManager extends ObservableReactComponent<{}> { } /** - * Handles changes in the users selected in the "Select users" dropdown. - * @param selectedOptions - */ - @action - handleChange = (selectedOptions: any) => { - this.selectedUsers = selectedOptions as UserOptions[]; - }; - - /** * Creates the group when the enter key has been pressed (when in the input). * @param e */ @@ -309,7 +298,6 @@ export class GroupManager extends ObservableReactComponent<{}> { <input ref={this.inputRef} onKeyDown={this.handleKeyDown} - // eslint-disable-next-line jsx-a11y/no-autofocus autoFocus type="text" placeholder="Group name" @@ -323,7 +311,9 @@ export class GroupManager extends ObservableReactComponent<{}> { className="select-users" isMulti options={this.options} - onChange={this.handleChange} + onChange={selectedOptions => { + runInAction(() => (this.selectedUsers = Array.from(selectedOptions))); + }} placeholder="Select users" value={this.selectedUsers} closeMenuOnSelect={false} diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx index da9e1aa28..88d73d742 100644 --- a/src/client/util/GroupMemberView.tsx +++ b/src/client/util/GroupMemberView.tsx @@ -1,5 +1,3 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button, IconButton, Size, Type } from 'browndash-components'; import { action, observable } from 'mobx'; diff --git a/src/client/util/History.ts b/src/client/util/History.ts index 52d0223d5..0d0c056a4 100644 --- a/src/client/util/History.ts +++ b/src/client/util/History.ts @@ -85,7 +85,7 @@ export namespace HistoryUtil { const parsers: { [type: string]: (pathname: string[], opts: qs.ParsedQuery) => ParsedUrl | undefined } = {}; const stringifiers: { [type: string]: (state: ParsedUrl) => string } = {}; - type ParserValue = true | 'none' | 'json' | ((value: string) => any); + type ParserValue = true | 'none' | 'json' | ((value: string) => string | null | (string | null)[]); type Parser = { [key: string]: ParserValue; @@ -106,7 +106,7 @@ export namespace HistoryUtil { return value; } parsers[type] = (pathname, opts) => { - const current: any = { type }; + const current: DocUrl & { [key: string]: null | (string | null)[] | string } = { type: 'doc', docId: '' }; for (const required in requiredFields) { if (!(required in opts)) { return undefined; @@ -148,7 +148,7 @@ export namespace HistoryUtil { path = customStringifier(state, path); } const queryObj = OmitKeys(state, keys).extract; - const query: any = {}; + const query: { [key: string]: string | null } = {}; Object.keys(queryObj).forEach(key => { query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key]); }); diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts index 8d4eefa7e..266e05f08 100644 --- a/src/client/util/Import & Export/ImageUtils.ts +++ b/src/client/util/Import & Export/ImageUtils.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-namespace */ import { ClientUtils } from '../../../ClientUtils'; import { Doc } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx index db1e3d6cd..63dedf820 100644 --- a/src/client/util/Import & Export/ImportMetadataEntry.tsx +++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx @@ -1,5 +1,3 @@ -/* 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'; diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index a07550e09..4231c2ca8 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -1,3 +1,4 @@ +import { Property } from 'csstype'; import * as React from 'react'; import { Utils } from '../../Utils'; import { Gestures } from '../../pen-gestures/GestureTypes'; @@ -11,7 +12,7 @@ export namespace InteractionUtils { const ERASER_BUTTON = 5; - export function makePolygon(shape: string, points: { X: number; Y: number }[]) { + export function makePolygon(shape: Gestures, points: { X: number; Y: number }[]) { // if arrow/line/circle, the two end points should be the starting and the ending point let left = points[0].X; let top = points[0].Y; @@ -19,7 +20,7 @@ export namespace InteractionUtils { let bottom = points[1].Y; if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y + 1 === points[0].Y) { // pointer is up (first and last points are the same) - if (![Gestures.Arrow, Gestures.Line, Gestures.Circle].includes(shape as any as Gestures)) { + if (![Gestures.Arrow, Gestures.Line, Gestures.Circle].includes(shape)) { // otherwise take max and min const xs = points.map(p => p.X); const ys = points.map(p => p.Y); @@ -98,8 +99,8 @@ export namespace InteractionUtils { color: string, width: number, strokeWidth: number, - lineJoin: string, - strokeLineCap: string, + lineJoin: Property.StrokeLinejoin, + strokeLineCap: Property.StrokeLinecap, bezier: string, fill: string, arrowStart: string, @@ -108,8 +109,8 @@ export namespace InteractionUtils { dash: string | undefined, scalexIn: number, scaleyIn: number, - shape: string, - pevents: string, + shape: Gestures, + pevents: Property.PointerEvents, opacity: number, nodefs: boolean, downHdlr?: (e: React.PointerEvent) => void, @@ -154,7 +155,7 @@ export namespace InteractionUtils { <marker id={`arrowStart${defGuid}`} markerUnits="userSpaceOnUse" orient="auto" overflow="visible" refX={markerStrokeWidth * (arrowLengthFactor - arrowNotchFactor)} refY={0} markerWidth="10" markerHeight="7"> <polygon style={{ stroke: color }} - strokeLinejoin={lineJoin as any} + strokeLinejoin={lineJoin as 'inherit' | 'round' | 'bevel' | 'miter'} strokeWidth={(markerStrokeWidth * 2) / 3} points={`${arrowLengthFactor * markerStrokeWidth} ${-markerStrokeWidth * arrowWidthFactor}, ${markerStrokeWidth * (arrowLengthFactor - arrowNotchFactor)} 0, ${arrowLengthFactor * markerStrokeWidth} ${ markerStrokeWidth * arrowWidthFactor @@ -166,7 +167,7 @@ export namespace InteractionUtils { <marker id={`arrowEnd${defGuid}`} markerUnits="userSpaceOnUse" orient="auto" overflow="visible" refX={markerStrokeWidth * arrowNotchFactor} refY={0} markerWidth="10" markerHeight="7"> <polygon style={{ stroke: color }} - strokeLinejoin={lineJoin as any} + strokeLinejoin={lineJoin as 'inherit' | 'miter' | 'round' | 'bevel'} strokeWidth={(markerStrokeWidth * 2) / 3} points={`0 ${-markerStrokeWidth * arrowWidthFactor}, ${markerStrokeWidth * arrowNotchFactor} 0, 0 ${markerStrokeWidth * arrowWidthFactor}, ${arrowLengthFactor * markerStrokeWidth} 0`} /> @@ -184,10 +185,10 @@ export namespace InteractionUtils { filter: mask ? `url(#mask${defGuid})` : undefined, opacity: 1.0, // opacity: strokeWidth !== width ? 0.5 : undefined, - pointerEvents: (pevents as any) === 'all' ? 'visiblepainted' : (pevents as any), + pointerEvents: pevents === 'all' ? 'visiblePainted' : pevents, stroke: color ?? 'rgb(0, 0, 0)', strokeWidth, - strokeLinecap: strokeLineCap as any, + strokeLinecap: strokeLineCap, strokeDasharray: dashArray, transition: 'inherit', }} diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index 9a0edcfec..0a3a0ba49 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -50,7 +50,7 @@ export class LinkFollower { const backLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_2 as Doc)); // link docs where 'sourceDoc' is link_anchor_2 const fwdLinkWithoutTargetView = fwdLinks.find(l => !getView(DocCast(l.link_anchor_2))); const backLinkWithoutTargetView = backLinks.find(l => !getView(DocCast(l.link_anchor_1))); - const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView ?? backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView; + const linkWithoutTargetDoc = traverseBacklink === undefined ? (fwdLinkWithoutTargetView ?? backLinkWithoutTargetView) : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView; const linkDocList = linkWithoutTargetDoc && !sourceDoc.followAllLinks ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? fwdLinks.concat(backLinks) : traverseBacklink ? backLinks : fwdLinks; const followLinks = sourceDoc.followLinkToggle || sourceDoc.followAllLinks ? linkDocList : linkDocList.slice(0, 1); let count = 0; @@ -82,7 +82,7 @@ export class LinkFollower { willZoomCentered: BoolCast(srcAnchor.followLinkZoom, false), zoomTime: NumCast(srcAnchor.followLinkTransitionTime, 500), zoomScale: Cast(srcAnchor.followLinkZoomScale, 'number', null), - easeFunc: StrCast(srcAnchor.followLinkEase, 'ease') as any, + easeFunc: StrCast(srcAnchor.followLinkEase, 'ease') as 'ease' | 'linear', openLocation: StrCast(srcAnchor.followLinkLocation, OpenWhere.lightbox) as OpenWhere, effect: srcAnchor, zoomTextSelections: BoolCast(srcAnchor.followLinkZoomText), diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 56d5dce4e..e11482572 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -31,13 +31,13 @@ export class LinkManager { @observable public currentLink: Opt<Doc> = undefined; @observable public currentLinkAnchor: Opt<Doc> = undefined; public static get Instance(): LinkManager { - return Doc.UserDoc() ? LinkManager._instance ?? new LinkManager() : (undefined as any as LinkManager); + return Doc.UserDoc() ? (LinkManager._instance ?? new LinkManager()) : (undefined as unknown as LinkManager); } public static Links(doc: Doc | undefined) { return doc ? LinkManager.Instance.getAllRelatedLinks(doc) : []; } - public addLinkDB = async (linkDb: any) => { + public addLinkDB = async (linkDb: Doc) => { await Promise.all( ((await DocListCastAsync(linkDb.data)) ?? []).map(link => // makes sure link anchors are loaded to avoid incremental updates to computedFns in LinkManager @@ -95,35 +95,24 @@ export class LinkManager { const watchUserLinkDB = (userLinkDBDoc: Doc) => { const toRealField = (field: FieldType) => (field instanceof ProxyField ? field.value : field); // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields if (userLinkDBDoc.data) { + // observe pushes/splices on a user link DB 'data' field (should only happen for local changes) observe( - userLinkDBDoc.data, + userLinkDBDoc.data as unknown as Doc[], change => { - // observe pushes/splices on a user link DB 'data' field (should only happen for local changes) - switch (change.type as any) { + switch (change.type) { case 'splice': - (change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link))); - (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link))); + change.added.forEach(link => addLinkToDoc(toRealField(link))); + change.removed.forEach(link => remLinkFromDoc(toRealField(link))); break; - case 'update': // let oldValue = change.oldValue; - default: - } - }, - true - ); - observe( - userLinkDBDoc, - 'data', // obsever when a new array of links is assigned as the link DB 'data' field (should happen whenever a remote user adds/removes a link) - change => { - switch (change.type as any) { case 'update': - Promise.all([...((change.oldValue as any as Doc[]) || []), ...((change.newValue as any as Doc[]) || [])]).then(doclist => { - const oldDocs = doclist.slice(0, ((change.oldValue as any as Doc[]) || []).length); - const newDocs = doclist.slice(((change.oldValue as any as Doc[]) || []).length, doclist.length); + Promise.all([...((change.oldValue as unknown as Doc[]) || []), ...((change.newValue as unknown as Doc[]) || [])]).then(doclist => { + const oldDocs = doclist.slice(0, ((change.oldValue as unknown as Doc[]) || []).length); + const newDocs = doclist.slice(((change.oldValue as unknown as Doc[]) || []).length, doclist.length); const added = newDocs?.filter(link => !(oldDocs || []).includes(link)); const removed = oldDocs?.filter(link => !(newDocs || []).includes(link)); - added?.forEach((link: any) => addLinkToDoc(toRealField(link))); - removed?.forEach((link: any) => remLinkFromDoc(toRealField(link))); + added?.forEach(link => addLinkToDoc(toRealField(link))); + removed?.forEach(link => remLinkFromDoc(toRealField(link))); }); break; default: @@ -136,9 +125,9 @@ export class LinkManager { observe( this.userLinkDBs, change => { - switch (change.type as any) { + switch (change.type) { case 'splice': - (change as any).added.forEach(watchUserLinkDB); + change.added.forEach(watchUserLinkDB); break; case 'update': // let oldValue = change.oldValue; default: @@ -188,7 +177,7 @@ export class LinkManager { return []; } - const dirLinks = Array.from(anchor[DocData][DirectLinks]).filter(l => Doc.GetProto(anchor) === anchor[DocData] || ['1', '2'].includes(LinkManager.anchorIndex(l, anchor) as any)); + const dirLinks = Array.from(anchor[DocData][DirectLinks]).filter(l => Doc.GetProto(anchor) === anchor[DocData] || ['1', '2'].includes(LinkManager.anchorIndex(l, anchor) as '0' | '1' | '2')); const anchorRoot = DocCast(anchor.rootDocument, anchor); // template Doc fields store annotations on the topmost root of a template (not on themselves since the template layout items are only for layout) const annos = DocListCast(anchorRoot[Doc.LayoutFieldKey(anchor) + '_annotations']); return Array.from( @@ -283,7 +272,7 @@ export function UPDATE_SERVER_CACHE() { ScriptingGlobals.add( // eslint-disable-next-line prefer-arrow-callback - function links(doc: any) { + function links(doc: Doc) { return new List(LinkManager.Links(doc)); }, 'returns all the links to the document or its annotations', diff --git a/src/client/util/ProsemirrorCopy/prompt.js b/src/client/util/ProsemirrorCopy/prompt.js deleted file mode 100644 index b9068195f..000000000 --- a/src/client/util/ProsemirrorCopy/prompt.js +++ /dev/null @@ -1,179 +0,0 @@ -const prefix = "ProseMirror-prompt" - -export function openPrompt(options) { - let wrapper = document.body.appendChild(document.createElement("div")) - wrapper.className = prefix - wrapper.style.zIndex = 1000; - wrapper.style.width = 250; - wrapper.style.textAlign = "center"; - - let mouseOutside = e => { if (!wrapper.contains(e.target)) close() } - setTimeout(() => window.addEventListener("mousedown", mouseOutside), 50) - let close = () => { - window.removeEventListener("mousedown", mouseOutside) - if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper) - } - - let domFields = [] - for (let name in options.fields) domFields.push(options.fields[name].render()) - - let submitButton = document.createElement("button") - submitButton.type = "submit" - submitButton.className = prefix + "-submit" - submitButton.textContent = "OK" - let cancelButton = document.createElement("button") - cancelButton.type = "button" - cancelButton.className = prefix + "-cancel" - cancelButton.textContent = "Cancel" - cancelButton.addEventListener("click", close) - - let form = wrapper.appendChild(document.createElement("form")) - let title = document.createElement("h5") - title.style.marginBottom = 15 - title.style.marginTop = 10 - if (options.title) form.appendChild(title).textContent = options.title - domFields.forEach(field => { - form.appendChild(document.createElement("div")).appendChild(field) - }) - let b = document.createElement("div"); - b.style.marginTop = 15; - let buttons = form.appendChild(b) - // buttons.className = prefix + "-buttons" - buttons.appendChild(submitButton) - buttons.appendChild(document.createTextNode(" ")) - buttons.appendChild(cancelButton) - - let box = wrapper.getBoundingClientRect() - wrapper.style.top = options.flyout_top + "px" - wrapper.style.left = options.flyout_left + "px" - - let submit = () => { - let params = getValues(options.fields, domFields) - if (params) { - close() - options.callback(params) - } - } - - form.addEventListener("submit", e => { - e.preventDefault() - submit() - }) - - form.addEventListener("keydown", e => { - if (e.keyCode == 27) { - e.preventDefault() - close() - } else if (e.keyCode == 13 && !(e.ctrlKey || e.metaKey || e.shiftKey)) { - e.preventDefault() - submit() - } else if (e.keyCode == 9) { - window.setTimeout(() => { - if (!wrapper.contains(document.activeElement)) close() - }, 500) - } - }) - - let input = form.elements[0] - if (input) input.focus() -} - -function getValues(fields, domFields) { - let result = Object.create(null), i = 0 - for (let name in fields) { - let field = fields[name], dom = domFields[i++] - let value = field.read(dom), bad = field.validate(value) - if (bad) { - reportInvalid(dom, bad) - return null - } - result[name] = field.clean(value) - } - return result -} - -function reportInvalid(dom, message) { - // FIXME this is awful and needs a lot more work - let parent = dom.parentNode - let msg = parent.appendChild(document.createElement("div")) - msg.style.left = (dom.offsetLeft + dom.offsetWidth + 2) + "px" - msg.style.top = (dom.offsetTop - 5) + "px" - msg.className = "ProseMirror-invalid" - msg.textContent = message - setTimeout(() => parent.removeChild(msg), 1500) -} - -// ::- The type of field that `FieldPrompt` expects to be passed to it. -export class Field { - // :: (Object) - // Create a field with the given options. Options support by all - // field types are: - // - // **`value`**`: ?any` - // : The starting value for the field. - // - // **`label`**`: string` - // : The label for the field. - // - // **`required`**`: ?bool` - // : Whether the field is required. - // - // **`validate`**`: ?(any) → ?string` - // : A function to validate the given value. Should return an - // error message if it is not valid. - constructor(options) { this.options = options } - - // render:: (state: EditorState, props: Object) → dom.Node - // Render the field to the DOM. Should be implemented by all subclasses. - - // :: (dom.Node) → any - // Read the field's value from its DOM node. - read(dom) { return dom.value } - - // :: (any) → ?string - // A field-type-specific validation function. - validateType(_value) { } - - validate(value) { - if (!value && this.options.required) - return "Required field" - return this.validateType(value) || (this.options.validate && this.options.validate(value)) - } - - clean(value) { - return this.options.clean ? this.options.clean(value) : value - } -} - -// ::- A field class for single-line text fields. -export class TextField extends Field { - render() { - let input = document.createElement("input") - input.type = "text" - input.placeholder = this.options.label - input.value = this.options.value || "" - input.autocomplete = "off" - input.style.marginBottom = 4 - input.style.border = "1px solid black" - input.style.padding = "4px 4px" - return input - } -} - - -// ::- A field class for dropdown fields based on a plain `<select>` -// tag. Expects an option `options`, which should be an array of -// `{value: string, label: string}` objects, or a function taking a -// `ProseMirror` instance and returning such an array. -export class SelectField extends Field { - render() { - let select = document.createElement("select") - this.options.options.forEach(o => { - let opt = select.appendChild(document.createElement("option")) - opt.value = o.value - opt.selected = o.value == this.options.value - opt.label = o.label - }) - return select - } -} diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx index a07ad2047..a01b64eda 100644 --- a/src/client/util/RTFMarkup.tsx +++ b/src/client/util/RTFMarkup.tsx @@ -5,7 +5,7 @@ import { MainViewModal } from '../views/MainViewModal'; import { SnappingManager } from './SnappingManager'; @observer -export class RTFMarkup extends React.Component<{}> { +export class RTFMarkup extends React.Component<object> { // eslint-disable-next-line no-use-before-define static Instance: RTFMarkup; @observable private isOpen = false; // whether the SharingManager modal is open or not @@ -14,7 +14,7 @@ export class RTFMarkup extends React.Component<{}> { this.isOpen = status; }); - constructor(props: {}) { + constructor(props: object) { super(props); makeObservable(this); RTFMarkup.Instance = this; diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts index c5afe549c..62a09a8bc 100644 --- a/src/client/util/ReplayMovements.ts +++ b/src/client/util/ReplayMovements.ts @@ -7,11 +7,12 @@ import { SnappingManager } from './SnappingManager'; import { Movement, Presentation } from './TrackMovements'; import { ViewBoxInterface } from '../views/ViewBoxInterface'; import { StrCast } from '../../fields/Types'; +import { FieldViewProps } from '../views/nodes/FieldView'; export class ReplayMovements { private timers: NodeJS.Timeout[] | null; private videoBoxDisposeFunc: IReactionDisposer | null; - private videoBox: ViewBoxInterface<any> | null; + private videoBox: ViewBoxInterface<FieldViewProps> | null; private isPlaying: boolean; // create static instance and getter for global use @@ -62,7 +63,7 @@ export class ReplayMovements { this.timers?.map(timer => clearTimeout(timer)); }; - setVideoBox = async (videoBox: ViewBoxInterface<any>) => { + setVideoBox = async (videoBox: ViewBoxInterface<FieldViewProps>) => { if (this.videoBox !== null) { console.warn('setVideoBox on already videoBox'); } @@ -147,7 +148,7 @@ export class ReplayMovements { // generate a set of all unique docIds const docIdtoFirstMove = new Map<Doc, Movement>(); movements.forEach(move => { - if (!docIdtoFirstMove.has(move.doc)) docIdtoFirstMove.set(move.doc, move); + if (!docIdtoFirstMove.has(move.doc as Doc)) docIdtoFirstMove.set(move.doc as Doc, move); }); return docIdtoFirstMove; }; @@ -175,8 +176,8 @@ export class ReplayMovements { const handleFirstMovements = () => { // if the first movement is a closed tab, open it const firstMovement = filteredMovements[0]; - const isClosed = this.getCollectionFFView(firstMovement.doc) === undefined; - if (isClosed) this.openTab(firstMovement.doc); + const isClosed = this.getCollectionFFView(firstMovement.doc as Doc) === undefined; + if (isClosed) this.openTab(firstMovement.doc as Doc); // for the open tabs, set it to the first move const docIdtoFirstMove = this.getFirstMovements(filteredMovements); @@ -192,12 +193,12 @@ export class ReplayMovements { const timeDiff = movement.time - timeViewed * 1000; return setTimeout(() => { - const collectionFFView = this.getCollectionFFView(movement.doc); + const collectionFFView = this.getCollectionFFView(movement.doc as Doc); if (collectionFFView) { this.zoomAndPan(movement, collectionFFView); } else { // tab wasn't open - open it and play the movement - const openedColFFView = this.openTab(movement.doc); + const openedColFFView = this.openTab(movement.doc as Doc); openedColFFView && this.zoomAndPan(movement, openedColFFView); } diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 6948469cc..c63d3d7cb 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -1,11 +1,7 @@ -/* eslint-disable import/no-unresolved */ -/* eslint-disable import/no-webpack-loader-syntax */ // export const ts = (window as any).ts; -// // @ts-ignore // import * as typescriptlib from '!!raw-loader!../../../node_modules/typescript/lib/lib.d.ts' // import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts' -// eslint-disable-next-line node/no-unpublished-import -import * as typescriptlib from '!!raw-loader!./type_decls.d'; +import typescriptlib from 'type_decls.d'; import * as ts from 'typescript'; import { Doc, FieldType } from '../../fields/Doc'; import { RefField } from '../../fields/RefField'; @@ -16,13 +12,13 @@ export { ts }; export interface ScriptSuccess { success: true; - result: any; + result: unknown; } export interface ScriptError { success: false; - error: any; - result: any; + error: unknown; + result: unknown; } export type ScriptResult = ScriptSuccess | ScriptError; @@ -34,12 +30,12 @@ export interface CompiledScript { readonly originalScript: string; // eslint-disable-next-line no-use-before-define readonly options: Readonly<ScriptOptions>; - run(args?: { [name: string]: any }, onError?: (res: any) => void, errorVal?: any): ScriptResult; + run(args?: { [name: string]: unknown }, onError?: (res: string) => void, errorVal?: unknown): ScriptResult; } export interface CompileError { compiled: false; - errors: any[]; + errors: ts.Diagnostic[]; } export type CompileResult = CompiledScript | CompileError; @@ -51,7 +47,7 @@ export function isCompileError(toBeDetermined: CompileResult): toBeDetermined is } // eslint-disable-next-line no-use-before-define -function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult { +function Run(script: string | undefined, customParams: string[], diagnostics: ts.Diagnostic[], originalScript: string, options: ScriptOptions): CompileResult { const errors = diagnostics.filter(diag => diag.category === ts.DiagnosticCategory.Error); if ((options.typecheck !== false && errors.length) || !script) { return { compiled: false, errors }; @@ -74,8 +70,8 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an if (!compiledFunction) return { compiled: false, errors }; const { capturedVariables = {} } = options; // eslint-disable-next-line default-param-last - const run = (args: { [name: string]: any } = {}, onError?: (e: any) => void, errorVal?: any): ScriptResult => { - const argsArray: any[] = []; + const run = (args: { [name: string]: unknown } = {}, onError?: (e: string) => void, errorVal?: ts.Diagnostic): ScriptResult => { + const argsArray: unknown[] = []; // eslint-disable-next-line no-restricted-syntax for (const name of customParams) { if (name !== 'this') { @@ -94,7 +90,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an return { success: true, result }; } catch (error) { batch?.end(); - onError?.(script + ' ' + error); + onError?.(script + ' ' + (error as string).toString()); return { success: false, error, result: errorVal }; } }; @@ -111,7 +107,7 @@ class ScriptingCompilerHost { files: File[] = []; // getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined): ts.SourceFile | undefined { - getSourceFile(fileName: string, languageVersion: any /* , onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined */): any | undefined { + getSourceFile(fileName: string, languageVersion: ts.ScriptTarget | ts.CreateSourceFileOptions /* , onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined */): ts.SourceFile | undefined { const contents = this.readFile(fileName); if (contents !== undefined) { return ts.createSourceFile(fileName, contents, languageVersion, true); @@ -165,18 +161,19 @@ export interface ScriptOptions { requiredType?: string; // does function required a typed return value addReturn?: boolean; // does the compiler automatically add a return statement params?: { [name: string]: string }; // list of function parameters and their types - capturedVariables?: { [name: string]: Doc | number | string | boolean }; // list of captured variables + capturedVariables?: { [name: string]: Doc | number | string | boolean | undefined }; // list of captured variables typecheck?: boolean; // should the compiler perform typechecking editable?: boolean; // can the script edit Docs traverser?: TraverserParam; transformer?: Transformer; // does the editor display a text label by each document that can be used as a captured document reference - globals?: { [name: string]: any }; + globals?: { [name: string]: unknown }; } // function forEachNode(node:ts.Node, fn:(node:any) => void); function forEachNode(node: ts.Node, onEnter: Traverser, onExit?: Traverser, indentation = '') { return ( onEnter(node, indentation) || + // eslint-disable-next-line @typescript-eslint/no-explicit-any ts.forEachChild(node, (n: any) => { forEachNode(n, onEnter, onExit, indentation + ' '); }) || @@ -187,8 +184,9 @@ function forEachNode(node: ts.Node, onEnter: Traverser, onExit?: Traverser, inde export function CompileScript(script: string, options: ScriptOptions = {}): CompileResult { const captured = options.capturedVariables ?? {}; const signature = Object.keys(captured).reduce((p, v) => { - const formatCapture = (obj: any) => `${v}=${obj instanceof RefField ? 'XXX' : obj.toString()}`; - if (captured[v] instanceof Array) return p + (captured[v] as any).map(formatCapture); + const formatCapture = (obj: FieldType | undefined) => `${v}=${obj instanceof RefField ? 'XXX' : obj?.toString()}`; + const captureVal = captured[v]; + if (captureVal instanceof Array) return p + captureVal.map(formatCapture); return p + formatCapture(captured[v]); }, ''); const found = ScriptField.GetScriptFieldCache(script + ':' + signature); @@ -250,7 +248,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp const funcScript = `(function(${paramString})${reqTypes} { ${body} })`; host.writeFile('file.ts', funcScript); - if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib.default); + if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib); const program = ts.createProgram(['file.ts'], {}, host); const testResult = program.emit(); const outputText = host.readFile('file.js'); diff --git a/src/client/util/ScriptingGlobals.ts b/src/client/util/ScriptingGlobals.ts index ac524394a..444e8fc0a 100644 --- a/src/client/util/ScriptingGlobals.ts +++ b/src/client/util/ScriptingGlobals.ts @@ -2,23 +2,22 @@ import ts from 'typescript'; export { ts }; -const _scriptingGlobals: { [name: string]: any } = {}; -const _scriptingDescriptions: { [name: string]: any } = {}; -const _scriptingParams: { [name: string]: any } = {}; -// eslint-disable-next-line import/no-mutable-exports -export let scriptingGlobals: { [name: string]: any } = _scriptingGlobals; +const _scriptingGlobals: { [name: string]: unknown } = {}; +const _scriptingDescriptions: { [name: string]: string } = {}; +const _scriptingParams: { [name: string]: string } = {}; +export let scriptingGlobals: { [name: string]: unknown } = _scriptingGlobals; + export namespace ScriptingGlobals { export function getGlobals() { return Object.keys(_scriptingGlobals); } // prettier-ignore export function getGlobalObj() { return _scriptingGlobals; } // prettier-ignore export function getDescriptions() { return _scriptingDescriptions; } // prettier-ignore export function getParameters() { return _scriptingParams; } // prettier-ignore - export function add(global: { name: string }): void; - export function add(name: string, global: any): void; - export function add(global: { name: string }, decription?: string, params?: string): void; - export function add(first: any, second?: any, third?: string) { - let n: any; - let obj: any; + export function add(name: string, namespace_func_or_object: unknown): void; + export function add(func: { name: string }, description?: string, params?: string): void; + export function add(first: string | { name: string }, second?: unknown, params?: string): void { + let n: string = ''; + let obj: unknown; if (second !== undefined) { if (typeof first === 'string') { @@ -27,32 +26,32 @@ export namespace ScriptingGlobals { } else { obj = first; n = first.name; - _scriptingDescriptions[n] = second; - if (third !== undefined) { - _scriptingParams[n] = third; + _scriptingDescriptions[n] = second as string; + if (params !== undefined) { + _scriptingParams[n] = params; } } - } else if (first && typeof first.name === 'string') { + } else if (first instanceof Object && 'name' in first && typeof first.name === 'string') { n = first.name; obj = first; } else { throw new Error('Must either register an object with a name, or give a name and an object'); } if (n === undefined || n === 'undefined') { - return false; + return; // false; } // eslint-disable-next-line no-prototype-builtins if (_scriptingGlobals.hasOwnProperty(n)) { throw new Error(`Global with name ${n} is already registered, choose another name`); } _scriptingGlobals[n] = obj; - return true; + return; // true; } - export function makeMutableGlobalsCopy(globals?: { [name: string]: any }) { + export function makeMutableGlobalsCopy(globals?: { [name: string]: unknown }) { return { ..._scriptingGlobals, ...(globals || {}) }; } - export function setScriptingGlobals(globals: { [key: string]: any }) { + export function setScriptingGlobals(globals: { [key: string]: unknown }) { scriptingGlobals = globals; } @@ -75,11 +74,12 @@ export namespace ScriptingGlobals { } // const types = Object.keys(ts.SyntaxKind).map(kind => ts.SyntaxKind[kind]); - export function printNodeType(node: any, indentation = '') { + export function printNodeType(node: ts.Node, indentation = '') { console.log(indentation + ts.SyntaxKind[node.kind]); } } -export function scriptingGlobal(constructor: { new (...args: any[]): any }) { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function scriptingGlobal(constructor: { new (...args: any[]): unknown }) { ScriptingGlobals.add(constructor); } diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 609fedfa9..733eae5f4 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -22,6 +22,7 @@ export namespace SearchUtil { const results = new ObservableMap<Doc, string[]>(); if (collectionDoc) { const docs = DocListCast(collectionDoc[Doc.LayoutFieldKey(collectionDoc)]); + // eslint-disable-next-line @typescript-eslint/ban-types const docIDs: String[] = []; SearchUtil.foreachRecursiveDoc(docs, (depth: number, doc: Doc) => { const dtype = StrCast(doc.type) as DocumentType; diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 0b942116c..1ab84421c 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -115,7 +115,7 @@ ScriptingGlobals.add(function redo() { return UndoManager.Redo(); }); // eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) { +ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: Doc[]) { const docs = SelectionManager.Docs().filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null))); return docs.length ? new List(docs) : prevValue; }); diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts index d9d22437c..ccb02fb79 100644 --- a/src/client/util/SerializationHelper.ts +++ b/src/client/util/SerializationHelper.ts @@ -1,14 +1,15 @@ import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema } from 'serializr'; +import Context from 'serializr/lib/core/Context'; // import { Field } from '../../fields/Doc'; let serializing = 0; -export function afterDocDeserialize(cb: (err: any, val: any) => void, err: any, newValue: any) { +export function afterDocDeserialize(cb: (err: unknown, val: unknown) => void, err: unknown, newValue: unknown) { serializing++; cb(err, newValue); serializing--; } -const serializationTypes: { [name: string]: { ctor: { new (): any }; afterDeserialize?: (obj: any) => void | Promise<any> } } = {}; +const serializationTypes: { [name: string]: { ctor: { new (): unknown }; afterDeserialize?: (obj: unknown) => void | Promise<unknown> } } = {}; const reverseMap: { [ctor: string]: string } = {}; export namespace SerializationHelper { @@ -16,7 +17,7 @@ export namespace SerializationHelper { return serializing > 0; } - export function Serialize(obj: any /* Field */): any { + export function Serialize(obj: unknown /* Field */): unknown { if (obj === undefined || obj === null) { return null; } @@ -37,7 +38,7 @@ export namespace SerializationHelper { return json; } - export async function Deserialize(obj: any): Promise<any> { + export async function Deserialize(obj: unknown): Promise<unknown> { if (obj === undefined || obj === null) { return undefined; } @@ -46,16 +47,17 @@ export namespace SerializationHelper { return obj; } - if (!obj.__type) { - console.warn("No property 'type' found in JSON."); + const objtype = '__type' in obj ? (obj.__type as string) : undefined; + if (!objtype) { + console.warn(`No property ${objtype} found in JSON.`); return undefined; } - if (!(obj.__type in serializationTypes)) { - throw Error(`type '${obj.__type}' not registered. Make sure you register it using a @Deserializable decorator`); + if (!(objtype in serializationTypes)) { + throw Error(`type '${objtype}' not registered. Make sure you register it using a @Deserializable decorator`); } - const type = serializationTypes[obj.__type]; + const type = serializationTypes[objtype]; const value = await new Promise(res => { deserialize(type.ctor, obj, (err, result) => res(result)); }); @@ -65,11 +67,12 @@ export namespace SerializationHelper { } } -export function Deserializable(classNameForSerializer: string, afterDeserialize?: (obj: any) => void | Promise<any>, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void { - function addToMap(className: string, Ctor: { new (...args: any[]): any }) { - const schema = getDefaultModelSchema(Ctor) as any; - if (schema.targetClass !== Ctor || constructorArgs) { - setDefaultModelSchema(Ctor, { ...schema, factory: (context: any) => new Ctor(...(constructorArgs ?? []).map(arg => context.json[arg])) }); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function Deserializable(classNameForSerializer: string, afterDeserialize?: (obj: unknown) => void | Promise<unknown>, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void { + function addToMap(className: string, Ctor: { new (...args: unknown[]): unknown }) { + const schema = getDefaultModelSchema(Ctor); + if (schema && (schema.targetClass !== Ctor || constructorArgs)) { + setDefaultModelSchema(Ctor, { ...schema, factory: (context: Context) => new Ctor(...(constructorArgs ?? []).map(arg => context.json[arg])) }); } if (!(className in serializationTypes)) { serializationTypes[className] = { ctor: Ctor, afterDeserialize }; @@ -78,12 +81,12 @@ export function Deserializable(classNameForSerializer: string, afterDeserialize? throw new Error(`Name ${className} has already been registered as deserializable`); } } - return (ctor: { new (...args: any[]): any }) => addToMap(classNameForSerializer, ctor); + return (ctor: { new (...args: unknown[]): unknown }) => addToMap(classNameForSerializer, ctor); } export function autoObject(): PropSchema { return custom( s => SerializationHelper.Serialize(s), - (json: any, context: any, oldValue: any, cb: (err: any, result: any) => void) => SerializationHelper.Deserialize(json).then(res => cb(null, res)) + (json: object, context: Context, oldValue: unknown, cb: (err: unknown, result: unknown) => void) => SerializationHelper.Deserialize(json).then(res => cb(null, res)) ); } diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx index 57363663d..11db5ee5e 100644 --- a/src/client/util/ServerStats.tsx +++ b/src/client/util/ServerStats.tsx @@ -6,18 +6,29 @@ import './SharingManager.scss'; import { PingManager } from './PingManager'; import { SettingsManager } from './SettingsManager'; +/** + * NOTE: this must be kept in synch with UserStats definition in server's DashStats.ts file + * UserStats holds the stats associated with a particular user. + */ +interface UserStats { + socketId: string; + username: string; + time: string; + operations: number; + rate: number; +} @observer -export class ServerStats extends React.Component<{}> { +export class ServerStats extends React.Component<object> { // 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; + @observable _stats: { socketMap: UserStats[]; currentConnections: number } | undefined = undefined; // private get linkVisible() { // return this.targetDoc ? this.targetDoc['acl_' + PublicKey] !== SharingPermissions.None : false; // } - constructor(props: {}) { + constructor(props: object) { super(props); makeObservable(this); ServerStats.Instance = this; @@ -41,7 +52,7 @@ export class ServerStats extends React.Component<{}> { <br /> <span>Active users:{this._stats?.socketMap.length}</span> - {this._stats?.socketMap.map((user: any) => <p>{user.username}</p>)} + {this._stats?.socketMap.map(user => <p key={user.username}>{user.username}</p>)} </div> </div> ); diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 278931cdd..fde8869e3 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -1,5 +1,3 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button, ColorPicker, Dropdown, DropdownType, EditableText, Group, NumberDropdown, Size, Toggle, ToggleType, Type } from 'browndash-components'; import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; @@ -234,6 +232,17 @@ export class SettingsManager extends React.Component<object> { color={SettingsManager.userColor} /> <Toggle + formLabel="Recognize Face Images" + formLabelPlacement="right" + toggleType={ToggleType.SWITCH} + onClick={() => { + Doc.UserDoc().recognizeFaceImages = !Doc.UserDoc().recognizeFaceImages; + }} + toggleStatus={BoolCast(Doc.UserDoc().recognizeFaceImages)} + size={Size.XSMALL} + color={SettingsManager.userColor} + /> + <Toggle formLabel="Show Full Toolbar" formLabelPlacement="right" toggleType={ToggleType.SWITCH} diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index c2a52cae9..117d7935e 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -1,13 +1,10 @@ -/* 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 */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button, IconButton, Size, Type } from 'browndash-components'; import { concat, intersection } from 'lodash'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import Select from 'react-select'; +import Select, { MultiValue } from 'react-select'; import * as RequestPromise from 'request-promise'; import { ClientUtils } from '../../ClientUtils'; import { Utils } from '../../Utils'; @@ -27,6 +24,7 @@ import { SearchUtil } from './SearchUtil'; import './SharingManager.scss'; import { SnappingManager } from './SnappingManager'; import { undoable } from './UndoManager'; +import { LinkManager } from './LinkManager'; export interface User { email: string; @@ -64,7 +62,7 @@ interface ValidatedUser { } @observer -export class SharingManager extends React.Component<{}> { +export class SharingManager extends React.Component<object> { // eslint-disable-next-line no-use-before-define public static Instance: SharingManager; private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the share button, used for the position of the popup @@ -90,7 +88,7 @@ export class SharingManager extends React.Component<{}> { // return this.targetDoc ? this.targetDoc['acl_' + PublicKey] !== SharingPermissions.None : false; // } - constructor(props: {}) { + constructor(props: object) { super(props); makeObservable(this); SharingManager.Instance = this; @@ -108,8 +106,8 @@ export class SharingManager extends React.Component<{}> { * Handles changes in the users selected in react-select */ @action - handleUsersChange = (selectedOptions: any) => { - this.selectedUsers = selectedOptions as UserOptions[]; + handleUsersChange = (selectedOptions: MultiValue<UserOptions> /* , actionMeta: ActionMeta<UserOptions> */) => { + this.selectedUsers = Array.from(selectedOptions); }; /** @@ -490,12 +488,12 @@ export class SharingManager extends React.Component<{}> { const docs = await DocServer.GetRefFields(raw.reduce((list, user) => [...list, user.sharingDocumentId, user.linkDatabaseId], [] as string[])); raw.map( action((newUser: User) => { - const sharingDoc = docs[newUser.sharingDocumentId]; - const linkDatabase = docs[newUser.linkDatabaseId]; + const sharingDoc = docs.get(newUser.sharingDocumentId); + const linkDatabase = docs.get(newUser.linkDatabaseId); if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) { if (!this.users.find(user => user.user.email === newUser.email)) { this.users.push({ user: newUser, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) }); - // LinkManager.addLinkDB(linkDatabase); + LinkManager.Instance.addLinkDB(linkDatabase); } } }) @@ -539,9 +537,8 @@ export class SharingManager extends React.Component<{}> { // eslint-disable-next-line react/no-unused-class-component-methods shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => { const user = this.users.find(({ user: { email } }) => email === emailId)!; - const self = this; if (group.docsShared) { - if (!user) retry && this.populateUsers().then(() => self.shareWithAddedMember(group, emailId, false)); + if (!user) retry && this.populateUsers().then(() => this.shareWithAddedMember(group, emailId, false)); else { DocListCastAsync(user.sharingDoc[storage]).then(userdocs => DocListCastAsync(group.docsShared).then(dl => { diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts index cc0366c5b..95ccc7735 100644 --- a/src/client/util/SnappingManager.ts +++ b/src/client/util/SnappingManager.ts @@ -79,5 +79,5 @@ export class SnappingManager { public static userColor: string | undefined; public static userVariantColor: string | undefined; public static userBackgroundColor: string | undefined; - public static SettingsStyle: any; + public static SettingsStyle: CSSStyleSheet | null; } diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts index 25a3c9ad8..7da0281c0 100644 --- a/src/client/util/TrackMovements.ts +++ b/src/client/util/TrackMovements.ts @@ -9,13 +9,13 @@ export type Movement = { panX: number; panY: number; scale: number; - doc: Doc; + doc: Doc | string; }; export type Presentation = { movements: Movement[] | null; totalTime: number; - meta: Object | Object[]; + meta: object | object[]; }; export class TrackMovements { @@ -142,7 +142,7 @@ export class TrackMovements { ); }; - start = (meta?: Object) => { + start = (meta?: object) => { this.initTabTracker(); // update the presentation mode @@ -245,7 +245,7 @@ export class TrackMovements { // these three will lead to the combined presentation const combinedMovements: Movement[] = []; let sumTime = 0; - const combinedMetas: any[] = []; + const combinedMetas: (object | object[])[] = []; presentations.forEach(presentation => { const { movements, totalTime, meta } = presentation; diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts index 9ef2aa8d7..345eff00a 100644 --- a/src/client/util/TypedEvent.ts +++ b/src/client/util/TypedEvent.ts @@ -1,5 +1,5 @@ export interface Listener<T> { - (event: T): any; + (event: T): unknown; } export interface Disposable { diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index 534ffd2c8..ce0e7768b 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -5,7 +5,7 @@ import { Without } from '../../Utils'; import { RichTextField } from '../../fields/RichTextField'; import { SnappingManager } from './SnappingManager'; -function getBatchName(target: any, key: string | symbol): string { +function getBatchName(target: (...args: unknown[]) => unknown, key: string | symbol): string { const keyName = key.toString(); if (target?.constructor?.name) { return `${target.constructor.name}.${keyName}`; @@ -13,19 +13,19 @@ function getBatchName(target: any, key: string | symbol): string { return keyName; } -function propertyDecorator(target: any, key: string | symbol) { +function propertyDecorator(target: (...args: unknown[]) => unknown, key: string | symbol) { Object.defineProperty(target, key, { configurable: true, enumerable: false, get: function () { return 5; }, - set: function (value: any) { + set: function (value: (...args: unknown[]) => unknown) { Object.defineProperty(this, key, { enumerable: false, writable: true, configurable: true, - value: function (...args: any[]) { + value: function (...args: unknown[]) { const batch = UndoManager.StartBatch(getBatchName(target, key)); try { return value.apply(this, args); @@ -38,7 +38,8 @@ function propertyDecorator(target: any, key: string | symbol) { }); } -export function undoable(fn: (...args: any[]) => any, batchName: string): (...args: any[]) => any { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function undoable<T>(fn: (...args: any[]) => T, batchName: string): (...args: unknown[]) => T { return function (...fargs) { const batch = UndoManager.StartBatch(batchName); try { @@ -50,13 +51,12 @@ export function undoable(fn: (...args: any[]) => any, batchName: string): (...ar }; } +// eslint-disable-next-line no-redeclare, @typescript-eslint/no-explicit-any export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any; -// eslint-disable-next-line no-redeclare -export function undoBatch(fn: (...args: any[]) => any): (...args: any[]) => any; -// eslint-disable-next-line no-redeclare -export function undoBatch(target: any, key?: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any { +// eslint-disable-next-line no-redeclare, @typescript-eslint/no-explicit-any +export function undoBatch(target: any, key?: string | symbol, descriptor?: TypedPropertyDescriptor<(...args: any[]) => unknown>): any { if (!key) { - return function (...fargs: any[]) { + return function (...fargs: unknown[]) { const batch = UndoManager.StartBatch(''); try { return target.apply(undefined, fargs); @@ -71,10 +71,10 @@ export function undoBatch(target: any, key?: string | symbol, descriptor?: Typed } const oldFunction = descriptor.value; - descriptor.value = function (...args: any[]) { + descriptor.value = function (...args: unknown[]) { const batch = UndoManager.StartBatch(getBatchName(target, key)); try { - return oldFunction.apply(this, args); + return oldFunction?.apply(this, args); } finally { batch.end(); } @@ -99,12 +99,12 @@ export namespace UndoManager { export const undoStack: UndoBatch[] = observable([]); export const redoStack: UndoBatch[] = observable([]); export const batchCounter = observable.box(0); - let _fieldPrinter: (val: any) => string = val => val?.toString(); - export function SetFieldPrinter(printer: (val: any) => string) { + let _fieldPrinter: (val: unknown) => string = val => val?.toString?.() || ''; + export function SetFieldPrinter(printer: (val: unknown) => string) { _fieldPrinter = printer; } - export function AddEvent(event: UndoEvent, value?: any): void { + export function AddEvent(event: UndoEvent, value?: unknown): void { if (currentBatch && batchCounter.get() && !undoing) { SnappingManager.PrintToConsole && console.log( @@ -220,7 +220,7 @@ export namespace UndoManager { batch.end(); } } - export const UndoTempBatch = action((success: any) => { + export const UndoTempBatch = action((success: boolean) => { if (tempEvents && !success) { undoing = true; for (let i = tempEvents.length - 1; i >= 0; i--) { @@ -243,7 +243,6 @@ export namespace UndoManager { } undoing = true; - // eslint-disable-next-line prettier/prettier commands .slice() .reverse() diff --git a/src/client/util/reportManager/ReportManager.scss b/src/client/util/reportManager/ReportManager.scss index d82d7fdeb..fd343ac8e 100644 --- a/src/client/util/reportManager/ReportManager.scss +++ b/src/client/util/reportManager/ReportManager.scss @@ -96,12 +96,12 @@ transition: all 0.2s ease; background: transparent; - &:hover { - // border-bottom-color: $text-gray; - } - &:focus { - // border-bottom-color: #4476f7; - } + // &:hover { + // // border-bottom-color: $text-gray; + // } + // &:focus { + // // border-bottom-color: #4476f7; + // } } // View issues diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx index 2224e642d..c969f9036 100644 --- a/src/client/util/reportManager/ReportManager.tsx +++ b/src/client/util/reportManager/ReportManager.tsx @@ -1,5 +1,3 @@ -/* eslint-disable jsx-a11y/label-has-associated-control */ -/* eslint-disable jsx-a11y/media-has-caption */ /* eslint-disable react/no-unused-class-component-methods */ import { Octokit } from '@octokit/core'; import { Button, Dropdown, DropdownType, IconButton, Type } from 'browndash-components'; @@ -27,7 +25,7 @@ import { BugType, FileData, Priority, ReportForm, ViewState, bugDropdownItems, d * Class for reporting and viewing Github issues within the app. */ @observer -export class ReportManager extends React.Component<{}> { +export class ReportManager extends React.Component<object> { // eslint-disable-next-line no-use-before-define public static Instance: ReportManager; @observable private isOpen = false; @@ -109,7 +107,7 @@ export class ReportManager extends React.Component<{}> { this.setFetchingIssues(false); }); - constructor(props: {}) { + constructor(props: object) { super(props); makeObservable(this); ReportManager.Instance = this; diff --git a/src/client/util/reportManager/ReportManagerComponents.tsx b/src/client/util/reportManager/ReportManagerComponents.tsx index cecebc648..92f877859 100644 --- a/src/client/util/reportManager/ReportManagerComponents.tsx +++ b/src/client/util/reportManager/ReportManagerComponents.tsx @@ -1,8 +1,5 @@ /* 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 ReactMarkdown from 'react-markdown'; @@ -98,7 +95,7 @@ export function IssueCard({ issue, onSelect }: IssueCardProps) { <label className="issue-label">#{issue.number}</label> <div className="issue-tags"> {issue.labels.map(label => { - const labelString = typeof label === 'string' ? label : label.name ?? ''; + const labelString = typeof label === 'string' ? label : (label.name ?? ''); const colors = getLabelColors(labelString); return <Tag key={labelString} text={labelString} backgroundColor={colors[0]} color={colors[1]} />; })} @@ -295,14 +292,16 @@ export function IssueView({ issue }: IssueViewProps) { <div> <div className="issue-tags"> {issue.labels.map(label => { - const labelString = typeof label === 'string' ? label : label.name ?? ''; + const labelString = typeof label === 'string' ? label : (label.name ?? ''); const colors = getLabelColors(labelString); return <Tag key={labelString} text={labelString} backgroundColor={colors[0]} color={colors[1]} fontSize="12px" />; })} </div> </div> )} - <ReactMarkdown children={issueBody} className="issue-content" remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} /> + <ReactMarkdown className="issue-content" remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}> + {issueBody} + </ReactMarkdown> </div> ); } diff --git a/src/client/util/reportManager/reportManagerSchema.ts b/src/client/util/reportManager/reportManagerSchema.ts index 171c24393..7162371e3 100644 --- a/src/client/util/reportManager/reportManagerSchema.ts +++ b/src/client/util/reportManager/reportManagerSchema.ts @@ -66,7 +66,7 @@ export interface Issue { */ url: string; user: null | TentacledSimpleUser; - [property: string]: any; + [property: string]: unknown; } /** @@ -94,7 +94,7 @@ export interface PurpleSimpleUser { subscriptions_url: string; type: string; url: string; - [property: string]: any; + [property: string]: unknown; } /** @@ -122,7 +122,7 @@ export interface AssigneeElement { subscriptions_url: string; type: string; url: string; - [property: string]: any; + [property: string]: unknown; } /** @@ -164,7 +164,7 @@ export interface FluffySimpleUser { subscriptions_url: string; type: string; url: string; - [property: string]: any; + [property: string]: unknown; } export interface LabelObject { @@ -175,7 +175,7 @@ export interface LabelObject { name?: string; node_id?: string; url?: string; - [property: string]: any; + [property: string]: unknown; } /** @@ -207,7 +207,7 @@ export interface Milestone { title: string; updated_at: Date; url: string; - [property: string]: any; + [property: string]: unknown; } /** @@ -235,7 +235,7 @@ export interface MilestoneSimpleUser { subscriptions_url: string; type: string; url: string; - [property: string]: any; + [property: string]: unknown; } /** @@ -288,7 +288,7 @@ export interface GitHubApp { slug?: string; updated_at: Date; webhook_secret?: null | string; - [property: string]: any; + [property: string]: unknown; } /** @@ -316,7 +316,7 @@ export interface GitHubAppSimpleUser { subscriptions_url: string; type: string; url: string; - [property: string]: any; + [property: string]: unknown; } /** @@ -336,7 +336,7 @@ export interface PullRequest { merged_at?: Date | null; patch_url: null | string; url: null | string; - [property: string]: any; + [property: string]: unknown; } export interface ReactionRollup { @@ -350,7 +350,7 @@ export interface ReactionRollup { rocket: number; total_count: number; url: string; - [property: string]: any; + [property: string]: unknown; } /** @@ -562,7 +562,7 @@ export interface Repository { * Whether to require contributors to sign off on web-based commits */ web_commit_signoff_required?: boolean; - [property: string]: any; + [property: string]: unknown; } /** @@ -575,7 +575,7 @@ export interface LicenseSimple { node_id: string; spdx_id: null | string; url: null | string; - [property: string]: any; + [property: string]: unknown; } /** @@ -628,7 +628,7 @@ export interface RepositorySimpleUser { subscriptions_url: string; type: string; url: string; - [property: string]: any; + [property: string]: unknown; } /** @@ -656,7 +656,7 @@ export interface OwnerObject { subscriptions_url: string; type: string; url: string; - [property: string]: any; + [property: string]: unknown; } export interface RepositoryPermissions { @@ -665,7 +665,7 @@ export interface RepositoryPermissions { pull: boolean; push: boolean; triage?: boolean; - [property: string]: any; + [property: string]: unknown; } /** @@ -809,7 +809,7 @@ export interface TemplateRepository { use_squash_pr_title_as_default?: boolean; visibility?: string; watchers_count?: number; - [property: string]: any; + [property: string]: unknown; } export interface Owner { @@ -831,7 +831,7 @@ export interface Owner { subscriptions_url?: string; type?: string; url?: string; - [property: string]: any; + [property: string]: unknown; } export interface TemplateRepositoryPermissions { @@ -840,7 +840,7 @@ export interface TemplateRepositoryPermissions { pull?: boolean; push?: boolean; triage?: boolean; - [property: string]: any; + [property: string]: unknown; } export enum StateReason { @@ -874,5 +874,5 @@ export interface TentacledSimpleUser { subscriptions_url: string; type: string; url: string; - [property: string]: any; + [property: string]: unknown; } diff --git a/src/client/util/reportManager/reportManagerUtils.ts b/src/client/util/reportManager/reportManagerUtils.ts index f14967e0a..d51418cbe 100644 --- a/src/client/util/reportManager/reportManagerUtils.ts +++ b/src/client/util/reportManager/reportManagerUtils.ts @@ -3,6 +3,7 @@ import { Octokit } from '@octokit/core'; import { Networking } from '../../Network'; import { Issue } from './reportManagerSchema'; +import { Upload } from '../../../server/SharedMediaTypes'; // enums and interfaces @@ -53,7 +54,7 @@ export const emptyReportForm = { * Fetches issues from Github. * @returns array of all issues */ -export const getAllIssues = async (octokit: Octokit): Promise<any[]> => { +export const getAllIssues = async (octokit: Octokit): Promise<unknown[]> => { const res = await octokit.request('GET /repos/{owner}/{repo}/issues', { owner: 'brown-dash', repo: 'Dash-Web', @@ -103,7 +104,10 @@ export const fileLinktoServerLink = (fileLink: string): string => { * @param link response from file upload * @returns server file path */ -export const getServerPath = (link: any): string => link.result.accessPaths.agnostic.server as string; +export const getServerPath = (link: Upload.FileResponse<Upload.FileInformation>): string => { + if (link.result instanceof Error) return ''; + return link.result.accessPaths.agnostic.server; +}; /** * Uploads media files to the server. @@ -114,11 +118,11 @@ export const uploadFilesToServer = async (mediaFiles: FileData[]): Promise<strin // need to always upload to browndash const links = await Networking.UploadFilesToServer(mediaFiles.map(file => ({ file: file.file }))); return (links ?? []).map(getServerPath).map(fileLinktoServerLink); - } catch (err) { - if (err instanceof Error) { - alert(err.message); + } catch (result) { + if (result instanceof Error) { + alert(result.message); } else { - alert(err); + alert(result); } } return undefined; diff --git a/src/client/util/request-image-size.ts b/src/client/util/request-image-size.ts index 48cb6e3a5..c619192ed 100644 --- a/src/client/util/request-image-size.ts +++ b/src/client/util/request-image-size.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ /** * request-image-size: Detect image dimensions via request. * Licensed under the MIT license. @@ -9,43 +10,36 @@ * https://github.com/jo/http-image-size */ -const request = require('request'); -const imageSize = require('image-size'); +// const imageSize = require('image-size'); const HttpError = require('standard-http-error'); +import * as request from 'request'; +import { imageSize } from 'image-size'; +import { ISizeCalculationResult } from 'image-size/dist/types/interface'; -module.exports = function requestImageSize(options: any) { - let opts: any = { - encoding: null, - }; - - if (options && typeof options === 'object') { - opts = Object.assign(options, opts); - } else if (options && typeof options === 'string') { - opts = { - uri: options, - ...opts, - }; - } else { +module.exports = function requestImageSize(url: string) { + if (!url) { return Promise.reject(new Error('You should provide an URI string or a "request" options object.')); } - opts.encoding = null; - return new Promise((resolve, reject) => { - const req = request(opts); + const req = request(url); - req.on('response', (res: any) => { + req.on('response', res => { if (res.statusCode >= 400) { reject(new HttpError(res.statusCode, res.statusMessage)); return; } let buffer = Buffer.from([]); - let size: any; + let size: ISizeCalculationResult; - res.on('data', (chunk: any) => { + res.on('data', chunk => { buffer = Buffer.concat([buffer, chunk]); + }); + res.on('error', reject); + + res.on('end', () => { try { size = imageSize(buffer); if (size) { @@ -54,19 +48,12 @@ module.exports = function requestImageSize(options: any) { } } catch (err) { /* empty */ - console.log("Error: ", err) + console.log('Error: ', err); } - }); - - res.on('error', reject); - - res.on('end', () => { if (!size) { reject(new Error('Image has no size')); return; } - - size.downloaded = buffer.length; resolve(size); }); }); diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d deleted file mode 100644 index 1a93bbe59..000000000 --- a/src/client/util/type_decls.d +++ /dev/null @@ -1,224 +0,0 @@ -//@ts-ignore -declare type PropertyKey = string | number | symbol; -interface Array<T> { - length: number; - toString(): string; - toLocaleString(): string; - pop(): T | undefined; - push(...items: T[]): number; - concat(...items: ConcatArray<T>[]): T[]; - concat(...items: (T | ConcatArray<T>)[]): T[]; - join(separator?: string): string; - reverse(): T[]; - shift(): T | undefined; - slice(start?: number, end?: number): T[]; - sort(compareFn?: (a: T, b: T) => number): this; - splice(start: number, deleteCount?: number): T[]; - splice(start: number, deleteCount: number, ...items: T[]): T[]; - unshift(...items: T[]): number; - indexOf(searchElement: T, fromIndex?: number): number; - lastIndexOf(searchElement: T, fromIndex?: number): number; - every(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean; - some(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean; - forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void; - map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[]; - filter<S extends T>(callbackfn: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[]; - filter(callbackfn: (value: T, index: number, array: T[]) => any, thisArg?: any): T[]; - reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T; - reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T; - reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U; - reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T; - reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T; - reduceRight<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U; - - [n: number]: T; -} - -interface Function { - apply(this: Function, thisArg: any, argArray?: any): any; - call(this: Function, thisArg: any, ...argArray: any[]): any; - bind(this: Function, thisArg: any, ...argArray: any[]): any; - toString(): string; - - prototype: any; - readonly length: number; - - // Non-standard extensions - arguments: any; - caller: Function; -} -interface Boolean { - valueOf(): boolean; -} -interface Number { - toString(radix?: number): string; - toFixed(fractionDigits?: number): string; - toExponential(fractionDigits?: number): string; - toPrecision(precision?: number): string; - valueOf(): number; -} -interface IArguments { - [index: number]: any; - length: number; - callee: Function; -} -interface RegExp { - readonly flags: string; - readonly sticky: boolean; - readonly unicode: boolean; -} -interface Date { - now() : string; -} -interface String { - codePointAt(pos: number): number | undefined; - includes(searchString: string, position?: number): boolean; - endsWith(searchString: string, endPosition?: number): boolean; - normalize(form: "NFC" | "NFD" | "NFKC" | "NFKD"): string; - normalize(form?: string): string; - repeat(count: number): string; - replace(a:any, b:any):string; // bcz: fix this - startsWith(searchString: string, position?: number): boolean; - anchor(name: string): string; - big(): string; - blink(): string; - bold(): string; - fixed(): string; - fontcolor(color: string): string; - fontsize(size: number): string; - fontsize(size: string): string; - italics(): string; - link(url: string): string; - small(): string; - strike(): string; - sub(): string; - sup(): string; -} -interface Object { - constructor: Function; - toString(): string; - toLocaleString(): string; - valueOf(): Object; - hasOwnProperty(v: PropertyKey): boolean; - isPrototypeOf(v: Object): boolean; - propertyIsEnumerable(v: PropertyKey): boolean; -} -interface ConcatArray<T> { - readonly length: number; - readonly [n: number]: T; - join(separator?: string): string; - slice(start?: number, end?: number): T[]; -} -interface URL { - hash: string; - host: string; - hostname: string; - href: string; - readonly origin: string; - password: string; - pathname: string; - port: string; - protocol: string; - search: string; - username: string; - toJSON(): string; -} -interface PromiseLike<T> { - then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): PromiseLike<TResult1 | TResult2>; -} -interface Promise<T> { - then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>; - catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>; -} - -declare const Update: unique symbol; -declare const Self: unique symbol; -declare const SelfProxy: unique symbol; -declare const DataSym: unique symbol; -declare const HandleUpdate: unique symbol; -declare const Id: unique symbol; -declare const OnUpdate: unique symbol; -declare const Parent: unique symbol; -declare const Copy: unique symbol; -declare const ToScriptString: unique symbol; - -declare abstract class RefField { - readonly [Id]: FieldId; - - constructor(); -} - -declare type FieldId = string; - -declare abstract class ObjectField { - abstract [Copy](): ObjectField; -} - -declare abstract class URLField extends ObjectField { - readonly url: URL; - - constructor(url: string); - constructor(url: URL); -} - -declare class RichTextField extends URLField { - [Copy](): ObjectField; - constructor(data:string, text: string); -} -declare class AudioField extends URLField { [Copy](): ObjectField; } -declare class VideoField extends URLField { [Copy](): ObjectField; } -declare class ImageField extends URLField { [Copy](): ObjectField; } -declare class WebField extends URLField { [Copy](): ObjectField; } -declare class PdfField extends URLField { [Copy](): ObjectField; } - -declare const ComputedField: any; -declare const CompileScript: any; - -// @ts-ignore -declare type Extract<T, U> = T extends U ? T : never; -declare type Field = number | string | boolean | ObjectField | RefField; -declare type FieldWaiting<T extends RefField = RefField> = T extends undefined ? never : Promise<T | undefined>; -declare type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract<T, RefField>>; - -declare type Opt<T> = T | undefined; -declare class Doc extends RefField { - constructor(); - - [key: string]: FieldResult; - // [ToScriptString](): string; -} - -declare class List<T extends Field> extends ObjectField { - constructor(fields?: T[]); - [index: number]: T | (T extends RefField ? Promise<T> : never); - [Copy](): ObjectField; -} - -declare class InkField extends ObjectField { - constructor(data:Array<{X:number, Y:number}>); - [Copy](): ObjectField; -} - -// @ts-ignore -declare const console: any; - -interface DocumentOptions { } - -declare const Docs: { - ImageDocument(url: string, options?: DocumentOptions): Doc; - VideoDocument(url: string, options?: DocumentOptions): Doc; - TextDocument(options?: DocumentOptions): Doc; - PdfDocument(url: string, options?: DocumentOptions): Doc; - WebDocument(url: string, options?: DocumentOptions): Doc; - HtmlDocument(html: string, options?: DocumentOptions): Doc; - MapDocument(url: string, options?: DocumentOptions): Doc; - KVPDocument(document: Doc, options?: DocumentOptions): Doc; - FreeformDocument(documents: Doc[], options?: DocumentOptions): Doc; - SchemaDocument(columns: string[], documents: Doc[], options?: DocumentOptions): Doc; - TreeDocument(documents: Doc[], options?: DocumentOptions): Doc; - StackingDocument(documents: Doc[], options?: DocumentOptions): Doc; -}; - -declare function idToDoc(id:string):any; -declare function assignDoc(doc:Doc, field:any, id:any):string; -declare function d(...args:any[]):any; |