@olenbetong/appframe-ds 1.3.4 → 1.3.6

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 (178) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.json +3 -3
  3. package/scripts/copyAssets.mjs +27 -27
  4. package/src/AfLookup/Lookup.css +129 -129
  5. package/src/AfLookup/Lookup.tsx +186 -186
  6. package/src/AfLookup/LookupCombobox.css +78 -78
  7. package/src/AfLookup/LookupCombobox.tsx +237 -237
  8. package/src/AfLookup/LookupEdit.tsx +130 -130
  9. package/src/AfLookup/LookupGrid.tsx +124 -124
  10. package/src/AfLookup/index.ts +5 -5
  11. package/src/autocomplete/Autocomplete.css +256 -256
  12. package/src/autocomplete/Autocomplete.tsx +320 -320
  13. package/src/autocomplete/AutocompleteFrame.tsx +168 -168
  14. package/src/autocomplete/Combobox.tsx +286 -286
  15. package/src/autocomplete/index.ts +4 -4
  16. package/src/autocomplete/types.ts +204 -204
  17. package/src/autocomplete/utils.ts +183 -183
  18. package/src/binding/BoundNumericTextField.tsx +102 -102
  19. package/src/binding/BoundTextField.tsx +49 -49
  20. package/src/binding/CancelButton.tsx +30 -30
  21. package/src/binding/CancelIconButton.tsx +30 -30
  22. package/src/binding/DataEditToolbar.css +23 -23
  23. package/src/binding/DataEditToolbar.tsx +84 -84
  24. package/src/binding/DeleteButton.tsx +35 -35
  25. package/src/binding/DeleteIconButton.tsx +35 -35
  26. package/src/binding/RefreshButton.tsx +31 -31
  27. package/src/binding/RefreshIconButton.tsx +33 -33
  28. package/src/binding/RefreshRowIconButton.tsx +33 -33
  29. package/src/binding/SaveButton.tsx +37 -37
  30. package/src/binding/SaveIconButton.tsx +38 -38
  31. package/src/container/Container.tsx +49 -49
  32. package/src/container/index.ts +1 -1
  33. package/src/cutoff/CutOffPicker.css +12 -12
  34. package/src/cutoff/CutOffPicker.tsx +165 -165
  35. package/src/cutoff/context.tsx +194 -194
  36. package/src/cutoff/dsCutOffDates.ts +21 -21
  37. package/src/cutoff/index.ts +4 -4
  38. package/src/cutoff/utils.test.ts +59 -59
  39. package/src/cutoff/utils.ts +276 -276
  40. package/src/filter/ChipListInput.tsx +79 -79
  41. package/src/filter/FieldFilterPanel.css +150 -150
  42. package/src/filter/FieldFilterPanel.tsx +309 -309
  43. package/src/filter/FilterBuilder.css +152 -152
  44. package/src/filter/FilterBuilder.test.tsx +153 -153
  45. package/src/filter/FilterBuilder.tsx +435 -435
  46. package/src/filter/FilterEditor.css +231 -231
  47. package/src/filter/FilterEditor.test.tsx +285 -285
  48. package/src/filter/FilterEditor.tsx +75 -75
  49. package/src/filter/FilterGroupEditor.tsx +160 -160
  50. package/src/filter/FilterNameDialog.css +22 -22
  51. package/src/filter/FilterNameDialog.tsx +81 -81
  52. package/src/filter/FilterRow.tsx +117 -117
  53. package/src/filter/FilterShareDialog.css +28 -28
  54. package/src/filter/FilterShareDialog.tsx +201 -201
  55. package/src/filter/FilterStringField.tsx +78 -78
  56. package/src/filter/FilterValueEditor.tsx +239 -239
  57. package/src/filter/SavedFilterTree.css +107 -107
  58. package/src/filter/SavedFilterTree.test.tsx +94 -94
  59. package/src/filter/SavedFilterTree.tsx +218 -218
  60. package/src/filter/fieldFilter.test.ts +158 -158
  61. package/src/filter/fieldFilter.ts +139 -139
  62. package/src/filter/index.ts +27 -27
  63. package/src/filter/types.ts +38 -38
  64. package/src/filter/useDistinctValues.test.ts +102 -102
  65. package/src/filter/useDistinctValues.ts +230 -230
  66. package/src/global.d.ts +11 -11
  67. package/src/grid/AfGridColumnsPanel.tsx +118 -118
  68. package/src/grid/AfGridContext.tsx +51 -51
  69. package/src/grid/AfGridError.tsx +46 -46
  70. package/src/grid/AfHeaderFilterCell.tsx +147 -147
  71. package/src/grid/AfHeaderFilterPanel.tsx +111 -111
  72. package/src/grid/GridEditLookup.css +12 -12
  73. package/src/grid/GridEditLookup.tsx +121 -121
  74. package/src/grid/Toolbar.tsx +233 -233
  75. package/src/grid/editing.ts +100 -100
  76. package/src/grid/filter.ts +78 -78
  77. package/src/grid/formatters.ts +48 -48
  78. package/src/grid/index.css +245 -245
  79. package/src/grid/index.tsx +503 -503
  80. package/src/grid/license.ts +48 -48
  81. package/src/grid/localization.ts +369 -369
  82. package/src/grid/slots/index.tsx +334 -334
  83. package/src/grid/slots/slots.css +64 -64
  84. package/src/grid/slots/slots.test.tsx +39 -39
  85. package/src/grid/theme.tsx +138 -138
  86. package/src/grid/useAfColumns.tsx +371 -371
  87. package/src/grid/useAfCurrentIndex.ts +63 -63
  88. package/src/grid/useAfData.ts +20 -20
  89. package/src/grid/useAfFilter.ts +160 -160
  90. package/src/grid/useAfFilterFields.ts +55 -55
  91. package/src/grid/useAfGridApi.ts +71 -71
  92. package/src/grid/useAfKeyBindings.ts +36 -36
  93. package/src/grid/useAfNewItemRow.ts +258 -258
  94. package/src/grid/useAfPagination.ts +53 -53
  95. package/src/grid/useAfPersistedState.ts +230 -230
  96. package/src/grid/useAfRowEditModel.ts +145 -145
  97. package/src/grid/useAfRowGrouping.ts +47 -47
  98. package/src/grid/useAfServerAggregation.test.ts +68 -68
  99. package/src/grid/useAfServerAggregation.ts +158 -158
  100. package/src/grid/useAfSortModel.ts +84 -84
  101. package/src/i18n.ts +13 -13
  102. package/src/index.ts +14 -14
  103. package/src/input/InputAdornments.css +84 -84
  104. package/src/input/InputAdornments.tsx +102 -102
  105. package/src/input/index.ts +1 -1
  106. package/src/layout/AppMenu.tsx +74 -74
  107. package/src/layout/AppMenuDrawer.css +70 -70
  108. package/src/layout/AppMenuDrawer.tsx +122 -122
  109. package/src/layout/ErrorBoundary.tsx +162 -162
  110. package/src/layout/PageContainer.css +48 -48
  111. package/src/layout/PageContainer.tsx +41 -41
  112. package/src/layout/ResponsiveDialog.css +86 -86
  113. package/src/layout/ResponsiveDialog.tsx +55 -55
  114. package/src/layout/index.tsx +69 -69
  115. package/src/link/Link.tsx +23 -23
  116. package/src/link/index.ts +1 -1
  117. package/src/mdi/Mdi.css +138 -138
  118. package/src/mdi/Mdi.test.tsx +91 -91
  119. package/src/mdi/Mdi.tsx +141 -141
  120. package/src/mdi/MdiConfirmCloseDialog.tsx +47 -47
  121. package/src/mdi/MdiContext.tsx +59 -59
  122. package/src/mdi/MdiDocumentContext.tsx +60 -60
  123. package/src/mdi/MdiPanel.tsx +61 -61
  124. package/src/mdi/MdiRestore.test.tsx +86 -86
  125. package/src/mdi/MdiTab.tsx +30 -30
  126. package/src/mdi/controller.test.ts +382 -382
  127. package/src/mdi/controller.ts +656 -656
  128. package/src/mdi/dockviewHost.ts +132 -132
  129. package/src/mdi/fakeHost.ts +200 -200
  130. package/src/mdi/host.ts +44 -44
  131. package/src/mdi/ids.test.ts +20 -20
  132. package/src/mdi/ids.ts +18 -18
  133. package/src/mdi/index.ts +31 -31
  134. package/src/mdi/layoutRestore.test.ts +156 -156
  135. package/src/mdi/layoutRestore.ts +311 -311
  136. package/src/mdi/layoutSpec.test.ts +143 -143
  137. package/src/mdi/layoutSpec.ts +214 -214
  138. package/src/mdi/layoutStore.test.ts +93 -93
  139. package/src/mdi/layoutStore.ts +99 -99
  140. package/src/mdi/memorySettingsStore.ts +21 -21
  141. package/src/mdi/registry.ts +34 -34
  142. package/src/mdi/testTypes.ts +37 -37
  143. package/src/mdi/theme.ts +22 -22
  144. package/src/mdi/types.ts +214 -214
  145. package/src/page/Page.css +53 -53
  146. package/src/page/PageBlock.css +64 -64
  147. package/src/page/PageBlock.tsx +92 -92
  148. package/src/page/PageHeader.css +88 -88
  149. package/src/page/PageHeader.tsx +115 -115
  150. package/src/page/PageTitle.css +5 -5
  151. package/src/page/PageTitle.tsx +21 -21
  152. package/src/page/index.ts +4 -4
  153. package/src/paper/Paper.tsx +52 -52
  154. package/src/progress/LinearProgress.css +93 -93
  155. package/src/progress/LinearProgress.tsx +104 -104
  156. package/src/progress/index.ts +1 -1
  157. package/src/report-downloader/ReportDownloader.css +67 -67
  158. package/src/report-downloader/components/StatusItem.tsx +87 -87
  159. package/src/report-downloader/components/StatusList.tsx +28 -28
  160. package/src/report-downloader/index.ts +41 -41
  161. package/src/report-downloader/status.tsx +92 -92
  162. package/src/sortable/DropIndicator.css +51 -51
  163. package/src/sortable/DropIndicator.tsx +27 -27
  164. package/src/sortable/SortableItemList.css +21 -21
  165. package/src/sortable/SortableItemList.tsx +264 -264
  166. package/src/sortable/index.ts +8 -8
  167. package/src/sortable/reorderSortOrder.ts +126 -126
  168. package/src/sortable/sortOrder.ts +101 -101
  169. package/src/sortable/types.ts +9 -9
  170. package/src/sortable/useSortableContainer.ts +53 -53
  171. package/src/sortable/useSortableItem.ts +95 -95
  172. package/src/sortable/useSortableList.ts +117 -117
  173. package/src/test/happy-dom-document-class.ts +24 -24
  174. package/src/test/setup.ts +12 -12
  175. package/src/theme/useDsColorScheme.ts +53 -53
  176. package/tsconfig.build.json +9 -9
  177. package/tsconfig.json +8 -8
  178. package/vitest.config.ts +10 -10
@@ -1,87 +1,87 @@
1
- import { Button, Paragraph, Spinner } from "@digdir/designsystemet-react";
2
- import { CheckmarkCircleFillIcon, ExclamationmarkTriangleFillIcon, XMarkOctagonFillIcon } from "@navikt/aksel-icons";
3
- import { type DownloadItem } from "@olenbetong/appframe-core";
4
-
5
- import clsx from "clsx";
6
- import { getLocalizedString } from "../../i18n.js";
7
-
8
- const statuses: Record<string, { icon: React.ReactNode; text: string }> = {
9
- cancelled: {
10
- icon: <ExclamationmarkTriangleFillIcon aria-hidden />,
11
- text: "Report cancelled",
12
- },
13
- error: {
14
- icon: <XMarkOctagonFillIcon aria-hidden />,
15
- text: "An error occurred",
16
- },
17
- requested: {
18
- icon: <Spinner aria-hidden data-size="sm" />,
19
- text: "Report request sent. Please wait while our server generates the report",
20
- },
21
- downloading: {
22
- icon: <Spinner aria-hidden data-size="sm" />,
23
- text: "Downloading report",
24
- },
25
- downloaded: {
26
- icon: <Spinner aria-hidden data-size="sm" />,
27
- text: "Sending report to browser",
28
- },
29
- completed: {
30
- icon: <CheckmarkCircleFillIcon aria-hidden />,
31
- text: "The report was successfully generated and downloaded",
32
- },
33
- };
34
-
35
- function formatBytes(bytes: number, decimals: number) {
36
- if (bytes === 0) return "0 Bytes";
37
-
38
- let k = 1024;
39
- let dm = decimals || 2;
40
- let sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
41
- let i = Math.floor(Math.log(bytes) / Math.log(k));
42
-
43
- return `${Number.parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
44
- }
45
-
46
- export type StatusItemProps = {
47
- item: DownloadItem;
48
- onCancelItem: () => void;
49
- onCloseItem: () => void;
50
- };
51
-
52
- export function StatusItem({ onCancelItem, onCloseItem, item }: StatusItemProps) {
53
- let { bytesReceived = 0, error, status = "requested" } = item;
54
-
55
- let icon = statuses[status]?.icon;
56
- let text = statuses[status] ? getLocalizedString(statuses[status].text) : "";
57
- let working = ["requested", "downloading"].includes(status);
58
- let stopped = ["cancelled", "completed", "error"].includes(status);
59
-
60
- if (working) {
61
- text += "...";
62
- }
63
-
64
- if (status === "error" && error) {
65
- text = error;
66
- } else if (status === "downloading" && bytesReceived) {
67
- text += ` (${formatBytes(bytesReceived, 2)})`;
68
- }
69
-
70
- return (
71
- <div className="ObReportDownloader-item">
72
- <div className={clsx("ObReportDownloader-icon", status)}>{icon}</div>
73
- <Paragraph className="ObReportDownloader-itemText">{text}</Paragraph>
74
- <div>
75
- {stopped ? (
76
- <Button variant="secondary" data-size="sm" onClick={onCloseItem}>
77
- {getLocalizedString("Close")}
78
- </Button>
79
- ) : (
80
- <Button variant="secondary" data-size="sm" onClick={onCancelItem}>
81
- {getLocalizedString("Cancel")}
82
- </Button>
83
- )}
84
- </div>
85
- </div>
86
- );
87
- }
1
+ import { Button, Paragraph, Spinner } from "@digdir/designsystemet-react";
2
+ import { CheckmarkCircleFillIcon, ExclamationmarkTriangleFillIcon, XMarkOctagonFillIcon } from "@navikt/aksel-icons";
3
+ import { type DownloadItem } from "@olenbetong/appframe-core";
4
+
5
+ import clsx from "clsx";
6
+ import { getLocalizedString } from "../../i18n.js";
7
+
8
+ const statuses: Record<string, { icon: React.ReactNode; text: string }> = {
9
+ cancelled: {
10
+ icon: <ExclamationmarkTriangleFillIcon aria-hidden />,
11
+ text: "Report cancelled",
12
+ },
13
+ error: {
14
+ icon: <XMarkOctagonFillIcon aria-hidden />,
15
+ text: "An error occurred",
16
+ },
17
+ requested: {
18
+ icon: <Spinner aria-hidden data-size="sm" />,
19
+ text: "Report request sent. Please wait while our server generates the report",
20
+ },
21
+ downloading: {
22
+ icon: <Spinner aria-hidden data-size="sm" />,
23
+ text: "Downloading report",
24
+ },
25
+ downloaded: {
26
+ icon: <Spinner aria-hidden data-size="sm" />,
27
+ text: "Sending report to browser",
28
+ },
29
+ completed: {
30
+ icon: <CheckmarkCircleFillIcon aria-hidden />,
31
+ text: "The report was successfully generated and downloaded",
32
+ },
33
+ };
34
+
35
+ function formatBytes(bytes: number, decimals: number) {
36
+ if (bytes === 0) return "0 Bytes";
37
+
38
+ let k = 1024;
39
+ let dm = decimals || 2;
40
+ let sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
41
+ let i = Math.floor(Math.log(bytes) / Math.log(k));
42
+
43
+ return `${Number.parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
44
+ }
45
+
46
+ export type StatusItemProps = {
47
+ item: DownloadItem;
48
+ onCancelItem: () => void;
49
+ onCloseItem: () => void;
50
+ };
51
+
52
+ export function StatusItem({ onCancelItem, onCloseItem, item }: StatusItemProps) {
53
+ let { bytesReceived = 0, error, status = "requested" } = item;
54
+
55
+ let icon = statuses[status]?.icon;
56
+ let text = statuses[status] ? getLocalizedString(statuses[status].text) : "";
57
+ let working = ["requested", "downloading"].includes(status);
58
+ let stopped = ["cancelled", "completed", "error"].includes(status);
59
+
60
+ if (working) {
61
+ text += "...";
62
+ }
63
+
64
+ if (status === "error" && error) {
65
+ text = error;
66
+ } else if (status === "downloading" && bytesReceived) {
67
+ text += ` (${formatBytes(bytesReceived, 2)})`;
68
+ }
69
+
70
+ return (
71
+ <div className="ObReportDownloader-item">
72
+ <div className={clsx("ObReportDownloader-icon", status)}>{icon}</div>
73
+ <Paragraph className="ObReportDownloader-itemText">{text}</Paragraph>
74
+ <div>
75
+ {stopped ? (
76
+ <Button variant="secondary" data-size="sm" onClick={onCloseItem}>
77
+ {getLocalizedString("Close")}
78
+ </Button>
79
+ ) : (
80
+ <Button variant="secondary" data-size="sm" onClick={onCancelItem}>
81
+ {getLocalizedString("Cancel")}
82
+ </Button>
83
+ )}
84
+ </div>
85
+ </div>
86
+ );
87
+ }
@@ -1,28 +1,28 @@
1
- import type { DownloadItem } from "@olenbetong/appframe-core";
2
-
3
- import clsx from "clsx";
4
-
5
- import { StatusItem } from "./StatusItem.js";
6
-
7
- export type StatusListProps = {
8
- onCloseItem: (id: number) => void;
9
- onCancelItem: (id: number) => void;
10
- items: (DownloadItem & { id: number })[];
11
- };
12
-
13
- export function StatusList({ items, onCancelItem, onCloseItem }: StatusListProps) {
14
- return (
15
- <div className="ObReportDownloader-root">
16
- <div className={clsx("ObReportDownloader-list", items.length === 0 && "closed")}>
17
- {items.map((item) => (
18
- <StatusItem
19
- key={item.id}
20
- onCancelItem={() => onCancelItem(item.id)}
21
- onCloseItem={() => onCloseItem(item.id)}
22
- item={item}
23
- />
24
- ))}
25
- </div>
26
- </div>
27
- );
28
- }
1
+ import type { DownloadItem } from "@olenbetong/appframe-core";
2
+
3
+ import clsx from "clsx";
4
+
5
+ import { StatusItem } from "./StatusItem.js";
6
+
7
+ export type StatusListProps = {
8
+ onCloseItem: (id: number) => void;
9
+ onCancelItem: (id: number) => void;
10
+ items: (DownloadItem & { id: number })[];
11
+ };
12
+
13
+ export function StatusList({ items, onCancelItem, onCloseItem }: StatusListProps) {
14
+ return (
15
+ <div className="ObReportDownloader-root">
16
+ <div className={clsx("ObReportDownloader-list", items.length === 0 && "closed")}>
17
+ {items.map((item) => (
18
+ <StatusItem
19
+ key={item.id}
20
+ onCancelItem={() => onCancelItem(item.id)}
21
+ onCloseItem={() => onCloseItem(item.id)}
22
+ item={item}
23
+ />
24
+ ))}
25
+ </div>
26
+ </div>
27
+ );
28
+ }
@@ -1,41 +1,41 @@
1
- import { downloadRequest } from "@olenbetong/appframe-core";
2
- import type { DataObjectReportParams } from "@olenbetong/appframe-data";
3
- import { exportOptionsFromDataObject, getDataObjectReportRequest } from "@olenbetong/appframe-data";
4
-
5
- import downloader, { mountStatusDisplay } from "./status.js";
6
-
7
- export function openDataObjectReportWithProgress(params: DataObjectReportParams) {
8
- let { dataObject, fileName, format } = params;
9
- let { controller, request } = getDataObjectReportRequest(params);
10
-
11
- return downloadRequestWithProgress(request, {
12
- controller,
13
- fileName: `${fileName ?? dataObject}.${format}`,
14
- });
15
- }
16
-
17
- export type DownloadParameters = {
18
- controller?: AbortController;
19
- fileName: string;
20
- };
21
-
22
- export function downloadRequestWithProgress(request: Request, { controller, fileName }: DownloadParameters) {
23
- let abortController = controller ?? new AbortController();
24
-
25
- mountStatusDisplay();
26
-
27
- let id = downloader.start({}, controller);
28
-
29
- function updateProgress(progress: any) {
30
- downloader.update(id, progress);
31
-
32
- if (["cancelled", "completed"].includes(progress.status)) {
33
- setTimeout(() => downloader.remove(id), 1500);
34
- }
35
- }
36
-
37
- downloadRequest(request, { onProgress: updateProgress, fileName, signal: abortController.signal });
38
- }
39
-
40
- export type { DataObjectReportParams };
41
- export { exportOptionsFromDataObject, getDataObjectReportRequest };
1
+ import { downloadRequest } from "@olenbetong/appframe-core";
2
+ import type { DataObjectReportParams } from "@olenbetong/appframe-data";
3
+ import { exportOptionsFromDataObject, getDataObjectReportRequest } from "@olenbetong/appframe-data";
4
+
5
+ import downloader, { mountStatusDisplay } from "./status.js";
6
+
7
+ export function openDataObjectReportWithProgress(params: DataObjectReportParams) {
8
+ let { dataObject, fileName, format } = params;
9
+ let { controller, request } = getDataObjectReportRequest(params);
10
+
11
+ return downloadRequestWithProgress(request, {
12
+ controller,
13
+ fileName: `${fileName ?? dataObject}.${format}`,
14
+ });
15
+ }
16
+
17
+ export type DownloadParameters = {
18
+ controller?: AbortController;
19
+ fileName: string;
20
+ };
21
+
22
+ export function downloadRequestWithProgress(request: Request, { controller, fileName }: DownloadParameters) {
23
+ let abortController = controller ?? new AbortController();
24
+
25
+ mountStatusDisplay();
26
+
27
+ let id = downloader.start({}, controller);
28
+
29
+ function updateProgress(progress: any) {
30
+ downloader.update(id, progress);
31
+
32
+ if (["cancelled", "completed"].includes(progress.status)) {
33
+ setTimeout(() => downloader.remove(id), 1500);
34
+ }
35
+ }
36
+
37
+ downloadRequest(request, { onProgress: updateProgress, fileName, signal: abortController.signal });
38
+ }
39
+
40
+ export type { DataObjectReportParams };
41
+ export { exportOptionsFromDataObject, getDataObjectReportRequest };
@@ -1,92 +1,92 @@
1
- import "./ReportDownloader.css";
2
-
3
- import type { DownloadItem } from "@olenbetong/appframe-core";
4
-
5
- import mitt from "mitt";
6
- import { useEffect, useState } from "react";
7
- import { createRoot } from "react-dom/client";
8
-
9
- import { StatusList } from "./components/StatusList.js";
10
-
11
- class Downloader {
12
- eventHandler = mitt<{ update: undefined }>();
13
- #items: (DownloadItem & { id: number })[] = [];
14
- #id = 1;
15
- #controllers: Record<number, AbortController> = {};
16
-
17
- start(item: Partial<DownloadItem>, controller?: AbortController) {
18
- this.eventHandler.emit("update");
19
-
20
- let id = this.#id++;
21
- this.#items.push({ id, status: "requested", ...item });
22
- this.#controllers[id] = controller ?? new AbortController();
23
-
24
- return id;
25
- }
26
-
27
- cancel(id: number) {
28
- this.#controllers[id]?.abort();
29
- }
30
-
31
- update(id: number, item: DownloadItem) {
32
- this.#items = this.#items.map((i) => (i.id === id ? { ...i, ...item } : i));
33
- this.eventHandler.emit("update");
34
- }
35
-
36
- remove(id: number) {
37
- this.#items = this.#items.filter((i) => i.id !== id);
38
- this.eventHandler.emit("update");
39
- }
40
-
41
- get items() {
42
- return this.#items;
43
- }
44
- }
45
-
46
- const downloader = new Downloader();
47
-
48
- function StatusDisplay() {
49
- let [items, setItems] = useState<(DownloadItem & { id: number })[]>([]);
50
-
51
- useEffect(() => {
52
- function handleUpdate() {
53
- setItems([...downloader.items]);
54
- }
55
-
56
- handleUpdate();
57
-
58
- downloader.eventHandler.on("update", handleUpdate);
59
-
60
- return () => {
61
- downloader.eventHandler.off("update", handleUpdate);
62
- };
63
- }, []);
64
-
65
- return (
66
- <StatusList
67
- items={items}
68
- onCloseItem={(id: number) => downloader.remove(id)}
69
- onCancelItem={(id: number) => downloader.cancel(id)}
70
- />
71
- );
72
- }
73
-
74
- let mounted = false;
75
-
76
- /**
77
- * Mounts the download status list into its own root at the end of `<body>`.
78
- * Called the first time a download is started, so importing the module doesn't
79
- * touch the DOM (which would break server rendering and tests).
80
- */
81
- function mountStatusDisplay() {
82
- if (mounted || typeof document === "undefined") return;
83
-
84
- mounted = true;
85
-
86
- let element = document.createElement("div");
87
- document.body.append(element);
88
- createRoot(element).render(<StatusDisplay />);
89
- }
90
-
91
- export { mountStatusDisplay };
92
- export default downloader;
1
+ import "./ReportDownloader.css";
2
+
3
+ import type { DownloadItem } from "@olenbetong/appframe-core";
4
+
5
+ import mitt from "mitt";
6
+ import { useEffect, useState } from "react";
7
+ import { createRoot } from "react-dom/client";
8
+
9
+ import { StatusList } from "./components/StatusList.js";
10
+
11
+ class Downloader {
12
+ eventHandler = mitt<{ update: undefined }>();
13
+ #items: (DownloadItem & { id: number })[] = [];
14
+ #id = 1;
15
+ #controllers: Record<number, AbortController> = {};
16
+
17
+ start(item: Partial<DownloadItem>, controller?: AbortController) {
18
+ this.eventHandler.emit("update");
19
+
20
+ let id = this.#id++;
21
+ this.#items.push({ id, status: "requested", ...item });
22
+ this.#controllers[id] = controller ?? new AbortController();
23
+
24
+ return id;
25
+ }
26
+
27
+ cancel(id: number) {
28
+ this.#controllers[id]?.abort();
29
+ }
30
+
31
+ update(id: number, item: DownloadItem) {
32
+ this.#items = this.#items.map((i) => (i.id === id ? { ...i, ...item } : i));
33
+ this.eventHandler.emit("update");
34
+ }
35
+
36
+ remove(id: number) {
37
+ this.#items = this.#items.filter((i) => i.id !== id);
38
+ this.eventHandler.emit("update");
39
+ }
40
+
41
+ get items() {
42
+ return this.#items;
43
+ }
44
+ }
45
+
46
+ const downloader = new Downloader();
47
+
48
+ function StatusDisplay() {
49
+ let [items, setItems] = useState<(DownloadItem & { id: number })[]>([]);
50
+
51
+ useEffect(() => {
52
+ function handleUpdate() {
53
+ setItems([...downloader.items]);
54
+ }
55
+
56
+ handleUpdate();
57
+
58
+ downloader.eventHandler.on("update", handleUpdate);
59
+
60
+ return () => {
61
+ downloader.eventHandler.off("update", handleUpdate);
62
+ };
63
+ }, []);
64
+
65
+ return (
66
+ <StatusList
67
+ items={items}
68
+ onCloseItem={(id: number) => downloader.remove(id)}
69
+ onCancelItem={(id: number) => downloader.cancel(id)}
70
+ />
71
+ );
72
+ }
73
+
74
+ let mounted = false;
75
+
76
+ /**
77
+ * Mounts the download status list into its own root at the end of `<body>`.
78
+ * Called the first time a download is started, so importing the module doesn't
79
+ * touch the DOM (which would break server rendering and tests).
80
+ */
81
+ function mountStatusDisplay() {
82
+ if (mounted || typeof document === "undefined") return;
83
+
84
+ mounted = true;
85
+
86
+ let element = document.createElement("div");
87
+ document.body.append(element);
88
+ createRoot(element).render(<StatusDisplay />);
89
+ }
90
+
91
+ export { mountStatusDisplay };
92
+ export default downloader;
@@ -1,51 +1,51 @@
1
- :root {
2
- --ob-drop-indicator-thickness: 0.125rem;
3
- --ob-drop-indicator-color: #3f3fa3;
4
- --ob-drop-indicator-terminal-size: 0.75rem;
5
- }
6
-
7
- .ObDropIndicator-root {
8
- position: absolute;
9
- z-index: 10;
10
-
11
- display: flex;
12
- align-items: center;
13
-
14
- pointer-events: none;
15
-
16
- &::before {
17
- content: "";
18
-
19
- border: var(--ob-drop-indicator-thickness) solid var(--ob-drop-indicator-color);
20
- border-radius: 50%;
21
-
22
- width: var(--ob-drop-indicator-terminal-size);
23
- height: var(--ob-drop-indicator-terminal-size);
24
- }
25
-
26
- &::after {
27
- content: "";
28
-
29
- height: var(--ob-drop-indicator-thickness);
30
- flex-grow: 1;
31
- background-color: var(--ob-drop-indicator-color);
32
- }
33
-
34
- &.ObDropIndicator-top,
35
- &.ObDropIndicator-bottom {
36
- height: var(--ob-drop-indicator-thickness);
37
-
38
- left: calc(-1 * var(--ob-drop-indicator-terminal-size) / 2);
39
- width: calc(100% + var(--ob-drop-indicator-terminal-size) / 2);
40
- }
41
-
42
- &.ObDropIndicator-top {
43
- top: var(--ob-drop-indicator-gap, 0rem);
44
- transform: translateY(calc(var(--ob-drop-indicator-thickness) / -2));
45
- }
46
-
47
- &.ObDropIndicator-bottom {
48
- bottom: var(--ob-drop-indicator-gap, 0rem);
49
- transform: translateY(calc(var(--ob-drop-indicator-thickness) / 2));
50
- }
51
- }
1
+ :root {
2
+ --ob-drop-indicator-thickness: 0.125rem;
3
+ --ob-drop-indicator-color: #3f3fa3;
4
+ --ob-drop-indicator-terminal-size: 0.75rem;
5
+ }
6
+
7
+ .ObDropIndicator-root {
8
+ position: absolute;
9
+ z-index: 10;
10
+
11
+ display: flex;
12
+ align-items: center;
13
+
14
+ pointer-events: none;
15
+
16
+ &::before {
17
+ content: "";
18
+
19
+ border: var(--ob-drop-indicator-thickness) solid var(--ob-drop-indicator-color);
20
+ border-radius: 50%;
21
+
22
+ width: var(--ob-drop-indicator-terminal-size);
23
+ height: var(--ob-drop-indicator-terminal-size);
24
+ }
25
+
26
+ &::after {
27
+ content: "";
28
+
29
+ height: var(--ob-drop-indicator-thickness);
30
+ flex-grow: 1;
31
+ background-color: var(--ob-drop-indicator-color);
32
+ }
33
+
34
+ &.ObDropIndicator-top,
35
+ &.ObDropIndicator-bottom {
36
+ height: var(--ob-drop-indicator-thickness);
37
+
38
+ left: calc(-1 * var(--ob-drop-indicator-terminal-size) / 2);
39
+ width: calc(100% + var(--ob-drop-indicator-terminal-size) / 2);
40
+ }
41
+
42
+ &.ObDropIndicator-top {
43
+ top: var(--ob-drop-indicator-gap, 0rem);
44
+ transform: translateY(calc(var(--ob-drop-indicator-thickness) / -2));
45
+ }
46
+
47
+ &.ObDropIndicator-bottom {
48
+ bottom: var(--ob-drop-indicator-gap, 0rem);
49
+ transform: translateY(calc(var(--ob-drop-indicator-thickness) / 2));
50
+ }
51
+ }
@@ -1,27 +1,27 @@
1
- import "./DropIndicator.css";
2
-
3
- import type { Edge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/types";
4
- import clsx from "clsx";
5
- import type { CSSProperties } from "react";
6
-
7
- export type DropIndicatorProps = {
8
- /** Which edge of the item the dragged item will be dropped on */
9
- edge: Edge;
10
- /** Distance from the edge, e.g. to account for a gap between items */
11
- gap?: string;
12
- className?: string;
13
- };
14
-
15
- /**
16
- * Line rendered on the edge of a list item to show where a dragged item will
17
- * be placed. Must be rendered inside a positioned (`position: relative`)
18
- * element, normally the list item itself.
19
- */
20
- export function DropIndicator({ edge, gap, className }: DropIndicatorProps) {
21
- return (
22
- <div
23
- className={clsx("ObDropIndicator-root", `ObDropIndicator-${edge}`, className)}
24
- style={{ "--ob-drop-indicator-gap": gap ?? "0rem" } as CSSProperties}
25
- />
26
- );
27
- }
1
+ import "./DropIndicator.css";
2
+
3
+ import type { Edge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/types";
4
+ import clsx from "clsx";
5
+ import type { CSSProperties } from "react";
6
+
7
+ export type DropIndicatorProps = {
8
+ /** Which edge of the item the dragged item will be dropped on */
9
+ edge: Edge;
10
+ /** Distance from the edge, e.g. to account for a gap between items */
11
+ gap?: string;
12
+ className?: string;
13
+ };
14
+
15
+ /**
16
+ * Line rendered on the edge of a list item to show where a dragged item will
17
+ * be placed. Must be rendered inside a positioned (`position: relative`)
18
+ * element, normally the list item itself.
19
+ */
20
+ export function DropIndicator({ edge, gap, className }: DropIndicatorProps) {
21
+ return (
22
+ <div
23
+ className={clsx("ObDropIndicator-root", `ObDropIndicator-${edge}`, className)}
24
+ style={{ "--ob-drop-indicator-gap": gap ?? "0rem" } as CSSProperties}
25
+ />
26
+ );
27
+ }