diff options
Diffstat (limited to 'src/client/views/collections')
15 files changed, 750 insertions, 559 deletions
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 2d906dfe7..8803f6f79 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -1,10 +1,13 @@ +/* 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'; import * as React from 'react'; import { returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction, numberRange } from '../../../Utils'; -import { Doc, DocListCast } from '../../../fields/Doc'; +import { Doc } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { ScriptField } from '../../../fields/ScriptField'; @@ -52,13 +55,19 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF @observable private collapsed: boolean = false; @observable private _paletteOn = false; private set _heading(value: string) { - runInAction(() => this._props.headingObject && (this._props.headingObject.heading = this.heading = value)); + runInAction(() => { + this._props.headingObject && (this._props.headingObject.heading = this.heading = value); + }); } private set _color(value: string) { - runInAction(() => this._props.headingObject && (this._props.headingObject.color = this.color = value)); + runInAction(() => { + this._props.headingObject && (this._props.headingObject.color = this.color = value); + }); } private set _collapsed(value: boolean) { - runInAction(() => this._props.headingObject && (this._props.headingObject.collapsed = this.collapsed = value)); + runInAction(() => { + this._props.headingObject && (this._props.headingObject.collapsed = this.collapsed = value); + }); } private _dropDisposer?: DragManager.DragDropDisposer; @@ -88,7 +97,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF if (this.collapsed) { this._props.setDocHeight(this.heading, 20); } else { - const rawHeight = this._contRef.current!.getBoundingClientRect().height + 15; //+ 15 accounts for the group header + const rawHeight = this._contRef.current!.getBoundingClientRect().height + 15; // +15 accounts for the group header const transformScale = this._props.screenToLocalTransform().Scale; const trueHeight = rawHeight * transformScale; this._props.setDocHeight(this.heading, trueHeight); @@ -102,7 +111,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF const key = this._props.pivotField; const castedValue = this.getValue(this.heading); if (this._props.parent.onInternalDrop(e, de)) { - key && de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !this.onLayoutDoc(key))); + key && de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, true)); } return true; } @@ -118,7 +127,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF }; @action - headingChanged = (value: string, shiftDown?: boolean) => { + headingChanged = (value: string /* , shiftDown?: boolean */) => { this._createEmbeddingSelected = false; const key = this._props.pivotField; const castedValue = this.getValue(value); @@ -141,7 +150,9 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF this._color = color; }; - pointerEnteredRow = action(() => SnappingManager.IsDragging && (this._background = '#b4b4b4')); + pointerEnteredRow = action(() => { + SnappingManager.IsDragging && (this._background = '#b4b4b4'); + }); @action pointerLeaveRow = () => { @@ -153,13 +164,13 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF addDocument = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => { if (!value && !forceEmptyNote) return false; this._createEmbeddingSelected = false; - const key = this._props.pivotField; + const { pivotField } = this._props; const newDoc = Docs.Create.TextDocument('', { _layout_autoHeight: true, _width: 200, _layout_fitWidth: true, title: value }); FormattedTextBox.SetSelectOnLoad(newDoc); FormattedTextBox.SelectOnLoadChar = value; - key && ((this.onLayoutDoc(key) ? newDoc : newDoc[DocData])[key] = this.getValue(this._props.heading)); + pivotField && (newDoc[DocData][pivotField] = this.getValue(this._props.heading)); const docs = this._props.parent.childDocList; - return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list) + 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( @@ -198,21 +209,11 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF @action headerDown = (e: React.PointerEvent<HTMLDivElement>) => { if (e.button === 0 && !e.ctrlKey) { - setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => !this._props.chromeHidden && this.collapseSection(e)); + setupMoveUpEvents(this, e, this.headerMove, emptyFunction, clickEv => !this._props.chromeHidden && this.collapseSection(clickEv)); this._createEmbeddingSelected = false; } }; - /** - * Returns true if a key is on the layout doc of the documents in the collection. - */ - onLayoutDoc = (key: string): boolean => { - DocListCast(this._props.parent.Document.data).forEach(doc => { - if (Doc.Get(doc, key, true)) return true; - }); - return false; - }; - renderColorPicker = () => { const selected = this.color; @@ -229,27 +230,29 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF return ( <div className="collectionStackingView-colorPicker"> <div className="colorOptions"> - <div className={'colorPicker' + (selected === pink ? ' active' : '')} style={{ backgroundColor: pink }} onClick={() => this.changeColumnColor(pink!)}></div> - <div className={'colorPicker' + (selected === purple ? ' active' : '')} style={{ backgroundColor: purple }} onClick={() => this.changeColumnColor(purple!)}></div> - <div className={'colorPicker' + (selected === blue ? ' active' : '')} style={{ backgroundColor: blue }} onClick={() => this.changeColumnColor(blue!)}></div> - <div className={'colorPicker' + (selected === yellow ? ' active' : '')} style={{ backgroundColor: yellow }} onClick={() => this.changeColumnColor(yellow!)}></div> - <div className={'colorPicker' + (selected === red ? ' active' : '')} style={{ backgroundColor: red }} onClick={() => this.changeColumnColor(red!)}></div> - <div className={'colorPicker' + (selected === gray ? ' active' : '')} style={{ backgroundColor: gray }} onClick={() => this.changeColumnColor(gray)}></div> - <div className={'colorPicker' + (selected === green ? ' active' : '')} style={{ backgroundColor: green }} onClick={() => this.changeColumnColor(green!)}></div> - <div className={'colorPicker' + (selected === cyan ? ' active' : '')} style={{ backgroundColor: cyan }} onClick={() => this.changeColumnColor(cyan!)}></div> - <div className={'colorPicker' + (selected === orange ? ' active' : '')} style={{ backgroundColor: orange }} onClick={() => this.changeColumnColor(orange!)}></div> + <div className={'colorPicker' + (selected === pink ? ' active' : '')} style={{ backgroundColor: pink }} onClick={() => this.changeColumnColor(pink!)} /> + <div className={'colorPicker' + (selected === purple ? ' active' : '')} style={{ backgroundColor: purple }} onClick={() => this.changeColumnColor(purple!)} /> + <div className={'colorPicker' + (selected === blue ? ' active' : '')} style={{ backgroundColor: blue }} onClick={() => this.changeColumnColor(blue!)} /> + <div className={'colorPicker' + (selected === yellow ? ' active' : '')} style={{ backgroundColor: yellow }} onClick={() => this.changeColumnColor(yellow!)} /> + <div className={'colorPicker' + (selected === red ? ' active' : '')} style={{ backgroundColor: red }} onClick={() => this.changeColumnColor(red!)} /> + <div className={'colorPicker' + (selected === gray ? ' active' : '')} style={{ backgroundColor: gray }} onClick={() => this.changeColumnColor(gray)} /> + <div className={'colorPicker' + (selected === green ? ' active' : '')} style={{ backgroundColor: green }} onClick={() => this.changeColumnColor(green!)} /> + <div className={'colorPicker' + (selected === cyan ? ' active' : '')} style={{ backgroundColor: cyan }} onClick={() => this.changeColumnColor(cyan!)} /> + <div className={'colorPicker' + (selected === orange ? ' active' : '')} style={{ backgroundColor: orange }} onClick={() => this.changeColumnColor(orange!)} /> </div> </div> ); }; - toggleEmbedding = action(() => (this._createEmbeddingSelected = true)); - toggleVisibility = () => (this._collapsed = !this.collapsed); + toggleEmbedding = action(() => { + this._createEmbeddingSelected = true; + }); + toggleVisibility = () => { + this._collapsed = !this.collapsed; + }; @action - textCallback = (char: string) => { - return this.addDocument('', false); - }; + textCallback = (/* char: string */) => this.addDocument('', false); @computed get contentLayout() { const rows = Math.max(1, Math.min(this._props.docList.length, Math.floor((this._props.parent._props.PanelWidth() - 2 * this._props.parent.xMargin) / (this._props.parent.columnWidth + this._props.parent.gridGap)))); @@ -263,22 +266,22 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF className="collectionStackingView-addDocumentButton" style={ { - //width: style.columnWidth / style.numGroupColumns, - //padding: `${NumCast(this._props.parent.layoutDoc._yPadding, this._props.parent.yMargin)}px 0px 0px 0px`, + // width: style.columnWidth / style.numGroupColumns, + // padding: `${NumCast(this._props.parent.layoutDoc._yPadding, this._props.parent.yMargin)}px 0px 0px 0px`, } }> - <EditableView GetValue={returnEmptyString} SetValue={this.addDocument} textCallback={this.textCallback} contents={'+ NEW'} /> + <EditableView GetValue={returnEmptyString} SetValue={this.addDocument} textCallback={this.textCallback} contents="+ NEW" /> </div> ) : null} <div - className={`collectionStackingView-masonryGrid`} + className="collectionStackingView-masonryGrid" ref={this._contRef} style={{ padding: stackPad, minHeight: this._props.showHandle && this._props.parent._props.isContentActive() ? '10px' : undefined, width: this._props.parent.NodeWidth, gridGap: this._props.parent.gridGap, - gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this._props.parent.columnWidth}px`, ''), + gridTemplateColumns: numberRange(rows).reduce(list => list + ` ${this._props.parent.columnWidth}px`, ''), }}> {this._props.parent.children(this._props.docList)} </div> @@ -290,7 +293,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF const noChrome = this._props.chromeHidden; const key = this._props.pivotField; const evContents = this.heading ? this.heading : this._props.type && this._props.type === 'number' ? '0' : `NO ${key.toUpperCase()} VALUE`; - const editableHeaderView = <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} />; + const editableHeaderView = <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine />; return this._props.Document.miniHeaders ? ( <div className="collectionStackingView-miniHeader">{editableHeaderView}</div> ) : !this._props.headingObject ? null : ( @@ -304,6 +307,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF {noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? null : ( <div className="collectionStackingView-sectionColor"> <button + type="button" className="collectionStackingView-sectionColorButton" onPointerDown={e => setupMoveUpEvents( @@ -311,7 +315,9 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF e, returnFalse, emptyFunction, - action(e => (this._paletteOn = !this._paletteOn)) + action(() => { + this._paletteOn = !this._paletteOn; + }) ) }> <FontAwesomeIcon icon="palette" size="lg" /> @@ -320,13 +326,13 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF </div> )} {noChrome ? null : ( - <button className="collectionStackingView-sectionDelete" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, noChrome ? emptyFunction : this.collapseSection)}> + <button type="button" className="collectionStackingView-sectionDelete" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, noChrome ? emptyFunction : this.collapseSection)}> <FontAwesomeIcon icon={this.collapsed ? 'chevron-down' : 'chevron-up'} size="lg" /> </button> )} {noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? null : ( <div className="collectionStackingView-sectionOptions" onPointerDown={e => e.stopPropagation()}> - <button className="collectionStackingView-sectionOptionButton" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.deleteRow)}> + <button type="button" className="collectionStackingView-sectionOptionButton" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.deleteRow)}> <FontAwesomeIcon icon="trash" size="lg" /> </button> </div> diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index a23725348..5a509128d 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -36,6 +36,7 @@ interface CollectionMenuProps { @observer export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { + // eslint-disable-next-line no-use-before-define @observable static Instance: CollectionMenu; @observable SelectedCollection: DocumentView | undefined = undefined; @@ -63,7 +64,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { } @action - toggleMenuPin = (e: React.MouseEvent) => { + toggleMenuPin = () => { Doc.UserDoc()['menuCollections-pinned'] = this.Pinned = !this.Pinned; if (!this.Pinned && this._left < 0) { this.jumpTo(300, 300); @@ -165,7 +166,9 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { } interface CollectionViewMenuProps { + // eslint-disable-next-line react/no-unused-prop-types type: CollectionViewType; + // eslint-disable-next-line react/no-unused-prop-types fieldKey: string; docView: DocumentView; } @@ -377,16 +380,18 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu setupMoveUpEvents( this, e, - (e, down, delta) => { + moveEv => { const vtype = this.props.type; const c = { params: ['target'], title: vtype, script: `this.target._type_collection = '${StrCast(this.props.type)}'`, - immediate: (source: Doc[]) => (this.document._type_collection = Doc.getDocTemplate(source?.[0])), + immediate: (source: Doc[]) => { + this.document._type_collection = Doc.getDocTemplate(source?.[0]); + }, initialize: emptyFunction, }; - DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title), { target: this.document }, c.params, c.initialize, e.clientX, e.clientY); + DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title), { target: this.document }, c.params, c.initialize, moveEv.clientX, moveEv.clientY); return true; }, emptyFunction, @@ -397,8 +402,10 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu setupMoveUpEvents( this, e, - (e, down, delta) => { - this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title, { target: this.document }, c.params, c.initialize, e.clientX, e.clientY)); + moveEv => { + this._buttonizableCommands + ?.filter(c => c.title === this._currentKey) + .map(c => DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title, { target: this.document }, c.params, c.initialize, moveEv.clientX, moveEv.clientY)); return true; }, emptyFunction, diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 4ceeb631d..2ba6f5bf4 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -59,7 +59,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { } @computed get chromeHidden() { - return BoolCast(this.layoutDoc.chromeHidden) || this._props.onBrowseClickScript?.() ? true : false; + return !!(BoolCast(this.layoutDoc.chromeHidden) || this._props.onBrowseClickScript?.()); } // columnHeaders returns the list of SchemaHeaderFields currently being used by the layout doc to render the columns @computed get colHeaderData() { @@ -68,7 +68,6 @@ export class CollectionNoteTakingView extends CollectionSubView() { if (needsUnsetCategory || colHeaderData === undefined || colHeaderData.length === 0) { setTimeout(() => { const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null) ?? []); - const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders?.find(sh => sh.heading === 'unset')); if (needsUnsetCategory || columnHeaders.length === 0) { columnHeaders.push(new SchemaHeaderField('unset', undefined, undefined, 1)); this.resizeColumns(columnHeaders); @@ -112,12 +111,12 @@ export class CollectionNoteTakingView extends CollectionSubView() { // to render the docs you see within an individual column. children = (docs: Doc[]) => { TraceMobx(); - return docs.map((d, i) => { + return docs.map(d => { const height = () => this.getDocHeight(d); const width = () => this.getDocWidth(d); const style = { width: width(), marginTop: this.gridGap, height: height() }; return ( - <div className={`collectionNoteTakingView-columnDoc`} key={d[Id]} style={style}> + <div className="collectionNoteTakingView-columnDoc" key={d[Id]} style={style}> {this.getDisplayDoc(d, width)} </div> ); @@ -136,7 +135,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { const sections = new Map<SchemaHeaderField, Doc[]>(columnHeaders.map(sh => [sh, []] as [SchemaHeaderField, []])); const rowCol = this.docsDraggedRowCol; // this will sort the docs into the correct columns (minus the ones you're currently dragging) - docs.map(d => { + docs.forEach(d => { const sectionValue = (d[this.notetakingCategoryField] as object) ?? `unset`; // look for if header exists already const existingHeader = columnHeaders.find(sh => sh.heading === sectionValue.toString()); @@ -154,7 +153,9 @@ export class CollectionNoteTakingView extends CollectionSubView() { removeDocDragHighlight = () => { setTimeout( - action(() => (this.docsDraggedRowCol.length = 0)), + action(() => { + this.docsDraggedRowCol.length = 0; + }), 100 ); }; @@ -192,13 +193,11 @@ export class CollectionNoteTakingView extends CollectionSubView() { Object.keys(this._disposers).forEach(key => this._disposers[key]()); } - moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => { - return this._props.removeDocument?.(doc) && addDocument?.(doc) ? true : false; - }; + moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean) => !!(this._props.removeDocument?.(doc) && addDocument?.(doc)); createRef = (ele: HTMLDivElement | null) => { this._masonryGridRef = ele; - this.createDashEventsTarget(ele!); //so the whole grid is the drop target? + this.createDashEventsTarget(ele!); }; @computed get onChildClickHandler() { @@ -218,14 +217,15 @@ export class CollectionNoteTakingView extends CollectionSubView() { Doc.BrushDoc(doc); const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); if (found) { - const top = found.getBoundingClientRect().top; + const { top } = found.getBoundingClientRect(); const localTop = this.ScreenToLocalBoxXf().transformPoint(0, top); if (Math.floor(localTop[1]) !== 0 && Math.ceil(this._props.PanelHeight()) < (this._mainCont?.scrollHeight || 0)) { - let focusSpeed = options.zoomTime ?? 500; + const focusSpeed = options.zoomTime ?? 500; smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); return focusSpeed; } } + return undefined; }; styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => { @@ -240,6 +240,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { return this._props.childOpacity(); } break; + default: } return this._props.styleProvider?.(doc, props, property); }; @@ -255,7 +256,9 @@ export class CollectionNoteTakingView extends CollectionSubView() { const noteTakingDocTransform = () => this.getDocTransform(doc, dref); return ( <DocumentView - ref={r => (dref = r || undefined)} + ref={r => { + dref = r || undefined; + }} Document={doc} TemplateDataDocument={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)} pointerEvents={this.blockPointerEventsWhenDragging} @@ -267,8 +270,8 @@ export class CollectionNoteTakingView extends CollectionSubView() { layout_fitWidth={this._props.childLayoutFitWidth} isContentActive={emptyFunction} onKey={this.onKeyDown} - //TODO: change this from a prop to a parameter passed into a function - dontHideOnDrag={true} + // TODO: change this from a prop to a parameter passed into a function + dontHideOnDrag isDocumentActive={this.isContentActive} LayoutTemplate={this._props.childLayoutTemplate} LayoutTemplateString={this._props.childLayoutString} @@ -302,7 +305,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { // getDocTransform is used to get the coordinates of a document when we go from a view like freeform to columns getDocTransform(doc: Doc, dref?: DocumentView) { - const y = this._scroll; // required for document decorations to update when the text box container is scrolled + this._scroll; // required for document decorations to update when the text box container is scrolled const { translateX, translateY } = ClientUtils.GetScreenTransform(dref?.ContentDiv || undefined); // 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); @@ -347,7 +350,6 @@ export class CollectionNoteTakingView extends CollectionSubView() { // Adding example: column widths are [0.6, 0.4] --> user adds column at end --> column widths are [0.4, 0.267, 0.33] @action resizeColumns = (headers: SchemaHeaderField[]) => { - const n = headers.length; const curWidths = headers.reduce((sum, hdr) => sum + Math.abs(hdr.width), 0); const scaleFactor = 1 / curWidths; this.dataDoc[this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>( @@ -385,7 +387,11 @@ export class CollectionNoteTakingView extends CollectionSubView() { // we alter the pivot fields of the docs in case they are moved to a new column. const colIndex = this.getColumnFromXCoord(xCoord); const colHeader = colIndex === undefined ? 'unset' : StrCast(this.colHeaderData[colIndex].heading); - DragManager.docsBeingDragged.map(doc => doc[DocData]).forEach(d => (d[this.notetakingCategoryField] = colHeader)); + DragManager.docsBeingDragged + .map(doc => doc[DocData]) + .forEach(d => { + d[this.notetakingCategoryField] = colHeader; + }); // used to notify sections to re-render this.docsDraggedRowCol.length = 0; const columnFromCoord = this.getColumnFromXCoord(xCoord); @@ -396,7 +402,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { // getColumnFromXCoord returns the column index for a given x-coordinate (currently always the client's mouse coordinate). // This function is used to know which document a column SHOULD be in while it is being dragged. getColumnFromXCoord = (xCoord: number): number | undefined => { - let colIndex: number | undefined = undefined; + let colIndex: number | undefined; const numColumns = this.colHeaderData.length; const coords = []; let colStartXCoord = 0; @@ -419,10 +425,10 @@ export class CollectionNoteTakingView extends CollectionSubView() { const docsMatchingHeader: Doc[] = []; const colIndex = this.getColumnFromXCoord(xCoord); const colHeader = colIndex === undefined ? 'unset' : StrCast(this.colHeaderData[colIndex].heading); - this.childDocs?.map(d => { + this.childDocs?.forEach(d => { if (d instanceof Promise) return; const sectionValue = (d[this.notetakingCategoryField] as object) ?? 'unset'; - if (sectionValue.toString() == colHeader) { + if (sectionValue.toString() === colHeader) { docsMatchingHeader.push(d); } }); @@ -438,6 +444,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { FormattedTextBox.SetSelectOnLoad(newDoc); return this.addDocument?.(newDoc); } + return undefined; }; // onInternalDrop is used when dragging and dropping a document within the view, such as dragging @@ -575,6 +582,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { alert('You cannot use an existing column name. Please try a new column name'); return value; } + return undefined; }); const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null)); const newColWidth = 1 / (this.numGroupColumns + 1); @@ -593,17 +601,33 @@ export class CollectionNoteTakingView extends CollectionSubView() { const subItems: ContextMenuProps[] = []; subItems.push({ description: `${this.layoutDoc._notetaking_columns_autoCreate ? 'Manually' : 'Automatically'} Create columns`, - event: () => (this.layoutDoc._notetaking_columns_autoCreate = !this.layoutDoc._notetaking_columns_autoCreate), + event: () => { + this.layoutDoc._notetaking_columns_autoCreate = !this.layoutDoc._notetaking_columns_autoCreate; + }, icon: 'computer', }); subItems.push({ description: 'Remove Empty Columns', event: this.removeEmptyColumns, icon: 'computer' }); subItems.push({ description: `${this.layoutDoc._notetaking_columns_autoSize ? 'Variable Size' : 'Autosize'} Columns`, - event: () => (this.layoutDoc._notetaking_columns_autoSize = !this.layoutDoc._notetaking_columns_autoSize), + event: () => { + this.layoutDoc._notetaking_columns_autoSize = !this.layoutDoc._notetaking_columns_autoSize; + }, icon: 'plus', }); - subItems.push({ description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight), icon: 'plus' }); - subItems.push({ description: 'Clear All', event: () => (this.dataDoc.data = new List([])), icon: 'times' }); + subItems.push({ + description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, + event: () => { + this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight; + }, + icon: 'plus', + }); + subItems.push({ + description: 'Clear All', + event: () => { + this.dataDoc.data = new List([]); + }, + icon: 'times', + }); ContextMenu.Instance.addItem({ description: 'Options...', subitems: subItems, icon: 'eye' }); } }; @@ -628,6 +652,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { const sections = Array.from(this.Sections.entries()); return sections.reduce((list, sec, i) => { list.push(this.sectionNoteTaking(sec[0], sec[1])); + // eslint-disable-next-line react/no-array-index-key i !== sections.length - 1 && list.push(<CollectionNoteTakingViewDivider key={`divider${i}`} isContentActive={this.isContentActive} index={i} setColumnStartXCoords={this.setColumnStartXCoords} xMargin={this.xMargin} />); return list; }, [] as JSX.Element[]); @@ -661,14 +686,18 @@ export class CollectionNoteTakingView extends CollectionSubView() { background: this.backgroundColor(), pointerEvents: this.backgroundEvents, }} - onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))} - onPointerLeave={action(e => (this.docsDraggedRowCol.length = 0))} + onScroll={action(e => { + this._scroll = e.currentTarget.scrollTop; + })} + onPointerLeave={action(() => { + this.docsDraggedRowCol.length = 0; + })} onPointerMove={e => e.buttons && this.onPointerMove(false, e.clientX, e.clientY)} onDragOver={e => this.onPointerMove(true, e.clientX, e.clientY)} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu} onWheel={e => this._props.isContentActive() && e.stopPropagation()}> - <>{this.renderedSections}</> + {this.renderedSections} <div className="collectionNotetaking-pivotField" style={{ right: 0, top: 0, position: 'absolute' }}> <FieldsDropdown Document={this.Document} diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index e39f1c700..6dfc38e2e 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -1,3 +1,5 @@ +/* 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'; @@ -67,6 +69,7 @@ export class CollectionPileView extends CollectionSubView() { return ( <div className="collectionPileView-innards" style={{ pointerEvents: this.contentEvents }}> <CollectionFreeFormView + // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} // layoutEngine={this.layoutEngine} addDocument={this.addPileDoc} @@ -117,16 +120,16 @@ export class CollectionPileView extends CollectionSubView() { setupMoveUpEvents( this, e, - (e: PointerEvent, down: number[], delta: number[]) => { - if (this.layoutEngine() === 'pass' && this.childDocs.length && e.shiftKey) { + (moveEv: PointerEvent, down: number[], delta: number[]) => { + if (this.layoutEngine() === 'pass' && this.childDocs.length && moveEv.shiftKey) { dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]); if (dist > 100) { if (!this._undoBatch) { this._undoBatch = UndoManager.StartBatch('layout pile'); } const doc = this.childDocs[0]; - doc.x = e.clientX; - doc.y = e.clientY; + doc.x = moveEv.clientX; + doc.y = moveEv.clientY; this._props.addDocTab(doc, OpenWhere.inParentFromScreen) && (this._props.removeDocument?.(doc) || false); dist = 0; } @@ -155,7 +158,7 @@ export class CollectionPileView extends CollectionSubView() { render() { return ( - <div className={`collectionPileView`} onClick={this.onClick} onPointerDown={this.pointerDown} style={{ width: this._props.PanelWidth(), height: '100%' }}> + <div className="collectionPileView" onClick={this.onClick} onPointerDown={this.pointerDown} style={{ width: this._props.PanelWidth(), height: '100%' }}> {this.contents} </div> ); diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 3f6638b25..7adf44a5c 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -1,3 +1,8 @@ +/* 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'; @@ -22,7 +27,7 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; -import { CollectionSubView } from '../collections/CollectionSubView'; +import { CollectionSubView } from './CollectionSubView'; import { LightboxView } from '../LightboxView'; import { AudioWaveform } from '../nodes/audio/AudioWaveform'; import { DocumentView, OpenWhere } from '../nodes/DocumentView'; @@ -58,9 +63,14 @@ export enum TrimScope { @observer export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() { + // eslint-disable-next-line no-use-before-define public static SelectingRegions: Set<CollectionStackedTimeline> = new Set(); public static StopSelecting() { - this.SelectingRegions.forEach(action(region => (region._selectingRegion = false))); + this.SelectingRegions.forEach( + action(region => { + region._selectingRegion = false; + }) + ); this.SelectingRegions.clear(); } constructor(props: any) { @@ -131,7 +141,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack componentWillUnmount() { document.removeEventListener('keydown', this.keyEvents, true); if (this._selectingRegion) { - runInAction(() => (this._selectingRegion = false)); + runInAction(() => { + this._selectingRegion = false; + }); CollectionStackedTimeline.SelectingRegions.delete(this); } } @@ -175,9 +187,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this._props.endTag], val) ?? null); // converts screen pixel offset to time - toTimeline = (screen_delta: number, width: number) => { - return Math.max(this.clipStart, Math.min(this.clipEnd, (screen_delta / width) * this.clipDuration + this.clipStart)); - }; + // prettier-ignore + toTimeline = (screenDelta: number, width: number) => // + Math.max(this.clipStart, Math.min(this.clipEnd, (screenDelta / width) * this.clipDuration + this.clipStart)); @computed get rangeClick() { // prettier-ignore @@ -235,6 +247,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this._props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd)); e.stopPropagation(); break; + default: } } }; @@ -254,17 +267,15 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack @action onPointerDownTimeline = (e: React.PointerEvent): void => { const rect = this._timeline?.getBoundingClientRect(); - const clientX = e.clientX; - const diff = rect ? clientX - rect?.x : null; - const shiftKey = e.shiftKey; + const { clientX, shiftKey } = e; if (rect && this._props.isContentActive()) { const wasPlaying = this._props.playing(); if (wasPlaying) this._props.Pause(); - var wasSelecting = this._markerEnd !== undefined; + let wasSelecting = this._markerEnd !== undefined; setupMoveUpEvents( this, e, - action(e => { + action(() => { if (!wasSelecting) { this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width); wasSelecting = true; @@ -273,8 +284,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); return false; }), - action((e, movement, isClick) => { - this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); + action((upEvent, movement, isClick) => { + this._markerEnd = this.toTimeline(upEvent.clientX - rect.x, rect.width); if (this._markerEnd < this._markerStart) { const tmp = this._markerStart; this._markerStart = this._markerEnd; @@ -287,8 +298,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack (!isClick || !wasSelecting) && (this._markerEnd = undefined); this._timelineWrapper && (this._timelineWrapper.style.cursor = ''); }), - (e, doubleTap) => { - if (e.button !== 2) { + (clickEv, doubleTap) => { + if (clickEv.button !== 2) { this._props.select(false); !wasPlaying && doubleTap && this._props.Play(); } @@ -310,7 +321,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack onHover = (e: React.MouseEvent): void => { e.stopPropagation(); const rect = this._timeline?.getBoundingClientRect(); - const clientX = e.clientX; + const { clientX } = e; if (rect) { this._hoverTime = this.toTimeline(clientX - rect.x, rect.width); if (this.thumbnails) { @@ -328,14 +339,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack setupMoveUpEvents( this, e, - action((e, [], []) => { + action(moveEv => { if (rect && this._props.isContentActive()) { - this._trimStart = Math.min(Math.max(this.trimStart + (e.movementX / rect.width) * this.clipDuration, this.clipStart), this.trimEnd - this.minTrimLength); + this._trimStart = Math.min(Math.max(this.trimStart + (moveEv.movementX / rect.width) * this.clipDuration, this.clipStart), this.trimEnd - this.minTrimLength); } return false; }), emptyFunction, - action((e, doubleTap) => { + action((clickEv, doubleTap) => { doubleTap && (this._trimStart = this.clipStart); }) ); @@ -348,14 +359,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack setupMoveUpEvents( this, e, - action((e, [], []) => { + action(moveEv => { if (rect && this._props.isContentActive()) { - this._trimEnd = Math.max(Math.min(this.trimEnd + (e.movementX / rect.width) * this.clipDuration, this.clipEnd), this.trimStart + this.minTrimLength); + this._trimEnd = Math.max(Math.min(this.trimEnd + (moveEv.movementX / rect.width) * this.clipDuration, this.clipEnd), this.trimStart + this.minTrimLength); } return false; }), emptyFunction, - action((e, doubleTap) => { + action((clickEv, doubleTap) => { doubleTap && (this._trimEnd = this.clipEnd); }) ); @@ -384,7 +395,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack // handles dragging and dropping markers in timeline @action - internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number) { + internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData) { if (super.onInternalDrop(e, de)) { // determine x coordinate of drop and assign it to the documents being dragged --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view const localPt = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y); @@ -404,7 +415,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack } onInternalDrop = (e: Event, de: DragManager.DropEvent) => { - if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, 0); + if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData); return false; }; @@ -442,7 +453,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack } @action - playOnClick = (anchorDoc: Doc, clientX: number) => { + playOnClick = (anchorDoc: Doc /* , clientX: number */) => { const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05; const endTime = this.anchorEnd(anchorDoc); if (this.layoutDoc.autoPlayAnchors) { @@ -451,17 +462,15 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this._props.playFrom(seekTimeInSeconds, endTime); this.scrollToTime(seekTimeInSeconds); } - } else { - if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) && endTime > NumCast(this.layoutDoc._layout_currentTimecode)) { - if (!this.layoutDoc.autoPlayAnchors && this._props.playing()) { - this._props.Pause(); - } else { - this._props.Play(); - } + } else if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) && endTime > NumCast(this.layoutDoc._layout_currentTimecode)) { + if (!this.layoutDoc.autoPlayAnchors && this._props.playing()) { + this._props.Pause(); } else { - this._props.playFrom(seekTimeInSeconds, endTime); - this.scrollToTime(seekTimeInSeconds); + this._props.Play(); } + } else { + this._props.playFrom(seekTimeInSeconds, endTime); + this.scrollToTime(seekTimeInSeconds); } return { select: true }; }; @@ -480,19 +489,17 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack const rect = this._timeline?.getBoundingClientRect(); rect && this._props.setTime(this.toTimeline(clientX - rect.x, rect.width)); } + } else if (this.layoutDoc.autoPlayAnchors) { + this._props.playFrom(seekTimeInSeconds, endTime); } else { - if (this.layoutDoc.autoPlayAnchors) { - this._props.playFrom(seekTimeInSeconds, endTime); - } else { - this._props.setTime(seekTimeInSeconds); - } + this._props.setTime(seekTimeInSeconds); } return { select: true }; }; // makes sure no anchors overlaps each other by setting the correct position and width getLevel = (m: Doc, placed: { anchorStartTime: number; anchorEndTime: number; level: number }[]) => { - const timelineContentWidth = this.timelineContentWidth; + const { timelineContentWidth } = this; const x1 = this.anchorStart(m); const x2 = this.anchorEnd(m, x1 + (10 / timelineContentWidth) * this.clipDuration); let max = 0; @@ -504,6 +511,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack max = Math.max(max, p.level); return p.level; } + return undefined; }) ); let level = max + 1; @@ -565,10 +573,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack onWheel={e => this.isContentActive() && e.stopPropagation()} onScroll={this.setScroll} onMouseMove={e => this.isContentActive() && this.onHover(e)} - ref={wrapper => (this._timelineWrapper = wrapper)}> + ref={wrapper => { + this._timelineWrapper = wrapper; + }}> <div className="collectionStackedTimeline" - ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)} + ref={(timeline: HTMLDivElement | null) => { + this._timeline = timeline; + }} onClick={e => this.isContentActive() && StopEvent(e)} onPointerDown={e => this.isContentActive() && this.onPointerDownTimeline(e)} style={{ width: this.timelineContentWidth }}> @@ -583,7 +595,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack const height = this._props.PanelHeight() / maxLevel; return this.Document.hideAnchors ? null : ( <div - className={'collectionStackedTimeline-marker-timeline'} + className="collectionStackedTimeline-marker-timeline" key={d.anchor[Id]} style={{ left, @@ -593,6 +605,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack pointerEvents: 'none', }}> <StackedTimelineAnchor + // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} mark={d.anchor} containerViewPath={this._props.containerViewPath} @@ -647,7 +660,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack {this.IsTrimming !== TrimScope.None && ( <> - <div className="collectionStackedTimeline-trim-shade" style={{ width: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%` }}></div> + <div className="collectionStackedTimeline-trim-shade" style={{ width: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%` }} /> <div className="collectionStackedTimeline-trim-controls" @@ -655,8 +668,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack left: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%`, width: `${((this.trimEnd - this.trimStart) / this.clipDuration) * 100}%`, }}> - <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimLeft}></div> - <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimRight}></div> + <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimLeft} /> + <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimRight} /> </div> <div @@ -664,7 +677,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack style={{ left: `${((this.trimEnd - this.clipStart) / this.clipDuration) * 100}%`, width: `${((this.clipEnd - this.trimEnd) / this.clipDuration) * 100}%`, - }}></div> + }} + /> </> )} </div> @@ -686,7 +700,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack interface StackedTimelineAnchorProps { mark: Doc; whenChildContentsActiveChanged: (isActive: boolean) => void; - addDocTab: (doc: Doc, where: OpenWhere) => boolean; + addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean; rangeClickScript: () => ScriptField; rangePlayScript: () => ScriptField; left: number; @@ -736,13 +750,13 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch this._disposer = reaction( () => this._props.currentTimecode(), time => { - const dictationDoc = Cast(this._props.layoutDoc.data_dictation, Doc, null); - const isDictation = dictationDoc && LinkManager.Links(this._props.mark).some(link => Cast(link.link_anchor_1, Doc, null)?.annotationOn === dictationDoc); + // const dictationDoc = Cast(this._props.layoutDoc.data_dictation, Doc, null); + // const isDictation = dictationDoc && LinkManager.Links(this._props.mark).some(link => Cast(link.link_anchor_1, Doc, null)?.annotationOn === dictationDoc); if ( !LightboxView.LightboxDoc && // bcz: when should links be followed? we don't want to move away from the video to follow a link but we can open it in a sidebar/etc. But we don't know that upfront. // for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video. - /*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this._props.layoutDoc))*/ + /* (isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this._props.layoutDoc)) */ !this._props.layoutDoc.dontAutoFollowLinks && LinkManager.Links(this._props.mark).length && time > NumCast(this._props.mark[this._props.startTag]) && @@ -764,34 +778,33 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch // starting the drag event for anchor resizing @action onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => { - //this._props._timeline?.setPointerCapture(e.pointerId); - const newTime = (e: PointerEvent) => { - const rect = (e.target as any).getBoundingClientRect(); - return this._props.toTimeline(e.clientX - rect.x, rect.width); + const newTime = (timeDownEv: PointerEvent) => { + const rect = (timeDownEv.target as any).getBoundingClientRect(); + return this._props.toTimeline(timeDownEv.clientX - rect.x, rect.width); }; - const changeAnchor = (anchor: Doc, left: boolean, time: number | undefined) => { + const changeAnchor = (time: number | undefined) => { const timelineOnly = Cast(anchor[this._props.startTag], 'number', null) !== undefined; if (timelineOnly) { - if (!left && time !== undefined && time <= NumCast(anchor[this._props.startTag])) time = undefined; - Doc.SetInPlace(anchor, left ? this._props.startTag : this._props.endTag, time, true); - if (!left) Doc.SetInPlace(anchor, 'layout_borderRounding', time !== undefined ? undefined : '100%', true); + const timeMod = !left && time !== undefined && time <= NumCast(anchor[this._props.startTag]) ? undefined : time; + Doc.SetInPlace(anchor, left ? this._props.startTag : this._props.endTag, timeMod, true); + if (!left) Doc.SetInPlace(anchor, 'layout_borderRounding', timeMod !== undefined ? undefined : '100%', true); } else { anchor[left ? '_timecodeToShow' : '_timecodeToHide'] = time; } return false; }; this.noEvents = true; - var undo: UndoManager.Batch | undefined; + let undo: UndoManager.Batch | undefined; setupMoveUpEvents( this, e, - e => { + moveEv => { if (!undo) undo = UndoManager.StartBatch('drag anchor'); - this._props.setTime(newTime(e)); - return changeAnchor(anchor, left, newTime(e)); + this._props.setTime(newTime(moveEv)); + return changeAnchor(newTime(moveEv)); }, - action(e => { - this._props.setTime(newTime(e)); + action(upEv => { + this._props.setTime(newTime(upEv)); undo?.end(); this.noEvents = false; }), @@ -829,7 +842,9 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch {...this._props} NativeWidth={returnZero} NativeHeight={returnZero} - ref={action((r: DocumentView | null) => (anchor.view = r))} + ref={action((r: DocumentView | null) => { + anchor.view = r; + })} Document={mark} TemplateDataDocument={undefined} containerViewPath={this._props.containerViewPath} @@ -852,7 +867,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch onClickScript={script} onDoubleClickScript={this._props.layoutDoc.autoPlayAnchors ? undefined : doublescript} ignoreAutoHeight={false} - hideResizeHandles={true} + hideResizeHandles contextMenuItems={this.contextMenuItems} /> ), @@ -878,12 +893,15 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch ); } } +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function formatToTime(time: number): any { return formatTime(time); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function min(num1: number, num2: number): number { return Math.min(num1, num2); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function max(num1: number, num2: number): number { return Math.max(num1, num2); }); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 7d93f4074..f3dedaedf 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -3,11 +3,12 @@ import * as React from 'react'; import * as rp from 'request-promise'; import { ClientUtils, returnFalse } from '../../../ClientUtils'; import CursorField from '../../../fields/CursorField'; -import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc'; +import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc'; import { AclPrivate } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; +import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; @@ -61,8 +62,8 @@ export interface SubCollectionViewProps extends CollectionViewProps { isAnyChildContentActive: () => boolean; } -export function CollectionSubView<X>(moreProps?: X) { - class CollectionSubView extends ViewBoxBaseComponent<X & SubCollectionViewProps>() { +export function CollectionSubView<X>() { + class CollectionSubViewInternal extends ViewBoxBaseComponent<X & SubCollectionViewProps>() { private dropDisposer?: DragManager.DragDropDisposer; private gestureDisposer?: GestureUtils.GestureEventDisposer; protected _mainCont?: HTMLDivElement; @@ -167,23 +168,24 @@ export function CollectionSubView<X>(moreProps?: X) { if (notFiltered) { notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.Document).length > 0; const fieldKey = Doc.LayoutFieldKey(d); - const annos = !Field.toString(Doc.LayoutField(d) as FieldType).includes(CollectionView.name); - const data = d[annos ? fieldKey + '_annotations' : fieldKey]; - const side = annos && d[fieldKey + '_sidebar']; - if (data !== undefined || side !== undefined) { - let subDocs = [...DocListCast(data), ...DocListCast(side)]; + const isAnnotatableDoc = d[fieldKey] instanceof List && !(d[fieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc)); + const docChildDocs = d[isAnnotatableDoc ? fieldKey + '_annotations' : fieldKey]; + const sidebarDocs = isAnnotatableDoc && d[fieldKey + '_sidebar']; + if (docChildDocs !== undefined || sidebarDocs !== undefined) { + let subDocs = [...DocListCast(docChildDocs), ...DocListCast(sidebarDocs)]; if (subDocs.length > 0) { let newarray: Doc[] = []; notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, childDocFilters, childFiltersByRanges, d).length); while (subDocs.length > 0 && !notFiltered) { newarray = []; + // eslint-disable-next-line no-loop-func subDocs.forEach(t => { - const fieldKey = Doc.LayoutFieldKey(t); - const annos = !Field.toString(Doc.LayoutField(t) as FieldType).includes(CollectionView.name); + const docFieldKey = Doc.LayoutFieldKey(t); + const isSubDocAnnotatable = t[docFieldKey] instanceof List && !(t[docFieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc)); notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !childFiltersByRanges.length) || DocUtils.FilterDocs([t], childDocFilters, childFiltersByRanges, d).length)); - DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc)); - annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc)); + DocListCast(t[isSubDocAnnotatable ? docFieldKey + '_annotations' : docFieldKey]).forEach(newdoc => newarray.push(newdoc)); + isSubDocAnnotatable && DocListCast(t[docFieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc)); }); subDocs = newarray; } @@ -227,6 +229,7 @@ export function CollectionSubView<X>(moreProps?: X) { } @undoBatch + // eslint-disable-next-line @typescript-eslint/no-unused-vars protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {} protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, targetDropAction: dropActionType) { @@ -245,7 +248,7 @@ export function CollectionSubView<X>(moreProps?: X) { addDocument = (doc: Doc | Doc[], annotationKey?: string) => this._props.addDocument?.(doc, annotationKey) || false; removeDocument = (doc: Doc | Doc[], annotationKey?: string) => this._props.removeDocument?.(doc, annotationKey) || false; - moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this._props.moveDocument?.(doc, targetCollection, addDocument) || false; + moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean) => this._props.moveDocument?.(doc, targetCollection, addDocument) || false; protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean { const { docDragData } = de.complete; @@ -531,5 +534,5 @@ export function CollectionSubView<X>(moreProps?: X) { }; } - return CollectionSubView; + return CollectionSubViewInternal; } diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 7f234ffe9..e1d2e3c40 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -1,3 +1,5 @@ +/* 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'; @@ -16,7 +18,6 @@ import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { FieldsDropdown } from '../FieldsDropdown'; import { DocumentView } from '../nodes/DocumentView'; -import { FocusViewOptions } from '../nodes/FieldView'; import { PresBox } from '../nodes/trails'; import { CollectionSubView } from './CollectionSubView'; import './CollectionTimeView.scss'; @@ -68,7 +69,7 @@ export class CollectionTimeView extends CollectionSubView() { }; @action - scrollPreview = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: FocusViewOptions) => { + scrollPreview = (docView: DocumentView, anchor: Doc /* , focusSpeed: number, options: FocusViewOptions */) => { // if in preview, then override document's fields with view spec this._focusFilters = StrListCast(anchor.config_docFilters); this._focusRangeFilters = StrListCast(anchor.config_docRangeFilters); @@ -77,13 +78,15 @@ export class CollectionTimeView extends CollectionSubView() { }; layoutEngine = () => this._layoutEngine; - toggleVisibility = action(() => (this._collapsed = !this._collapsed)); + toggleVisibility = action(() => { + this._collapsed = !this._collapsed; + }); onMinDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, e, - action((e: PointerEvent, down: number[], delta: number[]) => { + action((moveEv: PointerEvent, down: number[], delta: number[]) => { const minReq = NumCast(this.Document[this._props.fieldKey + '-timelineMinReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMin'], 0)); const maxReq = NumCast(this.Document[this._props.fieldKey + '-timelineMaxReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMax'], 10)); this.Document[this._props.fieldKey + '-timelineMinReq'] = minReq + ((maxReq - minReq) * delta[0]) / this._props.PanelWidth(); @@ -99,7 +102,7 @@ export class CollectionTimeView extends CollectionSubView() { setupMoveUpEvents( this, e, - action((e: PointerEvent, down: number[], delta: number[]) => { + action((moveEv, down: number[], delta: number[]) => { const minReq = NumCast(this.Document[this._props.fieldKey + '-timelineMinReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMin'], 0)); const maxReq = NumCast(this.Document[this._props.fieldKey + '-timelineMaxReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMax'], 10)); this.Document[this._props.fieldKey + '-timelineMaxReq'] = maxReq + ((maxReq - minReq) * delta[0]) / this._props.PanelWidth(); @@ -114,7 +117,7 @@ export class CollectionTimeView extends CollectionSubView() { setupMoveUpEvents( this, e, - action((e: PointerEvent, down: number[], delta: number[]) => { + action((moveEv: PointerEvent, down: number[], delta: number[]) => { const minReq = NumCast(this.Document[this._props.fieldKey + '-timelineMinReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMin'], 0)); const maxReq = NumCast(this.Document[this._props.fieldKey + '-timelineMaxReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMax'], 10)); this.Document[this._props.fieldKey + '-timelineMinReq'] = minReq - ((maxReq - minReq) * delta[0]) / this._props.PanelWidth(); @@ -134,7 +137,7 @@ export class CollectionTimeView extends CollectionSubView() { }; @action - contentsDown = (e: React.MouseEvent) => { + contentsDown = () => { const prevFilterIndex = NumCast(this.layoutDoc._prevFilterIndex); if (prevFilterIndex > 0) { this.goTo(prevFilterIndex - 1); @@ -147,6 +150,7 @@ export class CollectionTimeView extends CollectionSubView() { return ( <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this._props.isContentActive() ? undefined : 'none' }} onClick={this.contentsDown}> <CollectionFreeFormView + // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} engineProps={{ pivotField: this.pivotField, childFilters: this.childDocFilters, childFiltersByRanges: this.childDocRangeFilters }} fitContentsToBox={returnTrue} @@ -162,7 +166,7 @@ export class CollectionTimeView extends CollectionSubView() { const fieldKey = Doc.LayoutFieldKey(doc); doc[fieldKey + '-timelineCur'] = ComputedField.MakeFunction("(activePresentationItem()[this._pivotField || 'year'] || 0)"); } - specificMenu = (e: React.MouseEvent) => { + specificMenu = () => { const layoutItems: ContextMenuProps[] = []; const doc = this.layoutDoc; @@ -194,7 +198,7 @@ export class CollectionTimeView extends CollectionSubView() { render() { let nonNumbers = 0; - this.childDocs.map(doc => { + this.childDocs.forEach(doc => { const num = NumCast(doc[this.pivotField], Number(StrCast(doc[this.pivotField]))); if (isNaN(num)) { nonNumbers++; @@ -226,13 +230,20 @@ export class CollectionTimeView extends CollectionSubView() { </> )} <div style={{ right: 0, top: 0, position: 'absolute' }}> - <FieldsDropdown Document={this.Document} selectFunc={fieldKey => (this.layoutDoc._pivotField = fieldKey)} placeholder={StrCast(this.layoutDoc._pivotField)} /> + <FieldsDropdown + Document={this.Document} + selectFunc={fieldKey => { + this.layoutDoc._pivotField = fieldKey; + }} + placeholder={StrCast(this.layoutDoc._pivotField)} + /> </div> </div> ); } } +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) { const pivotField = StrCast(pivotDoc._pivotField, 'author'); let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex); @@ -249,6 +260,7 @@ ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBou const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc); if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) { if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) { + // eslint-disable-next-line prefer-destructuring pivotDoc._pivotField = filterVals[0]; } } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 32473f20b..538a6fd5e 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,3 +1,5 @@ +/* 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 { observer } from 'mobx-react'; import * as React from 'react'; @@ -23,13 +25,13 @@ import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from '../EditableView'; import { DocumentView } from '../nodes/DocumentView'; -import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView } from './CollectionSubView'; import './CollectionTreeView.scss'; import { TreeView } from './TreeView'; + const _global = (window /* browser */ || global) /* node */ as any; export type collectionTreeViewProps = { @@ -94,8 +96,10 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree // these should stay in synch with counterparts in DocComponent.ts ViewBoxAnnotatableComponent @observable _isAnyChildContentActive = false; - whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive))); - isContentActive = (outsideReaction?: boolean) => (this._isAnyChildContentActive ? true : this._props.isContentActive() ? true : false); + whenChildContentsActiveChanged = action((isActive: boolean) => { + this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)); + }); + isContentActive = () => (this._isAnyChildContentActive ? true : !!this._props.isContentActive()); componentWillUnmount() { this._isDisposing = true; @@ -105,7 +109,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree } componentDidMount() { - //this._props.setContentView?.(this); + // this._props.setContentView?.(this); this._disposers.autoheight = reaction( () => this.layoutDoc.layout_autoHeight, auto => auto && this.computeHeight(), @@ -128,20 +132,19 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree observeHeight = (ref: any) => { if (ref) { this.refList.add(ref); - this.observer = new _global.ResizeObserver( - action((entries: any) => { - if (this.layoutDoc.layout_autoHeight && ref && this.refList.size && !SnappingManager.IsDragging) { - this.computeHeight(); - } - }) - ); + this.observer = new _global.ResizeObserver(() => { + if (this.layoutDoc.layout_autoHeight && ref && this.refList.size && !SnappingManager.IsDragging) { + this.computeHeight(); + } + }); this.layoutDoc.layout_autoHeight && this.computeHeight(); this.observer.observe(ref); } }; protected createTreeDropTarget = (ele: HTMLDivElement) => { this._treedropDisposer?.(); - if ((this._mainEle = ele)) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.Document, this.onInternalPreDrop.bind(this)); + this._mainEle = ele; + if (ele) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.Document, this.onInternalPreDrop.bind(this)); }; protected onInternalDrop(e: Event, de: DragManager.DropEvent) { @@ -169,7 +172,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree } }; - dragConfig = (dragData: DragManager.DocumentDragData) => (dragData.treeViewDoc = this.Document); + dragConfig = (dragData: DragManager.DocumentDragData) => { dragData.treeViewDoc = this.Document; }; // prettier-ignore screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, -this._headerHeight); @@ -198,10 +201,10 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree @action addDoc = (docs: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => { - const doclist = docs instanceof Doc ? [docs] : docs; - const addDocRelativeTo = (doc: Doc | Doc[]) => doclist.reduce((flg, doc) => flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before), true); + const addDocRelativeTo = (adocs: Doc | Doc[]) => (adocs as Doc[]).reduce((flg, doc) => flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before), true); if (this.Document.resolvedDataDoc instanceof Promise) return false; - const res = relativeTo === undefined ? this._props.addDocument?.(docs) || false : addDocRelativeTo(docs); + const doclist = docs instanceof Doc ? [docs] : docs; + const res = relativeTo === undefined ? this._props.addDocument?.(doclist) || false : addDocRelativeTo(doclist); res && doclist.forEach(doc => { Doc.SetContainer(doc, this.Document); @@ -209,7 +212,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree }); return res; }; - onContextMenu = (e: React.MouseEvent): void => { + onContextMenu = (): void => { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout const layoutItems: ContextMenuProps[] = []; const menuDoc = ScriptCast(Cast(this.layoutDoc.layout_headerButton, Doc, null)?.onClick).script.originalScript === CollectionTreeView.AddTreeFunc; @@ -217,11 +220,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree if (!Doc.noviceMode) { layoutItems.push({ description: 'Make tree state ' + (this.Document.treeView_OpenIsTransient ? 'persistent' : 'transient'), - event: () => (this.Document.treeView_OpenIsTransient = !this.Document.treeView_OpenIsTransient), + event: () => { this.Document.treeView_OpenIsTransient = !this.Document.treeView_OpenIsTransient; }, // prettier-ignore icon: 'paint-brush', }); - layoutItems.push({ description: (this.Document.treeView_HideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => (this.Document.treeView_HideHeaderFields = !this.Document.treeView_HideHeaderFields), icon: 'paint-brush' }); - layoutItems.push({ description: (this.Document.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.Document.treeView_HideTitle = !this.Document.treeView_HideTitle), icon: 'paint-brush' }); + layoutItems.push({ description: (this.Document.treeView_HideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => { this.Document.treeView_HideHeaderFields = !this.Document.treeView_HideHeaderFields; }, icon: 'paint-brush' }); // prettier-ignore + layoutItems.push({ description: (this.Document.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => { this.Document.treeView_HideTitle = !this.Document.treeView_HideTitle; }, icon: 'paint-brush' }); // prettier-ignore } ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' }); if (!Doc.noviceMode) { @@ -240,9 +243,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree return ( <EditableView contents={this.dataDoc.title} - display={'block'} + display="block" maxHeight={72} - height={'auto'} + height="auto" GetValue={() => StrCast(this.dataDoc.title)} SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => { if (enter && this.Document.treeView_Type === TreeViewType.outline) this.makeTextCollection(this.treeChildren); @@ -253,22 +256,24 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree ); } - onKey = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { + onKey = (e: React.KeyboardEvent /* , fieldProps: FieldViewProps */) => { if (this.outlineMode && e.key === 'Enter') { e.stopPropagation(); this.makeTextCollection(this.treeChildren); return true; } + return undefined; }; get documentTitle() { return ( <FormattedTextBox + // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} fieldKey="text" renderDepth={this._props.renderDepth + 1} isContentActive={this.isContentActive} isDocumentActive={this.isContentActive} - forceAutoHeight={true} // needed to make the title resize even if the rest of the tree view is not layout_autoHeight + forceAutoHeight // needed to make the title resize even if the rest of the tree view is not layout_autoHeight PanelWidth={this.documentTitleWidth} PanelHeight={this.documentTitleHeight} NativeDimScaling={returnOne} @@ -296,7 +301,12 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree const dragAction = StrCast(this.Document.childDragAction) as any as dropActionType; const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before); const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this._props.moveDocument?.(d, target, addDoc) || false; - if (this._renderCount < this.treeChildren.length) setTimeout(action(() => (this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20)))); + if (this._renderCount < this.treeChildren.length) + setTimeout( + action(() => { + this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20); + }) + ); return TreeView.GetChildElements( this.treeChildren, this, @@ -326,7 +336,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree this.observeHeight, this.unobserveHeight, this.childContextMenuItems(), - //TODO: [AL] add these this._props.AddToMap, this._props.RemFromMap, this._props.hierarchyIndex, @@ -337,7 +346,9 @@ 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={action((r: any) => { + (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} @@ -420,7 +431,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree return ( <div style={{ display: 'flex', flexDirection: 'column', height: '100%', pointerEvents: 'all' }}> {!this.buttonMenu && !this.noviceExplainer ? null : ( - <div className="documentButtonMenu" ref={action((r: HTMLDivElement | null) => r && (this._headerHeight = DivHeight(r)))}> + <div + className="documentButtonMenu" + ref={action((r: HTMLDivElement | null) => { + r && (this._headerHeight = DivHeight(r)); + })}> {this.buttonMenu} {this.noviceExplainer} </div> @@ -453,7 +468,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree minHeight: '100%', }} onWheel={e => e.stopPropagation()} - onClick={e => (!this.layoutDoc.forceActive ? this._props.select(false) : SelectionManager.DeselectAll())} + onClick={() => (!this.layoutDoc.forceActive ? this._props.select(false) : SelectionManager.DeselectAll())} onDrop={this.onTreeDrop}> <ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul> </div> @@ -470,13 +485,14 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree <div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}> {!(this.Document instanceof Doc) || !this.treeChildren ? null : this.Document.treeView_HasOverlay ? ( <CollectionFreeFormView + // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} setContentViewBox={emptyFunction} NativeWidth={returnZero} NativeHeight={returnZero} pointerEvents={this._props.isContentActive() && SnappingManager.IsDragging ? returnAll : returnNone} - isAnnotationOverlay={true} - isAnnotationOverlayScrollable={true} + isAnnotationOverlay + isAnnotationOverlayScrollable childDocumentsActive={this._props.isContentActive} fieldKey={this._props.fieldKey + '_annotations'} dropAction={dropActionType.move} @@ -500,6 +516,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree } } +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function addTreeFolder(doc: Doc) { CollectionTreeView.addTreeFolder(doc); }); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 8a2e83ed9..a661cf6a2 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -32,7 +32,7 @@ import { ObservableReactComponent } from '../ObservableReactComponent'; import { DefaultStyleProvider, StyleProp } from '../StyleProvider'; import { Colors } from '../global/globalEnums'; import { DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView'; -import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView'; +import { FieldViewProps } from '../nodes/FieldView'; import { KeyValueBox } from '../nodes/KeyValueBox'; import { PresBox, PresMovement } from '../nodes/trails'; import { CollectionDockingView } from './CollectionDockingView'; @@ -42,6 +42,146 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV const _global = (window /* browser */ || global) /* node */ as any; +interface TabMinimapViewProps { + document: Doc; + tabView: () => DocumentView | undefined; + addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean; + PanelWidth: () => number; + PanelHeight: () => number; + background: () => string; +} +interface TabMiniThumbProps { + miniWidth: () => number; + miniHeight: () => number; + miniTop: () => number; + miniLeft: () => number; +} + +@observer +class TabMiniThumb extends React.Component<TabMiniThumbProps> { + render() { + const { miniWidth, miniHeight, miniLeft, miniTop } = this.props; + return <div className="miniThumb" style={{ width: `${miniWidth()}%`, height: `${miniHeight()}%`, left: `${miniLeft()}%`, top: `${miniTop()}%` }} />; + } +} +@observer +export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps> { + static miniStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => { + if (doc) { + switch (property.split(':')[0]) { + case StyleProp.PointerEvents: return 'none'; + case StyleProp.DocContents: { + const background = (() => { + switch (doc.type as DocumentType) { + case DocumentType.PDF: return 'pink'; + case DocumentType.AUDIO: return 'lightgreen'; + case DocumentType.WEB: return 'brown'; + case DocumentType.IMG: return 'blue'; + case DocumentType.MAP: return 'orange'; + case DocumentType.VID: return 'purple'; + case DocumentType.RTF: return 'yellow'; + case DocumentType.COL: return undefined; + default: return 'gray'; + } // prettier-ignore + })(); + return !background ? undefined : <div style={{ width: NumCast(doc._width), height: NumCast(doc._height), position: 'absolute', display: 'block', background }} />; + } + default: return DefaultStyleProvider(doc, props, property); + } // prettier-ignore + } + return undefined; + }; + + @computed get renderBounds() { + const compView = this._props.tabView()?.ComponentView as CollectionFreeFormView; + const bounds = compView?.freeformData?.(true)?.bounds; + if (!bounds) return undefined; + const xbounds = bounds.r - bounds.x; + const ybounds = bounds.b - bounds.y; + const dim = Math.max(xbounds, ybounds); + return { l: bounds.x + xbounds / 2 - dim / 2, t: bounds.y + ybounds / 2 - dim / 2, cx: bounds.x + xbounds / 2, cy: bounds.y + ybounds / 2, dim }; + } + @computed get xPadding() { + return !this.renderBounds ? 0 : Math.max(0, this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cx - this.renderBounds.l)); + } + @computed get yPadding() { + return !this.renderBounds ? 0 : Math.max(0, this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cy - this.renderBounds.l)); + } + childLayoutTemplate = () => Cast(this._props.document.childLayoutTemplate, Doc, null); + returnMiniSize = () => NumCast(this._props.document._miniMapSize, 150); + miniDown = (e: React.PointerEvent) => { + const doc = this._props.document; + const miniSize = this.returnMiniSize(); + doc && + setupMoveUpEvents( + this, + e, + action((moveEv, down: number[], delta: number[]) => { + const renderBounds = this.renderBounds ?? { l: 0, r: 0, t: 0, b: 0, dim: 1 }; + doc._freeform_panX = clamp(NumCast(doc._freeform_panX) + (delta[0] / miniSize) * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim); + doc._freeform_panY = clamp(NumCast(doc._freeform_panY) + (delta[1] / miniSize) * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim); + return false; + }), + emptyFunction, + emptyFunction + ); + }; + popup = () => { + if (!this.renderBounds) return <div />; + const { renderBounds } = this; + const miniWidth = () => (this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100; + const miniHeight = () => (this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100; + const miniLeft = () => 50 + ((NumCast(this._props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2; + const miniTop = () => 50 + ((NumCast(this._props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2; + const miniSize = this.returnMiniSize(); + return ( + <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}> + <CollectionFreeFormView + Document={this._props.document} + docViewPath={returnEmptyDocViewList} + childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this. + noOverlay // don't render overlay Docs since they won't scale + isContentActive={emptyFunction} + isAnyChildContentActive={returnFalse} + select={emptyFunction} + isSelected={returnFalse} + dontRegisterView + fieldKey={Doc.LayoutFieldKey(this._props.document)} + addDocument={returnFalse} + moveDocument={returnFalse} + removeDocument={returnFalse} + PanelWidth={this.returnMiniSize} + PanelHeight={this.returnMiniSize} + ScreenToLocalTransform={Transform.Identity} + renderDepth={0} + whenChildContentsActiveChanged={emptyFunction} + focus={emptyFunction} + styleProvider={TabMinimapView.miniStyleProvider} + 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} + searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist} + fitContentsToBox={returnTrue} + xPadding={this.xPadding} + yPadding={this.yPadding} + /> + <div className="miniOverlay" onPointerDown={this.miniDown}> + <TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} /> + </div> + </div> + ); + }; + render() { + return this._props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._props.document)) || this._props.document?._type_collection !== CollectionViewType.Freeform ? null : ( + <div className="miniMap-hidden"> + <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SnappingManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} /> + </div> + ); + } +} + interface TabDocViewProps { documentId: FieldId; keyValue?: boolean; @@ -92,7 +232,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { @action init = (tab: any, doc: Opt<Doc>) => { if (tab.contentItem === tab.header.parent.getActiveContentItem()) this._activated = true; - if (tab.DashDoc !== doc && doc && tab.hasOwnProperty('contentItem') && tab.contentItem.config.type !== 'stack') { + if (tab.DashDoc !== doc && doc && tab.contentItem?.config.type !== 'stack') { tab._disposers = {} as { [name: string]: IReactionDisposer }; tab.contentItem.config.fixed && (tab.contentItem.parent.config.fixed = true); tab.DashDoc = doc; @@ -130,17 +270,17 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { setupMoveUpEvents( this, e, - e => - !e.defaultPrevented && - DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY, undefined, () => { + moveEv => + !moveEv.defaultPrevented && + DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), moveEv.clientX, moveEv.clientY, undefined, () => { CollectionDockingView.CloseSplit(doc); }), returnFalse, - action(e => { + action(clickEv => { if (this.view) { SelectionManager.SelectView(this.view, false); const child = getChild(); - simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); + simulateMouseClick(child, clickEv.clientX, clickEv.clientY + 30, clickEv.screenX, clickEv.screenY + 30); } else { this._activated = true; setTimeout(() => this.view && SelectionManager.SelectView(this.view, false)); @@ -160,7 +300,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { ({ variant, degree, highlight }) => { const color = highlight?.highlightIndex === Doc.DocBrushStatus.highlighted ? highlight.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; @@ -186,19 +326,19 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { ); } // shifts the focus to this tab when another tab is dragged over it - tab.element[0].onmouseenter = (e: MouseEvent) => { + tab.element[0].onmouseenter = () => { if (SnappingManager.IsDragging && tab.contentItem !== tab.header.parent.getActiveContentItem()) { tab.header.parent.setActiveContentItem(tab.contentItem); tab.setActive(true); } this._document && Doc.BrushDoc(this._document); }; - tab.element[0].onmouseleave = (e: MouseEvent) => { + tab.element[0].onmouseleave = () => { this._document && Doc.UnBrushDoc(this._document); }; tab.element[0].oncontextmenu = (e: MouseEvent) => { - let child = getChild(); + const child = getChild(); if (child) { simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); e.stopPropagation(); @@ -220,12 +360,9 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { () => SelectionManager.IsSelected(this._document), action(selected => { if (selected) this._activated = true; - const toggle = tab.element[0].children[2].children[0] as HTMLInputElement; if (selected && tab.contentItem !== tab.header.parent.getActiveContentItem()) { undoable(() => tab.header.parent.setActiveContentItem(tab.contentItem), 'tab switch')(); } - //toggle.style.fontWeight = selected ? 'bold' : ''; - // toggle.style.textTransform = selected ? "uppercase" : ""; }), { fireImmediately: true } ); @@ -233,14 +370,16 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { // highlight the tab when the tab document is brushed in any part of the UI tab._disposers.reactionDisposer = reaction( () => doc?.title, - title => (titleEle.value = title), + title => { + titleEle.value = title; + }, { fireImmediately: true } ); // clean up the tab when it is closed tab.closeElement - .off('click') //unbind the current click handler - .click(function () { + .off('click') // unbind the current click handler + .click(() => { Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); SelectionManager.DeselectAll(); UndoManager.RunInBatch(() => tab.contentItem.remove(), 'delete tab'); @@ -250,7 +389,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { /** * Adds a document to the presentation view - **/ + * */ @action public static PinDoc(docs: Doc | Doc[], pinProps: PinProps) { const docList = docs instanceof Doc ? [docs] : docs; @@ -310,7 +449,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { if (curPres.expandBoolean) pinDoc.presentation_expandInlineButton = true; Doc.AddDocToList(curPres, 'data', pinDoc, PresBox.Instance?.sortArray()?.lastElement()); PresBox.Instance?.clearSelectedArray(); - pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); //Update selected array + pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); // Update selected array }); if ( // open the presentation trail if it's not already opened @@ -328,6 +467,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { componentDidMount() { new _global.ResizeObserver( action((entries: any) => { + // eslint-disable-next-line no-restricted-syntax for (const entry of entries) { this._panelWidth = entry.contentRect.width; this._panelHeight = entry.contentRect.height; @@ -374,29 +514,30 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { // "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name, // "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right // lightbox - will add the document to any collection along the path from the document to the docking view that has a field isLightbox. if none is found, it adds to the full screen lightbox - addDocTab = (doc: Doc, location: OpenWhere) => { + addDocTab = (docsIn: Doc | Doc[], location: OpenWhere) => { + const docs = docsIn instanceof Doc ? [docsIn] : docsIn; SelectionManager.DeselectAll(); const whereFields = location.split(':'); const keyValue = whereFields.includes(OpenWhereMod.keyvalue); const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none; const panelName = whereFields.length > 1 ? whereFields.lastElement() : ''; - if (doc.dockingConfig && !keyValue) return DashboardView.openDashboard(doc); + if (docs[0]?.dockingConfig && !keyValue) return DashboardView.openDashboard(docs[0]); // prettier-ignore switch (whereFields[0]) { case undefined: case OpenWhere.lightbox: if (this.layoutDoc?._isLightbox) { - const lightboxView = !doc.annotationOn && DocCast(doc.embedContainer) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.embedContainer)) : undefined; + const lightboxView = !docs[0].annotationOn && DocCast(docs[0].embedContainer) ? DocumentManager.Instance.getFirstDocumentView(DocCast(docs[0].embedContainer)) : undefined; const data = lightboxView?.dataDoc[Doc.LayoutFieldKey(lightboxView.Document)]; if (lightboxView && (!data || data instanceof List)) { - lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.Document)] = new List<Doc>([doc]); + lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.Document)] = new List<Doc>(docs); return true; } } - return LightboxView.Instance.AddDocTab(doc, OpenWhere.lightbox); - case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods); - case OpenWhere.replace: return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack, panelName, undefined, keyValue); - case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack, TabDocView.DontSelectOnActivate, keyValue); - case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack, undefined, keyValue); + return LightboxView.Instance.AddDocTab(docs[0], OpenWhere.lightbox); + case OpenWhere.close: return CollectionDockingView.CloseSplit(docs[0], whereMods); + case OpenWhere.replace: return CollectionDockingView.ReplaceTab(docs[0], whereMods, this.stack, panelName, undefined, keyValue); + case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(docs[0], whereMods, this.stack, TabDocView.DontSelectOnActivate, keyValue); + case OpenWhere.add:default:return CollectionDockingView.AddSplit(docs[0], whereMods, this.stack, undefined, keyValue); } }; remDocTab = (doc: Doc | Doc[]) => { @@ -410,12 +551,12 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { getCurrentFrame = () => NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame); static Activate = (tabDoc: Doc) => { - const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(tab => tab.DashDoc === tabDoc && !tab.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; }; @action - focusFunc = (doc: Doc, options: FocusViewOptions) => { + focusFunc = () => { if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) { this.tab.header.parent.setActiveContentItem(this.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) } @@ -498,8 +639,12 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { } this._lastTab = this.tab; (this._mainCont as any).InitTab = (tab: any) => 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((entries: any) => this._forceInvalidateScreenToLocal++)).observe(ref); + 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); } }}> {this.docView} @@ -507,141 +652,3 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { ); } } - -interface TabMinimapViewProps { - document: Doc; - tabView: () => DocumentView | undefined; - addDocTab: (doc: Doc, where: OpenWhere) => boolean; - PanelWidth: () => number; - PanelHeight: () => number; - background: () => string; -} -interface TabMiniThumbProps { - miniWidth: () => number; - miniHeight: () => number; - miniTop: () => number; - miniLeft: () => number; -} - -@observer -class TabMiniThumb extends React.Component<TabMiniThumbProps> { - render() { - const { miniWidth, miniHeight, miniLeft, miniTop } = this.props; - return <div className="miniThumb" style={{ width: `${miniWidth()}%`, height: `${miniHeight()}%`, left: `${miniLeft()}%`, top: `${miniTop()}%` }} />; - } -} -@observer -export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps> { - static miniStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => { - if (doc) { - switch (property.split(':')[0]) { - case StyleProp.PointerEvents: return 'none'; - case StyleProp.DocContents: { - const background = (() => { - switch (doc.type as DocumentType) { - case DocumentType.PDF: return 'pink'; - case DocumentType.AUDIO: return 'lightgreen'; - case DocumentType.WEB: return 'brown'; - case DocumentType.IMG: return 'blue'; - case DocumentType.MAP: return 'orange'; - case DocumentType.VID: return 'purple'; - case DocumentType.RTF: return 'yellow'; - case DocumentType.COL: return undefined; - default: return 'gray'; - } // prettier-ignore - })(); - return !background ? undefined : <div style={{ width: NumCast(doc._width), height: NumCast(doc._height), position: 'absolute', display: 'block', background }} />; - } - default: return DefaultStyleProvider(doc, props, property); - } // prettier-ignore - } - }; - - @computed get renderBounds() { - const compView = this._props.tabView()?.ComponentView as CollectionFreeFormView; - const bounds = compView?.freeformData?.(true)?.bounds; - if (!bounds) return undefined; - const xbounds = bounds.r - bounds.x; - const ybounds = bounds.b - bounds.y; - const dim = Math.max(xbounds, ybounds); - return { l: bounds.x + xbounds / 2 - dim / 2, t: bounds.y + ybounds / 2 - dim / 2, cx: bounds.x + xbounds / 2, cy: bounds.y + ybounds / 2, dim }; - } - @computed get xPadding() { - return !this.renderBounds ? 0 : Math.max(0, this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cx - this.renderBounds.l)); - } - @computed get yPadding() { - return !this.renderBounds ? 0 : Math.max(0, this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cy - this.renderBounds.l)); - } - childLayoutTemplate = () => Cast(this._props.document.childLayoutTemplate, Doc, null); - returnMiniSize = () => NumCast(this._props.document._miniMapSize, 150); - miniDown = (e: React.PointerEvent) => { - const doc = this._props.document; - const miniSize = this.returnMiniSize(); - doc && - setupMoveUpEvents( - this, - e, - action((e: PointerEvent, down: number[], delta: number[]) => { - const renderBounds = this.renderBounds ?? { l: 0, r: 0, t: 0, b: 0, dim: 1 }; - doc._freeform_panX = clamp(NumCast(doc._freeform_panX) + (delta[0] / miniSize) * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim); - doc._freeform_panY = clamp(NumCast(doc._freeform_panY) + (delta[1] / miniSize) * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim); - return false; - }), - emptyFunction, - emptyFunction - ); - }; - popup = () => { - if (!this.renderBounds) return <></>; - const renderBounds = this.renderBounds; - const miniWidth = () => (this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100; - const miniHeight = () => (this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100; - const miniLeft = () => 50 + ((NumCast(this._props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2; - const miniTop = () => 50 + ((NumCast(this._props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2; - const miniSize = this.returnMiniSize(); - return ( - <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}> - <CollectionFreeFormView - Document={this._props.document} - docViewPath={returnEmptyDocViewList} - childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this. - noOverlay={true} // don't render overlay Docs since they won't scale - isContentActive={emptyFunction} - isAnyChildContentActive={returnFalse} - select={emptyFunction} - isSelected={returnFalse} - dontRegisterView={true} - fieldKey={Doc.LayoutFieldKey(this._props.document)} - addDocument={returnFalse} - moveDocument={returnFalse} - removeDocument={returnFalse} - PanelWidth={this.returnMiniSize} - PanelHeight={this.returnMiniSize} - ScreenToLocalTransform={Transform.Identity} - renderDepth={0} - whenChildContentsActiveChanged={emptyFunction} - focus={emptyFunction} - styleProvider={TabMinimapView.miniStyleProvider} - addDocTab={this._props.addDocTab} - pinToPres={TabDocView.PinDoc} - childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist} - childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist} - searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist} - fitContentsToBox={returnTrue} - xPadding={this.xPadding} - yPadding={this.yPadding} - /> - <div className="miniOverlay" onPointerDown={this.miniDown}> - <TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} /> - </div> - </div> - ); - }; - render() { - return this._props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._props.document)) || this._props.document?._type_collection !== CollectionViewType.Freeform ? null : ( - <div className="miniMap-hidden"> - <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SnappingManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} /> - </div> - ); - } -} diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 20323a521..fab8e3892 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -55,7 +55,7 @@ export interface TreeViewProps { treeViewParent: Doc; renderDepth: number; dragAction: dropActionType; - addDocTab: (doc: Doc, where: OpenWhere) => boolean; + addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean; panelWidth: () => number; panelHeight: () => number; addDocument: (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => boolean; @@ -200,11 +200,11 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { } return false; }; - @undoBatch remove = (doc: Doc | Doc[], key: string) => { + @undoBatch remove = (docs: Doc | Doc[], key: string) => { this.treeView._props.select(false); - const ind = DocListCast(this.dataDoc[key]).indexOf(doc instanceof Doc ? doc : doc.lastElement()); + const ind = DocListCast(this.dataDoc[key]).indexOf(docs instanceof Doc ? docs : docs.lastElement()); - const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true); + const res = (docs instanceof Doc ? [docs] : docs).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true); res && ind > 0 && DocumentManager.Instance.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.treeView.DocumentView?.())?.select(false); return res; }; @@ -318,7 +318,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { this._props.hierarchyIndex !== undefined && this._props.AddToMap?.(this.Document, this._props.hierarchyIndex); } - onDragUp = (e: PointerEvent) => { + onDragUp = () => { document.removeEventListener('pointerup', this.onDragUp, true); document.removeEventListener('pointermove', this.onDragMove, true); }; @@ -403,7 +403,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { if (!this._header.current) return false; const rect = this._header.current.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; - const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false); + const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || !!(!before && this.treeViewOpen && this.childDocs?.length); if (de.complete.linkDragData) { const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor(); const destDoc = this.Document; @@ -431,14 +431,14 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { return false; }; - localAdd = (doc: Doc | Doc[]) => { - const innerAdd = (doc: Doc) => { + localAdd = (docs: Doc | Doc[]): boolean => { + const innerAdd = (doc: Doc): boolean => { const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer)); return added; }; - return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); + return (docs instanceof Doc ? [docs] : docs).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); }; dropping: boolean = false; @@ -513,16 +513,16 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { const leftOffset = observable({ width: 0 }); const expandedWidth = () => this._props.panelWidth() - leftOffset.width; if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc)) { - const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key); - const moveDoc = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(doc, target, addDoc); - const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => { - const innerAdd = (doc: Doc) => { + const remDoc = (docs: Doc | Doc[]) => this.remove(docs, key); + const moveDoc = (docs: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(docs, target, addDoc); + const addDoc = (docs: Doc | Doc[], addBefore?: Doc, before?: boolean) => { + const innerAdd = (iDoc: Doc) => { const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; - const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); - dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer)); + const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, iDoc, addBefore, before, false, true); + dataIsComputed && Doc.SetContainer(iDoc, DocCast(this.Document.embedContainer)); return added; }; - return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); + return (docs instanceof Doc ? [docs] : docs).reduce((flg, iDoc) => flg && innerAdd(iDoc), true as boolean); }; contentElement = TreeView.GetChildElements( contents instanceof Doc ? [contents] : DocListCast(contents), @@ -643,7 +643,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { return added; }; - const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true); + const addDoc = (docs: Doc | Doc[], addBefore?: Doc, before?: boolean) => (docs instanceof Doc ? [docs] : docs).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true); const docs = expandKey === 'embeddings' ? this.childEmbeddings : expandKey === 'links' ? this.childLinks : expandKey === 'annotations' ? this.childAnnos : this.childDocs; let downX = 0; let downY = 0; @@ -911,7 +911,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { // prettier-ignore switch (property.split(':')[0]) { case StyleProp.Opacity: return this.treeView.outlineMode ? undefined : 1; - case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : undefined;//StrCast(doc._backgroundColor, StrCast(doc.backgroundColor)); + case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : undefined; // StrCast(doc._backgroundColor, StrCast(doc.backgroundColor)); case StyleProp.Highlighting: if (this.treeView.outlineMode) return undefined; break; case StyleProp.BoxShadow: return undefined; @@ -1192,7 +1192,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { const pt = [de.clientX, de.clientY]; const rect = this._header.current!.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; - const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false); + const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || !!(!before && this.treeViewOpen && this.childDocs?.length); this.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, dropActionType.copy, undefined, undefined, false, false)); }; @@ -1259,7 +1259,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { remove: undefined | ((doc: Doc | Doc[]) => boolean), move: DragManager.MoveFunction, dragAction: dropActionType, - addDocTab: (doc: Doc, where: OpenWhere) => boolean, + addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean, styleProvider: undefined | StyleProviderFuncType, screenToLocalXf: () => Transform, isContentActive: (outsideReaction?: boolean) => boolean, @@ -1282,11 +1282,6 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { hierarchyIndex?: number[], renderCount?: number ) { - const viewSpecScript = Cast(treeViewParent.viewSpecScript, ScriptField); - if (viewSpecScript) { - childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result); - } - const docs = TreeView.sortDocs(childDocs, StrCast(treeViewParent.treeView_SortCriterion, TreeSort.WhenAdded)); const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView._props.NativeDimScaling?.() || 1); const treeViewRefs = new Map<Doc, TreeView | undefined>(); @@ -1327,7 +1322,6 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { dataDoc={pair.data} treeViewParent={treeViewParent} prevSibling={docs[i]} - // TODO: [AL] add these hierarchyIndex={hierarchyIndex ? [...hierarchyIndex, i + 1] : undefined} AddToMap={AddToMap} RemFromMap={RemFromMap} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx index 29d835b28..65a529d62 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx @@ -4,7 +4,6 @@ import * as React from 'react'; import { Doc, DocListCast, FieldType, FieldResult } from '../../../../fields/Doc'; import { InkTool } from '../../../../fields/InkField'; import { StrCast } from '../../../../fields/Types'; -import { DocumentManager } from '../../../util/DocumentManager'; import { LinkManager } from '../../../util/LinkManager'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton'; @@ -49,8 +48,8 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio setupStates = () => { this._originalbackground = StrCast(this._props.Document[DocData].backgroundColor); // state entry functions - const setBackground = (colour: string) => () => {this._props.Document[DocData].backgroundColor = colour;} // prettier-ignore - const setOpacity = (opacity: number) => () => {this._props.LayoutDoc.opacity = opacity;} // prettier-ignore + // const setBackground = (colour: string) => () => {this._props.Document[DocData].backgroundColor = colour;} // prettier-ignore + // const setOpacity = (opacity: number) => () => {this._props.LayoutDoc.opacity = opacity;} // prettier-ignore // arc transition trigger conditions const firstDoc = () => (this._props.childDocs().length ? this._props.childDocs()[0] : undefined); const numDocs = () => this._props.childDocs().length; @@ -73,7 +72,6 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio let trail: number; - const trailView = () => DocumentManager.Instance.DocumentViews.find(view => view.Document === Doc.MyTrails); const presentationMode = () => Doc.ActivePresentation?.presentation_status; // set of states @@ -83,6 +81,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio docCreated: [() => numDocs(), () => { docX = firstDoc()?.x; docY = firstDoc()?.y; + // eslint-disable-next-line no-use-before-define return oneDoc; }], } @@ -93,18 +92,20 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio { // docCreated: [() => numDocs() > 1, () => multipleDocs], docDeleted: [() => numDocs() < 1, () => start], - docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { + docMoved: [() => (docX && docX !== docNewX()) || (docY && docY !== docNewY()), () => { docX = firstDoc()?.x; docY = firstDoc()?.y; + // eslint-disable-next-line no-use-before-define return movedDoc; }], } ); // prettier-ignore const movedDoc = InfoState( - 'Great moves. Try creating a second document. You can see the list of supported document types by typing a colon (\":\")', + 'Great moves. Try creating a second document. You can see the list of supported document types by typing a colon (":")', { - docCreated: [() => numDocs() == 2, () => multipleDocs], + // eslint-disable-next-line no-use-before-define + docCreated: [() => numDocs() === 2, () => multipleDocs], docDeleted: [() => numDocs() < 1, () => start], }, 'dash-colon-menu.gif', @@ -114,6 +115,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio const multipleDocs = InfoState( 'Let\'s create a new link. Click the link icon on one of your documents.', { + // eslint-disable-next-line no-use-before-define linkStarted: [() => linkStart(), () => startedLink], docRemoved: [() => numDocs() < 2, () => oneDoc], }, @@ -124,6 +126,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio 'Now click the highlighted link icon on your other document.', { linkUnstart: [() => linkUnstart(), () => multipleDocs], + // eslint-disable-next-line no-use-before-define linkCreated: [() => numDocLinks(), () => madeLink], docRemoved: [() => numDocs() < 2, () => oneDoc], }, @@ -136,6 +139,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio linkCreated: [() => !numDocLinks(), () => multipleDocs], linkViewed: [() => linkMenuOpen(), () => { alert(numDocLinks() + " cheer for " + numDocLinks() + " link!"); + // eslint-disable-next-line no-use-before-define return viewedLink; }], }, @@ -147,10 +151,12 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio { linkDeleted: [() => !numDocLinks(), () => multipleDocs], docRemoved: [() => numDocs() < 2, () => oneDoc], - docCreated: [() => numDocs() == 3, () => { + docCreated: [() => numDocs() === 3, () => { trail = pin().length; + // eslint-disable-next-line no-use-before-define return presentDocs; }], + // eslint-disable-next-line no-use-before-define activePen: [() => activeTool() === InkTool.Pen, () => penMode], }, 'documentation.png', @@ -164,6 +170,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio () => pin().length > trail, () => { trail = pin().length; + // eslint-disable-next-line no-use-before-define return pinnedDoc1; }, ], @@ -186,11 +193,13 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio () => pin().length > trail, () => { trail = pin().length; + // eslint-disable-next-line no-use-before-define return pinnedDoc2; }, ], // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode], // manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode], + // eslint-disable-next-line no-use-before-define autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode], docRemoved: [() => numDocs() < 3, () => viewedLink], }); @@ -200,11 +209,13 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio () => pin().length > trail, () => { trail = pin().length; + // eslint-disable-next-line no-use-before-define return pinnedDoc3; }, ], // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode], // manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode], + // eslint-disable-next-line no-use-before-define autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode], docRemoved: [() => numDocs() < 3, () => viewedLink], }); @@ -219,6 +230,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio ], // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode], // manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode], + // eslint-disable-next-line no-use-before-define autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode], docRemoved: [() => numDocs() < 3, () => viewedLink], }); @@ -236,15 +248,18 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio const manualPresentationMode = InfoState("You're in manual presentation mode.", { // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode], + // eslint-disable-next-line no-use-before-define autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode], docRemoved: [() => numDocs() < 3, () => viewedLink], - docCreated: [() => numDocs() == 4, () => completed], + // eslint-disable-next-line no-use-before-define + docCreated: [() => numDocs() === 4, () => completed], }); const autoPresentationMode = InfoState("You're in auto presentation mode.", { // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode], manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode], docRemoved: [() => numDocs() < 3, () => viewedLink], + // eslint-disable-next-line no-use-before-define docCreated: [() => numDocs() === 4, () => completed], }); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index a0b96c75a..3970c6807 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-use-before-define */ import { Doc, Field, FieldType, FieldResult } from '../../../../fields/Doc'; import { Id, ToString } from '../../../../fields/FieldSymbols'; import { ObjectField } from '../../../../fields/ObjectField'; @@ -48,7 +49,7 @@ export interface PoolData { export interface ViewDefResult { ele: JSX.Element; bounds?: ViewDefBounds; - inkMask?: number; //sort elements into either the mask layer (which has a mixedBlendMode appropriate for transparent masks), or the regular documents layer; -1 = no mask, 0 = mask layer but stroke is transparent (hidden, as in during a presentation when you want to smoothly animate it into being a mask), >0 = mask layer and not hidden + inkMask?: number; // sort elements into either the mask layer (which has a mixedBlendMode appropriate for transparent masks), or the regular documents layer; -1 = no mask, 0 = mask layer but stroke is transparent (hidden, as in during a presentation when you want to smoothly animate it into being a mask), >0 = mask layer and not hidden } function toLabel(target: FieldResult<FieldType>) { if (typeof target === 'number' || Number(target)) { @@ -84,9 +85,9 @@ interface PivotColumn { filters: string[]; } -export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { +export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] /* , engineProps: any */) { const docMap = new Map<string, PoolData>(); - childPairs.forEach(({ layout, data }, i) => { + childPairs.forEach(({ layout, data }) => { docMap.set(layout[Id], { x: NumCast(layout.x), y: NumCast(layout.y), @@ -97,10 +98,15 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc replica: '', }); }); + // eslint-disable-next-line no-use-before-define return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []); } -export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { +function toNumber(val: FieldResult<FieldType>) { + return val === undefined ? undefined : NumCast(val, Number(StrCast(val))); +} + +export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] /* , engineProps: any */) { const docMap = new Map<string, PoolData>(); const burstDiam = [NumCast(pivotDoc._width), NumCast(pivotDoc._height)]; const burstScale = NumCast(pivotDoc._starburstDocScale, 1); @@ -132,7 +138,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do let nonNumbers = 0; const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField) || 'author'; - childPairs.map(pair => { + childPairs.forEach(pair => { const listValue = Cast(pair.layout[pivotFieldKey], listSpec('string'), null); const num = toNumber(pair.layout[pivotFieldKey]); @@ -184,11 +190,11 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do const textlen = Array.from(pivotColumnGroups.keys()) .map(c => getTextWidth(toLabel(c), desc)) .reduce((p, c) => Math.max(p, c), 0 as number); - const max_text = Math.min(Math.ceil(textlen / 120) * 28, panelDim[1] / 2); + const maxText = Math.min(Math.ceil(textlen / 120) * 28, panelDim[1] / 2); const maxInColumn = Array.from(pivotColumnGroups.values()).reduce((p, s) => Math.max(p, s.docs.length), 1); const colWidth = panelDim[0] / pivotColumnGroups.size; - const colHeight = panelDim[1] - max_text; + const colHeight = panelDim[1] - maxText; let numCols = 0; let bestArea = 0; let pivotAxisWidth = 0; @@ -212,7 +218,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do let x = 0; const sortedPivotKeys = pivotNumbers ? Array.from(pivotColumnGroups.keys()).sort((n1: FieldResult, n2: FieldResult) => toNumber(n1)! - toNumber(n2)!) : Array.from(pivotColumnGroups.keys()).sort(); sortedPivotKeys.forEach(key => { - const val = pivotColumnGroups.get(key)!; + const val = pivotColumnGroups.get(key); let y = 0; let xCount = 0; const text = toLabel(key); @@ -222,11 +228,11 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do x, y: pivotAxisWidth, width: pivotAxisWidth * expander * numCols, - height: max_text, + height: maxText, fontSize, payload: val, }); - val.docs.forEach((doc, i) => { + val?.docs.forEach((doc, i) => { const layoutDoc = Doc.Layout(doc); let wid = pivotAxisWidth; let hgt = pivotAxisWidth / (Doc.NativeAspect(layoutDoc) || 1); @@ -262,14 +268,11 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do payload: pivotColumnGroups.get(key)!.filters, })); groupNames.push(...dividers); - return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []); -} - -function toNumber(val: FieldResult<FieldType>) { - return val === undefined ? undefined : NumCast(val, Number(StrCast(val))); + // eslint-disable-next-line no-use-before-define + return normalizeResults(panelDim, maxText, docMap, poolData, viewDefsToJSX, groupNames, 0, []); } -export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps?: any) { +export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] /* , engineProps?: any */) { const fieldKey = 'data'; const pivotDateGroups = new Map<number, Doc[]>(); const docMap = new Map<string, PoolData>(); @@ -340,6 +343,7 @@ export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc: if (!stack && (curTime === undefined || Math.abs(x - (curTime - minTime) * scaling) > pivotAxisWidth)) { groupNames.push({ type: 'text', text: toLabel(key), x: x, y: stack * 25, height: fontHeight, fontSize, payload: undefined }); } + // eslint-disable-next-line no-use-before-define layoutDocsAtTime(keyDocs, key); }); if (sortedKeys.length && curTime !== undefined && curTime > sortedKeys[sortedKeys.length - 1]) { @@ -404,7 +408,7 @@ function normalizeResults( Array.from(docMap.entries()) .filter(ele => ele[1].pair) - .map(ele => { + .forEach(ele => { const newPosRaw = ele[1]; if (newPosRaw) { const newPos: PoolData = { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index a70713429..d9c988d54 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,3 +1,4 @@ +/* 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'; @@ -22,7 +23,6 @@ import { TraceMobx } from '../../../../fields/util'; import { Gestures, PointData } from '../../../../pen-gestures/GestureTypes'; import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; import { aggregateBounds, emptyFunction, intersectRect, Utils } from '../../../../Utils'; -import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; @@ -48,6 +48,7 @@ import { DocumentView, OpenWhere } from '../../nodes/DocumentView'; import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { PresBox } from '../../nodes/trails/PresBox'; +// eslint-disable-next-line import/extensions import { CreateImage } from '../../nodes/WebBoxRenderer'; import { StyleProp } from '../../StyleProvider'; import { CollectionSubView } from '../CollectionSubView'; @@ -76,7 +77,7 @@ export interface collectionFreeformViewProps { @observer export class CollectionFreeFormView extends CollectionSubView<Partial<collectionFreeformViewProps>>() { public get displayName() { - return 'CollectionFreeFormView(' + this.Document.title?.toString() + ')'; + return 'CollectionFreeFormView(' + (this.Document.title?.toString() ?? '') + ')'; } // this makes mobx trace() statements more descriptive @observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, ''); @@ -94,8 +95,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection super(props); makeObservable(this); } - @observable - public static ShowPresPaths = false; private _panZoomTransitionTimer: any; private _lastX: number = 0; @@ -238,7 +237,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } }; @observable _keyframeEditing = false; - @action setKeyFrameEditing = (set: boolean) => (this._keyframeEditing = set); + @action setKeyFrameEditing = (set: boolean) => { + this._keyframeEditing = set; + }; getKeyFrameEditing = () => this._keyframeEditing; onBrowseClickHandler = () => this._props.onBrowseClickScript?.() || ScriptCast(this.layoutDoc.onBrowseClick); onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick); @@ -256,7 +257,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1)); panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1)); - zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); //, NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1)); + zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); // , NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1)); PanZoomCenterXf = () => (this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.centeringShiftX}px, ${this.centeringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`); ScreenToContentsXf = () => this.screenToFreeformContentsXf.copy(); getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); @@ -272,7 +273,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection addDocument = (newBox: Doc | Doc[]) => { let retVal = false; if (newBox instanceof Doc) { - if ((retVal = this._props.addDocument?.(newBox) || false)) { + retVal = this._props.addDocument?.(newBox) || false; + if (retVal) { this.bringToFront(newBox); this.updateCluster(newBox); } @@ -282,15 +284,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } if (retVal) { const newBoxes = newBox instanceof Doc ? [newBox] : newBox; - for (const newBox of newBoxes) { - if (newBox.activeFrame !== undefined) { - const vals = CollectionFreeFormDocumentView.animFields.map(field => newBox[field.key]); - CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[`${field.key}_indexed`]); - CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[field.key]); - delete newBox.activeFrame; - CollectionFreeFormDocumentView.animFields.forEach((field, i) => field.key !== 'opacity' && (newBox[field.key] = vals[i])); + newBoxes.forEach(box => { + if (box.activeFrame !== undefined) { + const vals = CollectionFreeFormDocumentView.animFields.map(field => box[field.key]); + CollectionFreeFormDocumentView.animFields.forEach(field => delete box[`${field.key}_indexed`]); + CollectionFreeFormDocumentView.animFields.forEach(field => delete box[field.key]); + delete box.activeFrame; + CollectionFreeFormDocumentView.animFields.forEach((field, i) => { + field.key !== 'opacity' && (box[field.key] = vals[i]); + }); } - } + }); if (this.Document._currentFrame !== undefined && !this._props.isAnnotationOverlay) { CollectionFreeFormDocumentView.setupKeyframes(newBoxes, NumCast(this.Document._currentFrame), true); } @@ -313,14 +317,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; focus = (anchor: Doc, options: FocusViewOptions) => { - if (this._lightboxDoc) return; + if (this._lightboxDoc) return undefined; if (anchor === this.Document) { // if (options.willZoomCentered && options.zoomScale) { // this.fitContentOnce(); // options.didMove = true; // } } - if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor) && !this.childLayoutPairs.map(pair => pair.layout).includes(anchor)) return; + // prettier-ignore + if (anchor.type !== DocumentType.CONFIG && + !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor) && // + !this.childLayoutPairs.map(pair => pair.layout).includes(anchor)) { + return undefined; + } 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) && !LightboxView.LightboxDoc); @@ -336,12 +345,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.setPan(panX, panY, focusTime, true); // 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; } + return undefined; }; getView = async (doc: Doc, options: FocusViewOptions): Promise<Opt<DocumentView>> => new Promise<Opt<DocumentView>>(res => { if (doc.hidden && this._lightboxDoc !== doc) options.didMove = !(doc.hidden = false); - if (doc === this.Document) return res(this.DocumentView?.()); + if (doc === this.Document) { + res(this.DocumentView?.()); + return; + } const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv)); findDoc(dv => res(dv)); }); @@ -357,7 +370,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection .map(pair => pair.layout) .slice() .sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); - zsorted.forEach((doc, index) => (doc.zIndex = doc.stroke_isInkMask ? 5000 : index + 1)); + zsorted.forEach((doc, index) => { + doc.zIndex = doc.stroke_isInkMask ? 5000 : index + 1; + }); const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000)); const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)]; @@ -410,7 +425,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection let added = false; // do nothing if link is dropped into any freeform view parent of dragged document const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x, y, title: 'dropped annotation' }); - added = this._props.addDocument?.(source) ? true : false; + added = !!this._props.addDocument?.(source); de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed if (de.complete.linkDocument) { de.complete.linkDocument.layout_isSvg = true; @@ -425,8 +440,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection onInternalDrop = (e: Event, de: DragManager.DropEvent) => { if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de, de.complete.annoDragData); - else if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData); - else if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData); + 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); return false; }; @@ -487,8 +502,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } @action - updateClusters(_freeform_useClusters: boolean) { - this.Document._freeform_useClusters = _freeform_useClusters; + updateClusters(useClusters: boolean) { + this.Document._freeform_useClusters = useClusters; this._clusterSets.length = 0; this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c)); } @@ -498,12 +513,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const childLayouts = this.childLayoutPairs.map(pair => pair.layout); if (this.Document._freeform_useClusters) { const docFirst = docs[0]; - docs.map(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1))); + docs.forEach(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1))); const preferredInd = NumCast(docFirst.layout_cluster); - docs.map(doc => (doc.layout_cluster = -1)); + docs.forEach(doc => { + doc.layout_cluster = -1; + }); docs.map(doc => this._clusterSets.map((set, i) => - set.map(member => { + set.forEach(member => { if (docFirst.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && CollectionFreeFormView.overlapping(doc, member, this._clusterDistance)) { docFirst.layout_cluster = i; } @@ -518,19 +535,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ) { docFirst.layout_cluster = preferredInd; } - this._clusterSets.map((set, i) => { + this._clusterSets.forEach((set, i) => { if (docFirst.layout_cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) { docFirst.layout_cluster = i; } }); if (docFirst.layout_cluster === -1) { - docs.map(doc => { + docs.forEach(doc => { doc.layout_cluster = this._clusterSets.length; this._clusterSets.push([doc]); }); } else if (this._clusterSets.length) { for (let i = this._clusterSets.length; i <= NumCast(docFirst.layout_cluster); i++) !this._clusterSets[i] && this._clusterSets.push([]); - docs.map(doc => this._clusterSets[(doc.layout_cluster = NumCast(docFirst.layout_cluster))].push(doc)); + docs.forEach(doc => { + this._clusterSets[(doc.layout_cluster = NumCast(docFirst.layout_cluster))].push(doc); + }); } childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.layout_cluster === i) && this.updateCluster(child)); } @@ -573,17 +592,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (doc && this.childDocList?.includes(doc)) switch (property.split(':')[0]) { case StyleProp.BackgroundColor: - const cluster = NumCast(doc?.layout_cluster); - if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) { - if (this._clusterSets.length <= cluster) { - setTimeout(() => doc && this.updateCluster(doc)); - } else { - // choose a cluster color from a palette - const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)']; - styleProp = colors[cluster % colors.length]; - const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor); - // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document - set?.map(s => (styleProp = StrCast(s.backgroundColor))); + { + const cluster = NumCast(doc?.layout_cluster); + if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) { + if (this._clusterSets.length <= cluster) { + setTimeout(() => doc && this.updateCluster(doc)); + } else { + // choose a cluster color from a palette + const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)']; + styleProp = colors[cluster % colors.length]; + const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor); + // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document + set?.forEach(s => { + styleProp = StrCast(s.backgroundColor); + }); + } } } break; @@ -591,6 +614,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (doc && this.Document._currentFrame !== undefined) { return CollectionFreeFormDocumentView.getStringValues(doc, NumCast(this.Document._currentFrame))?.fillColor; } + break; + default: } return styleProp; }; @@ -626,9 +651,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection case InkTool.None: if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) { this._hitCluster = this.pickCluster(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY)); - setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false, false); + setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1, false); } break; + default: } } } @@ -639,13 +665,34 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @undoBatch onGesture = (e: Event, ge: GestureUtils.GestureEvent) => { switch (ge.gesture) { - default: + // case Gestures.Rectangle: + // { + // const strokes = this.getActiveDocuments() + // .filter(doc => doc.type === DocumentType.INK) + // .map(i => { + // const d = Cast(i.stroke, InkField); + // const x = NumCast(i.x) - Math.min(...(d?.inkData.map(pd => pd.X) ?? [0])); + // const y = NumCast(i.y) - Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0])); + // return !d ? [] : d.inkData.map(pd => ({ X: x + pd.X, Y: y + pd.Y })); + // }); + + // CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => {}); + // } + // break; + case Gestures.Text: + if (ge.text) { + const B = this.screenToFreeformContentsXf.transformPoint(ge.points[0].X, ge.points[0].Y); + this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] })); + e.stopPropagation(); + } + break; case Gestures.Line: case Gestures.Circle: case Gestures.Rectangle: case Gestures.Triangle: case Gestures.Stroke: - const points = ge.points; + default: { + const { points } = ge; const B = this.screenToFreeformContentsXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height); const inkWidth = ActiveInkWidth() * this.ScreenToLocalBoxXf().Scale; const inkDoc = Docs.Create.InkDocument( @@ -664,29 +711,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } this.addDocument(inkDoc); e.stopPropagation(); - break; - case Gestures.Rectangle: - const strokes = this.getActiveDocuments() - .filter(doc => doc.type === DocumentType.INK) - .map(i => { - const d = Cast(i.stroke, InkField); - const x = NumCast(i.x) - Math.min(...(d?.inkData.map(pd => pd.X) ?? [0])); - const y = NumCast(i.y) - Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0])); - return !d ? [] : d.inkData.map(pd => ({ X: x + pd.X, Y: y + pd.Y })); - }); - - CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => {}); - break; - case Gestures.Text: - if (ge.text) { - const B = this.screenToFreeformContentsXf.transformPoint(ge.points[0].X, ge.points[0].Y); - this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] })); - e.stopPropagation(); - } + } } }; @action - onEraserUp = (e: PointerEvent): void => { + onEraserUp = (): void => { this._deleteList.forEach(ink => ink._props.removeDocument?.(ink.Document)); this._deleteList = []; this._batch?.end(); @@ -792,7 +821,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return this.childDocs .map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())) .filter(inkView => inkView?.ComponentView instanceof InkingStroke) - .map(inkView => ({ inkViewBounds: inkView!.getBounds, inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! })) + .map(inkView => inkView!) + .map(inkView => ({ inkViewBounds: inkView.getBounds, inkStroke: inkView.ComponentView as InkingStroke, inkView })) .filter( ({ inkViewBounds }) => inkViewBounds && // bounding box of eraser segment and ink stroke overlap @@ -807,7 +837,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection // Convert from screen space to ink space for the intersection. const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint); const currPointInkSpace = inkStroke.ptFromScreen(currPoint); - for (var i = 0; i < inkData.length - 3; i += 4) { + for (let i = 0; i < inkData.length - 3; i += 4) { const rawIntersects = InkField.Segment(inkData, i).intersects({ // compute all unique intersections p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y }, @@ -833,16 +863,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action segmentInkStroke = (ink: DocumentView, excludeT: number): Segment[] => { const segments: Segment[] = []; - var segment: Segment = []; - var startSegmentT = 0; + let segment: Segment = []; + let startSegmentT = 0; const { inkData } = (ink?.ComponentView as InkingStroke).inkScaledData(); // This iterates through all segments of the curve and splits them where they intersect another curve. // if 'excludeT' is specified, then any segment containing excludeT will be skipped (ie, deleted) - for (var i = 0; i < inkData.length - 3; i += 4) { + for (let i = 0; i < inkData.length - 3; i += 4) { const inkSegment = InkField.Segment(inkData, i); // Getting all t-value intersections of the current curve with all other curves. const tVals = this.getInkIntersections(i, ink, inkSegment).sort(); if (tVals.length) { + // eslint-disable-next-line no-loop-func tVals.forEach((t, index) => { const docCurveTVal = t + Math.floor(i / 4); if (excludeT < startSegmentT || excludeT > docCurveTVal) { @@ -895,9 +926,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] }; const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point)); const otherCtrlPts = otherScreenPts.map(spt => (ink.ComponentView as InkingStroke).ptFromScreen(spt)); - for (var j = 0; j < otherCtrlPts.length - 3; j += 4) { + for (let j = 0; j < otherCtrlPts.length - 3; j += 4) { const neighboringSegment = i === j || i === j - 4 || i === j + 4; // Ensuring that the curve intersected by the eraser is not checked for further ink intersections. + // eslint-disable-next-line no-continue if (ink?.Document === otherInk.Document && neighboringSegment) continue; const otherCurve = new Bezier(otherCtrlPts.slice(j, j + 4).map(p => ({ x: p.X, y: p.Y }))); @@ -908,7 +940,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (apt.d !== undefined && apt.d < 1 && apt.t !== undefined && !tVals.includes(apt.t)) { tVals.push(apt.t); } - this.bintersects(curve, otherCurve).forEach((val: string | number, i: number) => { + this.bintersects(curve, otherCurve).forEach((val: string | number /* , i: number */) => { // Converting the Bezier.js Split type to a t-value number. const t = +val.toString().split('/')[0]; if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). @@ -971,8 +1003,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.scrollPan({ deltaX: -deltaX * this.screenToFreeformContentsXf.Scale, deltaY: e.shiftKey ? 0 : -deltaY * this.screenToFreeformContentsXf.Scale }); break; } - default: + // eslint-disable-next-line no-fallthrough case freeformScrollMode.Zoom: + default: if ((e.ctrlKey || !scrollable) && this._props.isContentActive()) { this.zoom(e.clientX, e.clientY, Math.max(-1, Math.min(1, e.deltaY))); // if (!this._props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc? // e.preventDefault(); @@ -982,7 +1015,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; @action - setPan(panX: number, panY: number, panTime: number = 0, clamp: boolean = false) { + setPan(panXIn: number, panYIn: number, panTime: number = 0, clamp: boolean = false) { + let panX = panXIn; + let panY = panYIn; // this is the easiest way to do this -> will talk with Bob about using mobx to do this to remove this line of code. if (Doc.UserDoc()?.presentationMode === 'watching') ReplayMovements.Instance.pauseFromInteraction(); @@ -1034,11 +1069,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection scale * NumCast(this.dataDoc._panY_max, nativeHeight) + (!this._props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning let newPanY = Math.max(minPanY, Math.min(maxPanY, panY)); - if (false && NumCast(this.layoutDoc.layout_scrollTop) && NumCast(this.layoutDoc._freeform_scale, minScale) !== minScale) { - } else if (fitYscroll > 2 && this.layoutDoc.layout_scrollTop === undefined && NumCast(this.layoutDoc._freeform_scale, minScale) === minScale) { - const maxPanY = minPanY + fitYscroll; - const relTop = (panY - minPanY) / (maxPanY - minPanY); - setTimeout(() => (this.layoutDoc.layout_scrollTop = relTop * maxScrollTop), 10); + if (fitYscroll > 2 && this.layoutDoc.layout_scrollTop === undefined && NumCast(this.layoutDoc._freeform_scale, minScale) === minScale) { + const maxPanScrollY = minPanY + fitYscroll; + const relTop = (panY - minPanY) / (maxPanScrollY - minPanY); + setTimeout(() => { + this.layoutDoc.layout_scrollTop = relTop * maxScrollTop; + }, 10); newPanY = minPanY; } !this.Document._verticalScroll && (this.Document[this.panXFieldKey] = this.isAnnotationOverlay ? newPanX : panX); @@ -1090,7 +1126,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this._panZoomTransition = transitionTime; this._panZoomTransitionTimer && clearTimeout(this._panZoomTransitionTimer); this._panZoomTransitionTimer = setTimeout( - action(() => (this._panZoomTransition = 0)), + action(() => { + this._panZoomTransition = 0; + }), transitionTime ); }; @@ -1173,6 +1211,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection e.stopPropagation?.(); return this.createTextDocCopy(fieldProps, !e.altKey && e.key !== 'Tab'); } + return undefined; }; @computed get childPointerEvents() { const engine = this._props.layoutEngine?.() || StrCast(this.Document._layoutEngine); @@ -1194,6 +1233,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const childData = entry.pair.data; return ( <CollectionFreeFormDocumentView + // eslint-disable-next-line react/jsx-props-no-spreading {...OmitKeys(entry, ['replica', 'pair']).omit} key={childLayout[Id] + (entry.replica || '')} Document={childLayout} @@ -1205,7 +1245,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection isGroupActive={this._props.isGroupActive} renderDepth={this._props.renderDepth + 1} hideDecorations={BoolCast(childLayout._layout_isSvg && childLayout.type === DocumentType.LINK)} - suppressSetHeight={this.layoutEngine ? true : false} + suppressSetHeight={!!this.layoutEngine} RenderCutoffProvider={this.renderCutoffProvider} CollectionFreeFormView={this} LayoutTemplate={childLayout.z ? undefined : this._props.childLayoutTemplate} @@ -1239,37 +1279,42 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection /> ); } - addDocTab = action((doc: Doc, where: OpenWhere) => { - if (this._props.isAnnotationOverlay) return this._props.addDocTab(doc, where); + addDocTab = action((docsIn: Doc | Doc[], where: OpenWhere) => { + const docs = docsIn instanceof Doc ? [docsIn] : docsIn; + if (this._props.isAnnotationOverlay) return this._props.addDocTab(docs, where); switch (where) { case OpenWhere.inParent: - return this._props.addDocument?.(doc) || false; - case OpenWhere.inParentFromScreen: - const docContext = DocCast((doc instanceof Doc ? doc : doc?.[0])?.embedContainer); + return this._props.addDocument?.(docs) || false; + case OpenWhere.inParentFromScreen: { + const docContext = DocCast(docs[0]?.embedContainer); return ( (this.addDocument?.( - (doc instanceof Doc ? [doc] : doc).map(doc => { - const pt = this.screenToFreeformContentsXf.transformPoint(NumCast(doc.x), NumCast(doc.y)); - doc.x = pt[0]; - doc.y = pt[1]; + (docs instanceof Doc ? [docs] : docs).map(doc => { + [doc.x, doc.y] = this.screenToFreeformContentsXf.transformPoint(NumCast(doc.x), NumCast(doc.y)); return doc; }) ) && (!docContext || this._props.removeDocument?.(docContext))) || false ); + } case undefined: case OpenWhere.lightbox: - if (this.layoutDoc._isLightbox) { - this._lightboxDoc = doc; - return true; - } - if (doc === this.Document || this.childDocList?.includes(doc) || this.childLayoutPairs.map(pair => pair.layout)?.includes(doc)) { - if (doc.hidden) doc.hidden = false; - return true; + { + const firstDoc = docs[0]; + if (this.layoutDoc._isLightbox) { + this._lightboxDoc = firstDoc; + return true; + } + if (firstDoc === this.Document || this.childDocList?.includes(firstDoc) || this.childLayoutPairs.map(pair => pair.layout)?.includes(firstDoc)) { + if (firstDoc.hidden) firstDoc.hidden = false; + return true; + } } + break; + default: } - return this._props.addDocTab(doc, where); + return this._props.addDocTab(docs, where); }); @observable _lightboxDoc: Opt<Doc> = undefined; @@ -1314,9 +1359,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection e.stopPropagation(); }; - viewDefsToJSX = (views: ViewDefBounds[]) => { - return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!); - }; + viewDefsToJSX = (views: ViewDefBounds[]) => (!Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!)); viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> { const { x, y, z } = viewDef; @@ -1337,7 +1380,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ), bounds: viewDef, }; - } else if (viewDef.type === 'div') { + } + if (viewDef.type === 'div') { return [x, y].some(val => val === undefined) ? undefined : { @@ -1353,9 +1397,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection bounds: viewDef, }; } + return undefined; } renderCutoffProvider = computedFn( + // eslint-disable-next-line prefer-arrow-callback function renderCutoffProvider(this: any, doc: Doc) { return this.Document.isTemplateDoc ? false : !this._renderCutoffData.get(doc[Id] + ''); }.bind(this) @@ -1369,7 +1415,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } doFreeformLayout(poolData: Map<string, PoolData>) { - this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => poolData.set(pair.layout[Id], this.getCalculatedPositions(pair))); + this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => poolData.set(pair.layout[Id], this.getCalculatedPositions(pair))); return [] as ViewDefResult[]; } @@ -1385,6 +1431,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection case computeTimelineLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) }; case computePivotLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) }; case computeStarburstLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeStarburstLayout) }; + default: } return { newPool, computedElementData: this.doFreeformLayout(newPool) }; } @@ -1469,7 +1516,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this._disposers.pointerevents = reaction( () => this.childPointerEvents, - pointerevents => (this._childPointerEvents = pointerevents as any), + pointerevents => { + this._childPointerEvents = pointerevents as any; + }, { fireImmediately: true } ); @@ -1643,7 +1692,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } }; - onContextMenu = (e: React.MouseEvent) => { + onContextMenu = () => { if (this._props.isAnnotationOverlay || !ContextMenu.Instance) return; const appearance = ContextMenu.Instance.findByDescription('Appearance...'); @@ -1654,7 +1703,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' }); return; } - !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' }); + !Doc.noviceMode && + Doc.UserDoc().defaultTextLayout && + appearanceItems.push({ + description: 'Reset default note style', + event: () => { + Doc.UserDoc().defaultTextLayout = undefined; + }, + icon: 'eye', + }); appearanceItems.push({ description: `Pin View`, event: () => this._props.pinToPres(this.Document, { pinViewport: MarqueeView.CurViewBounds(this.dataDoc, this._props.PanelWidth(), this._props.PanelHeight()) }), icon: 'map-pin' }); !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' }); this._props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' }); @@ -1669,8 +1726,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const optionItems = options && 'subitems' in options ? options.subitems : []; !this._props.isAnnotationOverlay && !Doc.noviceMode && - optionItems.push({ description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline', event: action(() => (this._showAnimTimeline = !this._showAnimTimeline)), icon: 'eye' }); - this._props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null).backgroundColor = StrCast(this.layoutDoc.backgroundColor)), icon: 'palette' }); + optionItems.push({ + description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline', + event: action(() => { + this._showAnimTimeline = !this._showAnimTimeline; + }), + icon: 'eye', + }); + this._props.renderDepth && + optionItems.push({ + description: 'Use Background Color as Default', + event: () => { + Cast(Doc.UserDoc().emptyCollection, Doc, null).backgroundColor = StrCast(this.layoutDoc.backgroundColor); + }, + icon: 'palette', + }); this._props.renderDepth && optionItems.push({ description: 'Fit Content Once', event: this.fitContentOnce, icon: 'object-group' }); if (!Doc.noviceMode) { optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? 'Freeze' : 'Unfreeze') + ' Aspect', event: this.toggleNativeDimensions, icon: 'snowflake' }); @@ -1750,7 +1820,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ); } - showPresPaths = () => CollectionFreeFormView.ShowPresPaths; + showPresPaths = () => SnappingManager.ShowPresPaths; brushedView = () => this._brushedView; gridColor = () => DashColor(lightOrDark(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor))) @@ -1908,7 +1978,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ) : ( <> {this._firstRender ? this.placeholder : this.marqueeView} - {this._props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />} + { + // eslint-disable-next-line no-use-before-define + this._props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} /> + } {!this.GroupChildDrag ? null : <div className="collectionFreeForm-groupDropper" />} </> )} @@ -1920,7 +1993,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @observer class CollectionFreeFormOverlayView extends React.Component<{ elements: () => ViewDefResult[] }> { render() { - // eslint-disable-next-line react/destructuring-assignment return this.props.elements().filter(ele => ele.bounds?.z).map(ele => ele.ele); // prettier-ignore } } @@ -1959,6 +2031,7 @@ ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) { const selView = SelectionManager.Views; if (readOnly) return selView[0].ComponentView?.getKeyFrameEditing?.() ? Colors.MEDIUM_BLUE : 'transparent'; runInAction(() => selView[0].ComponentView?.setKeyFrameEditing?.(!selView[0].ComponentView?.getKeyFrameEditing?.())); + return undefined; }); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function pinWithView(pinContent: boolean) { @@ -1989,7 +2062,7 @@ ScriptingGlobals.add(function datavizFromSchema() { if (!view.layoutDoc.schema_columnKeys) { view.layoutDoc.schema_columnKeys = new List<string>(['title', 'type', 'author', 'author_date']); } - const keys = Cast(view.layoutDoc.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text'); + const keys = Cast(view.layoutDoc.schema_columnKeys, listSpec('string'))?.filter(key => key !== 'text'); if (!keys) return; const children = DocListCast(view.Document[Doc.LayoutFieldKey(view.Document)]); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b03e435ce..2f9cc49e0 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -196,7 +196,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps const eachCell = ns.join('\t').split('\t'); let eachRow = []; for (let i = 1; i < eachCell.length; i++) { - eachRow.push(eachCell[i].replace(/\,/g, '')); + eachRow.push(eachCell[i].replace(/,/g, '')); if (i % headers.length === 0) { csvRows.push(eachRow); eachRow = []; @@ -377,7 +377,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps }); @undoBatch - pileup = action((e: KeyboardEvent | React.PointerEvent | undefined) => { + pileup = action(() => { const selected = this.marqueeSelect(false); SelectionManager.DeselectAll(); selected.forEach(d => this._props.removeDocument?.(d)); @@ -485,7 +485,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps }); @undoBatch - summary = action((e: KeyboardEvent | React.PointerEvent | undefined) => { + summary = action(() => { const selected = this.marqueeSelect(false).map(d => { this._props.removeDocument?.(d); d.x = NumCast(d.x) - this.Bounds.left; @@ -530,8 +530,8 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps (e as any).propagationIsStopped = true; if (e.key === 'g') this.collection(e, true); if (e.key === 'c' || e.key === 't') this.collection(e); - if (e.key === 's' || e.key === 'S') this.summary(e); - if (e.key === 'p') this.pileup(e); + if (e.key === 's' || e.key === 'S') this.summary(); + if (e.key === 'p') this.pileup(); this.cleanupInteractions(false); } if (e.key === 'r' || e.key === ' ') { diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 08eb35586..ee30006ae 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -1,3 +1,6 @@ +/* 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'; import { Popup, Size, Type } from 'browndash-components'; import { action, computed, makeObservable, observable } from 'mobx'; @@ -26,7 +29,7 @@ import { EditableView } from '../../EditableView'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DefaultStyleProvider } from '../../StyleProvider'; import { Colors } from '../../global/globalEnums'; -import { OpenWhere, returnEmptyDocViewList } from '../../nodes/DocumentView'; +import { returnEmptyDocViewList } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import { KeyValueBox } from '../../nodes/KeyValueBox'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; @@ -64,8 +67,8 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro makeObservable(this); } - static addFieldDoc = (doc: Doc, where: OpenWhere) => { - DocFocusOrOpen(doc); + static addFieldDoc = (doc: Doc | Doc[] /* , where: OpenWhere */) => { + DocFocusOrOpen(doc instanceof Doc ? doc : doc[0]); return true; }; public static renderProps(props: SchemaTableCellProps) { @@ -186,7 +189,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro return ( <div className="schema-table-cell" - onPointerDown={action(e => !this.selected && this._props.selectCell(this._props.Document, this._props.col))} + onPointerDown={action(() => !this.selected && this._props.selectCell(this._props.Document, this._props.col))} style={{ padding: this._props.padding, maxWidth: this._props.maxWidth?.(), width: this._props.columnWidth() || undefined, border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}> {this.content} </div> @@ -207,7 +210,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro choosePath(url: URL) { if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href if (url.href.indexOf(window.location.origin) === -1) return ClientUtils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver - if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; //Why is this here — good question + if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; // Why is this here — good question const ext = extname(url.href); return url.href.replace(ext, '_s' + ext); @@ -246,7 +249,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro }; @action - removeHoverPreview = (e: React.PointerEvent) => { + removeHoverPreview = () => { if (!this._previewRef) return; document.body.removeChild(this._previewRef); }; @@ -258,7 +261,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro const height = this._props.rowHeight() ? this._props.rowHeight() - (this._props.padding || 6) * 2 : undefined; const width = height ? height * aspect : undefined; // increase the width of the image if necessary to maintain proportionality - return <img src={this.url} width={width ? width : undefined} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />; + return <img src={this.url} width={width || undefined} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />; } } @@ -282,15 +285,15 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp // } else { // ^ DateCast is always undefined for some reason, but that is what the field should be set to date && (this._props.Document[this._props.fieldKey] = new DateField(date)); - //} + // } }, 'date change'); render() { - const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props); + const { pointerEvents } = SchemaTableCell.renderProps(this._props); return ( <> <div style={{ pointerEvents: 'none' }}> - <DatePicker dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={e => {}} /> + <DatePicker dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={emptyFunction} /> </div> {pointerEvents === 'none' ? null : ( <Popup @@ -301,7 +304,7 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp background={SettingsManager.userBackgroundColor} popup={ <div style={{ width: 'fit-content', height: '200px' }}> - <DatePicker open={true} dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={this.handleChange} /> + <DatePicker open dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={this.handleChange} /> </div> } /> @@ -329,7 +332,7 @@ export class SchemaRTFCell extends ObservableReactComponent<SchemaTableCellProps fieldProps.isContentActive = this.selectedFunc; return ( <div className="schemaRTFCell" style={{ fontStyle: this.selected ? undefined : 'italic', color, textDecoration, cursor, pointerEvents }}> - {this.selected ? <FormattedTextBox {...fieldProps} autoFocus={true} onBlur={() => this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))} + {this.selected ? <FormattedTextBox {...fieldProps} autoFocus onBlur={() => this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))} </div> ); } @@ -355,8 +358,8 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp checked={BoolCast(this._props.Document[this._props.fieldKey])} onChange={undoBatch((value: React.ChangeEvent<HTMLInputElement> | undefined) => { if ((value?.nativeEvent as any).shiftKey) { - this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString()); - } else KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString()); + this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? '')); + } else KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? '')); })} /> <EditableView @@ -391,7 +394,7 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col; } render() { - const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props); + const { color, textDecoration, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props); const options = this._props.options?.map(facet => ({ value: facet, label: facet })); return ( <div className="schemaSelectionCell" style={{ color, textDecoration, cursor, pointerEvents }}> |
