@ansible/ansible-ui-framework 0.0.190

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. package/LICENSE +21 -0
  2. package/cjs/framework/BulkActionDialog.js +217 -0
  3. package/cjs/framework/BulkProgressDialog.js +240 -0
  4. package/cjs/framework/FilterDrawer.js +64 -0
  5. package/cjs/framework/PageBody.js +47 -0
  6. package/cjs/framework/PageCatalog.js +178 -0
  7. package/cjs/framework/PageCells.js +141 -0
  8. package/cjs/framework/PageColumnModal.js +130 -0
  9. package/cjs/framework/PageDataList.js +61 -0
  10. package/cjs/framework/PageDialog.js +28 -0
  11. package/cjs/framework/PageForm.js +376 -0
  12. package/cjs/framework/PageFramework.js +11 -0
  13. package/cjs/framework/PageHeader.js +96 -0
  14. package/cjs/framework/PageLayout.js +41 -0
  15. package/cjs/framework/PagePagination.js +28 -0
  16. package/cjs/framework/PageTable.js +205 -0
  17. package/cjs/framework/PageTabs.js +82 -0
  18. package/cjs/framework/PageToolbar.js +209 -0
  19. package/cjs/framework/Settings.js +122 -0
  20. package/cjs/framework/TypedActions.js +303 -0
  21. package/cjs/framework/components/BulkSelector.js +89 -0
  22. package/cjs/framework/components/Collapse.js +22 -0
  23. package/cjs/framework/components/DetailInfo.js +23 -0
  24. package/cjs/framework/components/Details.js +88 -0
  25. package/cjs/framework/components/Dotted.js +19 -0
  26. package/cjs/framework/components/DropdownControlled.js +28 -0
  27. package/cjs/framework/components/ErrorBoundary.js +45 -0
  28. package/cjs/framework/components/Grid.js +64 -0
  29. package/cjs/framework/components/Help.js +24 -0
  30. package/cjs/framework/components/IconWrapper.js +55 -0
  31. package/cjs/framework/components/LoadingPage.js +14 -0
  32. package/cjs/framework/components/Masonry.js +113 -0
  33. package/cjs/framework/components/Scrollable.js +87 -0
  34. package/cjs/framework/components/SingleSelect.js +70 -0
  35. package/cjs/framework/components/patternfly-colors.js +32 -0
  36. package/cjs/framework/components/useBreakPoint.js +145 -0
  37. package/cjs/framework/components/useOpen.js +36 -0
  38. package/cjs/framework/components/useWindowLocation.js +70 -0
  39. package/cjs/framework/index.js +39 -0
  40. package/cjs/framework/useFrameworkTranslations.js +38 -0
  41. package/cjs/framework/useSelectDialog.js +81 -0
  42. package/cjs/framework/useSelectMultipleDialog.js +62 -0
  43. package/cjs/framework/useTableItems.js +485 -0
  44. package/cjs/framework/useView.js +155 -0
  45. package/cjs/framework/utils/compare.js +59 -0
  46. package/cjs/framework/utils/download-file.js +23 -0
  47. package/cjs/framework/utils/random-string.js +17 -0
  48. package/cjs/frontend/controller/access/organizations/Organization.js +2 -0
  49. package/mjs/framework/BulkActionDialog.d.ts +25 -0
  50. package/mjs/framework/BulkActionDialog.js +104 -0
  51. package/mjs/framework/BulkProgressDialog.d.ts +20 -0
  52. package/mjs/framework/BulkProgressDialog.js +131 -0
  53. package/mjs/framework/FilterDrawer.d.ts +8 -0
  54. package/mjs/framework/FilterDrawer.js +24 -0
  55. package/mjs/framework/PageBody.d.ts +4 -0
  56. package/mjs/framework/PageBody.js +29 -0
  57. package/mjs/framework/PageCatalog.d.ts +113 -0
  58. package/mjs/framework/PageCatalog.js +140 -0
  59. package/mjs/framework/PageCells.d.ts +35 -0
  60. package/mjs/framework/PageCells.js +102 -0
  61. package/mjs/framework/PageColumnModal.d.ts +7 -0
  62. package/mjs/framework/PageColumnModal.js +64 -0
  63. package/mjs/framework/PageDataList.d.ts +46 -0
  64. package/mjs/framework/PageDataList.js +45 -0
  65. package/mjs/framework/PageDialog.d.ts +10 -0
  66. package/mjs/framework/PageDialog.js +12 -0
  67. package/mjs/framework/PageForm.d.ts +147 -0
  68. package/mjs/framework/PageForm.js +316 -0
  69. package/mjs/framework/PageFramework.d.ts +4 -0
  70. package/mjs/framework/PageFramework.js +7 -0
  71. package/mjs/framework/PageHeader.d.ts +45 -0
  72. package/mjs/framework/PageHeader.js +80 -0
  73. package/mjs/framework/PageLayout.d.ts +15 -0
  74. package/mjs/framework/PageLayout.js +23 -0
  75. package/mjs/framework/PagePagination.d.ts +10 -0
  76. package/mjs/framework/PagePagination.js +22 -0
  77. package/mjs/framework/PageTable.d.ts +68 -0
  78. package/mjs/framework/PageTable.js +170 -0
  79. package/mjs/framework/PageTabs.d.ts +15 -0
  80. package/mjs/framework/PageTabs.js +45 -0
  81. package/mjs/framework/PageToolbar.d.ts +57 -0
  82. package/mjs/framework/PageToolbar.js +148 -0
  83. package/mjs/framework/Settings.d.ts +19 -0
  84. package/mjs/framework/Settings.js +87 -0
  85. package/mjs/framework/TypedActions.d.ts +80 -0
  86. package/mjs/framework/TypedActions.js +251 -0
  87. package/mjs/framework/components/BulkSelector.d.ts +11 -0
  88. package/mjs/framework/components/BulkSelector.js +56 -0
  89. package/mjs/framework/components/Collapse.d.ts +5 -0
  90. package/mjs/framework/components/Collapse.js +7 -0
  91. package/mjs/framework/components/DetailInfo.d.ts +5 -0
  92. package/mjs/framework/components/DetailInfo.js +8 -0
  93. package/mjs/framework/components/Details.d.ts +38 -0
  94. package/mjs/framework/components/Details.js +68 -0
  95. package/mjs/framework/components/Dotted.d.ts +4 -0
  96. package/mjs/framework/components/Dotted.js +4 -0
  97. package/mjs/framework/components/DropdownControlled.d.ts +4 -0
  98. package/mjs/framework/components/DropdownControlled.js +8 -0
  99. package/mjs/framework/components/ErrorBoundary.d.ts +15 -0
  100. package/mjs/framework/components/ErrorBoundary.js +25 -0
  101. package/mjs/framework/components/Grid.d.ts +6 -0
  102. package/mjs/framework/components/Grid.js +27 -0
  103. package/mjs/framework/components/Help.d.ts +5 -0
  104. package/mjs/framework/components/Help.js +9 -0
  105. package/mjs/framework/components/IconWrapper.d.ts +8 -0
  106. package/mjs/framework/components/IconWrapper.js +40 -0
  107. package/mjs/framework/components/LoadingPage.d.ts +6 -0
  108. package/mjs/framework/components/LoadingPage.js +9 -0
  109. package/mjs/framework/components/Masonry.d.ts +6 -0
  110. package/mjs/framework/components/Masonry.js +69 -0
  111. package/mjs/framework/components/Scrollable.d.ts +7 -0
  112. package/mjs/framework/components/Scrollable.js +60 -0
  113. package/mjs/framework/components/SingleSelect.d.ts +18 -0
  114. package/mjs/framework/components/SingleSelect.js +37 -0
  115. package/mjs/framework/components/patternfly-colors.d.ts +13 -0
  116. package/mjs/framework/components/patternfly-colors.js +28 -0
  117. package/mjs/framework/components/useBreakPoint.d.ts +5 -0
  118. package/mjs/framework/components/useBreakPoint.js +118 -0
  119. package/mjs/framework/components/useOpen.d.ts +4 -0
  120. package/mjs/framework/components/useOpen.js +16 -0
  121. package/mjs/framework/components/useWindowLocation.d.ts +6 -0
  122. package/mjs/framework/components/useWindowLocation.js +48 -0
  123. package/mjs/framework/index.d.ts +23 -0
  124. package/mjs/framework/index.js +23 -0
  125. package/mjs/framework/useFrameworkTranslations.d.ts +17 -0
  126. package/mjs/framework/useFrameworkTranslations.js +22 -0
  127. package/mjs/framework/useSelectDialog.d.ts +30 -0
  128. package/mjs/framework/useSelectDialog.js +49 -0
  129. package/mjs/framework/useSelectMultipleDialog.d.ts +21 -0
  130. package/mjs/framework/useSelectMultipleDialog.js +29 -0
  131. package/mjs/framework/useTableItems.d.ts +76 -0
  132. package/mjs/framework/useTableItems.js +371 -0
  133. package/mjs/framework/useView.d.ts +15 -0
  134. package/mjs/framework/useView.js +108 -0
  135. package/mjs/framework/utils/compare.d.ts +3 -0
  136. package/mjs/framework/utils/compare.js +53 -0
  137. package/mjs/framework/utils/download-file.d.ts +2 -0
  138. package/mjs/framework/utils/download-file.js +18 -0
  139. package/mjs/framework/utils/random-string.d.ts +1 -0
  140. package/mjs/framework/utils/random-string.js +12 -0
  141. package/mjs/frontend/controller/access/organizations/Organization.d.ts +44 -0
  142. package/mjs/frontend/controller/access/organizations/Organization.js +1 -0
  143. package/package.json +39 -0
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Button, Modal, ModalVariant } from '@patternfly/react-core';
3
+ import { useCallback } from 'react';
4
+ import { usePageDialog } from './PageDialog';
5
+ import { PageTable } from './PageTable';
6
+ import { useFrameworkTranslations } from './useFrameworkTranslations';
7
+ export function SelectMultipleDialog(props) {
8
+ const { title, view, tableColumns, toolbarFilters, confirmText, cancelText, onSelect } = props;
9
+ const [_, setDialog] = usePageDialog();
10
+ const onClose = useCallback(() => setDialog(undefined), [setDialog]);
11
+ const [translations] = useFrameworkTranslations();
12
+ return (_jsx(Modal, { title: title, isOpen: true, onClose: onClose, variant: ModalVariant.medium, tabIndex: 0, actions: [
13
+ _jsx(Button, { variant: "primary", onClick: () => {
14
+ onClose();
15
+ onSelect(view.selectedItems);
16
+ }, isAriaDisabled: view.selectedItems.length === 0, children: confirmText ?? translations.confirmText }, "confirm"),
17
+ _jsx(Button, { variant: "link", onClick: onClose, children: cancelText ?? translations.cancelText }, "cancel"),
18
+ ], hasNoBodyWrapper: true, children: _jsx("div", { style: {
19
+ display: 'flex',
20
+ flexDirection: 'column',
21
+ maxHeight: 500,
22
+ overflow: 'hidden',
23
+ }, children: _jsx(PageTable, { tableColumns: tableColumns, toolbarFilters: toolbarFilters, ...view, emptyStateTitle: props.emptyStateTitle ?? translations.noItemsFound, errorStateTitle: props.errorStateTitle ?? translations.errorText, showSelect: true }) }) }));
24
+ }
25
+ export function useSelectMultipleDialog() {
26
+ const [_, setDialog] = usePageDialog();
27
+ const openSelectMultipleDialog = useCallback((props) => setDialog(_jsx(SelectMultipleDialog, { ...props })), [setDialog]);
28
+ return openSelectMultipleDialog;
29
+ }
@@ -0,0 +1,76 @@
1
+ /// <reference types="react" />
2
+ export declare function useTableItems<T extends object>(items: T[], keyFn: (item: T) => string | number, defaults?: {
3
+ search?: string | null;
4
+ }): {
5
+ allSelected: boolean;
6
+ filtered: T[];
7
+ isSelected: (item: T) => boolean;
8
+ page: number;
9
+ paged: T[];
10
+ perPage: number;
11
+ search: string;
12
+ searched: T[];
13
+ selectAll: () => void;
14
+ selectItem: (item: T) => void;
15
+ selectPage: () => void;
16
+ selectedItems: T[];
17
+ setFilterFn: (filterFn: (item: T) => boolean) => void;
18
+ setPage: import("react").Dispatch<import("react").SetStateAction<number>>;
19
+ setPerPage: import("react").Dispatch<import("react").SetStateAction<number>>;
20
+ setSearch: ((search: string) => void) & {
21
+ clear(): void;
22
+ } & {
23
+ flush(): void;
24
+ };
25
+ setSearchFn: (searchFn: (item: T, search: string) => number) => void;
26
+ setSort: import("react").Dispatch<import("react").SetStateAction<ISort<T> | undefined>>;
27
+ sort: ISort<T> | undefined;
28
+ sorted: T[];
29
+ unselectAll: () => void;
30
+ unselectItem: (item: T) => void;
31
+ };
32
+ export interface ISelected<T extends object> {
33
+ selectedItems: T[];
34
+ selectItem: (item: T) => void;
35
+ selectItems: (items: T[]) => void;
36
+ unselectItem: (item: T) => void;
37
+ unselectItems: (items: T[]) => void;
38
+ isSelected: (item: T) => boolean;
39
+ selectAll: () => void;
40
+ unselectAll: () => void;
41
+ allSelected: boolean;
42
+ keyFn: (item: T) => string | number;
43
+ }
44
+ export declare function useSelected<T extends object>(items: T[], keyFn: (item: T) => string | number): ISelected<T>;
45
+ export declare function useSelectedInMemory<T extends object>(items: T[], keyFn: (item: T) => string | number): {
46
+ selectedItems: T[];
47
+ selectItem: (item: T) => void;
48
+ unselectItem: (item: T) => void;
49
+ isSelected: (item: T) => boolean;
50
+ selectItems: (items: T[]) => void;
51
+ selectAll: () => void;
52
+ unselectAll: () => void;
53
+ allSelected: boolean;
54
+ keyFn: (item: T) => string | number;
55
+ };
56
+ export interface ISort<T extends object> {
57
+ id: string;
58
+ sortFn: (l: T, r: T) => number;
59
+ direction: 'asc' | 'desc';
60
+ }
61
+ export declare function useSorted<T extends object>(items: T[]): {
62
+ sorted: T[];
63
+ sort: ISort<T> | undefined;
64
+ setSort: import("react").Dispatch<import("react").SetStateAction<ISort<T> | undefined>>;
65
+ };
66
+ export declare function useFiltered<T extends object>(items: T[], keyFn: (item: T) => string | number): {
67
+ filtered: T[];
68
+ setFilterFn: (filterFn: (item: T) => boolean) => void;
69
+ };
70
+ export declare function usePaged<T extends object>(source: T[]): {
71
+ paged: T[];
72
+ page: number;
73
+ setPage: import("react").Dispatch<import("react").SetStateAction<number>>;
74
+ perPage: number;
75
+ setPerPage: import("react").Dispatch<import("react").SetStateAction<number>>;
76
+ };
@@ -0,0 +1,371 @@
1
+ import debounce from 'debounce';
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+ export function useTableItems(items, keyFn, defaults) {
4
+ const { selectedItems, selectItem, unselectItem, isSelected, selectItems, unselectAll, allSelected, } = useSelected(items, keyFn);
5
+ const { sorted, sort, setSort } = useSorted(items);
6
+ const { filtered, setFilterFn } = useFiltered(sorted, keyFn);
7
+ const { searched, search, setSearch, setSearchFn } = useSearched(filtered, keyFn, defaults?.search);
8
+ const { paged, page, setPage, perPage, setPerPage } = usePaged(searched);
9
+ const selectPage = useCallback(() => selectItems(paged), [paged, selectItems]);
10
+ const selectAll = useCallback(() => selectItems(searched), [searched, selectItems]);
11
+ return useMemo(() => ({
12
+ allSelected,
13
+ filtered,
14
+ isSelected,
15
+ page,
16
+ paged,
17
+ perPage,
18
+ search,
19
+ searched,
20
+ selectAll,
21
+ selectItem,
22
+ selectPage,
23
+ selectedItems,
24
+ setFilterFn,
25
+ setPage,
26
+ setPerPage,
27
+ setSearch,
28
+ setSearchFn,
29
+ setSort,
30
+ sort,
31
+ sorted,
32
+ unselectAll,
33
+ unselectItem,
34
+ }), [
35
+ allSelected,
36
+ filtered,
37
+ isSelected,
38
+ page,
39
+ paged,
40
+ perPage,
41
+ search,
42
+ searched,
43
+ selectAll,
44
+ selectItem,
45
+ selectPage,
46
+ selectedItems,
47
+ setFilterFn,
48
+ setPage,
49
+ setPerPage,
50
+ setSearch,
51
+ setSearchFn,
52
+ setSort,
53
+ sort,
54
+ sorted,
55
+ unselectAll,
56
+ unselectItem,
57
+ ]);
58
+ }
59
+ export function useSelected(items, keyFn) {
60
+ const [selectedMap, setSelectedMap] = useState({});
61
+ useEffect(() => {
62
+ setSelectedMap((selectedMap) => {
63
+ let changed = false;
64
+ items.forEach((item) => {
65
+ const key = keyFn(item);
66
+ if (selectedMap[key] && selectedMap[key] !== item) {
67
+ changed = true;
68
+ selectedMap[key] = item;
69
+ }
70
+ });
71
+ return changed ? { ...selectedMap } : selectedMap;
72
+ });
73
+ }, [items, keyFn]);
74
+ const selectItem = useCallback((item) => {
75
+ setSelectedMap((selectedMap) => {
76
+ const itemKey = keyFn(item);
77
+ const existing = selectedMap[itemKey];
78
+ if (existing !== item) {
79
+ selectedMap = { ...selectedMap };
80
+ selectedMap[itemKey] = item;
81
+ }
82
+ return selectedMap;
83
+ });
84
+ }, [keyFn]);
85
+ const unselectItem = useCallback((item) => {
86
+ setSelectedMap((selectedMap) => {
87
+ const itemKey = keyFn(item);
88
+ const existing = selectedMap[itemKey];
89
+ if (existing) {
90
+ selectedMap = { ...selectedMap };
91
+ delete selectedMap[itemKey];
92
+ }
93
+ return selectedMap;
94
+ });
95
+ }, [keyFn]);
96
+ const isSelected = useCallback((item) => {
97
+ const itemKey = keyFn(item);
98
+ return selectedMap[itemKey] !== undefined;
99
+ }, [keyFn, selectedMap]);
100
+ const selectItems = useCallback((items) => {
101
+ setSelectedMap((selectedMap) => {
102
+ selectedMap = { ...selectedMap };
103
+ for (const item of items) {
104
+ const itemKey = keyFn(item);
105
+ selectedMap[itemKey] = item;
106
+ }
107
+ return selectedMap;
108
+ });
109
+ }, [keyFn]);
110
+ const selectAll = useCallback(() => selectItems(items), [items, selectItems]);
111
+ const unselectItems = useCallback((items) => {
112
+ for (const item of items) {
113
+ unselectItem(item);
114
+ }
115
+ }, [unselectItem]);
116
+ const unselectAll = useCallback(() => {
117
+ setSelectedMap((selectedMap) => {
118
+ if (Object.keys(selectedMap).length > 0) {
119
+ return {};
120
+ }
121
+ return selectedMap;
122
+ });
123
+ }, []);
124
+ const selectedItems = useMemo(() => Object.values(selectedMap), [selectedMap]);
125
+ const allSelected = useMemo(() => selectedItems.length === items.length, [items.length, selectedItems.length]);
126
+ return useMemo(() => ({
127
+ selectedItems,
128
+ selectItem,
129
+ unselectItem,
130
+ isSelected,
131
+ selectItems,
132
+ selectAll,
133
+ unselectAll,
134
+ allSelected,
135
+ keyFn,
136
+ unselectItems,
137
+ }), [
138
+ allSelected,
139
+ isSelected,
140
+ keyFn,
141
+ selectAll,
142
+ selectItem,
143
+ selectItems,
144
+ selectedItems,
145
+ unselectAll,
146
+ unselectItem,
147
+ unselectItems,
148
+ ]);
149
+ }
150
+ export function useSelectedInMemory(items, keyFn) {
151
+ const [selectedMap, setSelectedMap] = useState({});
152
+ useEffect(() => {
153
+ setSelectedMap((selectedMap) => {
154
+ let changed = false;
155
+ const itemsKeys = items.reduce((itemsKeys, item) => {
156
+ const key = keyFn(item);
157
+ itemsKeys[key] = item;
158
+ if (selectedMap[key] && selectedMap[key] !== item) {
159
+ changed = true;
160
+ selectedMap[key] = item;
161
+ }
162
+ return itemsKeys;
163
+ }, {});
164
+ const removeKeyMap = {};
165
+ for (const key in selectedMap) {
166
+ if (!itemsKeys[key]) {
167
+ removeKeyMap[key] = true;
168
+ }
169
+ }
170
+ const removeKeys = Object.keys(removeKeyMap);
171
+ if (removeKeys.length) {
172
+ changed = true;
173
+ for (const key of removeKeys) {
174
+ delete selectedMap[key];
175
+ }
176
+ }
177
+ return changed ? { ...selectedMap } : selectedMap;
178
+ });
179
+ }, [items, keyFn]);
180
+ const selectItem = useCallback((item) => {
181
+ setSelectedMap((selectedMap) => {
182
+ const itemKey = keyFn(item);
183
+ const existing = selectedMap[itemKey];
184
+ if (existing !== item) {
185
+ selectedMap = { ...selectedMap };
186
+ selectedMap[itemKey] = item;
187
+ }
188
+ return selectedMap;
189
+ });
190
+ }, [keyFn]);
191
+ const unselectItem = useCallback((item) => {
192
+ setSelectedMap((selectedMap) => {
193
+ const itemKey = keyFn(item);
194
+ const existing = selectedMap[itemKey];
195
+ if (existing) {
196
+ selectedMap = { ...selectedMap };
197
+ delete selectedMap[itemKey];
198
+ }
199
+ return selectedMap;
200
+ });
201
+ }, [keyFn]);
202
+ const isSelected = useCallback((item) => {
203
+ const itemKey = keyFn(item);
204
+ return selectedMap[itemKey] !== undefined;
205
+ }, [keyFn, selectedMap]);
206
+ const selectItems = useCallback((items) => {
207
+ setSelectedMap((selectedMap) => {
208
+ selectedMap = { ...selectedMap };
209
+ for (const item of items) {
210
+ const itemKey = keyFn(item);
211
+ selectedMap[itemKey] = item;
212
+ }
213
+ return selectedMap;
214
+ });
215
+ }, [keyFn]);
216
+ const selectAll = useCallback(() => {
217
+ selectItems(items);
218
+ }, [items, selectItems]);
219
+ const unselectAll = useCallback(() => {
220
+ setSelectedMap((selectedMap) => {
221
+ if (Object.keys(selectedMap).length > 0) {
222
+ return {};
223
+ }
224
+ return selectedMap;
225
+ });
226
+ }, []);
227
+ const selectedItems = useMemo(() => Object.values(selectedMap), [selectedMap]);
228
+ const allSelected = useMemo(() => selectedItems.length === items.length, [items.length, selectedItems.length]);
229
+ return useMemo(() => ({
230
+ selectedItems,
231
+ selectItem,
232
+ unselectItem,
233
+ isSelected,
234
+ selectItems,
235
+ selectAll,
236
+ unselectAll,
237
+ allSelected,
238
+ keyFn,
239
+ }), [
240
+ allSelected,
241
+ isSelected,
242
+ keyFn,
243
+ selectAll,
244
+ selectItem,
245
+ selectItems,
246
+ selectedItems,
247
+ unselectAll,
248
+ unselectItem,
249
+ ]);
250
+ }
251
+ export function useSorted(items) {
252
+ const [sort, setSort] = useState();
253
+ const { direction, sortFn } = sort ?? {};
254
+ const sorted = useMemo(() => {
255
+ if (sortFn) {
256
+ if (direction === 'asc') {
257
+ return [...items.sort(sortFn)];
258
+ }
259
+ else {
260
+ return [...items.sort(sortFn).reverse()];
261
+ }
262
+ }
263
+ else {
264
+ return items;
265
+ }
266
+ }, [direction, items, sortFn]);
267
+ return useMemo(() => ({ sorted, sort, setSort }), [sort, sorted]);
268
+ }
269
+ export function useFiltered(items, keyFn) {
270
+ const filterMapRef = useRef({
271
+ map: {},
272
+ });
273
+ const [filterFn, setFilterFnState] = useState();
274
+ const setFilterFn = useCallback((filterFn) => setFilterFnState(() => filterFn), []);
275
+ const [filtered, setFiltered] = useState([]);
276
+ useEffect(() => {
277
+ filterMapRef.current.map = {};
278
+ }, [filterFn]);
279
+ const cachedFilterFn = useCallback((item) => {
280
+ const key = keyFn(item);
281
+ let cached = filterMapRef.current.map[key];
282
+ if (!cached) {
283
+ cached = { item, passes: filterFn ? filterFn(item) : true };
284
+ filterMapRef.current.map[key] = cached;
285
+ }
286
+ else if (cached.item !== item) {
287
+ cached.item = item;
288
+ cached.passes = filterFn ? filterFn(item) : true;
289
+ }
290
+ return cached.passes;
291
+ }, [filterFn, keyFn]);
292
+ useEffect(() => {
293
+ if (filterFn) {
294
+ setFiltered(items.filter(cachedFilterFn));
295
+ }
296
+ else {
297
+ setFiltered(items);
298
+ }
299
+ }, [items, filterFn, cachedFilterFn]);
300
+ return useMemo(function memoFiltered() {
301
+ return { filtered, setFilterFn };
302
+ }, [filtered, setFilterFn]);
303
+ }
304
+ function useSearched(items, keyFn, defaultSearch) {
305
+ const searchMapRef = useRef({
306
+ map: {},
307
+ });
308
+ const [searchFn, setSearchFnState] = useState();
309
+ const setSearchFn = useCallback((searchFn) => setSearchFnState(() => searchFn), []);
310
+ const [searched, setSearched] = useState([]);
311
+ const [search, setSearchState] = useState(defaultSearch ?? '');
312
+ // eslint-disable-next-line react-hooks/exhaustive-deps
313
+ const setSearch = useCallback(debounce((search) => setSearchState(search), 200), []);
314
+ useEffect(() => {
315
+ searchMapRef.current.map = {};
316
+ }, [search, searchFn]);
317
+ const cachedSearchFn = useCallback((item) => {
318
+ const key = keyFn(item);
319
+ let cached = searchMapRef.current.map[key];
320
+ if (!cached) {
321
+ cached = { item, score: searchFn ? searchFn(item, search) : 0 };
322
+ searchMapRef.current.map[key] = cached;
323
+ }
324
+ else if (cached.item !== item) {
325
+ cached.item = item;
326
+ cached.score = searchFn ? searchFn(item, search) : 0;
327
+ }
328
+ return cached;
329
+ }, [keyFn, searchFn, search]);
330
+ useEffect(() => {
331
+ if (searchFn && search) {
332
+ setSearched(items
333
+ .map(cachedSearchFn)
334
+ .filter((cached) => cached.score < 0.5)
335
+ .sort((l, r) => l.score - r.score)
336
+ .map((cached) => cached.item));
337
+ }
338
+ else {
339
+ setSearched(items);
340
+ }
341
+ }, [search, items, searchFn, cachedSearchFn]);
342
+ return useMemo(function memoFiltered() {
343
+ return { searched, search, setSearch, setSearchFn };
344
+ }, [searched, search, setSearch, setSearchFn]);
345
+ }
346
+ export function usePaged(source) {
347
+ const [paged, setPaged] = useState([]);
348
+ const [page, setPage] = useState(1);
349
+ const [perPage, setPerPage] = useState(10);
350
+ useEffect(() => {
351
+ setPaged((paged) => {
352
+ const newPaged = source.slice((page - 1) * perPage, page * perPage);
353
+ if (paged.length !== newPaged.length) {
354
+ return newPaged;
355
+ }
356
+ let index = 0;
357
+ for (const item of newPaged) {
358
+ if (paged[index++] !== item) {
359
+ return newPaged;
360
+ }
361
+ }
362
+ return paged;
363
+ });
364
+ }, [page, perPage, source]);
365
+ useEffect(() => {
366
+ if (page > Math.ceil(source.length / perPage)) {
367
+ setPage(1);
368
+ }
369
+ }, [page, perPage, source.length]);
370
+ return useMemo(() => ({ paged, page, setPage, perPage, setPerPage }), [page, paged, perPage]);
371
+ }
@@ -0,0 +1,15 @@
1
+ import { Dispatch, SetStateAction } from 'react';
2
+ export interface IView {
3
+ page: number;
4
+ setPage: (page: number) => void;
5
+ perPage: number;
6
+ setPerPage: (perPage: number) => void;
7
+ sort: string;
8
+ setSort: (sort: string) => void;
9
+ sortDirection: 'asc' | 'desc';
10
+ setSortDirection: (sortDirection: 'asc' | 'desc') => void;
11
+ filters: Record<string, string[]>;
12
+ setFilters: Dispatch<SetStateAction<Record<string, string[]>>>;
13
+ clearAllFilters: () => void;
14
+ }
15
+ export declare function useView(view?: Partial<IView> | undefined, disableQueryString?: boolean): IView;
@@ -0,0 +1,108 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { useSearchParams } from 'react-router-dom';
3
+ export function useView(view, disableQueryString) {
4
+ const [searchParams, setSearchParams] = useSearchParams();
5
+ const [page, setPage] = useState(() => {
6
+ if (!disableQueryString) {
7
+ const queryPage = searchParams.get('page');
8
+ if (queryPage) {
9
+ const page = Number(queryPage);
10
+ if (Number.isInteger(page)) {
11
+ return page;
12
+ }
13
+ }
14
+ }
15
+ return view?.page ?? 1;
16
+ });
17
+ const [perPage, setPerPage] = useState(() => {
18
+ if (!disableQueryString) {
19
+ const queryPerPage = searchParams.get('perPage');
20
+ if (queryPerPage) {
21
+ const perPage = Number(queryPerPage);
22
+ if (Number.isInteger(perPage)) {
23
+ return perPage;
24
+ }
25
+ }
26
+ }
27
+ const localPerPage = localStorage.getItem('perPage');
28
+ if (localPerPage) {
29
+ const perPage = Number(localPerPage);
30
+ if (Number.isInteger(perPage)) {
31
+ return perPage;
32
+ }
33
+ }
34
+ return view?.perPage ?? 10;
35
+ });
36
+ const [sort, setSort] = useState(() => {
37
+ if (!disableQueryString) {
38
+ const querySort = searchParams.get('sort');
39
+ if (querySort) {
40
+ if (querySort.startsWith('-'))
41
+ return querySort.slice(1);
42
+ return querySort;
43
+ }
44
+ }
45
+ return view?.sort ?? '';
46
+ });
47
+ const [sortDirection, setSortDirection] = useState(() => {
48
+ if (!disableQueryString) {
49
+ const querySort = searchParams.get('sort');
50
+ if (querySort) {
51
+ if (querySort.startsWith('-'))
52
+ return 'desc';
53
+ return 'asc';
54
+ }
55
+ }
56
+ return view?.sortDirection ?? 'asc';
57
+ });
58
+ const [filters, setFilters] = useState(() => {
59
+ const filters = {};
60
+ for (const key of searchParams.keys()) {
61
+ switch (key) {
62
+ case 'sort':
63
+ case 'page':
64
+ case 'perPage':
65
+ break;
66
+ default: {
67
+ const value = searchParams.get(key);
68
+ if (value) {
69
+ const values = value.split(',');
70
+ filters[key] = values;
71
+ }
72
+ }
73
+ }
74
+ }
75
+ if (Object.keys(filters))
76
+ return filters;
77
+ return view?.filters ?? {};
78
+ });
79
+ const clearAllFilters = useCallback(() => setFilters({}), [setFilters]);
80
+ useEffect(() => {
81
+ if (disableQueryString)
82
+ return;
83
+ const newSearchParams = new URLSearchParams();
84
+ sortDirection === 'asc'
85
+ ? newSearchParams.set('sort', sort)
86
+ : newSearchParams.set('sort', `-${sort}`);
87
+ newSearchParams.set('page', page.toString());
88
+ newSearchParams.set('perPage', perPage.toString());
89
+ for (const filter in filters) {
90
+ newSearchParams.set(filter, filters[filter].join(','));
91
+ }
92
+ localStorage.setItem('perPage', perPage.toString());
93
+ setSearchParams(newSearchParams, { replace: true });
94
+ }, [sort, sortDirection, setSearchParams, disableQueryString, page, perPage, filters]);
95
+ return useMemo(() => ({
96
+ page,
97
+ setPage,
98
+ perPage,
99
+ setPerPage,
100
+ sort,
101
+ setSort,
102
+ sortDirection,
103
+ setSortDirection,
104
+ filters,
105
+ setFilters,
106
+ clearAllFilters,
107
+ }), [clearAllFilters, filters, page, perPage, sort, sortDirection]);
108
+ }
@@ -0,0 +1,3 @@
1
+ export declare function compareUnknowns(a: unknown | undefined | null, b: unknown | undefined | null): 0 | 1 | -1;
2
+ export declare function compareStrings(a: string | undefined | null, b: string | undefined | null): 0 | 1 | -1;
3
+ export declare function compareNumbers(a: number | undefined | null, b: number | undefined | null): 0 | 1 | -1;
@@ -0,0 +1,53 @@
1
+ /* istanbul ignore file */
2
+ export function compareUnknowns(a, b) {
3
+ /* istanbul ignore next */
4
+ if (a == undefined && b == undefined)
5
+ return 0;
6
+ /* istanbul ignore next */
7
+ if (a == undefined)
8
+ return 1;
9
+ /* istanbul ignore next */
10
+ if (b == undefined)
11
+ return -1;
12
+ /* istanbul ignore else */
13
+ if (typeof a === 'string') {
14
+ /* istanbul ignore else */
15
+ if (typeof b === 'string') {
16
+ return compareStrings(a, b);
17
+ }
18
+ else if (typeof b === 'number') {
19
+ return compareStrings(a, b.toString());
20
+ }
21
+ }
22
+ else if (typeof a === 'number') {
23
+ /* istanbul ignore else */
24
+ if (typeof b === 'number') {
25
+ return compareNumbers(a, b);
26
+ }
27
+ else if (typeof b === 'string') {
28
+ return compareStrings(a.toString(), b);
29
+ }
30
+ }
31
+ /* istanbul ignore next */
32
+ return 0;
33
+ }
34
+ /* istanbul ignore next */
35
+ export function compareStrings(a, b) {
36
+ if (a == undefined && b == undefined)
37
+ return 0;
38
+ if (a == undefined)
39
+ return 1;
40
+ if (b == undefined)
41
+ return -1;
42
+ return a < b ? -1 : a > b ? 1 : 0;
43
+ }
44
+ /* istanbul ignore next */
45
+ export function compareNumbers(a, b) {
46
+ if (a == undefined && b == undefined)
47
+ return 0;
48
+ if (a == undefined)
49
+ return 1;
50
+ if (b == undefined)
51
+ return -1;
52
+ return a < b ? -1 : a > b ? 1 : 0;
53
+ }
@@ -0,0 +1,2 @@
1
+ export declare function downloadTextFile(name: string, content: string): void;
2
+ export declare function downloadCvsFile(name: string, content: string[]): void;
@@ -0,0 +1,18 @@
1
+ export function downloadTextFile(name, content) {
2
+ const file = new Blob(content.split('/n'), { type: 'text/plain' });
3
+ const element = document.createElement('a');
4
+ element.href = URL.createObjectURL(file);
5
+ element.download = name + '.txt';
6
+ document.body.appendChild(element);
7
+ element.click();
8
+ document.body.removeChild(element);
9
+ }
10
+ export function downloadCvsFile(name, content) {
11
+ const file = new Blob(content, { type: 'text/csv' });
12
+ const element = document.createElement('a');
13
+ element.href = URL.createObjectURL(file);
14
+ element.download = name + '.csv';
15
+ document.body.appendChild(element);
16
+ element.click();
17
+ document.body.removeChild(element);
18
+ }
@@ -0,0 +1 @@
1
+ export declare function randomString(length: number, base?: number): string;