From 4574b7f03ccc85c4bebdbfd9475788456086704f Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 8 Aug 2024 12:27:40 -0400 Subject: many changes to add typing in place of 'any's etc --- src/client/views/MainView.tsx | 53 ++++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 34 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index ef1bcfb64..ac30c5a14 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,4 +1,3 @@ -/* eslint-disable node/no-unpublished-import */ import { library } from '@fortawesome/fontawesome-svg-core'; import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons'; import * as far from '@fortawesome/free-regular-svg-icons'; @@ -7,7 +6,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -// eslint-disable-next-line import/no-relative-packages +import ResizeObserver from 'resize-observer-polyfill'; import '../../../node_modules/browndash-components/dist/styles/global.min.css'; import { ClientUtils, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; @@ -58,7 +57,6 @@ import { ImageLabelHandler } from './collections/collectionFreeForm/ImageLabelHa import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu'; import { CollectionLinearView } from './collections/collectionLinear'; import { LinkMenu } from './linking/LinkMenu'; -import { AudioBox } from './nodes/AudioBox'; import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp'; import { DocButtonState } from './nodes/DocumentLinksButton'; import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; @@ -77,11 +75,11 @@ import { AnchorMenu } from './pdf/AnchorMenu'; import { GPTPopup } from './pdf/GPTPopup/GPTPopup'; import { TopBar } from './topbar/TopBar'; +// eslint-disable-next-line @typescript-eslint/no-var-requires const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore -const _global = (window /* browser */ || global) /* node */ as any; @observer -export class MainView extends ObservableReactComponent<{}> { +export class MainView extends ObservableReactComponent { // eslint-disable-next-line no-use-before-define public static Instance: MainView; public static Live: boolean = false; @@ -92,7 +90,7 @@ export class MainView extends ObservableReactComponent<{}> { @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row) @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons @observable private _panelContent: string = 'none'; - @observable private _sidebarContent: any = Doc.MyLeftSidebarPanel; + @observable private _sidebarContent: Doc = Doc.MyLeftSidebarPanel; @observable private _leftMenuFlyoutWidth: number = 0; @computed get _hideUI() { return this.mainDoc && this.mainDoc._type_collection !== CollectionViewType.Docking; @@ -152,7 +150,7 @@ export class MainView extends ObservableReactComponent<{}> { } }; headerBarDocWidth = () => this.mainDocViewWidth(); - headerBarDocHeight = () => (this._hideUI ? 0 : this.headerBarHeight ?? 0); + headerBarDocHeight = () => (this._hideUI ? 0 : (this.headerBarHeight ?? 0)); topMenuHeight = () => (this._hideUI ? 0 : 35); topMenuWidth = returnZero; // value is ignored ... leftMenuWidth = () => (this._hideUI ? 0 : Number(LEFT_MENU_WIDTH.replace('px', ''))); @@ -169,7 +167,7 @@ export class MainView extends ObservableReactComponent<{}> { reaction( // when a multi-selection occurs, remove focus from all active elements to allow keyboad input to go only to global key manager to act upon selection () => DocumentView.Selected().slice(), - views => views.length > 1 && (document.activeElement as any)?.blur !== undefined && (document.activeElement as any)!.blur() + views => views.length > 1 && document.activeElement instanceof HTMLElement && document.activeElement?.blur() ); reaction( () => Doc.MyDockedBtns.linearView_IsOpen, @@ -234,9 +232,9 @@ export class MainView extends ObservableReactComponent<{}> { tag.src = 'https://www.youtube.com/iframe_api'; const firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag); - document.addEventListener('dash', (e: any) => { + document.addEventListener('dash', (e: Event) => { // event used by chrome plugin to tell Dash which document to focus on - const id = GetDocFromUrl(e.detail); + const id = GetDocFromUrl((e as Event & { detail: string }).detail); DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentView.showDocument(doc, { willPan: false }) : null)); }); document.addEventListener('linkAnnotationToDash', Hypothesis.linkListener); @@ -253,12 +251,12 @@ export class MainView extends ObservableReactComponent<{}> { // document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener); } - constructor(props: any) { + constructor(props: object) { super(props); makeObservable(this); DocumentViewInternal.addDocTabFunc = MainView.addDocTabFunc_impl; MainView.Instance = this; - DashboardView._urlState = HistoryUtil.parseUrl(window.location) || ({} as any); + DashboardView._urlState = HistoryUtil.parseUrl(window.location) ?? { type: 'doc', docId: '' }; // causes errors to be generated when modifying an observable outside of an action configure({ enforceActions: 'observed' }); @@ -293,7 +291,7 @@ export class MainView extends ObservableReactComponent<{}> { fa.faExternalLinkAlt, fa.faCalendar, fa.faSquare, - far.faSquare as any, + far.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, @@ -445,7 +443,7 @@ export class MainView extends ObservableReactComponent<{}> { fa.faHandPaper, fa.faMap, fa.faUser, - faHireAHelper as any, + faHireAHelper, fa.faTrashRestore, fa.faUsers, fa.faWrench, @@ -456,14 +454,14 @@ export class MainView extends ObservableReactComponent<{}> { fa.faArchive, fa.faBezierCurve, fa.faCircle, - far.faCircle as any, + far.faCircle, fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, fa.faAngleDoubleDown, fa.faAngleDoubleLeft, fa.faAngleDoubleUp, - faBuffer as any, + faBuffer, fa.faExpand, fa.faUndo, fa.faSlidersH, @@ -570,7 +568,6 @@ export class MainView extends ObservableReactComponent<{}> { ); DocumentManager.removeOverlayViews(); Doc.linkFollowUnhighlight(); - AudioBox.Enabled = true; const targets = document.elementsFromPoint(e.x, e.y); if (targets.length) { let targClass = targets[0].className.toString(); @@ -590,18 +587,6 @@ export class MainView extends ObservableReactComponent<{}> { document.addEventListener('pointerdown', this.globalPointerDown, true); document.addEventListener('pointermove', this.globalPointerMove, true); document.addEventListener('pointerup', this.globalPointerClick, true); - document.addEventListener( - 'click', - (e: MouseEvent) => { - if (!e.cancelBubble) { - const pathstr = (e as any)?.path?.map((p: any) => p.classList?.toString()).join(); - if (pathstr?.includes('libraryFlyout')) { - DocumentView.DeselectAll(); - } - } - }, - false - ); document.oncontextmenu = () => false; }; @@ -777,7 +762,7 @@ export class MainView extends ObservableReactComponent<{}> {
{ className="mainView-dashboardArea" ref={r => { r && - new _global.ResizeObserver( + new ResizeObserver( action(() => { this._dashUIWidth = r.getBoundingClientRect().width; this._dashUIHeight = r.getBoundingClientRect().height; @@ -976,11 +961,11 @@ export class MainView extends ObservableReactComponent<{}> { {[ ...SnappingManager.HorizSnapLines.map((l, i) => ( // eslint-disable-next-line react/no-array-index-key - + )), ...SnappingManager.VertSnapLines.map((l, i) => ( // eslint-disable-next-line react/no-array-index-key - + )), ]} @@ -1038,7 +1023,7 @@ export class MainView extends ObservableReactComponent<{}> { } ref={r => { r && - new _global.ResizeObserver( + new ResizeObserver( action(() => { this._windowWidth = r.getBoundingClientRect().width; this._windowHeight = r.getBoundingClientRect().height; -- cgit v1.2.3-70-g09d2 From b84bdc5a629dfa6310b24dd5eedee2843558b73a Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 12 Aug 2024 21:38:22 -0400 Subject: more any -> type fixes --- deploy/mobile/image.html | 16 - deploy/mobile/ink.html | 13 - src/.DS_Store | Bin 10244 -> 10244 bytes src/ClientUtils.ts | 4 - src/ServerUtils.ts | 1 + src/client/util/Scripting.ts | 2 +- src/client/util/type_decls.txt | 224 ++++++ src/client/views/EditableView.tsx | 4 +- src/client/views/GestureOverlay.tsx | 7 +- src/client/views/LightboxView.tsx | 12 +- src/client/views/MainView.tsx | 12 +- src/client/views/OverlayView.tsx | 8 +- src/client/views/PropertiesView.tsx | 8 +- src/client/views/TemplateMenu.tsx | 4 +- src/client/views/collections/CollectionMenu.tsx | 4 +- .../collections/CollectionStackedTimeline.tsx | 4 +- .../views/collections/CollectionStackingView.tsx | 5 +- .../CollectionStackingViewFieldColumn.tsx | 11 +- .../views/collections/CollectionTreeView.tsx | 7 +- src/client/views/collections/TabDocView.tsx | 14 +- src/client/views/collections/TreeView.tsx | 4 +- .../CollectionMulticolumnView.tsx | 8 +- .../CollectionMultirowView.tsx | 6 +- .../collectionMulticolumn/MulticolumnResizer.tsx | 2 +- .../collectionMulticolumn/MultirowResizer.tsx | 2 +- .../collectionSchema/CollectionSchemaView.tsx | 57 +- .../collectionSchema/SchemaColumnHeader.tsx | 4 +- .../collections/collectionSchema/SchemaRowBox.tsx | 4 +- .../collectionSchema/SchemaTableCell.tsx | 32 +- src/client/views/linking/LinkPopup.tsx | 4 +- src/client/views/newlightbox/NewLightboxView.tsx | 14 +- .../RecommendationList/RecommendationList.tsx | 7 +- src/client/views/nodes/KeyValuePair.tsx | 4 +- src/client/views/nodes/LabelBox.tsx | 5 +- src/client/views/nodes/LinkDocPreview.tsx | 7 +- .../views/nodes/MapboxMapBox/MapboxContainer.tsx | 13 +- src/client/views/nodes/trails/PresElementBox.tsx | 18 +- src/client/views/topbar/TopBar.tsx | 8 +- src/fields/Doc.ts | 4 + src/mobile/ImageUpload.scss | 139 ---- src/mobile/ImageUpload.tsx | 170 ---- src/mobile/InkControls.tsx | 0 src/mobile/MobileInkOverlay.scss | 39 - src/mobile/MobileInkOverlay.tsx | 183 ----- src/mobile/MobileInterface.scss | 445 ----------- src/mobile/MobileInterface.tsx | 871 --------------------- src/mobile/MobileMain.tsx | 26 - src/mobile/MobileMenu.scss | 271 ------- webpack.config.js | 6 +- 49 files changed, 382 insertions(+), 2331 deletions(-) delete mode 100644 deploy/mobile/image.html delete mode 100644 deploy/mobile/ink.html create mode 100644 src/client/util/type_decls.txt delete mode 100644 src/mobile/ImageUpload.scss delete mode 100644 src/mobile/ImageUpload.tsx delete mode 100644 src/mobile/InkControls.tsx delete mode 100644 src/mobile/MobileInkOverlay.scss delete mode 100644 src/mobile/MobileInkOverlay.tsx delete mode 100644 src/mobile/MobileInterface.scss delete mode 100644 src/mobile/MobileInterface.tsx delete mode 100644 src/mobile/MobileMain.tsx delete mode 100644 src/mobile/MobileMenu.scss (limited to 'src/client/views/MainView.tsx') diff --git a/deploy/mobile/image.html b/deploy/mobile/image.html deleted file mode 100644 index cca6f763b..000000000 --- a/deploy/mobile/image.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - Dash Mobile - - - - - - -
- - - - \ No newline at end of file diff --git a/deploy/mobile/ink.html b/deploy/mobile/ink.html deleted file mode 100644 index 938d85794..000000000 --- a/deploy/mobile/ink.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - Test view - - - - -
- - - - \ No newline at end of file diff --git a/src/.DS_Store b/src/.DS_Store index 75cff7b55..7a0b53ce0 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts index f9e282993..fc048b155 100644 --- a/src/ClientUtils.ts +++ b/src/ClientUtils.ts @@ -82,10 +82,6 @@ export function returnEmptyFilter() { return [] as string[]; } -export function returnEmptyDoclist() { - return [] as any[]; -} - // eslint-disable-next-line @typescript-eslint/no-namespace export namespace ClientUtils { export const CLICK_TIME = 300; diff --git a/src/ServerUtils.ts b/src/ServerUtils.ts index ade4ca35d..7e821cda2 100644 --- a/src/ServerUtils.ts +++ b/src/ServerUtils.ts @@ -2,6 +2,7 @@ import { Socket } from 'socket.io'; import { Message } from './server/Message'; import { Utils } from './Utils'; +// eslint-disable-next-line @typescript-eslint/no-namespace export namespace ServerUtils { export function Emit(socket: Socket, message: Message, args: T) { Utils.log('Emit', message.Name, args, false); diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 133f8f2ce..b9e0943b6 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -1,7 +1,7 @@ // export const ts = (window as any).ts; // 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' -import * as typescriptlib from '!!raw-loader!./type_decls.d'; +import * as typescriptlib from './type_decls.d'; import * as ts from 'typescript'; import { Doc, FieldType } from '../../fields/Doc'; import { RefField } from '../../fields/RefField'; diff --git a/src/client/util/type_decls.txt b/src/client/util/type_decls.txt new file mode 100644 index 000000000..1a93bbe59 --- /dev/null +++ b/src/client/util/type_decls.txt @@ -0,0 +1,224 @@ +//@ts-ignore +declare type PropertyKey = string | number | symbol; +interface Array { + length: number; + toString(): string; + toLocaleString(): string; + pop(): T | undefined; + push(...items: T[]): number; + concat(...items: ConcatArray[]): T[]; + concat(...items: (T | ConcatArray)[]): 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(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[]; + filter(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(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(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 { + 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 { + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): PromiseLike; +} +interface Promise { + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise; + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise; +} + +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 extends U ? T : never; +declare type Field = number | string | boolean | ObjectField | RefField; +declare type FieldWaiting = T extends undefined ? never : Promise; +declare type FieldResult = Opt | FieldWaiting>; + +declare type Opt = T | undefined; +declare class Doc extends RefField { + constructor(); + + [key: string]: FieldResult; + // [ToScriptString](): string; +} + +declare class List extends ObjectField { + constructor(fields?: T[]); + [index: number]: T | (T extends RefField ? Promise : 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; diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index e02e39b8b..23da5a666 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -26,7 +26,7 @@ export interface EditableProps { /** * The contents to render when not editing */ - contents: string; + contents: JSX.Element | string; fieldContents?: FieldViewProps; fontStyle?: string; fontSize?: number; @@ -301,7 +301,7 @@ export class EditableView extends ObservableReactComponent { fontStyle: this._props.fontStyle, fontSize: this._props.fontSize, }}> - {this._props.fieldContents ? : this._props.contents ? this._props.contents?.valueOf() : ''} + {this._props.fieldContents ? : (this._props.contents ?? '')}
); diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 804c41091..bcd4d1ee5 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -2,9 +2,9 @@ import * as fitCurve from 'fit-curve'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../ClientUtils'; +import { returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; -import { Doc, Opt } from '../../fields/Doc'; +import { Doc, Opt, returnEmptyDoclist } from '../../fields/Doc'; import { InkData, InkField, InkTool } from '../../fields/InkField'; import { NumCast } from '../../fields/Types'; import { @@ -30,6 +30,7 @@ import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { Transform } from '../util/Transform'; import './GestureOverlay.scss'; import { ObservableReactComponent } from './ObservableReactComponent'; +import { returnEmptyDocViewList } from './StyleProvider'; import { ActiveFillColor, DocumentView } from './nodes/DocumentView'; export enum ToolglassTools { @@ -466,7 +467,7 @@ export class GestureOverlay extends ObservableReactComponent { ScreenToLocalTransform={this.lightboxScreenToLocal} renderDepth={0} suppressSetHeight={!!this._doc._layout_fitWidth} - containerViewPath={returnEmptyDoclist} + containerViewPath={returnEmptyDocViewList} childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index ac30c5a14..62ad0a3fb 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -8,9 +8,9 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import ResizeObserver from 'resize-observer-polyfill'; import '../../../node_modules/browndash-components/dist/styles/global.min.css'; -import { ClientUtils, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils'; +import { ClientUtils, lightOrDark, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; -import { Doc, DocListCast, GetDocFromUrl, Opt } from '../../fields/Doc'; +import { Doc, DocListCast, GetDocFromUrl, Opt, returnEmptyDoclist } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { DocCast, StrCast, toList } from '../../fields/Types'; @@ -627,7 +627,7 @@ export class MainView extends ObservableReactComponent { Document={this.headerBarDoc} addDocTab={DocumentViewInternal.addDocTabFunc} pinToPres={emptyFunction} - containerViewPath={returnEmptyDoclist} + containerViewPath={returnEmptyDocViewList} styleProvider={DefaultStyleProvider} addDocument={this.addHeaderDoc} removeDocument={this.removeHeaderDoc} @@ -662,7 +662,7 @@ export class MainView extends ObservableReactComponent { addDocument={undefined} addDocTab={DocumentViewInternal.addDocTabFunc} pinToPres={emptyFunction} - containerViewPath={returnEmptyDoclist} + containerViewPath={returnEmptyDocViewList} styleProvider={this._hideUI ? DefaultStyleProvider : undefined} isContentActive={returnTrue} removeDocument={undefined} @@ -766,7 +766,7 @@ export class MainView extends ObservableReactComponent { addDocument={undefined} addDocTab={DocumentViewInternal.addDocTabFunc} pinToPres={DocumentView.PinDoc} - containerViewPath={returnEmptyDoclist} + containerViewPath={returnEmptyDocViewList} styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem || this._sidebarContent.proto === Doc.MyTrails ? DashboardStyleProvider : DefaultStyleProvider} removeDocument={returnFalse} ScreenToLocalTransform={this.mainContainerXf} @@ -799,7 +799,7 @@ export class MainView extends ObservableReactComponent { PanelWidth={this.leftMenuWidth} PanelHeight={this.leftMenuHeight} renderDepth={0} - containerViewPath={returnEmptyDoclist} + containerViewPath={returnEmptyDocViewList} focus={emptyFunction} styleProvider={DefaultStyleProvider} isContentActive={returnTrue} diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 7bf10467e..5e9677b45 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -4,9 +4,9 @@ import { computedFn } from 'mobx-utils'; import * as React from 'react'; import ReactLoading from 'react-loading'; import ResizeObserver from 'resize-observer-polyfill'; -import { returnEmptyDoclist, returnEmptyFilter, returnTrue, setupMoveUpEvents } from '../../ClientUtils'; +import { returnEmptyFilter, returnTrue, setupMoveUpEvents } from '../../ClientUtils'; import { Utils, emptyFunction } from '../../Utils'; -import { Doc } from '../../fields/Doc'; +import { Doc, returnEmptyDoclist } from '../../fields/Doc'; import { Height, Width } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { NumCast, toList } from '../../fields/Types'; @@ -16,7 +16,7 @@ import { dropActionType } from '../util/DropActionTypes'; import { Transform } from '../util/Transform'; import { ObservableReactComponent } from './ObservableReactComponent'; import './OverlayView.scss'; -import { DefaultStyleProvider } from './StyleProvider'; +import { DefaultStyleProvider, returnEmptyDocViewList } from './StyleProvider'; import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; export type OverlayDisposer = () => void; @@ -226,7 +226,7 @@ export class OverlayView extends ObservableReactComponent { whenChildContentsActiveChanged={emptyFunction} focus={emptyFunction} styleProvider={DefaultStyleProvider} - containerViewPath={returnEmptyDoclist} + containerViewPath={returnEmptyDocViewList} addDocTab={DocumentViewInternal.addDocTabFunc} pinToPres={emptyFunction} childFilters={returnEmptyFilter} diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 5952d6641..daa8e1720 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -10,9 +10,9 @@ import * as React from 'react'; import { ColorResult, SketchPicker } from 'react-color'; import * as Icons from 'react-icons/bs'; // {BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs" import ResizeObserver from 'resize-observer-polyfill'; -import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../ClientUtils'; +import { ClientUtils, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; -import { Doc, Field, FieldResult, FieldType, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc'; +import { Doc, Field, FieldResult, FieldType, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast, returnEmptyDoclist } from '../../fields/Doc'; import { AclAdmin, DocAcl, DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { InkField } from '../../fields/InkField'; @@ -36,7 +36,7 @@ import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector import { PropertiesDocContextSelector } from './PropertiesDocContextSelector'; import { PropertiesSection } from './PropertiesSection'; import './PropertiesView.scss'; -import { DefaultStyleProvider, SetFilterOpener as SetPropertiesFilterOpener } from './StyleProvider'; +import { DefaultStyleProvider, SetFilterOpener as SetPropertiesFilterOpener, returnEmptyDocViewList } from './StyleProvider'; import { DocumentView } from './nodes/DocumentView'; import { StyleProviderFuncType } from './nodes/FieldView'; import { OpenWhere } from './nodes/OpenWhere'; @@ -322,7 +322,7 @@ export class PropertiesView extends ObservableReactComponent boolean; createDropTarget: (ele: HTMLDivElement) => void; screenToLocalTransform: () => Transform; - refList: any[]; + refList: HTMLElement[]; } @observer @@ -64,7 +61,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< @observable _heading = ''; @observable _color = ''; - constructor(props: any) { + constructor(props: CSVFieldColumnProps) { super(props); makeObservable(this); this._heading = this._props.headingObject ? this._props.headingObject.heading : this._props.heading; @@ -118,7 +115,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< this._props.pivotField && drop.docs?.forEach(d => Doc.SetInPlace(d, this._props.pivotField, drop.val, false)); return true; }); - getValue = (value: string): any => { + getValue = (value: string) => { const parsed = parseInt(value); if (!isNaN(parsed)) return parsed; if (value.toLowerCase().indexOf('true') > -1) return true; @@ -212,7 +209,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
{colors.map(col => { const palette = PastelSchemaPalette.get(col); - return
this.changeColumnColor(palette!)} />; + return
this.changeColumnColor(palette!)} />; })}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 0557a9102..a60cd98ac 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -2,8 +2,8 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti import { observer } from 'mobx-react'; import * as React from 'react'; import ResizeObserver from 'resize-observer-polyfill'; -import { DivHeight, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../ClientUtils'; -import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc'; +import { DivHeight, returnAll, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../ClientUtils'; +import { Doc, DocListCast, Opt, returnEmptyDoclist, StrListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { listSpec } from '../../../fields/Schema'; @@ -25,6 +25,7 @@ import { EditableView } from '../EditableView'; import { DocumentView } from '../nodes/DocumentView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProp'; +import { returnEmptyDocViewList } from '../StyleProvider'; import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import './CollectionTreeView.scss'; @@ -369,7 +370,7 @@ export class CollectionTreeView extends CollectionSubView { PanelWidth={this.PanelWidth} PanelHeight={this.PanelHeight} styleProvider={DefaultStyleProvider} - childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist} - childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist} + childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDocViewList} + childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDocViewList} searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist} addDocument={undefined} removeDocument={this.remDocTab} @@ -628,7 +628,7 @@ export class TabDocView extends ObservableReactComponent { dontCenter="y" whenChildContentsActiveChanged={this.whenChildContentActiveChanges} focus={this.focusFunc} - containerViewPath={returnEmptyDoclist} + containerViewPath={returnEmptyDocViewList} pinToPres={TabDocView.PinDoc} /> {this.disableMinimap() ? null : } diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 54053d038..c0fe7a894 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -4,9 +4,9 @@ import { IconButton, Size } from 'browndash-components'; import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { ClientUtils, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils'; +import { ClientUtils, lightOrDark, return18, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; -import { Doc, DocListCast, Field, FieldResult, FieldType, Opt, StrListCast } from '../../../fields/Doc'; +import { Doc, DocListCast, Field, FieldResult, FieldType, Opt, StrListCast, returnEmptyDoclist } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index b8509a005..5125bdb6c 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -1,5 +1,3 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { Button, IconButton } from 'browndash-components'; @@ -14,7 +12,7 @@ import { SettingsManager } from '../../../util/SettingsManager'; import { Transform } from '../../../util/Transform'; import { undoBatch, undoable } from '../../../util/UndoManager'; import { DocumentView } from '../../nodes/DocumentView'; -import { CollectionSubView } from '../CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView'; import './CollectionMulticolumnView.scss'; import ResizeBar from './MulticolumnResizer'; import WidthLabel from './MulticolumnWidthLabel'; @@ -42,7 +40,7 @@ const resizerWidth = 8; export class CollectionMulticolumnView extends CollectionSubView() { @observable _startIndex = 0; - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } @@ -305,7 +303,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} addDocTab={this._props.addDocTab} pinToPres={this._props.pinToPres} - dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy' + dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'x' | 'y' | 'xy'} /> ); }; diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 3fe3d5343..2833ff2f8 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -8,7 +8,7 @@ import { dropActionType } from '../../../util/DropActionTypes'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; import { DocumentView } from '../../nodes/DocumentView'; -import { CollectionSubView } from '../CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView'; import './CollectionMultirowView.scss'; import HeightLabel from './MultirowHeightLabel'; import ResizeBar from './MultirowResizer'; @@ -33,7 +33,7 @@ const resizerHeight = 8; @observer export class CollectionMultirowView extends CollectionSubView() { - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } @@ -284,7 +284,7 @@ export class CollectionMultirowView extends CollectionSubView() { whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} addDocTab={this._props.addDocTab} pinToPres={this._props.pinToPres} - dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy' + dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'y' | 'x' | 'xy'} /> ); }; diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx index 931e2c5e0..10a6fa2e9 100644 --- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx +++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx @@ -68,7 +68,7 @@ export default class ResizeBar extends React.Component { style={{ pointerEvents: this.props.isContentActive?.() ? 'all' : 'none', width: this.props.width, - backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor), + backgroundColor: !this.props.isContentActive?.() ? '' : (this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor) as string), }}>
this.registerResizing(e)} />
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx index cff0a8b4c..918365700 100644 --- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx +++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx @@ -66,7 +66,7 @@ export default class ResizeBar extends React.Component { style={{ pointerEvents: this.props.isContentActive?.() ? 'all' : 'none', height: this.props.height, - backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor), + backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor) as string, }}>
this.registerResizing(e)} />
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 6bea53355..8b0639b3b 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -4,7 +4,7 @@ import { Popup, PopupTrigger, Type } from 'browndash-components'; import { ObservableMap, action, computed, makeObservable, observable, observe, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../ClientUtils'; +import { returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../ClientUtils'; import { emptyFunction } from '../../../../Utils'; import { Doc, DocListCast, Field, FieldType, NumListCast, Opt, StrListCast } from '../../../../fields/Doc'; import { DocData } from '../../../../fields/DocSymbols'; @@ -22,12 +22,12 @@ import { ContextMenu } from '../../ContextMenu'; import { EditableView } from '../../EditableView'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { StyleProp } from '../../StyleProp'; -import { DefaultStyleProvider } from '../../StyleProvider'; +import { DefaultStyleProvider, returnEmptyDocViewList } from '../../StyleProvider'; import { Colors } from '../../global/globalEnums'; import { DocumentView } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import { FocusViewOptions } from '../../nodes/FocusViewOptions'; -import { CollectionSubView } from '../CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView'; import './CollectionSchemaView.scss'; import { SchemaColumnHeader } from './SchemaColumnHeader'; import { SchemaRowBox } from './SchemaRowBox'; @@ -49,14 +49,14 @@ const defaultColumnKeys: string[] = ['title', 'type', 'author', 'author_date', ' @observer export class CollectionSchemaView extends CollectionSubView() { - private _keysDisposer: any; + private _keysDisposer?: () => void; private _previewRef: HTMLDivElement | null = null; private _makeNewColumn: boolean = false; private _documentOptions: DocumentOptions = new DocumentOptions(); private _tableContentRef: HTMLDivElement | null = null; private _menuTarget = React.createRef(); - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } @@ -76,7 +76,7 @@ export class CollectionSchemaView extends CollectionSubView() { @observable _columnMenuIndex: number | undefined = undefined; @observable _newFieldWarning: string = ''; @observable _makeNewField: boolean = false; - @observable _newFieldDefault: any = 0; + @observable _newFieldDefault: boolean | number | string | undefined = 0; @observable _newFieldType: ColumnType = ColumnType.Number; @observable _menuValue: string = ''; @observable _filterColumnIndex: number | undefined = undefined; @@ -161,11 +161,11 @@ export class CollectionSchemaView extends CollectionSubView() { Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => this.fieldInfos.set(pair[0], pair[1])); this._keysDisposer = observe( this.dataDoc[this.fieldKey ?? 'data'] as List, - (change: any) => { + change => { switch (change.type) { case 'splice': // prettier-ignore - (change as any).added.forEach((doc: Doc) => // for each document added + change.added.filter(doc => doc instanceof Doc).map(doc => doc as Doc).forEach((doc: Doc) => // for each document added Doc.GetAllPrototypes(doc.value as Doc).forEach(proto => // for all of its prototypes (and itself) Object.keys(proto).forEach(action(key => // check if any of its keys are new, and add them !this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo("-no description-", key === 'author')))))); @@ -270,7 +270,7 @@ export class CollectionSchemaView extends CollectionSubView() { addRow = (doc: Doc | Doc[]) => this.addDocument(doc); @undoBatch - changeColumnKey = (index: number, newKey: string, defaultVal?: any) => { + changeColumnKey = (index: number, newKey: string, defaultVal?: string | number | boolean) => { if (!this.documentKeys.includes(newKey)) { this.addNewKey(newKey, defaultVal); } @@ -281,7 +281,7 @@ export class CollectionSchemaView extends CollectionSubView() { }; @undoBatch - addColumn = (key: string, defaultVal?: any) => { + addColumn = (key: string, defaultVal?: string | number | boolean) => { if (!this.documentKeys.includes(key)) { this.addNewKey(key, defaultVal); } @@ -298,7 +298,7 @@ export class CollectionSchemaView extends CollectionSubView() { }; @action - addNewKey = (key: string, defaultVal: any) => + addNewKey = (key: string, defaultVal?: string | number | boolean) => this.childDocs.forEach(doc => { doc[DocData][key] = defaultVal; }); @@ -317,7 +317,7 @@ export class CollectionSchemaView extends CollectionSubView() { }; @action - startResize = (e: any, index: number) => { + startResize = (e: React.PointerEvent, index: number) => { this._displayColumnWidths = this.storedColumnWidths; setupMoveUpEvents(this, e, moveEv => this.resizeColumn(moveEv, index), this.finishResize, emptyFunction); }; @@ -604,7 +604,7 @@ export class CollectionSchemaView extends CollectionSubView() { }; scrollToDoc = (doc: Doc, options: FocusViewOptions) => { - const found = this._tableContentRef && Array.from(this._tableContentRef.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); + const found = this._tableContentRef && Array.from(this._tableContentRef.getElementsByClassName('documentView-node')).find(node => node.id === doc[Id]); if (found) { const rect = found.getBoundingClientRect(); const localRect = this.ScreenToLocalBoxXf().transformBounds(rect.left, rect.top, rect.width, rect.height); @@ -625,9 +625,9 @@ export class CollectionSchemaView extends CollectionSubView() { type="number" name="" id="" - value={this._newFieldDefault ?? 0} + value={Number(this._newFieldDefault ?? 0)} onPointerDown={e => e.stopPropagation()} - onChange={action((e: any) => { + onChange={action(e => { this._newFieldDefault = e.target.value; })} /> @@ -637,11 +637,9 @@ export class CollectionSchemaView extends CollectionSubView() { <> e.stopPropagation()} - onChange={action((e: any) => { + onChange={action(e => { this._newFieldDefault = e.target.checked; })} /> @@ -654,9 +652,9 @@ export class CollectionSchemaView extends CollectionSubView() { type="text" name="" id="" - value={this._newFieldDefault ?? ''} + value={this._newFieldDefault?.toString() ?? ''} onPointerDown={e => e.stopPropagation()} - onChange={action((e: any) => { + onChange={action(e => { this._newFieldDefault = e.target.value; })} /> @@ -683,7 +681,7 @@ export class CollectionSchemaView extends CollectionSubView() { }; @action - setKey = (key: string, defaultVal?: any) => { + setKey = (key: string, defaultVal?: string | number | boolean) => { if (this._makeNewColumn) { this.addColumn(key, defaultVal); } else { @@ -856,16 +854,16 @@ export class CollectionSchemaView extends CollectionSubView() { onKeysPassiveWheel = (e: WheelEvent) => { // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this) - if (!this._oldKeysWheel.scrollTop && e.deltaY <= 0) e.preventDefault(); + if (!this._oldKeysWheel?.scrollTop && e.deltaY <= 0) e.preventDefault(); e.stopPropagation(); }; - _oldKeysWheel: any; + _oldKeysWheel: HTMLDivElement | null = null; @computed get keysDropdown() { return (
{ + onPointerDown={action(e => { e.stopPropagation(); this._makeNewField = true; })}> @@ -880,6 +878,7 @@ export class CollectionSchemaView extends CollectionSubView() { }}> {this._menuKeys.map(key => (
{ e.stopPropagation(); @@ -962,7 +961,7 @@ export class CollectionSchemaView extends CollectionSubView() { {this.renderFilterOptions}
{ + onPointerDown={action(e => { e.stopPropagation(); this.closeFilterMenu(); })}> @@ -1013,7 +1012,7 @@ export class CollectionSchemaView extends CollectionSubView() { screenToLocal = () => this.ScreenToLocalBoxXf().translate(-this.tableWidth, 0); previewWidthFunc = () => this.previewWidth; onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); - _oldWheel: any; + _oldWheel: HTMLDivElement | null = null; render() { return (
this.createDashEventsTarget(ele)} onDrop={this.onExternalDrop.bind(this)} onPointerMove={e => this.onPointerMove(e)}> @@ -1112,7 +1111,7 @@ export class CollectionSchemaView extends CollectionSubView() { childFiltersByRanges={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} styleProvider={DefaultStyleProvider} - containerViewPath={returnEmptyDoclist} + containerViewPath={returnEmptyDocViewList} moveDocument={this._props.moveDocument} addDocument={this.addRow} removeDocument={this._props.removeDocument} @@ -1137,7 +1136,7 @@ interface CollectionSchemaViewDocProps { @observer class CollectionSchemaViewDoc extends ObservableReactComponent { - constructor(props: any) { + constructor(props: CollectionSchemaViewDocProps) { super(props); makeObservable(this); } diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx index 6b5a34ec0..e0ed8d01e 100644 --- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx +++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx @@ -18,8 +18,8 @@ export interface SchemaColumnHeaderProps { setSort: (field: string | undefined, desc?: boolean) => void; removeColumn: (index: number) => void; rowHeight: () => number; - resizeColumn: (e: any, index: number) => void; - dragColumn: (e: any, index: number) => boolean; + resizeColumn: (e: React.PointerEvent, index: number) => void; + dragColumn: (e: PointerEvent, index: number) => boolean; openContextMenu: (x: number, y: number, index: number) => void; setColRef: (index: number, ref: HTMLDivElement) => void; } diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 760089ffb..a7e0e916b 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -58,8 +58,8 @@ export class SchemaRowBox extends ViewBoxBaseComponent() { selectCell = (doc: Doc, col: number, shift: boolean, ctrl: boolean) => this.schemaView?.selectCell(doc, col, shift, ctrl); deselectCell = () => this.schemaView?.deselectAllCells(); selectedCells = () => this.schemaView?._selectedDocs; - setColumnValues = (field: any, value: any) => this.schemaView?.setColumnValues(field, value) ?? false; - setSelectedColumnValues = (field: any, value: any) => this.schemaView?.setSelectedColumnValues(field, value) ?? false; + setColumnValues = (field: string, value: string) => this.schemaView?.setColumnValues(field, value) ?? false; + setSelectedColumnValues = (field: string, value: string) => this.schemaView?.setSelectedColumnValues(field, value) ?? false; columnWidth = computedFn((index: number) => () => this.schemaView?.displayColumnWidths[index] ?? CollectionSchemaView._minColWidth); render() { return ( diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 5874364e0..22506cac1 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -1,4 +1,3 @@ -/* eslint-disable jsx-a11y/alt-text */ /* eslint-disable react/jsx-props-no-spreading */ /* eslint-disable no-use-before-define */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -10,10 +9,10 @@ import * as React from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; import Select from 'react-select'; -import { ClientUtils, StopEvent, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../ClientUtils'; +import { ClientUtils, StopEvent, returnEmptyFilter, returnFalse, returnZero } from '../../../../ClientUtils'; import { emptyFunction } from '../../../../Utils'; import { DateField } from '../../../../fields/DateField'; -import { Doc, DocListCast, Field } from '../../../../fields/Doc'; +import { Doc, DocListCast, Field, returnEmptyDoclist } from '../../../../fields/Doc'; import { RichTextField } from '../../../../fields/RichTextField'; import { ColumnType } from '../../../../fields/SchemaHeaderField'; import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast, toList } from '../../../../fields/Types'; @@ -22,7 +21,7 @@ import { FInfo, FInfoFieldType } from '../../../documents/Documents'; import { dropActionType } from '../../../util/DropActionTypes'; import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; -import { undoBatch, undoable } from '../../../util/UndoManager'; +import { undoable } from '../../../util/UndoManager'; import { EditableView } from '../../EditableView'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DefaultStyleProvider, returnEmptyDocViewList } from '../../StyleProvider'; @@ -138,7 +137,7 @@ export class SchemaTableCell extends ObservableReactComponent selectedCell(this._props) && this._props.autoFocus && r?.setIsFocused(true)} oneLine={this._props.oneLine} allowCRs={this._props.allowCRs} - contents={undefined} + contents={''} fieldContents={fieldProps} editing={selectedCell(this._props) ? undefined : false} GetValue={() => Field.toKeyValueString(fieldProps.Document, this._props.fieldKey, SnappingManager.MetaKey)} @@ -209,7 +208,7 @@ export class SchemaTableCell extends ObservableReactComponent { - constructor(props: any) { + constructor(props: SchemaTableCellProps) { super(props); makeObservable(this); } @@ -276,7 +275,7 @@ export class SchemaImageCell extends ObservableReactComponent { - constructor(props: any) { + constructor(props: SchemaTableCellProps) { super(props); makeObservable(this); } @@ -324,7 +323,7 @@ export class SchemaDateCell extends ObservableReactComponent { - constructor(props: any) { + constructor(props: SchemaTableCellProps) { super(props); makeObservable(this); } @@ -343,7 +342,7 @@ export class SchemaRTFCell extends ObservableReactComponent { - constructor(props: any) { + constructor(props: SchemaTableCellProps) { super(props); makeObservable(this); } @@ -356,18 +355,19 @@ export class SchemaBoolCell extends ObservableReactComponent | undefined) => { - if ((value?.nativeEvent as any).shiftKey) { + onChange={undoable((value: React.ChangeEvent | undefined) => { + if ((value?.nativeEvent as MouseEvent | PointerEvent).shiftKey) { this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? '')); } else Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? '')); - })} + }, 'set bool cell')} /> + Field.toKeyValueString(this._props.Document, this._props.fieldKey)} - SetValue={undoBatch((value: string, shiftDown?: boolean, enterKey?: boolean) => { + SetValue={undoable((value: string, shiftDown?: boolean, enterKey?: boolean) => { if (shiftDown && enterKey) { this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value); this._props.finishEdit?.(); @@ -376,7 +376,7 @@ export class SchemaBoolCell extends ObservableReactComponent
); @@ -384,7 +384,7 @@ export class SchemaBoolCell extends ObservableReactComponent { - constructor(props: any) { + constructor(props: SchemaTableCellProps) { super(props); makeObservable(this); } diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx index 76a8396ff..9ce5f8fc9 100644 --- a/src/client/views/linking/LinkPopup.tsx +++ b/src/client/views/linking/LinkPopup.tsx @@ -1,9 +1,9 @@ /* eslint-disable react/require-default-props */ import { observer } from 'mobx-react'; import * as React from 'react'; -import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils'; +import { returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; -import { Doc } from '../../../fields/Doc'; +import { Doc, returnEmptyDoclist } from '../../../fields/Doc'; import { Transform } from '../../util/Transform'; import { DefaultStyleProvider, returnEmptyDocViewList } from '../StyleProvider'; import { SearchBox } from '../search/SearchBox'; diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx index c86ddb745..b060fc0b6 100644 --- a/src/client/views/newlightbox/NewLightboxView.tsx +++ b/src/client/views/newlightbox/NewLightboxView.tsx @@ -1,17 +1,15 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../../ClientUtils'; +import { returnEmptyFilter, returnTrue } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; -import { CreateLinkToActiveAudio, Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { CreateLinkToActiveAudio, Doc, DocListCast, Opt, returnEmptyDoclist } from '../../../fields/Doc'; import { InkTool } from '../../../fields/InkField'; import { Cast, NumCast, StrCast, toList } from '../../../fields/Types'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { GestureOverlay } from '../GestureOverlay'; -import { DefaultStyleProvider } from '../StyleProvider'; +import { DefaultStyleProvider, returnEmptyDocViewList } from '../StyleProvider'; import { DocumentView } from '../nodes/DocumentView'; import { OpenWhere } from '../nodes/OpenWhere'; import { ExploreView } from './ExploreView'; @@ -68,7 +66,7 @@ export class NewLightboxView extends React.Component { @action public static SetCookie(cookie: string) { if (this.LightboxDoc && cookie) { - this._docFilters = (f => (this._docFilters ? [this._docFilters.push(f) as any, this._docFilters][1] : [f]))(`cookies:${cookie}:provide`); + this._docFilters = (f => (this._docFilters ? ([this._docFilters.push(f) as unknown, this._docFilters][1] as string[]) : [f]))(`cookies:${cookie}:provide`); } } public static AddDocTab = (docsIn: Doc | Doc[], location: OpenWhere, layoutTemplate?: Doc | string) => { @@ -264,7 +262,7 @@ export class NewLightboxView extends React.Component { styleProvider={DefaultStyleProvider} ScreenToLocalTransform={this.newLightboxScreenToLocal} renderDepth={0} - containerViewPath={returnEmptyDoclist} + containerViewPath={returnEmptyDocViewList} childFilters={this.docFilters} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} @@ -320,7 +318,7 @@ export class NewLightboxView extends React.Component {
)}
- +
); diff --git a/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx b/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx index dc3339cd3..27413bac3 100644 --- a/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx +++ b/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx @@ -1,6 +1,4 @@ /* eslint-disable react/jsx-props-no-spreading */ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ /* eslint-disable guard-for-in */ import { IconButton, Size, Type } from 'browndash-components'; import * as React from 'react'; @@ -168,7 +166,8 @@ export function RecommendationList() {
{keywordsLoc && keywordsLoc.map((word, ind) => ( -
+
+ {' '} {word} )}
-
{recs && recs.map((rec: IRecommendation) => )}
+
{recs && recs.map(rec => )}
); } diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 8e74e1ca2..85aff04c3 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -2,9 +2,9 @@ import { Tooltip } from '@mui/material'; import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../ClientUtils'; +import { returnEmptyFilter, returnFalse, returnZero } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; -import { Doc, Field } from '../../../fields/Doc'; +import { Doc, Field, returnEmptyDoclist } from '../../../fields/Doc'; import { DocCast } from '../../../fields/Types'; import { DocumentOptions, FInfo } from '../../documents/Documents'; import { Transform } from '../../util/Transform'; diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index d33d12603..e39caecb6 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -1,10 +1,11 @@ import { Property } from 'csstype'; -import { action, computed, makeObservable, trace } from 'mobx'; +import { action, computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import * as textfit from 'textfit'; import { Field, FieldType } from '../../../fields/Doc'; import { BoolCast, NumCast, StrCast } from '../../../fields/Types'; +import { TraceMobx } from '../../../fields/util'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; @@ -100,7 +101,7 @@ export class LabelBox extends ViewBoxBaseComponent() { return textfitParams; }; render() { - trace(); + TraceMobx(); const boxParams = this.fitTextToBox(undefined); // this causes mobx to trigger re-render when data changes const label = this.Title.startsWith('#') ? null : this.Title; return ( diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 10ff86a38..5026f52fb 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -4,9 +4,9 @@ import { action, computed, makeObservable, observable, runInAction } from 'mobx' import { observer } from 'mobx-react'; import * as React from 'react'; import wiki from 'wikijs'; -import { returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../ClientUtils'; +import { returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; -import { Doc, Opt } from '../../../fields/Doc'; +import { Doc, Opt, returnEmptyDoclist } from '../../../fields/Doc'; import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types'; import { DocServer } from '../../DocServer'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -17,6 +17,7 @@ import { SearchUtil } from '../../util/SearchUtil'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { ObservableReactComponent } from '../ObservableReactComponent'; +import { returnEmptyDocViewList } from '../StyleProvider'; import { DocumentView } from './DocumentView'; import { StyleProviderFuncType } from './FieldView'; import './LinkDocPreview.scss'; @@ -286,7 +287,7 @@ export class LinkDocPreview extends ObservableReactComponent deselectPin = () => { if (this.selectedPin) { // Removes filter - Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'remove'); - Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'remove'); + Doc.setDocFilter(this.Document, 'latitude', NumCast(this.selectedPin.latitude), 'remove'); + Doc.setDocFilter(this.Document, 'longitude', NumCast(this.selectedPin.longitude), 'remove'); Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove'); const temp = this.selectedPin; @@ -536,8 +537,8 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent deleteSelectedPin = undoable(() => { if (this.selectedPin) { // Removes filter - Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'remove'); - Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'remove'); + Doc.setDocFilter(this.Document, 'latitude', NumCast(this.selectedPin.latitude), 'remove'); + Doc.setDocFilter(this.Document, 'longitude', NumCast(this.selectedPin.longitude), 'remove'); Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove'); this.removePushpin(this.selectedPin); diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 25adfba23..a76805960 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -1,11 +1,9 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils'; +import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils'; import { Doc, DocListCast, Opt } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; @@ -23,6 +21,7 @@ import { EditableView } from '../../EditableView'; import { Colors } from '../../global/globalEnums'; import { PinDocView } from '../../PinFuncs'; import { StyleProp } from '../../StyleProp'; +import { returnEmptyDocViewList } from '../../StyleProvider'; import { DocumentView } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { PresBox } from './PresBox'; @@ -105,7 +104,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { embedHeight = () => this.collapsedHeight + this.expandViewHeight; embedWidth = () => this._props.PanelWidth() / 2; // prettier-ignore - styleProvider = ( doc: Doc | undefined, props: Opt, property: string ): any => + styleProvider = ( doc: Doc | undefined, props: Opt, property: string ) => (property === StyleProp.Opacity ? 1 : this._props.styleProvider?.(doc, props, property)); /** * The function that is responsible for rendering a preview or not for this @@ -123,7 +122,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { hideLinkButton ScreenToLocalTransform={Transform.Identity} renderDepth={this._props.renderDepth + 1} - containerViewPath={returnEmptyDoclist} + containerViewPath={returnEmptyDocViewList} childFilters={this._props.childFilters} childFiltersByRanges={this._props.childFiltersByRanges} searchFilterDocs={this._props.searchFilterDocs} @@ -144,6 +143,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { const childDocs = DocListCast(this.targetDoc.data); const groupSlides = childDocs.map((doc: Doc, ind: number) => (
{ e.stopPropagation(); @@ -156,7 +156,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { StrCast(doc.title)} SetValue={(value: string) => { @@ -179,7 +179,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { @action headerDown = (e: React.PointerEvent) => { - const element = e.target as any; + const element = e.target as HTMLDivElement; e.stopPropagation(); e.preventDefault(); if (element && !(e.ctrlKey || e.metaKey || e.button === 2)) { @@ -580,7 +580,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { className={`presItem-slide ${isCurrent ? 'active' : ''}${activeItem.runProcess ? ' testingv2' : ''}`} style={{ display: 'infline-block', - backgroundColor: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor), + backgroundColor: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string, // layout_boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined, border: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? presBoxColor + ' solid 2.5px' : undefined) : undefined, }}> @@ -602,7 +602,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { } }} onClick={e => e.stopPropagation()}>{`${this.indexInPres + 1}. `}
- StrCast(activeItem.title)} SetValue={this.onSetValue} /> + StrCast(activeItem.title)} SetValue={this.onSetValue} />
{/*
{"Movement speed"}
}>
{this.transition}
*/} {/*
{"Duration"}
}>
{this.duration}
*/} diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index e558e14e3..a85606bc4 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -5,8 +5,8 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { Flip } from 'react-awesome-reveal'; import { FaBug } from 'react-icons/fa'; -import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils'; -import { Doc, DocListCast } from '../../../fields/Doc'; +import { returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils'; +import { Doc, DocListCast, returnEmptyDoclist } from '../../../fields/Doc'; import { AclAdmin, DashVersion } from '../../../fields/DocSymbols'; import { StrCast } from '../../../fields/Types'; import { GetEffectiveAcl } from '../../../fields/util'; @@ -33,11 +33,11 @@ import './TopBar.scss'; * and settings and help buttons. Future scope for this bar is to include the collaborators that are on the same Dashboard. */ @observer -export class TopBar extends ObservableReactComponent<{}> { +export class TopBar extends ObservableReactComponent { // eslint-disable-next-line no-use-before-define static Instance: TopBar; @observable private _flipDocumentation = 0; - constructor(props: any) { + constructor(props: object) { super(props); makeObservable(this); TopBar.Instance = this; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index e6a95fd30..7e7c319bf 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1658,6 +1658,10 @@ export namespace Doc { } } +export function returnEmptyDoclist() { + return [] as Doc[]; +} + export function RTFIsFragment(html: string) { return html.indexOf('data-pm-slice') !== -1; } diff --git a/src/mobile/ImageUpload.scss b/src/mobile/ImageUpload.scss deleted file mode 100644 index e4156ee8e..000000000 --- a/src/mobile/ImageUpload.scss +++ /dev/null @@ -1,139 +0,0 @@ -@import '../client/views/global/globalCssVariables.module.scss'; - -.imgupload_cont { - display: flex; - justify-content: center; - flex-direction: column; - align-items: center; - max-width: 400px; - min-width: 400px; - - .upload_label { - font-weight: 700; - color: black; - background-color: rgba(0, 0, 0, 0); - border: solid 3px black; - margin: 10px; - font-size: 30; - height: 70px; - width: 80%; - display: flex; - font-family: sans-serif; - text-transform: uppercase; - justify-content: center; - flex-direction: column; - border-radius: 10px; - } - - .file { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - direction: ltr; - } - - .button_file { - text-align: center; - height: 50%; - width: 50%; - background-color: paleturquoise; - color: grey; - font-size: 3em; - } - - .inputfile { - width: 0.1px; - height: 0.1px; - opacity: 0; - overflow: hidden; - position: absolute; - z-index: -1; - } - - .inputfile + label { - font-weight: 700; - color: black; - background-color: rgba(0, 0, 0, 0); - border: solid 3px black; - margin: 10px; - font-size: 30; - height: 70px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin-top: 30px; - width: 80%; - display: flex; - font-family: sans-serif; - text-transform: uppercase; - justify-content: center; - flex-direction: column; - border-radius: 10px; - } - - .inputfile.active + label { - font-style: italic; - color: black; - background-color: lightgreen; - border: solid 3px darkgreen; - } - - .status { - font-size: 2em; - } -} - -.image-upload { - top: 100%; - opacity: 0; -} - -.image-upload.active { - top: 0; - position: absolute; - z-index: 999; - height: 100vh; - width: 100vw; - opacity: 1; -} - -.uploadContainer { - top: 40; - position: absolute; - z-index: 1000; - height: 20vh; - width: 80vw; - opacity: 1; -} - -.closeUpload { - position: absolute; - border-radius: 10px; - top: 3; - color: black; - font-size: 30; - right: 3; - z-index: 1002; - padding: 0px 3px; - background: rgba(0, 0, 0, 0); - transition: 0.5s ease all; - border: 0px solid; -} - -.loadingImage { - display: inline-flex; - width: max-content; -} - -.loadingSlab { - position: relative; - width: 30px; - height: 30px; - margin: 10; - border-radius: 20px; - opacity: 0.2; - background-color: black; - transition: - all 2s, - opacity 1.5s; -} diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx deleted file mode 100644 index 36c0d6a4d..000000000 --- a/src/mobile/ImageUpload.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, observable } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import * as rp from 'request-promise'; -import { ClientUtils } from '../ClientUtils'; -import { DocServer } from '../client/DocServer'; -import { Networking } from '../client/Network'; -import { Docs } from '../client/documents/Documents'; -import { MainViewModal } from '../client/views/MainViewModal'; -import { Doc, Opt } from '../fields/Doc'; -import { List } from '../fields/List'; -import { listSpec } from '../fields/Schema'; -import { Cast } from '../fields/Types'; -import './ImageUpload.scss'; - -// eslint-disable-next-line @typescript-eslint/no-var-requires -const { DFLT_IMAGE_NATIVE_DIM } = require('../client/views/global/globalCssVariables.module.scss'); // prettier-ignore - -export interface ImageUploadProps { - Document: Doc; // Target document for upload (upload location) -} - -const inputRef = React.createRef(); -const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', '')); - -@observer -export class Uploader extends React.Component { - @observable nm: string = 'Choose files'; // Text of 'Choose Files' button - @observable process: string = ''; // Current status of upload - @observable private dialogueBoxOpacity = 1; - - onClick = async () => { - try { - // eslint-disable-next-line react/destructuring-assignment - const col = this.props.Document; - await Docs.Prototypes.initialize(); - const imgPrev = document.getElementById('img_preview'); - this.setOpacity(1, '1'); // Slab 1 - if (imgPrev && inputRef.current) { - const { files } = inputRef.current; - this.setOpacity(2, '1'); // Slab 2 - if (files && files.length !== 0) { - this.process = 'Uploading Files'; - for (let index = 0; index < files.length; ++index) { - const file = files[index]; - // eslint-disable-next-line no-await-in-loop - const res = await Networking.UploadFilesToServer({ file }); - this.setOpacity(3, '1'); // Slab 3 - // For each item that the user has selected - res.map(async ({ result }) => { - const { name } = file; - if (result instanceof Error) { - return; - } - const path = result.accessPaths.agnostic.client; - let doc = null; - // Case 1: File is a video - if (file.type === 'video/mp4') { - doc = Docs.Create.VideoDocument(path, { _nativeWidth: defaultNativeImageDim, _width: 400, title: name }); - // Case 2: File is a PDF document - } else if (file.type === 'application/pdf') { - doc = Docs.Create.PdfDocument(path, { _nativeWidth: defaultNativeImageDim, _width: 400, title: name }); - // Case 3: File is another document type (most likely Image) - } else { - doc = Docs.Create.ImageDocument(path, { _nativeWidth: defaultNativeImageDim, _width: 400, title: name }); - } - this.setOpacity(4, '1'); // Slab 4 - const docidsRes = await rp.get(ClientUtils.prepend('/getUserDocumentIds')); - if (!docidsRes) { - throw new Error('No user id returned'); - } - const field = await DocServer.GetRefField(JSON.parse(docidsRes).userDocumentId); - let pending: Opt; - if (field instanceof Doc) { - pending = col; - } - if (pending) { - const data = Cast(pending.data, listSpec(Doc)); - if (data) data.push(doc); - else pending.data = new List([doc]); - this.setOpacity(5, '1'); // Slab 5 - this.process = 'File ' + (index + 1).toString() + ' Uploaded'; - this.setOpacity(6, '1'); // Slab 6 - } - if (index + 1 === files.length) { - this.process = 'Uploads Completed'; - this.setOpacity(7, '1'); // Slab 7 - } - }); - } - // Case in which the user pressed upload and no files were selected - } else { - this.process = 'No file selected'; - } - // Three seconds after upload the menu will reset - setTimeout(this.clearUpload, 3000); - } - } catch (error) { - console.log(JSON.stringify(error)); - } - }; - - // Returns the upload interface for mobile - private get uploadInterface() { - return ( -
-
this.closeUpload()}> - -
- - - -
- Upload -
- -
-
-
-
-
-
-
-
-
-

{this.process}

-
- ); - } - - // Updates label after a files is selected (so user knows a file is uploaded) - inputLabel = async () => { - const files: FileList | null = await inputRef.current!.files; - if (files && files.length === 1) { - this.nm = files[0].name; - } else if (files && files.length > 1) { - this.nm = files.length.toString() + ' files selected'; - } - }; // Loops through load icons, and resets buttons - @action - clearUpload = () => { - for (let i = 1; i < 8; i++) { - this.setOpacity(i, '0.2'); - } - this.nm = 'Choose files'; - - if (inputRef.current) { - inputRef.current.value = ''; - } - this.process = ''; - }; - - // Clears the upload and closes the upload menu - closeUpload = () => { - this.clearUpload(); - }; - - // Handles the setting of the loading bar - setOpacity = (index: number, opacity: string) => { - const slab = document.getElementById('slab' + index); - if (slab) slab.style.opacity = opacity; - }; - - render() { - return ; - } -} diff --git a/src/mobile/InkControls.tsx b/src/mobile/InkControls.tsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/mobile/MobileInkOverlay.scss b/src/mobile/MobileInkOverlay.scss deleted file mode 100644 index b9c1fb146..000000000 --- a/src/mobile/MobileInkOverlay.scss +++ /dev/null @@ -1,39 +0,0 @@ -.mobileInkOverlay { - border: 10px dashed red; - background-color: rgba(0, 0, 0, .05); -} - -.mobileInkOverlay-border { - // background-color: rgba(0, 255, 0, .4); - position: absolute; - pointer-events: auto; - cursor: pointer; - - &.top { - width: calc(100% + 20px); - height: 10px; - top: -10px; - left: -10px; - } - - &.left { - width: 10px; - height: calc(100% + 20px); - top: -10px; - left: -10px; - } - - &.right { - width: 10px; - height: calc(100% + 20px); - top: -10px; - right: -10px; - } - - &.bottom { - width: calc(100% + 20px); - height: 10px; - bottom: -10px; - left: -10px; - } -} \ No newline at end of file diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx deleted file mode 100644 index 6babd2f39..000000000 --- a/src/mobile/MobileInkOverlay.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import { action, observable } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { DocServer } from '../client/DocServer'; -import { DragManager } from '../client/util/DragManager'; -import { Doc } from '../fields/Doc'; -import { Gestures } from '../pen-gestures/GestureTypes'; -import { GestureContent, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent } from '../server/Message'; -import './MobileInkOverlay.scss'; - -@observer -export default class MobileInkOverlay extends React.Component { - public static Instance: MobileInkOverlay; - - @observable private _scale: number = 1; - @observable private _width: number = 0; - @observable private _height: number = 0; - @observable private _x: number = -300; - @observable private _y: number = -300; - @observable private _text: string = ''; - - @observable private _offsetX: number = 0; - @observable private _offsetY: number = 0; - @observable private _isDragging: boolean = false; - private _mainCont: React.RefObject = React.createRef(); - - constructor(props: Readonly<{}>) { - super(props); - MobileInkOverlay.Instance = this; - } - - initialSize(mobileWidth: number, mobileHeight: number) { - const maxWidth = window.innerWidth - 30; - const maxHeight = window.innerHeight - 30; // -30 for padding - if (mobileWidth > maxWidth || mobileHeight > maxHeight) { - const scale = Math.min(maxWidth / mobileWidth, maxHeight / mobileHeight); - return { width: mobileWidth * scale, height: mobileHeight * scale, scale: scale }; - } - return { width: mobileWidth, height: mobileHeight, scale: 1 }; - } - - @action - initMobileInkOverlay(content: MobileInkOverlayContent) { - const { width, height, text } = content; - const scaledSize = this.initialSize(width ? width : 0, height ? height : 0); - this._width = scaledSize.width; - this._height = scaledSize.height; - this._scale = scaledSize.scale; - this._x = 300; // TODO: center on screen - this._y = 25; // TODO: center on screen - this._text = text ? text : ''; - } - - @action - updatePosition(content: UpdateMobileInkOverlayPositionContent) { - const { dx, dy, dsize } = content; - if (dx) this._x += dx; - if (dy) this._y += dy; - // TODO: scale dsize - } - - drawStroke = (content: GestureContent) => { - // TODO: figure out why strokes drawn in corner of mobile interface dont get inserted - - const { points, bounds } = content; - console.log('received points', points, bounds); - - const B = { - right: bounds.right * this._scale + this._x, - left: bounds.left * this._scale + this._x, // TODO: scale - bottom: bounds.bottom * this._scale + this._y, - top: bounds.top * this._scale + this._y, // TODO: scale - width: bounds.width * this._scale, - height: bounds.height * this._scale, - }; - - const target = document.elementFromPoint(this._x + 10, this._y + 10); - target?.dispatchEvent( - new CustomEvent('dashOnGesture', { - bubbles: true, - detail: { - points: points, - gesture: Gestures.Stroke, - bounds: B, - }, - }) - ); - }; - - uploadDocument = async (content: MobileDocumentUploadContent) => { - const { docId } = content; - const doc = await DocServer.GetRefField(docId); - - if (doc && doc instanceof Doc) { - const target = document.elementFromPoint(this._x + 10, this._y + 10); - const dragData = new DragManager.DocumentDragData([doc]); - const complete = new DragManager.DragCompleteEvent(false, dragData); - - if (target) { - console.log('dispatching upload doc!!!!', target, doc); - target.dispatchEvent( - new CustomEvent('dashOnDrop', { - bubbles: true, - detail: { - x: this._x, - y: this._y, - complete: complete, - altKey: false, - metaKey: false, - ctrlKey: false, - shiftKey: false, - embedKey: false, - }, - }) - ); - } else { - alert('TARGET IS UNDEFINED'); - } - } - }; - - @action - dragStart = (e: React.PointerEvent) => { - document.removeEventListener('pointermove', this.dragging); - document.removeEventListener('pointerup', this.dragEnd); - document.addEventListener('pointermove', this.dragging); - document.addEventListener('pointerup', this.dragEnd); - - this._isDragging = true; - this._offsetX = e.pageX - this._mainCont.current!.getBoundingClientRect().left; - this._offsetY = e.pageY - this._mainCont.current!.getBoundingClientRect().top; - - e.preventDefault(); - e.stopPropagation(); - }; - - @action - dragging = (e: PointerEvent) => { - const x = e.pageX - this._offsetX; - const y = e.pageY - this._offsetY; - - // TODO: don't allow drag over library? - this._x = Math.min(Math.max(x, 0), window.innerWidth - this._width); - this._y = Math.min(Math.max(y, 0), window.innerHeight - this._height); - - e.preventDefault(); - e.stopPropagation(); - }; - - @action - dragEnd = (e: PointerEvent) => { - document.removeEventListener('pointermove', this.dragging); - document.removeEventListener('pointerup', this.dragEnd); - - this._isDragging = false; - - e.preventDefault(); - e.stopPropagation(); - }; - - render() { - return ( -
-

{this._text}

-
-
-
-
-
- ); - } -} diff --git a/src/mobile/MobileInterface.scss b/src/mobile/MobileInterface.scss deleted file mode 100644 index 4b32c3da0..000000000 --- a/src/mobile/MobileInterface.scss +++ /dev/null @@ -1,445 +0,0 @@ -$navbar-height: 120px; -$pathbar-height: 50px; - -@media only screen and (max-device-width: 480px) { - * { - margin: 0px; - padding: 0px; - box-sizing: border-box; - font-family: sans-serif; - } -} - -body { - overflow: hidden; -} - -.mobileInterface-container { - height: 100%; - position: relative; - touch-action: none; - width: 100%; - - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -} - -// Topbar of Dash Mobile -.navbar { - position: fixed; - top: 0px; - left: 0px; - width: 100vw; - height: $navbar-height; - background-color: whitesmoke; - z-index: 150; - - .cover { - position: absolute; - right: 0px; - top: 0px; - height: 120px; - width: 120px; - background-color: whitesmoke; - z-index: 200; - } - - .toggle-btn { - position: absolute; - right: 20px; - top: 30px; - height: 70px; - width: 70px; - transition: all 400ms ease-in-out 200ms; - z-index: 180; - } - - .background { - position: absolute; - right: 0px; - top: 0px; - height: 120px; - width: 120px; - //border: 1px solid black; - } - - .background.active { - background-color: lightgrey; - } - - .toggle-btn-home { - right: -200px; - } - - .header { - position: absolute; - top: 50%; - top: calc(9px + 50%); - right: 50%; - transform: translate(50%, -50%); - font-size: 40; - font-weight: 700; - text-align: center; - user-select: none; - text-transform: uppercase; - font-family: Arial, Helvetica, sans-serif; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - direction: ltr; - width: 600px; - } - - .toggle-btn span { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 70%; - height: 4px; - background: black; - transition: all 200ms ease; - z-index: 180; - } - - .toggle-btn span:nth-child(1) { - transition: top 200ms ease-in-out; - top: 30%; - } - - .toggle-btn span:nth-child(3) { - transition: top 200ms ease-in-out; - top: 70%; - } - - .toggle-btn.active { - transition: transform 200ms ease-in-out 200ms; - transform: rotate(135deg); - } - - .toggle-btn.active span:nth-child(1) { - top: 50%; - } - - .toggle-btn.active span:nth-child(2) { - transform: translate(-50%, -50%) rotate(90deg); - } - - .toggle-btn.active span:nth-child(3) { - top: 50%; - } -} - -.sidebar { - position: fixed; - top: 120px; - opacity: 0; - right: -100%; - width: 80%; - height: calc(80% - (120px)); - z-index: 101; - background-color: whitesmoke; - transition: all 400ms ease 50ms; - padding: 20px; - box-shadow: 0 0 5px 5px grey; - - .item { - width: 100%; - padding: 13px 12px; - border-bottom: 1px solid rgba(200, 200, 200, 0.7); - font-family: Arial, Helvetica, sans-serif; - font-style: normal; - font-weight: normal; - user-select: none; - display: inline-flex; - font-size: 35px; - text-transform: uppercase; - color: black; - } - - .ink:focus { - outline: 1px solid blue; - } - - .sidebarButtons { - top: 80px; - position: relative; - } -} - - - - - - -.blanket { - position: fixed; - top: 120px; - opacity: 0.5; - right: -100%; - width: 100%; - height: calc(100% - (120px)); - z-index: 101; - background-color: grey; - padding: 20px; -} - -.blanket.active { - position: absolute; - right: 0%; - z-index: 100; -} - -.home { - position: absolute; - top: 30px; - left: 30px; - font-size: 60; - user-select: none; - text-transform: uppercase; - font-family: Arial, Helvetica, sans-serif; - z-index: 200; -} - -.item-type { - display: inline; - text-transform: lowercase; - margin-left: 20px; - font-size: 35px; - font-style: italic; - color: rgb(28, 28, 28); -} - -.item-title { - max-width: 70%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.right { - margin-left: 20px; - z-index: 200; -} - -.open { - right: 20px; - font-size: 35; - position: absolute; -} - -.left { - width: 100%; - height: 100%; -} - - - -.sidebar.active { - position: absolute; - right: 0%; - opacity: 1; - z-index: 101; -} - -.back { - position: absolute; - left: 42px; - top: 0; - background: #1a1a1a; - width: 50px; - height: 100%; - display: flex; - justify-content: center; - text-align: center; - flex-direction: column; - align-items: center; - border-radius: 10px; - font-size: 25px; - user-select: none; - z-index: 100; -} - -.pathbar { - position: fixed; - top: 118px; - left: 0px; - background: #1a1a1a; - z-index: 120; - border-radius: 0px; - width: 100%; - height: 80px; - overflow: hidden; - - .pathname { - position: relative; - font-size: 25; - top: 50%; - width: 86%; - left: 12%; - color: whitesmoke; - transform: translate(0%, -50%); - z-index: 20; - font-family: sans-serif; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - direction: rtl; - text-align: left; - text-transform: uppercase; - } - - .scrollmenu { - overflow: auto; - width: 100%; - height: 100%; - white-space: nowrap; - display: inline-flex; - } - - .hidePath { - position: absolute; - height: 100%; - width: 200px; - left: 0px; - top: 0px; - background-image: linear-gradient(to right, #1a1a1a, rgba(0, 0, 0, 0)); - text-align: center; - user-select: none; - z-index: 99; - pointer-events: none; - } - - .pathbarItem { - position: relative; - display: flex; - align-items: center; - color: whitesmoke; - text-align: center; - justify-content: center; - user-select: none; - transform: translate(100px, 0px); - font-size: 30px; - padding: 10px; - text-transform: uppercase; - - .pathbarText { - font-family: sans-serif; - text-align: center; - height: 50px; - padding: 10px; - font-size: 30px; - border-radius: 10px; - text-transform: uppercase; - margin-left: 20px; - position: relative; - } - - .pathIcon { - transform: translate(0px, 0px); - position: relative; - } - } -} - - -/** -* docButton appears at the bottom of mobile document -* Buttons include: pin to presentation, download, upload, reload -*/ -.docButton { - position: relative; - width: 100px; - display: flex; - height: 100px; - font-size: 70px; - text-align: center; - border: 3px solid black; - margin: 20px; - z-index: 100; - border-radius: 100%; - justify-content: center; - flex-direction: column; - align-items: center; -} - -.docButtonContainer { - top: 80%; - position: absolute; - display: flex; - transform: translate(-50%, 0); - left: 50%; - z-index: 100; -} - -.toolbar { - left: 50%; - transform: translate(-50%); - position: absolute; - height: max-content; - top: 0px; - border-radius: 20px; - background-color: lightgrey; - opacity: 0; - transition: all 400ms ease 50ms; -} - -.toolbar.active { - display: inline-block; - width: 300px; - padding: 5px; - opacity: 1; - height: max-content; - top: -450px; -} - -.colorSelector { - position: absolute; - top: 550px; - left: 280px; - transform: translate(-50%, 0); - z-index: 100; - display: inline-flex; - width: max-content; - height: max-content; - pointer-events: all; - font-size: 80px; - user-select: none; -} - -// Menu buttons for toggling between list and icon view -.homeSwitch { - position: fixed; - top: 212; - right: 36px; - display: inline-flex; - width: max-content; - z-index: 99; - height: 70px; - - .list { - width: 70px; - height: 70px; - margin: 5; - padding: 10; - align-items: center; - text-align: center; - font-size: 50; - border-style: solid; - border-width: 3; - border-color: black; - background: whitesmoke; - align-self: center; - border-radius: 10px; - } - - .list.active { - color: darkred; - border-color: darkred; - } -} \ No newline at end of file diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx deleted file mode 100644 index 4f37c45a8..000000000 --- a/src/mobile/MobileInterface.tsx +++ /dev/null @@ -1,871 +0,0 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; -import { - faAddressCard, - faAlignLeft, - faAlignRight, - faAngleDoubleLeft, - faAngleRight, - faArrowDown, - faArrowLeft, - faArrowRight, - faArrowUp, - faArrowsAltH, - faAsterisk, - faBars, - faBell, - faBolt, - faBook, - faBrain, - faBullseye, - faCalculator, - faCamera, - faCaretDown, - faCaretLeft, - faCaretRight, - faCaretSquareDown, - faCaretSquareRight, - faCaretUp, - faCat, - faCheck, - faChevronLeft, - faChevronRight, - faClipboard, - faClone, - faCloudUploadAlt, - faCommentAlt, - faCompressArrowsAlt, - faCut, - faEdit, - faEllipsisV, - faEraser, - faExclamation, - faExpand, - faExternalLinkAlt, - faExternalLinkSquareAlt, - faEye, - faFileAlt, - faFileAudio, - faFileDownload, - faFilePdf, - faFilm, - faFilter, - faFolderOpen, - faFont, - faGlobeAsia, - faHandPointLeft, - faHighlighter, - faHome, - faImage, - faLocationArrow, - faLongArrowAltLeft, - faLongArrowAltRight, - faMicrophone, - faCircleHalfStroke, - faMinus, - faMobile, - faMousePointer, - faMusic, - faObjectGroup, - faPaintBrush, - faPalette, - faPause, - faPen, - faPenNib, - faPhone, - faPlay, - faPlus, - faPortrait, - faQuestionCircle, - faQuoteLeft, - faRedoAlt, - faReply, - faSearch, - faStamp, - faStickyNote, - faStop, - faTasks, - faTerminal, - faTh, - faThLarge, - faThumbtack, - faTimes, - faToggleOn, - faTrash, - faTrashAlt, - faTree, - faTv, - faUndoAlt, - faVideo, - faWindowClose, - faWindowMaximize, - faFile as fileSolid, -} from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, runInAction } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../ClientUtils'; -import { CollectionViewType, DocumentType } from '../client/documents/DocumentTypes'; -import { Docs, DocumentOptions } from '../client/documents/Documents'; -import { CurrentUserUtils } from '../client/util/CurrentUserUtils'; -import { ScriptingGlobals } from '../client/util/ScriptingGlobals'; -import { SettingsManager } from '../client/util/SettingsManager'; -import { Transform } from '../client/util/Transform'; -import { UndoManager } from '../client/util/UndoManager'; -import { DashboardView } from '../client/views/DashboardView'; -import { GestureOverlay } from '../client/views/GestureOverlay'; -import { AudioBox } from '../client/views/nodes/AudioBox'; -import { DocumentView } from '../client/views/nodes/DocumentView'; -import { RadialMenu } from '../client/views/nodes/RadialMenu'; -import { RichTextMenu } from '../client/views/nodes/formattedText/RichTextMenu'; -import { Doc, DocListCast } from '../fields/Doc'; -import { InkTool } from '../fields/InkField'; -import { List } from '../fields/List'; -import { ScriptField } from '../fields/ScriptField'; -import { Cast, FieldValue, StrCast } from '../fields/Types'; -import './AudioUpload.scss'; -import { Uploader } from './ImageUpload'; -import './ImageUpload.scss'; -import './MobileInterface.scss'; -import { emptyFunction } from '../Utils'; - -library.add( - ...[ - faTasks, - faReply, - faQuoteLeft, - faHandPointLeft, - faFolderOpen, - faAngleDoubleLeft, - faExternalLinkSquareAlt, - faMobile, - faThLarge, - faWindowClose, - faEdit, - faTrashAlt, - faPalette, - faAngleRight, - faBell, - faTrash, - faCamera, - faExpand, - faCaretDown, - faCaretLeft, - faCaretRight, - faCaretSquareDown, - faCaretSquareRight, - faArrowsAltH, - faPlus, - faMinus, - faTerminal, - faToggleOn, - fileSolid, - faExternalLinkAlt, - faLocationArrow, - faSearch, - faFileDownload, - faStop, - faCalculator, - faWindowMaximize, - faAddressCard, - faQuestionCircle, - faArrowLeft, - faArrowRight, - faArrowDown, - faArrowUp, - faBolt, - faBullseye, - faCaretUp, - faCat, - faCheck, - faChevronRight, - faClipboard, - faClone, - faCloudUploadAlt, - faCommentAlt, - faCompressArrowsAlt, - faCut, - faEllipsisV, - faEraser, - faExclamation, - faFileAlt, - faFileAudio, - faFilePdf, - faFilm, - faFilter, - faFont, - faGlobeAsia, - faHighlighter, - faLongArrowAltRight, - faMicrophone, - faCircleHalfStroke, - faMousePointer, - faMusic, - faObjectGroup, - faPause, - faPen, - faPenNib, - faPhone, - faPlay, - faPortrait, - faRedoAlt, - faStamp, - faStickyNote, - faThumbtack, - faTree, - faTv, - faUndoAlt, - faBook, - faVideo, - faAsterisk, - faBrain, - faImage, - faPaintBrush, - faTimes, - faEye, - faHome, - faLongArrowAltLeft, - faBars, - faTh, - faChevronLeft, - faAlignLeft, - faAlignRight, - ].map(m => m as any) -); - -@observer -export class MobileInterface extends React.Component { - static Instance: MobileInterface; - private _library: Doc; - private _mainDoc: any = CurrentUserUtils.setupActiveMobileMenu(Doc.UserDoc()); - @observable private _sidebarActive: boolean = false; //to toggle sidebar display - @observable private _imageUploadActive: boolean = false; //to toggle image upload - @observable private _audioUploadActive: boolean = false; - @observable private _menuListView: boolean = false; //to switch between menu view (list / icon) - @observable private _ink: boolean = false; //toggle whether ink is being dispalyed - @observable private _homeMenu: boolean = true; // to determine whether currently at home menu - @observable private dashboards: Doc | null = null; // currently selected document - @observable private _activeDoc: Doc = this._mainDoc; // doc updated as the active mobile page is updated (initially home menu) - @observable private _homeDoc: Doc = this._mainDoc; // home menu as a document - @observable private _parents: Array = []; // array of parent docs (for pathbar) - - @computed private get mainContainer() { - return Doc.UserDoc() ? FieldValue(Cast(Doc.UserDoc().activeMobile, Doc)) : Doc.GuestMobile; - } - - constructor(props: Readonly<{}>) { - super(props); - this._library = CurrentUserUtils.setupDashboards(Doc.UserDoc(), 'myDashboards'); // to access documents in Dash Web - MobileInterface.Instance = this; - } - - @action - componentDidMount() { - // if the home menu is in list view -> adjust the menu toggle appropriately - this._menuListView = this._homeDoc._type_collection === 'stacking' ? true : false; - Doc.ActiveTool = InkTool.None; // ink should intially be set to none - Doc.UserDoc().activeMobile = this._homeDoc; // active mobile set to home - - // remove double click to avoid mobile zoom in - document.removeEventListener('dblclick', this.onReactDoubleClick); - document.addEventListener('dblclick', this.onReactDoubleClick); - } - - @action - componentWillUnmount = () => { - document.removeEventListener('dblclick', this.onReactDoubleClick); - }; - - // Prevent zooming in when double tapping the screen - onReactDoubleClick = (e: MouseEvent) => { - e.stopPropagation(); - }; - - // Switch the mobile view to the given doc - @action - switchCurrentView = (doc: Doc, renderView?: () => JSX.Element, onSwitch?: () => void) => { - if (!Doc.UserDoc()) return; - if (this._activeDoc === this._homeDoc) { - this._parents.push(this._activeDoc); - this._homeMenu = false; - } - this._activeDoc = doc; - Doc.UserDoc().activeMobile = doc; - onSwitch?.(); - - // Ensures that switching to home is not registed - UndoManager.undoStack.length = 0; - UndoManager.redoStack.length = 0; - }; - - // For toggling the hamburger menu - @action - toggleSidebar = () => { - this._sidebarActive = !this._sidebarActive; - - if (this._ink) { - this.onSwitchInking(); - } - }; - /** - * Method called when 'Library' button is pressed on the home screen - */ - switchToLibrary = async () => { - this.switchCurrentView(this._library); - runInAction(() => (this._homeMenu = false)); - this.toggleSidebar(); - }; - - /** - * Back method for navigating through items - */ - @action - back = () => { - const header = document.getElementById('header') as HTMLElement; - const doc = Cast(this._parents.pop(), Doc) as Doc; // Parent document - // Case 1: Parent document is 'dashboards' - if (doc === (Cast(this._library, Doc) as Doc)) { - this.dashboards = null; - this.switchCurrentView(this._library); - // Case 2: Parent document is the 'home' menu (root node) - } else if (doc === (Cast(this._homeDoc, Doc) as Doc)) { - this._homeMenu = true; - this._parents = []; - this.dashboards = null; - this.switchCurrentView(this._homeDoc); - // Case 3: Parent document is any document - } else if (doc) { - this.dashboards = doc; - this.switchCurrentView(doc); - this._homeMenu = false; - header.textContent = String(doc.title); - } - this._ink = false; // turns ink off - }; - - /** - * Return 'Home", which implies returning to 'Home' menu buttons - */ - @action - returnHome = () => { - if (!this._homeMenu || this._sidebarActive) { - this._homeMenu = true; - this._parents = []; - this.dashboards = null; - this.switchCurrentView(this._homeDoc); - } - if (this._sidebarActive) { - this.toggleSidebar(); - } - }; - - /** - * Return to primary Dashboard in library (Dashboards Doc) - */ - @action - returnMain = () => { - this._parents = [this._homeDoc]; - this.switchCurrentView(this._library); - this._homeMenu = false; - this.dashboards = null; - }; - - /** - * Note: window.innerWidth and window.screen.width compute different values. - * window.screen.width is the display size, however window.innerWidth is the - * display resolution which computes differently. - */ - returnWidth = () => window.innerWidth; //The windows width - returnHeight = () => window.innerHeight - 300; //Calculating the windows height (-300 to account for topbar) - whitebackground = () => 'white'; - /** - * DocumentView for graphic display of all documents - */ - @computed get displayDashboards() { - return !this.mainContainer ? null : ( -
- -
- ); - } - - /** - * Handles the click functionality in the library panel. - * Navigates to the given doc and updates the sidebar. - * @param doc: doc for which the method is called - */ - handleClick = async (doc: Doc) => { - runInAction(() => { - if (doc.type !== 'collection' && this._sidebarActive) { - this._parents.push(this._activeDoc); - this.switchCurrentView(doc); - this._homeMenu = false; - this.toggleSidebar(); - } else { - this._parents.push(this._activeDoc); - this.switchCurrentView(doc); - this._homeMenu = false; - this.dashboards = doc; - } - }); - }; - - /** - * Called when an item in the library is clicked and should - * be opened (open icon on RHS of all menu items) - * @param doc doc to be opened - */ - @action - openFromSidebar = (doc: Doc) => { - this._parents.push(this._activeDoc); - this.switchCurrentView(doc); - this._homeMenu = false; - this.dashboards = doc; - this.toggleSidebar(); - }; - - // Renders the graphical pathbar - renderPathbar = () => { - const docPath = [...this._parents, this._activeDoc]; - const items = docPath.map((doc: Doc, index: any) => ( -
- {index === 0 ? null : } -
this.handlePathClick(doc, index)}> - {StrCast(doc.title)} -
-
- )); - return ( -
-
{items}
- {!this._parents.length ? null : ( -
- -
- )} -
-
- ); - }; - - // Handles when user clicks on a document in the pathbar - @action - handlePathClick = async (doc: Doc, index: number) => { - const library = await this._library; - if (doc === library) { - this.dashboards = null; - this.switchCurrentView(doc); - this._parents.length = index; - } else if (doc === this._homeDoc) { - this.returnHome(); - } else { - this.dashboards = doc; - this.switchCurrentView(doc); - this._parents.length = index; - } - }; - - // Renders the contents of the menu and sidebar - @computed get renderDefaultContent() { - if (this._homeMenu) { - return ( -
-
- - -
e.stopPropagation()}>
- -
- {this.renderPathbar()} -
- ); - } - // stores dashboards documents as 'dashboards' variable - let dashboards = Doc.MyDashboards; - if (this.dashboards) { - dashboards = this.dashboards; - } - // returns a list of navbar buttons as 'buttons' - const buttons = DocListCast(dashboards.data).map((doc: Doc, index: any) => { - if (doc.type !== 'ink') { - return ( -
-
this.handleClick(doc)}> - {' '} - {doc.title as string}{' '} -
-
this.handleClick(doc)}> - {doc.type as string} -
- this.handleClick(doc)} className="right" icon="angle-right" size="lg" style={{ display: `${doc.type === 'collection' ? 'block' : 'none'}` }} /> - this.openFromSidebar(doc)} icon="external-link-alt" size="lg" /> -
- ); - } - }); - - return ( -
-
- - -
- - - -
-
-
- {this.renderPathbar()} -
-
- {this.dashboards ? ( - <> - {buttons} -
- -
Return to dashboards
-
- - ) : ( - <> - {buttons} -
this.createNewDashboard()}> - -
Create New Dashboard
-
- - )} -
-
-
-
- ); - } - - /** - * Handles the 'Create New Dashboard' button in the menu (taken from MainView.tsx) - */ - @action - createNewDashboard = (id?: string) => { - const scens = Doc.MyDashboards; - const dashboardCount = DocListCast(scens.data).length + 1; - const freeformOptions: DocumentOptions = { - x: 0, - y: 400, - title: 'Collection ' + dashboardCount, - }; - - const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); - const dashboardDoc = DashboardView.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, 'row'); - - const toggleComic = ScriptField.MakeScript(`toggleComicMode()`); - const cloneDashboard = ScriptField.MakeScript(`cloneDashboard()`); - dashboardDoc.contextMenuScripts = new List([toggleComic!, cloneDashboard!]); - dashboardDoc.contextMenuLabels = new List(['Toggle Comic Mode', 'New Dashboard Layout']); - - Doc.AddDocToList(scens, 'data', dashboardDoc); - }; - - // Button for switching between pen and ink mode - @action - onSwitchInking = () => { - const button = document.getElementById('inkButton') as HTMLElement; - button.style.backgroundColor = this._ink ? 'white' : 'black'; - button.style.color = this._ink ? 'black' : 'white'; - - if (!this._ink) { - Doc.ActiveTool = InkTool.Pen; - this._ink = true; - } else { - Doc.ActiveTool = InkTool.None; - this._ink = false; - } - }; - - // The static ink menu that appears at the top - @computed get inkMenu() { - return this._activeDoc._type_collection !== CollectionViewType.Docking || !this._ink ? null :
{/* */}
; - } - - // DocButton that uses UndoManager and handles the opacity change if CanUndo is true - @computed get undo() { - if (this.mainContainer && this._activeDoc.type === 'collection' && this._activeDoc !== this._homeDoc && this._activeDoc !== Doc.SharingDoc() && this._activeDoc.title !== 'WORKSPACES') { - return ( -
{ - UndoManager.Undo(); - e.stopPropagation(); - }}> - -
- ); - } else return null; - } - - // DocButton that uses UndoManager and handles the opacity change if CanRedo is true - @computed get redo() { - if (this.mainContainer && this._activeDoc.type === 'collection' && this._activeDoc !== this._homeDoc && this._activeDoc !== Doc.SharingDoc() && this._activeDoc.title !== 'WORKSPACES') { - return ( -
{ - UndoManager.Redo(); - e.stopPropagation(); - }}> - -
- ); - } else return null; - } - - // DocButton for switching into ink mode - @computed get drawInk() { - return !this.mainContainer || this._activeDoc._type_collection !== CollectionViewType.Docking ? null : ( -
- -
- ); - } - - // DocButton: Button that appears on the bottom of the screen to initiate image upload - @computed get uploadImageButton() { - if (this._activeDoc.type === DocumentType.COL && this._activeDoc !== this._homeDoc && this._activeDoc._type_collection !== CollectionViewType.Docking && this._activeDoc.title !== 'WORKSPACES') { - return ( -
- -
- ); - } else return null; - } - - // DocButton to download images on the mobile - @computed get downloadDocument() { - if (this._activeDoc.type === 'image' || this._activeDoc.type === 'pdf' || this._activeDoc.type === 'video') { - return ( -
window.open(this._activeDoc['data-path']?.toString())}> - {' '} - {/* daa-path holds the url */} - -
- ); - } else return null; - } - - // DocButton for pinning images to presentation - @computed get pinToPresentation() { - // Only making button available if it is an image - if (!(this._activeDoc.type === 'collection' || this._activeDoc.type === 'presentation')) { - return ( -
DocumentView.PinDoc(this._activeDoc, {})}> - -
- ); - } else return null; - } - - // Buttons for switching the menu between large and small icons - @computed get switchMenuView() { - return this._activeDoc.title !== this._homeDoc.title ? null : ( -
-
- -
-
- -
-
- ); - } - - // Logic for switching the menu into the icons - @action - changeToIconView = () => { - if ((this._homeDoc._type_collection = 'stacking')) { - this._menuListView = false; - this._homeDoc._type_collection = 'masonry'; - this._homeDoc.columnWidth = 300; - this._homeDoc._columnWidth = 300; - const menuButtons = DocListCast(this._homeDoc.data); - menuButtons.map(doc => { - const buttonData = DocListCast(doc.data); - buttonData[1]._nativeWidth = 0.1; - buttonData[1]._width = 0.1; - buttonData[1]._dimMagnitude = 0; - buttonData[1]._opacity = 0; - doc._nativeWidth = 400; - }); - } - }; - - // Logic for switching the menu into the stacking view - @action - changeToListView = () => { - if ((this._homeDoc._type_collection = 'masonry')) { - this._homeDoc._type_collection = 'stacking'; - this._menuListView = true; - const menuButtons = DocListCast(this._homeDoc.data); - menuButtons.map(doc => { - const buttonData = DocListCast(doc.data); - buttonData[1]._nativeWidth = 450; - buttonData[1]._dimMagnitude = 2; - buttonData[1]._opacity = 1; - doc._nativeWidth = 900; - }); - } - }; - - // For setting up the presentation document for the home menu - @action - setupDefaultPresentation = () => { - const presentation = Doc.ActivePresentation; - - if (presentation) { - this.switchCurrentView(presentation); - this._homeMenu = false; - } - }; - - // For toggling image upload pop up - @action - toggleUpload = () => (this._imageUploadActive = !this._imageUploadActive); - - // For toggling audio record and dictate pop up - @action - toggleAudio = () => (this._audioUploadActive = !this._audioUploadActive); - - // Button for toggling the upload pop up in a collection - @action - toggleUploadInCollection = () => { - const button = document.getElementById('imageButton') as HTMLElement; - button.style.backgroundColor = this._imageUploadActive ? 'white' : 'black'; - button.style.color = this._imageUploadActive ? 'black' : 'white'; - - this._imageUploadActive = !this._imageUploadActive; - }; - - // For closing the image upload pop up - @action - closeUpload = () => { - this._imageUploadActive = false; - }; - - // Returns the image upload pop up - @computed get uploadImage() { - const doc = !this._homeMenu ? this._activeDoc : (Cast(Doc.SharingDoc(), Doc) as Doc); - return ; - } - - // Radial menu can only be used if it is a colleciton and it is not a homeDoc - // (and cannot be used on Dashboard to avoid pin to presentation opening on right) - @computed get displayRadialMenu() { - return this._activeDoc.type === 'collection' && this._activeDoc !== this._homeDoc && this._activeDoc._type_collection !== CollectionViewType.Docking ? : null; - } - - onDragOver = (e: React.DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - }; - - /** - * MENU BUTTON - * Switch view from mobile menu to access the mobile uploads - * Global function name: openMobileUploads() - */ - @action - switchToMobileUploads = () => { - const mobileUpload = Cast(Doc.SharingDoc(), Doc) as Doc; - this.switchCurrentView(mobileUpload); - this._homeMenu = false; - }; - - render() { - return ( -
- -
{this.uploadImage}
- {this.switchMenuView} - {this.inkMenu} - -
- -
-
- {this.pinToPresentation} - {this.downloadDocument} - {this.undo} - {this.redo} - {this.drawInk} - {this.uploadImageButton} -
- {this.displayDashboards} - {this.renderDefaultContent} -
- {this.displayRadialMenu} -
- ); - } -} - -//Global functions for mobile menu -ScriptingGlobals.add(function switchToMobileLibrary() { - return MobileInterface.Instance.switchToLibrary(); -}, 'opens the library to navigate through dashboards on Dash Mobile'); -ScriptingGlobals.add(function openMobileUploads() { - return MobileInterface.Instance.toggleUpload(); -}, 'opens the upload files menu for Dash Mobile'); -ScriptingGlobals.add(function switchToMobileUploadCollection() { - return MobileInterface.Instance.switchToMobileUploads(); -}, 'opens the mobile uploads collection on Dash Mobile'); -ScriptingGlobals.add(function openMobileAudio() { - return MobileInterface.Instance.toggleAudio(); -}, 'opens the record and dictate menu on Dash Mobile'); -ScriptingGlobals.add(function switchToMobilePresentation() { - return MobileInterface.Instance.setupDefaultPresentation(); -}, 'opens the presentation on Dash Mobile'); -ScriptingGlobals.add(function openMobileSettings() { - return SettingsManager.Instance.openMgr(); -}, 'opens settings on Dash Mobile'); - -// Other global functions for mobile -ScriptingGlobals.add( - function switchMobileView(doc: Doc, renderView?: () => JSX.Element, onSwitch?: () => void) { - return MobileInterface.Instance.switchCurrentView(doc, renderView, onSwitch); - }, - 'changes the active document displayed on the Dash Mobile', - '(doc: any)' -); diff --git a/src/mobile/MobileMain.tsx b/src/mobile/MobileMain.tsx deleted file mode 100644 index 07839b6f6..000000000 --- a/src/mobile/MobileMain.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { DocServer } from '../client/DocServer'; -import { Docs } from '../client/documents/Documents'; -import { CurrentUserUtils } from '../client/util/CurrentUserUtils'; -import { AssignAllExtensions } from '../extensions/Extensions'; -import { MobileInterface } from './MobileInterface'; - -AssignAllExtensions(); - -(async () => { - const info = await CurrentUserUtils.loadCurrentUser(); - DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email + ' (mobile)'); - await Docs.Prototypes.initialize(); - await CurrentUserUtils.loadUserDocument(info); - document.getElementById('root')!.addEventListener( - 'wheel', - event => { - if (event.ctrlKey) { - event.preventDefault(); - } - }, - true - ); - ReactDOM.render(, document.getElementById('root')); -})(); diff --git a/src/mobile/MobileMenu.scss b/src/mobile/MobileMenu.scss deleted file mode 100644 index 7f286efc4..000000000 --- a/src/mobile/MobileMenu.scss +++ /dev/null @@ -1,271 +0,0 @@ -$navbar-height: 120px; -$pathbar-height: 50px; - -* { - margin: 0px; - padding: 0px; - box-sizing: border-box; - font-family: "Open Sans"; -} - -body { - overflow: hidden; -} - -.navbar { - position: fixed; - top: 0px; - left: 0px; - width: 100vw; - height: $navbar-height; - background-color: whitesmoke; - border-bottom: 5px solid black; -} - -.navbar .toggle-btn { - position: absolute; - right: 20px; - top: 30px; - height: 70px; - width: 70px; - transition: all 300ms ease-in-out 200ms; -} - -.navbar .header { - position: absolute; - top: 50%; - top: calc(9px + 50%); - right: 50%; - transform: translate(50%, -50%); - font-size: 40; - user-select: none; - text-transform: uppercase; - font-family: Arial, Helvetica, sans-serif; -} - -.navbar .toggle-btn span { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 70%; - height: 4px; - background: black; - transition: all 200ms ease; -} - -.navbar .toggle-btn span:nth-child(1) { - transition: top 200ms ease-in-out; - top: 30%; -} - -.navbar .toggle-btn span:nth-child(3) { - transition: top 200ms ease-in-out; - top: 70%; -} - -.navbar .toggle-btn.active { - transition: transform 200ms ease-in-out 200ms; - transform: rotate(135deg); -} - -.navbar .toggle-btn.active span:nth-child(1) { - top: 50%; -} - -.navbar .toggle-btn.active span:nth-child(2) { - transform: translate(-50%, -50%) rotate(90deg); -} - -.navbar .toggle-btn.active span:nth-child(3) { - top: 50%; -} -// .navbar .home { -// position: relative; -// right: 5px; -// transform: translate(50%, -50%); -// font-size: 40; -// user-select: none; -// text-transform: uppercase; -// font-family: Arial, Helvetica, sans-serif; -// z-index: 200; -// } - -.sidebar { - position: absolute; - top: 200px; - opacity: 0; - right: -100%; - width: 100%; - height: calc(100% - (200px)); - z-index: 5; - background-color: whitesmoke; - transition: all 400ms ease 50ms; - padding: 20px; - // overflow-y: auto; - // -webkit-overflow-scrolling: touch; - - // border-right: 5px solid black; -} - -.sidebar .item { - width: 100%; - padding: 13px 12px; - border-bottom: 1px solid rgba(200, 200, 200, 0.7); - font-family: Arial, Helvetica, sans-serif; - font-style: normal; - font-weight: normal; - user-select: none; - font-size: 35px; - text-transform: uppercase; - color: black; - -} - -.sidebar .ink { - width: 100%; - padding: 13px 12px; - border-bottom: 1px solid rgba(200, 200, 200, 0.7); - font-family: Arial, Helvetica, sans-serif; - font-style: normal; - font-weight: normal; - user-select: none; - font-size: 35px; - text-transform: uppercase; - color: black; -} - -.sidebar .ink:focus { - outline: 1px solid blue; -} - -.sidebar .home { - position: absolute; - top: -135px; - right: calc(50% + 80px); - transform: translate(0%, -50%); - font-size: 40; - user-select: none; - text-transform: uppercase; - font-family: Arial, Helvetica, sans-serif; - z-index: 200; -} - -.type { - display: inline; - text-transform: lowercase; - margin-left: 20px; - font-size: 35px; - font-style: italic; - color: rgb(28, 28, 28); -} - -.right { - margin-left: 20px; - z-index: 200; -} - -.left { - width: 100%; - height: 100%; -} - -.sidebar .logout { - width: 100%; - padding: 13px 12px; - border-bottom: 1px solid rgba(200, 200, 200, 0.7); - font-family: Arial, Helvetica, sans-serif; - font-style: normal; - font-weight: normal; - user-select: none; - font-size: 30px; - text-transform: uppercase; - color: black; -} - -.sidebar .settings { - width: 100%; - padding: 13px 12px; - border-bottom: 1px solid rgba(200, 200, 200, 0.7); - font-family: Arial, Helvetica, sans-serif; - font-style: normal; - font-weight: normal; - user-select: none; - font-size: 30px; - text-transform: uppercase; - color: black; -} - - -.sidebar.active { - right: 0%; - opacity: 1; -} - -.back { - position: absolute; - top: -140px; - left: 50px; - transform: translate(0%, -50%); - color: black; - font-size: 60; - user-select: none; - text-transform: uppercase; - z-index: 100; - font-family: Arial, Helvetica, sans-serif; -} - - -.pathbar { - position: absolute; - top: 118px; - background: #1a1a1a; - z-index: 20; - border-radius: 0px; - width: 100%; - height: 80px; - transition: all 400ms ease 50ms; -} - -.pathname { - position: relative; - font-size: 25; - top: 50%; - width: 90%; - left: 3%; - color: whitesmoke; - transform: translate(0%, -50%); - z-index: 20; - font-family: sans-serif; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - direction: rtl; - text-align: left; - text-transform: uppercase; -} - -.homeContainer { - position: relative; - top: 200px; - height: calc(100% - 250px); - width: 90%; - overflow: scroll; - left: 5%; - background-color: lightpink; -} - -.pinButton { - position: relative; - width: 100px; - height: 100px; - font-size: 90px; - text-align: center; - left: 50%; - transform: translate(-50%, 0); - border-style: solid; - border-radius: 50px; - border-width: medium; - background-color: pink; - z-index: 100; -} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 9c74bf24e..e1afc64e5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -53,8 +53,6 @@ module.exports = { viewer: ['./src/debug/Viewer.tsx', 'webpack-hot-middleware/client?reload=true'], repl: ['./src/debug/Repl.tsx', 'webpack-hot-middleware/client?reload=true'], test: ['./src/debug/Test.tsx', 'webpack-hot-middleware/client?reload=true'], - inkControls: ['./src/mobile/InkControls.tsx', 'webpack-hot-middleware/client?reload=true'], - mobileInterface: ['./src/client/views/Main.tsx', 'webpack-hot-middleware/client?reload=true'], }, devtool: 'source-map', output: { @@ -119,6 +117,10 @@ module.exports = { { loader: 'sass-loader' }, ], }, + { + test: /\.(txt|d)$/i, + type: 'asset/source', + }, // -------- // SCSS MODULES - all have .module. in their name and can export to .tsx -- cgit v1.2.3-70-g09d2 From 25ee9e6b3f7da67bcf94eb2affd5793c67777930 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 21 Aug 2024 17:04:32 -0400 Subject: cleanup of face recognition. some lint fixes. --- eslint.config.mjs | 8 + src/.DS_Store | Bin 10244 -> 10244 bytes src/client/util/CurrentUserUtils.ts | 5 - src/client/util/DocumentManager.ts | 1 - src/client/views/MainView.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 4 +- .../collectionFreeForm/FaceCollectionBox.tsx | 121 ++++++------- src/client/views/nodes/DocumentView.tsx | 8 +- src/client/views/search/FaceRecognitionHandler.tsx | 190 +++++++++++++++------ src/fields/Doc.ts | 6 +- src/fields/RichTextUtils.ts | 4 - src/server/database.ts | 3 - src/server/server_Initialization.ts | 2 +- 13 files changed, 198 insertions(+), 156 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/eslint.config.mjs b/eslint.config.mjs index 04655ce13..119f2f486 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -29,6 +29,7 @@ export default [ 'import/prefer-default-export': 'off', 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': 'off', 'prefer-template': 'off', 'no-inner-declarations': 'off', 'no-plusplus': 'off', @@ -42,6 +43,13 @@ export default [ '@typescript-eslint/no-unused-vars': 'error', '@typescript-eslint/no-namespace': 'off', 'react/destructuring-assignment': 0, + 'prefer-arrow-callback': 'error', + 'no-return-assign': 'error', + 'no-await-in-loop': 'error', + 'no-loop-func': 'error', + 'no-conditional-assign': 'error', + 'no-use-before-define': 'error', + 'no-explicit-any': 'error', 'no-restricted-globals': ['error', 'event'], }, }, diff --git a/src/.DS_Store b/src/.DS_Store index 7a0b53ce0..426a2ee90 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 2962682c2..0681a21cb 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -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)); } @@ -183,7 +179,6 @@ 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)", }); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index a10237fcc..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()); } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 62ad0a3fb..9f1c7da3d 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -75,7 +75,7 @@ import { AnchorMenu } from './pdf/AnchorMenu'; import { GPTPopup } from './pdf/GPTPopup/GPTPopup'; import { TopBar } from './topbar/TopBar'; -// eslint-disable-next-line @typescript-eslint/no-var-requires +// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore @observer diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index a6768ab35..5782d407e 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,4 +1,4 @@ -import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; +import { action, computed, makeObservable, observable } from 'mobx'; import * as React from 'react'; import * as rp from 'request-promise'; import { ClientUtils, returnFalse } from '../../../ClientUtils'; @@ -9,7 +9,7 @@ import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, DocCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx index d5a2809dc..94f9a3c94 100644 --- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx +++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx @@ -3,28 +3,28 @@ import { IconButton, Size } from 'browndash-components'; import * as faceapi from 'face-api.js'; import { FaceMatcher } from 'face-api.js'; import 'ldrs/ring'; -import { action, computed, makeObservable, observable } from 'mobx'; +import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import React from 'react'; -import { Utils } from '../../../../Utils'; -import { Doc, DocListCast } from '../../../../fields/Doc'; -import { DocData } from '../../../../fields/DocSymbols'; +import { setupMoveUpEvents } from '../../../../ClientUtils'; +import { Utils, emptyFunction } from '../../../../Utils'; +import { Doc, Opt } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; -import { listSpec } from '../../../../fields/Schema'; -import { Cast, ImageCast, StrCast } from '../../../../fields/Types'; +import { ImageCast } from '../../../../fields/Types'; import { DocumentType } from '../../../documents/DocumentTypes'; import { Docs } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; +import { dropActionType } from '../../../util/DropActionTypes'; import { SnappingManager } from '../../../util/SnappingManager'; import { undoable } from '../../../util/UndoManager'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DocumentView } from '../../nodes/DocumentView'; import { FieldView, FieldViewProps } from '../../nodes/FieldView'; +import { FaceRecognitionHandler } from '../../search/FaceRecognitionHandler'; import './FaceCollectionBox.scss'; import { MarqueeOptionsMenu } from './MarqueeOptionsMenu'; -import { FaceRecognitionHandler } from '../../search/FaceRecognitionHandler'; interface FaceDocumentProps { faceDoc: Doc; @@ -50,49 +50,29 @@ export class FaceDocumentItem extends ObservableReactComponent doc.type === DocumentType.IMG); - filteredDocs.forEach(doc => { - // If the current Face Document has no items, and the doc has more than one face descriptor, don't let the user add the document first. - if ((this._props.faceDoc[DocData].face_descriptors as List>).length === 0 && (doc[DocData][FaceRecognitionHandler.FacesField(doc)] as List>).length > 1) { + de.complete.docDragData?.droppedDocuments + ?.filter(doc => doc.type === DocumentType.IMG) + .forEach(imgDoc => { + // If the current Face Document has no faces, and the doc has more than one face descriptor, don't let the user add the document first. Or should we just use the first face ? + if (FaceRecognitionHandler.FaceDocDescriptors(this._props.faceDoc).length === 0 && FaceRecognitionHandler.ImageDocFaceDescriptors(imgDoc).length > 1) { alert('Cannot add a document with multiple faces as the first item!'); } else { - // Loop through the documents' face descriptors. - // Choose the face with the smallest distance to add. - const float32Array = (this._props.faceDoc[DocData].face_descriptors as List>).map(faceDescriptor => new Float32Array(Array.from(faceDescriptor))); - const labeledFaceDescriptor = new faceapi.LabeledFaceDescriptors(StrCast(this._props.faceDoc[DocData].face_label), float32Array); - const faceDescriptors: faceapi.LabeledFaceDescriptors[] = [labeledFaceDescriptor]; - - const faceMatcher = new FaceMatcher(faceDescriptors, 1); - let cur_lowest_distance = 1; - let cur_matching_face = new List(); - - (doc[DocData][FaceRecognitionHandler.FacesField(doc)] as List>).forEach(face => { - // If the face has the current lowest distance, mark it as such - // Once that lowest distance is found, add the face descriptor to the faceDoc, and add the associated doc - const convered_32_array: Float32Array = new Float32Array(Array.from(face)); - const match = faceMatcher.matchDescriptor(convered_32_array); - - if (match.distance < cur_lowest_distance) { - cur_lowest_distance = match.distance; - cur_matching_face = face; - } - }); - - const faceFieldKey = FaceRecognitionHandler.FaceField(doc, this._props.faceDoc); - if (doc[DocData][faceFieldKey]) { - Cast(doc[DocData][faceFieldKey], listSpec('number'), null).push(cur_matching_face as unknown as number); // items are lists of numbers, not numbers, but type system can't handle that - } else { - doc[DocData][faceFieldKey] = new List>([cur_matching_face]); - } - - Doc.AddDocToList(this._props.faceDoc[DocData], 'face_docList', doc); - Cast(this._props.faceDoc[DocData].face_descriptors, listSpec('number'), null).push(cur_matching_face as unknown as number); // items are lists of numbers, not numbers, but type system can't handle that + // Loop through the documents' face descriptors and choose the face in the iage with the smallest distance (most similar to the face colleciton) + const faceDescriptorsAsFloat32Array = FaceRecognitionHandler.FaceDocDescriptors(this._props.faceDoc).map(fd => new Float32Array(Array.from(fd))); + const labeledFaceDescriptor = new faceapi.LabeledFaceDescriptors(FaceRecognitionHandler.FaceDocLabel(this._props.faceDoc), faceDescriptorsAsFloat32Array); + const faceMatcher = new FaceMatcher([labeledFaceDescriptor], 1); + const { face_match } = FaceRecognitionHandler.ImageDocFaceDescriptors(imgDoc).reduce( + (prev, face) => { + const match = faceMatcher.matchDescriptor(new Float32Array(Array.from(face))); + return match.distance < prev.dist ? { dist: match.distance, face_match: face } : prev; + }, + { dist: 1, face_match: new List() as Opt> } + ); + + // assign the face in the image that's closest to the face collection to be the face that's assigned to the collection + face_match && FaceRecognitionHandler.ImageDocAddFace(imgDoc, face_match, this._props.faceDoc); } }); - return false; - } return false; } @@ -108,23 +88,16 @@ export class FaceDocumentItem extends ObservableReactComponent { - if (Doc.ActiveDashboard) { - Doc.RemoveDocFromList(Doc.ActiveDashboard[DocData], 'faceDocuments', this._props.faceDoc); - } - }, 'remove face'); + FaceRecognitionHandler.DeleteFaceDoc(this._props.faceDoc); + }, 'delete face'); /** * Deletes a document from a Face Document's associated docs list. * @param doc */ - @action - deleteAssociatedDoc = (doc: Doc) => { - this._props.faceDoc[DocData].face_descriptors = new List>( - (this._props.faceDoc[DocData].face_descriptors as List>).filter(fd => !(doc[DocData][FaceRecognitionHandler.FaceField(doc, this._props.faceDoc)] as List>).includes(fd)) - ); - doc[DocData][FaceRecognitionHandler.FaceField(doc, this._props.faceDoc)] = new List>(); - Doc.RemoveDocFromList(this._props.faceDoc[DocData], 'face_docList', doc); - }; + deleteAssociatedDoc = undoable((imgDoc: Doc) => { + FaceRecognitionHandler.FaceDocRemoveImageDocFace(imgDoc, this._props.faceDoc); + }, 'remove doc from face'); render() { return ( @@ -133,7 +106,7 @@ export class FaceDocumentItem extends ObservableReactComponent
-

{StrCast(this._props.faceDoc[DocData].face_label)}

+

{FaceRecognitionHandler.FaceDocLabel(this._props.faceDoc)}

{this._displayImages ? (
- {DocListCast(this._props.faceDoc[DocData].face_docList).map(doc => { + {FaceRecognitionHandler.FaceDocFaces(this._props.faceDoc).map(doc => { const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.'); return ( -
+
+ setupMoveUpEvents( + this, + e, + () => { + DragManager.StartDocumentDrag([e.target as HTMLElement], new DragManager.DocumentDragData([doc], dropActionType.embed), e.clientX, e.clientY); + return true; + }, + emptyFunction, + emptyFunction + ) + }> DocumentView.showDocument(doc, { willZoomCentered: true })} style={{ maxWidth: '60px', margin: '10px' }} src={`${name}_o.${type}`} />
this.deleteAssociatedDoc(doc)} icon={'x'} style={{ width: '4px' }} size={Size.XSMALL} /> @@ -168,25 +155,15 @@ export class FaceCollectionBox extends ViewBoxBaseComponent() { return FieldView.LayoutString(FaceCollectionBox, fieldKey); } - public static Instance: FaceCollectionBox; - - @computed get currentDocs() { - if (Doc.ActiveDashboard) { - return DocListCast(Doc.ActiveDashboard[DocData].faceDocuments); - } - return []; - } - constructor(props: FieldViewProps) { super(props); makeObservable(this); - FaceCollectionBox.Instance = this; } render() { return (
- {this.currentDocs.map(doc => ( + {FaceRecognitionHandler.FaceDocuments().map(doc => ( ))}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c807d99ac..5efdb3df4 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,5 +1,4 @@ /* eslint-disable no-use-before-define */ -/* eslint-disable react/jsx-props-no-spreading */ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { Property } from 'csstype'; import { Howl } from 'howler'; @@ -341,7 +340,6 @@ export class DocumentViewInternal extends DocComponent { if (this._props.isGroupActive?.() === GroupActive.child && !this._props.isDocumentActive?.()) return; - // eslint-disable-next-line no-use-before-define this._longPressSelector = setTimeout(() => SnappingManager.LongPress && this._props.select(false), 1000); if (!DocumentView.DownDocView) DocumentView.DownDocView = this._docView; @@ -405,7 +402,6 @@ export class DocumentViewInternal extends DocComponent() { ) ); } - // eslint-disable-next-line default-param-last + public static FocusOrOpen(docIn: Doc, optionsIn: FocusViewOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) { let doc = docIn; const options = optionsIn; diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx index dc271fe73..3ef6f9674 100644 --- a/src/client/views/search/FaceRecognitionHandler.tsx +++ b/src/client/views/search/FaceRecognitionHandler.tsx @@ -10,7 +10,22 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; /** - * A class that handles face recognition. + * A singleton class that handles face recognition and manages face Doc collections for each face found. + * Displaying an image doc anywhere will trigger this class to test if the image contains any faces. + * If it does, each recognized face will be compared to a global set of faces (each is a face collection Doc + * that have already been found. If the face matches a face collection Doc, then it will be added to that + * collection along with the numerical representation of the face, its face descriptor. + * + * Image Doc's that are added to one or more face collection Docs will be given these metadata fields: + * _Face - a nunerical representation of the Nth face found in the image + * _Faces - a list of all the numerical face representations found in the image (why is this needed?) + * + * Face collection Doc's are created for each person identified and are stored in the Dashboard's faceDocument's list + * + * Each Face collection Doc represents all the images found for that person. It has these fields: + * face_label - a string label for the person that was recognized (currently it's just a 'face#') + * face_descriptors - a list of all the face descriptors for different images of the person + * face_docList - a list of all image Docs that contain a face for the person */ export class FaceRecognitionHandler { static _instance: FaceRecognitionHandler; @@ -18,13 +33,90 @@ export class FaceRecognitionHandler { private _processingDocs: Set = new Set(); private _pendingLoadDocs: Doc[] = []; - public static FaceField = (target: Doc, doc: Doc) => `${Doc.LayoutFieldKey(target)}_${doc.face_label}`; - public static FacesField = (target: Doc) => `${Doc.LayoutFieldKey(target)}_Faces`; + private static imgDocFaceField = (imgDoc: Doc, faceDoc: Doc) => `${Doc.LayoutFieldKey(imgDoc)}_${FaceRecognitionHandler.FaceDocLabel(faceDoc)}`; + /** + * initializes an image with an empty list of face descriptors + * @param imgDoc image to initialize + */ + private static initImageDocFaceDescriptors = (imgDoc: Doc) => { + imgDoc[DocData][`${Doc.LayoutFieldKey(imgDoc)}_Faces`] = new List>(); + }; + /** + * returns the face descriptors for each face found on an image Doc + * @param imgDoc + * @returns list of face descriptors + */ + public static ImageDocFaceDescriptors = (imgDoc: Doc) => imgDoc[DocData][`${Doc.LayoutFieldKey(imgDoc)}_Faces`] as List>; + + /** + * Adds metadata to an image Doc describing a face found in the image + * @param imgDoc image Doc containing faces + * @param faceDescriptor descriptor for the face found + * @param faceDoc face collection Doc containing the same face + */ + public static ImageDocAddFace = (imgDoc: Doc, faceDescriptor: List, faceDoc: Doc) => { + const faceFieldKey = FaceRecognitionHandler.imgDocFaceField(imgDoc, faceDoc); + if (imgDoc[DocData][faceFieldKey]) { + Cast(imgDoc[DocData][faceFieldKey], listSpec('number'), null).push(faceDescriptor as unknown as number); // items are lists of numbers, not numbers, but type system can't handle that + } else { + imgDoc[DocData][faceFieldKey] = new List>([faceDescriptor]); + } + }; + + /** + * returns a list of all face collection Docs on the current dashboard + * @returns face collection Doc list + */ + public static FaceDocuments = () => DocListCast(Doc.ActiveDashboard?.[DocData].faceDocuments); + + public static DeleteFaceDoc = (faceDoc: Doc) => Doc.ActiveDashboard && Doc.RemoveDocFromList(Doc.ActiveDashboard[DocData], 'faceDocuments', faceDoc); + + /** + * returns the labels associated with a face collection Doc + * @param faceDoc the face collection Doc + * @returns label string + */ + public static FaceDocLabel = (faceDoc: Doc) => StrCast(faceDoc[DocData].face_label); + /** + * Returns all the face descriptors associated with a face collection Doc + * @param faceDoc a face collection Doc + * @returns face descriptors + */ + public static FaceDocDescriptors = (faceDoc: Doc) => faceDoc[DocData].face_descriptors as List>; + + /** + * Returns a list of all face image Docs associated with the face collection + * @param faceDoc a face collection Doc + * @returns image Docs + */ + public static FaceDocFaces = (faceDoc: Doc) => DocListCast(faceDoc[DocData].face_docList); + + /** + * Adds a face image to the list of faces in a face collection Doc, and updates the face collection's list of image descriptors + * @param img - image with faces to add to a face collection Doc + * @param faceDescriptor - the face descriptor for the face in the image to add + * @param faceDoc - the face collection Doc + */ + public static FaceDocAddImageDocFace = (img: Doc, faceDescriptor: List, faceDoc: Doc) => { + Doc.AddDocToList(faceDoc, 'face_docList', img); + Cast(faceDoc.face_descriptors, listSpec('number'), null).push(faceDescriptor as unknown as number); // items are lists of numbers, not numbers, but type system can't handle that + }; + + /** + * Removes a face from a face Doc collection, and updates the face collection's list of image descriptors + * @param imgDoc - image with faces to remove from the face Doc collectoin + * @param faceDoc - the face Doc collection + */ + public static FaceDocRemoveImageDocFace = (imgDoc: Doc, faceDoc: Doc) => { + imgDoc[DocData][FaceRecognitionHandler.imgDocFaceField(imgDoc, faceDoc)] = new List>(); + Doc.RemoveDocFromList(faceDoc[DocData], 'face_docList', imgDoc); + faceDoc[DocData].face_descriptors = new List>(FaceRecognitionHandler.FaceDocDescriptors(faceDoc).filter(fd => !(imgDoc[DocData][FaceRecognitionHandler.imgDocFaceField(imgDoc, faceDoc)] as List>).includes(fd))); + }; constructor() { FaceRecognitionHandler._instance = this; - this.loadModels().then(() => this._pendingLoadDocs.forEach(this.findMatches)); - DocumentManager.Instance.AddAnyViewRenderedCB(dv => FaceRecognitionHandler.Instance.findMatches(dv.Document)); + this.loadModels().then(() => this._pendingLoadDocs.forEach(this.classifyFacesInImage)); + DocumentManager.Instance.AddAnyViewRenderedCB(dv => FaceRecognitionHandler.Instance.classifyFacesInImage(dv.Document)); } @computed get examinedFaceDocs() { @@ -48,48 +140,42 @@ export class FaceRecognitionHandler { /** * When a document is added, look for matching face documents. - * @param doc The document being analyzed. + * @param imgDoc The document being analyzed. */ - public findMatches = async (doc: Doc) => { + public classifyFacesInImage = async (imgDoc: Doc) => { if (!this._loadedModels || !Doc.ActiveDashboard) { - this._pendingLoadDocs.push(doc); + this._pendingLoadDocs.push(imgDoc); return; } - if (doc.type === DocumentType.LOADING && !doc.loadingError) { - setTimeout(() => this.findMatches(doc), 1000); + if (imgDoc.type === DocumentType.LOADING && !imgDoc.loadingError) { + setTimeout(() => this.classifyFacesInImage(imgDoc), 1000); return; } - const imgUrl = ImageCast(doc[Doc.LayoutFieldKey(doc)]); + const imgUrl = ImageCast(imgDoc[Doc.LayoutFieldKey(imgDoc)]); // If the doc isn't an image or currently already been examined or is being processed, stop examining the document. - if (!imgUrl || this.examinedFaceDocs.includes(doc) || this._processingDocs.has(doc)) { + if (!imgUrl || this.examinedFaceDocs.includes(imgDoc) || this._processingDocs.has(imgDoc)) { return; } // Mark the document as being processed. - this._processingDocs.add(doc); + this._processingDocs.add(imgDoc); + FaceRecognitionHandler.initImageDocFaceDescriptors(imgDoc); // Get the image the document contains and analyze for faces. const [name, type] = imgUrl.url.href.split('.'); const imageURL = `${name}_o.${type}`; - const img = await this.loadImage(imageURL); - - const fullFaceDescriptions = await faceapi.detectAllFaces(img).withFaceLandmarks().withFaceDescriptors(); - - doc[DocData][FaceRecognitionHandler.FacesField(doc)] = new List>(); + const imgDocFaceDescriptions = await faceapi.detectAllFaces(img).withFaceLandmarks().withFaceDescriptors(); // For each face detected, find a match. - for (const fd of fullFaceDescriptions) { - let match = this.findMatch(fd.descriptor); - const converted_list = new List(Array.from(fd.descriptor)); - - if (match) { - // If a matching Face Document has been found, add the document to the Face Document's associated docs and append the face - // descriptor to the Face Document's descriptor list. - Doc.AddDocToList(match, 'face_docList', doc); - Cast(match.face_descriptors, listSpec('number'), null).push(converted_list as unknown as number); // items are lists of numbers, not numbers, but type system can't handle that + for (const fd of imgDocFaceDescriptions) { + let faceDocMatch = this.findMatchingFaceDoc(fd.descriptor); + const faceDescriptor = new List(Array.from(fd.descriptor)); + + if (faceDocMatch) { + FaceRecognitionHandler.FaceDocAddImageDocFace(imgDoc, faceDescriptor, faceDocMatch); } else { // If a matching Face Document has not been found, create a new Face Document. Doc.UserDoc().faceDocNum = NumCast(Doc.UserDoc().faceDocNum) + 1; @@ -98,53 +184,45 @@ export class FaceRecognitionHandler { newFaceDocument.title = `Face ${Doc.UserDoc().faceDocNum}`; newFaceDocument.face = ''; // just to make prettyprinting look better newFaceDocument.face_label = `Face${Doc.UserDoc().faceDocNum}`; - newFaceDocument.face_docList = new List([doc]); - newFaceDocument.face_descriptors = new List>([converted_list]); + newFaceDocument.face_docList = new List([imgDoc]); + newFaceDocument.face_descriptors = new List>([faceDescriptor]); Doc.AddDocToList(Doc.ActiveDashboard[DocData], 'faceDocuments', newFaceDocument); - match = newFaceDocument; + faceDocMatch = newFaceDocument; } // Assign a field in the document of the matching Face Document. - const faceDescripField = FaceRecognitionHandler.FaceField(doc, match); - if (doc[DocData][faceDescripField]) { - Cast(doc[DocData][faceDescripField], listSpec('number'), null).push(converted_list as unknown as number); // items are lists of numbers, not numbers, but type system can't handle that - } else { - doc[DocData][faceDescripField] = new List>([converted_list]); - } - - Cast(doc[DocData][FaceRecognitionHandler.FacesField(doc)], listSpec('number'), null).push(converted_list as unknown as number); // items are lists of numbers, not numbers, but type system can't handle that - - Doc.AddDocToList(Doc.UserDoc(), 'examinedFaceDocs', doc); + FaceRecognitionHandler.ImageDocAddFace(imgDoc, faceDescriptor, faceDocMatch); + Doc.AddDocToList(Doc.UserDoc(), 'examinedFaceDocs', imgDoc); } - this._processingDocs.delete(doc); + this._processingDocs.delete(imgDoc); }; /** - * Finds a matching Face Document given a descriptor - * @param cur_descriptor The current descriptor whose match is being searched for. - * @returns The most similar Face Document. + * Finds the most similar matching Face Document to a face descriptor + * @param faceDescriptor face descriptor number list + * @returns face Doc */ - private findMatch(cur_descriptor: Float32Array) { - if (!Doc.ActiveDashboard || DocListCast(Doc.ActiveDashboard[DocData].faceDocuments).length < 1) { - return null; + private findMatchingFaceDoc = (faceDescriptor: Float32Array) => { + if (!Doc.ActiveDashboard || FaceRecognitionHandler.FaceDocuments().length < 1) { + return undefined; } - const faceDescriptors: faceapi.LabeledFaceDescriptors[] = DocListCast(Doc.ActiveDashboard[DocData].faceDocuments).map(faceDocument => { - const float32Array = (faceDocument[DocData].face_descriptors as List>).map(faceDescriptor => new Float32Array(Array.from(faceDescriptor))); - return new faceapi.LabeledFaceDescriptors(StrCast(faceDocument[DocData].face_label), float32Array); + const faceDescriptors = FaceRecognitionHandler.FaceDocuments().map(faceDoc => { + const float32Array = FaceRecognitionHandler.FaceDocDescriptors(faceDoc).map(fd => new Float32Array(Array.from(fd))); + return new faceapi.LabeledFaceDescriptors(FaceRecognitionHandler.FaceDocLabel(faceDoc), float32Array); }); const faceMatcher = new FaceMatcher(faceDescriptors, 0.6); - const match = faceMatcher.findBestMatch(cur_descriptor); + const match = faceMatcher.findBestMatch(faceDescriptor); if (match.label !== 'unknown') { - for (const doc of DocListCast(Doc.ActiveDashboard[DocData].faceDocuments)) { - if (doc[DocData].face_label === match.label) { - return doc; + for (const faceDoc of FaceRecognitionHandler.FaceDocuments()) { + if (FaceRecognitionHandler.FaceDocLabel(faceDoc) === match.label) { + return faceDoc; } } } - return null; - } + return undefined; + }; /** * Loads an image diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index eb6ed9757..ffb5aab79 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-namespace */ -/* eslint-disable default-param-last */ /* eslint-disable no-use-before-define */ import { action, computed, makeObservable, observable, ObservableMap, ObservableSet, runInAction } from 'mobx'; import { computedFn } from 'mobx-utils'; @@ -127,9 +125,7 @@ export type FieldResult = Opt | FieldWaiting * If no default value is given, and the returned value is not undefined, it can be safely modified. */ export function DocListCastAsync(field: FieldResult): Promise; -// eslint-disable-next-line no-redeclare export function DocListCastAsync(field: FieldResult, defaultValue: Doc[]): Promise; -// eslint-disable-next-line no-redeclare export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) { const list = Cast(field, listSpec(Doc)); return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue); @@ -437,7 +433,6 @@ export class Doc extends RefField { const writeMode = DocServer.getFieldWriteMode(fKey); if (fKey.startsWith('acl_') || writeMode !== DocServer.WriteMode.Playground) { delete this[CachedUpdates][fKey]; - // eslint-disable-next-line no-await-in-loop await fn(); } else { this[CachedUpdates][fKey] = fn; @@ -1576,6 +1571,7 @@ export namespace Doc { try { resolved = JSON.parse(typeof data === 'string' ? data : JSON.stringify(data)); } catch (e) { + console.error(e); return undefined; } let output: Opt; diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts index d1316d256..b3534dde7 100644 --- a/src/fields/RichTextUtils.ts +++ b/src/fields/RichTextUtils.ts @@ -394,13 +394,11 @@ export namespace RichTextUtils { for (const markName of Object.keys(schema.marks)) { // eslint-disable-next-line no-cond-assign if (ignored.includes(markName) || !(mark = markMap[markName])) { - // eslint-disable-next-line no-continue continue; } let converted = MarkToStyle.get(markName) || (markName as keyof docsV1.Schema$TextStyle); let value: unknown = true; if (!converted) { - // eslint-disable-next-line no-continue continue; } // eslint-disable-next-line @typescript-eslint/no-shadow @@ -412,10 +410,8 @@ export namespace RichTextUtils { const docDelimeter = '/doc/'; const alreadyShared = '?sharing=true'; if (new RegExp(window.location.origin + docDelimeter).test(url) && !url.endsWith(alreadyShared)) { - // eslint-disable-next-line no-await-in-loop const linkDoc = await DocServer.GetRefField(url.split(docDelimeter)[1]); if (linkDoc instanceof Doc) { - // eslint-disable-next-line no-await-in-loop let exported = (await Cast(linkDoc.link_anchor_2, Doc))!; if (!exported.customLayout) { exported = Doc.MakeEmbedding(exported); diff --git a/src/server/database.ts b/src/server/database.ts index 975b9eb80..10dc540c3 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-namespace */ import * as mongodb from 'mongodb'; import * as mongoose from 'mongoose'; import { Opt } from '../fields/Doc'; @@ -148,9 +147,7 @@ export namespace Database { } public delete(query: any, collectionName?: string): Promise; - // eslint-disable-next-line no-dupe-class-members public delete(id: string, collectionName?: string): Promise; - // eslint-disable-next-line no-dupe-class-members public delete(idIn: any, collectionName = DocumentsCollection) { let id = idIn; if (typeof id === 'string') { diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts index 97c63a93e..0cf9a6e58 100644 --- a/src/server/server_Initialization.ts +++ b/src/server/server_Initialization.ts @@ -115,7 +115,7 @@ function registerEmbeddedBrowseRelativePathHandler(server: express.Express) { // eslint-disable-next-line @typescript-eslint/no-explicit-any function proxyServe(req: any, requrl: string, response: any) { - // eslint-disable-next-line global-require, @typescript-eslint/no-require-imports + // eslint-disable-next-line global-require, @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires const htmlBodyMemoryStream = new (require('memorystream'))(); let wasinBrFormat = false; const sendModifiedBody = () => { -- cgit v1.2.3-70-g09d2