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

Merge branch 'feature/dome/table-column-index' into 'master'

[dome] table column auto indexing

See merge request frama-c/frama-c!2698
parents 451a840d 4f6a4012
No related branches found
No related tags found
No related merge requests found
...@@ -51,6 +51,13 @@ export type RenderByFields<Row> = { ...@@ -51,6 +51,13 @@ export type RenderByFields<Row> = {
// --- Table Columns // --- Table Columns
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
/**
Column position.
You may use hierarchical index to order columns.
See [[ColumnGroup]].
*/
export type index = number | number[]
/** /**
@template Row - table row data of some table entries @template Row - table row data of some table entries
@template Cell - type of cell data to render in this column @template Cell - type of cell data to render in this column
...@@ -67,8 +74,9 @@ export interface ColumnProps<Row, Cell> { ...@@ -67,8 +74,9 @@ export interface ColumnProps<Row, Cell> {
/** /**
Column position. Column position.
By default, column will appear according to their mounting order. By default, column will appear according to their mounting order.
See also [[ColumnGroup]].
*/ */
index?: number; index?: index;
/** CSS vertical alignment on cells. */ /** CSS vertical alignment on cells. */
align?: 'left' | 'center' | 'right'; align?: 'left' | 'center' | 'right';
/** Column base width in pixels (default 60px). */ /** Column base width in pixels (default 60px). */
...@@ -630,9 +638,15 @@ class TableState<Key, Row> { ...@@ -630,9 +638,15 @@ class TableState<Key, Row> {
this.rebuild(); this.rebuild();
} }
useColumn(props: ColProps<Row>): Trigger { useColumn(
props: ColProps<Row>,
path: number[],
index: number,
): Trigger {
const id = props.id; const id = props.id;
this.setRegistry(id, props); const theIndex = props.index ?? index;
const thePath = path.concat(theIndex);
this.setRegistry(id, { ...props, index: thePath });
return () => this.setRegistry(id, null); return () => this.setRegistry(id, null);
} }
...@@ -653,8 +667,13 @@ class TableState<Key, Row> { ...@@ -653,8 +667,13 @@ class TableState<Key, Row> {
// --- Columns Components // --- Columns Components
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
const ColumnContext = interface ColumnIndex {
React.createContext<undefined | TableState<any, any>>(undefined); state: TableState<any, any>;
path: number[];
index: number;
}
const ColumnContext = React.createContext<undefined | ColumnIndex>(undefined);
/** /**
Table Column. Table Column.
...@@ -663,10 +682,79 @@ const ColumnContext = ...@@ -663,10 +682,79 @@ const ColumnContext =
*/ */
export function Column<Row, Cell>(props: ColumnProps<Row, Cell>) { export function Column<Row, Cell>(props: ColumnProps<Row, Cell>) {
const context = React.useContext(ColumnContext); const context = React.useContext(ColumnContext);
React.useEffect(() => context && context.useColumn(props)); React.useEffect(() => {
if (context) {
const { state, path, index } = context;
state.useColumn(props, path, index);
}
});
return null; return null;
} }
function spawnIndex(
state: TableState<any, any>,
path: number[],
children: any
) {
const indexChild = (elt: React.ReactElement, k: number) => (
<ColumnContext.Provider value={{ state, path, index: k }}>
{elt}
</ColumnContext.Provider>
);
return <>{React.Children.map(children, indexChild)}</>;
}
/** Column Groups.
You should use this component in replacement of React fragments rendering
several columns:
* ```ts
* function MyCustomColumn(props) {
* ...
* return (
* <ColumnGroup>
* <Column id='A' index={3} ... />
* <Column id='B' ... />
* <MyOtherCustomColumns ... />
* <Column id='C' index={0} ... />
* </ColumnGroup>
* );
* }
* ```
When rendering a column or a column group, there is an implicit column
indexing context which is provided by the parent Table component and
propagated down to the virtual DOM. This context provides each column and
column group with a default index position, which can be locally adjusted
with the `index` property.
More precisely, if a column group is assigned to index `K` by the inherited
context, its `i`-th child will inherit a refined indexing context
corresponding to index `[...K, i]`. Then, inside this refined context, a
column or column-group rendered from the `i`-th child of the column group
with be assigned to a default index of `[...K, i]`, or a refined index of
`[...K, ...index]` if it the sub-column or sub-column-group has an `index`
property.
This indexing context allows you to number columns locally inside a
`<ColumnGroup />` without having to take into account its neighbours. This
also means that columns rendered in a group never goes outside of this group.
Column groups hence provide hierarchical column ordering.
The immediate children of a Table component are implicitely rendered in a
column group initialized at index `[i]` for the `i`-th child. To cancel
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 }) {
const context = React.useContext(ColumnContext);
if (!context) return null;
const { state, path, index: defaultIndex } = context;
const newPath = path.concat(props.index ?? defaultIndex);
return spawnIndex(state, newPath, props.children);
}
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// --- Virtualized Column // --- Virtualized Column
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
...@@ -710,6 +798,14 @@ function makeColumn<Key, Row>( ...@@ -710,6 +798,14 @@ function makeColumn<Key, Row>(
); );
}; };
const byIndex = (a: Cprops, b: Cprops) => {
const ak = a.index ?? 0;
const bk = b.index ?? 0;
if (ak < bk) return -1;
if (bk < ak) return 1;
return 0;
}
function makeCprops<Key, Row>(state: TableState<Key, Row>) { function makeCprops<Key, Row>(state: TableState<Key, Row>) {
const cols: Cprops[] = []; const cols: Cprops[] = [];
state.columns.forEach((col) => { state.columns.forEach((col) => {
...@@ -717,7 +813,7 @@ function makeCprops<Key, Row>(state: TableState<Key, Row>) { ...@@ -717,7 +813,7 @@ function makeCprops<Key, Row>(state: TableState<Key, Row>) {
cols.push(col); cols.push(col);
} }
}); });
cols.sort((a, b) => (a.index ?? 0) - (b.index ?? 0)); cols.sort(byIndex);
return cols; return cols;
} }
...@@ -943,54 +1039,35 @@ function makeTable<Key, Row>( ...@@ -943,54 +1039,35 @@ function makeTable<Key, Row>(
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
/** Table View. /** Table View.
This component is base on [React-Virtualized This component is base on
Tables](https://bvaughn.github.io/react-virtualized/#/components/Table), [React-Virtualized](https://bvaughn.github.io/react-virtualized/#/components/Table)
offering a lazy, super-optimized rendering process that scales on huge which offers a super-optimized lazy rendering process that scales on huge
datasets. datasets.
A table shall be connected to an instance of [[Model]] class to retrieve the A table shall be connected to an instance of [[Model]] class to retrieve the
data and get informed of data updates. data and get informed of data updates.
The table children shall be instances of [[Column]] class, and can be grouped The table children shall only be component finally rendering [[Column]]
into arbitrary level of React fragments or custom components. elements. You can use [[ColumnGroup]] and column index properties to manage
column natural order precisely.
Clicking on table headers trigger re-ordering callback on the model with the Clicking on table headers trigger re-ordering callback on the model with the
expected column and direction, unless disabled _via_ the column x expected column and direction, unless disabled _via_ the column x
specification. However, actual sorting (and corresponding feedback on table specification. However, actual sorting (and corresponding feedback on table
headers) would only take place if the model supports re-ordering and headers) would only take place if the model supports re-ordering and
eventually triggers a reload. eventually triggers a reload.
Right-clicking the table headers displays a popup-menu with actions to reset Right-clicking the table headers displays a popup-menu with actions to reset
natural ordering, reset column widths and select column visibility. natural ordering, reset column widths and select column visibility.
Tables do not control item selection state. Instead, you shall supply the Tables do not control item selection state. Instead, you shall supply the
selection state and callback _via_ properties, like any other controlled selection state and callback _via_ properties, like any other controlled
React components. React components.
Item selection can be based either on single-row or multiple-row. In case of @template Key - unique identifiers of table entries.
single-row selection (`multipleSelection:false`, the default), selection @template Row - data associated to each key in the table entries.
state must be a single item or `undefined`, and the `onSelection` callback is */
called with the same type of values.
In case of multiple-row selection (`multipleSelection:true`), the selection
state shall be an _array_ of items, and `onSelection` callback also. Single
items are _not_ accepted, but `undefined` selection can be used in place of
an empty array.
Clicking on a row triggers the `onSelection` callback with the updated
selection. In single-selection mode, the clicked item is sent to the
callback. In multiple-selection mode, key modifiers are taken into account
for determining the new slection. By default, the new selection only contains
the clicked item. If the `Shift` modifier has been pressed, the current
selection is extended with a range of items from the last selected one, to
the newly selected one. If the `CtrlOrCmd` modifier has been pressed, the
selection is extended with the newly clicked item. Clicking an already
selected item with the `CtrlOrCmd` modifier removes it from the current
selection.
@template Key - unique identifiers of table entries @template Row - data
associated to each key in the table entries */
export function Table<Key, Row>(props: TableProps<Key, Row>) { export function Table<Key, Row>(props: TableProps<Key, Row>) {
...@@ -1007,16 +1084,15 @@ export function Table<Key, Row>(props: TableProps<Key, Row>) { ...@@ -1007,16 +1084,15 @@ export function Table<Key, Row>(props: TableProps<Key, Row>) {
return state.unwind; return state.unwind;
}); });
Dome.useEvent('dome.defaults', state.clearSettings); Dome.useEvent('dome.defaults', state.clearSettings);
return ( return (
<div className='dome-xTable'> <div className='dome-xTable'>
<ColumnContext.Provider value={state}> <React.Fragment key='columns'>
{props.children} {spawnIndex(state, [], props.children)}
</ColumnContext.Provider> </React.Fragment>
<AutoSizer key='table'> <AutoSizer key='table'>
{(size: Size) => makeTable(props, state, size)} {(size: Size) => makeTable(props, state, size)}
</AutoSizer> </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