@burdenoff/microfe-cosmicintersection 2026.827.1 → 2026.830.2
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.
- package/dist/cosmicintersection/components/ActionMenu.d.ts +1 -1
- package/dist/cosmicintersection/components/ActionMenu.d.ts.map +1 -1
- package/dist/cosmicintersection/components/ActionMenu.js +51 -54
- package/dist/cosmicintersection/components/ActionMenu.js.map +1 -1
- package/dist/cosmicintersection/components/EntityFormShell.d.ts +1 -1
- package/dist/cosmicintersection/components/EntityFormShell.d.ts.map +1 -1
- package/dist/cosmicintersection/components/EntityFormShell.js +29 -27
- package/dist/cosmicintersection/components/EntityFormShell.js.map +1 -1
- package/dist/cosmicintersection/components/ErrorState.d.ts +1 -1
- package/dist/cosmicintersection/components/ErrorState.d.ts.map +1 -1
- package/dist/cosmicintersection/components/ErrorState.js +19 -17
- package/dist/cosmicintersection/components/ErrorState.js.map +1 -1
- package/dist/cosmicintersection/components/FileUploadField.d.ts.map +1 -1
- package/dist/cosmicintersection/components/FileUploadField.js +61 -60
- package/dist/cosmicintersection/components/FileUploadField.js.map +1 -1
- package/dist/cosmicintersection/components/LoadingState.d.ts +1 -1
- package/dist/cosmicintersection/components/LoadingState.d.ts.map +1 -1
- package/dist/cosmicintersection/components/LoadingState.js +15 -13
- package/dist/cosmicintersection/components/LoadingState.js.map +1 -1
- package/dist/cosmicintersection/components/PageLayout.d.ts.map +1 -1
- package/dist/cosmicintersection/components/PageLayout.js +28 -27
- package/dist/cosmicintersection/components/PageLayout.js.map +1 -1
- package/dist/cosmicintersection/components/Pagination.d.ts.map +1 -1
- package/dist/cosmicintersection/components/Pagination.js +38 -35
- package/dist/cosmicintersection/components/Pagination.js.map +1 -1
- package/dist/cosmicintersection/components/StatusBadge.d.ts.map +1 -1
- package/dist/cosmicintersection/components/StatusBadge.js +30 -9
- package/dist/cosmicintersection/components/StatusBadge.js.map +1 -1
- package/dist/cosmicintersection/pages/access/AccessPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/access/AccessPage.js +147 -146
- package/dist/cosmicintersection/pages/access/AccessPage.js.map +1 -1
- package/dist/cosmicintersection/pages/analyses/AnalysesPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/analyses/AnalysesPage.js +260 -259
- package/dist/cosmicintersection/pages/analyses/AnalysesPage.js.map +1 -1
- package/dist/cosmicintersection/pages/collections/CollectionsPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/collections/CollectionsPage.js +63 -62
- package/dist/cosmicintersection/pages/collections/CollectionsPage.js.map +1 -1
- package/dist/cosmicintersection/pages/dashboard/DashboardPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/dashboard/DashboardPage.js +132 -131
- package/dist/cosmicintersection/pages/dashboard/DashboardPage.js.map +1 -1
- package/dist/cosmicintersection/pages/datasets/DatasetsPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/datasets/DatasetsPage.js +178 -177
- package/dist/cosmicintersection/pages/datasets/DatasetsPage.js.map +1 -1
- package/dist/cosmicintersection/pages/discovery/DiscoveryPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/discovery/DiscoveryPage.js +138 -145
- package/dist/cosmicintersection/pages/discovery/DiscoveryPage.js.map +1 -1
- package/dist/cosmicintersection/pages/lineage/LineagePage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/lineage/LineagePage.js +78 -88
- package/dist/cosmicintersection/pages/lineage/LineagePage.js.map +1 -1
- package/dist/cosmicintersection/pages/notebooks/NotebooksPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js +125 -124
- package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js.map +1 -1
- package/dist/cosmicintersection/pages/pipelines/PipelinesPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js +152 -151
- package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js.map +1 -1
- package/dist/cosmicintersection/pages/releases/ReleasesPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/releases/ReleasesPage.js +175 -174
- package/dist/cosmicintersection/pages/releases/ReleasesPage.js.map +1 -1
- package/dist/cosmicintersection/pages/runs/RunsPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/runs/RunsPage.js +197 -188
- package/dist/cosmicintersection/pages/runs/RunsPage.js.map +1 -1
- package/dist/cosmicintersection/pages/sources/SourcesPage.d.ts.map +1 -1
- package/dist/cosmicintersection/pages/sources/SourcesPage.js +65 -64
- package/dist/cosmicintersection/pages/sources/SourcesPage.js.map +1 -1
- package/dist/dev/main.d.ts.map +1 -1
- package/dist/shared/hooks/useTr.d.ts +12 -0
- package/dist/shared/hooks/useTr.d.ts.map +1 -0
- package/dist/shared/hooks/useTr.js +16 -0
- package/dist/shared/hooks/useTr.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { notify as e } from "../lib/toast.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
2
|
+
import { useTr as t } from "../../shared/hooks/useTr.js";
|
|
3
|
+
import { useId as n, useRef as r, useState as i } from "react";
|
|
4
|
+
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
5
|
+
import { Download as s, FileUp as c, Loader2 as l, Paperclip as u, X as d } from "lucide-react";
|
|
6
|
+
import { Button as f } from "@burdenoff/fe-libs/ui";
|
|
7
|
+
import { gql as p } from "@apollo/client";
|
|
8
|
+
import { useApolloClient as m, useQuery as h } from "@apollo/client/react";
|
|
8
9
|
//#region src/cosmicintersection/components/FileUploadField.tsx
|
|
9
|
-
var
|
|
10
|
+
var g = p`
|
|
10
11
|
mutation CosInitiateUpload($input: InitiateUploadInput!) {
|
|
11
12
|
initiateUpload(input: $input) {
|
|
12
13
|
session {
|
|
@@ -15,7 +16,7 @@ var h = f`
|
|
|
15
16
|
uploadUrl
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
|
-
`,
|
|
19
|
+
`, _ = p`
|
|
19
20
|
mutation CosCompleteUpload($input: CompleteUploadInput!) {
|
|
20
21
|
completeUpload(input: $input) {
|
|
21
22
|
fileId
|
|
@@ -24,123 +25,123 @@ var h = f`
|
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
|
-
`,
|
|
28
|
+
`, v = p`
|
|
28
29
|
query CosGetFileDownloadUrl($id: ID!, $expiresIn: Int) {
|
|
29
30
|
getFileDownloadUrl(id: $id, expiresIn: $expiresIn)
|
|
30
31
|
}
|
|
31
|
-
`,
|
|
32
|
-
function
|
|
33
|
-
return
|
|
32
|
+
`, y = 3600;
|
|
33
|
+
function b(e) {
|
|
34
|
+
return h(v, {
|
|
34
35
|
variables: {
|
|
35
36
|
id: e ?? "",
|
|
36
|
-
expiresIn:
|
|
37
|
+
expiresIn: y
|
|
37
38
|
},
|
|
38
39
|
skip: !e,
|
|
39
40
|
fetchPolicy: "no-cache"
|
|
40
41
|
});
|
|
41
42
|
}
|
|
42
|
-
function
|
|
43
|
-
let
|
|
43
|
+
function x({ value: p, onChange: h, label: v, hint: y, folderId: x, accept: S, disabled: C = !1, className: w }) {
|
|
44
|
+
let T = t(), E = m(), D = r(null), O = n(), [k, A] = i(!1), [j, M] = i(null), N = b(k ? null : p).data?.getFileDownloadUrl ?? null, P = async (t) => {
|
|
44
45
|
let n = t.target.files?.[0];
|
|
45
46
|
if (t.target.value = "", n) {
|
|
46
|
-
|
|
47
|
+
A(!0);
|
|
47
48
|
try {
|
|
48
|
-
let t = n.name.includes(".") ? n.name.substring(n.name.lastIndexOf(".") + 1) : "", r = n.type || "application/octet-stream", i = await
|
|
49
|
-
mutation:
|
|
49
|
+
let t = n.name.includes(".") ? n.name.substring(n.name.lastIndexOf(".") + 1) : "", r = n.type || "application/octet-stream", i = await E.mutate({
|
|
50
|
+
mutation: g,
|
|
50
51
|
variables: { input: {
|
|
51
52
|
fileName: n.name,
|
|
52
53
|
fileSize: String(n.size),
|
|
53
54
|
extension: t,
|
|
54
55
|
mimeType: r,
|
|
55
|
-
...
|
|
56
|
+
...x ? { folderId: x } : {}
|
|
56
57
|
} }
|
|
57
58
|
}), a = i.data?.initiateUpload?.session, o = i.data?.initiateUpload?.uploadUrl;
|
|
58
|
-
if (!a || !o) throw Error(i.error?.message ?? "Could not start the upload.");
|
|
59
|
+
if (!a || !o) throw Error(i.error?.message ?? T("cosmicintersection.common.uploadStartError", "Could not start the upload."));
|
|
59
60
|
await new Promise((e, t) => {
|
|
60
61
|
let i = new XMLHttpRequest();
|
|
61
62
|
i.addEventListener("load", () => {
|
|
62
63
|
i.status >= 200 && i.status < 300 ? e() : t(/* @__PURE__ */ Error(`Upload failed with status ${i.status}`));
|
|
63
64
|
}), i.addEventListener("error", () => t(/* @__PURE__ */ Error("Upload network error"))), i.addEventListener("abort", () => t(/* @__PURE__ */ Error("Upload aborted"))), i.open("PUT", o), i.setRequestHeader("Content-Type", r), i.send(n);
|
|
64
65
|
});
|
|
65
|
-
let s = await
|
|
66
|
-
mutation:
|
|
66
|
+
let s = await E.mutate({
|
|
67
|
+
mutation: _,
|
|
67
68
|
variables: { input: {
|
|
68
69
|
sessionId: a.id,
|
|
69
|
-
...
|
|
70
|
+
...x ? { folderId: x } : {}
|
|
70
71
|
} }
|
|
71
72
|
}), c = s.data?.completeUpload?.fileId ?? s.data?.completeUpload?.file?.id;
|
|
72
|
-
if (!c) throw Error(s.error?.message ?? "The file was uploaded but not confirmed.");
|
|
73
|
-
|
|
73
|
+
if (!c) throw Error(s.error?.message ?? T("cosmicintersection.common.uploadConfirmError", "The file was uploaded but not confirmed."));
|
|
74
|
+
M(n.name), await h(c), e.success(T("cosmicintersection.common.uploadSuccess", "File uploaded"));
|
|
74
75
|
} catch (t) {
|
|
75
|
-
e.error("Upload failed", t instanceof Error ? t.message : "Please try again.");
|
|
76
|
+
e.error(T("cosmicintersection.common.uploadFailed", "Upload failed"), t instanceof Error ? t.message : T("cosmicintersection.common.uploadTryAgain", "Please try again."));
|
|
76
77
|
} finally {
|
|
77
|
-
|
|
78
|
+
A(!1);
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
|
-
},
|
|
81
|
-
|
|
81
|
+
}, F = async () => {
|
|
82
|
+
M(null), await h("");
|
|
82
83
|
};
|
|
83
|
-
return /* @__PURE__ */
|
|
84
|
-
className:
|
|
85
|
-
children: [/* @__PURE__ */
|
|
84
|
+
return /* @__PURE__ */ o("div", {
|
|
85
|
+
className: w,
|
|
86
|
+
children: [/* @__PURE__ */ o("div", {
|
|
86
87
|
className: "flex flex-wrap items-center gap-3",
|
|
87
|
-
children: [/* @__PURE__ */
|
|
88
|
+
children: [/* @__PURE__ */ a("div", {
|
|
88
89
|
className: "flex size-12 shrink-0 items-center justify-center rounded-card border border-dashed border-border-default bg-bg-sunken text-text-secondary",
|
|
89
|
-
children: /* @__PURE__ */
|
|
90
|
+
children: /* @__PURE__ */ a(u, {
|
|
90
91
|
className: "size-5",
|
|
91
92
|
"aria-hidden": "true"
|
|
92
93
|
})
|
|
93
|
-
}), /* @__PURE__ */
|
|
94
|
+
}), /* @__PURE__ */ o("div", {
|
|
94
95
|
className: "space-y-1",
|
|
95
|
-
children: [/* @__PURE__ */
|
|
96
|
+
children: [/* @__PURE__ */ o("div", {
|
|
96
97
|
className: "flex items-center gap-2",
|
|
97
|
-
children: [/* @__PURE__ */
|
|
98
|
+
children: [/* @__PURE__ */ o(f, {
|
|
98
99
|
type: "button",
|
|
99
100
|
variant: "secondary",
|
|
100
101
|
size: "sm",
|
|
101
|
-
disabled:
|
|
102
|
-
onClick: () =>
|
|
103
|
-
children: [
|
|
104
|
-
}),
|
|
102
|
+
disabled: C || k,
|
|
103
|
+
onClick: () => D.current?.click(),
|
|
104
|
+
children: [k ? /* @__PURE__ */ a(l, { className: "size-4 animate-spin" }) : /* @__PURE__ */ a(c, { className: "size-4" }), k ? T("cosmicintersection.common.uploading", "Uploading…") : p ? T("cosmicintersection.common.replaceFile", "Replace file") : v]
|
|
105
|
+
}), p && !k ? /* @__PURE__ */ o(f, {
|
|
105
106
|
type: "button",
|
|
106
107
|
variant: "ghost",
|
|
107
108
|
size: "sm",
|
|
108
|
-
onClick: () => void
|
|
109
|
-
disabled:
|
|
110
|
-
children: [/* @__PURE__ */
|
|
109
|
+
onClick: () => void F(),
|
|
110
|
+
disabled: C,
|
|
111
|
+
children: [/* @__PURE__ */ a(d, { className: "size-4" }), T("cosmicintersection.common.remove", "Remove")]
|
|
111
112
|
}) : null]
|
|
112
|
-
}),
|
|
113
|
+
}), p ? /* @__PURE__ */ o("div", {
|
|
113
114
|
className: "flex flex-wrap items-center gap-2 text-xs text-text-secondary",
|
|
114
|
-
children: [/* @__PURE__ */
|
|
115
|
+
children: [/* @__PURE__ */ a("span", {
|
|
115
116
|
className: "max-w-[16rem] truncate",
|
|
116
|
-
children:
|
|
117
|
-
}),
|
|
118
|
-
href:
|
|
117
|
+
children: j ?? `File ${p}`
|
|
118
|
+
}), N ? /* @__PURE__ */ o("a", {
|
|
119
|
+
href: N,
|
|
119
120
|
target: "_blank",
|
|
120
121
|
rel: "noopener noreferrer",
|
|
121
122
|
className: "inline-flex items-center gap-1 text-text-primary underline-offset-2 hover:underline",
|
|
122
|
-
children: [/* @__PURE__ */
|
|
123
|
+
children: [/* @__PURE__ */ a(s, {
|
|
123
124
|
className: "size-3.5",
|
|
124
125
|
"aria-hidden": "true"
|
|
125
|
-
}), "Download"]
|
|
126
|
+
}), T("cosmicintersection.common.download", "Download")]
|
|
126
127
|
}) : null]
|
|
127
|
-
}) :
|
|
128
|
+
}) : y ? /* @__PURE__ */ a("p", {
|
|
128
129
|
className: "text-xs text-text-secondary",
|
|
129
|
-
children:
|
|
130
|
+
children: y
|
|
130
131
|
}) : null]
|
|
131
132
|
})]
|
|
132
|
-
}), /* @__PURE__ */
|
|
133
|
-
ref:
|
|
134
|
-
id:
|
|
133
|
+
}), /* @__PURE__ */ a("input", {
|
|
134
|
+
ref: D,
|
|
135
|
+
id: O,
|
|
135
136
|
type: "file",
|
|
136
|
-
accept:
|
|
137
|
+
accept: S,
|
|
137
138
|
className: "sr-only",
|
|
138
|
-
"aria-label":
|
|
139
|
-
onChange: (e) => void
|
|
139
|
+
"aria-label": v,
|
|
140
|
+
onChange: (e) => void P(e)
|
|
140
141
|
})]
|
|
141
142
|
});
|
|
142
143
|
}
|
|
143
144
|
//#endregion
|
|
144
|
-
export {
|
|
145
|
+
export { v as GET_FILE_DOWNLOAD_URL, x as default, b as useFileDownloadUrl };
|
|
145
146
|
|
|
146
147
|
//# sourceMappingURL=FileUploadField.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileUploadField.js","names":[],"sources":["../../../src/cosmicintersection/components/FileUploadField.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useApolloClient, useQuery } from '@apollo/client/react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Download, FileUp, Loader2, Paperclip, X } from 'lucide-react';\nimport { useId, useRef, useState } from 'react';\n\nimport { notify } from '../lib/toast';\n\n/**\n * Generic artifact uploader backed by wspace-files-svc.\n *\n * Dataset/notebook/analysis blobs are NOT stored by this MFE — they live in\n * wspace-files as `File` records, and the Cos entities only hold the returned\n * `fileId` (CosDataset.fileId / CosNotebook.fileId / cosCompleteAnalysis.outputFileId).\n * This control performs the shared 3-step presigned upload (initiateUpload → PUT\n * → completeUpload) inline — the same protocol microfe-files uses — and hands the\n * resulting `fileId` back via `onChange`. It never keeps a local blob store and\n * accepts any file type.\n */\n\n// initiateUpload returns a presigned PUT URL + the upload session id.\nconst INITIATE_UPLOAD = gql`\n mutation CosInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n session {\n id\n }\n uploadUrl\n }\n }\n`;\n\n// completeUpload finalizes the session and returns the committed File id.\nconst COMPLETE_UPLOAD = gql`\n mutation CosCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file {\n id\n }\n }\n }\n`;\n\n// Presigned, time-limited download URL for a stored File — used to render a\n// \"Download\" affordance for an already-attached artifact.\nexport const GET_FILE_DOWNLOAD_URL = gql`\n query CosGetFileDownloadUrl($id: ID!, $expiresIn: Int) {\n getFileDownloadUrl(id: $id, expiresIn: $expiresIn)\n }\n`;\n\n// One hour — artifact links are resolved on demand when a detail page renders.\nconst DOWNLOAD_URL_TTL_SECONDS = 3_600;\n\ninterface InitiateUploadResponse {\n initiateUpload?: { session?: { id: string } | null; uploadUrl?: string | null } | null;\n}\n\ninterface CompleteUploadResponse {\n completeUpload?: { fileId?: string | null; file?: { id: string } | null } | null;\n}\n\ninterface DownloadUrlResponse {\n getFileDownloadUrl?: string | null;\n}\n\n/**\n * Resolve a presigned download URL for an attached File id. Skips when no id is\n * present. Detail pages use this to render a \"Download\" link for a dataset/\n * notebook/analysis artifact.\n */\nexport function useFileDownloadUrl(fileId: string | null | undefined) {\n return useQuery<DownloadUrlResponse, { id: string; expiresIn: number }>(GET_FILE_DOWNLOAD_URL, {\n variables: { id: fileId ?? '', expiresIn: DOWNLOAD_URL_TTL_SECONDS },\n skip: !fileId,\n fetchPolicy: 'no-cache',\n });\n}\n\nexport interface FileUploadFieldProps {\n /** Current attached wspace-files File id, if any. */\n value?: string | null;\n /** Called with the new File id once an upload completes, or '' when cleared. */\n onChange: (fileId: string) => void | Promise<void>;\n label: string;\n hint?: string;\n /** Optional wspace-files folder id; when omitted the file lands in the root. */\n folderId?: string;\n /** `accept` attribute for the file input; defaults to any file type. */\n accept?: string;\n disabled?: boolean;\n className?: string;\n}\n\nexport function FileUploadField({\n value,\n onChange,\n label,\n hint,\n folderId,\n accept,\n disabled = false,\n className,\n}: FileUploadFieldProps) {\n const client = useApolloClient();\n const inputRef = useRef<HTMLInputElement | null>(null);\n const inputId = useId();\n const [uploading, setUploading] = useState(false);\n const [fileName, setFileName] = useState<string | null>(null);\n\n const download = useFileDownloadUrl(uploading ? null : value);\n const downloadUrl = download.data?.getFileDownloadUrl ?? null;\n\n const handleSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n // Reset the input so selecting the same file again re-triggers change.\n event.target.value = '';\n if (!file) {\n return;\n }\n\n setUploading(true);\n try {\n const extension = file.name.includes('.')\n ? file.name.substring(file.name.lastIndexOf('.') + 1)\n : '';\n const mimeType = file.type || 'application/octet-stream';\n\n const initiate = await client.mutate<InitiateUploadResponse>({\n mutation: INITIATE_UPLOAD,\n variables: {\n input: {\n fileName: file.name,\n fileSize: String(file.size),\n extension,\n mimeType,\n ...(folderId ? { folderId } : {}),\n },\n },\n });\n const session = initiate.data?.initiateUpload?.session;\n const uploadUrl = initiate.data?.initiateUpload?.uploadUrl;\n if (!session || !uploadUrl) {\n throw new Error(initiate.error?.message ?? 'Could not start the upload.');\n }\n\n await new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.addEventListener('load', () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n reject(new Error(`Upload failed with status ${xhr.status}`));\n }\n });\n xhr.addEventListener('error', () => reject(new Error('Upload network error')));\n xhr.addEventListener('abort', () => reject(new Error('Upload aborted')));\n xhr.open('PUT', uploadUrl);\n xhr.setRequestHeader('Content-Type', mimeType);\n xhr.send(file);\n });\n\n const complete = await client.mutate<CompleteUploadResponse>({\n mutation: COMPLETE_UPLOAD,\n variables: {\n input: {\n sessionId: session.id,\n ...(folderId ? { folderId } : {}),\n },\n },\n });\n const uploadedId =\n complete.data?.completeUpload?.fileId ?? complete.data?.completeUpload?.file?.id;\n if (!uploadedId) {\n throw new Error(complete.error?.message ?? 'The file was uploaded but not confirmed.');\n }\n\n setFileName(file.name);\n await onChange(uploadedId);\n notify.success('File uploaded');\n } catch (error) {\n notify.error('Upload failed', error instanceof Error ? error.message : 'Please try again.');\n } finally {\n setUploading(false);\n }\n };\n\n const clear = async () => {\n setFileName(null);\n await onChange('');\n };\n\n return (\n <div className={className}>\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"flex size-12 shrink-0 items-center justify-center rounded-card border border-dashed border-border-default bg-bg-sunken text-text-secondary\">\n <Paperclip className=\"size-5\" aria-hidden=\"true\" />\n </div>\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n disabled={disabled || uploading}\n onClick={() => inputRef.current?.click()}\n >\n {uploading ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <FileUp className=\"size-4\" />\n )}\n {uploading ? 'Uploading…' : value ? 'Replace file' : label}\n </Button>\n {value && !uploading ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => void clear()}\n disabled={disabled}\n >\n <X className=\"size-4\" />\n Remove\n </Button>\n ) : null}\n </div>\n {value ? (\n <div className=\"flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n <span className=\"max-w-[16rem] truncate\">{fileName ?? `File ${value}`}</span>\n {downloadUrl ? (\n <a\n href={downloadUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1 text-text-primary underline-offset-2 hover:underline\"\n >\n <Download className=\"size-3.5\" aria-hidden=\"true\" />\n Download\n </a>\n ) : null}\n </div>\n ) : hint ? (\n <p className=\"text-xs text-text-secondary\">{hint}</p>\n ) : null}\n </div>\n </div>\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n accept={accept}\n className=\"sr-only\"\n aria-label={label}\n onChange={(event) => void handleSelect(event)}\n />\n </div>\n );\n}\n\nexport default FileUploadField;\n"],"mappings":";;;;;;;;AAqBA,IAAM,IAAkB,CAAG;;;;;;;;;GAYrB,IAAkB,CAAG;;;;;;;;;GAad,IAAwB,CAAG;;;;GAOlC,IAA2B;AAmBjC,SAAgB,EAAmB,GAAmC;CACpE,OAAO,EAAiE,GAAuB;EAC7F,WAAW;GAAE,IAAI,KAAU;GAAI,WAAW;EAAyB;EACnE,MAAM,CAAC;EACP,aAAa;CACf,CAAC;AACH;AAiBA,SAAgB,EAAgB,EAC9B,UACA,aACA,UACA,SACA,aACA,WACA,cAAW,IACX,gBACuB;CACvB,IAAM,IAAS,EAAgB,GACzB,IAAW,EAAgC,IAAI,GAC/C,IAAU,EAAM,GAChB,CAAC,GAAW,KAAgB,EAAS,EAAK,GAC1C,CAAC,GAAU,KAAe,EAAwB,IAAI,GAGtD,IADW,EAAmB,IAAY,OAAO,CACnC,EAAS,MAAM,sBAAsB,MAEnD,IAAe,OAAO,MAA+C;EACzE,IAAM,IAAO,EAAM,OAAO,QAAQ;EAElC,MAAM,OAAO,QAAQ,IAChB,GAIL;KAAa,EAAI;GACjB,IAAI;IACF,IAAM,IAAY,EAAK,KAAK,SAAS,GAAG,IACpC,EAAK,KAAK,UAAU,EAAK,KAAK,YAAY,GAAG,IAAI,CAAC,IAClD,IACE,IAAW,EAAK,QAAQ,4BAExB,IAAW,MAAM,EAAO,OAA+B;KAC3D,UAAU;KACV,WAAW,EACT,OAAO;MACL,UAAU,EAAK;MACf,UAAU,OAAO,EAAK,IAAI;MAC1B;MACA;MACA,GAAI,IAAW,EAAE,YAAS,IAAI,CAAC;KACjC,EACF;IACF,CAAC,GACK,IAAU,EAAS,MAAM,gBAAgB,SACzC,IAAY,EAAS,MAAM,gBAAgB;IACjD,IAAI,CAAC,KAAW,CAAC,GACf,MAAU,MAAM,EAAS,OAAO,WAAW,6BAA6B;IAG1E,MAAM,IAAI,SAAe,GAAS,MAAW;KAC3C,IAAM,IAAM,IAAI,eAAe;KAY/B,AAXA,EAAI,iBAAiB,cAAc;MACjC,AAAI,EAAI,UAAU,OAAO,EAAI,SAAS,MACpC,EAAQ,IAER,EAAO,gBAAI,MAAM,6BAA6B,EAAI,QAAQ,CAAC;KAE/D,CAAC,GACD,EAAI,iBAAiB,eAAe,EAAO,gBAAI,MAAM,sBAAsB,CAAC,CAAC,GAC7E,EAAI,iBAAiB,eAAe,EAAO,gBAAI,MAAM,gBAAgB,CAAC,CAAC,GACvE,EAAI,KAAK,OAAO,CAAS,GACzB,EAAI,iBAAiB,gBAAgB,CAAQ,GAC7C,EAAI,KAAK,CAAI;IACf,CAAC;IAED,IAAM,IAAW,MAAM,EAAO,OAA+B;KAC3D,UAAU;KACV,WAAW,EACT,OAAO;MACL,WAAW,EAAQ;MACnB,GAAI,IAAW,EAAE,YAAS,IAAI,CAAC;KACjC,EACF;IACF,CAAC,GACK,IACJ,EAAS,MAAM,gBAAgB,UAAU,EAAS,MAAM,gBAAgB,MAAM;IAChF,IAAI,CAAC,GACH,MAAU,MAAM,EAAS,OAAO,WAAW,0CAA0C;IAKvF,AAFA,EAAY,EAAK,IAAI,GACrB,MAAM,EAAS,CAAU,GACzB,EAAO,QAAQ,eAAe;GAChC,SAAS,GAAO;IACd,EAAO,MAAM,iBAAiB,aAAiB,QAAQ,EAAM,UAAU,mBAAmB;GAC5F,UAAU;IACR,EAAa,EAAK;GACpB;EA/DiB;CAgEnB,GAEM,IAAQ,YAAY;EAExB,AADA,EAAY,IAAI,GAChB,MAAM,EAAS,EAAE;CACnB;CAEA,OACE,kBAAC,OAAD;EAAgB;YAAhB,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KAAW,WAAU;KAAS,eAAY;IAAQ,CAAA;GAC/C,CAAA,GACL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACE,MAAK;MACL,SAAQ;MACR,MAAK;MACL,UAAU,KAAY;MACtB,eAAe,EAAS,SAAS,MAAM;gBALzC,CAOG,IACC,kBAAC,GAAD,EAAS,WAAU,sBAAuB,CAAA,IAE1C,kBAAC,GAAD,EAAQ,WAAU,SAAU,CAAA,GAE7B,IAAY,eAAe,IAAQ,iBAAiB,CAC/C;SACP,KAAS,CAAC,IACT,kBAAC,GAAD;MACE,MAAK;MACL,SAAQ;MACR,MAAK;MACL,eAAe,KAAK,EAAM;MAChB;gBALZ,CAOE,kBAAC,GAAD,EAAG,WAAU,SAAU,CAAA,GAAC,QAElB;UACN,IACD;QACJ,IACC,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBAA0B,KAAY,QAAQ;KAAc,CAAA,GAC3E,IACC,kBAAC,KAAD;MACE,MAAM;MACN,QAAO;MACP,KAAI;MACJ,WAAU;gBAJZ,CAME,kBAAC,GAAD;OAAU,WAAU;OAAW,eAAY;MAAQ,CAAA,GAAC,UAEnD;UACD,IACD;SACH,IACF,kBAAC,KAAD;KAAG,WAAU;eAA+B;IAAQ,CAAA,IAClD,IACD;KACF;MACL,kBAAC,SAAD;GACE,KAAK;GACL,IAAI;GACJ,MAAK;GACG;GACR,WAAU;GACV,cAAY;GACZ,WAAW,MAAU,KAAK,EAAa,CAAK;EAC7C,CAAA,CACE;;AAET"}
|
|
1
|
+
{"version":3,"file":"FileUploadField.js","names":[],"sources":["../../../src/cosmicintersection/components/FileUploadField.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useApolloClient, useQuery } from '@apollo/client/react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Download, FileUp, Loader2, Paperclip, X } from 'lucide-react';\nimport { useId, useRef, useState } from 'react';\n\nimport { notify } from '../lib/toast';\nimport { useTr } from '../../shared/hooks/useTr';\n\n/**\n * Generic artifact uploader backed by wspace-files-svc.\n *\n * Dataset/notebook/analysis blobs are NOT stored by this MFE — they live in\n * wspace-files as `File` records, and the Cos entities only hold the returned\n * `fileId` (CosDataset.fileId / CosNotebook.fileId / cosCompleteAnalysis.outputFileId).\n * This control performs the shared 3-step presigned upload (initiateUpload → PUT\n * → completeUpload) inline — the same protocol microfe-files uses — and hands the\n * resulting `fileId` back via `onChange`. It never keeps a local blob store and\n * accepts any file type.\n */\n\n// initiateUpload returns a presigned PUT URL + the upload session id.\nconst INITIATE_UPLOAD = gql`\n mutation CosInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n session {\n id\n }\n uploadUrl\n }\n }\n`;\n\n// completeUpload finalizes the session and returns the committed File id.\nconst COMPLETE_UPLOAD = gql`\n mutation CosCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file {\n id\n }\n }\n }\n`;\n\n// Presigned, time-limited download URL for a stored File — used to render a\n// \"Download\" affordance for an already-attached artifact.\nexport const GET_FILE_DOWNLOAD_URL = gql`\n query CosGetFileDownloadUrl($id: ID!, $expiresIn: Int) {\n getFileDownloadUrl(id: $id, expiresIn: $expiresIn)\n }\n`;\n\n// One hour — artifact links are resolved on demand when a detail page renders.\nconst DOWNLOAD_URL_TTL_SECONDS = 3_600;\n\ninterface InitiateUploadResponse {\n initiateUpload?: { session?: { id: string } | null; uploadUrl?: string | null } | null;\n}\n\ninterface CompleteUploadResponse {\n completeUpload?: { fileId?: string | null; file?: { id: string } | null } | null;\n}\n\ninterface DownloadUrlResponse {\n getFileDownloadUrl?: string | null;\n}\n\n/**\n * Resolve a presigned download URL for an attached File id. Skips when no id is\n * present. Detail pages use this to render a \"Download\" link for a dataset/\n * notebook/analysis artifact.\n */\nexport function useFileDownloadUrl(fileId: string | null | undefined) {\n return useQuery<DownloadUrlResponse, { id: string; expiresIn: number }>(GET_FILE_DOWNLOAD_URL, {\n variables: { id: fileId ?? '', expiresIn: DOWNLOAD_URL_TTL_SECONDS },\n skip: !fileId,\n fetchPolicy: 'no-cache',\n });\n}\n\nexport interface FileUploadFieldProps {\n /** Current attached wspace-files File id, if any. */\n value?: string | null;\n /** Called with the new File id once an upload completes, or '' when cleared. */\n onChange: (fileId: string) => void | Promise<void>;\n label: string;\n hint?: string;\n /** Optional wspace-files folder id; when omitted the file lands in the root. */\n folderId?: string;\n /** `accept` attribute for the file input; defaults to any file type. */\n accept?: string;\n disabled?: boolean;\n className?: string;\n}\n\nexport function FileUploadField({\n value,\n onChange,\n label,\n hint,\n folderId,\n accept,\n disabled = false,\n className,\n}: FileUploadFieldProps) {\n const tr = useTr();\n const client = useApolloClient();\n const inputRef = useRef<HTMLInputElement | null>(null);\n const inputId = useId();\n const [uploading, setUploading] = useState(false);\n const [fileName, setFileName] = useState<string | null>(null);\n\n const download = useFileDownloadUrl(uploading ? null : value);\n const downloadUrl = download.data?.getFileDownloadUrl ?? null;\n\n const handleSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n // Reset the input so selecting the same file again re-triggers change.\n event.target.value = '';\n if (!file) {\n return;\n }\n\n setUploading(true);\n try {\n const extension = file.name.includes('.')\n ? file.name.substring(file.name.lastIndexOf('.') + 1)\n : '';\n const mimeType = file.type || 'application/octet-stream';\n\n const initiate = await client.mutate<InitiateUploadResponse>({\n mutation: INITIATE_UPLOAD,\n variables: {\n input: {\n fileName: file.name,\n fileSize: String(file.size),\n extension,\n mimeType,\n ...(folderId ? { folderId } : {}),\n },\n },\n });\n const session = initiate.data?.initiateUpload?.session;\n const uploadUrl = initiate.data?.initiateUpload?.uploadUrl;\n if (!session || !uploadUrl) {\n throw new Error(\n initiate.error?.message ??\n tr('cosmicintersection.common.uploadStartError', 'Could not start the upload.')\n );\n }\n\n await new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.addEventListener('load', () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n reject(new Error(`Upload failed with status ${xhr.status}`));\n }\n });\n xhr.addEventListener('error', () => reject(new Error('Upload network error')));\n xhr.addEventListener('abort', () => reject(new Error('Upload aborted')));\n xhr.open('PUT', uploadUrl);\n xhr.setRequestHeader('Content-Type', mimeType);\n xhr.send(file);\n });\n\n const complete = await client.mutate<CompleteUploadResponse>({\n mutation: COMPLETE_UPLOAD,\n variables: {\n input: {\n sessionId: session.id,\n ...(folderId ? { folderId } : {}),\n },\n },\n });\n const uploadedId =\n complete.data?.completeUpload?.fileId ?? complete.data?.completeUpload?.file?.id;\n if (!uploadedId) {\n throw new Error(\n complete.error?.message ??\n tr(\n 'cosmicintersection.common.uploadConfirmError',\n 'The file was uploaded but not confirmed.'\n )\n );\n }\n\n setFileName(file.name);\n await onChange(uploadedId);\n notify.success(tr('cosmicintersection.common.uploadSuccess', 'File uploaded'));\n } catch (error) {\n notify.error(\n tr('cosmicintersection.common.uploadFailed', 'Upload failed'),\n error instanceof Error\n ? error.message\n : tr('cosmicintersection.common.uploadTryAgain', 'Please try again.')\n );\n } finally {\n setUploading(false);\n }\n };\n\n const clear = async () => {\n setFileName(null);\n await onChange('');\n };\n\n return (\n <div className={className}>\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"flex size-12 shrink-0 items-center justify-center rounded-card border border-dashed border-border-default bg-bg-sunken text-text-secondary\">\n <Paperclip className=\"size-5\" aria-hidden=\"true\" />\n </div>\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n disabled={disabled || uploading}\n onClick={() => inputRef.current?.click()}\n >\n {uploading ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <FileUp className=\"size-4\" />\n )}\n {uploading\n ? tr('cosmicintersection.common.uploading', 'Uploading…')\n : value\n ? tr('cosmicintersection.common.replaceFile', 'Replace file')\n : label}\n </Button>\n {value && !uploading ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => void clear()}\n disabled={disabled}\n >\n <X className=\"size-4\" />\n {tr('cosmicintersection.common.remove', 'Remove')}\n </Button>\n ) : null}\n </div>\n {value ? (\n <div className=\"flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n <span className=\"max-w-[16rem] truncate\">{fileName ?? `File ${value}`}</span>\n {downloadUrl ? (\n <a\n href={downloadUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1 text-text-primary underline-offset-2 hover:underline\"\n >\n <Download className=\"size-3.5\" aria-hidden=\"true\" />\n {tr('cosmicintersection.common.download', 'Download')}\n </a>\n ) : null}\n </div>\n ) : hint ? (\n <p className=\"text-xs text-text-secondary\">{hint}</p>\n ) : null}\n </div>\n </div>\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n accept={accept}\n className=\"sr-only\"\n aria-label={label}\n onChange={(event) => void handleSelect(event)}\n />\n </div>\n );\n}\n\nexport default FileUploadField;\n"],"mappings":"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"}
|
|
@@ -10,6 +10,6 @@ export interface LoadingStateProps {
|
|
|
10
10
|
* Consistent loading affordance for every CosmicIntersection query surface.
|
|
11
11
|
* Token-styled only — no raw palette colours.
|
|
12
12
|
*/
|
|
13
|
-
export declare function LoadingState({ variant, rows, label, className, }: LoadingStateProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export declare function LoadingState({ variant, rows, label: labelProp, className, }: LoadingStateProps): import("react/jsx-runtime").JSX.Element;
|
|
14
14
|
export default LoadingState;
|
|
15
15
|
//# sourceMappingURL=LoadingState.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LoadingState.d.ts","sourceRoot":"","sources":["../../../src/cosmicintersection/components/LoadingState.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LoadingState.d.ts","sourceRoot":"","sources":["../../../src/cosmicintersection/components/LoadingState.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,gFAAgF;IAChF,OAAO,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAC7B,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAmB,EACnB,IAAQ,EACR,KAAK,EAAE,SAAS,EAChB,SAAS,GACV,EAAE,iBAAiB,2CAuBnB;AAED,eAAe,YAAY,CAAC"}
|
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
2
|
+
import { jsx as t, jsxs as n } from "react/jsx-runtime";
|
|
3
|
+
import { Skeleton as r, Spinner as i } from "@burdenoff/fe-libs/ui";
|
|
3
4
|
//#region src/cosmicintersection/components/LoadingState.tsx
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
function a({ variant: a = "spinner", rows: o = 5, label: s, className: c }) {
|
|
6
|
+
let l = e(), u = s ?? l("cosmicintersection.common.loading", "Loading…");
|
|
7
|
+
return a === "rows" ? /* @__PURE__ */ t("div", {
|
|
8
|
+
className: `space-y-3 ${c ?? ""}`,
|
|
7
9
|
role: "status",
|
|
8
|
-
"aria-label":
|
|
9
|
-
children: Array.from({ length:
|
|
10
|
-
}) : /* @__PURE__ */
|
|
11
|
-
className: `flex min-h-[240px] flex-col items-center justify-center gap-3 ${
|
|
10
|
+
"aria-label": u,
|
|
11
|
+
children: Array.from({ length: o }).map((e, n) => /* @__PURE__ */ t(r, { className: "h-14 w-full rounded-card" }, n))
|
|
12
|
+
}) : /* @__PURE__ */ n("div", {
|
|
13
|
+
className: `flex min-h-[240px] flex-col items-center justify-center gap-3 ${c ?? ""}`,
|
|
12
14
|
role: "status",
|
|
13
|
-
"aria-label":
|
|
14
|
-
children: [/* @__PURE__ */
|
|
15
|
+
"aria-label": u,
|
|
16
|
+
children: [/* @__PURE__ */ t(i, { className: "size-6 text-text-secondary" }), /* @__PURE__ */ t("p", {
|
|
15
17
|
className: "text-sm text-text-secondary",
|
|
16
|
-
children:
|
|
18
|
+
children: u
|
|
17
19
|
})]
|
|
18
20
|
});
|
|
19
21
|
}
|
|
20
22
|
//#endregion
|
|
21
|
-
export {
|
|
23
|
+
export { a as default };
|
|
22
24
|
|
|
23
25
|
//# sourceMappingURL=LoadingState.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LoadingState.js","names":[],"sources":["../../../src/cosmicintersection/components/LoadingState.tsx"],"sourcesContent":["import { Skeleton, Spinner } from '@burdenoff/fe-libs/ui';\n\nexport interface LoadingStateProps {\n /** `spinner` centres a spinner; `rows` renders shimmering list placeholders. */\n variant?: 'spinner' | 'rows';\n /** Number of placeholder rows for the `rows` variant. */\n rows?: number;\n label?: string;\n className?: string;\n}\n\n/**\n * Consistent loading affordance for every CosmicIntersection query surface.\n * Token-styled only — no raw palette colours.\n */\nexport function LoadingState({\n variant = 'spinner',\n rows = 5,\n label
|
|
1
|
+
{"version":3,"file":"LoadingState.js","names":[],"sources":["../../../src/cosmicintersection/components/LoadingState.tsx"],"sourcesContent":["import { Skeleton, Spinner } from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface LoadingStateProps {\n /** `spinner` centres a spinner; `rows` renders shimmering list placeholders. */\n variant?: 'spinner' | 'rows';\n /** Number of placeholder rows for the `rows` variant. */\n rows?: number;\n label?: string;\n className?: string;\n}\n\n/**\n * Consistent loading affordance for every CosmicIntersection query surface.\n * Token-styled only — no raw palette colours.\n */\nexport function LoadingState({\n variant = 'spinner',\n rows = 5,\n label: labelProp,\n className,\n}: LoadingStateProps) {\n const tr = useTr();\n const label = labelProp ?? tr('cosmicintersection.common.loading', 'Loading…');\n if (variant === 'rows') {\n return (\n <div className={`space-y-3 ${className ?? ''}`} role=\"status\" aria-label={label}>\n {Array.from({ length: rows }).map((_, index) => (\n <Skeleton key={index} className=\"h-14 w-full rounded-card\" />\n ))}\n </div>\n );\n }\n\n return (\n <div\n className={`flex min-h-[240px] flex-col items-center justify-center gap-3 ${className ?? ''}`}\n role=\"status\"\n aria-label={label}\n >\n <Spinner className=\"size-6 text-text-secondary\" />\n <p className=\"text-sm text-text-secondary\">{label}</p>\n </div>\n );\n}\n\nexport default LoadingState;\n"],"mappings":";;;;AAgBA,SAAgB,EAAa,EAC3B,aAAU,WACV,UAAO,GACP,OAAO,GACP,gBACoB;CACpB,IAAM,IAAK,EAAM,GACX,IAAQ,KAAa,EAAG,qCAAqC,UAAU;CAW7E,OAVI,MAAY,SAEZ,kBAAC,OAAD;EAAK,WAAW,aAAa,KAAa;EAAM,MAAK;EAAS,cAAY;YACvE,MAAM,KAAK,EAAE,QAAQ,EAAK,CAAC,EAAE,KAAK,GAAG,MACpC,kBAAC,GAAD,EAAsB,WAAU,2BAA4B,GAA7C,CAA6C,CAC7D;CACE,CAAA,IAKP,kBAAC,OAAD;EACE,WAAW,iEAAiE,KAAa;EACzF,MAAK;EACL,cAAY;YAHd,CAKE,kBAAC,GAAD,EAAS,WAAU,6BAA8B,CAAA,GACjD,kBAAC,KAAD;GAAG,WAAU;aAA+B;EAAS,CAAA,CAClD;;AAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageLayout.d.ts","sourceRoot":"","sources":["../../../src/cosmicintersection/components/PageLayout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"PageLayout.d.ts","sourceRoot":"","sources":["../../../src/cosmicintersection/components/PageLayout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAMvC,UAAU,eAAe;IACvB,KAAK,EAAE,SAAS,CAAC;IACjB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;IACpB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,eAAe,2CAmC9F;AAED,eAAe,UAAU,CAAC"}
|
|
@@ -1,46 +1,47 @@
|
|
|
1
1
|
import { useCosmicIntersection as e } from "../context/CosmicIntersectionContext.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { useTr as t } from "../../shared/hooks/useTr.js";
|
|
3
|
+
import { cn as n } from "../utils/cn.js";
|
|
4
|
+
import { jsx as r, jsxs as i } from "react/jsx-runtime";
|
|
5
|
+
import { ChevronLeft as a } from "lucide-react";
|
|
5
6
|
//#region src/cosmicintersection/components/PageLayout.tsx
|
|
6
|
-
function
|
|
7
|
-
let { navigate:
|
|
8
|
-
return /* @__PURE__ */
|
|
9
|
-
className:
|
|
10
|
-
children: [/* @__PURE__ */
|
|
11
|
-
className:
|
|
12
|
-
children: [/* @__PURE__ */
|
|
7
|
+
function o({ title: o, description: s, actions: c, children: l, backPath: u }) {
|
|
8
|
+
let { navigate: d } = e(), f = t();
|
|
9
|
+
return /* @__PURE__ */ i("div", {
|
|
10
|
+
className: n("flex h-full flex-col bg-bg-surface"),
|
|
11
|
+
children: [/* @__PURE__ */ i("div", {
|
|
12
|
+
className: n("flex items-start justify-between gap-4 border-b border-border-seam bg-bg-surface px-6 py-5"),
|
|
13
|
+
children: [/* @__PURE__ */ i("div", {
|
|
13
14
|
className: "flex min-w-0 flex-col gap-1",
|
|
14
15
|
children: [
|
|
15
|
-
|
|
16
|
+
u ? /* @__PURE__ */ i("button", {
|
|
16
17
|
type: "button",
|
|
17
|
-
onClick: () => u
|
|
18
|
-
className:
|
|
19
|
-
children: [/* @__PURE__ */
|
|
18
|
+
onClick: () => d(u),
|
|
19
|
+
className: n("flex w-fit items-center gap-1 rounded-button text-sm text-text-secondary transition-colors hover:text-text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus"),
|
|
20
|
+
children: [/* @__PURE__ */ r(a, {
|
|
20
21
|
className: "size-4",
|
|
21
22
|
"aria-hidden": "true"
|
|
22
|
-
}), "Back"]
|
|
23
|
+
}), f("cosmicintersection.common.back", "Back")]
|
|
23
24
|
}) : null,
|
|
24
|
-
/* @__PURE__ */
|
|
25
|
-
className:
|
|
26
|
-
children: a
|
|
27
|
-
}),
|
|
28
|
-
o ? /* @__PURE__ */ n("p", {
|
|
29
|
-
className: t("mt-0.5 text-sm text-text-secondary"),
|
|
25
|
+
/* @__PURE__ */ r("h1", {
|
|
26
|
+
className: n("truncate text-2xl font-semibold text-text-primary"),
|
|
30
27
|
children: o
|
|
28
|
+
}),
|
|
29
|
+
s ? /* @__PURE__ */ r("p", {
|
|
30
|
+
className: n("mt-0.5 text-sm text-text-secondary"),
|
|
31
|
+
children: s
|
|
31
32
|
}) : null
|
|
32
33
|
]
|
|
33
|
-
}),
|
|
34
|
+
}), c ? /* @__PURE__ */ r("div", {
|
|
34
35
|
className: "flex shrink-0 items-center gap-2",
|
|
35
|
-
children:
|
|
36
|
+
children: c
|
|
36
37
|
}) : null]
|
|
37
|
-
}), /* @__PURE__ */
|
|
38
|
-
className:
|
|
39
|
-
children:
|
|
38
|
+
}), /* @__PURE__ */ r("div", {
|
|
39
|
+
className: n("flex-1 overflow-auto p-6"),
|
|
40
|
+
children: l
|
|
40
41
|
})]
|
|
41
42
|
});
|
|
42
43
|
}
|
|
43
44
|
//#endregion
|
|
44
|
-
export {
|
|
45
|
+
export { o as default };
|
|
45
46
|
|
|
46
47
|
//# sourceMappingURL=PageLayout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageLayout.js","names":[],"sources":["../../../src/cosmicintersection/components/PageLayout.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { ChevronLeft } from 'lucide-react';\nimport { useCosmicIntersection } from '../context/CosmicIntersectionContext';\nimport { cn } from '../utils/cn';\n\ninterface PageLayoutProps {\n title: ReactNode;\n description?: ReactNode;\n actions?: ReactNode;\n children: ReactNode;\n /** basePath-relative path for the header back button (e.g. \"datasets\"). */\n backPath?: string;\n}\n\n/**\n * Standard page scaffold: a sticky-feeling header (optional back button, title,\n * description, right-aligned actions) over a scrollable content region. Token\n * styling only.\n */\nexport function PageLayout({ title, description, actions, children, backPath }: PageLayoutProps) {\n const { navigate } = useCosmicIntersection();\n\n return (\n <div className={cn('flex h-full flex-col bg-bg-surface')}>\n <div\n className={cn(\n 'flex items-start justify-between gap-4 border-b border-border-seam bg-bg-surface px-6 py-5'\n )}\n >\n <div className=\"flex min-w-0 flex-col gap-1\">\n {backPath ? (\n <button\n type=\"button\"\n onClick={() => navigate(backPath)}\n className={cn(\n 'flex w-fit items-center gap-1 rounded-button text-sm text-text-secondary transition-colors hover:text-text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus'\n )}\n >\n <ChevronLeft className=\"size-4\" aria-hidden=\"true\" />\n Back\n </button>\n ) : null}\n <h1 className={cn('truncate text-2xl font-semibold text-text-primary')}>{title}</h1>\n {description ? (\n <p className={cn('mt-0.5 text-sm text-text-secondary')}>{description}</p>\n ) : null}\n </div>\n {actions ? <div className=\"flex shrink-0 items-center gap-2\">{actions}</div> : null}\n </div>\n\n <div className={cn('flex-1 overflow-auto p-6')}>{children}</div>\n </div>\n );\n}\n\nexport default PageLayout;\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"PageLayout.js","names":[],"sources":["../../../src/cosmicintersection/components/PageLayout.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { ChevronLeft } from 'lucide-react';\nimport { useCosmicIntersection } from '../context/CosmicIntersectionContext';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { cn } from '../utils/cn';\n\ninterface PageLayoutProps {\n title: ReactNode;\n description?: ReactNode;\n actions?: ReactNode;\n children: ReactNode;\n /** basePath-relative path for the header back button (e.g. \"datasets\"). */\n backPath?: string;\n}\n\n/**\n * Standard page scaffold: a sticky-feeling header (optional back button, title,\n * description, right-aligned actions) over a scrollable content region. Token\n * styling only.\n */\nexport function PageLayout({ title, description, actions, children, backPath }: PageLayoutProps) {\n const { navigate } = useCosmicIntersection();\n const tr = useTr();\n\n return (\n <div className={cn('flex h-full flex-col bg-bg-surface')}>\n <div\n className={cn(\n 'flex items-start justify-between gap-4 border-b border-border-seam bg-bg-surface px-6 py-5'\n )}\n >\n <div className=\"flex min-w-0 flex-col gap-1\">\n {backPath ? (\n <button\n type=\"button\"\n onClick={() => navigate(backPath)}\n className={cn(\n 'flex w-fit items-center gap-1 rounded-button text-sm text-text-secondary transition-colors hover:text-text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus'\n )}\n >\n <ChevronLeft className=\"size-4\" aria-hidden=\"true\" />\n {tr('cosmicintersection.common.back', 'Back')}\n </button>\n ) : null}\n <h1 className={cn('truncate text-2xl font-semibold text-text-primary')}>{title}</h1>\n {description ? (\n <p className={cn('mt-0.5 text-sm text-text-secondary')}>{description}</p>\n ) : null}\n </div>\n {actions ? <div className=\"flex shrink-0 items-center gap-2\">{actions}</div> : null}\n </div>\n\n <div className={cn('flex-1 overflow-auto p-6')}>{children}</div>\n </div>\n );\n}\n\nexport default PageLayout;\n"],"mappings":";;;;;;AAoBA,SAAgB,EAAW,EAAE,UAAO,gBAAa,YAAS,aAAU,eAA6B;CAC/F,IAAM,EAAE,gBAAa,EAAsB,GACrC,IAAK,EAAM;CAEjB,OACE,kBAAC,OAAD;EAAK,WAAW,EAAG,oCAAoC;YAAvD,CACE,kBAAC,OAAD;GACE,WAAW,EACT,4FACF;aAHF,CAKE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACG,IACC,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAS,CAAQ;MAChC,WAAW,EACT,oMACF;gBALF,CAOE,kBAAC,GAAD;OAAa,WAAU;OAAS,eAAY;MAAQ,CAAA,GACnD,EAAG,kCAAkC,MAAM,CACtC;UACN;KACJ,kBAAC,MAAD;MAAI,WAAW,EAAG,mDAAmD;gBAAI;KAAU,CAAA;KAClF,IACC,kBAAC,KAAD;MAAG,WAAW,EAAG,oCAAoC;gBAAI;KAAe,CAAA,IACtE;IACD;OACJ,IAAU,kBAAC,OAAD;IAAK,WAAU;cAAoC;GAAa,CAAA,IAAI,IAC5E;MAEL,kBAAC,OAAD;GAAK,WAAW,EAAG,0BAA0B;GAAI;EAAc,CAAA,CAC5D;;AAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pagination.d.ts","sourceRoot":"","sources":["../../../src/cosmicintersection/components/Pagination.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Pagination.d.ts","sourceRoot":"","sources":["../../../src/cosmicintersection/components/Pagination.tsx"],"names":[],"mappings":"AAiBA,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,mBAAmB,CAsBrF;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,kFAAkF;IAClF,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,KAAK,EACL,WAAW,EACX,OAAO,EACP,MAAM,EACN,MAAM,EACN,SAAmB,EACnB,OAAe,EACf,SAAS,GACV,EAAE,eAAe,kDAqDjB;AAED,eAAe,UAAU,CAAC"}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
2
|
+
import { useState as t } from "react";
|
|
3
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
4
|
+
import { ChevronLeft as i, ChevronRight as a } from "lucide-react";
|
|
5
|
+
import { Button as o } from "@burdenoff/fe-libs/ui";
|
|
5
6
|
//#region src/cosmicintersection/components/Pagination.tsx
|
|
6
|
-
var
|
|
7
|
-
function
|
|
8
|
-
let { pageSize: n =
|
|
9
|
-
return
|
|
7
|
+
var s = 50;
|
|
8
|
+
function c(e = {}) {
|
|
9
|
+
let { pageSize: n = s, resetKey: r } = e, [i, a] = t(0), [o, c] = t(r);
|
|
10
|
+
return o !== r && (c(r), i !== 0 && a(0)), {
|
|
10
11
|
limit: n,
|
|
11
12
|
offset: i,
|
|
12
13
|
page: Math.floor(i / Math.max(1, n)) + 1,
|
|
@@ -15,51 +16,53 @@ function s(t = {}) {
|
|
|
15
16
|
reset: () => a(0)
|
|
16
17
|
};
|
|
17
18
|
}
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
function l({ offset: t, limit: s, loadedCount: c, hasMore: l, onPrev: u, onNext: d, itemLabel: f = "items", loading: p = !1, className: m }) {
|
|
20
|
+
let h = e();
|
|
21
|
+
if (t === 0 && !l && c === 0) return null;
|
|
22
|
+
let g = c === 0 ? 0 : t + 1, _ = t + c, v = t > 0, y = l, b = Math.floor(t / Math.max(1, s)) + 1;
|
|
23
|
+
return /* @__PURE__ */ r("div", {
|
|
24
|
+
className: `flex flex-wrap items-center justify-between gap-3 pt-4 ${m ?? ""}`,
|
|
23
25
|
role: "navigation",
|
|
24
26
|
"aria-label": "Pagination",
|
|
25
27
|
children: [/* @__PURE__ */ n("p", {
|
|
26
28
|
className: "text-xs tabular-nums text-text-secondary",
|
|
27
|
-
children:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
d
|
|
34
|
-
]
|
|
35
|
-
}), /* @__PURE__ */ n("div", {
|
|
29
|
+
children: h("cosmicintersection.common.showingResults", "Showing {{from}}–{{to}} {{itemLabel}}", {
|
|
30
|
+
from: g,
|
|
31
|
+
to: _,
|
|
32
|
+
itemLabel: f
|
|
33
|
+
})
|
|
34
|
+
}), /* @__PURE__ */ r("div", {
|
|
36
35
|
className: "flex items-center gap-2",
|
|
37
36
|
children: [
|
|
38
|
-
/* @__PURE__ */
|
|
37
|
+
/* @__PURE__ */ r("span", {
|
|
39
38
|
className: "text-xs tabular-nums text-text-secondary",
|
|
40
|
-
children: [
|
|
39
|
+
children: [
|
|
40
|
+
h("cosmicintersection.common.page", "Page"),
|
|
41
|
+
" ",
|
|
42
|
+
b
|
|
43
|
+
]
|
|
41
44
|
}),
|
|
42
|
-
/* @__PURE__ */
|
|
45
|
+
/* @__PURE__ */ r(o, {
|
|
43
46
|
size: "sm",
|
|
44
47
|
variant: "outline",
|
|
45
|
-
onClick:
|
|
46
|
-
disabled: !
|
|
47
|
-
"aria-label": "Previous page",
|
|
48
|
-
children: [/* @__PURE__ */
|
|
48
|
+
onClick: u,
|
|
49
|
+
disabled: !v || p,
|
|
50
|
+
"aria-label": h("cosmicintersection.common.prev", "Previous page"),
|
|
51
|
+
children: [/* @__PURE__ */ n(i, { className: "size-4" }), h("cosmicintersection.common.prev", "Prev")]
|
|
49
52
|
}),
|
|
50
|
-
/* @__PURE__ */
|
|
53
|
+
/* @__PURE__ */ r(o, {
|
|
51
54
|
size: "sm",
|
|
52
55
|
variant: "outline",
|
|
53
|
-
onClick:
|
|
54
|
-
disabled: !
|
|
55
|
-
"aria-label": "Next page",
|
|
56
|
-
children: ["Next", /* @__PURE__ */
|
|
56
|
+
onClick: d,
|
|
57
|
+
disabled: !y || p,
|
|
58
|
+
"aria-label": h("cosmicintersection.common.next", "Next page"),
|
|
59
|
+
children: [h("cosmicintersection.common.next", "Next"), /* @__PURE__ */ n(a, { className: "size-4" })]
|
|
57
60
|
})
|
|
58
61
|
]
|
|
59
62
|
})]
|
|
60
63
|
});
|
|
61
64
|
}
|
|
62
65
|
//#endregion
|
|
63
|
-
export {
|
|
66
|
+
export { l as default, c as usePagination };
|
|
64
67
|
|
|
65
68
|
//# sourceMappingURL=Pagination.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pagination.js","names":[],"sources":["../../../src/cosmicintersection/components/Pagination.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\n\n/**\n * Offset/limit pagination for the CosmicIntersection list surfaces.\n *\n * The Cos* connection resolvers return `{ items, pageInfo { hasMore, cursor } }`\n * with NO total count, so paging is driven by `offset`/`limit` + the boolean\n * `hasMore` rather than a computable page count. `usePagination` owns the window;\n * the page feeds `limit`/`offset` into `PaginationInput`, then renders\n * `<Pagination>` with `hasMore` so Next gates correctly.\n */\n\nconst DEFAULT_PAGE_SIZE = 50;\n\nexport interface UsePaginationOptions {\n pageSize?: number;\n /** When this changes (a filter/search term), the window resets to page 1. */\n resetKey?: unknown;\n}\n\nexport interface UsePaginationResult {\n limit: number;\n offset: number;\n /** 1-based page index derived from offset/limit. */\n page: number;\n nextPage: () => void;\n prevPage: () => void;\n reset: () => void;\n}\n\nexport function usePagination(options: UsePaginationOptions = {}): UsePaginationResult {\n const { pageSize = DEFAULT_PAGE_SIZE, resetKey } = options;\n const [offset, setOffset] = useState(0);\n\n // Reset to the first page whenever resetKey changes (React's documented\n // derive-state-during-render pattern — no extra effect/render pass).\n const [prevResetKey, setPrevResetKey] = useState(resetKey);\n if (prevResetKey !== resetKey) {\n setPrevResetKey(resetKey);\n if (offset !== 0) {\n setOffset(0);\n }\n }\n\n return {\n limit: pageSize,\n offset,\n page: Math.floor(offset / Math.max(1, pageSize)) + 1,\n nextPage: () => setOffset((current) => current + pageSize),\n prevPage: () => setOffset((current) => Math.max(0, current - pageSize)),\n reset: () => setOffset(0),\n };\n}\n\nexport interface PaginationProps {\n offset: number;\n limit: number;\n /** Number of rows returned for the current page (`items.length`). */\n loadedCount: number;\n /** Whether the backend reports more rows after this page (`pageInfo.hasMore`). */\n hasMore: boolean;\n onPrev: () => void;\n onNext: () => void;\n /** Plural noun for the range label, e.g. \"datasets\". Defaults to \"items\". */\n itemLabel?: string;\n loading?: boolean;\n className?: string;\n}\n\nexport function Pagination({\n offset,\n limit,\n loadedCount,\n hasMore,\n onPrev,\n onNext,\n itemLabel = 'items',\n loading = false,\n className,\n}: PaginationProps) {\n // Nothing to page through: first page and no more results.\n if (offset === 0 && !hasMore && loadedCount === 0) {\n return null;\n }\n\n const rangeStart = loadedCount === 0 ? 0 : offset + 1;\n const rangeEnd = offset + loadedCount;\n const canPrev = offset > 0;\n const canNext = hasMore;\n const page = Math.floor(offset / Math.max(1, limit)) + 1;\n\n return (\n <div\n className={`flex flex-wrap items-center justify-between gap-3 pt-4 ${className ?? ''}`}\n role=\"navigation\"\n aria-label=\"Pagination\"\n >\n <p className=\"text-xs tabular-nums text-text-secondary\">\n Showing {
|
|
1
|
+
{"version":3,"file":"Pagination.js","names":[],"sources":["../../../src/cosmicintersection/components/Pagination.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\n\n/**\n * Offset/limit pagination for the CosmicIntersection list surfaces.\n *\n * The Cos* connection resolvers return `{ items, pageInfo { hasMore, cursor } }`\n * with NO total count, so paging is driven by `offset`/`limit` + the boolean\n * `hasMore` rather than a computable page count. `usePagination` owns the window;\n * the page feeds `limit`/`offset` into `PaginationInput`, then renders\n * `<Pagination>` with `hasMore` so Next gates correctly.\n */\n\nconst DEFAULT_PAGE_SIZE = 50;\n\nexport interface UsePaginationOptions {\n pageSize?: number;\n /** When this changes (a filter/search term), the window resets to page 1. */\n resetKey?: unknown;\n}\n\nexport interface UsePaginationResult {\n limit: number;\n offset: number;\n /** 1-based page index derived from offset/limit. */\n page: number;\n nextPage: () => void;\n prevPage: () => void;\n reset: () => void;\n}\n\nexport function usePagination(options: UsePaginationOptions = {}): UsePaginationResult {\n const { pageSize = DEFAULT_PAGE_SIZE, resetKey } = options;\n const [offset, setOffset] = useState(0);\n\n // Reset to the first page whenever resetKey changes (React's documented\n // derive-state-during-render pattern — no extra effect/render pass).\n const [prevResetKey, setPrevResetKey] = useState(resetKey);\n if (prevResetKey !== resetKey) {\n setPrevResetKey(resetKey);\n if (offset !== 0) {\n setOffset(0);\n }\n }\n\n return {\n limit: pageSize,\n offset,\n page: Math.floor(offset / Math.max(1, pageSize)) + 1,\n nextPage: () => setOffset((current) => current + pageSize),\n prevPage: () => setOffset((current) => Math.max(0, current - pageSize)),\n reset: () => setOffset(0),\n };\n}\n\nexport interface PaginationProps {\n offset: number;\n limit: number;\n /** Number of rows returned for the current page (`items.length`). */\n loadedCount: number;\n /** Whether the backend reports more rows after this page (`pageInfo.hasMore`). */\n hasMore: boolean;\n onPrev: () => void;\n onNext: () => void;\n /** Plural noun for the range label, e.g. \"datasets\". Defaults to \"items\". */\n itemLabel?: string;\n loading?: boolean;\n className?: string;\n}\n\nexport function Pagination({\n offset,\n limit,\n loadedCount,\n hasMore,\n onPrev,\n onNext,\n itemLabel = 'items',\n loading = false,\n className,\n}: PaginationProps) {\n const tr = useTr();\n // Nothing to page through: first page and no more results.\n if (offset === 0 && !hasMore && loadedCount === 0) {\n return null;\n }\n\n const rangeStart = loadedCount === 0 ? 0 : offset + 1;\n const rangeEnd = offset + loadedCount;\n const canPrev = offset > 0;\n const canNext = hasMore;\n const page = Math.floor(offset / Math.max(1, limit)) + 1;\n\n return (\n <div\n className={`flex flex-wrap items-center justify-between gap-3 pt-4 ${className ?? ''}`}\n role=\"navigation\"\n aria-label=\"Pagination\"\n >\n <p className=\"text-xs tabular-nums text-text-secondary\">\n {tr('cosmicintersection.common.showingResults', 'Showing {{from}}–{{to}} {{itemLabel}}', {\n from: rangeStart,\n to: rangeEnd,\n itemLabel,\n })}\n </p>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs tabular-nums text-text-secondary\">\n {tr('cosmicintersection.common.page', 'Page')} {page}\n </span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={onPrev}\n disabled={!canPrev || loading}\n aria-label={tr('cosmicintersection.common.prev', 'Previous page')}\n >\n <ChevronLeft className=\"size-4\" />\n {tr('cosmicintersection.common.prev', 'Prev')}\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={onNext}\n disabled={!canNext || loading}\n aria-label={tr('cosmicintersection.common.next', 'Next page')}\n >\n {tr('cosmicintersection.common.next', 'Next')}\n <ChevronRight className=\"size-4\" />\n </Button>\n </div>\n </div>\n );\n}\n\nexport default Pagination;\n"],"mappings":";;;;;;AAeA,IAAM,IAAoB;AAkB1B,SAAgB,EAAc,IAAgC,CAAC,GAAwB;CACrF,IAAM,EAAE,cAAW,GAAmB,gBAAa,GAC7C,CAAC,GAAQ,KAAa,EAAS,CAAC,GAIhC,CAAC,GAAc,KAAmB,EAAS,CAAQ;CAQzD,OAPI,MAAiB,MACnB,EAAgB,CAAQ,GACpB,MAAW,KACb,EAAU,CAAC,IAIR;EACL,OAAO;EACP;EACA,MAAM,KAAK,MAAM,IAAS,KAAK,IAAI,GAAG,CAAQ,CAAC,IAAI;EACnD,gBAAgB,GAAW,MAAY,IAAU,CAAQ;EACzD,gBAAgB,GAAW,MAAY,KAAK,IAAI,GAAG,IAAU,CAAQ,CAAC;EACtE,aAAa,EAAU,CAAC;CAC1B;AACF;AAiBA,SAAgB,EAAW,EACzB,WACA,UACA,gBACA,YACA,WACA,WACA,eAAY,SACZ,aAAU,IACV,gBACkB;CAClB,IAAM,IAAK,EAAM;CAEjB,IAAI,MAAW,KAAK,CAAC,KAAW,MAAgB,GAC9C,OAAO;CAGT,IAAM,IAAa,MAAgB,IAAI,IAAI,IAAS,GAC9C,IAAW,IAAS,GACpB,IAAU,IAAS,GACnB,IAAU,GACV,IAAO,KAAK,MAAM,IAAS,KAAK,IAAI,GAAG,CAAK,CAAC,IAAI;CAEvD,OACE,kBAAC,OAAD;EACE,WAAW,0DAA0D,KAAa;EAClF,MAAK;EACL,cAAW;YAHb,CAKE,kBAAC,KAAD;GAAG,WAAU;aACV,EAAG,4CAA4C,yCAAyC;IACvF,MAAM;IACN,IAAI;IACJ;GACF,CAAC;EACA,CAAA,GACH,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACG,EAAG,kCAAkC,MAAM;MAAE;MAAE;KAC5C;;IACN,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,SAAS;KACT,UAAU,CAAC,KAAW;KACtB,cAAY,EAAG,kCAAkC,eAAe;eALlE,CAOE,kBAAC,GAAD,EAAa,WAAU,SAAU,CAAA,GAChC,EAAG,kCAAkC,MAAM,CACtC;;IACR,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,SAAS;KACT,UAAU,CAAC,KAAW;KACtB,cAAY,EAAG,kCAAkC,WAAW;eAL9D,CAOG,EAAG,kCAAkC,MAAM,GAC5C,kBAAC,GAAD,EAAc,WAAU,SAAU,CAAA,CAC5B;;GACL;IACF;;AAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusBadge.d.ts","sourceRoot":"","sources":["../../../src/cosmicintersection/components/StatusBadge.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"StatusBadge.d.ts","sourceRoot":"","sources":["../../../src/cosmicintersection/components/StatusBadge.tsx"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAiD9E,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGrD;AAuBD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,gBAAgB,2CAc/E;AAED,eAAe,WAAW,CAAC"}
|