@olenbetong/appframe-ds 1.2.0 → 1.3.1

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 (177) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/package.json +7 -7
  3. package/scripts/copyAssets.mjs +27 -27
  4. package/src/AfLookup/Lookup.css +129 -129
  5. package/src/AfLookup/Lookup.tsx +184 -184
  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 -0
  23. package/src/binding/DataEditToolbar.tsx +84 -92
  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 +259 -259
  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 +220 -220
  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 -489
  80. package/src/grid/license.ts +48 -48
  81. package/src/grid/localization.ts +369 -369
  82. package/src/grid/slots/index.tsx +307 -307
  83. package/src/grid/slots/slots.css +64 -64
  84. package/src/grid/theme.tsx +138 -138
  85. package/src/grid/useAfColumns.tsx +371 -371
  86. package/src/grid/useAfCurrentIndex.ts +63 -63
  87. package/src/grid/useAfData.ts +20 -20
  88. package/src/grid/useAfFilter.ts +160 -160
  89. package/src/grid/useAfFilterFields.ts +55 -55
  90. package/src/grid/useAfGridApi.ts +71 -71
  91. package/src/grid/useAfKeyBindings.ts +36 -36
  92. package/src/grid/useAfNewItemRow.ts +258 -258
  93. package/src/grid/useAfPagination.ts +53 -53
  94. package/src/grid/useAfPersistedState.ts +230 -230
  95. package/src/grid/useAfRowEditModel.ts +145 -145
  96. package/src/grid/useAfRowGrouping.ts +47 -47
  97. package/src/grid/useAfServerAggregation.test.ts +68 -0
  98. package/src/grid/useAfServerAggregation.ts +158 -145
  99. package/src/grid/useAfSortModel.ts +84 -84
  100. package/src/i18n.ts +13 -13
  101. package/src/index.ts +14 -14
  102. package/src/input/InputAdornments.css +84 -75
  103. package/src/input/InputAdornments.tsx +102 -102
  104. package/src/input/index.ts +1 -1
  105. package/src/layout/AppMenu.tsx +74 -74
  106. package/src/layout/AppMenuDrawer.css +70 -70
  107. package/src/layout/AppMenuDrawer.tsx +122 -122
  108. package/src/layout/ErrorBoundary.tsx +162 -162
  109. package/src/layout/PageContainer.css +21 -2
  110. package/src/layout/PageContainer.tsx +18 -2
  111. package/src/layout/ResponsiveDialog.css +86 -86
  112. package/src/layout/ResponsiveDialog.tsx +55 -55
  113. package/src/layout/index.tsx +69 -69
  114. package/src/link/Link.tsx +23 -23
  115. package/src/link/index.ts +1 -1
  116. package/src/mdi/Mdi.css +138 -138
  117. package/src/mdi/Mdi.test.tsx +91 -91
  118. package/src/mdi/Mdi.tsx +141 -141
  119. package/src/mdi/MdiConfirmCloseDialog.tsx +47 -47
  120. package/src/mdi/MdiContext.tsx +59 -59
  121. package/src/mdi/MdiDocumentContext.tsx +60 -60
  122. package/src/mdi/MdiPanel.tsx +61 -61
  123. package/src/mdi/MdiRestore.test.tsx +86 -86
  124. package/src/mdi/MdiTab.tsx +30 -30
  125. package/src/mdi/controller.test.ts +382 -382
  126. package/src/mdi/controller.ts +656 -656
  127. package/src/mdi/dockviewHost.ts +132 -132
  128. package/src/mdi/fakeHost.ts +200 -200
  129. package/src/mdi/host.ts +44 -44
  130. package/src/mdi/ids.test.ts +20 -20
  131. package/src/mdi/ids.ts +18 -18
  132. package/src/mdi/index.ts +31 -31
  133. package/src/mdi/layoutRestore.test.ts +156 -156
  134. package/src/mdi/layoutRestore.ts +311 -311
  135. package/src/mdi/layoutSpec.test.ts +143 -143
  136. package/src/mdi/layoutSpec.ts +214 -214
  137. package/src/mdi/layoutStore.test.ts +93 -93
  138. package/src/mdi/layoutStore.ts +99 -99
  139. package/src/mdi/memorySettingsStore.ts +21 -21
  140. package/src/mdi/registry.ts +34 -34
  141. package/src/mdi/testTypes.ts +37 -37
  142. package/src/mdi/theme.ts +22 -22
  143. package/src/mdi/types.ts +214 -214
  144. package/src/page/Page.css +53 -53
  145. package/src/page/PageBlock.css +64 -64
  146. package/src/page/PageBlock.tsx +92 -92
  147. package/src/page/PageHeader.css +88 -88
  148. package/src/page/PageHeader.tsx +115 -115
  149. package/src/page/PageTitle.css +5 -5
  150. package/src/page/PageTitle.tsx +21 -21
  151. package/src/page/index.ts +4 -4
  152. package/src/paper/Paper.tsx +52 -52
  153. package/src/progress/LinearProgress.css +93 -93
  154. package/src/progress/LinearProgress.tsx +104 -104
  155. package/src/progress/index.ts +1 -1
  156. package/src/report-downloader/ReportDownloader.css +67 -67
  157. package/src/report-downloader/components/StatusItem.tsx +87 -87
  158. package/src/report-downloader/components/StatusList.tsx +28 -28
  159. package/src/report-downloader/index.ts +41 -41
  160. package/src/report-downloader/status.tsx +92 -92
  161. package/src/sortable/DropIndicator.css +51 -51
  162. package/src/sortable/DropIndicator.tsx +27 -27
  163. package/src/sortable/SortableItemList.css +21 -21
  164. package/src/sortable/SortableItemList.tsx +264 -264
  165. package/src/sortable/index.ts +8 -8
  166. package/src/sortable/reorderSortOrder.ts +126 -126
  167. package/src/sortable/sortOrder.ts +101 -101
  168. package/src/sortable/types.ts +9 -9
  169. package/src/sortable/useSortableContainer.ts +53 -53
  170. package/src/sortable/useSortableItem.ts +95 -95
  171. package/src/sortable/useSortableList.ts +117 -117
  172. package/src/test/happy-dom-document-class.ts +24 -24
  173. package/src/test/setup.ts +12 -12
  174. package/src/theme/useDsColorScheme.ts +53 -53
  175. package/tsconfig.build.json +9 -9
  176. package/tsconfig.json +8 -8
  177. 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
+ }