Skip to content
Snippets Groups Projects
Commit 7befa5fb authored by Loïc Correnson's avatar Loïc Correnson
Browse files

[dome] lint tables

parent a828485c
No related branches found
No related tags found
No related merge requests found
......@@ -10,4 +10,3 @@ lib
src/api
# lint Dome step by step
src/dome/src/renderer/layout
src/dome/src/renderer/table
......@@ -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 } ],
}
};
......@@ -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;
}
......
......@@ -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;
......
......@@ -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>
);
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment