diff options
Diffstat (limited to 'src/client/views/collections')
36 files changed, 615 insertions, 632 deletions
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx index a08a7c7c1..9eb16917b 100644 --- a/src/client/views/collections/CollectionCalendarView.tsx +++ b/src/client/views/collections/CollectionCalendarView.tsx @@ -6,11 +6,11 @@ import { dateRangeStrToDates, returnTrue } from '../../../ClientUtils'; import { Doc, DocListCast } from '../../../fields/Doc'; import { StrCast } from '../../../fields/Types'; import { CollectionStackingView } from './CollectionStackingView'; -import { CollectionSubView } from './CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; @observer export class CollectionCalendarView extends CollectionSubView() { - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index de46180e6..28a769896 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -18,7 +18,7 @@ import { StyleProp } from '../StyleProp'; import { DocumentView } from '../nodes/DocumentView'; import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup'; import './CollectionCardDeckView.scss'; -import { CollectionSubView } from './CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; enum cardSortings { Time = 'time', @@ -68,7 +68,7 @@ export class CollectionCardView extends CollectionSubView() { } }; - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } @@ -86,11 +86,11 @@ export class CollectionCardView extends CollectionSubView() { } @computed get cardSort_customField() { - return StrCast(this.Document.cardSort_customField) as any as 'chat' | 'star' | 'idea' | 'like'; + return StrCast(this.Document.cardSort_customField) as 'chat' | 'star' | 'idea' | 'like'; } @computed get cardSort() { - return StrCast(this.Document.cardSort) as any as cardSortings; + return StrCast(this.Document.cardSort) as cardSortings; } /** * how much to scale down the contents of the view so that everything will fit @@ -428,7 +428,6 @@ export class CollectionCardView extends CollectionSubView() { return ( <div className="card-button-container" style={{ width: `${totalWidth}px` }}> {numberRange(amButtons).map(i => ( - // eslint-disable-next-line jsx-a11y/control-has-associated-label <button key={i} type="button" @@ -496,8 +495,8 @@ export class CollectionCardView extends CollectionSubView() { className="collectionCardView-outer" ref={this.createDashEventsTarget} style={{ - background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor), - color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color), + background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string, + color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string, }}> <div className="card-wrapper" diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 27c85533f..c799eb3c8 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.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 { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; @@ -15,8 +13,9 @@ import { StyleProp } from '../StyleProp'; import { DocumentView } from '../nodes/DocumentView'; import { FocusViewOptions } from '../nodes/FocusViewOptions'; import './CollectionCarousel3DView.scss'; -import { CollectionSubView } from './CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; +// eslint-disable-next-line @typescript-eslint/no-var-requires const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss'); @observer @@ -24,7 +23,7 @@ export class CollectionCarousel3DView extends CollectionSubView() { @computed get scrollSpeed() { return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; // default scroll speed } - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } @@ -180,8 +179,8 @@ export class CollectionCarousel3DView extends CollectionSubView() { className="collectionCarousel3DView-outer" ref={this.createDashEventsTarget} style={{ - background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor), - color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color), + background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string, + color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string, }}> <div className="carousel-wrapper" style={{ transform: `translateX(${this.translateX}px)` }}> {this.content} diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 2adad68e0..4bec2d963 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -1,5 +1,3 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ /* eslint-disable react/jsx-props-no-spreading */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { computed, makeObservable } from 'mobx'; @@ -12,13 +10,12 @@ import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; import { ContextMenu } from '../ContextMenu'; -import { ContextMenuProps } from '../ContextMenuItem'; import { StyleProp } from '../StyleProp'; import { DocumentView } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import './CollectionCarouselView.scss'; -import { CollectionSubView } from './CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; enum cardMode { PRACTICE = 'practice', @@ -35,7 +32,7 @@ export class CollectionCarouselView extends CollectionSubView() { get practiceField() { return this.fieldKey + "_practice"; } // prettier-ignore get starField() { return this.fieldKey + "_star"; } // prettier-ignore - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } @@ -124,7 +121,7 @@ export class CollectionCarouselView extends CollectionSubView() { this.advance(e); }; - captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<FieldViewProps>, property: string): any => { + captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<FieldViewProps>, property: string) => { // first look for properties on the document in the carousel, then fallback to properties on the container const childValue = doc?.['caption_' + property] ? this._props.styleProvider?.(doc, captionProps, property) : undefined; return childValue ?? this._props.styleProvider?.(this.layoutDoc, captionProps, property); @@ -137,7 +134,7 @@ export class CollectionCarouselView extends CollectionSubView() { const cm = ContextMenu.Instance; const revealOptions = cm.findByDescription('Filter Flashcards'); - const revealItems: ContextMenuProps[] = revealOptions && 'subitems' in revealOptions ? revealOptions.subitems : []; + const revealItems = revealOptions?.subitems ?? []; revealItems.push({description: 'All', event: () => {this.layoutDoc.filterOp = undefined;}, icon: 'layer-group',}); // prettier-ignore revealItems.push({description: 'Star', event: () => {this.layoutDoc.filterOp = cardMode.STAR;}, icon: 'star',}); // prettier-ignore revealItems.push({description: 'Practice Mode', event: () => {this.layoutDoc.filterOp = cardMode.PRACTICE;}, icon: 'check',}); // prettier-ignore @@ -161,7 +158,7 @@ export class CollectionCarouselView extends CollectionSubView() { onDoubleClickScript={this.onContentDoubleClick} onClickScript={this.onContentClick} isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive} - isContentActive={this._props.childContentsActive ?? this._props.isContentActive() === false ? returnFalse : emptyFunction} + isContentActive={(this._props.childContentsActive ?? this._props.isContentActive() === false) ? returnFalse : emptyFunction} hideCaptions={!!carouselShowsCaptions} // hide captions if the carousel is configured to show the captions renderDepth={this._props.renderDepth + 1} LayoutTemplate={this._props.childLayoutTemplate} @@ -177,7 +174,7 @@ export class CollectionCarouselView extends CollectionSubView() { key="caption" onWheel={StopEvent} style={{ - borderRadius: this._props.styleProvider?.(this.layoutDoc, captionProps, StyleProp.BorderRounding), + borderRadius: this._props.styleProvider?.(this.layoutDoc, captionProps, StyleProp.BorderRounding) as string, marginRight: this.marginX, marginLeft: this.marginX, width: `calc(100% - ${this.marginX * 2}px)`, @@ -218,8 +215,8 @@ export class CollectionCarouselView extends CollectionSubView() { ref={this.createDashEventsTarget} onContextMenu={this.specificMenu} style={{ - background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor), - color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color), + background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string, + color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string, }}> {this.content} {/* Displays a message to the user to add more flashcards if they are in practice mode and no flashcards are there. */} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 73179a266..e0aa79c7b 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -2,12 +2,14 @@ import { action, IReactionDisposer, makeObservable, observable, reaction } from import { observer } from 'mobx-react'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; +import ResizeObserver from 'resize-observer-polyfill'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, incrementTitleCopy, returnTrue, UpdateIcon } from '../../../ClientUtils'; import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc'; import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; +import { FieldType } from '../../../fields/ObjectField'; import { ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { GetEffectiveAcl, inheritParentAcls, SetPropSetterCb } from '../../../fields/util'; @@ -28,19 +30,17 @@ import { OverlayView } from '../OverlayView'; import { ScriptingRepl } from '../ScriptingRepl'; import { UndoStack } from '../UndoStack'; import './CollectionDockingView.scss'; -import { CollectionSubView } from './CollectionSubView'; - -const _global = (window /* browser */ || global) /* node */ as any; +import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; @observer export class CollectionDockingView extends CollectionSubView() { - static tabClass: JSX.Element | null = null; + static tabClass: unknown = null; /** * Initialize by assigning the add split method to DocumentView and by * configuring golden layout to render its documents using the specified React component * @param ele - typically would be set to TabDocView */ - public static Init(ele: any) { + public static Init(ele: unknown) { this.tabClass = ele; DocumentView.addSplit = CollectionDockingView.AddSplit; } @@ -53,20 +53,22 @@ export class CollectionDockingView extends CollectionSubView() { private _flush: UndoManager.Batch | undefined; private _unmounting = false; private _ignoreStateChange = ''; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private _goldenLayout: any = null; + // eslint-disable-next-line @typescript-eslint/no-explicit-any public tabMap: Set<any> = new Set(); public get HasFullScreen() { return this._goldenLayout._maximisedItem !== null; } - private _goldenLayout: any = null; - static _highlightStyleSheet: any = addStyleSheet(); + static _highlightStyleSheet = addStyleSheet(); - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); if (this._props.renderDepth < 0) CollectionDockingView.Instance = this; // Why is this here? - (window as any).React = React; - (window as any).ReactDOM = ReactDOM; + (window as unknown as { React: unknown }).React = React; + (window as unknown as { ReactDOM: unknown }).ReactDOM = ReactDOM; DragManager.StartWindowDrag = this.StartOtherDrag; this.Document.myTrails; // this is equivalent to having a prefetchProxy for myTrails which is needed for the My Trails button in the UI which assumes that Doc.ActiveDashboard.myTrails is legit... } @@ -88,10 +90,11 @@ export class CollectionDockingView extends CollectionSubView() { }; tabItemDropped = () => DragManager.CompleteWindowDrag?.(false); + // eslint-disable-next-line @typescript-eslint/no-explicit-any tabDragStart = (proxy: any, finishDrag?: (aborted: boolean) => void) => { this._flush = this._flush ?? UndoManager.StartBatch('tab move'); - const dashDoc = proxy?._contentItem?.tab?.DashDoc as Doc; - dashDoc && (DragManager.DocDragData = new DragManager.DocumentDragData([proxy._contentItem.tab.DashDoc])); + //const dashDoc = proxy?._contentItem?.tab?.DashDoc as Doc; + //dashDoc && (DragManager.DocDragData = new DragManager.DocumentDragData([proxy._contentItem.tab.DashDoc])); DragManager.CompleteWindowDrag = (aborted: boolean) => { if (aborted) { proxy._dragListener.AbortDrag(); @@ -129,12 +132,13 @@ export class CollectionDockingView extends CollectionSubView() { } @undoBatch + // eslint-disable-next-line @typescript-eslint/no-explicit-any public static ReplaceTab(document: Doc, mods: OpenWhereMod, stack: any, panelName: string, addToSplit?: boolean, keyValue?: boolean): boolean { const instance = CollectionDockingView.Instance; if (!instance) return false; const newConfig = DashboardView.makeDocumentConfig(document, panelName, undefined, keyValue); if (!panelName && stack) { - const activeContentItemIndex = stack.contentItems.findIndex((item: any) => item.config === stack._activeContentItem.config); + const activeContentItemIndex = stack.contentItems.findIndex((item: { config: unknown }) => item.config === stack._activeContentItem.config); const newContentItem = stack.layoutManager.createContentItem(newConfig, instance._goldenLayout); stack.addChild(newContentItem.contentItems[0], undefined); stack.contentItems[activeContentItemIndex].remove(); @@ -154,6 +158,7 @@ export class CollectionDockingView extends CollectionSubView() { } @undoBatch + // eslint-disable-next-line @typescript-eslint/no-explicit-any public static ToggleSplit(doc: Doc, location: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) { return Array.from(CollectionDockingView.Instance?.tabMap.keys() ?? []).findIndex(tab => tab.DashDoc === doc) !== -1 ? CollectionDockingView.CloseSplit(doc) : CollectionDockingView.AddSplit(doc, location, stack, panelName, keyValue); } @@ -162,6 +167,7 @@ export class CollectionDockingView extends CollectionSubView() { // Creates a split on any side of the docking view based on the passed input pullSide and then adds the Document to the requested side // @action + // eslint-disable-next-line @typescript-eslint/no-explicit-any public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) { if (document?._type_collection === CollectionViewType.Docking && !keyValue) return DashboardView.openDashboard(document); if (!CollectionDockingView.Instance) return false; @@ -320,7 +326,7 @@ export class CollectionDockingView extends CollectionSubView() { * @param target * @param title */ - titleChanged = (target: any, value: any) => { + titleChanged = (target: Doc, value: FieldType) => { const title = Field.toString(value); if (title.startsWith('@') && !title.substring(1).match(/[()[\]@]/) && title.length > 1) { const embedding = DocListCast(target.proto_embeddings).lastElement(); @@ -339,7 +345,7 @@ export class CollectionDockingView extends CollectionSubView() { () => DocumentView.LightboxDoc(), doc => setTimeout(() => !doc && this.onResize()) ); - new _global.ResizeObserver(this.onResize).observe(this._containerRef.current); + new ResizeObserver(this.onResize).observe(this._containerRef.current); this._reactionDisposer = reaction( () => StrCast(this.Document.dockingConfig), config => { @@ -428,7 +434,7 @@ export class CollectionDockingView extends CollectionSubView() { @action onPointerDown = (e: React.PointerEvent): void => { let hitFlyout = false; - for (let par = e.target as any; !hitFlyout && par; par = par.parentElement) { + for (let par = e.target as HTMLElement | null; !hitFlyout && par; par = par.parentElement) { hitFlyout = par.className === 'dockingViewButtonSelector'; } if (!hitFlyout) { @@ -513,6 +519,7 @@ export class CollectionDockingView extends CollectionSubView() { return changesMade; }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any tabDestroyed = (tab: any) => { this._flush = this._flush ?? UndoManager.StartBatch('tab movement'); const dashDoc = tab.DashDoc; @@ -530,18 +537,21 @@ export class CollectionDockingView extends CollectionSubView() { const { fieldKey } = CollectionDockingView.Instance.props; Doc.RemoveDocFromList(dview, fieldKey, dashDoc); this.tabMap.delete(tab); - tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); + tab._disposers && Object.values(tab._disposers).forEach(disposer => (disposer as () => void)()); this.stateChanged(); } }; - tabCreated = (tab: any) => { + tabCreated = (tab: { contentItem: { element: HTMLElement[] } }) => { this.tabMap.add(tab); - tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content) + // InitTab is added to the tab's HTMLElement in TabDocView + const tabdocviewContent = tab.contentItem.element[0]?.firstChild?.firstChild as unknown as { InitTab?: (tab: object) => void }; + tabdocviewContent?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content) }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any stackCreated = (stackIn: any) => { const stack = stackIn.header ? stackIn : stackIn.origin; - stack.header?.element.on('mousedown', (e: any) => { + stack.header?.element.on('mousedown', (e: MouseEvent) => { const dashboard = Doc.ActiveDashboard; if (dashboard && e.target === stack.header?.element[0] && e.button === 2) { dashboard.pane_count = NumCast(dashboard.pane_count) + 1; @@ -594,7 +604,7 @@ export class CollectionDockingView extends CollectionSubView() { }) ); - stack.element.click((e: any) => { + stack.element.click((e: { originalEvent: MouseEvent }) => { if (stack.contentItems.length === 0 && Array.from(document.elementsFromPoint(e.originalEvent.x, e.originalEvent.y)).some(ele => ele?.className === 'empty-tabs-message')) { addNewDoc(); } @@ -632,7 +642,7 @@ export class CollectionDockingView extends CollectionSubView() { ScriptingGlobals.add( // eslint-disable-next-line prefer-arrow-callback - function openInLightbox(doc: any) { + function openInLightbox(doc: Doc) { CollectionDockingView.Instance?._props.addDocTab(doc, OpenWhere.lightboxAlways); }, 'opens up document in a lightbox', @@ -640,33 +650,22 @@ ScriptingGlobals.add( ); ScriptingGlobals.add( // eslint-disable-next-line prefer-arrow-callback - function openDoc(doc: any, where: OpenWhere) { + function openDoc(doc: Doc | string, where: OpenWhere) { switch (where) { case OpenWhere.addRight: - return CollectionDockingView.AddSplit(doc, OpenWhereMod.right); + return doc instanceof Doc && CollectionDockingView.AddSplit(doc, OpenWhereMod.right); case OpenWhere.overlay: default: - // prettier-ignore switch (doc) { case '<ScriptingRepl />': return OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' }); case "<UndoStack />": return OverlayView.Instance.addWindow(<UndoStack />, { x: 300, y: 100, width: 200, height: 200, title: 'Undo stack' }); - default: - } - Doc.AddToMyOverlay(doc); - return true; + default: return doc instanceof Doc && Doc.AddToMyOverlay(doc); + } // prettier-ignore } }, 'opens up document in location specified', '(doc: any)' ); -ScriptingGlobals.add( - // eslint-disable-next-line prefer-arrow-callback - function openRepl() { - return 'openRepl'; - }, - 'opens up document in screen overlay layer', - '(doc: any)' -); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(async function snapshotDashboard() { const batch = UndoManager.StartBatch('snapshot'); diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 9a6f1e2eb..710c00841 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -1,6 +1,3 @@ -/* eslint-disable jsx-a11y/control-has-associated-label */ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -16,7 +13,7 @@ import { DragManager } from '../../util/DragManager'; import { CompileScript } from '../../util/Scripting'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; -import { undoBatch } from '../../util/UndoManager'; +import { undoBatch, undoable } from '../../util/UndoManager'; import { EditableView } from '../EditableView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; @@ -37,13 +34,13 @@ interface CMVFieldRowProps { createDropTarget: (ele: HTMLDivElement) => void; screenToLocalTransform: () => Transform; setDocHeight: (key: string, thisHeight: number) => void; - refList: any[]; + refList: Element[]; showHandle: boolean; } @observer export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVFieldRowProps> { - constructor(props: any) { + constructor(props: CMVFieldRowProps) { super(props); makeObservable(this); } @@ -73,7 +70,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF private _dropDisposer?: DragManager.DragDropDisposer; private _headerRef: React.RefObject<HTMLDivElement> = React.createRef(); private _contRef: React.RefObject<HTMLDivElement> = React.createRef(); - private _ele: any; + private _ele: HTMLDivElement | null = null; createRowDropRef = (ele: HTMLDivElement | null) => { this._dropDisposer?.(); @@ -118,7 +115,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF return false; }); - getValue = (value: string): any => { + getValue = (value: string) => { const parsed = parseInt(value); if (!isNaN(parsed)) return parsed; if (value.toLowerCase().indexOf('true') > -1) return true; @@ -173,7 +170,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF return docs ? !!docs.splice(0, 0, newDoc) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list) }; - deleteRow = undoBatch( + deleteRow = undoable( action(() => { this._createEmbeddingSelected = false; const key = this._props.pivotField; @@ -182,11 +179,12 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF const index = this._props.parent.colHeaderData.indexOf(this._props.headingObject); this._props.parent.colHeaderData.splice(index, 1); } - }) + }), + 'delete row' ); @action - collapseSection = (e: any) => { + collapseSection = (e: PointerEvent) => { this._createEmbeddingSelected = false; this.toggleVisibility(); e.stopPropagation(); diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 3eb3008c4..dab1298d5 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -1,7 +1,3 @@ -/* eslint-disable jsx-a11y/label-has-associated-control */ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -/* eslint-disable jsx-a11y/control-has-associated-label */ /* eslint-disable react/no-unused-class-component-methods */ /* eslint-disable react/sort-comp */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -10,20 +6,20 @@ import { Toggle, ToggleType, Type } from 'browndash-components'; import { Lambda, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../ClientUtils'; +import { ClientUtils, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; -import { Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { Doc, DocListCast, Opt, returnEmptyDoclist } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { RichTextField } from '../../../fields/RichTextField'; -import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; -import { undoBatch } from '../../util/UndoManager'; +import { undoBatch, undoable } from '../../util/UndoManager'; import { AntimodeMenu } from '../AntimodeMenu'; import { EditableView } from '../EditableView'; import { DefaultStyleProvider, returnEmptyDocViewList } from '../StyleProvider'; @@ -47,7 +43,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { private _docBtnRef = React.createRef<HTMLDivElement>(); - constructor(props: any) { + constructor(props: CollectionMenuProps) { super(props); makeObservable(this); CollectionMenu.Instance = this; @@ -185,7 +181,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu params: ['target', 'source'], title: 'item view', script: 'this.target.childLayoutTemplate = getDocTemplate(this.source?.[0])', - immediate: undoBatch((source: Doc[]) => { + immediate: undoable((source: Doc[]) => { let formatStr = source.length && Cast(source[0].text, RichTextField, null)?.Text; try { formatStr && JSON.parse(formatStr); @@ -200,25 +196,25 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu Doc.SetInPlace(this.target, 'childLayoutString', undefined, true); Doc.SetInPlace(this.target, 'childLayoutTemplate', undefined, true); } - }), + }, ''), initialize: emptyFunction, }; _narrativeCommand = { params: ['target', 'source'], title: 'child click view', script: 'this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])', - immediate: undoBatch((source: Doc[]) => { + immediate: undoable((source: Doc[]) => { source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0])); - }), + }, 'narrative command'), initialize: emptyFunction, }; _contentCommand = { params: ['target', 'source'], title: 'set content', script: 'getProto(this.target).data = copyField(this.source);', - immediate: undoBatch((source: Doc[]) => { + immediate: undoable((source: Doc[]) => { this.target[DocData].data = new List<Doc>(source); - }), + }, ''), initialize: emptyFunction, }; _onClickCommand = { @@ -229,19 +225,19 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu getProto(this.proxy[0]).target = this.target.target; getProto(this.proxy[0]).source = copyField(this.target.source); }}`, - immediate: undoBatch(() => {}), + immediate: undoable(() => {}, ''), initialize: emptyFunction, }; _viewCommand = { params: ['target'], title: 'bookmark view', script: "this.target._freeform_panX = this.target_freeform_panX; this.target._freeform_panY = this['target-freeform_panY']; this.target._freeform_scale = this['target_freeform_scale']; gotoFrame(this.target, this['target-currentFrame']);", - immediate: undoBatch(() => { + immediate: undoable(() => { this.target._freeform_panX = 0; this.target._freeform_panY = 0; this.target._freeform_scale = 1; this.target._currentFrame = this.target._currentFrame === undefined ? undefined : 0; - }), + }, ''), initialize: (button: Doc) => { button['target-panX'] = this.target._freeform_panX; button['target-panY'] = this.target._freeform_panY; @@ -253,18 +249,18 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu params: ['target'], title: 'fit content', script: 'this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox;', - immediate: undoBatch(() => { + immediate: undoable(() => { this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox; - }), + }, ''), initialize: emptyFunction, }; _fitContentCommand = { params: ['target'], title: 'toggle clusters', script: 'this.target._freeform_useClusters = !this.target._freeform_useClusters;', - immediate: undoBatch(() => { + immediate: undoable(() => { this.target._freeform_useClusters = !this.target._freeform_useClusters; - }), + }, ''), initialize: emptyFunction, }; _saveFilterCommand = { @@ -272,15 +268,15 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu title: 'save filter', script: `this.target._childFilters = compareLists(this.target_childFilters,this.target._childFilters) ? undefined : copyField(this.target_childFilters); this.target._searchFilterDocs = compareLists(this.target_searchFilterDocs,this.target._searchFilterDocs) ? undefined: copyField(this.target_searchFilterDocs);`, - immediate: undoBatch(() => { + immediate: undoable(() => { this.target._childFilters = undefined; this.target._searchFilterDocs = undefined; - }), + }, ''), initialize: (button: Doc) => { const activeDash = Doc.ActiveDashboard; if (activeDash) { - button.target_childFilters = (Doc.MySearcher._childFilters || activeDash._childFilters) instanceof ObjectField ? ObjectField.MakeCopy((Doc.MySearcher._childFilters || activeDash._childFilters) as any as ObjectField) : undefined; - button.target_searchFilterDocs = activeDash._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(activeDash._searchFilterDocs as any as ObjectField) : undefined; + button.target_childFilters = (Doc.MySearcher._childFilters || activeDash._childFilters) instanceof ObjectField ? ObjectField.MakeCopy((Doc.MySearcher._childFilters || activeDash._childFilters) as ObjectField) : undefined; + button.target_searchFilterDocs = activeDash._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(activeDash._searchFilterDocs) : undefined; } }, }; @@ -338,15 +334,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu this._currentKey = this._currentKey || (this._buttonizableCommands?.length ? this._buttonizableCommands[0]?.title : ''); }); - @undoBatch - viewChanged = (e: React.ChangeEvent) => { - const target = this.document !== Doc.MyLeftSidebarPanel ? this.document : DocCast(this.document.proto); - target._type_collection = (e.target as any).selectedOptions[0].value; - }; - - commandChanged = (e: React.ChangeEvent) => { + commandChanged = (e: React.ChangeEvent<HTMLSelectElement>) => { runInAction(() => { - this._currentKey = (e.target as any).selectedOptions[0].value; + this._currentKey = e.target.selectedOptions[0].value; }); }; @@ -485,7 +475,7 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi }; @action - onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => { + onKeyChange = (e: React.FormEvent<Element>, { newValue }: { newValue: string }) => { this._currentKey = newValue; }; @@ -538,7 +528,6 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi autosuggestProps: { inputProps: { value: this._currentKey, - // @ts-ignore onChange: this.onKeyChange, }, getSuggestionValue: this.getSuggestionValue, @@ -605,9 +594,9 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu */ onNumColsChange = (e: React.ChangeEvent<HTMLInputElement>) => { if (e.currentTarget.valueAsNumber > 0) - undoBatch(() => { + undoable(() => { this.document.gridNumCols = e.currentTarget.valueAsNumber; - })(); + }, '')(); }; /** @@ -636,9 +625,9 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu onIncrementButtonClick = () => { this.clicked = true; this.entered && (this.document.gridNumCols as number)--; - undoBatch(() => { + undoable(() => { this.document.gridNumCols = this.numCols + 1; - })(); + }, '')(); this.entered = false; }; @@ -649,9 +638,9 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu this.clicked = true; if (this.numCols > 1 && !this.decrementLimitReached) { this.entered && (this.document.gridNumCols as number)++; - undoBatch(() => { + undoable(() => { this.document.gridNumCols = this.numCols - 1; - })(); + }, '')(); if (this.numCols === 1) this.decrementLimitReached = true; } this.entered = false; @@ -733,7 +722,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu onChange={this.changeCompactType} value={StrCast(this.document.gridStartCompaction, StrCast(this.document.gridCompaction))}> {['vertical', 'horizontal', 'none'].map(type => ( - <option className="collectionGridViewChrome-viewOption" onPointerDown={stopPropagation} value={type}> + <option key={type} className="collectionGridViewChrome-viewOption" onPointerDown={stopPropagation} value={type}> {this.resize ? type[0].toUpperCase() + type.substring(1) : 'Compact: ' + type} </option> ))} diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 16c474996..e1f0a3e41 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -30,9 +30,8 @@ import { StyleProp } from '../StyleProp'; import './CollectionNoteTakingView.scss'; import { CollectionNoteTakingViewColumn } from './CollectionNoteTakingViewColumn'; import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivider'; -import { CollectionSubView } from './CollectionSubView'; - -const _global = (window /* browser */ || global) /* node */ as any; +import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; +import { Property } from 'csstype'; /** * CollectionNoteTakingView is a column-based view for displaying documents. In this view, the user can (1) @@ -52,9 +51,9 @@ export class CollectionNoteTakingView extends CollectionSubView() { public DividerWidth = 16; @observable docsDraggedRowCol: number[] = []; @observable _scroll = 0; - @observable _refList: any[] = []; + @observable _refList: HTMLElement[] = []; - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } @@ -78,7 +77,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { return colHeaderData ?? ([] as SchemaHeaderField[]); } @computed get headerMargin() { - return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin); + return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin) as number; } @computed get xMargin() { return NumCast(this.layoutDoc._xMargin, 5); @@ -216,7 +215,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { // let's dive in and get the actual document we want to drag/move around focusDocument = (doc: Doc, options: FocusViewOptions) => { Doc.BrushDoc(doc); - const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); + const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find(node => node.id === doc[Id]); if (found) { const { top } = found.getBoundingClientRect(); const localTop = this.ScreenToLocalBoxXf().transformPoint(0, top); @@ -295,7 +294,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { addDocument={this._props.addDocument} moveDocument={this._props.moveDocument} removeDocument={this._props.removeDocument} - contentPointerEvents={StrCast(this.layoutDoc.childContentPointerEvents) as any} + contentPointerEvents={StrCast(this.layoutDoc.childContentPointerEvents) as Property.PointerEvents} whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} addDocTab={this._props.addDocTab} pinToPres={this._props.pinToPres} @@ -313,14 +312,14 @@ export class CollectionNoteTakingView extends CollectionSubView() { // how to get the width of a document. Currently returns the width of the column (minus margins) // if a note doc. Otherwise, returns the normal width (for graphs, images, etc...) - getDocWidth(d: Doc) { + getDocWidth = (d: Doc) => { const heading = !d[this.notetakingCategoryField] ? 'unset' : Field.toString(d[this.notetakingCategoryField] as FieldType); const existingHeader = this.colHeaderData.find(sh => sh.heading === heading); const existingWidth = this.layoutDoc._notetaking_columns_autoSize ? 1 / (this.colHeaderData.length ?? 1) : existingHeader?.width ? existingHeader.width : 0; const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth : this.maxColWidth; const width = d.layout_fitWidth ? maxWidth : NumCast(d._width); return Math.min(maxWidth - CollectionNoteTakingViewColumn.ColumnMargin, width < maxWidth ? width : maxWidth); - } + }; // how to get the height of a document. Nothing special here. getDocHeight(d?: Doc) { @@ -364,7 +363,8 @@ export class CollectionNoteTakingView extends CollectionSubView() { // onPointerMove is used to preview where a document will drop in a column once a drag is complete. @action onPointerMove = (force: boolean, ex: number, ey: number) => { - if (this.childDocList?.includes(DragManager.DocDragData?.draggedDocuments?.lastElement() as any) || force || SnappingManager.CanEmbed) { + const dragDoc = DragManager.DraggedDocs?.lastElement(); + if ((dragDoc && this.childDocList?.includes(dragDoc)) || force || SnappingManager.CanEmbed) { // get the current docs for the column based on the mouse's x coordinate const xCoord = this.ScreenToLocalBoxXf().transformPoint(ex, ey)[0] - 2 * this.gridGap; const colDocs = this.getDocsFromXCoord(xCoord); @@ -500,7 +500,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { super.onExternalDrop( e, {}, - undoBatch( + undoable( action(docus => { this.onPointerMove(true, e.clientX, e.clientY); docus?.map((doc: Doc) => this.addDocument(doc)); @@ -513,7 +513,8 @@ export class CollectionNoteTakingView extends CollectionSubView() { docs.splice(targInd, 0, newDoc); } this.removeDocDragHighlight(); - }) + }), + 'drop into note view' ) ); }; @@ -673,7 +674,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { return this.isContentActive() === false ? 'none' : undefined; } - observer = new _global.ResizeObserver(() => this._props.setHeight?.(this.headerMargin + Math.max(...this._refList.map(DivHeight)))); + observer = new ResizeObserver(() => this._props.setHeight?.(this.headerMargin + Math.max(...this._refList.map(DivHeight)))); render() { TraceMobx(); diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx index 44ab1968d..8c6a6b551 100644 --- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx @@ -1,4 +1,3 @@ -/* eslint-disable jsx-a11y/control-has-associated-label */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; @@ -16,7 +15,7 @@ import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, undoable } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; -import { EditableView } from '../EditableView'; +import { EditableProps, EditableView } from '../EditableView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import './CollectionNoteTakingView.scss'; @@ -24,7 +23,7 @@ import './CollectionNoteTakingView.scss'; interface CSVFieldColumnProps { Document: Doc; TemplateDataDocument: Opt<Doc>; - backgroundColor?: (() => string) | undefined; + backgroundColor?: () => string | undefined; docList: Doc[]; heading: string; pivotField: string; @@ -35,15 +34,15 @@ interface CSVFieldColumnProps { yMargin: number; numGroupColumns: number; gridGap: number; - headings: () => object[]; + headings: () => [SchemaHeaderField, Doc[]][]; select: (ctrlPressed: boolean) => void; isContentActive: () => boolean | undefined; renderChildren: (docs: Doc[]) => JSX.Element[]; addDocument: (doc: Doc | Doc[]) => boolean; createDropTarget: (ele: HTMLDivElement) => void; screenToLocalTransform: () => Transform; - refList: any[]; - editableViewProps: () => any; + refList: HTMLElement[]; + editableViewProps: () => EditableProps; resizeColumns: (headers: SchemaHeaderField[]) => boolean; maxColWidth: number; dividerWidth: number; @@ -103,7 +102,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV 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; @@ -272,7 +271,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV style={{ width: this.columnWidth, background: this._hover && SnappingManager.IsDragging ? '#b4b4b4' : 'inherit', - marginLeft: this._props.headings().findIndex((h: any) => h[0] === this._props.headingObject) === 0 ? NumCast(this._props.Document.xMargin) : 0, + marginLeft: this._props.headings().findIndex(h => h[0] === this._props.headingObject) === 0 ? NumCast(this._props.Document.xMargin) : 0, }}> <div className="collectionNoteTakingViewFieldColumn" key={this._heading} ref={this.createColumnDropRef}> {this.innards} diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 5b3f625db..eea128803 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -1,10 +1,8 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ import { action, computed, IReactionDisposer, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; -import { Doc, DocListCast } from '../../../fields/Doc'; +import { Doc, DocListCast, FieldResult } from '../../../fields/Doc'; import { ScriptField } from '../../../fields/ScriptField'; import { NumCast, StrCast, toList } from '../../../fields/Types'; import { emptyFunction } from '../../../Utils'; @@ -15,15 +13,15 @@ import { OpenWhere } from '../nodes/OpenWhere'; import { computePassLayout, computeStarburstLayout } from './collectionFreeForm'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import './CollectionPileView.scss'; -import { CollectionSubView } from './CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import { DocumentView } from '../nodes/DocumentView'; @observer export class CollectionPileView extends CollectionSubView() { - _originalChrome: any = ''; + _originalChrome: FieldResult = ''; _disposers: { [name: string]: IReactionDisposer } = {}; - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index fac885300..486c826b6 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -1,14 +1,11 @@ /* eslint-disable react/jsx-props-no-spreading */ -/* eslint-disable jsx-a11y/alt-text */ /* eslint-disable no-use-before-define */ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; -import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../ClientUtils'; -import { Doc, Opt } from '../../../fields/Doc'; +import { returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../ClientUtils'; +import { Doc, Opt, returnEmptyDoclist } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; @@ -34,7 +31,7 @@ import { LabelBox } from '../nodes/LabelBox'; import { OpenWhere } from '../nodes/OpenWhere'; import { ObservableReactComponent } from '../ObservableReactComponent'; import './CollectionStackedTimeline.scss'; -import { CollectionSubView } from './CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; export type CollectionStackedTimelineProps = { Play: () => void; @@ -72,7 +69,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack ); this.SelectingRegions.clear(); } - constructor(props: any) { + constructor(props: SubCollectionViewProps & CollectionStackedTimelineProps) { super(props); makeObservable(this); } @@ -182,7 +179,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack }); anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this._props.startTag])); - anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this._props.endTag], val) ?? null); + anchorEnd = (anchor: Doc, val?: number) => NumCast(anchor._timecodeToHide, NumCast(anchor[this._props.endTag], val) ?? null); // converts screen pixel offset to time // prettier-ignore @@ -192,13 +189,13 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack @computed get rangeClick() { // prettier-ignore return ScriptField.MakeFunction('stackedTimeline.clickAnchor(this, clientX)', - { stackedTimeline: 'any', clientX: 'number' }, { stackedTimeline: this as any } + { stackedTimeline: 'any', clientX: 'number' }, { stackedTimeline: 'string' /* should be CollectionStackedTimeline */ } )!; } @computed get rangePlay() { // prettier-ignore return ScriptField.MakeFunction('stackedTimeline.playOnClick(this, clientX)', - { stackedTimeline: 'any', clientX: 'number' }, { stackedTimeline: this as any })!; + { stackedTimeline: 'any', clientX: 'number' }, { stackedTimeline: 'string' /* should be CollectionStackedTimeline */})!; } rangeClickScript = () => this.rangeClick; rangePlayScript = () => this.rangePlay; @@ -426,7 +423,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack const anchor = docAnchor ?? Docs.Create.LabelDocument({ - title: ComputedField.MakeFunction(`this["${endTag}"] ? "#" + formatToTime(this["${startTag}"]) + "-" + formatToTime(this["${endTag}"]) : "#" + formatToTime(this["${startTag}"])`) as any, + title: ComputedField.MakeFunction(`this["${endTag}"] ? "#" + formatToTime(this["${startTag}"]) + "-" + formatToTime(this["${endTag}"]) : "#" + formatToTime(this["${startTag}"])`) as unknown as string, // title can take a function or a string _label_minFontSize: 12, _label_maxFontSize: 24, _dragOnlyWithinContainer: true, @@ -777,8 +774,8 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch @action onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => { const newTime = (timeDownEv: PointerEvent) => { - const rect = (timeDownEv.target as any).getBoundingClientRect(); - return this._props.toTimeline(timeDownEv.clientX - rect.x, rect.width); + const rect = (timeDownEv.target as HTMLElement).getBoundingClientRect?.(); + return !rect ? 0 : this._props.toTimeline(timeDownEv.clientX - rect.x, rect.width); }; const changeAnchor = (time: number | undefined) => { const timelineOnly = Cast(anchor[this._props.startTag], 'number', null) !== undefined; @@ -850,7 +847,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch styleProvider={this._props.styleProvider} renderDepth={this._props.renderDepth + 1} LayoutTemplate={undefined} - LayoutTemplateString={LabelBox.LayoutStringWithTitle('data', this.computeTitle())} + LayoutTemplateString={LabelBox.LayoutString('data')} isDocumentActive={this._props.isDocumentActive} PanelWidth={width} PanelHeight={height} @@ -892,7 +889,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch } } // eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function formatToTime(time: number): any { +ScriptingGlobals.add(function formatToTime(time: number): string { return formatTime(time); }); // eslint-disable-next-line prefer-arrow-callback diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 56d2a6c9c..3f8aee792 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -1,11 +1,10 @@ /* eslint-disable react/jsx-props-no-spreading */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -// eslint-disable-next-line import/no-extraneous-dependencies import * as CSS from 'csstype'; import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { ClientUtils, DivHeight, returnEmptyDoclist, returnNone, returnZero, setupMoveUpEvents, smoothScroll } from '../../../ClientUtils'; +import { ClientUtils, DivHeight, returnNone, returnZero, setupMoveUpEvents, smoothScroll } from '../../../ClientUtils'; import { Doc, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; @@ -31,12 +30,11 @@ import { DocumentView } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FocusViewOptions } from '../nodes/FocusViewOptions'; import { StyleProp } from '../StyleProp'; +import { returnEmptyDocViewList } from '../StyleProvider'; import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow'; import './CollectionStackingView.scss'; import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn'; -import { CollectionSubView } from './CollectionSubView'; - -const _global = (window /* browser */ || global) /* node */ as any; +import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; export type collectionStackingViewProps = { sortFunc?: (a: Doc, b: Doc) => number; @@ -57,8 +55,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection _docXfs: { height: () => number; width: () => number; stackedDocTransform: () => Transform }[] = []; // Doesn't look like this field is being used anywhere. Obsolete? _columnStart: number = 0; + _oldWheel: HTMLElement | null = null; - @observable _refList: any[] = []; + @observable _refList: HTMLElement[] = []; // map of node headers to their heights. Used in Masonry @observable _heightMap = new Map<string, number>(); // Assuming that this is the current css cursor style @@ -85,7 +84,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } // how much margin we give the header @computed get headerMargin() { - return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin); + return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin) as number; } @computed get xMargin() { return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this._props.PanelWidth())); @@ -118,7 +117,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection return this._props.PanelWidth() - this.gridGap; } - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); if (this.colHeaderData === undefined) { @@ -260,7 +259,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection focusDocument = (doc: Doc, options: FocusViewOptions) => { Doc.BrushDoc(doc); - const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); + const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find(node => node.id === doc[Id]); if (found) { const { top } = found.getBoundingClientRect(); const localTop = this.ScreenToLocalBoxXf().transformPoint(0, top); @@ -321,7 +320,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection const dataDoc = doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined; const height = () => this.getDocHeight(doc); const panelHeight = () => (this.isStackingView ? height() : Math.min(height(), this._props.PanelHeight())); - const panelWidth = () => (this.isStackingView ? width() : this.columnWidth); + const panelWidth = () => this.columnWidth; const stackedDocTransform = () => this.getDocTransform(doc); this._docXfs.push({ stackedDocTransform, width, height }); return count > this._renderCount ? null : ( @@ -344,7 +343,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection LayoutTemplateString={this._props.childLayoutString} NativeWidth={this._props.childIgnoreNativeSize ? returnZero : this._props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox NativeHeight={this._props.childIgnoreNativeSize ? returnZero : this._props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeHeight(doc)) ? height : undefined} - dontCenter={this._props.childIgnoreNativeSize ? 'xy' : (StrCast(this.layoutDoc.layout_dontCenter) as any)} + dontCenter={this._props.childIgnoreNativeSize ? 'xy' : (StrCast(this.layoutDoc.layout_dontCenter) as 'x' | 'y' | 'xy')} dontRegisterView={BoolCast(this.layoutDoc.childDontRegisterViews, this._props.dontRegisterView)} // used to be true if DataDoc existed, but template textboxes won't layout_autoHeight resize if dontRegisterView is set, but they need to. rootSelected={this.rootSelected} showTitle={this._props.childlayout_showTitle} @@ -363,7 +362,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection addDocument={this._props.addDocument} moveDocument={this._props.moveDocument} removeDocument={this._props.removeDocument} - contentPointerEvents={StrCast(this.layoutDoc.childContentPointerEvents) as any} + contentPointerEvents={StrCast(this.layoutDoc.childContentPointerEvents) as CSS.Property.PointerEvents | undefined} whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} addDocTab={this._props.addDocTab} pinToPres={this._props.pinToPres} @@ -374,9 +373,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection getDocTransform(doc: Doc) { const dref = this.docRefs.get(doc); this._scroll; // must be referenced for document decorations to update when the text box container is scrolled - const { translateX, translateY } = ClientUtils.GetScreenTransform(dref?.ContentDiv); - // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off - return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.ScreenToLocalBoxXf().Scale); + const { translateX, translateY, scale } = ClientUtils.GetScreenTransform(dref?.ContentDiv); + return new Transform(-translateX + (dref?.centeringX || 0) * scale, + -translateY + (dref?.centeringY || 0) * scale, 1) + .scale(1 / scale); // prettier-ignore } getDocWidth(d?: Doc) { if (!d) return 0; @@ -623,7 +623,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection if (!e.isPropagationStopped()) { const cm = ContextMenu.Instance; const options = cm.findByDescription('Options...'); - const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : []; + const optionItems: ContextMenuProps[] = options?.subitems ?? []; optionItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => { this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill; }, icon: 'plus' }); // prettier-ignore optionItems.push({ description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => { this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight; }, icon: 'plus' }); // prettier-ignore optionItems.push({ description: 'Clear All', event: () => { this.dataDoc[this.fieldKey ?? 'data'] = new List([]); } , icon: 'times' }); // prettier-ignore @@ -663,7 +663,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection renderDepth={this._props.renderDepth} focus={emptyFunction} styleProvider={this._props.styleProvider} - containerViewPath={returnEmptyDoclist} + containerViewPath={returnEmptyDocViewList} whenChildContentsActiveChanged={emptyFunction} childFilters={this._props.childFilters} childFiltersByRanges={this._props.childFiltersByRanges} @@ -688,10 +688,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection return this._props.isContentActive() === false ? 'none' : undefined; } - observer = new _global.ResizeObserver(() => this._props.setHeight?.(this.headerMargin + (this.isStackingView ? Math.max(...this._refList.map(DivHeight)) : this._refList.reduce((p, r) => p + DivHeight(r), 0)))); + observer = new ResizeObserver(() => this._props.setHeight?.(this.headerMargin + (this.isStackingView ? Math.max(...this._refList.map(DivHeight)) : this._refList.reduce((p, r) => p + DivHeight(r), 0)))); onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); - _oldWheel: any; render() { TraceMobx(); const editableViewProps = { @@ -722,8 +721,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection }} style={{ overflowY: this.isContentActive() ? 'auto' : 'hidden', - background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor), - pointerEvents: (this._props.pointerEvents?.() as any) ?? this.backgroundEvents, + background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) as string, + pointerEvents: this._props.pointerEvents?.() ?? this.backgroundEvents, }} onScroll={action(e => { this._scroll = e.currentTarget.scrollTop; diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index e2ad5b31d..5ae08e535 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -1,6 +1,3 @@ -/* eslint-disable jsx-a11y/control-has-associated-label */ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; @@ -51,7 +48,7 @@ interface CSVFieldColumnProps { addDocument: (doc: Doc | Doc[]) => 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< <div className="colorOptions"> {colors.map(col => { const palette = PastelSchemaPalette.get(col); - return <div className={'colorPicker' + (selected === palette ? ' active' : '')} style={{ backgroundColor: palette }} onClick={() => this.changeColumnColor(palette!)} />; + return <div key={col} className={'colorPicker' + (selected === palette ? ' active' : '')} style={{ backgroundColor: palette }} onClick={() => this.changeColumnColor(palette!)} />; })} </div> </div> diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 26528b2b3..a6768ab35 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -22,7 +22,7 @@ import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; import { SnappingManager } from '../../util/SnappingManager'; -import { UndoManager, undoBatch } from '../../util/UndoManager'; +import { UndoManager } from '../../util/UndoManager'; import { ViewBoxBaseComponent } from '../DocComponent'; import { FieldViewProps } from '../nodes/FieldView'; import { DocumentView } from '../nodes/DocumentView'; @@ -67,7 +67,7 @@ export function CollectionSubView<X>() { private gestureDisposer?: GestureUtils.GestureEventDisposer; protected _mainCont?: HTMLDivElement; - constructor(props: any) { + constructor(props: X & SubCollectionViewProps) { super(props); makeObservable(this); } @@ -227,7 +227,6 @@ export function CollectionSubView<X>() { } } - @undoBatch // eslint-disable-next-line @typescript-eslint/no-unused-vars protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {} @@ -294,7 +293,6 @@ export function CollectionSubView<X>() { return false; } - @undoBatch protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions, completed?: (docs: Doc[]) => void) { if (e.ctrlKey) { e.stopPropagation(); // bcz: this is a hack to stop propagation when dropping an image on a text document with shift+ctrl @@ -386,7 +384,7 @@ export function CollectionSubView<X>() { addDocument(htmlDoc); if (srcWeb) { const iframe = DocumentView.Selected()[0].ContentDiv?.getElementsByTagName('iframe')?.[0]; - const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any; + const focusNode = iframe?.contentDocument?.getSelection()?.focusNode; if (focusNode) { const anchor = srcWeb?.ComponentView?.getAnchor?.(true); anchor && DocUtils.MakeLink(htmlDoc, anchor, {}); @@ -465,23 +463,6 @@ export function CollectionSubView<X>() { if (item.kind === 'file') { const file = item.getAsFile(); file?.type && files.push(file); - - file?.type === 'application/json' && - ClientUtils.readUploadedFileAsText(file).then(result => { - const json = JSON.parse(result as string); - addDocument( - Docs.Create.TreeDocument( - json['rectangular-puzzle'].crossword.clues[0].clue.map((c: any) => { - const label = Docs.Create.LabelDocument({ title: c['#text'], _width: 120, _height: 20 }); - const proto = Doc.GetProto(label); - proto._width = 120; - proto._height = 20; - return proto; - }), - { _width: 150, _height: 600, title: 'across', backgroundColor: 'white', _createDocOnCR: true } - ) - ); - }); } } this.slowLoadDocuments(files, options, generatedDocuments, text, completed, addDocument).then(batch.end); diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 0369e4a2a..8a24db330 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -1,5 +1,3 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -18,7 +16,7 @@ import { ContextMenuProps } from '../ContextMenuItem'; import { FieldsDropdown } from '../FieldsDropdown'; import { PinDocView } from '../PinFuncs'; import { DocumentView } from '../nodes/DocumentView'; -import { CollectionSubView } from './CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import './CollectionTimeView.scss'; import { ViewDefBounds, computePivotLayout, computeTimelineLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; @@ -32,7 +30,7 @@ export class CollectionTimeView extends CollectionSubView() { @observable _viewDefDivClick: Opt<ScriptField> = undefined; @observable _focusPivotField: Opt<string> = undefined; - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } @@ -51,7 +49,7 @@ export class CollectionTimeView extends CollectionSubView() { getAnchor = (addAsAnnotation: boolean) => { const anchor = Docs.Create.ConfigDocument({ - title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any, + title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as unknown as string, // title can take a functiono or a string annotationOn: this.Document, }); PinDocView(anchor, { pinData: { type_collection: true, pivot: true, filters: true } }, this.Document); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index c39df2c76..a60cd98ac 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,10 +1,9 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { DivHeight, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../ClientUtils'; -import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc'; +import ResizeObserver from 'resize-observer-polyfill'; +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'; @@ -19,21 +18,20 @@ import { dropActionType } from '../../util/DropActionTypes'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; -import { undoBatch, UndoManager } from '../../util/UndoManager'; +import { undoable, undoBatch, UndoManager } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; 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 } from './CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import './CollectionTreeView.scss'; import { TreeViewType } from './CollectionTreeViewType'; import { TreeView } from './TreeView'; -const _global = (window /* browser */ || global) /* node */ as any; - export type collectionTreeViewProps = { treeViewExpandedView?: 'fields' | 'layout' | 'links' | 'data'; treeViewOpen?: boolean; @@ -52,14 +50,13 @@ export type collectionTreeViewProps = { export class CollectionTreeView extends CollectionSubView<Partial<collectionTreeViewProps>>() { public static AddTreeFunc = 'addTreeFolder(this.embedContainer)'; private _treedropDisposer?: DragManager.DragDropDisposer; - private _mainEle?: HTMLDivElement; private _titleRef?: HTMLDivElement | HTMLInputElement | null; private _disposers: { [name: string]: IReactionDisposer } = {}; private _isDisposing = false; // notes that instance is in process of being disposed - private refList: Set<any> = new Set(); // list of tree view items to monitor for height changes - private observer: any; // observer for monitoring tree view items. + private refList: Set<HTMLElement> = new Set(); // list of tree view items to monitor for height changes + private observer: ResizeObserver | undefined; // observer for monitoring tree view items. - constructor(props: any) { + constructor(props: SubCollectionViewProps & collectionTreeViewProps) { super(props); makeObservable(this); } @@ -83,8 +80,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree @observable _titleHeight = 0; // height of the title bar - MainEle = () => this._mainEle; - // these should stay in synch with counterparts in DocComponent.ts ViewBoxAnnotatableComponent @observable _isAnyChildContentActive = false; whenChildContentsActiveChanged = action((isActive: boolean) => { @@ -116,14 +111,14 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree !this._props.dontRegisterView && this._props.setHeight?.(bodyHeight + titleHeight); } }; - unobserveHeight = (ref: any) => { + unobserveHeight = (ref: HTMLElement) => { this.refList.delete(ref); this.layoutDoc.layout_autoHeight && this.computeHeight(); }; - observeHeight = (ref: any) => { + observeHeight = (ref: HTMLElement) => { if (ref) { this.refList.add(ref); - this.observer = new _global.ResizeObserver(() => { + this.observer = new ResizeObserver(() => { if (this.layoutDoc.layout_autoHeight && ref && this.refList.size && !SnappingManager.IsDragging) { this.computeHeight(); } @@ -134,7 +129,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree }; protected createTreeDropTarget = (ele: HTMLDivElement) => { this._treedropDisposer?.(); - this._mainEle = ele; if (ele) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.Document, this.onInternalPreDrop.bind(this)); }; @@ -220,7 +214,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' }); if (!Doc.noviceMode) { const existingOnClick = ContextMenu.Instance.findByDescription('OnClick...'); - const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : []; + const onClicks: ContextMenuProps[] = existingOnClick?.subitems ?? []; onClicks.push({ description: 'Edit onChecked Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onCheckedClick'), 'edit onCheckedClick'), icon: 'edit' }); !existingOnClick && ContextMenu.Instance.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); } @@ -233,16 +227,16 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree get editableTitle() { return ( <EditableView - contents={this.dataDoc.title} + contents={StrCast(this.dataDoc.title)} display="block" maxHeight={72} height="auto" GetValue={() => StrCast(this.dataDoc.title)} - SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => { + SetValue={undoable((value: string, shift: boolean, enter: boolean) => { if (enter && this.Document.treeView_Type === TreeViewType.outline) this.makeTextCollection(this.treeChildren); this.dataDoc.title = value; return true; - })} + }, 'set doc title')} /> ); } @@ -289,7 +283,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree @observable _renderCount = 1; @computed get treeViewElements() { TraceMobx(); - const dragAction = StrCast(this.Document.childDragAction) as any as dropActionType; + const dragAction = StrCast(this.Document.childDragAction) as dropActionType; const treeAddDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before); const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this._props.moveDocument?.(d, target, addDoc) || false; if (this._renderCount < this.treeChildren.length) @@ -337,9 +331,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree return this.dataDoc === null ? null : ( <div className="collectionTreeView-titleBar" - ref={action((r: any) => { - (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this.ScreenToLocalBoxXf().Scale); - })} + ref={r => + runInAction(() => { + (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this.ScreenToLocalBoxXf().Scale); + }) + } key={this.Document[Id]} style={!this.outlineMode ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}}> {this.outlineMode ? this.documentTitle : this.editableTitle} @@ -374,7 +370,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree renderDepth={this._props.renderDepth + 1} focus={emptyFunction} styleProvider={this._props.styleProvider} - containerViewPath={returnEmptyDoclist} + containerViewPath={returnEmptyDocViewList} whenChildContentsActiveChanged={emptyFunction} childFilters={this._props.childFilters} childFiltersByRanges={this._props.childFiltersByRanges} @@ -414,8 +410,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree @observable _headerHeight = 0; @computed get content() { - const background = () => this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor); - const color = () => this._props.styleProvider?.(this.Document, this._props, StyleProp.Color); + const background = () => this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) as string; + const color = () => this._props.styleProvider?.(this.Document, this._props, StyleProp.Color) as string; const pointerEvents = () => (this._props.isContentActive() === false ? 'none' : undefined); const titleBar = this._props.treeViewHideTitle || this.Document.treeView_HideTitle ? null : this.titleBar; return ( diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index a750b731a..66c4896c0 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -49,7 +49,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr private reactionDisposer: IReactionDisposer | undefined; @observable _isContentActive: boolean | undefined = undefined; - constructor(props: any) { + constructor(props: CollectionViewProps) { super(props); makeObservable(this); this._annotationKeySuffix = returnEmptyString; @@ -73,7 +73,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr } get collectionViewType(): CollectionViewType | undefined { - const viewField = StrCast(this.layoutDoc._type_collection) as any as CollectionViewType; + const viewField = StrCast(this.layoutDoc._type_collection) as CollectionViewType; if (CollectionView._safeMode) { switch (viewField) { case CollectionViewType.Freeform: @@ -133,7 +133,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr ]; const existingVm = ContextMenu.Instance.findByDescription(category); - const catItems = existingVm && 'subitems' in existingVm ? existingVm.subitems : []; + const catItems = existingVm?.subitems ?? []; catItems.push({ description: 'Add a Perspective...', addDivider: true, noexpand: true, subitems: subItems, icon: 'eye' }); !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: catItems, icon: 'eye' }); } @@ -152,7 +152,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr }); const options = cm.findByDescription('Options...'); - const optionItems = options && 'subitems' in options ? options.subitems : []; + const optionItems = options?.subitems ?? []; !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.Document.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => {this.Document.forceActive = !this.Document.forceActive}, icon: 'project-diagram' }) : null; // prettier-ignore if (this.Document.childLayout instanceof Doc) { optionItems.push({ description: 'View Child Layout', event: () => this._props.addDocTab(this.Document.childLayout as Doc, OpenWhere.addRight), icon: 'project-diagram' }); @@ -166,7 +166,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr if (!Doc.noviceMode && !this.Document.annotationOn && !this._props.hideClickBehaviors) { const existingOnClick = cm.findByDescription('OnClick...'); - const onClicks = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : []; + const onClicks = existingOnClick?.subitems ?? []; const funcs = [ { key: 'onChildClick', name: 'On Child Clicked' }, { key: 'onChildDoubleClick', name: 'On Child Double Clicked' }, @@ -196,7 +196,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr if (!Doc.noviceMode) { const more = cm.findByDescription('More...'); - const moreItems = more && 'subitems' in more ? more.subitems : []; + const moreItems = more?.subitems ?? []; moreItems.push({ description: 'Export Image Hierarchy', icon: 'columns', event: () => ImageUtils.ExportHierarchyToFileSystem(this.Document) }); !more && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'hand-point-right' }); } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 46f61290e..31b6be927 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -6,15 +6,16 @@ import { IReactionDisposer, ObservableSet, action, computed, makeObservable, obs import { observer } from 'mobx-react'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; -import { ClientUtils, DashColor, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils'; +import ResizeObserver from 'resize-observer-polyfill'; +import { ClientUtils, DashColor, lightOrDark, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; -import { Doc, Opt } from '../../../fields/Doc'; +import { Doc, Opt, returnEmptyDoclist } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { FieldId } from '../../../fields/RefField'; import { ComputedField } from '../../../fields/ScriptField'; -import { Cast, DocCast, NumCast, StrCast, toList } from '../../../fields/Types'; +import { Cast, NumCast, StrCast, toList } from '../../../fields/Types'; import { DocServer } from '../../DocServer'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; @@ -41,8 +42,6 @@ import { CollectionView } from './CollectionView'; import './TabDocView.scss'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; -const _global = (window /* browser */ || global) /* node */ as any; - interface TabMinimapViewProps { document: Doc; tabView: () => DocumentView | undefined; @@ -67,7 +66,7 @@ class TabMiniThumb extends React.Component<TabMiniThumbProps> { } @observer export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps> { - static miniStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => { + static miniStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => { if (doc) { switch (property.split(':')[0]) { case StyleProp.PointerEvents: return 'none'; @@ -158,8 +157,8 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps addDocTab={this._props.addDocTab} // eslint-disable-next-line no-use-before-define pinToPres={TabDocView.PinDoc} - childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist} - childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist} + childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyFilter} + childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyFilter} searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist} fitContentsToBox={returnTrue} xPadding={this.xPadding} @@ -183,6 +182,7 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps interface TabDocViewProps { documentId: FieldId; keyValue?: boolean; + // eslint-disable-next-line @typescript-eslint/no-explicit-any glContainer: any; } @observer @@ -274,7 +274,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { } static Activate = (tabDoc: Doc) => { - const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(findTab => findTab.DashDoc === tabDoc && !findTab.contentItem.config.props.keyValue); + const tab = Array.from(CollectionDockingView.Instance?.tabMap ?? []).find(findTab => findTab.DashDoc === tabDoc && !findTab.contentItem.config.props.keyValue); tab?.header.parent.setActiveContentItem(tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) return tab !== undefined; }; @@ -286,7 +286,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { // } // return undefined; // } - constructor(props: any) { + constructor(props: TabDocViewProps) { super(props); makeObservable(this); DocumentView.activateTabView = TabDocView.Activate; @@ -327,10 +327,12 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { get view() { return this._view; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any _lastTab: any; _lastView: DocumentView | undefined; @action + // eslint-disable-next-line @typescript-eslint/no-explicit-any init = (tab: any, doc: Opt<Doc>) => { if (tab.contentItem === tab.header.parent.getActiveContentItem()) this._activated = true; if (tab.DashDoc !== doc && doc && tab.contentItem?.config.type !== 'stack') { @@ -357,10 +359,11 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { titleEle.size = StrCast(doc.title).length + 3; titleEle.value = doc.title; titleEle.onkeydown = (e: KeyboardEvent) => e.stopPropagation(); - titleEle.onchange = (e: any) => { + titleEle.onchange = (e: InputEvent) => { undoable(() => { - titleEle.size = e.currentTarget.value.length + 3; - doc[DocData].title = e.currentTarget.value; + const target = e.currentTarget as unknown as { value: string }; + titleEle.size = target?.value.length + 3; + doc[DocData].title = target?.value ?? ''; }, 'edit tab title')(); }; @@ -399,9 +402,10 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { tab._disposers.color = reaction( () => ({ variant: SnappingManager.userVariantColor, degree: Doc.GetBrushStatus(doc), highlight: DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting) }), ({ variant, degree, highlight }) => { - const color = highlight?.highlightIndex === Doc.DocBrushStatus.highlighted ? highlight.highlightColor : degree ? ['transparent', variant, variant, 'orange'][degree] : variant; + const { highlightIndex, highlightColor } = (highlight as { highlightIndex: number; highlightColor: string }) ?? { highlightIndex: undefined, highlightColor: undefined }; + const color = highlightIndex === Doc.DocBrushStatus.highlighted ? highlightColor : degree ? ['transparent', variant, variant, 'orange'][degree] : variant; - const textColor = color === variant ? SnappingManager.userColor ?? '' : lightOrDark(color); + const textColor = color === variant ? (SnappingManager.userColor ?? '') : lightOrDark(color); titleEle.style.color = textColor; iconWrap.style.color = textColor; closeWrap.style.color = textColor; @@ -448,8 +452,8 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { }; // select the tab document when the tab is directly clicked and activate the tab whenver the tab document is selected - titleEle.onpointerdown = action((e: any) => { - if (e.target.className !== 'lm_iconWrap') { + titleEle.onpointerdown = action((e: PointerEvent) => { + if ((e.target as HTMLElement)?.className !== 'lm_iconWrap') { if (this.view) DocumentView.SelectView(this.view, false); else this._activated = true; if (Date.now() - titleEle.lastClick < 1000) titleEle.select(); @@ -481,7 +485,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { tab.closeElement .off('click') // unbind the current click handler .click(() => { - Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); + Object.values(tab._disposers).forEach(disposer => (disposer as () => void)()); DocumentView.DeselectAll(); UndoManager.RunInBatch(() => tab.contentItem.remove(), 'delete tab'); }); @@ -489,8 +493,8 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { }; componentDidMount() { - new _global.ResizeObserver( - action((entries: any) => { + new ResizeObserver( + action(entries => { // eslint-disable-next-line no-restricted-syntax for (const entry of entries) { this._panelWidth = entry.contentRect.width; @@ -523,6 +527,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { public static DontSelectOnActivate = 'dontSelectOnActivate'; @action.bound + // eslint-disable-next-line @typescript-eslint/no-explicit-any private onActiveContentItemChanged(contentItem: any) { if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) { this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab; @@ -612,8 +617,8 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { PanelWidth={this.PanelWidth} PanelHeight={this.PanelHeight} styleProvider={DefaultStyleProvider} - childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist} - childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist} + childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyFilter} + childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyFilter} searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist} addDocument={undefined} removeDocument={this.remDocTab} @@ -623,7 +628,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { dontCenter="y" whenChildContentsActiveChanged={this.whenChildContentActiveChanges} focus={this.focusFunc} - containerViewPath={returnEmptyDoclist} + containerViewPath={returnEmptyDocViewList} pinToPres={TabDocView.PinDoc} /> {this.disableMinimap() ? null : <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} />} @@ -649,13 +654,13 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { this._view && DocumentView.removeView(this._view); } this._lastTab = this.tab; - (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document); + (this._mainCont as { InitTab?: (tab: object) => void }).InitTab = (tab: object) => this.init(tab, this._document); DocServer.GetRefField(this._props.documentId).then( action(doc => { doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document); }) ); - new _global.ResizeObserver(action(() => this._forceInvalidateScreenToLocal++)).observe(ref); + ref && new ResizeObserver(action(() => this._forceInvalidateScreenToLocal++)).observe(ref); } }}> {this.docView} diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index b82421e6b..b10a521ca 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -1,15 +1,12 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { IconButton, Size } from 'browndash-components'; -import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx'; +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, FieldType, Opt, StrListCast, returnEmptyDoclist } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; @@ -41,14 +38,15 @@ import { CollectionView } from './CollectionView'; import { TreeSort } from './TreeSort'; import './TreeView.scss'; +// eslint-disable-next-line @typescript-eslint/no-var-requires const { TREE_BULLET_WIDTH } = require('../global/globalCssVariables.module.scss'); // prettier-ignore export interface TreeViewProps { treeView: CollectionTreeView; // eslint-disable-next-line no-use-before-define parentTreeView: TreeView | CollectionTreeView | undefined; - observeHeight: (ref: any) => void; - unobserveHeight: (ref: any) => void; + observeHeight: (ref: HTMLDivElement) => void; + unobserveHeight: (ref: HTMLDivElement) => void; prevSibling?: Doc; Document: Doc; dataDoc?: Doc; @@ -188,7 +186,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { moving: boolean = false; @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { if (this.Document !== target && addDoc !== returnFalse) { - const canAdd1 = (this._props.parentTreeView as any).dropping || !(ComputedField.WithoutComputed(() => FieldValue(this._props.parentTreeView?.Document.data)) instanceof ComputedField); + const canAdd1 = (this._props.parentTreeView as TreeView).dropping || !(ComputedField.WithoutComputed(() => FieldValue(this._props.parentTreeView?.Document.data)) instanceof ComputedField); // bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse if (canAdd1 && this._props.removeDoc?.(doc) === true) { @@ -251,7 +249,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { return []; } - const runningChildren: FieldResult[] = []; + const runningChildren: Doc[] = []; childList.forEach(child => { if (child.runProcess && TreeView.GetRunningChildren.get(child)) { if (child.runProcess) { @@ -263,7 +261,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { return runningChildren; }; - static GetRunningChildren = new Map<Doc, any>(); + static GetRunningChildren = new Map<Doc, () => Doc[]>(); static ToggleChildrenRun = new Map<Doc, () => void>(); constructor(props: TreeViewProps) { super(props); @@ -285,7 +283,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { TreeView.GetRunningChildren.set(this.Document, () => this.getRunningChildren(this.childDocs)); } - _treeEle: any; + _treeEle: HTMLDivElement | null = null; protected createTreeDropTarget = (ele: HTMLDivElement) => { this._treedropDisposer?.(); ele && ((this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), this.Document, this.preTreeDrop.bind(this))), this.Document); @@ -469,14 +467,12 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { return false; } - refTransform = (ref: HTMLDivElement | undefined | null) => { + refTransform = (ref: HTMLElement | undefined | null) => { if (!ref) return this.ScreenToLocalTransform(); - const { translateX, translateY } = ClientUtils.GetScreenTransform(ref); - const outerXf = ClientUtils.GetScreenTransform(this.treeView.MainEle()); - const offset = this.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); - return this.ScreenToLocalTransform().translate(offset[0], offset[1]); + const { translateX, translateY, scale } = ClientUtils.GetScreenTransform(ref); + return new Transform(-translateX, -translateY, 1).scale(1 / scale); }; - docTransform = () => this.refTransform(this._dref?.ContentRef?.current); + docTransform = () => this.refTransform(this._dref?.ContentDiv); getTransform = () => this.refTransform(this._tref.current); embeddedPanelWidth = () => this._props.panelWidth() / (this.treeView._props.NativeDimScaling?.() || 1); embeddedPanelHeight = () => { @@ -526,7 +522,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { return toList(docs).reduce((flg, iDoc) => flg && innerAdd(iDoc), true as boolean); }; contentElement = TreeView.GetChildElements( - toList(contents as any), + contents instanceof Doc ? [contents] : DocListCast(contents), this.treeView, this, doc, @@ -574,9 +570,11 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { rows.push( <div style={{ display: 'flex', overflow: 'auto' }} key={key}> <span - ref={action((r: any) => { - if (r) leftOffset.width = r.getBoundingClientRect().width; - })} + ref={r => + runInAction(() => { + if (r) leftOffset.width = r.getBoundingClientRect().width; + }) + } style={{ fontWeight: 'bold' }}> {key + ':'} @@ -610,7 +608,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { return rows; } - _renderTimer: any; + _renderTimer: NodeJS.Timeout | undefined; @observable _renderCount = 1; @computed get renderContent() { TraceMobx(); @@ -756,7 +754,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { } get onCheckedClick() { - return this.Document.type === DocumentType.COL ? undefined : this._props.onCheckedClick?.() ?? ScriptCast(this.Document.onCheckedClick); + return this.Document.type === DocumentType.COL ? undefined : (this._props.onCheckedClick?.() ?? ScriptCast(this.Document.onCheckedClick)); } @action @@ -779,9 +777,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { @computed get renderBullet() { TraceMobx(); - const iconType = this.treeView._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':treeOpen' : !this.childDocs.length ? ':empty' : '')) || 'question'; + const iconType = (this.treeView._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':treeOpen' : !this.childDocs.length ? ':empty' : '')) as string) || 'question'; const color = SettingsManager.userColor; - const checked = this.onCheckedClick ? this.Document.treeView_Checked ?? 'unchecked' : undefined; + const checked = this.onCheckedClick ? (this.Document.treeView_Checked ?? 'unchecked') : undefined; return ( <div className={`bullet${this.treeView.outlineMode ? '-outline' : ''}`} @@ -791,7 +789,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { style={ this.treeView.outlineMode ? { - opacity: this.titleStyleProvider?.(this.Document, this.treeView._props, StyleProp.Opacity), + opacity: this.titleStyleProvider?.(this.Document, this.treeView._props, StyleProp.Opacity) as number, } : { pointerEvents: this._props.isContentActive() ? 'all' : undefined, @@ -831,7 +829,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { @action expandNextviewType = () => { if (this.treeViewOpen && !this.Document.isFolder && !this.treeView.outlineMode && !this.Document.treeView_ExpandedViewLock) { - const next = (modes: any[]) => modes[(modes.indexOf(StrCast(this.treeViewExpandedView)) + 1) % modes.length]; + const next = (modes: string[]) => modes[(modes.indexOf(StrCast(this.treeViewExpandedView)) + 1) % modes.length]; this.Document.treeView_ExpandedView = next(this.validExpandViewTypes); } this.treeViewOpen = true; @@ -899,13 +897,13 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { onChildDoubleClick = () => ScriptCast(this.treeView.Document.treeView_ChildDoubleClick, !this.treeView.outlineMode ? this._openScript?.() : null); refocus = () => this.treeView._props.focus(this.treeView.Document, {}); - ignoreEvent = (e: any) => { + ignoreEvent = (e: React.MouseEvent) => { if (this._props.isContentActive(true)) { e.stopPropagation(); e.preventDefault(); } }; - titleStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => { + titleStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => { if (!doc || doc !== this.Document) return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView const { treeView } = this; @@ -925,7 +923,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { style={{ // just render a title for a tree view label (identified by treeViewDoc being set in 'props') maxWidth: props?.PanelWidth() || undefined, - background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor), + background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor) as string, outline: SnappingManager.IsDragging ? undefined: `solid ${highlightColor} ${highlightIndex}px`, paddingLeft: NumCast(treeView.Document.childXPadding, NumCast(treeView._props.childXPadding, Doc.IsComicStyle(doc)?20:0)), paddingRight: NumCast(treeView.Document.childXPadding, NumCast(treeView._props.childXPadding, Doc.IsComicStyle(doc)?20:0)), @@ -940,7 +938,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { } return treeView._props.styleProvider?.(doc, props, property); }; - embeddedStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => { + embeddedStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => { if (property.startsWith(StyleProp.Decorations)) return null; return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView }; @@ -992,28 +990,30 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { this._editTitle = e; })} GetValue={() => StrCast(this.Document.title)} - OnTab={undoBatch((shift?: boolean) => { + OnTab={undoable((shift?: boolean) => { if (!shift) this._props.indentDocument?.(true); else this._props.outdentDocument?.(true); - })} - OnEmpty={undoBatch(() => this.treeView.outlineMode && this._props.removeDoc?.(this.Document))} + }, 'create new tree Doc')} + OnEmpty={undoable(() => this.treeView.outlineMode && this._props.removeDoc?.(this.Document), 'remove tree doc')} OnFillDown={() => this.treeView.fileSysMode && this.makeFolder()} - SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => { + SetValue={undoable((value: string, shiftKey: boolean, enterKey: boolean) => { Doc.SetInPlace(this.Document, 'title', value, false); - this.treeView.outlineMode && enterKey && this.makeTextCollection(); - })} + return this.treeView.outlineMode && enterKey && this.makeTextCollection(); + }, 'set tree doc title')} /> ) : ( <DocumentView key="title" - ref={action((r: any) => { - this._docRef = r || undefined; - if (this._docRef && TreeView._editTitleOnLoad?.id === this.Document[Id] && TreeView._editTitleOnLoad.parent === this._props.parentTreeView) { - this._docRef.select(false); - this.setEditTitle(this._docRef); - TreeView._editTitleOnLoad = undefined; - } - })} + ref={r => + runInAction(() => { + this._docRef = r || undefined; + if (this._docRef && TreeView._editTitleOnLoad?.id === this.Document[Id] && TreeView._editTitleOnLoad.parent === this._props.parentTreeView) { + this._docRef.select(false); + this.setEditTitle(this._docRef); + TreeView._editTitleOnLoad = undefined; + } + }) + } Document={this.Document} fitWidth={returnTrue} scriptContext={this} @@ -1070,9 +1070,11 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { </div> <div className="treeView-rightButtons" - ref={action((r: any) => { - r && (this.headerEleWidth = r.getBoundingClientRect().width); - })}> + ref={r => + runInAction(() => { + r && (this.headerEleWidth = r.getBoundingClientRect().width); + }) + }> {this.titleButtons} </div> </> @@ -1092,7 +1094,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { this, e, () => { - (this._dref ?? this._docRef)?.startDragging(e.clientX, e.clientY, '' as any); + (this._dref ?? this._docRef)?.startDragging(e.clientX, e.clientY, undefined); return true; }, returnFalse, @@ -1181,7 +1183,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { @computed get renderBorder() { const sorting = StrCast(this.Document.treeView_SortCriterion, TreeSort.WhenAdded); - const sortings = (this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewSortings) ?? {}) as { [key: string]: { color: string; label: string } }; + const sortings = (this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewSortings) ?? {}) as { [key: string]: { color: string; icon: JSX.Element } }; return ( <div className={`treeView-border${this.treeView.outlineMode ? TreeViewType.outline : ''}`} style={{ borderColor: sortings[sorting]?.color }}> {!this.treeViewOpen ? null : this.renderContent} @@ -1274,8 +1276,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { firstLevel: boolean, whenChildContentsActiveChanged: (isActive: boolean) => void, dontRegisterView: boolean | undefined, - observerHeight: (ref: any) => void, - unobserveHeight: (ref: any) => void, + observerHeight: (ref: HTMLElement) => void, + unobserveHeight: (ref: HTMLElement) => void, contextMenuItems: { script: ScriptField; filter: ScriptField; label: string; icon: string }[], // TODO: [AL] add these AddToMap?: (treeViewDoc: Doc, index: number[]) => void, diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx index fc39cafaa..c17371151 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx @@ -12,7 +12,7 @@ import './CollectionFreeFormView.scss'; * returns a truthy value */ // eslint-disable-next-line no-use-before-define -export type infoArc = [() => any, (res?: any) => infoState]; +export type infoArc = [() => unknown, (res?: unknown) => infoState]; export const StateMessage = Symbol('StateMessage'); export const StateMessageGIF = Symbol('StateMessageGIF'); @@ -20,9 +20,9 @@ export const StateEntryFunc = Symbol('StateEntryFunc'); export class infoState { [StateMessage]: string = ''; [StateMessageGIF]?: string = ''; - [StateEntryFunc]?: () => any; + [StateEntryFunc]?: () => unknown; [key: string]: infoArc; - constructor(message: string, arcs: { [key: string]: infoArc }, messageGif?: string, entryFunc?: () => any) { + constructor(message: string, arcs: { [key: string]: infoArc }, messageGif?: string, entryFunc?: () => unknown) { this[StateMessage] = message; Object.assign(this, arcs); this[StateMessageGIF] = messageGif; @@ -44,7 +44,7 @@ export function InfoState( msg: string, // arcs: { [key: string]: infoArc }, gif?: string, - entryFunc?: () => any + entryFunc?: () => unknown ) { // eslint-disable-next-line new-cap return new infoState(msg, arcs, gif, entryFunc); @@ -52,7 +52,7 @@ export function InfoState( export interface CollectionFreeFormInfoStateProps { infoState: infoState; - next: (state: infoState) => any; + next: (state: infoState) => unknown; close: () => void; } @@ -61,7 +61,7 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec _disposers: IReactionDisposer[] = []; @observable _expanded = false; - constructor(props: any) { + constructor(props: CollectionFreeFormInfoStateProps) { super(props); makeObservable(this); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index de51cc73c..79aad0ef2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -9,7 +9,7 @@ import { aggregateBounds } from '../../../../Utils'; export interface ViewDefBounds { type: string; - payload: any; + payload: unknown; x: number; y: number; z?: number; @@ -72,11 +72,15 @@ function toLabel(target: FieldResult<FieldType>) { */ function getTextWidth(text: string, font: string): number { // re-use canvas object for better performance - const canvas = (getTextWidth as any).canvas || ((getTextWidth as any).canvas = document.createElement('canvas')); + const selfStoreHack = getTextWidth as unknown as { canvas: Element }; + const canvas = (selfStoreHack.canvas = (selfStoreHack.canvas as unknown as HTMLCanvasElement) ?? document.createElement('canvas')); const context = canvas.getContext('2d'); - context.font = font; - const metrics = context.measureText(text); - return metrics.width; + if (context) { + context.font = font; + const metrics = context.measureText(text); + return metrics.width; + } + return 0; } interface PivotColumn { @@ -131,13 +135,13 @@ export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc return normalizeResults(burstDiam, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]); } -export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { +export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: unknown) { const docMap = new Map<string, PoolData>(); const fieldKey = 'data'; const pivotColumnGroups = new Map<FieldResult<FieldType>, PivotColumn>(); let nonNumbers = 0; - const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField) || 'author'; + const pivotFieldKey = toLabel((engineProps as { pivotField?: string })?.pivotField ?? pivotDoc._pivotField) || 'author'; childPairs.forEach(pair => { const listValue = Cast(pair.layout[pivotFieldKey], listSpec('string'), null); @@ -265,7 +269,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, - payload: pivotColumnGroups.get(key)!.filters, + payload: pivotColumnGroups.get(key)?.filters, })); groupNames.push(...dividers); // eslint-disable-next-line no-use-before-define diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx index e543b4008..bc9dd022c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx @@ -54,8 +54,8 @@ export class CollectionFreeFormPannableContents extends ObservableReactComponent <div className={'collectionfreeformview' + (this._props.viewDefDivClick ? '-viewDef' : '-none')} onScroll={e => { - const target = e.target as any; - if (getComputedStyle(target)?.overflow === 'visible') { + const { target } = e; + if (target instanceof Element && getComputedStyle(target)?.overflow === 'visible') { target.scrollTop = target.scrollLeft = 0; // if collection is visible, scrolling messes things up since there are no scroll bars } }} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index b6e1fca77..c4cf8dee7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,15 +1,14 @@ /* eslint-disable react/jsx-props-no-spreading */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -/* eslint-disable jsx-a11y/no-static-element-interactions */ import { Bezier } from 'bezier-js'; import { Colors } from 'browndash-components'; +import { Property } from 'csstype'; import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../../ClientUtils'; import { DateField } from '../../../../fields/DateField'; -import { ActiveEraserWidth, ActiveInkWidth, Doc, DocListCast, Field, FieldType, Opt, SetActiveInkColor, SetActiveInkWidth } from '../../../../fields/Doc'; +import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc'; import { DocData, Height, Width } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { InkData, InkField, InkTool, Segment } from '../../../../fields/InkField'; @@ -32,20 +31,20 @@ import { CompileScript } from '../../../util/Scripting'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; import { freeformScrollMode, SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; -import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager'; +import { undoable, UndoManager } from '../../../util/UndoManager'; import { Timeline } from '../../animationtimeline/Timeline'; import { ContextMenu } from '../../ContextMenu'; import { InkingStroke } from '../../InkingStroke'; import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp'; -import { DocumentView } from '../../nodes/DocumentView'; +import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveEraserWidth, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, DocumentView, SetActiveInkColor, SetActiveInkWidth } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import { FocusViewOptions } from '../../nodes/FocusViewOptions'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { OpenWhere, OpenWhereMod } from '../../nodes/OpenWhere'; import { PinDocView, PinProps } from '../../PinFuncs'; import { StyleProp } from '../../StyleProp'; -import { CollectionSubView } from '../CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView'; import { TreeViewType } from '../CollectionTreeViewType'; import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid'; import { CollectionFreeFormClusters } from './CollectionFreeFormClusters'; @@ -70,7 +69,7 @@ export interface collectionFreeformViewProps { childPointerEvents?: () => string | undefined; viewField?: string; noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale) - engineProps?: any; + engineProps?: unknown; getScrollHeight?: () => number | undefined; } @@ -82,13 +81,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection public unprocessedDocs: Doc[] = []; public static collectionsWithUnprocessedInk = new Set<CollectionFreeFormView>(); public static from(dv?: DocumentView): CollectionFreeFormView | undefined { - const parent = CollectionFreeFormDocumentView.from(dv)?._props.parent; + const parent = CollectionFreeFormDocumentView.from(dv)?._props.reactParent; return parent instanceof CollectionFreeFormView ? parent : undefined; } private _clusters = new CollectionFreeFormClusters(this); - private _oldWheel: any; - private _panZoomTransitionTimer: any; + private _oldWheel: HTMLDivElement | null = null; + private _panZoomTransitionTimer: NodeJS.Timeout | undefined = undefined; + private _brushtimer: NodeJS.Timeout | undefined = undefined; + private _brushtimer1: NodeJS.Timeout | undefined = undefined; private _lastX: number = 0; private _lastY: number = 0; private _downX: number = 0; @@ -97,8 +98,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection private _disposers: { [name: string]: IReactionDisposer } = {}; private _renderCutoffData = observable.map<string, boolean>(); private _batch: UndoManager.Batch | undefined = undefined; - private _brushtimer: any; - private _brushtimer1: any; private _keyTimer: NodeJS.Timeout | undefined; // timer for turning off transition flag when key frame change has completed. Need to clear this if you do a second navigation before first finishes, or else first timer can go off during second naviation. private _presEaseFunc: string = 'ease'; @@ -122,14 +121,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @observable _marqueeViewRef = React.createRef<MarqueeView>(); @observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined = undefined; // highlighted region of freeform canvas used by presentations to indicate a region @observable GroupChildDrag: boolean = false; // child document view being dragged. needed to update drop areas of groups when a group item is dragged. - @observable _childPointerEvents: 'none' | 'all' | 'visiblepainted' | undefined = undefined; + @observable _childPointerEvents: Property.PointerEvents | undefined = undefined; @observable _lightboxDoc: Opt<Doc> = undefined; @observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, ''); @observable _keyframeEditing = false; @observable _eraserX: number = 0; @observable _eraserY: number = 0; @observable _showEraserCircle: boolean = false; // to determine whether the radius eraser should show - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } @@ -139,12 +138,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @computed get childPointerEvents() { return SnappingManager.IsResizing ? 'none' - : this._props.childPointerEvents?.() ?? + : (this._props.childPointerEvents?.() ?? (this._props.viewDefDivClick || // (this.layoutEngine === computePassLayout.name && !this._props.isSelected()) || this.isContentActive() === false ? 'none' - : this._props.pointerEvents?.()); + : this._props.pointerEvents?.())); } @computed get contentViews() { const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1 && ele.inkMask !== undefined).map(ele => ele.ele); @@ -184,7 +183,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection .transform(this.panZoomXf); } @computed get backgroundColor() { - return this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor); + return this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) as string; } @computed get fitWidth() { return this._props.fitWidth?.(this.Document) ?? this.layoutDoc.layout_fitWidth; @@ -356,7 +355,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection * @param options * @returns */ - focus = (anchor: Doc, options: FocusViewOptions): any => { + focus = (anchor: Doc, options: FocusViewOptions) => { if (anchor.isGroup && !options.docTransform && options.contextPath?.length) { // don't focus on group if there's a context path because we're about to focus on a group item // which will override any group focus. (If we allowed the group to focus, it would mark didMove even if there were no net movement) @@ -373,14 +372,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const xfToCollection = options?.docTransform ?? Transform.Identity(); const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined }; const cantTransform = this.fitContentsToBox || ((this.Document.isGroup || this.layoutDoc._lockedTransform) && !DocumentView.LightboxDoc()); - const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? options?.zoomScale ?? 0.75 : undefined); + const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? (options?.zoomScale ?? 0.75) : undefined); // focus on the document in the collection const didMove = !cantTransform && !anchor.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale); if (didMove) options.didMove = true; // glr: freeform transform speed can be set by adjusting presentation_transition field - needs a way of knowing when presentation is not active... if (didMove) { - const focusTime = options?.instant ? 0 : options.zoomTime ?? 500; + const focusTime = options?.instant ? 0 : (options.zoomTime ?? 500); (options.zoomScale ?? options.willZoomCentered) && scale && (this.Document[this.scaleFieldKey] = scale); this.setPan(panX, panY, focusTime); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow return focusTime; @@ -442,8 +441,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return true; } - @undoBatch - internalAnchorAnnoDrop(e: Event, de: DragManager.DropEvent, annoDragData: DragManager.AnchorAnnoDragData) { + internalAnchorAnnoDrop = undoable((e: Event, de: DragManager.DropEvent, annoDragData: DragManager.AnchorAnnoDragData) => { const dropCreator = annoDragData.dropDocCreator; const [xp, yp] = this.screenToFreeformContentsXf.transformPoint(de.x, de.y); annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => { @@ -456,10 +454,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return dropDoc || this.Document; }; return true; - } + }, 'anchor drop'); - @undoBatch - internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData) { + internalLinkDrop = undoable((e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData) => { if (this.DocumentView?.() && linkDragData.linkDragView.containerViewPath?.().includes(this.DocumentView())) { const [x, y] = this.screenToFreeformContentsXf.transformPoint(de.x, de.y); // do nothing if link is dropped into any freeform view parent of dragged document @@ -475,9 +472,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return added; } return false; - } + }, 'link drop'); - onInternalDrop = (e: Event, de: DragManager.DropEvent) => { + onInternalDrop = (e: Event, de: DragManager.DropEvent): boolean => { if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de, de.complete.annoDragData); if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData); if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData); @@ -523,8 +520,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } }; - @undoBatch - onGesture = (e: Event, ge: GestureUtils.GestureEvent) => { + onGesture = undoable((e: Event, ge: GestureUtils.GestureEvent) => { switch (ge.gesture) { case Gestures.Text: if (ge.text) { @@ -550,7 +546,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection _width: B.width + inkWidth, _height: B.height + inkWidth, stroke_showLabel: BoolCast(Doc.UserDoc().activeInkHideTextLabels)}, // prettier-ignore - inkWidth + inkWidth, + ActiveInkColor(), + ActiveInkBezierApprox(), + ActiveFillColor(), + ActiveArrowStart(), + ActiveArrowEnd(), + ActiveDash(), + ActiveIsInkMask() ); if (Doc.ActiveTool === InkTool.Write) { this.unprocessedDocs.push(inkDoc); @@ -560,7 +563,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection e.stopPropagation(); } } - }; + }, 'gesture'); @action onEraserUp = (): void => { this._deleteList.lastElement()?._props.removeDocument?.(this._deleteList.map(ink => ink.Document)); @@ -634,7 +637,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection _width: B.width + inkWidth, _height: B.height + inkWidth, stroke_showLabel: BoolCast(Doc.UserDoc().activeInkHideTextLabels)}, // prettier-ignore - inkWidth + inkWidth, + ActiveInkColor(), + ActiveInkBezierApprox(), + ActiveFillColor(), + ActiveArrowStart(), + ActiveArrowEnd(), + ActiveDash(), + ActiveIsInkMask() ); }); newStrokes && this.addDocument?.(newStrokes); @@ -684,8 +694,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return false; }; - forceStrokeGesture = (e: PointerEvent, gesture: Gestures, points: InkData, text?: any) => { - this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, InkField.getBounds(points), text)); + forceStrokeGesture = (e: PointerEvent, gesture: Gestures, points: InkData) => { + this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, InkField.getBounds(points))); }; onPointerMove = (e: PointerEvent) => { @@ -1163,6 +1173,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection // for some reason bezier.js doesn't handle the case of intersecting a linear curve, so we wrap the intersection // call in a test for linearity bintersects = (curve: Bezier, otherCurve: Bezier) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any if ((curve as any)._linear) { // bezier.js doesn't intersect properly if the curve is actually a line -- so get intersect other curve against this line, then figure out the t coordinates of the intersection on this line const intersections = otherCurve.lineIntersects({ p1: curve.points[0], p2: curve.points[3] }); @@ -1172,6 +1183,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return intT ? [intT] : []; } } + // eslint-disable-next-line @typescript-eslint/no-explicit-any if ((otherCurve as any)._linear) { return curve.lineIntersects({ p1: otherCurve.points[0], p2: otherCurve.points[3] }); } @@ -1463,17 +1475,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return ret; }; childPointerEventsFunc = () => this._childPointerEvents; - childContentsActive = () => (this._props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)(); + childContentsActive = () => ((this._props.childContentsActive ?? this.isContentActive() === false) ? returnFalse : emptyFunction)(); getChildDocView(entry: PoolData) { const childLayout = entry.pair.layout; const childData = entry.pair.data; return ( <CollectionFreeFormDocumentView - // eslint-disable-next-line react/jsx-props-no-spreading - {...OmitKeys(entry, ['replica', 'pair']).omit} + // eslint-disable-next-line react/jsx-props-no-spreading, @typescript-eslint/no-explicit-any + {...(OmitKeys(entry, ['replica', 'pair']).omit as any)} key={childLayout[Id] + (entry.replica || '')} Document={childLayout} - parent={this} + reactParent={this} containerViewPath={this.DocumentView?.().docViewPath} styleProvider={this._clusters.styleProvider} TemplateDataDocument={childData} @@ -1588,7 +1600,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; } - onViewDefDivClick = (e: React.MouseEvent, payload: any) => { + onViewDefDivClick = (e: React.MouseEvent, payload: unknown) => { (this._props.viewDefDivClick || ScriptCast(this.Document.onViewDefDivClick))?.script.run({ this: this.Document, payload }); e.stopPropagation(); }; @@ -1622,7 +1634,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ele: ( <div className="collectionFreeform-customDiv" - title={viewDef.payload?.join(' ')} + title={StrListCast(viewDef.payload as string).join(' ')} key={'div' + x + y + z + viewDef.payload} onClick={e => this.onViewDefDivClick(e, viewDef)} style={{ width, height, backgroundColor: color, transform }} @@ -1639,11 +1651,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection * since rendering a large collection of documents can be slow, at startup, docs are rendered in batches. * each doc's render() method will call the cutoff provider which will let the doc know if it should render itself yet, or wait */ - renderCutoffProvider = computedFn((doc: Doc) => (this.Document.isTemplateDoc ? false : !this._renderCutoffData.get(doc[Id] + ''))); + renderCutoffProvider = computedFn((doc: Doc) => (this.Document.isTemplateDoc || this.Document.isTemplateForField ? false : !this._renderCutoffData.get(doc[Id] + ''))); doEngineLayout( poolData: Map<string, PoolData>, - engine: (poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) => ViewDefResult[] + engine: (poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: unknown) => ViewDefResult[] ) { return engine(poolData, this.Document, this.childLayoutPairs, [this._props.PanelWidth(), this._props.PanelHeight()], this.viewDefsToJSX, this._props.engineProps); } @@ -1673,7 +1685,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection .forEach(entry => elements.push({ ele: this.getChildDocView(entry[1]), - bounds: (entry[1].opacity === 0 ? { ...entry[1], width: 0, height: 0 } : { ...entry[1] }) as any, + bounds: entry[1].opacity === 0 ? { payload: undefined, type: '', ...entry[1], width: 0, height: 0 } : { payload: undefined, type: '', ...entry[1] }, inkMask: BoolCast(entry[1].pair.layout.stroke_isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1, }) ); @@ -1756,7 +1768,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this._disposers.pointerevents = reaction( () => this.childPointerEvents, pointerevents => { - this._childPointerEvents = pointerevents as any; + this._childPointerEvents = pointerevents as Property.PointerEvents | undefined; }, { fireImmediately: true } ); @@ -1795,24 +1807,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection Object.values(this._disposers).forEach(disposer => disposer?.()); } - updateIcon = () => - UpdateIcon( - this.layoutDoc[Id] + '-icon' + new Date().getTime(), - this.DocumentView?.().ContentDiv!, - NumCast(this.layoutDoc._width), - NumCast(this.layoutDoc._height), - this._props.PanelWidth(), - this._props.PanelHeight(), - 0, - 1, - false, - '', - (iconFile, nativeWidth, nativeHeight) => { - this.dataDoc.icon = new ImageField(iconFile); - this.dataDoc.icon_nativeWidth = nativeWidth; - this.dataDoc.icon_nativeHeight = nativeHeight; - } - ); + updateIcon = () => { + const contentDiv = this.DocumentView?.().ContentDiv; + contentDiv && + UpdateIcon( + this.layoutDoc[Id] + '-icon' + new Date().getTime(), + contentDiv, + NumCast(this.layoutDoc._width), + NumCast(this.layoutDoc._height), + this._props.PanelWidth(), + this._props.PanelHeight(), + 0, + 1, + false, + '', + (iconFile, nativeWidth, nativeHeight) => { + this.dataDoc.icon = new ImageField(iconFile); + this.dataDoc.icon_nativeWidth = nativeWidth; + this.dataDoc.icon_nativeHeight = nativeHeight; + } + ); + }; @action onCursorMove = (e: React.PointerEvent) => { @@ -1831,8 +1846,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this._showEraserCircle = true; }; - @undoBatch - promoteCollection = () => { + promoteCollection = undoable(() => { const childDocs = this.childDocs.slice(); childDocs.forEach(docIn => { const doc = docIn; @@ -1841,10 +1855,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection doc.y = scr?.[1]; }); this._props.addDocTab(childDocs, OpenWhere.inParentFromScreen); - }; + }, 'promote collection'); - @undoBatch - layoutDocsInGrid = () => { + layoutDocsInGrid = undoable(() => { const docs = this.childLayoutPairs.map(pair => pair.layout); const width = Math.max(...docs.map(doc => NumCast(doc._width))) + 20; const height = Math.max(...docs.map(doc => NumCast(doc._height))) + 20; @@ -1854,40 +1867,37 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection doc.x = NumCast(this.Document[this.panXFieldKey]) + (i % dim) * width - (width * dim) / 2; doc.y = NumCast(this.Document[this.panYFieldKey]) + Math.floor(i / dim) * height - (height * dim) / 2; }); - }; + }, 'layout docs in grid'); - @undoBatch - toggleNativeDimensions = () => Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight); + toggleNativeDimensions = undoable(() => Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight), 'toggle native dimensions'); /// /// resetView restores a freeform collection to unit scale and centered at (0,0) UNLESS /// the view is a group, in which case this does nothing (since Groups calculate their own scale and center) /// - @undoBatch - resetView = () => { + resetView = undoable(() => { this.layoutDoc[this.panXFieldKey] = NumCast(this.dataDoc[this.panXFieldKey + '_reset']); this.layoutDoc[this.panYFieldKey] = NumCast(this.dataDoc[this.panYFieldKey + '_reset']); this.layoutDoc[this.scaleFieldKey] = NumCast(this.dataDoc[this.scaleFieldKey + '_reset'], 1); - }; + }, 'reset view'); /// /// resetView restores a freeform collection to unit scale and centered at (0,0) UNLESS /// the view is a group, in which case this does nothing (since Groups calculate their own scale and center) /// - @undoBatch - toggleResetView = () => { + toggleResetView = undoable(() => { this.dataDoc[this.autoResetFieldKey] = !this.dataDoc[this.autoResetFieldKey]; if (this.dataDoc[this.autoResetFieldKey]) { this.dataDoc[this.panXFieldKey + '_reset'] = this.layoutDoc[this.panXFieldKey]; this.dataDoc[this.panYFieldKey + '_reset'] = this.layoutDoc[this.panYFieldKey]; this.dataDoc[this.scaleFieldKey + '_reset'] = this.layoutDoc[this.scaleFieldKey]; } - }; + }, 'toggle reset view'); onContextMenu = () => { if (this._props.isAnnotationOverlay || !ContextMenu.Instance) return; const appearance = ContextMenu.Instance.findByDescription('Appearance...'); - const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : []; + const appearanceItems = appearance?.subitems ?? []; !this.Document.isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' }); !this.Document.isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' }); if (this._props.setContentViewBox === emptyFunction) { @@ -1914,7 +1924,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' }); const options = ContextMenu.Instance.findByDescription('Options...'); - const optionItems = options && 'subitems' in options ? options.subitems : []; + const optionItems = options?.subitems ?? []; !this._props.isAnnotationOverlay && !Doc.noviceMode && optionItems.push({ @@ -1938,12 +1948,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } !options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); const mores = ContextMenu.Instance.findByDescription('More...'); - const moreItems = mores && 'subitems' in mores ? mores.subitems : []; + const moreItems = mores?.subitems ?? []; !mores && ContextMenu.Instance.addItem({ description: 'More...', subitems: moreItems, icon: 'eye' }); }; - @undoBatch - transcribeStrokes = () => { + transcribeStrokes = undoable(() => { if (this.Document.isGroup && this.Document.transcription) { const text = StrCast(this.Document.transcription); const lines = text.split('\n'); @@ -1951,7 +1960,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.addDocument(Docs.Create.TextDocument(text, { title: lines[0], x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) + 20, y: NumCast(this.layoutDoc.y), _width: 200, _height: height })); } - }; + }, 'transcribe strokes'); @action dragEnding = () => { @@ -1993,7 +2002,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection incrementalRender = action(() => { if (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.())) { const layoutUnrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])); - const loadIncrement = this.Document.isTemplateDoc ? Number.MAX_VALUE : 5; + const loadIncrement = this.Document.isTemplateDoc || this.Document.isTemplateForField ? Number.MAX_VALUE : 5; for (let i = 0; i < Math.min(layoutUnrendered.length, loadIncrement); i++) { this._renderCutoffData.set(layoutUnrendered[i][Id] + '', true); } @@ -2117,7 +2126,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection onDragOver={e => e.preventDefault()} onContextMenu={this.onContextMenu} style={{ - pointerEvents: this._props.isContentActive() && SnappingManager.IsDragging ? 'all' : (this._props.pointerEvents?.() as any), + pointerEvents: this._props.isContentActive() && SnappingManager.IsDragging ? 'all' : this._props.pointerEvents?.(), textAlign: this.isAnnotationOverlay ? 'initial' : undefined, transform: `scale(${this.nativeDimScaling})`, width: `${100 / this.nativeDimScaling}%`, diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx index 1d3f88df1..50b91e8fe 100644 --- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx +++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx @@ -1,26 +1,27 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +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 { observer } from 'mobx-react'; import React from 'react'; -import { Docs } from '../../../documents/Documents'; -import { DocumentType } from '../../../documents/DocumentTypes'; -import { ViewBoxBaseComponent } from '../../DocComponent'; -import { FieldView, FieldViewProps } from '../../nodes/FieldView'; -import 'ldrs/ring'; -import { SnappingManager } from '../../../util/SnappingManager'; -import { action, computed, makeObservable, observable, reaction } from 'mobx'; -import { Doc, DocListCast, NumListCast } from '../../../../fields/Doc'; +import { Utils } from '../../../../Utils'; +import { Doc, DocListCast } from '../../../../fields/Doc'; import { DocData } from '../../../../fields/DocSymbols'; +import { Id } from '../../../../fields/FieldSymbols'; +import { List } from '../../../../fields/List'; import { ImageCast, StrCast } from '../../../../fields/Types'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { Docs } from '../../../documents/Documents'; +import { DragManager } from '../../../util/DragManager'; +import { SnappingManager } from '../../../util/SnappingManager'; +import { ViewBoxBaseComponent } from '../../DocComponent'; import { ObservableReactComponent } from '../../ObservableReactComponent'; +import { DocumentView } from '../../nodes/DocumentView'; +import { FieldView, FieldViewProps } from '../../nodes/FieldView'; import './FaceCollectionBox.scss'; -import { IconButton, Size } from 'browndash-components'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { MarqueeOptionsMenu } from './MarqueeOptionsMenu'; -import { List } from '../../../../fields/List'; -import { DocumentView } from '../../nodes/DocumentView'; -import { Utils } from '../../../../Utils'; -import { DragManager } from '../../../util/DragManager'; -import * as faceapi from 'face-api.js'; -import { FaceMatcher } from 'face-api.js'; interface FaceDocumentProps { faceDoc: Doc; @@ -36,7 +37,7 @@ export class FaceDocumentItem extends ObservableReactComponent<FaceDocumentProps private _dropDisposer?: DragManager.DragDropDisposer; private _inputRef = React.createRef<HTMLInputElement>(); - constructor(props: any) { + constructor(props: FaceDocumentProps) { super(props); makeObservable(this); this.ref = React.createRef(); @@ -194,7 +195,7 @@ export class FaceCollectionBox extends ViewBoxBaseComponent<FieldViewProps>() { } } - constructor(props: any) { + constructor(props: FieldViewProps) { super(props); makeObservable(this); FaceCollectionBox.Instance = this; @@ -204,7 +205,7 @@ export class FaceCollectionBox extends ViewBoxBaseComponent<FieldViewProps>() { return ( <div className="searchBox-container" style={{ pointerEvents: 'all', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}> {this.currentDocs.map(doc => { - return <FaceDocumentItem faceDoc={doc} />; + return <FaceDocumentItem key={doc[Id]} faceDoc={doc} />; })} </div> ); diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index 2d9191dd7..61bd0241c 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -13,7 +13,7 @@ import { undoBatch } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { ContextMenuProps } from '../../ContextMenuItem'; import { DocumentView } from '../../nodes/DocumentView'; -import { CollectionSubView } from '../CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView'; import './CollectionGridView.scss'; import Grid, { Layout } from './Grid'; @@ -26,7 +26,7 @@ export class CollectionGridView extends CollectionSubView() { @observable private _scroll: number = 0; // required to make sure the decorations box container updates on scroll private dropLocation: object = {}; // sets the drop location for external drops - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } @@ -200,7 +200,7 @@ export class CollectionGridView extends CollectionSubView() { whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} onClickScript={this.onChildClickHandler} renderDepth={this._props.renderDepth + 1} - 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/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index eac0dc0e1..ceae43c04 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -1,8 +1,7 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { Toggle, ToggleType, Type } from 'browndash-components'; +import { Property } from 'csstype'; import { IReactionDisposer, action, makeObservable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -22,7 +21,7 @@ import { UndoStack } from '../../UndoStack'; import { DocumentLinksButton } from '../../nodes/DocumentLinksButton'; import { DocumentView } from '../../nodes/DocumentView'; import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup'; -import { CollectionSubView } from '../CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView'; import './CollectionLinearView.scss'; /** @@ -39,7 +38,7 @@ export class CollectionLinearView extends CollectionSubView() { private _widthDisposer?: IReactionDisposer; private _selectedDisposer?: IReactionDisposer; - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } @@ -239,7 +238,7 @@ export class CollectionLinearView extends CollectionSubView() { className="collectionLinearView-content" style={{ height: this.dimension(), - flexDirection: flexDir as any, + flexDirection: flexDir as Property.FlexDirection, gap: flexGap, }}> {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss index f983fd815..06d78c39e 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss @@ -1,43 +1,50 @@ -.collectionMulticolumnView_contents { - display: flex; - //overflow: hidden; // bcz: turned of to allow highlighting to appear when there is no border (e.g, for a component of the slide template) - width: 100%; +.collectionMulticolumnView_drop { height: 100%; + top: 0; + left: 0; + position: absolute; - .document-wrapper { + .collectionMulticolumnView_contents { display: flex; - flex-direction: column; + //overflow: hidden; // bcz: turned of to allow highlighting to appear when there is no border (e.g, for a component of the slide template) width: 100%; - align-items: center; - position: relative; - > .iconButton-container { - top: 0; - left: 0; - position: absolute; - } - - .contentFittingDocumentView { - margin: auto; - } + height: 100%; - .label-wrapper { + .document-wrapper { display: flex; - flex-direction: row; - justify-content: center; - height: 20px; + flex-direction: column; + width: 100%; + align-items: center; + position: relative; + > .iconButton-container { + top: 0; + left: 0; + position: absolute; + } + + .contentFittingDocumentView { + margin: auto; + } + + .label-wrapper { + display: flex; + flex-direction: row; + justify-content: center; + height: 20px; + } } - } - .multiColumnResizer { - cursor: ew-resize; - transition: 0.5s opacity ease; - display: flex; - flex-direction: column; + .multiColumnResizer { + cursor: ew-resize; + transition: 0.5s opacity ease; + display: flex; + flex-direction: column; - .multiColumnResizer-hdl { - width: 100%; - height: 100%; - transition: 0.5s background-color ease; + .multiColumnResizer-hdl { + width: 100%; + height: 100%; + transition: 0.5s background-color ease; + } } } } diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index b8509a005..d67e10c0b 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'; @@ -12,13 +10,14 @@ import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types import { DragManager } from '../../../util/DragManager'; import { SettingsManager } from '../../../util/SettingsManager'; import { Transform } from '../../../util/Transform'; -import { undoBatch, undoable } from '../../../util/UndoManager'; +import { 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'; import { dropActionType } from '../../../util/DropActionTypes'; +import { SnappingManager } from '../../../util/SnappingManager'; interface WidthSpecifier { magnitude: number; @@ -42,7 +41,7 @@ const resizerWidth = 8; export class CollectionMulticolumnView extends CollectionSubView() { @observable _startIndex = 0; - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } @@ -198,30 +197,28 @@ export class CollectionMulticolumnView extends CollectionSubView() { * documents before the target. */ private lookupIndividualTransform = (layout: Doc) => { - const { columnUnitLength } = this; - if (columnUnitLength === undefined) { + if (this.columnUnitLength === undefined) { return Transform.Identity(); // we're still waiting on promises to resolve } let offset = 0; // eslint-disable-next-line no-restricted-syntax for (const { layout: candidate } of this.childLayoutPairs) { if (candidate === layout) { - return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1)); + return this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0); } offset += this.lookupPixels(candidate) + resizerWidth; } return Transform.Identity(); }; - @undoBatch onInternalDrop = (e: Event, de: DragManager.DropEvent) => { let dropInd = -1; - if (de.complete.docDragData && this._mainCont) { + if (de.complete.docDragData && this._contRef.current) { let curInd = -1; de.complete.docDragData?.droppedDocuments.forEach(d => { curInd = this.childDocs.indexOf(d); }); - Array.from(this._mainCont.children).forEach((child, index) => { + Array.from(this._contRef.current.children).forEach((child, index) => { const brect = child.getBoundingClientRect(); if (brect.x < de.x && brect.x + brect.width > de.x) { if (curInd !== -1 && curInd === Math.floor(index / 2)) { @@ -305,7 +302,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'} /> ); }; @@ -319,11 +316,11 @@ export class CollectionMulticolumnView extends CollectionSubView() { this.childLayouts.forEach((layout, i) => { collector.push( // eslint-disable-next-line react/no-array-index-key - <Tooltip title={'Tab: ' + StrCast(layout.title)} key={'wrapper' + i}> + <Tooltip title={'Doc: ' + StrCast(layout.title)} key={'wrapper' + i}> <div className="document-wrapper" style={{ flexDirection: 'column', width: this.lookupPixels(layout) }}> {this.getDisplayDoc(layout)} {this.layoutDoc._chromeHidden ? null : ( - <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(() => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} /> + <Button tooltip="Remove document" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(() => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} /> )} <WidthLabel layout={layout} collectionDoc={this.Document} /> </div> @@ -345,49 +342,53 @@ export class CollectionMulticolumnView extends CollectionSubView() { return collector; } + _contRef = React.createRef<HTMLDivElement>(); render() { return ( - <div - className="collectionMulticolumnView_contents" - ref={this.createDashEventsTarget} - style={{ - width: `calc(100% - ${2 * NumCast(this.Document._xMargin)}px)`, - height: `calc(100% - ${2 * NumCast(this.Document._yMargin)}px)`, - marginLeft: NumCast(this.Document._xMargin), - marginRight: NumCast(this.Document._xMargin), - marginTop: NumCast(this.Document._yMargin), - marginBottom: NumCast(this.Document._yMargin), - }}> - {this.contents} - {!this._startIndex ? null : ( - <Tooltip title="scroll back"> - <div - style={{ position: 'absolute', bottom: 0, left: 0, background: SettingsManager.userVariantColor }} - onClick={action(() => { - this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown); - })}> - <Button - tooltip="Scroll back" - icon={<FontAwesomeIcon icon="chevron-left" size="lg" />} + <div className="collectionMulticolumnView_drop" ref={this.createDashEventsTarget}> + <div + className="collectionMulticolumnView_contents" + ref={this._contRef} + style={{ + pointerEvents: this._props.isContentActive() && SnappingManager.IsDragging ? 'all' : this._props.pointerEvents?.(), + width: `calc(100% - ${2 * NumCast(this.Document._xMargin)}px)`, + height: `calc(100% - ${2 * NumCast(this.Document._yMargin)}px)`, + marginLeft: NumCast(this.Document._xMargin), + marginRight: NumCast(this.Document._xMargin), + marginTop: NumCast(this.Document._yMargin), + marginBottom: NumCast(this.Document._yMargin), + }}> + {this.contents} + {!this._startIndex ? null : ( + <Tooltip title="scroll back"> + <div + style={{ position: 'absolute', bottom: 0, left: 0, background: SettingsManager.userVariantColor }} + onClick={action(() => { + this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown); + })}> + <Button + tooltip="Scroll back" + icon={<FontAwesomeIcon icon="chevron-left" size="lg" />} + onClick={action(() => { + this._startIndex = Math.max(0, this._startIndex - this.maxShown); + })} + color={SettingsManager.userColor} + /> + </div> + </Tooltip> + )} + {this._startIndex > this.childLayoutPairs.length - 1 || !this.maxShown ? null : ( + <Tooltip title="scroll forward"> + <div + style={{ position: 'absolute', bottom: 0, right: 0, background: SettingsManager.userVariantColor }} onClick={action(() => { - this._startIndex = Math.max(0, this._startIndex - this.maxShown); - })} - color={SettingsManager.userColor} - /> - </div> - </Tooltip> - )} - {this._startIndex > this.childLayoutPairs.length - 1 || !this.maxShown ? null : ( - <Tooltip title="scroll forward"> - <div - style={{ position: 'absolute', bottom: 0, right: 0, background: SettingsManager.userVariantColor }} - onClick={action(() => { - this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown); - })}> - <IconButton icon={<FaChevronRight />} color={SettingsManager.userColor} /> - </div> - </Tooltip> - )} + this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown); + })}> + <IconButton icon={<FaChevronRight />} color={SettingsManager.userColor} /> + </div> + </Tooltip> + )} + </div> </div> ); } diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss index f44eacb2a..0d49fabaa 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss @@ -1,34 +1,41 @@ -.collectionMultirowView_contents { - display: flex; - //overflow: hidden; // bcz: turned of to allow highlighting to appear when there is no border (e.g, for a component of the slide template) - width: 100%; +.collectionMultirowView_drop { height: 100%; - flex-direction: column; + top: 0; + left: 0; + position: absolute; - .document-wrapper { + .collectionMultirowView_contents { display: flex; - flex-direction: row; + //overflow: hidden; // bcz: turned of to allow highlighting to appear when there is no border (e.g, for a component of the slide template) + width: 100%; height: 100%; - align-items: center; + flex-direction: column; - .label-wrapper { + .document-wrapper { display: flex; flex-direction: row; - justify-content: center; - height: 20px; + height: 100%; + align-items: center; + + .label-wrapper { + display: flex; + flex-direction: row; + justify-content: center; + height: 20px; + } } - } - .multiRowResizer { - cursor: ns-resize; - transition: 0.5s opacity ease; - display: flex; - flex-direction: row; + .multiRowResizer { + cursor: ns-resize; + transition: 0.5s opacity ease; + display: flex; + flex-direction: row; - .multiRowResizer-hdl { - width: 100%; - height: 100%; - transition: 0.5s background-color ease; + .multiRowResizer-hdl { + width: 100%; + height: 100%; + transition: 0.5s background-color ease; + } } } } diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 3fe3d5343..bda8e91ac 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -6,9 +6,8 @@ import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types import { DragManager } from '../../../util/DragManager'; import { dropActionType } from '../../../util/DropActionTypes'; import { Transform } from '../../../util/Transform'; -import { undoBatch } from '../../../util/UndoManager'; import { DocumentView } from '../../nodes/DocumentView'; -import { CollectionSubView } from '../CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView'; import './CollectionMultirowView.scss'; import HeightLabel from './MultirowHeightLabel'; import ResizeBar from './MultirowResizer'; @@ -33,7 +32,7 @@ const resizerHeight = 8; @observer export class CollectionMultirowView extends CollectionSubView() { - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } @@ -193,15 +192,14 @@ export class CollectionMultirowView extends CollectionSubView() { return Transform.Identity(); // type coersion, this case should never be hit }; - @undoBatch onInternalDrop = (e: Event, de: DragManager.DropEvent) => { let dropInd = -1; - if (de.complete.docDragData && this._mainCont) { + if (de.complete.docDragData && this._contRef.current) { let curInd = -1; de.complete.docDragData?.droppedDocuments.forEach(d => { curInd = this.childDocs.indexOf(d); }); - Array.from(this._mainCont.children).forEach((child, index) => { + Array.from(this._contRef.current.children).forEach((child, index) => { const brect = child.getBoundingClientRect(); if (brect.y < de.y && brect.y + brect.height > de.y) { if (curInd !== -1 && curInd === Math.floor(index / 2)) { @@ -284,7 +282,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'} /> ); }; @@ -318,20 +316,23 @@ export class CollectionMultirowView extends CollectionSubView() { return collector; } + _contRef = React.createRef<HTMLDivElement>(); render() { return ( - <div - className="collectionMultirowView_contents" - style={{ - width: `calc(100% - ${2 * NumCast(this.Document._xMargin)}px)`, - height: `calc(100% - ${2 * NumCast(this.Document._yMargin)}px)`, - marginLeft: NumCast(this.Document._xMargin), - marginRight: NumCast(this.Document._xMargin), - marginTop: NumCast(this.Document._yMargin), - marginBottom: NumCast(this.Document._yMargin), - }} - ref={this.createDashEventsTarget}> - {this.contents} + <div className="collectionMultirowView_drop" ref={this.createDashEventsTarget}> + <div + ref={this._contRef} + className="collectionMultirowView_contents" + style={{ + width: `calc(100% - ${2 * NumCast(this.Document._xMargin)}px)`, + height: `calc(100% - ${2 * NumCast(this.Document._yMargin)}px)`, + marginLeft: NumCast(this.Document._xMargin), + marginRight: NumCast(this.Document._xMargin), + marginTop: NumCast(this.Document._yMargin), + marginBottom: NumCast(this.Document._yMargin), + }}> + {this.contents} + </div> </div> ); } 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<ResizerProps> { 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), }}> <div className="multiColumnResizer-hdl" onPointerDown={e => this.registerResizing(e)} /> </div> 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<ResizerProps> { 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, }}> <div className="multiRowResizer-hdl" onPointerDown={e => this.registerResizing(e)} /> </div> diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 7c2cfd15f..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,16 +22,17 @@ 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'; +// eslint-disable-next-line @typescript-eslint/no-var-requires const { SCHEMA_NEW_NODE_HEIGHT } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore export const FInfotoColType: { [key: string]: ColumnType } = { @@ -48,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<HTMLDivElement>(); - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } @@ -75,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; @@ -160,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<Doc>, - (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')))))); @@ -269,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); } @@ -280,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); } @@ -297,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; }); @@ -316,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); }; @@ -603,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); @@ -624,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; })} /> @@ -636,11 +637,9 @@ export class CollectionSchemaView extends CollectionSubView() { <> <input type="checkbox" - name="" - id="" - value={this._newFieldDefault} + value={this._newFieldDefault?.toString()} onPointerDown={e => e.stopPropagation()} - onChange={action((e: any) => { + onChange={action(e => { this._newFieldDefault = e.target.checked; })} /> @@ -653,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; })} /> @@ -682,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 { @@ -855,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 ( <div className="schema-key-search"> <div className="schema-column-menu-button" - onPointerDown={action((e: any) => { + onPointerDown={action(e => { e.stopPropagation(); this._makeNewField = true; })}> @@ -879,6 +878,7 @@ export class CollectionSchemaView extends CollectionSubView() { }}> {this._menuKeys.map(key => ( <div + key={key} className="schema-search-result" onPointerDown={e => { e.stopPropagation(); @@ -961,7 +961,7 @@ export class CollectionSchemaView extends CollectionSubView() { {this.renderFilterOptions} <div className="schema-column-menu-button" - onPointerDown={action((e: any) => { + onPointerDown={action(e => { e.stopPropagation(); this.closeFilterMenu(); })}> @@ -1012,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 ( <div className="collectionSchemaView" ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} onDrop={this.onExternalDrop.bind(this)} onPointerMove={e => this.onPointerMove(e)}> @@ -1111,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} @@ -1136,7 +1136,7 @@ interface CollectionSchemaViewDocProps { @observer class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaViewDocProps> { - 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<SchemaRowBoxProps>() { 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<SchemaTableCellPro ref={r => 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<SchemaTableCellPro // mj: most of this is adapted from old schema code so I'm not sure what it does tbh @observer export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellProps> { - constructor(props: any) { + constructor(props: SchemaTableCellProps) { super(props); makeObservable(this); } @@ -276,7 +275,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro @observer export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProps> { - constructor(props: any) { + constructor(props: SchemaTableCellProps) { super(props); makeObservable(this); } @@ -324,7 +323,7 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp } @observer export class SchemaRTFCell extends ObservableReactComponent<SchemaTableCellProps> { - constructor(props: any) { + constructor(props: SchemaTableCellProps) { super(props); makeObservable(this); } @@ -343,7 +342,7 @@ export class SchemaRTFCell extends ObservableReactComponent<SchemaTableCellProps } @observer export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProps> { - constructor(props: any) { + constructor(props: SchemaTableCellProps) { super(props); makeObservable(this); } @@ -356,18 +355,19 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp style={{ marginRight: 4 }} type="checkbox" checked={BoolCast(this._props.Document[this._props.fieldKey])} - onChange={undoBatch((value: React.ChangeEvent<HTMLInputElement> | undefined) => { - if ((value?.nativeEvent as any).shiftKey) { + onChange={undoable((value: React.ChangeEvent<HTMLInputElement> | 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')} /> + <EditableView - contents={undefined} + contents="" fieldContents={fieldProps} editing={selectedCell(this._props) ? undefined : false} GetValue={() => 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<SchemaTableCellProp const set = Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined); this._props.finishEdit?.(); return set; - })} + }, 'set bool cell')} /> </div> ); @@ -384,7 +384,7 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp } @observer export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableCellProps> { - constructor(props: any) { + constructor(props: SchemaTableCellProps) { super(props); makeObservable(this); } |
