diff --git a/ivette/.eslintignore b/ivette/.eslintignore index e5fe0a814d6250a91f43a72a3dda9cee662b1b47..5f433108db3c16614b6d657687fc0f552ecad338 100644 --- a/ivette/.eslintignore +++ b/ivette/.eslintignore @@ -10,4 +10,3 @@ lib src/api # lint Dome step by step src/dome/src/renderer/layout -src/dome/src/renderer/table diff --git a/ivette/.eslintrc.js b/ivette/.eslintrc.js index 10db14fc4fdb270ff4b03fd3e7523f6d42b9c924..616c6cfcd375dff57f7bb757cec10f553d5b19a9 100644 --- a/ivette/.eslintrc.js +++ b/ivette/.eslintrc.js @@ -98,5 +98,7 @@ module.exports = { // Checked by TSC compiler "default-case": "off", "consistent-return": "off", + // Allow modify properties of object passed in parameter + "no-param-reassign": [ "error", { "props": false } ], } }; diff --git a/ivette/src/dome/src/renderer/table/arrays.ts b/ivette/src/dome/src/renderer/table/arrays.ts index 705ec7fd0d214947bfa69aca49718097b11a21a6..047546e7269b55cf57f3aaacf6f490fe26f2c1f0 100644 --- a/ivette/src/dome/src/renderer/table/arrays.ts +++ b/ivette/src/dome/src/renderer/table/arrays.ts @@ -10,7 +10,9 @@ import * as Compare from 'dome/data/compare'; import type { ByFields, Order } from 'dome/data/compare'; import { - SortingInfo, Sorting, Filter, Filtering, Model, Collection, forEach + SortingInfo, Sorting, + Filter, Filtering, + Model, Collection, forEach, } from './models'; // -------------------------------------------------------------------------- @@ -23,7 +25,7 @@ interface PACK<Key, Row> { index: number | undefined; key: Key; row: Row; -}; +} type SORT<K, R> = Order<PACK<K, R>>; @@ -76,8 +78,7 @@ export class ArrayModel<Key, Row> private array?: Row[]; // Filtered-out Row Count - private filtered: number = 0; - + private filtered = 0; // Filtering function private filter?: Filter<Key, Row>; @@ -102,7 +103,8 @@ export class ArrayModel<Key, Row> protected sorter(): SORT<Key, Row> { let current = this.order; if (current) return current; - current = this.order = orderByRing(this.natural, this.columns, this.ring); + current = this.order = // eslint-disable-line no-multi-assign + orderByRing(this.natural, this.columns, this.ring); return current; } @@ -111,7 +113,7 @@ export class ArrayModel<Key, Row> const current = this.table; let filtered = 0; if (current !== undefined) return current; - let table: PACK<Key, Row>[] = []; + const table: PACK<Key, Row>[] = []; try { this.index.forEach((packed) => { packed.index = undefined; @@ -125,7 +127,7 @@ export class ArrayModel<Key, Row> } catch (err) { console.warn('[Dome] error when rebuilding table:', err); } - table.forEach((pack, index) => pack.index = index); + table.forEach((pack, index) => { pack.index = index; }); this.table = table; this.filtered = filtered; return table; @@ -191,8 +193,9 @@ export class ArrayModel<Key, Row> setOrderingByFields(byfields: ByFields<Row>) { this.natural = Compare.byFields(byfields); const columns = this.columns ?? {}; - for (let k of Object.keys(byfields)) { - const dataKey = k as (string & keyof Row); + const keys = Object.keys(byfields); + for (let i = 0; i < keys.length; i++) { + const dataKey = keys[i] as (string & keyof Row); const fn = byfields[dataKey]; if (fn) columns[dataKey] = (x: Row, y: Row) => { const dx = x[dataKey]; @@ -211,9 +214,9 @@ export class ArrayModel<Key, Row> Remove the sorting function for the provided column. */ deleteColumnOrder(dataKey: string) { - const columns = this.columns; + const { columns } = this; if (columns) delete columns[dataKey]; - this.ring = this.ring.filter(ord => ord.sortBy !== dataKey); + this.ring = this.ring.filter((ord) => ord.sortBy !== dataKey); this.reload(); } @@ -222,7 +225,7 @@ export class ArrayModel<Key, Row> Use `undefined` or `null` to reset the natural ordering. */ setSorting(ord?: undefined | null | SortingInfo) { if (ord) { - const ring = this.ring; + const { ring } = this; const cur = ring[0]; const fd = ord.sortBy; if ( @@ -235,11 +238,9 @@ export class ArrayModel<Key, Row> this.ring = newRing; this.reload(); } - } else { - if (this.ring.length > 0) { - this.ring = []; - this.reload(); - } + } else if (this.ring.length > 0) { + this.ring = []; + this.reload(); } } @@ -294,11 +295,11 @@ export class ArrayModel<Key, Row> const k = pack.index ?? -1; const n = current ? current.length : 0; const phi = this.filter; - const old_ok = 0 <= k && k < n; - const now_ok = phi ? phi(pack.row, pack.key) : true; - if (old_ok !== now_ok) return true; + const oldOk = 0 <= k && k < n; + const nowOk = phi ? phi(pack.row, pack.key) : true; + if (oldOk !== nowOk) return true; // Case where element was not displayed and will still not be - if (!old_ok) return false; + if (!oldOk) return false; // Detecting if ordering is preserved const order = this.sorter(); const prev = k - 1; @@ -360,11 +361,11 @@ export class ArrayModel<Key, Row> if (row === null) { // Nop return; - } else { - const newPack = { key, row, index: undefined }; - this.index.set(key, newPack); - doReload = this.needReloadForInsert(newPack); } + const newPack = { key, row, index: undefined }; + this.index.set(key, newPack); + doReload = this.needReloadForInsert(newPack); + } if (doReload) this.reload(); } @@ -417,7 +418,8 @@ export class ArrayModel<Key, Row> getArray(): Row[] { let arr = this.array; if (arr === undefined) { - arr = this.array = this.rebuild().map((e) => e.row); + arr = this.array = // eslint-disable-line no-multi-assign + this.rebuild().map((e) => e.row); } return arr; } diff --git a/ivette/src/dome/src/renderer/table/models.ts b/ivette/src/dome/src/renderer/table/models.ts index 0717180890e3c193eb320ce0c90437a30a7d035d..76c516f2950beaa5843e3c07a68e09647a863700 100644 --- a/ivette/src/dome/src/renderer/table/models.ts +++ b/ivette/src/dome/src/renderer/table/models.ts @@ -100,7 +100,8 @@ export function forEach<A>(data: Collection<A>, fn: (elt: A) => void) { individual updates. The model might not hold the entire collection of data at the same time, but - serves as a proxy for fetching data on demand. A model makes a distinction between: + serves as a proxy for fetching data on demand. A model makes a distinction + between: - `Key`: a key identifies a given entry in the table at any time; - `Row`: the row data associated to some `Key` at a given time; @@ -115,10 +116,13 @@ export function forEach<A>(data: Collection<A>, fn: (elt: A) => void) { When your data change over time, you shall invoke the following methods of the model to keep views in sync: - - [[update]] or [[updateIndex]] when single or contiguous row data changes over time; - - [[reload]] when the number of rows, their ordering, or (many) row data has been changed. + - [[update]] or [[updateIndex]] when single or contiguous row data + changes over time; + - [[reload]] when the number of rows, their ordering, or (many) row data + has been changed. - It is always safe to use `reload` instead of `update` although it might be less performant. + It is always safe to use `reload` instead of `update` although it might be + less performant. @template Key - identification of some entry @template Row - dynamic row data associated to some key @@ -139,16 +143,16 @@ export abstract class Model<Key, Row> { abstract getRowCount(): number; /** - Shall return the current row data at a given index in the table, with respect to - current filtering and ordering (if any). - Might return `undefined` if the index is invalid or not (yet) available. + Shall return the current row data at a given index in the table, with + respect to current filtering and ordering (if any). Might return + `undefined` if the index is invalid or not (yet) available. */ abstract getRowAt(index: number): undefined | Row; /** - Shall return the key at the given index. The specified index and data - are those of the last corresponding call to [[getRowAt]]. - Might return `undefined` if the index is invalid. + Shall return the key at the given index. The specified index and data are + those of the last corresponding call to [[getRowAt]]. Might return + `undefined` if the index is invalid. */ abstract getKeyAt(index: number): undefined | Key; @@ -160,11 +164,12 @@ export abstract class Model<Key, Row> { abstract getKeyFor(index: number, data: Row): undefined | Key; /** - Shall return the index of a given entry in the table, identified by its key, with - respect to current filtering and ordering (if any). - Shall return `undefined` if the specified key no longer belong to the table or - when it is currently filtered out. - Out-of-range indices would be treated as `undefined`. + Shall return the index of a given entry in the table, identified by its + key, with respect to current filtering and ordering (if any). + Shall return + `undefined` if the specified key no longer belong to the table or when it + is currently filtered out. Out-of-range indices would be treated as + `undefined`. */ abstract getIndexOf(key: Key): undefined | number; @@ -205,8 +210,10 @@ export abstract class Model<Key, Row> { The initial watching range is empty with no trigger. You normally never call this method directly. It is automatically called by table views. - @param onReload - optional callback for reloads (and updates, unless specified) - @param onUpdate - optional callback for updates (when different from reloads) + @param onReload - optional callback for reloads + (and updates, unless specified) + @param onUpdate - optional callback for updates + (when different from reloads) */ link(onReload?: Trigger, onUpdate?: Trigger): Client { const id = this.clientsId++; @@ -222,7 +229,7 @@ export abstract class Model<Key, Row> { watch(lower: number, upper: number) { w.lower = lower; w.upper = upper; - } + }, }; m.set(id, w); return w; diff --git a/ivette/src/dome/src/renderer/table/views.tsx b/ivette/src/dome/src/renderer/table/views.tsx index 5e7888b7c89035af4b1b8de6cfa738f47a075fdd..561b57057baf0c16666a84a8c56cbae2370f4403 100644 --- a/ivette/src/dome/src/renderer/table/views.tsx +++ b/ivette/src/dome/src/renderer/table/views.tsx @@ -30,7 +30,7 @@ import { Trigger, Client, Sorting, SortingInfo, Model } from './models'; import './style.css'; -const SVG = SVGraw as (props: { id: string, size?: number }) => JSX.Element; +const SVG = SVGraw as (props: { id: string; size?: number }) => JSX.Element; // -------------------------------------------------------------------------- // --- Rendering Interfaces @@ -56,7 +56,7 @@ export type RenderByFields<Row> = { You may use hierarchical index to order columns. See [[ColumnGroup]]. */ -export type index = number | number[] +export type index = number | number[]; /** @template Row - table row data of some table entries @@ -158,7 +158,7 @@ interface ColumnData { title?: string; headerMenu: () => void; headerRef: divRef; -}; +} interface PopupItem { label: string; @@ -166,11 +166,11 @@ interface PopupItem { enabled?: boolean; display?: boolean; onClick?: Trigger; -}; +} type PopupMenu = ('separator' | PopupItem)[]; -type Cmap<A> = Map<string, A> +type Cmap<A> = Map<string, A>; type Cprops = ColProps<any>; type ColProps<R> = ColumnProps<R, any>; @@ -195,13 +195,13 @@ const defaultGetter = (row: any, dataKey: string) => { const defaultRenderer = (d: any) => ( <div className="dome-xTable-renderer dome-text-label"> - {new String(d)} + {String(d)} </div> ); function makeRowGetter<Key, Row>(model?: Model<Key, Row>) { return ({ index }: Index) => model && model.getRowAt(index); -}; +} function makeDataGetter( getter: ((row: any, dataKey: string) => any) = defaultGetter, @@ -226,8 +226,8 @@ function makeDataRenderer( render: ((data: any) => ReactNode) = defaultRenderer, onContextMenu?: (row: any, index: number, dataKey: string) => void, ): TableCellRenderer { - return (props => { - const cellData = props.cellData; + return ((props) => { + const { cellData } = props; try { const contents = cellData ? render(cellData) : null; if (onContextMenu) { @@ -259,7 +259,7 @@ type ColSettings<A> = { [id: string]: undefined | null | A }; type TableSettings = { resize?: ColSettings<number>; visible?: ColSettings<boolean>; -} +}; // -------------------------------------------------------------------------- // --- Table State @@ -373,7 +373,7 @@ class TableState<Key, Row> { const wl = cwl ? cwl + offset : 0; const wr = cwr ? cwr - offset : 0; if (wl > 40 && wr > 40) { - const resize = this.resize; + const { resize } = this; resize.set(lcol, wl); resize.set(rcol, wr); this.offset = offset; @@ -394,8 +394,8 @@ class TableState<Key, Row> { if (userSettings) { const cws: ColSettings<number> = {}; const cvs: ColSettings<boolean> = {}; - const resize = this.resize; - const visible = this.visible; + const { resize } = this; + const { visible } = this; this.columns.forEach(({ id }) => { const cw = resize.get(id); const cv = visible.get(id); @@ -411,8 +411,8 @@ class TableState<Key, Row> { importSettings(settings?: string) { if (settings !== this.settings) { this.settings = settings; - const resize = this.resize; - const visible = this.visible; + const { resize } = this; + const { visible } = this; resize.clear(); visible.clear(); const theSettings: undefined | TableSettings = @@ -471,11 +471,12 @@ class TableState<Key, Row> { onSelection?: (data: Row, key: Key, index: number) => void; onRowClick(info: RowMouseEventHandlerParams) { - const index = info.index; + const { index } = info; const data = info.rowData as (Row | undefined); - const model = this.model; - const key = (data !== undefined) ? model?.getKeyFor(index, data) : undefined; - const onSelection = this.onSelection; + const { model } = this; + const key = + (data !== undefined) ? model?.getKeyFor(index, data) : undefined; + const { onSelection } = this; if (key !== undefined && data !== undefined && onSelection) onSelection(data, key, index); } @@ -487,11 +488,11 @@ class TableState<Key, Row> { rowClassName({ index }: Index): string { if (this.selectedIndex === index) return 'dome-xTable-selected'; - return (index & 1 ? 'dome-xTable-even' : 'dome-xTable-odd'); + return (index & 1 ? 'dome-xTable-even' : 'dome-xTable-odd'); // eslint-disable-line no-bitwise } keyStepper(index: number) { - const onSelection = this.onSelection; + const { onSelection } = this; const key = this.model?.getKeyAt(index); const data = this.model?.getRowAt(index); if (key !== undefined && data !== undefined && onSelection) { @@ -510,7 +511,7 @@ class TableState<Key, Row> { } onSorting(ord?: SortingInfo) { - const sorting = this.sorting; + const { sorting } = this; if (sorting) { sorting.setSorting(ord); this.sortBy = ord?.sortBy; @@ -550,16 +551,16 @@ class TableState<Key, Row> { // ---- Header Context Menu onHeaderMenu() { - let has_order = false; - let has_resize = false; - let has_visible = false; - const visible = this.visible; - const columns = this.columns; - columns.forEach(col => { - if (!col.disableSort) has_order = true; - if (!col.fixed) has_resize = true; + let hasOrder = false; + let hasResize = false; + let hasVisible = false; + const { visible } = this; + const { columns } = this; + columns.forEach((col) => { + if (!col.disableSort) hasOrder = true; + if (!col.fixed) hasResize = true; if (col.visible !== 'never' && col.visible !== 'always') - has_visible = true; + hasVisible = true; }); const resetSizing = () => { this.resize.clear(); @@ -573,27 +574,27 @@ class TableState<Key, Row> { const items: PopupMenu = [ { label: 'Reset ordering', - display: has_order && this.sorting, + display: hasOrder && this.sorting, onClick: this.onSorting, }, { label: 'Reset column widths', - display: has_resize, + display: hasResize, onClick: resetSizing, }, { label: 'Restore column defaults', - display: has_visible, + display: hasVisible, onClick: resetColumns, }, 'separator', ]; - columns.forEach(col => { + columns.forEach((col) => { switch (col.visible) { case 'never': case 'always': break; - default: + default: { const { id, label, title } = col; const checked = isVisible(visible, col); const onClick = () => { @@ -601,6 +602,7 @@ class TableState<Key, Row> { this.updateSettings(); }; items.push({ label: label || title || id, checked, onClick }); + } } }); Dome.popupMenu(items); @@ -643,7 +645,7 @@ class TableState<Key, Row> { path: number[], index: number, ): Trigger { - const id = props.id; + const { id } = props; const theIndex = props.index ?? index; const thePath = path.concat(theIndex); this.setRegistry(id, { ...props, index: thePath }); @@ -694,7 +696,7 @@ export function Column<Row, Cell>(props: ColumnProps<Row, Cell>) { function spawnIndex( state: TableState<any, any>, path: number[], - children: any + children: any, ) { const indexChild = (elt: React.ReactElement, k: number) => ( <ColumnContext.Provider value={{ state, path, index: k }}> @@ -747,7 +749,7 @@ function spawnIndex( this implicit root column group, just pack your columns inside a classical React fragment: `<Table … ><>{children}</></Table>`. */ -export function ColumnGroup(props: { index?: index, children: any }) { +export function ColumnGroup(props: { index?: index; children: any }) { const context = React.useContext(ColumnContext); if (!context) return null; const { state, path, index: defaultIndex } = context; @@ -776,7 +778,7 @@ function makeColumn<Key, Row>( }; const width = state.resize.get(id) || props.width || 60; const flexGrow = fill ? 1 : 0; - const sorting = state.sorting; + const { sorting } = state; const disableSort = props.disableSort || !sorting || !sorting.canSortBy(dataKey); const getter = state.computeGetter(id, dataKey, props); @@ -796,7 +798,7 @@ function makeColumn<Key, Row>( style={align} /> ); -}; +} const byIndex = (a: Cprops, b: Cprops) => { const ak = a.index ?? 0; @@ -804,7 +806,7 @@ const byIndex = (a: Cprops, b: Cprops) => { if (ak < bk) return -1; if (bk < ak) return 1; return 0; -} +}; function makeCprops<Key, Row>(state: TableState<Key, Row>) { const cols: Cprops[] = []; @@ -835,20 +837,24 @@ function makeColumns<Key, Row>(state: TableState<Key, Row>, cols: Cprops[]) { const headerIcon = (icon?: string) => ( icon && - (<div className='dome-xTable-header-icon'> - <SVG id={icon} /> - </div>) + ( + <div className="dome-xTable-header-icon"> + <SVG id={icon} /> + </div> + ) ); const headerLabel = (label?: string) => ( label && - (<label className='dome-xTable-header-label dome-text-label'> - {label} - </label>) + ( + <label className="dome-xTable-header-label dome-text-label"> + {label} + </label> + ) ); const makeSorter = (id: string) => ( - <div className='dome-xTable-header-sorter'> + <div className="dome-xTable-header-sorter"> <SVG id={id} size={8} /> </div> ); @@ -878,7 +884,7 @@ function headerRenderer(props: TableHeaderProps) { : undefined; return ( <div - className='dome-xTable-header' + className="dome-xTable-header" title={title} ref={headerRef} onContextMenu={headerMenu} @@ -919,15 +925,17 @@ const Resizer = (props: ResizerProps) => ( </DraggableCore> ); -type ResizeInfo = { id: string, fixed: boolean, left?: string, right?: string }; +type ResizeInfo = { id: string; fixed: boolean; left?: string; right?: string }; function makeResizers( state: TableState<any, any>, columns: Cprops[], ): null | JSX.Element[] { if (columns.length < 2) return null; - const resizing: ResizeInfo[] = columns.map(({ id, fixed = false }) => ({ id, fixed })); - var k: number, cid; // last non-fixed from left/right + const resizing: ResizeInfo[] = + columns.map(({ id, fixed = false }) => ({ id, fixed })); + let k: number; let + cid; // last non-fixed from left/right for (cid = undefined, k = 0; k < columns.length; k++) { const r = resizing[k]; r.left = cid; @@ -938,8 +946,9 @@ function makeResizers( r.right = cid; if (!r.fixed) cid = r.id; } - const cwidth = columns.map(col => state.computeWidth(col.id)); - var position = 0, resizers = []; + const cwidth = columns.map((col) => state.computeWidth(col.id)); + let position = 0; const + resizers = []; for (k = 0; k < columns.length - 1; k++) { const width = cwidth[k]; if (!width) return null; @@ -987,7 +996,7 @@ function makeTable<Key, Row>( ) { const { width, height } = size; - const model = props.model; + const { model } = props; const itemCount = model.getRowCount(); const tableHeight = CSS_HEADER_HEIGHT + CSS_ROW_HEIGHT * itemCount; const smallHeight = itemCount > 0 && tableHeight < height; @@ -1030,9 +1039,9 @@ function makeTable<Key, Row>( {columns} </VTable> {resizers} - </div > + </div> ); -}; +} // -------------------------------------------------------------------------- // --- Table View @@ -1041,7 +1050,7 @@ function makeTable<Key, Row>( /** Table View. This component is base on - [React-Virtualized](https://bvaughn.github.io/react-virtualized/#/components/Table) + [React-Virtualized](https://bvaughn.github.io/react-virtualized) which offers a super-optimized lazy rendering process that scales on huge datasets. @@ -1085,14 +1094,14 @@ export function Table<Key, Row>(props: TableProps<Key, Row>) { }); Dome.useEvent('dome.defaults', state.clearSettings); return ( - <div className='dome-xTable'> - <React.Fragment key='columns'> + <div className="dome-xTable"> + <React.Fragment key="columns"> {spawnIndex(state, [], props.children)} </React.Fragment> - <AutoSizer key='table'> + <AutoSizer key="table"> {(size: Size) => makeTable(props, state, size)} </AutoSizer> - </div > + </div> ); }