@ansible/ansible-ui-framework 0.0.190

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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;