@burdenoff/microfe-cosmicintersection 2026.512.1 → 2026.704.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.
- package/dist/cosmicintersection/CosmicIntersectionRoot.js +38 -113
- package/dist/cosmicintersection/CosmicIntersectionRoot.js.map +1 -1
- package/dist/cosmicintersection/CosmicIntersectionRoutes.js +102 -44
- package/dist/cosmicintersection/CosmicIntersectionRoutes.js.map +1 -1
- package/dist/cosmicintersection/api/cosmicApi.js +853 -0
- package/dist/cosmicintersection/api/cosmicApi.js.map +1 -0
- package/dist/cosmicintersection/components/ActionMenu.js +106 -0
- package/dist/cosmicintersection/components/ActionMenu.js.map +1 -0
- package/dist/cosmicintersection/components/EmptyState.js +35 -0
- package/dist/cosmicintersection/components/EmptyState.js.map +1 -0
- package/dist/cosmicintersection/components/EntityFormShell.js +57 -0
- package/dist/cosmicintersection/components/EntityFormShell.js.map +1 -0
- package/dist/cosmicintersection/components/EntityTagsField.js +77 -0
- package/dist/cosmicintersection/components/EntityTagsField.js.map +1 -0
- package/dist/cosmicintersection/components/ErrorState.js +40 -0
- package/dist/cosmicintersection/components/ErrorState.js.map +1 -0
- package/dist/cosmicintersection/components/FileUploadField.js +146 -0
- package/dist/cosmicintersection/components/FileUploadField.js.map +1 -0
- package/dist/cosmicintersection/components/LoadingState.js +23 -0
- package/dist/cosmicintersection/components/LoadingState.js.map +1 -0
- package/dist/cosmicintersection/components/PageLayout.js +46 -0
- package/dist/cosmicintersection/components/PageLayout.js.map +1 -0
- package/dist/cosmicintersection/components/Pagination.js +65 -0
- package/dist/cosmicintersection/components/Pagination.js.map +1 -0
- package/dist/cosmicintersection/components/SafeLink.js +35 -0
- package/dist/cosmicintersection/components/SafeLink.js.map +1 -0
- package/dist/cosmicintersection/components/StatusBadge.js +53 -0
- package/dist/cosmicintersection/components/StatusBadge.js.map +1 -0
- package/dist/cosmicintersection/components/index.js +11 -0
- package/dist/cosmicintersection/context/CosmicIntersectionContext.js +46 -0
- package/dist/cosmicintersection/context/CosmicIntersectionContext.js.map +1 -0
- package/dist/cosmicintersection/index.js +0 -2
- package/dist/cosmicintersection/lib/mutationResult.js +12 -0
- package/dist/cosmicintersection/lib/mutationResult.js.map +1 -0
- package/dist/cosmicintersection/lib/toast.js +36 -0
- package/dist/cosmicintersection/lib/toast.js.map +1 -0
- package/dist/cosmicintersection/pages/access/AccessPage.js +340 -0
- package/dist/cosmicintersection/pages/access/AccessPage.js.map +1 -0
- package/dist/cosmicintersection/pages/analyses/AnalysesPage.js +484 -0
- package/dist/cosmicintersection/pages/analyses/AnalysesPage.js.map +1 -0
- package/dist/cosmicintersection/pages/collections/CollectionDetailPage.js +164 -0
- package/dist/cosmicintersection/pages/collections/CollectionDetailPage.js.map +1 -0
- package/dist/cosmicintersection/pages/collections/CollectionFormPage.js +117 -0
- package/dist/cosmicintersection/pages/collections/CollectionFormPage.js.map +1 -0
- package/dist/cosmicintersection/pages/collections/CollectionsPage.js +103 -0
- package/dist/cosmicintersection/pages/collections/CollectionsPage.js.map +1 -0
- package/dist/cosmicintersection/pages/dashboard/DashboardPage.js +244 -0
- package/dist/cosmicintersection/pages/dashboard/DashboardPage.js.map +1 -0
- package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js +460 -0
- package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js.map +1 -0
- package/dist/cosmicintersection/pages/datasets/DatasetFormPage.js +218 -0
- package/dist/cosmicintersection/pages/datasets/DatasetFormPage.js.map +1 -0
- package/dist/cosmicintersection/pages/datasets/DatasetsPage.js +252 -0
- package/dist/cosmicintersection/pages/datasets/DatasetsPage.js.map +1 -0
- package/dist/cosmicintersection/pages/notebooks/NotebookDetailPage.js +393 -0
- package/dist/cosmicintersection/pages/notebooks/NotebookDetailPage.js.map +1 -0
- package/dist/cosmicintersection/pages/notebooks/NotebookFormPage.js +313 -0
- package/dist/cosmicintersection/pages/notebooks/NotebookFormPage.js.map +1 -0
- package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js +203 -0
- package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js.map +1 -0
- package/dist/cosmicintersection/pages/pipelines/PipelineDetailPage.js +244 -0
- package/dist/cosmicintersection/pages/pipelines/PipelineDetailPage.js.map +1 -0
- package/dist/cosmicintersection/pages/pipelines/PipelineFormPage.js +210 -0
- package/dist/cosmicintersection/pages/pipelines/PipelineFormPage.js.map +1 -0
- package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js +193 -0
- package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js.map +1 -0
- package/dist/cosmicintersection/pages/releases/ReleaseFormPage.js +184 -0
- package/dist/cosmicintersection/pages/releases/ReleaseFormPage.js.map +1 -0
- package/dist/cosmicintersection/pages/releases/ReleasesPage.js +368 -0
- package/dist/cosmicintersection/pages/releases/ReleasesPage.js.map +1 -0
- package/dist/cosmicintersection/pages/runs/RunDetailPage.js +375 -0
- package/dist/cosmicintersection/pages/runs/RunDetailPage.js.map +1 -0
- package/dist/cosmicintersection/pages/runs/RunsPage.js +180 -0
- package/dist/cosmicintersection/pages/runs/RunsPage.js.map +1 -0
- package/dist/cosmicintersection/pages/sources/SourceFormPage.js +229 -0
- package/dist/cosmicintersection/pages/sources/SourceFormPage.js.map +1 -0
- package/dist/cosmicintersection/pages/sources/SourcesPage.js +217 -0
- package/dist/cosmicintersection/pages/sources/SourcesPage.js.map +1 -0
- package/dist/cosmicintersection/utils/cn.js +10 -0
- package/dist/cosmicintersection/utils/cn.js.map +1 -0
- package/dist/index.js +2 -4
- package/dist/node_modules/clsx/dist/clsx.js +18 -0
- package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1716 -0
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
- package/package.json +2 -1
- package/dist/cosmicintersection/CosmicIntersectionRoot.d.ts +0 -5
- package/dist/cosmicintersection/CosmicIntersectionRoot.d.ts.map +0 -1
- package/dist/cosmicintersection/CosmicIntersectionRoutes.d.ts +0 -2
- package/dist/cosmicintersection/CosmicIntersectionRoutes.d.ts.map +0 -1
- package/dist/cosmicintersection/index.d.ts +0 -5
- package/dist/cosmicintersection/index.d.ts.map +0 -1
- package/dist/cosmicintersection/pages/AiDiscoveryPage.d.ts +0 -2
- package/dist/cosmicintersection/pages/AiDiscoveryPage.d.ts.map +0 -1
- package/dist/cosmicintersection/pages/AiDiscoveryPage.js +0 -363
- package/dist/cosmicintersection/pages/AiDiscoveryPage.js.map +0 -1
- package/dist/cosmicintersection/pages/DashboardPage.d.ts +0 -2
- package/dist/cosmicintersection/pages/DashboardPage.d.ts.map +0 -1
- package/dist/cosmicintersection/pages/DashboardPage.js +0 -306
- package/dist/cosmicintersection/pages/DashboardPage.js.map +0 -1
- package/dist/cosmicintersection/pages/SpaceScienceCitizenPage.d.ts +0 -2
- package/dist/cosmicintersection/pages/SpaceScienceCitizenPage.d.ts.map +0 -1
- package/dist/cosmicintersection/pages/SpaceScienceCitizenPage.js +0 -287
- package/dist/cosmicintersection/pages/SpaceScienceCitizenPage.js.map +0 -1
- package/dist/cosmicintersection/pages/SpaceScienceDataPage.d.ts +0 -2
- package/dist/cosmicintersection/pages/SpaceScienceDataPage.d.ts.map +0 -1
- package/dist/cosmicintersection/pages/SpaceScienceDataPage.js +0 -385
- package/dist/cosmicintersection/pages/SpaceScienceDataPage.js.map +0 -1
- package/dist/cosmicintersection/pages/SpaceScienceDataPage1.d.ts +0 -2
- package/dist/cosmicintersection/pages/SpaceScienceDataPage1.d.ts.map +0 -1
- package/dist/cosmicintersection/pages/SpaceScienceDataPage1.js +0 -420
- package/dist/cosmicintersection/pages/SpaceScienceDataPage1.js.map +0 -1
- package/dist/cosmicintersection/pages/SpaceScienceLearningPage.d.ts +0 -2
- package/dist/cosmicintersection/pages/SpaceScienceLearningPage.d.ts.map +0 -1
- package/dist/cosmicintersection/pages/SpaceScienceLearningPage.js +0 -309
- package/dist/cosmicintersection/pages/SpaceScienceLearningPage.js.map +0 -1
- package/dist/cosmicintersection/pages/SpaceScienceLearningPage1.d.ts +0 -2
- package/dist/cosmicintersection/pages/SpaceScienceLearningPage1.d.ts.map +0 -1
- package/dist/cosmicintersection/pages/SpaceScienceLearningPage1.js +0 -361
- package/dist/cosmicintersection/pages/SpaceScienceLearningPage1.js.map +0 -1
- package/dist/cosmicintersection/pages/SpaceSciencePage.d.ts +0 -2
- package/dist/cosmicintersection/pages/SpaceSciencePage.d.ts.map +0 -1
- package/dist/cosmicintersection/pages/SpaceSciencePage.js +0 -194
- package/dist/cosmicintersection/pages/SpaceSciencePage.js.map +0 -1
- package/dist/cosmicintersection/pages/SpaceScienceResearchPage.d.ts +0 -2
- package/dist/cosmicintersection/pages/SpaceScienceResearchPage.d.ts.map +0 -1
- package/dist/cosmicintersection/pages/SpaceScienceResearchPage.js +0 -290
- package/dist/cosmicintersection/pages/SpaceScienceResearchPage.js.map +0 -1
- package/dist/cosmicintersection/pages/SpaceScienceSimulationPage.d.ts +0 -2
- package/dist/cosmicintersection/pages/SpaceScienceSimulationPage.d.ts.map +0 -1
- package/dist/cosmicintersection/pages/SpaceScienceSimulationPage.js +0 -279
- package/dist/cosmicintersection/pages/SpaceScienceSimulationPage.js.map +0 -1
- package/dist/cosmicintersection/providers/CosmicIntersectionProvider.d.ts +0 -6
- package/dist/cosmicintersection/providers/CosmicIntersectionProvider.d.ts.map +0 -1
- package/dist/cosmicintersection/providers/CosmicIntersectionProvider.js +0 -27
- package/dist/cosmicintersection/providers/CosmicIntersectionProvider.js.map +0 -1
- package/dist/cosmicintersection/store/cosmicintersectionStore.d.ts +0 -18
- package/dist/cosmicintersection/store/cosmicintersectionStore.d.ts.map +0 -1
- package/dist/cosmicintersection/store/cosmicintersectionStore.js +0 -478
- package/dist/cosmicintersection/store/cosmicintersectionStore.js.map +0 -1
- package/dist/cosmicintersection/types/index.d.ts +0 -109
- package/dist/cosmicintersection/types/index.d.ts.map +0 -1
- package/dist/cosmicintersection/utils/cn.d.ts +0 -4
- package/dist/cosmicintersection/utils/cn.d.ts.map +0 -1
- package/dist/cosmicintersection/utils/i18n.d.ts +0 -6
- package/dist/cosmicintersection/utils/i18n.d.ts.map +0 -1
- package/dist/cosmicintersection/utils/i18n.js +0 -15
- package/dist/cosmicintersection/utils/i18n.js.map +0 -1
- package/dist/cosmicintersection/utils/navigation.d.ts +0 -2
- package/dist/cosmicintersection/utils/navigation.d.ts.map +0 -1
- package/dist/cosmicintersection/utils/navigation.js +0 -8
- package/dist/cosmicintersection/utils/navigation.js.map +0 -1
- package/dist/dev/main.d.ts +0 -2
- package/dist/dev/main.d.ts.map +0 -1
- package/dist/generated/global-operations.d.ts +0 -2
- package/dist/generated/global-operations.d.ts.map +0 -1
- package/dist/generated/global-types.d.ts +0 -18199
- package/dist/generated/global-types.d.ts.map +0 -1
- package/dist/generated/wspace-operations.d.ts +0 -2
- package/dist/generated/wspace-operations.d.ts.map +0 -1
- package/dist/generated/wspace-types.d.ts +0 -43046
- package/dist/generated/wspace-types.d.ts.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
|
@@ -0,0 +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":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as e, jsxs as t } from "react/jsx-runtime";
|
|
2
|
+
import { Skeleton as n, Spinner as r } from "@burdenoff/fe-libs/ui";
|
|
3
|
+
//#region src/cosmicintersection/components/LoadingState.tsx
|
|
4
|
+
function i({ variant: i = "spinner", rows: a = 5, label: o = "Loading…", className: s }) {
|
|
5
|
+
return i === "rows" ? /* @__PURE__ */ e("div", {
|
|
6
|
+
className: `space-y-3 ${s ?? ""}`,
|
|
7
|
+
role: "status",
|
|
8
|
+
"aria-label": o,
|
|
9
|
+
children: Array.from({ length: a }).map((t, r) => /* @__PURE__ */ e(n, { className: "h-14 w-full rounded-card" }, r))
|
|
10
|
+
}) : /* @__PURE__ */ t("div", {
|
|
11
|
+
className: `flex min-h-[240px] flex-col items-center justify-center gap-3 ${s ?? ""}`,
|
|
12
|
+
role: "status",
|
|
13
|
+
"aria-label": o,
|
|
14
|
+
children: [/* @__PURE__ */ e(r, { className: "size-6 text-text-secondary" }), /* @__PURE__ */ e("p", {
|
|
15
|
+
className: "text-sm text-text-secondary",
|
|
16
|
+
children: o
|
|
17
|
+
})]
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { i as default };
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=LoadingState.js.map
|
|
@@ -0,0 +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 = 'Loading…',\n className,\n}: LoadingStateProps) {\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":";;;AAeA,SAAgB,EAAa,EAC3B,aAAU,WACV,UAAO,GACP,WAAQ,YACR,gBACoB;AAWpB,QAVI,MAAY,SAEZ,kBAAC,OAAD;EAAK,WAAW,aAAa,KAAa;EAAM,MAAK;EAAS,cAAY;YACvE,MAAM,KAAK,EAAE,QAAQ,GAAM,CAAC,CAAC,KAAK,GAAG,MACpC,kBAAC,GAAD,EAAsB,WAAU,4BAA6B,EAA9C,EAA8C,CAC7D;EACE,CAAA,GAKR,kBAAC,OAAD;EACE,WAAW,iEAAiE,KAAa;EACzF,MAAK;EACL,cAAY;YAHd,CAKE,kBAAC,GAAD,EAAS,WAAU,8BAA+B,CAAA,EAClD,kBAAC,KAAD;GAAG,WAAU;aAA+B;GAAU,CAAA,CAClD"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useCosmicIntersection as e } from "../context/CosmicIntersectionContext.js";
|
|
2
|
+
import { cn as t } from "../utils/cn.js";
|
|
3
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
4
|
+
import { ChevronLeft as i } from "lucide-react";
|
|
5
|
+
//#region src/cosmicintersection/components/PageLayout.tsx
|
|
6
|
+
function a({ title: a, description: o, actions: s, children: c, backPath: l }) {
|
|
7
|
+
let { navigate: u } = e();
|
|
8
|
+
return /* @__PURE__ */ r("div", {
|
|
9
|
+
className: t("flex h-full flex-col bg-bg-surface"),
|
|
10
|
+
children: [/* @__PURE__ */ r("div", {
|
|
11
|
+
className: t("flex items-start justify-between gap-4 border-b border-border-seam bg-bg-surface px-6 py-5"),
|
|
12
|
+
children: [/* @__PURE__ */ r("div", {
|
|
13
|
+
className: "flex min-w-0 flex-col gap-1",
|
|
14
|
+
children: [
|
|
15
|
+
l ? /* @__PURE__ */ r("button", {
|
|
16
|
+
type: "button",
|
|
17
|
+
onClick: () => u(l),
|
|
18
|
+
className: t("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"),
|
|
19
|
+
children: [/* @__PURE__ */ n(i, {
|
|
20
|
+
className: "size-4",
|
|
21
|
+
"aria-hidden": "true"
|
|
22
|
+
}), "Back"]
|
|
23
|
+
}) : null,
|
|
24
|
+
/* @__PURE__ */ n("h1", {
|
|
25
|
+
className: t("truncate text-2xl font-semibold text-text-primary"),
|
|
26
|
+
children: a
|
|
27
|
+
}),
|
|
28
|
+
o ? /* @__PURE__ */ n("p", {
|
|
29
|
+
className: t("mt-0.5 text-sm text-text-secondary"),
|
|
30
|
+
children: o
|
|
31
|
+
}) : null
|
|
32
|
+
]
|
|
33
|
+
}), s ? /* @__PURE__ */ n("div", {
|
|
34
|
+
className: "flex shrink-0 items-center gap-2",
|
|
35
|
+
children: s
|
|
36
|
+
}) : null]
|
|
37
|
+
}), /* @__PURE__ */ n("div", {
|
|
38
|
+
className: t("flex-1 overflow-auto p-6"),
|
|
39
|
+
children: c
|
|
40
|
+
})]
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { a as default };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=PageLayout.js.map
|
|
@@ -0,0 +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":";;;;;AAmBA,SAAgB,EAAW,EAAE,UAAO,gBAAa,YAAS,aAAU,eAA6B;CAC/F,IAAM,EAAE,gBAAa,GAAuB;AAE5C,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,qCAAqC;YAAxD,CACE,kBAAC,OAAD;GACE,WAAW,EACT,6FACD;aAHH,CAKE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACG,IACC,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAS,EAAS;MACjC,WAAW,EACT,qMACD;gBALH,CAOE,kBAAC,GAAD;OAAa,WAAU;OAAS,eAAY;OAAS,CAAA,EAAA,OAE9C;UACP;KACJ,kBAAC,MAAD;MAAI,WAAW,EAAG,oDAAoD;gBAAG;MAAW,CAAA;KACnF,IACC,kBAAC,KAAD;MAAG,WAAW,EAAG,qCAAqC;gBAAG;MAAgB,CAAA,GACvE;KACA;OACL,IAAU,kBAAC,OAAD;IAAK,WAAU;cAAoC;IAAc,CAAA,GAAG,KAC3E;MAEN,kBAAC,OAAD;GAAK,WAAW,EAAG,2BAA2B;GAAG;GAAe,CAAA,CAC5D"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useState as e } from "react";
|
|
2
|
+
import { jsx as t, jsxs as n } from "react/jsx-runtime";
|
|
3
|
+
import { ChevronLeft as r, ChevronRight as i } from "lucide-react";
|
|
4
|
+
import { Button as a } from "@burdenoff/fe-libs/ui";
|
|
5
|
+
//#region src/cosmicintersection/components/Pagination.tsx
|
|
6
|
+
var o = 50;
|
|
7
|
+
function s(t = {}) {
|
|
8
|
+
let { pageSize: n = o, resetKey: r } = t, [i, a] = e(0), [s, c] = e(r);
|
|
9
|
+
return s !== r && (c(r), i !== 0 && a(0)), {
|
|
10
|
+
limit: n,
|
|
11
|
+
offset: i,
|
|
12
|
+
page: Math.floor(i / Math.max(1, n)) + 1,
|
|
13
|
+
nextPage: () => a((e) => e + n),
|
|
14
|
+
prevPage: () => a((e) => Math.max(0, e - n)),
|
|
15
|
+
reset: () => a(0)
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function c({ offset: e, limit: o, loadedCount: s, hasMore: c, onPrev: l, onNext: u, itemLabel: d = "items", loading: f = !1, className: p }) {
|
|
19
|
+
if (e === 0 && !c && s === 0) return null;
|
|
20
|
+
let m = s === 0 ? 0 : e + 1, h = e + s, g = e > 0, _ = c, v = Math.floor(e / Math.max(1, o)) + 1;
|
|
21
|
+
return /* @__PURE__ */ n("div", {
|
|
22
|
+
className: `flex flex-wrap items-center justify-between gap-3 pt-4 ${p ?? ""}`,
|
|
23
|
+
role: "navigation",
|
|
24
|
+
"aria-label": "Pagination",
|
|
25
|
+
children: [/* @__PURE__ */ n("p", {
|
|
26
|
+
className: "text-xs tabular-nums text-text-secondary",
|
|
27
|
+
children: [
|
|
28
|
+
"Showing ",
|
|
29
|
+
m,
|
|
30
|
+
"–",
|
|
31
|
+
h,
|
|
32
|
+
" ",
|
|
33
|
+
d
|
|
34
|
+
]
|
|
35
|
+
}), /* @__PURE__ */ n("div", {
|
|
36
|
+
className: "flex items-center gap-2",
|
|
37
|
+
children: [
|
|
38
|
+
/* @__PURE__ */ n("span", {
|
|
39
|
+
className: "text-xs tabular-nums text-text-secondary",
|
|
40
|
+
children: ["Page ", v]
|
|
41
|
+
}),
|
|
42
|
+
/* @__PURE__ */ n(a, {
|
|
43
|
+
size: "sm",
|
|
44
|
+
variant: "outline",
|
|
45
|
+
onClick: l,
|
|
46
|
+
disabled: !g || f,
|
|
47
|
+
"aria-label": "Previous page",
|
|
48
|
+
children: [/* @__PURE__ */ t(r, { className: "size-4" }), "Prev"]
|
|
49
|
+
}),
|
|
50
|
+
/* @__PURE__ */ n(a, {
|
|
51
|
+
size: "sm",
|
|
52
|
+
variant: "outline",
|
|
53
|
+
onClick: u,
|
|
54
|
+
disabled: !_ || f,
|
|
55
|
+
"aria-label": "Next page",
|
|
56
|
+
children: ["Next", /* @__PURE__ */ t(i, { className: "size-4" })]
|
|
57
|
+
})
|
|
58
|
+
]
|
|
59
|
+
})]
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
export { c as default, s as usePagination };
|
|
64
|
+
|
|
65
|
+
//# sourceMappingURL=Pagination.js.map
|
|
@@ -0,0 +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 {rangeStart}–{rangeEnd} {itemLabel}\n </p>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs tabular-nums text-text-secondary\">Page {page}</span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={onPrev}\n disabled={!canPrev || loading}\n aria-label=\"Previous page\"\n >\n <ChevronLeft className=\"size-4\" />\n Prev\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={onNext}\n disabled={!canNext || loading}\n aria-label=\"Next page\"\n >\n Next\n <ChevronRight className=\"size-4\" />\n </Button>\n </div>\n </div>\n );\n}\n\nexport default Pagination;\n"],"mappings":";;;;;AAcA,IAAM,IAAoB;AAkB1B,SAAgB,EAAc,IAAgC,EAAE,EAAuB;CACrF,IAAM,EAAE,cAAW,GAAmB,gBAAa,GAC7C,CAAC,GAAQ,KAAa,EAAS,EAAE,EAIjC,CAAC,GAAc,KAAmB,EAAS,EAAS;AAQ1D,QAPI,MAAiB,MACnB,EAAgB,EAAS,EACrB,MAAW,KACb,EAAU,EAAE,GAIT;EACL,OAAO;EACP;EACA,MAAM,KAAK,MAAM,IAAS,KAAK,IAAI,GAAG,EAAS,CAAC,GAAG;EACnD,gBAAgB,GAAW,MAAY,IAAU,EAAS;EAC1D,gBAAgB,GAAW,MAAY,KAAK,IAAI,GAAG,IAAU,EAAS,CAAC;EACvE,aAAa,EAAU,EAAE;EAC1B;;AAkBH,SAAgB,EAAW,EACzB,WACA,UACA,gBACA,YACA,WACA,WACA,eAAY,SACZ,aAAU,IACV,gBACkB;AAElB,KAAI,MAAW,KAAK,CAAC,KAAW,MAAgB,EAC9C,QAAO;CAGT,IAAM,IAAa,MAAgB,IAAI,IAAI,IAAS,GAC9C,IAAW,IAAS,GACpB,IAAU,IAAS,GACnB,IAAU,GACV,IAAO,KAAK,MAAM,IAAS,KAAK,IAAI,GAAG,EAAM,CAAC,GAAG;AAEvD,QACE,kBAAC,OAAD;EACE,WAAW,0DAA0D,KAAa;EAClF,MAAK;EACL,cAAW;YAHb,CAKE,kBAAC,KAAD;GAAG,WAAU;aAAb;IAAwD;IAC7C;IAAW;IAAE;IAAS;IAAE;IAC/B;MACJ,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAA2D,SAAM,EAAY;;IAC7E,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,SAAS;KACT,UAAU,CAAC,KAAW;KACtB,cAAW;eALb,CAOE,kBAAC,GAAD,EAAa,WAAU,UAAW,CAAA,EAAA,OAE3B;;IACT,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,SAAS;KACT,UAAU,CAAC,KAAW;KACtB,cAAW;eALb,CAMC,QAEC,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA,CAC5B;;IACL;KACF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as e } from "react/jsx-runtime";
|
|
2
|
+
//#region src/cosmicintersection/components/SafeLink.tsx
|
|
3
|
+
function t(e) {
|
|
4
|
+
if (!e) return null;
|
|
5
|
+
let t = e.trim();
|
|
6
|
+
if (!t) return null;
|
|
7
|
+
if (t.startsWith("/") && !t.startsWith("//")) return t;
|
|
8
|
+
let n = /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(t) ? t : `https://${t}`;
|
|
9
|
+
try {
|
|
10
|
+
let e = new URL(n);
|
|
11
|
+
return e.protocol === "http:" || e.protocol === "https:" ? e.toString() : null;
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function n({ href: n, children: r, className: i, fallbackClassName: a, ...o }) {
|
|
17
|
+
let s = t(n);
|
|
18
|
+
return s ? /* @__PURE__ */ e("a", {
|
|
19
|
+
href: s,
|
|
20
|
+
className: i,
|
|
21
|
+
...s.startsWith("/") ? {} : {
|
|
22
|
+
target: "_blank",
|
|
23
|
+
rel: "noopener noreferrer nofollow"
|
|
24
|
+
},
|
|
25
|
+
...o,
|
|
26
|
+
children: r ?? s
|
|
27
|
+
}) : /* @__PURE__ */ e("span", {
|
|
28
|
+
className: a ?? i,
|
|
29
|
+
children: r ?? n ?? ""
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { n as default, t as safeUrl };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=SafeLink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SafeLink.js","names":[],"sources":["../../../src/cosmicintersection/components/SafeLink.tsx"],"sourcesContent":["import type { AnchorHTMLAttributes, ReactNode } from 'react';\n\n/**\n * Validate and normalise a user-supplied URL before it is ever placed in an\n * `href`. Free-text URL fields (source endpoints, external references) are\n * persisted verbatim by the backend, so rendering them directly into an anchor\n * is a stored-XSS sink: a value like `javascript:alert(1)` or `data:text/html,...`\n * would execute on click. This helper is the single choke point that neutralises\n * that.\n *\n * Rules:\n * - empty / whitespace → `null` (caller renders plain text)\n * - bare domain → prefixed with `https://` (`example.com` → `https://example.com`)\n * - `http:` / `https:` → accepted, returned normalised\n * - anything else → `null` (rejects `javascript:`, `data:`, `file:`, `mailto:`…)\n *\n * Also used for absolute in-app links (e.g. `/conversations`), which are returned\n * as-is when they are root-relative.\n */\nexport function safeUrl(raw: string | null | undefined): string | null {\n if (!raw) {\n return null;\n }\n const trimmed = raw.trim();\n if (!trimmed) {\n return null;\n }\n\n // Root-relative in-app paths (e.g. \"/conversations\") are always safe.\n if (trimmed.startsWith('/') && !trimmed.startsWith('//')) {\n return trimmed;\n }\n\n const hasScheme = /^[a-zA-Z][a-zA-Z\\d+.-]*:/.test(trimmed);\n const candidate = hasScheme ? trimmed : `https://${trimmed}`;\n\n try {\n const url = new URL(candidate);\n if (url.protocol === 'http:' || url.protocol === 'https:') {\n return url.toString();\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport interface SafeLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {\n href: string | null | undefined;\n children?: ReactNode;\n /** Class applied to the rendered plain-text span when the href is unsafe. */\n fallbackClassName?: string;\n}\n\n/**\n * Anchor that only renders a clickable link when `href` passes `safeUrl`.\n * Unsafe/blank values fall back to inert text so a malicious `javascript:` URL\n * can never be activated. External links open in a new tab with\n * `noopener noreferrer`; root-relative in-app links open in the same tab.\n */\nexport function SafeLink({ href, children, className, fallbackClassName, ...rest }: SafeLinkProps) {\n const safe = safeUrl(href);\n\n if (!safe) {\n return <span className={fallbackClassName ?? className}>{children ?? href ?? ''}</span>;\n }\n\n const isInternal = safe.startsWith('/');\n\n return (\n <a\n href={safe}\n className={className}\n {...(isInternal ? {} : { target: '_blank', rel: 'noopener noreferrer nofollow' })}\n {...rest}\n >\n {children ?? safe}\n </a>\n );\n}\n\nexport default SafeLink;\n"],"mappings":";;AAmBA,SAAgB,EAAQ,GAA+C;AACrE,KAAI,CAAC,EACH,QAAO;CAET,IAAM,IAAU,EAAI,MAAM;AAC1B,KAAI,CAAC,EACH,QAAO;AAIT,KAAI,EAAQ,WAAW,IAAI,IAAI,CAAC,EAAQ,WAAW,KAAK,CACtD,QAAO;CAIT,IAAM,IADY,2BAA2B,KAAK,EAAQ,GAC5B,IAAU,WAAW;AAEnD,KAAI;EACF,IAAM,IAAM,IAAI,IAAI,EAAU;AAI9B,SAHI,EAAI,aAAa,WAAW,EAAI,aAAa,WACxC,EAAI,UAAU,GAEhB;SACD;AACN,SAAO;;;AAiBX,SAAgB,EAAS,EAAE,SAAM,aAAU,cAAW,sBAAmB,GAAG,KAAuB;CACjG,IAAM,IAAO,EAAQ,EAAK;AAQ1B,QANK,IAOH,kBAAC,KAAD;EACE,MAAM;EACK;EACX,GANe,EAAK,WAAW,IAAI,GAMjB,EAAE,GAAG;GAAE,QAAQ;GAAU,KAAK;GAAgC;EAChF,GAAI;YAEH,KAAY;EACX,CAAA,GAbG,kBAAC,QAAD;EAAM,WAAW,KAAqB;YAAY,KAAY,KAAQ;EAAU,CAAA"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as e } from "react/jsx-runtime";
|
|
2
|
+
//#region src/cosmicintersection/components/StatusBadge.tsx
|
|
3
|
+
var t = {
|
|
4
|
+
success: "bg-status-success-bg-subtle text-status-success-text border-status-success-border",
|
|
5
|
+
info: "bg-status-info-bg-subtle text-status-info-text border-status-info-border",
|
|
6
|
+
warning: "bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border",
|
|
7
|
+
error: "bg-status-error-bg-subtle text-status-error-text border-status-error-border",
|
|
8
|
+
neutral: "bg-status-neutral-bg-subtle text-status-neutral-text border-status-neutral-border"
|
|
9
|
+
}, n = {
|
|
10
|
+
AVAILABLE: "success",
|
|
11
|
+
CONNECTED: "success",
|
|
12
|
+
SUCCEEDED: "success",
|
|
13
|
+
COMPLETED: "success",
|
|
14
|
+
PUBLISHED: "success",
|
|
15
|
+
ACTIVE: "success",
|
|
16
|
+
PUBLIC: "success",
|
|
17
|
+
INGESTING: "warning",
|
|
18
|
+
SYNCING: "warning",
|
|
19
|
+
RUNNING: "warning",
|
|
20
|
+
DEGRADED: "warning",
|
|
21
|
+
PAUSED: "warning",
|
|
22
|
+
DEPRECATED: "warning",
|
|
23
|
+
EXPIRED: "warning",
|
|
24
|
+
EMBARGOED: "warning",
|
|
25
|
+
RESTRICTED: "warning",
|
|
26
|
+
UNDER_REVIEW: "warning",
|
|
27
|
+
DRAFT: "info",
|
|
28
|
+
CONFIGURING: "info",
|
|
29
|
+
QUEUED: "info",
|
|
30
|
+
PENDING: "info",
|
|
31
|
+
WORKSPACE: "info",
|
|
32
|
+
ERROR: "error",
|
|
33
|
+
FAILED: "error",
|
|
34
|
+
CANCELLED: "error",
|
|
35
|
+
RETRACTED: "error",
|
|
36
|
+
REVOKED: "error",
|
|
37
|
+
DISABLED: "error",
|
|
38
|
+
ARCHIVED: "neutral"
|
|
39
|
+
};
|
|
40
|
+
function r(e) {
|
|
41
|
+
let t = e.replace(/_/g, " ").toLowerCase();
|
|
42
|
+
return t.charAt(0).toUpperCase() + t.slice(1);
|
|
43
|
+
}
|
|
44
|
+
function i({ status: i, tone: a, label: o, className: s }) {
|
|
45
|
+
return /* @__PURE__ */ e("span", {
|
|
46
|
+
className: `inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium ${t[a ?? n[i] ?? "neutral"]} ${s ?? ""}`,
|
|
47
|
+
children: o ?? r(i)
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { i as default, r as formatEnumLabel };
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=StatusBadge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StatusBadge.js","names":[],"sources":["../../../src/cosmicintersection/components/StatusBadge.tsx"],"sourcesContent":["/**\n * StatusBadge — maps the Cos* enum values to the semantic status token families.\n *\n * One badge component covers every CosmicIntersection enum (dataset/source/\n * pipeline/run/step/notebook/analysis/release/grant status, plus access level).\n * Colour comes only from the `status-{success,info,warning,error,neutral}`\n * token families — never a raw palette colour — so it renders correctly in every\n * theme.\n */\n\nexport type StatusTone = 'success' | 'info' | 'warning' | 'error' | 'neutral';\n\nconst TONE_CLASS: Record<StatusTone, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text border-status-success-border',\n info: 'bg-status-info-bg-subtle text-status-info-text border-status-info-border',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border',\n error: 'bg-status-error-bg-subtle text-status-error-text border-status-error-border',\n neutral: 'bg-status-neutral-bg-subtle text-status-neutral-text border-status-neutral-border',\n};\n\n// Every Cos* enum value → tone. Values are unique enough across enums that a\n// single map is unambiguous; unknown values fall back to neutral.\nconst STATUS_TONE: Record<string, StatusTone> = {\n // healthy / terminal-good\n AVAILABLE: 'success',\n CONNECTED: 'success',\n SUCCEEDED: 'success',\n COMPLETED: 'success',\n PUBLISHED: 'success',\n ACTIVE: 'success',\n PUBLIC: 'success',\n // in-flight / attention\n INGESTING: 'warning',\n SYNCING: 'warning',\n RUNNING: 'warning',\n DEGRADED: 'warning',\n PAUSED: 'warning',\n DEPRECATED: 'warning',\n EXPIRED: 'warning',\n EMBARGOED: 'warning',\n RESTRICTED: 'warning',\n UNDER_REVIEW: 'warning',\n // queued / neutral-info\n DRAFT: 'info',\n CONFIGURING: 'info',\n QUEUED: 'info',\n PENDING: 'info',\n WORKSPACE: 'info',\n // failed / removed\n ERROR: 'error',\n FAILED: 'error',\n CANCELLED: 'error',\n RETRACTED: 'error',\n REVOKED: 'error',\n DISABLED: 'error',\n // archived / inert\n ARCHIVED: 'neutral',\n};\n\n/** Turn an ENUM_VALUE into a human label, e.g. GROUND_SENSOR -> \"Ground sensor\". */\nexport function formatEnumLabel(value: string): string {\n const lower = value.replace(/_/g, ' ').toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n}\n\nexport interface StatusBadgeProps {\n status: string;\n /** Override the derived tone (e.g. to force a colour for a domain-specific case). */\n tone?: StatusTone;\n /** Override the rendered label; defaults to a title-cased enum value. */\n label?: string;\n className?: string;\n}\n\nexport function StatusBadge({ status, tone, label, className }: StatusBadgeProps) {\n const resolvedTone: StatusTone = tone ?? STATUS_TONE[status] ?? 'neutral';\n return (\n <span\n className={`inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium ${TONE_CLASS[resolvedTone]} ${className ?? ''}`}\n >\n {label ?? formatEnumLabel(status)}\n </span>\n );\n}\n\nexport default StatusBadge;\n"],"mappings":";;AAYA,IAAM,IAAyC;CAC7C,SAAS;CACT,MAAM;CACN,SAAS;CACT,OAAO;CACP,SAAS;CACV,EAIK,IAA0C;CAE9C,WAAW;CACX,WAAW;CACX,WAAW;CACX,WAAW;CACX,WAAW;CACX,QAAQ;CACR,QAAQ;CAER,WAAW;CACX,SAAS;CACT,SAAS;CACT,UAAU;CACV,QAAQ;CACR,YAAY;CACZ,SAAS;CACT,WAAW;CACX,YAAY;CACZ,cAAc;CAEd,OAAO;CACP,aAAa;CACb,QAAQ;CACR,SAAS;CACT,WAAW;CAEX,OAAO;CACP,QAAQ;CACR,WAAW;CACX,WAAW;CACX,SAAS;CACT,UAAU;CAEV,UAAU;CACX;AAGD,SAAgB,EAAgB,GAAuB;CACrD,IAAM,IAAQ,EAAM,QAAQ,MAAM,IAAI,CAAC,aAAa;AACpD,QAAO,EAAM,OAAO,EAAE,CAAC,aAAa,GAAG,EAAM,MAAM,EAAE;;AAYvD,SAAgB,EAAY,EAAE,WAAQ,SAAM,UAAO,gBAA+B;AAEhF,QACE,kBAAC,QAAD;EACE,WAAW,sFAAsF,EAHpE,KAAQ,EAAY,MAAW,WAG8D,GAAG,KAAa;YAEzI,KAAS,EAAgB,EAAO;EAC5B,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import "./ActionMenu.js";
|
|
2
|
+
import "./EmptyState.js";
|
|
3
|
+
import "./EntityFormShell.js";
|
|
4
|
+
import "./EntityTagsField.js";
|
|
5
|
+
import "./ErrorState.js";
|
|
6
|
+
import "./FileUploadField.js";
|
|
7
|
+
import "./LoadingState.js";
|
|
8
|
+
import "./PageLayout.js";
|
|
9
|
+
import "./Pagination.js";
|
|
10
|
+
import "./SafeLink.js";
|
|
11
|
+
import "./StatusBadge.js";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createContext as e, useCallback as t, useContext as n, useMemo as r } from "react";
|
|
2
|
+
import { jsx as i } from "react/jsx-runtime";
|
|
3
|
+
//#region src/cosmicintersection/context/CosmicIntersectionContext.tsx
|
|
4
|
+
var a = e(null);
|
|
5
|
+
function o({ children: e, props: n }) {
|
|
6
|
+
let { basePath: o = "", navigate: s, currentUser: c, workspaceId: l, tenantId: u, apiGatewayUrl: d, authToken: f } = n, p = o.endsWith("/") ? o.slice(0, -1) : o, m = t((e) => {
|
|
7
|
+
if (!p || p === "/") {
|
|
8
|
+
s(e);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if (e.startsWith(p)) {
|
|
12
|
+
s(e);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
s(e.startsWith("/") ? `${p}${e}` : `${p}/${e}`);
|
|
16
|
+
}, [p, s]), h = r(() => ({
|
|
17
|
+
basePath: p,
|
|
18
|
+
navigate: m,
|
|
19
|
+
currentUser: c,
|
|
20
|
+
workspaceId: l,
|
|
21
|
+
tenantId: u,
|
|
22
|
+
apiGatewayUrl: d || "",
|
|
23
|
+
authToken: f || ""
|
|
24
|
+
}), [
|
|
25
|
+
p,
|
|
26
|
+
m,
|
|
27
|
+
c,
|
|
28
|
+
l,
|
|
29
|
+
u,
|
|
30
|
+
d,
|
|
31
|
+
f
|
|
32
|
+
]);
|
|
33
|
+
return /* @__PURE__ */ i(a.Provider, {
|
|
34
|
+
value: h,
|
|
35
|
+
children: e
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function s() {
|
|
39
|
+
let e = n(a);
|
|
40
|
+
if (!e) throw Error("useCosmicIntersection must be used within a CosmicIntersectionProvider");
|
|
41
|
+
return e;
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { o as CosmicIntersectionProvider, s as useCosmicIntersection };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=CosmicIntersectionContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CosmicIntersectionContext.js","names":[],"sources":["../../../src/cosmicintersection/context/CosmicIntersectionContext.tsx"],"sourcesContent":["/**\n * CosmicIntersectionContext\n *\n * Provides basePath-aware navigation and shared props throughout the\n * CosmicIntersection microfrontend. Uses the `navigate` function injected by the\n * host app shell — no react-router hooks are needed here, so the same tree works\n * whether the shell drives routing or the local dev harness does.\n */\n\nimport { createContext, useCallback, useContext, useMemo, type ReactNode } from 'react';\nimport type { CosmicIntersectionRootProps } from '../types';\n\ninterface CosmicIntersectionContextValue {\n basePath: string;\n navigate: (path: string) => void;\n currentUser: CosmicIntersectionRootProps['currentUser'];\n workspaceId?: string;\n tenantId?: string;\n apiGatewayUrl: string;\n authToken: string;\n}\n\nconst CosmicIntersectionContext = createContext<CosmicIntersectionContextValue | null>(null);\n\ninterface CosmicIntersectionProviderProps {\n children: ReactNode;\n props: CosmicIntersectionRootProps;\n}\n\nexport function CosmicIntersectionProvider({ children, props }: CosmicIntersectionProviderProps) {\n const {\n basePath = '',\n navigate: hostNavigate,\n currentUser,\n workspaceId,\n tenantId,\n apiGatewayUrl,\n authToken,\n } = props;\n\n // Normalize basePath (strip trailing slash) so joins never produce `//`.\n const normalizedBasePath = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n\n // basePath-aware navigation built on the host's navigate function.\n const navigate = useCallback(\n (path: string) => {\n if (!normalizedBasePath || normalizedBasePath === '/') {\n hostNavigate(path);\n return;\n }\n if (path.startsWith(normalizedBasePath)) {\n hostNavigate(path);\n return;\n }\n const fullPath = path.startsWith('/')\n ? `${normalizedBasePath}${path}`\n : `${normalizedBasePath}/${path}`;\n hostNavigate(fullPath);\n },\n [normalizedBasePath, hostNavigate]\n );\n\n const value = useMemo(\n () => ({\n basePath: normalizedBasePath,\n navigate,\n currentUser,\n workspaceId,\n tenantId,\n apiGatewayUrl: apiGatewayUrl || '',\n authToken: authToken || '',\n }),\n [normalizedBasePath, navigate, currentUser, workspaceId, tenantId, apiGatewayUrl, authToken]\n );\n\n return (\n <CosmicIntersectionContext.Provider value={value}>\n {children}\n </CosmicIntersectionContext.Provider>\n );\n}\n\nexport function useCosmicIntersection() {\n const context = useContext(CosmicIntersectionContext);\n if (!context) {\n throw new Error('useCosmicIntersection must be used within a CosmicIntersectionProvider');\n }\n return context;\n}\n\n/**\n * Hook for basePath-aware navigation. Returns a navigate function that\n * automatically prepends the microfrontend basePath. Falls back to a\n * `window.location`-based navigate if used outside the provider.\n */\nexport function useCosmicIntersectionNavigate() {\n const context = useContext(CosmicIntersectionContext);\n\n const fallbackNavigate = useCallback((path: string) => {\n const currentPath = window.location.pathname;\n const mfeMatch = currentPath.match(/^(\\/cosmicintersection)/);\n const basePath = mfeMatch ? mfeMatch[1] : '';\n\n let fullPath = path;\n if (basePath && !path.startsWith(basePath)) {\n fullPath = path.startsWith('/') ? `${basePath}${path}` : `${basePath}/${path}`;\n }\n window.location.href = fullPath;\n }, []);\n\n return context?.navigate ?? fallbackNavigate;\n}\n"],"mappings":";;;AAsBA,IAAM,IAA4B,EAAqD,KAAK;AAO5F,SAAgB,EAA2B,EAAE,aAAU,YAA0C;CAC/F,IAAM,EACJ,cAAW,IACX,UAAU,GACV,gBACA,gBACA,aACA,kBACA,iBACE,GAGE,IAAqB,EAAS,SAAS,IAAI,GAAG,EAAS,MAAM,GAAG,GAAG,GAAG,GAGtE,IAAW,GACd,MAAiB;AAChB,MAAI,CAAC,KAAsB,MAAuB,KAAK;AACrD,KAAa,EAAK;AAClB;;AAEF,MAAI,EAAK,WAAW,EAAmB,EAAE;AACvC,KAAa,EAAK;AAClB;;AAKF,IAHiB,EAAK,WAAW,IAAI,GACjC,GAAG,IAAqB,MACxB,GAAG,EAAmB,GAAG,IACP;IAExB,CAAC,GAAoB,EAAa,CACnC,EAEK,IAAQ,SACL;EACL,UAAU;EACV;EACA;EACA;EACA;EACA,eAAe,KAAiB;EAChC,WAAW,KAAa;EACzB,GACD;EAAC;EAAoB;EAAU;EAAa;EAAa;EAAU;EAAe;EAAU,CAC7F;AAED,QACE,kBAAC,EAA0B,UAA3B;EAA2C;EACxC;EACkC,CAAA;;AAIzC,SAAgB,IAAwB;CACtC,IAAM,IAAU,EAAW,EAA0B;AACrD,KAAI,CAAC,EACH,OAAU,MAAM,yEAAyE;AAE3F,QAAO"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { notify as e } from "./toast.js";
|
|
2
|
+
//#region src/cosmicintersection/lib/mutationResult.ts
|
|
3
|
+
function t(t, n, r) {
|
|
4
|
+
return t?.error ? (e.error(r, t.error.message), !1) : n == null || n === "" || n === !1 ? (e.error(r, "The server did not confirm the change. Please try again."), !1) : !0;
|
|
5
|
+
}
|
|
6
|
+
function n(e) {
|
|
7
|
+
if (e instanceof Error && e.message.length > 0) return e.message;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { n as describeError, t as mutationSucceeded };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=mutationResult.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutationResult.js","names":[],"sources":["../../../src/cosmicintersection/lib/mutationResult.ts"],"sourcesContent":["import type { ErrorLike } from '@apollo/client';\n\nimport { notify } from './toast';\n\n/**\n * The shared gateway Apollo client runs mutations with `errorPolicy: 'all'`, so a\n * server-side failure is returned IN-BAND on `result.error` rather than thrown —\n * awaiting the mutation resolves successfully even when nothing was persisted.\n * Write forms must therefore inspect the resolved result before treating a call\n * as a success, or they silently report a fake success and drop the user's data.\n *\n * `mutationSucceeded` centralises that check: it returns `true` only when there is\n * no in-band error AND the expected success payload id is present. Otherwise it\n * surfaces the real reason via the platform toast and returns `false` so the\n * caller keeps the form inputs intact and does not navigate away.\n */\nexport function mutationSucceeded(\n result: { error?: ErrorLike } | null | undefined,\n successId: string | null | undefined | boolean,\n failureMessage: string\n): boolean {\n if (result?.error) {\n notify.error(failureMessage, result.error.message);\n return false;\n }\n // Boolean mutations (archive/delete) confirm with `true`; entity mutations\n // confirm with a returned id. Treat a missing/false confirmation as failure.\n if (successId === undefined || successId === null || successId === '' || successId === false) {\n notify.error(failureMessage, 'The server did not confirm the change. Please try again.');\n return false;\n }\n return true;\n}\n\n/**\n * Normalises a thrown error (network failure, or a client configured with\n * `errorPolicy: 'none'`) into a human-readable description for the toast.\n */\nexport function describeError(error: unknown): string | undefined {\n if (error instanceof Error && error.message.length > 0) {\n return error.message;\n }\n return undefined;\n}\n"],"mappings":";;AAgBA,SAAgB,EACd,GACA,GACA,GACS;AAWT,QAVI,GAAQ,SACV,EAAO,MAAM,GAAgB,EAAO,MAAM,QAAQ,EAC3C,MAIL,KAAyC,QAAQ,MAAc,MAAM,MAAc,MACrF,EAAO,MAAM,GAAgB,2DAA2D,EACjF,MAEF;;AAOT,SAAgB,EAAc,GAAoC;AAChE,KAAI,aAAiB,SAAS,EAAM,QAAQ,SAAS,EACnD,QAAO,EAAM"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { toast as e } from "sonner";
|
|
2
|
+
//#region src/cosmicintersection/lib/toast.ts
|
|
3
|
+
var t = {
|
|
4
|
+
success(t, n) {
|
|
5
|
+
e.success(t, {
|
|
6
|
+
description: n,
|
|
7
|
+
duration: 4e3
|
|
8
|
+
});
|
|
9
|
+
},
|
|
10
|
+
info(t, n) {
|
|
11
|
+
e.info(t, {
|
|
12
|
+
description: n,
|
|
13
|
+
duration: 4e3
|
|
14
|
+
});
|
|
15
|
+
},
|
|
16
|
+
warning(t, n) {
|
|
17
|
+
e.warning(t, {
|
|
18
|
+
description: n,
|
|
19
|
+
duration: 8e3
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
error(t, n) {
|
|
23
|
+
e.error(t, {
|
|
24
|
+
description: n,
|
|
25
|
+
duration: Infinity,
|
|
26
|
+
closeButton: !0
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
dismiss(t) {
|
|
30
|
+
e.dismiss(t);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
export { t as notify };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=toast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toast.js","names":[],"sources":["../../../src/cosmicintersection/lib/toast.ts"],"sourcesContent":["import { toast } from 'sonner';\n\n/**\n * Module-wide toast API. sonner is a global singleton; the matching <Toaster>\n * is owned by the HOST app shell (mounted once via AppShellProvider). Every\n * page/component surfaces feedback through these helpers instead of hand-rolling\n * fixed-position toast markup, so success/error notifications stay consistent\n * with the rest of the platform.\n *\n * Durations follow the notification spec: success/info auto-dismiss, errors stay\n * until the user dismisses them (sonner renders a close button and announces via\n * aria-live).\n */\nexport const notify = {\n success(message: string, description?: string): void {\n toast.success(message, { description, duration: 4000 });\n },\n info(message: string, description?: string): void {\n toast.info(message, { description, duration: 4000 });\n },\n warning(message: string, description?: string): void {\n toast.warning(message, { description, duration: 8000 });\n },\n error(message: string, description?: string): void {\n toast.error(message, { description, duration: Infinity, closeButton: true });\n },\n dismiss(id?: string | number): void {\n toast.dismiss(id);\n },\n};\n\nexport { toast };\n"],"mappings":";;AAaA,IAAa,IAAS;CACpB,QAAQ,GAAiB,GAA4B;AACnD,IAAM,QAAQ,GAAS;GAAE;GAAa,UAAU;GAAM,CAAC;;CAEzD,KAAK,GAAiB,GAA4B;AAChD,IAAM,KAAK,GAAS;GAAE;GAAa,UAAU;GAAM,CAAC;;CAEtD,QAAQ,GAAiB,GAA4B;AACnD,IAAM,QAAQ,GAAS;GAAE;GAAa,UAAU;GAAM,CAAC;;CAEzD,MAAM,GAAiB,GAA4B;AACjD,IAAM,MAAM,GAAS;GAAE;GAAa,UAAU;GAAU,aAAa;GAAM,CAAC;;CAE9E,QAAQ,GAA4B;AAClC,IAAM,QAAQ,EAAG;;CAEpB"}
|