Skip to content

Commit c181d7b

Browse files
authored
Merge pull request #278 from performant-software/feature/cdc157_list_columns
CDC #157 - List Columns
2 parents f5943bf + ff4834b commit c181d7b

File tree

17 files changed

+280
-79
lines changed

17 files changed

+280
-79
lines changed

packages/controlled-vocabulary/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@performant-software/controlled-vocabulary",
3-
"version": "2.2.1",
3+
"version": "2.2.2",
44
"description": "A package of components to allow user to configure dropdown elements. Use with the \"controlled_vocabulary\" gem.",
55
"license": "MIT",
66
"main": "./dist/index.cjs.js",
@@ -23,8 +23,8 @@
2323
"underscore": "^1.13.2"
2424
},
2525
"peerDependencies": {
26-
"@performant-software/semantic-components": "^2.2.1",
27-
"@performant-software/shared-components": "^2.2.1",
26+
"@performant-software/semantic-components": "^2.2.2",
27+
"@performant-software/shared-components": "^2.2.2",
2828
"react": ">= 16.13.1 < 19.0.0",
2929
"react-dom": ">= 16.13.1 < 19.0.0"
3030
},

packages/core-data/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@performant-software/core-data",
3-
"version": "2.2.1",
3+
"version": "2.2.2",
44
"description": "A package of components used with the Core Data platform.",
55
"license": "MIT",
66
"main": "./dist/index.cjs.js",
@@ -37,8 +37,8 @@
3737
"underscore": "^1.13.2"
3838
},
3939
"peerDependencies": {
40-
"@performant-software/shared-components": "^2.2.1",
41-
"@performant-software/geospatial": "^2.2.1",
40+
"@performant-software/shared-components": "^2.2.2",
41+
"@performant-software/geospatial": "^2.2.2",
4242
"@peripleo/maplibre": "^0.5.2",
4343
"@peripleo/peripleo": "^0.5.2",
4444
"react": ">= 16.13.1 < 19.0.0",

packages/geospatial/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@performant-software/geospatial",
3-
"version": "2.2.1",
3+
"version": "2.2.2",
44
"description": "A package of components for all things map-related.",
55
"license": "MIT",
66
"main": "./dist/index.cjs.js",

packages/semantic-ui/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@performant-software/semantic-components",
3-
"version": "2.2.1",
3+
"version": "2.2.2",
44
"description": "A package of shared components based on the Semantic UI Framework.",
55
"license": "MIT",
66
"main": "./dist/index.cjs.js",
@@ -35,7 +35,7 @@
3535
"zotero-translation-client": "^5.0.1"
3636
},
3737
"peerDependencies": {
38-
"@performant-software/shared-components": "^2.2.1",
38+
"@performant-software/shared-components": "^2.2.2",
3939
"@samvera/clover-iiif": "^2.3.2",
4040
"react": ">= 16.13.1 < 19.0.0",
4141
"react-dnd": "^11.1.3",

packages/semantic-ui/src/components/DataList.js

Lines changed: 16 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import uuid from 'react-uuid';
66
import _ from 'underscore';
77
import { Icon, Input, Message } from 'semantic-ui-react';
88
import i18n from '../i18n/i18n';
9+
import ListSessionUtils from '../utils/ListSession';
910
import Toaster from './Toaster';
1011

1112
type Props = {
@@ -131,9 +132,6 @@ type State = {
131132
sortDirection: ?string
132133
};
133134

134-
const SESSION_KEY = 'DataList';
135-
const SESSION_DEFAULT = '{}';
136-
137135
const SORT_ASCENDING = 'ascending';
138136
const SORT_DESCENDING = 'descending';
139137

@@ -285,19 +283,6 @@ const useDataList = (WrappedComponent: ComponentType<any>) => (
285283
return { filters };
286284
}
287285

288-
/**
289-
* Returns the session storage key for the current list.
290-
*
291-
* @returns {string|null}
292-
*/
293-
getSessionKey() {
294-
if (!this.props.session) {
295-
return null;
296-
}
297-
298-
return `${SESSION_KEY}.${this.props.session.key}`;
299-
}
300-
301286
/**
302287
* Initializes the state based on the passed props.
303288
*
@@ -307,7 +292,8 @@ const useDataList = (WrappedComponent: ComponentType<any>) => (
307292
* saved: boolean, count: number, filters: (*|{}), page: number, error: null, loading: boolean, items: *[]}}
308293
*/
309294
initializeState(props: Props) {
310-
const session = this.restoreSession();
295+
const { key, storage } = props.session || {};
296+
const session = ListSessionUtils.restoreSession(key, storage) || {};
311297

312298
const filters = session.filters || this.getDefaultFilters(props);
313299
const page = session.page || 1;
@@ -445,6 +431,14 @@ const useDataList = (WrappedComponent: ComponentType<any>) => (
445431
});
446432
}
447433

434+
/**
435+
* When no columns are sortable, load data as is
436+
*
437+
*/
438+
onInit(page?: number = 1) {
439+
this.setState({ sortColumn: '', sortDirection: '', page }, this.fetchData.bind(this));
440+
}
441+
448442
/**
449443
* Sets the new active page and reloads the data.
450444
*
@@ -516,14 +510,6 @@ const useDataList = (WrappedComponent: ComponentType<any>) => (
516510
this.setState({ sortColumn, sortDirection, page }, this.fetchData.bind(this));
517511
}
518512

519-
/**
520-
* When no columns are sortable, load data as is
521-
*
522-
*/
523-
onInit(page?: number = 1) {
524-
this.setState({ sortColumn: '', sortDirection: '', page }, this.fetchData.bind(this));
525-
}
526-
527513
/**
528514
* Renders the DataList component.
529515
*
@@ -565,7 +551,7 @@ const useDataList = (WrappedComponent: ComponentType<any>) => (
565551
sortColumn={this.state.sortColumn}
566552
sortDirection={this.state.sortDirection}
567553
/>
568-
{this.state.saved && (
554+
{ this.state.saved && (
569555
<Toaster
570556
onDismiss={() => this.setState({ saved: false })}
571557
type={Toaster.MessageTypes.positive}
@@ -578,7 +564,7 @@ const useDataList = (WrappedComponent: ComponentType<any>) => (
578564
/>
579565
</Toaster>
580566
)}
581-
{this.state.error && (
567+
{ this.state.error && (
582568
<Toaster
583569
onDismiss={() => this.setState({ error: false })}
584570
timeout={0}
@@ -633,25 +619,11 @@ const useDataList = (WrappedComponent: ComponentType<any>) => (
633619
);
634620
}
635621

636-
/**
637-
* Restores the DataList session object.
638-
*/
639-
restoreSession() {
640-
const key = this.getSessionKey();
641-
642-
if (!key) {
643-
return {};
644-
}
645-
646-
const session = sessionStorage.getItem(key) || SESSION_DEFAULT;
647-
return JSON.parse(session);
648-
}
649-
650622
/**
651623
* Sets the DataList session object.
652624
*/
653625
setSession() {
654-
const key = this.getSessionKey();
626+
const { key, storage } = this.props.session || {};
655627

656628
if (!key) {
657629
return;
@@ -666,14 +638,14 @@ const useDataList = (WrappedComponent: ComponentType<any>) => (
666638
sortDirection
667639
} = this.state;
668640

669-
sessionStorage.setItem(key, JSON.stringify({
641+
ListSessionUtils.setSession(key, storage, {
670642
filters,
671643
page,
672644
perPage,
673645
search,
674646
sortColumn,
675647
sortDirection
676-
}));
648+
});
677649
}
678650
}
679651
);

packages/semantic-ui/src/components/DataTableColumnSelector.js

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import React, { Component, type ComponentType, type Element } from 'react';
55
import { Checkbox, Dropdown, Icon } from 'semantic-ui-react';
66
import _ from 'underscore';
77
import Draggable from './Draggable';
8+
import ListSessionUtils from '../utils/ListSession';
89
import type { Props as ListProps } from './List';
910
import './DataTableColumnSelector.css';
1011

@@ -75,20 +76,64 @@ const useColumnSelector = (WrappedComponent: ComponentType<any>) => (
7576
constructor(props: Props) {
7677
super(props);
7778

78-
this.state = {
79-
columns: props.columns
80-
};
79+
this.state = this.initializeState(props);
8180
}
8281

8382
/**
8483
* Reset the columns on the state when the props change.
8584
*
8685
* @param prevProps
8786
*/
88-
componentDidUpdate(prevProps: Props): * {
87+
componentDidUpdate(prevProps: Props, prevState: State): * {
8988
if (!ObjectUtils.isEqual(prevProps.columns, this.props.columns)) {
9089
this.setState({ columns: this.props.columns });
9190
}
91+
92+
if (!ObjectUtils.isEqual(prevState.columns, this.state.columns)) {
93+
this.setSession();
94+
}
95+
}
96+
97+
/**
98+
* Sets the initial state from the session, if provided.
99+
*
100+
* @param props
101+
*
102+
* @returns {{columns}|{columns: *}}
103+
*/
104+
initializeState(props) {
105+
const { key, storage } = props.session || {};
106+
const session = ListSessionUtils.restoreSession(key, storage) || {};
107+
108+
// If the session does not have any stored columns, use the provided props
109+
if (_.isEmpty(session.columns)) {
110+
return {
111+
columns: props.columns
112+
};
113+
}
114+
115+
const columns = [];
116+
117+
// Iterate over the session columns to preserve the ordering.
118+
_.each(session.columns, (column) => {
119+
const findColumn = _.findWhere(props.columns, { name: column.name });
120+
if (findColumn) {
121+
columns.push({ ...findColumn, ...column });
122+
}
123+
});
124+
125+
// Append any new columns not stored in the session
126+
const columnNames = _.pluck(columns, 'name');
127+
128+
_.each(props.columns, (column) => {
129+
if (!_.contains(columnNames, column.name)) {
130+
columns.push(column);
131+
}
132+
});
133+
134+
return {
135+
columns
136+
};
92137
}
93138

94139
/**
@@ -203,6 +248,20 @@ const useColumnSelector = (WrappedComponent: ComponentType<any>) => (
203248
</>
204249
);
205250
}
251+
252+
/**
253+
* Sets the list columns on the session.
254+
*/
255+
setSession() {
256+
const { key, storage } = this.props.session || {};
257+
258+
if (!key) {
259+
return;
260+
}
261+
262+
const columns = _.map(this.state.columns, (column) => _.pick(column, 'name', 'hidden'));
263+
ListSessionUtils.setSession(key, storage, { columns });
264+
}
206265
}
207266
);
208267

packages/semantic-ui/src/components/ItemList.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ type Sort = {
1515
};
1616

1717
type Props = DataListProps & ItemsProps & {
18+
/**
19+
* If `true`, a dimmer will be displayed over the list component.
20+
*/
21+
dimmable?: boolean,
22+
1823
/**
1924
* Callback fired when the sort dropdown is changed. This prop is provided by the <code>DataList</code>
2025
* higher-order component.
@@ -94,7 +99,7 @@ const ItemList = useDataList((props: Props) => {
9499
return (
95100
<>
96101
<Dimmer
97-
active={props.loading}
102+
active={props.dimmable && props.loading}
98103
inverted
99104
>
100105
<Loader
@@ -116,6 +121,7 @@ const ItemList = useDataList((props: Props) => {
116121
});
117122

118123
ItemList.defaultProps = {
124+
dimmable: true,
119125
filters: {},
120126
searchable: true,
121127
};

packages/semantic-ui/src/components/ListTable.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Hooks, ObjectJs as ObjectUtils } from '@performant-software/shared-comp
44
import React, { useEffect } from 'react';
55
import _ from 'underscore';
66
import DataTable from './DataTable';
7+
import ListSessionUtils from '../utils/ListSession';
78
import type { Props as DataTableProps } from './DataTable';
89
import useDataList, { SORT_ASCENDING, SORT_DESCENDING, type Props as DataListProps } from './DataList';
910
import './ListTable.css';
@@ -88,11 +89,12 @@ const ListTable = useDataList((props: Props) => {
8889
*/
8990
useEffect(() => {
9091
if (!ObjectUtils.isEqual(props.columns, prevColumns)) {
91-
const {
92-
page,
93-
defaultSort,
94-
defaultSortDirection = SORT_ASCENDING
95-
} = props;
92+
const { key, storage } = props.session || {};
93+
const session = ListSessionUtils.restoreSession(key, storage);
94+
95+
const defaultSort = session.sortColumn || props.defaultSort;
96+
const defaultSortDirection = session.sortDirection || props.defaultSortDirection || SORT_ASCENDING;
97+
const { page } = props;
9698

9799
if (defaultSort) {
98100
props.onSort(defaultSort, defaultSortDirection, page);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// @flow
2+
3+
const SESSION_DEFAULT = '{}';
4+
5+
/**
6+
* Restores the list session from the passed storage object.
7+
*
8+
* @param key
9+
* @param storage
10+
*
11+
* @returns {{}|any}
12+
*/
13+
const restoreSession = (key, storage) => {
14+
if (!(key && storage)) {
15+
return {};
16+
}
17+
18+
const session = storage.getItem(key) || SESSION_DEFAULT;
19+
return JSON.parse(session);
20+
};
21+
22+
/**
23+
* Sets the passed list session on the passed storage object.
24+
*
25+
* @param key
26+
* @param storage
27+
* @param session
28+
*/
29+
const setSession = (key, storage, session) => {
30+
if (!(key && storage)) {
31+
return;
32+
}
33+
34+
const currentSession = restoreSession(key, storage);
35+
storage.setItem(key, JSON.stringify({ ...currentSession, ...session }));
36+
};
37+
38+
export default {
39+
restoreSession,
40+
setSession
41+
};

0 commit comments

Comments
 (0)