@burdenoff/microfe-cosmicintersection 2026.704.2 → 2026.704.3
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/CosmicIntersectionRoutes.js +39 -27
- package/dist/cosmicintersection/CosmicIntersectionRoutes.js.map +1 -1
- package/dist/cosmicintersection/api/cosmicApi.js +6 -0
- package/dist/cosmicintersection/api/cosmicApi.js.map +1 -1
- package/dist/cosmicintersection/components/FacetPanel.js +66 -0
- package/dist/cosmicintersection/components/FacetPanel.js.map +1 -0
- package/dist/cosmicintersection/components/LineageGraph.js +388 -0
- package/dist/cosmicintersection/components/LineageGraph.js.map +1 -0
- package/dist/cosmicintersection/components/ResultsChart.js +329 -0
- package/dist/cosmicintersection/components/ResultsChart.js.map +1 -0
- package/dist/cosmicintersection/components/StepDAG.js +104 -0
- package/dist/cosmicintersection/components/StepDAG.js.map +1 -0
- package/dist/cosmicintersection/pages/access/AccessPage.js +207 -155
- package/dist/cosmicintersection/pages/access/AccessPage.js.map +1 -1
- package/dist/cosmicintersection/pages/analyses/AnalysesPage.js +325 -165
- package/dist/cosmicintersection/pages/analyses/AnalysesPage.js.map +1 -1
- package/dist/cosmicintersection/pages/collections/CollectionDetailPage.js +138 -93
- package/dist/cosmicintersection/pages/collections/CollectionDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/collections/CollectionFormPage.js.map +1 -1
- package/dist/cosmicintersection/pages/dashboard/DashboardPage.js +26 -15
- package/dist/cosmicintersection/pages/dashboard/DashboardPage.js.map +1 -1
- package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js +345 -219
- package/dist/cosmicintersection/pages/datasets/DatasetDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/datasets/DatasetsPage.js +321 -163
- package/dist/cosmicintersection/pages/datasets/DatasetsPage.js.map +1 -1
- package/dist/cosmicintersection/pages/discovery/DiscoveryPage.js +358 -0
- package/dist/cosmicintersection/pages/discovery/DiscoveryPage.js.map +1 -0
- package/dist/cosmicintersection/pages/lineage/LineagePage.js +171 -0
- package/dist/cosmicintersection/pages/lineage/LineagePage.js.map +1 -0
- package/dist/cosmicintersection/pages/notebooks/NotebookDetailPage.js +232 -162
- package/dist/cosmicintersection/pages/notebooks/NotebookDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js +143 -82
- package/dist/cosmicintersection/pages/notebooks/NotebooksPage.js.map +1 -1
- package/dist/cosmicintersection/pages/pipelines/PipelineDetailPage.js +286 -142
- package/dist/cosmicintersection/pages/pipelines/PipelineDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/pipelines/PipelineFormPage.js +305 -103
- package/dist/cosmicintersection/pages/pipelines/PipelineFormPage.js.map +1 -1
- package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js +183 -79
- package/dist/cosmicintersection/pages/pipelines/PipelinesPage.js.map +1 -1
- package/dist/cosmicintersection/pages/releases/ReleasesPage.js +262 -170
- package/dist/cosmicintersection/pages/releases/ReleasesPage.js.map +1 -1
- package/dist/cosmicintersection/pages/runs/RunDetailPage.js +179 -136
- package/dist/cosmicintersection/pages/runs/RunDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/runs/RunsPage.js +124 -47
- package/dist/cosmicintersection/pages/runs/RunsPage.js.map +1 -1
- package/dist/cosmicintersection/pages/sources/SourceDetailPage.js +114 -85
- package/dist/cosmicintersection/pages/sources/SourceDetailPage.js.map +1 -1
- package/dist/cosmicintersection/pages/sources/SourcesPage.js +263 -152
- package/dist/cosmicintersection/pages/sources/SourcesPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatasetsPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/datasets/DatasetsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Database, Plus } from 'lucide-react';\nimport {\n Badge,\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosArchiveDatasetMutation,\n useCosCollectionsQuery,\n useCosDatasetsQuery,\n useCosPublishDatasetMutation,\n useCosSourcesQuery,\n type CosDataset,\n type CosDatasetStatus,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\nconst DATASET_STATUSES: CosDatasetStatus[] = [\n 'DRAFT',\n 'INGESTING',\n 'AVAILABLE',\n 'DEPRECATED',\n 'ARCHIVED',\n];\n\nconst ALL = '__all__';\n\n/** BigInt byte string → human-readable size, or an em dash when absent/zero. */\nfunction formatBytes(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const bytes = Number(raw);\n if (!Number.isFinite(bytes) || bytes <= 0) {\n return '—';\n }\n const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;\n}\n\nfunction formatDate(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const date = new Date(raw);\n return Number.isNaN(date.getTime()) ? '—' : date.toLocaleDateString();\n}\n\nexport default function DatasetsPage() {\n const { navigate } = useCosmicIntersection();\n\n const [status, setStatus] = useState<string>(ALL);\n const [sourceId, setSourceId] = useState<string>(ALL);\n const [collectionId, setCollectionId] = useState<string>(ALL);\n\n const pagination = usePagination({\n pageSize: 20,\n resetKey: `${status}|${sourceId}|${collectionId}`,\n });\n\n const { data, loading, error, refetch } = useCosDatasetsQuery({\n status: status === ALL ? undefined : (status as CosDatasetStatus),\n sourceId: sourceId === ALL ? undefined : sourceId,\n collectionId: collectionId === ALL ? undefined : collectionId,\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const sourcesQuery = useCosSourcesQuery({ pagination: { limit: 100, offset: 0 } });\n const collectionsQuery = useCosCollectionsQuery();\n\n const [publishDataset] = useCosPublishDatasetMutation();\n const [archiveDataset] = useCosArchiveDatasetMutation();\n\n const datasets = useMemo(() => data?.cosListDatasets?.items ?? [], [data]);\n const pageInfo = data?.cosListDatasets?.pageInfo;\n const sources = sourcesQuery.data?.cosListSources?.items ?? [];\n const collections = collectionsQuery.data?.cosListCollections ?? [];\n\n const noFilters = status === ALL && sourceId === ALL && collectionId === ALL;\n\n const handlePublish = async (dataset: CosDataset) => {\n const response = await publishDataset({ variables: { id: dataset.id } });\n if (response.data?.cosPublishDataset) {\n notify.success('Dataset published', `“${dataset.name}” is now available.`);\n await refetch();\n }\n };\n\n const handleArchive = async (dataset: CosDataset) => {\n const response = await archiveDataset({ variables: { id: dataset.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveDataset) {\n notify.error('Could not archive dataset', 'The server did not confirm the change.');\n throw new Error('Archive not confirmed');\n }\n notify.success('Dataset archived');\n await refetch();\n };\n\n return (\n <PageLayout\n title=\"Datasets\"\n description=\"Federated science datasets — catalog, schema, lineage and access, all from live data.\"\n actions={\n <Button type=\"button\" onClick={() => navigate('datasets/create')}>\n <Plus className=\"size-4\" />\n New dataset\n </Button>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-end gap-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Status</span>\n <Select value={status} onValueChange={setStatus}>\n <SelectTrigger aria-label=\"Filter by status\" className=\"w-44\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {DATASET_STATUSES.map((value) => (\n <SelectItem key={value} value={value}>\n {formatEnumLabel(value)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Source</span>\n <Select value={sourceId} onValueChange={setSourceId}>\n <SelectTrigger aria-label=\"Filter by source\" className=\"w-48\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All sources</SelectItem>\n {sources.map((source) => (\n <SelectItem key={source.id} value={source.id}>\n {source.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Collection</span>\n <Select value={collectionId} onValueChange={setCollectionId}>\n <SelectTrigger aria-label=\"Filter by collection\" className=\"w-48\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All collections</SelectItem>\n {collections.map((collection) => (\n <SelectItem key={collection.id} value={collection.id}>\n {collection.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {loading ? (\n <LoadingState variant=\"rows\" rows={6} label=\"Loading datasets…\" />\n ) : error ? (\n <ErrorState\n message=\"We could not load datasets. Please try again.\"\n onRetry={() => void refetch()}\n />\n ) : datasets.length === 0 ? (\n <EmptyState\n icon={Database}\n title={noFilters ? 'No datasets yet.' : 'No datasets match these filters.'}\n description={\n noFilters\n ? 'Register your first dataset to start federating science data across sources.'\n : 'Try a different status, source or collection.'\n }\n action={\n noFilters ? (\n <Button type=\"button\" onClick={() => navigate('datasets/create')}>\n <Plus className=\"size-4\" />\n New dataset\n </Button>\n ) : undefined\n }\n />\n ) : (\n <>\n <div className=\"rounded-card border border-border-default bg-bg-elevated\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Access</TableHead>\n <TableHead>Format</TableHead>\n <TableHead className=\"text-right\">Size</TableHead>\n <TableHead>Tags</TableHead>\n <TableHead>Updated</TableHead>\n <TableHead className=\"text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {datasets.map((dataset) => (\n <TableRow\n key={dataset.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`datasets/${dataset.id}`)}\n >\n <TableCell>\n <span className=\"font-medium text-text-primary\">{dataset.name}</span>\n {dataset.description ? (\n <p className=\"mt-0.5 line-clamp-1 max-w-xs text-xs text-text-secondary\">\n {dataset.description}\n </p>\n ) : null}\n </TableCell>\n <TableCell>\n <StatusBadge status={dataset.status} />\n </TableCell>\n <TableCell>\n <span className=\"text-sm text-text-secondary\">\n {formatEnumLabel(dataset.accessLevel)}\n </span>\n </TableCell>\n <TableCell>\n <span className=\"text-sm text-text-secondary\">{dataset.format || '—'}</span>\n </TableCell>\n <TableCell className=\"text-right text-sm tabular-nums text-text-secondary\">\n {formatBytes(dataset.sizeBytes)}\n </TableCell>\n <TableCell>\n <div className=\"flex max-w-[12rem] flex-wrap gap-1\">\n {dataset.tags.length === 0 ? (\n <span className=\"text-xs text-text-secondary\">—</span>\n ) : (\n <>\n {dataset.tags.slice(0, 2).map((tag) => (\n <Badge key={tag} variant=\"secondary\">\n {tag}\n </Badge>\n ))}\n {dataset.tags.length > 2 ? (\n <Badge variant=\"secondary\">+{dataset.tags.length - 2}</Badge>\n ) : null}\n </>\n )}\n </div>\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(dataset.updatedAt)}\n </TableCell>\n <TableCell className=\"text-right\">\n <ActionMenu\n ariaLabel={`Actions for ${dataset.name}`}\n entityName={dataset.name}\n onView={() => navigate(`datasets/${dataset.id}`)}\n onEdit={() => navigate(`datasets/${dataset.id}/edit`)}\n extraItems={\n dataset.status === 'AVAILABLE' || dataset.status === 'ARCHIVED'\n ? []\n : [\n {\n key: 'publish',\n label: 'Publish',\n onSelect: () => void handlePublish(dataset),\n },\n ]\n }\n onDelete={() => handleArchive(dataset)}\n deleteLabel=\"Archive dataset\"\n />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={datasets.length}\n hasMore={Boolean(pageInfo?.hasMore)}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"datasets\"\n loading={loading}\n />\n </>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DatasetsPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/datasets/DatasetsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Compass, Database, Plus } from 'lucide-react';\nimport {\n Badge,\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosArchiveDatasetMutation,\n useCosCollectionsQuery,\n useCosDatasetsQuery,\n useCosPublishDatasetMutation,\n useCosSourcesQuery,\n type CosAccessLevel,\n type CosDataset,\n type CosDatasetStatus,\n} from '../../api/cosmicApi';\nimport {\n ActionMenu,\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n Pagination,\n StatusBadge,\n formatEnumLabel,\n usePagination,\n} from '../../components';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { notify } from '../../lib/toast';\n\nconst DATASET_STATUSES: CosDatasetStatus[] = [\n 'DRAFT',\n 'INGESTING',\n 'AVAILABLE',\n 'DEPRECATED',\n 'ARCHIVED',\n];\n\nconst ACCESS_LEVELS: CosAccessLevel[] = ['PUBLIC', 'WORKSPACE', 'RESTRICTED', 'EMBARGOED'];\n\nconst ALL = '__all__';\n\n/** Generous ceiling for deriving the distinct-format dropdown options. */\nconst FORMAT_POOL_LIMIT = 200;\n\n/** BigInt byte string → human-readable size, or an em dash when absent/zero. */\nfunction formatBytes(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const bytes = Number(raw);\n if (!Number.isFinite(bytes) || bytes <= 0) {\n return '—';\n }\n const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;\n}\n\nfunction formatDate(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const date = new Date(raw);\n return Number.isNaN(date.getTime()) ? '—' : date.toLocaleDateString();\n}\n\nexport default function DatasetsPage() {\n const { navigate } = useCosmicIntersection();\n\n const [status, setStatus] = useState<string>(ALL);\n const [sourceId, setSourceId] = useState<string>(ALL);\n const [collectionId, setCollectionId] = useState<string>(ALL);\n const [format, setFormat] = useState<string>(ALL);\n const [accessLevel, setAccessLevel] = useState<string>(ALL);\n\n const pagination = usePagination({\n pageSize: 20,\n resetKey: `${status}|${sourceId}|${collectionId}|${format}|${accessLevel}`,\n });\n\n const { data, loading, error, refetch } = useCosDatasetsQuery({\n status: status === ALL ? undefined : (status as CosDatasetStatus),\n sourceId: sourceId === ALL ? undefined : sourceId,\n collectionId: collectionId === ALL ? undefined : collectionId,\n format: format === ALL ? undefined : format,\n accessLevel: accessLevel === ALL ? undefined : (accessLevel as CosAccessLevel),\n pagination: { limit: pagination.limit, offset: pagination.offset },\n });\n\n const sourcesQuery = useCosSourcesQuery({ pagination: { limit: 100, offset: 0 } });\n const collectionsQuery = useCosCollectionsQuery();\n // Distinct format values are derived from a real sample of the catalog so the\n // Format dropdown reflects what actually exists (format is free-text, no enum).\n const formatsPool = useCosDatasetsQuery({\n pagination: { limit: FORMAT_POOL_LIMIT, offset: 0 },\n });\n\n const [publishDataset] = useCosPublishDatasetMutation();\n const [archiveDataset] = useCosArchiveDatasetMutation();\n\n const datasets = useMemo(() => data?.cosListDatasets?.items ?? [], [data]);\n const pageInfo = data?.cosListDatasets?.pageInfo;\n const sources = sourcesQuery.data?.cosListSources?.items ?? [];\n const collections = collectionsQuery.data?.cosListCollections ?? [];\n const formats = useMemo(() => {\n const seen = new Set<string>();\n for (const dataset of formatsPool.data?.cosListDatasets?.items ?? []) {\n if (dataset.format) {\n seen.add(dataset.format);\n }\n }\n return [...seen].sort((a, b) => a.localeCompare(b));\n }, [formatsPool.data]);\n\n const noFilters =\n status === ALL &&\n sourceId === ALL &&\n collectionId === ALL &&\n format === ALL &&\n accessLevel === ALL;\n\n const handlePublish = async (dataset: CosDataset) => {\n const response = await publishDataset({ variables: { id: dataset.id } });\n if (response.data?.cosPublishDataset) {\n notify.success('Dataset published', `“${dataset.name}” is now available.`);\n await refetch();\n }\n };\n\n const handleArchive = async (dataset: CosDataset) => {\n const response = await archiveDataset({ variables: { id: dataset.id } });\n if (response.error) {\n throw new Error(response.error.message);\n }\n if (!response.data?.cosArchiveDataset) {\n notify.error('Could not archive dataset', 'The server did not confirm the change.');\n throw new Error('Archive not confirmed');\n }\n notify.success('Dataset archived');\n await refetch();\n };\n\n return (\n <PageLayout\n title=\"Datasets\"\n description=\"Federated science datasets — catalog, schema, lineage and access, all from live data.\"\n actions={\n <>\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('discovery')}>\n <Compass className=\"size-4\" />\n Discover\n </Button>\n <Button type=\"button\" onClick={() => navigate('datasets/create')}>\n <Plus className=\"size-4\" />\n New dataset\n </Button>\n </>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-end gap-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Status</span>\n <Select value={status} onValueChange={setStatus}>\n <SelectTrigger aria-label=\"Filter by status\" className=\"w-44\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {DATASET_STATUSES.map((value) => (\n <SelectItem key={value} value={value}>\n {formatEnumLabel(value)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Source</span>\n <Select value={sourceId} onValueChange={setSourceId}>\n <SelectTrigger aria-label=\"Filter by source\" className=\"w-48\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All sources</SelectItem>\n {sources.map((source) => (\n <SelectItem key={source.id} value={source.id}>\n {source.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Collection</span>\n <Select value={collectionId} onValueChange={setCollectionId}>\n <SelectTrigger aria-label=\"Filter by collection\" className=\"w-48\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All collections</SelectItem>\n {collections.map((collection) => (\n <SelectItem key={collection.id} value={collection.id}>\n {collection.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Format</span>\n <Select value={format} onValueChange={setFormat} disabled={formats.length === 0}>\n <SelectTrigger aria-label=\"Filter by format\" className=\"w-40\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All formats</SelectItem>\n {formats.map((value) => (\n <SelectItem key={value} value={value}>\n {value}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"block font-medium text-text-primary\">Access</span>\n <Select value={accessLevel} onValueChange={setAccessLevel}>\n <SelectTrigger aria-label=\"Filter by access level\" className=\"w-44\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All access levels</SelectItem>\n {ACCESS_LEVELS.map((value) => (\n <SelectItem key={value} value={value}>\n {formatEnumLabel(value)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {loading ? (\n <LoadingState variant=\"rows\" rows={6} label=\"Loading datasets…\" />\n ) : error ? (\n <ErrorState\n message=\"We could not load datasets. Please try again.\"\n onRetry={() => void refetch()}\n />\n ) : datasets.length === 0 ? (\n <EmptyState\n icon={Database}\n title={noFilters ? 'No datasets yet.' : 'No datasets match these filters.'}\n description={\n noFilters\n ? 'Register your first dataset to start federating science data across sources.'\n : 'Try a different status, source, collection, format or access level.'\n }\n action={\n noFilters ? (\n <Button type=\"button\" onClick={() => navigate('datasets/create')}>\n <Plus className=\"size-4\" />\n New dataset\n </Button>\n ) : undefined\n }\n />\n ) : (\n <>\n {/* Desktop / tablet: table */}\n <div className=\"hidden rounded-card border border-border-default bg-bg-elevated md:block\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Access</TableHead>\n <TableHead>Format</TableHead>\n <TableHead className=\"text-right\">Size</TableHead>\n <TableHead>Tags</TableHead>\n <TableHead>Updated</TableHead>\n <TableHead className=\"text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {datasets.map((dataset) => (\n <TableRow\n key={dataset.id}\n className=\"cursor-pointer\"\n onClick={() => navigate(`datasets/${dataset.id}`)}\n >\n <TableCell>\n <span className=\"font-medium text-text-primary\">{dataset.name}</span>\n {dataset.description ? (\n <p className=\"mt-0.5 line-clamp-1 max-w-xs text-xs text-text-secondary\">\n {dataset.description}\n </p>\n ) : null}\n </TableCell>\n <TableCell>\n <StatusBadge status={dataset.status} />\n </TableCell>\n <TableCell>\n <span className=\"text-sm text-text-secondary\">\n {formatEnumLabel(dataset.accessLevel)}\n </span>\n </TableCell>\n <TableCell>\n <span className=\"text-sm text-text-secondary\">{dataset.format || '—'}</span>\n </TableCell>\n <TableCell className=\"text-right text-sm tabular-nums text-text-secondary\">\n {formatBytes(dataset.sizeBytes)}\n </TableCell>\n <TableCell>\n <div className=\"flex max-w-[12rem] flex-wrap gap-1\">\n {dataset.tags.length === 0 ? (\n <span className=\"text-xs text-text-secondary\">—</span>\n ) : (\n <>\n {dataset.tags.slice(0, 2).map((tag) => (\n <Badge key={tag} variant=\"secondary\">\n {tag}\n </Badge>\n ))}\n {dataset.tags.length > 2 ? (\n <Badge variant=\"secondary\">+{dataset.tags.length - 2}</Badge>\n ) : null}\n </>\n )}\n </div>\n </TableCell>\n <TableCell className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDate(dataset.updatedAt)}\n </TableCell>\n <TableCell className=\"text-right\">\n <ActionMenu\n ariaLabel={`Actions for ${dataset.name}`}\n entityName={dataset.name}\n onView={() => navigate(`datasets/${dataset.id}`)}\n onEdit={() => navigate(`datasets/${dataset.id}/edit`)}\n extraItems={\n dataset.status === 'AVAILABLE' || dataset.status === 'ARCHIVED'\n ? []\n : [\n {\n key: 'publish',\n label: 'Publish',\n onSelect: () => void handlePublish(dataset),\n },\n ]\n }\n onDelete={() => handleArchive(dataset)}\n deleteLabel=\"Archive dataset\"\n />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n\n {/* Mobile: stacked cards */}\n <ul className=\"space-y-3 md:hidden\">\n {datasets.map((dataset) => (\n <li key={dataset.id}>\n <div\n role=\"button\"\n tabIndex={0}\n aria-label={`Open ${dataset.name}`}\n onClick={() => navigate(`datasets/${dataset.id}`)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n navigate(`datasets/${dataset.id}`);\n }\n }}\n className=\"flex flex-col gap-3 rounded-card border border-border-default bg-bg-elevated p-4 outline-none transition-colors hover:bg-bg-sunken focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary\">{dataset.name}</p>\n {dataset.description ? (\n <p className=\"mt-0.5 line-clamp-2 text-xs text-text-secondary\">\n {dataset.description}\n </p>\n ) : null}\n </div>\n <div onClick={(event) => event.stopPropagation()}>\n <ActionMenu\n ariaLabel={`Actions for ${dataset.name}`}\n entityName={dataset.name}\n onView={() => navigate(`datasets/${dataset.id}`)}\n onEdit={() => navigate(`datasets/${dataset.id}/edit`)}\n extraItems={\n dataset.status === 'AVAILABLE' || dataset.status === 'ARCHIVED'\n ? []\n : [\n {\n key: 'publish',\n label: 'Publish',\n onSelect: () => void handlePublish(dataset),\n },\n ]\n }\n onDelete={() => handleArchive(dataset)}\n deleteLabel=\"Archive dataset\"\n />\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={dataset.status} />\n <StatusBadge status={dataset.accessLevel} />\n </div>\n\n <dl className=\"grid grid-cols-2 gap-x-4 gap-y-1 text-xs\">\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Format</dt>\n <dd className=\"text-text-primary\">{dataset.format || '—'}</dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Size</dt>\n <dd className=\"tabular-nums text-text-primary\">\n {formatBytes(dataset.sizeBytes)}\n </dd>\n </div>\n <div className=\"flex justify-between gap-2\">\n <dt className=\"text-text-secondary\">Updated</dt>\n <dd className=\"text-text-primary\">{formatDate(dataset.updatedAt)}</dd>\n </div>\n </dl>\n\n {dataset.tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {dataset.tags.slice(0, 4).map((tag) => (\n <Badge key={tag} variant=\"secondary\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n {dataset.tags.length > 4 ? (\n <span className=\"text-xs text-text-secondary\">\n +{dataset.tags.length - 4}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n </li>\n ))}\n </ul>\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={datasets.length}\n hasMore={Boolean(pageInfo?.hasMore)}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"datasets\"\n loading={loading}\n />\n </>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { useCosmicIntersection as e } from "../../context/CosmicIntersectionContext.js";
|
|
2
|
+
import { useCosCollectionsQuery as t, useCosDatasetsQuery as n, useCosSourcesQuery as r } from "../../api/cosmicApi.js";
|
|
3
|
+
import i from "../../components/EmptyState.js";
|
|
4
|
+
import a from "../../components/ErrorState.js";
|
|
5
|
+
import ee from "../../components/LoadingState.js";
|
|
6
|
+
import { cn as te } from "../../utils/cn.js";
|
|
7
|
+
import o from "../../components/PageLayout.js";
|
|
8
|
+
import s, { formatEnumLabel as c } from "../../components/StatusBadge.js";
|
|
9
|
+
import "../../components/index.js";
|
|
10
|
+
import ne from "../../components/FacetPanel.js";
|
|
11
|
+
import { useEffect as re, useMemo as l, useState as u } from "react";
|
|
12
|
+
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
13
|
+
import { ArrowUpDown as p, Database as m, FileText as h, Rows3 as g, Search as _, SlidersHorizontal as ie, Table as ae, X as v } from "lucide-react";
|
|
14
|
+
import { Badge as y, Button as b, Input as x, Select as S, SelectContent as C, SelectItem as w, SelectTrigger as T, SelectValue as E } from "@burdenoff/fe-libs/ui";
|
|
15
|
+
//#region src/cosmicintersection/pages/discovery/DiscoveryPage.tsx
|
|
16
|
+
var D = 200, oe = 300, se = [
|
|
17
|
+
"PUBLIC",
|
|
18
|
+
"WORKSPACE",
|
|
19
|
+
"RESTRICTED",
|
|
20
|
+
"EMBARGOED"
|
|
21
|
+
], O = {
|
|
22
|
+
relevance: "Best match",
|
|
23
|
+
name: "Name A–Z",
|
|
24
|
+
updated: "Recently updated",
|
|
25
|
+
size: "Largest first"
|
|
26
|
+
};
|
|
27
|
+
function ce(e) {
|
|
28
|
+
if (!e) return "—";
|
|
29
|
+
let t = Number(e);
|
|
30
|
+
if (!Number.isFinite(t) || t <= 0) return "—";
|
|
31
|
+
let n = [
|
|
32
|
+
"B",
|
|
33
|
+
"KB",
|
|
34
|
+
"MB",
|
|
35
|
+
"GB",
|
|
36
|
+
"TB",
|
|
37
|
+
"PB"
|
|
38
|
+
], r = t, i = 0;
|
|
39
|
+
for (; r >= 1024 && i < n.length - 1;) r /= 1024, i += 1;
|
|
40
|
+
return `${r >= 10 || i === 0 ? Math.round(r) : r.toFixed(1)} ${n[i]}`;
|
|
41
|
+
}
|
|
42
|
+
function k(e) {
|
|
43
|
+
let t = Number(e ?? 0);
|
|
44
|
+
return Number.isFinite(t) && t > 0 ? `${t.toLocaleString()} rows` : "no rows";
|
|
45
|
+
}
|
|
46
|
+
function A() {
|
|
47
|
+
let { navigate: A } = e(), [j, M] = u(""), [N, P] = u(""), [F, I] = u({}), [L, R] = u("relevance"), [z, B] = u(!1);
|
|
48
|
+
re(() => {
|
|
49
|
+
let e = window.setTimeout(() => P(j.trim()), oe);
|
|
50
|
+
return () => window.clearTimeout(e);
|
|
51
|
+
}, [j]);
|
|
52
|
+
let V = n({
|
|
53
|
+
q: N || void 0,
|
|
54
|
+
pagination: {
|
|
55
|
+
limit: D,
|
|
56
|
+
offset: 0
|
|
57
|
+
}
|
|
58
|
+
}), H = r({ pagination: {
|
|
59
|
+
limit: D,
|
|
60
|
+
offset: 0
|
|
61
|
+
} }), U = t(), W = l(() => V.data?.cosListDatasets?.items ?? [], [V.data]), le = !!V.data?.cosListDatasets?.pageInfo?.hasMore, G = l(() => H.data?.cosListSources?.items ?? [], [H.data]), K = l(() => U.data?.cosListCollections ?? [], [U.data]), q = l(() => {
|
|
62
|
+
let e = /* @__PURE__ */ new Map();
|
|
63
|
+
for (let t of G) e.set(t.id, t.type);
|
|
64
|
+
return e;
|
|
65
|
+
}, [G]), J = l(() => {
|
|
66
|
+
let e = /* @__PURE__ */ new Map();
|
|
67
|
+
for (let t of K) e.set(t.id, t.name);
|
|
68
|
+
return e;
|
|
69
|
+
}, [K]), Y = l(() => {
|
|
70
|
+
let e = F.instrument ?? [], t = F.access ?? [], n = F.format ?? [], r = F.mission ?? [];
|
|
71
|
+
return {
|
|
72
|
+
instrument: (t) => {
|
|
73
|
+
if (e.length === 0) return !0;
|
|
74
|
+
let n = t.sourceId ? q.get(t.sourceId) : void 0;
|
|
75
|
+
return n ? e.includes(n) : !1;
|
|
76
|
+
},
|
|
77
|
+
access: (e) => t.length === 0 || t.includes(e.accessLevel),
|
|
78
|
+
format: (e) => n.length === 0 || (e.format ? n.includes(e.format) : !1),
|
|
79
|
+
mission: (e) => r.length === 0 || (e.collectionId ? r.includes(e.collectionId) : !1)
|
|
80
|
+
};
|
|
81
|
+
}, [F, q]), X = l(() => (e, t) => {
|
|
82
|
+
let n = 0;
|
|
83
|
+
for (let r of W) {
|
|
84
|
+
let i = !0;
|
|
85
|
+
for (let t of Object.keys(Y)) if (t !== e && !Y[t](r)) {
|
|
86
|
+
i = !1;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
i && t(r) && (n += 1);
|
|
90
|
+
}
|
|
91
|
+
return n;
|
|
92
|
+
}, [W, Y]), ue = l(() => {
|
|
93
|
+
let e = /* @__PURE__ */ new Set(), t = /* @__PURE__ */ new Set(), n = /* @__PURE__ */ new Set();
|
|
94
|
+
for (let r of W) {
|
|
95
|
+
let i = r.sourceId ? q.get(r.sourceId) : void 0;
|
|
96
|
+
i && e.add(i), r.format && t.add(r.format), r.collectionId && n.add(r.collectionId);
|
|
97
|
+
}
|
|
98
|
+
return [
|
|
99
|
+
{
|
|
100
|
+
key: "instrument",
|
|
101
|
+
label: "Instrument",
|
|
102
|
+
options: [...e].map((e) => ({
|
|
103
|
+
value: e,
|
|
104
|
+
label: c(e),
|
|
105
|
+
count: X("instrument", (t) => t.sourceId ? q.get(t.sourceId) === e : !1)
|
|
106
|
+
})).sort((e, t) => t.count - e.count || e.label.localeCompare(t.label))
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
key: "access",
|
|
110
|
+
label: "Access",
|
|
111
|
+
options: se.map((e) => ({
|
|
112
|
+
value: e,
|
|
113
|
+
label: c(e),
|
|
114
|
+
count: X("access", (t) => t.accessLevel === e)
|
|
115
|
+
}))
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
key: "format",
|
|
119
|
+
label: "Format",
|
|
120
|
+
options: [...t].sort((e, t) => e.localeCompare(t)).map((e) => ({
|
|
121
|
+
value: e,
|
|
122
|
+
label: e,
|
|
123
|
+
count: X("format", (t) => t.format === e)
|
|
124
|
+
}))
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
key: "mission",
|
|
128
|
+
label: "Mission / Survey",
|
|
129
|
+
options: [...n].map((e) => ({
|
|
130
|
+
value: e,
|
|
131
|
+
label: J.get(e) ?? "Unknown collection",
|
|
132
|
+
count: X("mission", (t) => t.collectionId === e)
|
|
133
|
+
})).sort((e, t) => t.count - e.count || e.label.localeCompare(t.label))
|
|
134
|
+
}
|
|
135
|
+
];
|
|
136
|
+
}, [
|
|
137
|
+
W,
|
|
138
|
+
q,
|
|
139
|
+
J,
|
|
140
|
+
X
|
|
141
|
+
]), Z = l(() => {
|
|
142
|
+
let e = [...W.filter((e) => Y.instrument(e) && Y.access(e) && Y.format(e) && Y.mission(e))];
|
|
143
|
+
switch (L) {
|
|
144
|
+
case "name":
|
|
145
|
+
e.sort((e, t) => e.name.localeCompare(t.name));
|
|
146
|
+
break;
|
|
147
|
+
case "updated":
|
|
148
|
+
e.sort((e, t) => new Date(t.updatedAt).getTime() - new Date(e.updatedAt).getTime());
|
|
149
|
+
break;
|
|
150
|
+
case "size":
|
|
151
|
+
e.sort((e, t) => Number(t.sizeBytes) - Number(e.sizeBytes));
|
|
152
|
+
break;
|
|
153
|
+
default: break;
|
|
154
|
+
}
|
|
155
|
+
return e;
|
|
156
|
+
}, [
|
|
157
|
+
W,
|
|
158
|
+
Y,
|
|
159
|
+
L
|
|
160
|
+
]), Q = Object.values(F).reduce((e, t) => e + t.length, 0), de = (e, t) => {
|
|
161
|
+
I((n) => {
|
|
162
|
+
let r = n[e] ?? [], i = r.includes(t) ? r.filter((e) => e !== t) : [...r, t];
|
|
163
|
+
return {
|
|
164
|
+
...n,
|
|
165
|
+
[e]: i
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
}, $ = () => I({}), fe = V.loading && W.length === 0, pe = V.error, me = /* @__PURE__ */ d(ne, {
|
|
169
|
+
groups: ue,
|
|
170
|
+
selected: F,
|
|
171
|
+
onToggle: de,
|
|
172
|
+
onClear: $
|
|
173
|
+
});
|
|
174
|
+
return /* @__PURE__ */ d(o, {
|
|
175
|
+
title: "Discovery",
|
|
176
|
+
description: "Search and facet the federated science catalog — full-text across datasets, refined by instrument, access, format and mission.",
|
|
177
|
+
actions: /* @__PURE__ */ f(b, {
|
|
178
|
+
type: "button",
|
|
179
|
+
variant: "outline",
|
|
180
|
+
onClick: () => A("datasets"),
|
|
181
|
+
children: [/* @__PURE__ */ d(ae, { className: "size-4" }), "Table view"]
|
|
182
|
+
}),
|
|
183
|
+
children: /* @__PURE__ */ f("div", {
|
|
184
|
+
className: "space-y-5",
|
|
185
|
+
children: [
|
|
186
|
+
/* @__PURE__ */ f("div", {
|
|
187
|
+
className: "relative",
|
|
188
|
+
children: [
|
|
189
|
+
/* @__PURE__ */ d(_, {
|
|
190
|
+
className: "pointer-events-none absolute left-4 top-1/2 size-5 -translate-y-1/2 text-text-secondary",
|
|
191
|
+
"aria-hidden": "true"
|
|
192
|
+
}),
|
|
193
|
+
/* @__PURE__ */ d(x, {
|
|
194
|
+
type: "search",
|
|
195
|
+
value: j,
|
|
196
|
+
onChange: (e) => M(e.target.value),
|
|
197
|
+
placeholder: "Search datasets by name, description or tag…",
|
|
198
|
+
"aria-label": "Search datasets",
|
|
199
|
+
className: "h-12 rounded-card pl-12 pr-11 text-base"
|
|
200
|
+
}),
|
|
201
|
+
j ? /* @__PURE__ */ d("button", {
|
|
202
|
+
type: "button",
|
|
203
|
+
onClick: () => M(""),
|
|
204
|
+
"aria-label": "Clear search",
|
|
205
|
+
className: "absolute right-3 top-1/2 flex size-7 -translate-y-1/2 items-center justify-center rounded-full text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus",
|
|
206
|
+
children: /* @__PURE__ */ d(v, { className: "size-4" })
|
|
207
|
+
}) : null
|
|
208
|
+
]
|
|
209
|
+
}),
|
|
210
|
+
/* @__PURE__ */ d("div", {
|
|
211
|
+
className: "flex items-center justify-between gap-3 lg:hidden",
|
|
212
|
+
children: /* @__PURE__ */ f(b, {
|
|
213
|
+
type: "button",
|
|
214
|
+
variant: "outline",
|
|
215
|
+
onClick: () => B((e) => !e),
|
|
216
|
+
"aria-expanded": z,
|
|
217
|
+
children: [
|
|
218
|
+
/* @__PURE__ */ d(ie, { className: "size-4" }),
|
|
219
|
+
"Filters",
|
|
220
|
+
Q > 0 ? ` (${Q})` : ""
|
|
221
|
+
]
|
|
222
|
+
})
|
|
223
|
+
}),
|
|
224
|
+
/* @__PURE__ */ f("div", {
|
|
225
|
+
className: "flex flex-col gap-6 lg:flex-row",
|
|
226
|
+
children: [/* @__PURE__ */ d("aside", {
|
|
227
|
+
className: te("shrink-0 lg:block lg:w-60 lg:border-r lg:border-border-seam lg:pr-6", z ? "block" : "hidden"),
|
|
228
|
+
children: /* @__PURE__ */ d("div", {
|
|
229
|
+
className: "lg:sticky lg:top-0",
|
|
230
|
+
children: me
|
|
231
|
+
})
|
|
232
|
+
}), /* @__PURE__ */ f("div", {
|
|
233
|
+
className: "min-w-0 flex-1 space-y-4",
|
|
234
|
+
children: [
|
|
235
|
+
/* @__PURE__ */ f("div", {
|
|
236
|
+
className: "flex flex-wrap items-center justify-between gap-3",
|
|
237
|
+
children: [/* @__PURE__ */ f("div", {
|
|
238
|
+
className: "flex items-baseline gap-2",
|
|
239
|
+
children: [/* @__PURE__ */ d("span", {
|
|
240
|
+
className: "text-lg font-semibold tabular-nums text-text-primary",
|
|
241
|
+
children: Z.length
|
|
242
|
+
}), /* @__PURE__ */ f("span", {
|
|
243
|
+
className: "text-sm text-text-secondary",
|
|
244
|
+
children: [Z.length === 1 ? "dataset" : "datasets", N ? ` matching “${N}”` : ""]
|
|
245
|
+
})]
|
|
246
|
+
}), /* @__PURE__ */ f("label", {
|
|
247
|
+
className: "flex items-center gap-2 text-sm",
|
|
248
|
+
children: [/* @__PURE__ */ d(p, {
|
|
249
|
+
className: "size-4 text-text-secondary",
|
|
250
|
+
"aria-hidden": "true"
|
|
251
|
+
}), /* @__PURE__ */ f(S, {
|
|
252
|
+
value: L,
|
|
253
|
+
onValueChange: (e) => R(e),
|
|
254
|
+
children: [/* @__PURE__ */ d(T, {
|
|
255
|
+
"aria-label": "Sort results",
|
|
256
|
+
className: "w-48",
|
|
257
|
+
children: /* @__PURE__ */ d(E, {})
|
|
258
|
+
}), /* @__PURE__ */ d(C, { children: Object.keys(O).map((e) => /* @__PURE__ */ d(w, {
|
|
259
|
+
value: e,
|
|
260
|
+
children: O[e]
|
|
261
|
+
}, e)) })]
|
|
262
|
+
})]
|
|
263
|
+
})]
|
|
264
|
+
}),
|
|
265
|
+
le ? /* @__PURE__ */ f("p", {
|
|
266
|
+
className: "rounded-card border border-border-default bg-bg-sunken px-3 py-2 text-xs text-text-secondary",
|
|
267
|
+
children: [
|
|
268
|
+
"Showing facets and results for the first ",
|
|
269
|
+
D,
|
|
270
|
+
" matches. Refine your search to narrow the set."
|
|
271
|
+
]
|
|
272
|
+
}) : null,
|
|
273
|
+
fe ? /* @__PURE__ */ d(ee, {
|
|
274
|
+
variant: "rows",
|
|
275
|
+
rows: 6,
|
|
276
|
+
label: "Searching the catalog…"
|
|
277
|
+
}) : pe ? /* @__PURE__ */ d(a, {
|
|
278
|
+
message: "We could not run that search. Please try again.",
|
|
279
|
+
onRetry: () => void V.refetch()
|
|
280
|
+
}) : Z.length === 0 ? /* @__PURE__ */ d(i, {
|
|
281
|
+
icon: _,
|
|
282
|
+
title: N || Q > 0 ? "No datasets match" : "Start exploring",
|
|
283
|
+
description: N || Q > 0 ? "Try a broader search term or clear some facets." : "Search by name, description or tag, or pick a facet to browse the catalog.",
|
|
284
|
+
action: Q > 0 ? /* @__PURE__ */ d(b, {
|
|
285
|
+
type: "button",
|
|
286
|
+
variant: "outline",
|
|
287
|
+
onClick: $,
|
|
288
|
+
children: "Clear filters"
|
|
289
|
+
}) : void 0
|
|
290
|
+
}) : /* @__PURE__ */ d("div", {
|
|
291
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3",
|
|
292
|
+
children: Z.map((e) => /* @__PURE__ */ f("button", {
|
|
293
|
+
type: "button",
|
|
294
|
+
onClick: () => A(`datasets/${e.id}`),
|
|
295
|
+
className: "group flex flex-col overflow-hidden rounded-card border border-border-default bg-bg-elevated text-left outline-none transition-colors hover:border-border-strong hover:bg-bg-sunken focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
296
|
+
children: [/* @__PURE__ */ d("div", { className: "h-1 w-full bg-gradient-to-r from-accent-blue via-accent-purple to-accent-teal" }), /* @__PURE__ */ f("div", {
|
|
297
|
+
className: "flex flex-1 flex-col gap-3 p-4",
|
|
298
|
+
children: [
|
|
299
|
+
/* @__PURE__ */ d("h3", {
|
|
300
|
+
className: "line-clamp-2 text-sm font-semibold text-text-primary",
|
|
301
|
+
children: e.name
|
|
302
|
+
}),
|
|
303
|
+
/* @__PURE__ */ f("div", {
|
|
304
|
+
className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary",
|
|
305
|
+
children: [
|
|
306
|
+
/* @__PURE__ */ f("span", {
|
|
307
|
+
className: "inline-flex items-center gap-1",
|
|
308
|
+
children: [/* @__PURE__ */ d(h, {
|
|
309
|
+
className: "size-3.5",
|
|
310
|
+
"aria-hidden": "true"
|
|
311
|
+
}), e.format || "No format"]
|
|
312
|
+
}),
|
|
313
|
+
/* @__PURE__ */ f("span", {
|
|
314
|
+
className: "inline-flex items-center gap-1",
|
|
315
|
+
children: [/* @__PURE__ */ d(m, {
|
|
316
|
+
className: "size-3.5",
|
|
317
|
+
"aria-hidden": "true"
|
|
318
|
+
}), ce(e.sizeBytes)]
|
|
319
|
+
}),
|
|
320
|
+
/* @__PURE__ */ f("span", {
|
|
321
|
+
className: "inline-flex items-center gap-1",
|
|
322
|
+
children: [/* @__PURE__ */ d(g, {
|
|
323
|
+
className: "size-3.5",
|
|
324
|
+
"aria-hidden": "true"
|
|
325
|
+
}), k(e.rowCount)]
|
|
326
|
+
})
|
|
327
|
+
]
|
|
328
|
+
}),
|
|
329
|
+
/* @__PURE__ */ f("div", {
|
|
330
|
+
className: "flex flex-wrap items-center gap-1.5",
|
|
331
|
+
children: [/* @__PURE__ */ d(s, { status: e.status }), /* @__PURE__ */ d(s, { status: e.accessLevel })]
|
|
332
|
+
}),
|
|
333
|
+
e.tags.length > 0 ? /* @__PURE__ */ f("div", {
|
|
334
|
+
className: "mt-auto flex flex-wrap gap-1 pt-1",
|
|
335
|
+
children: [e.tags.slice(0, 3).map((e) => /* @__PURE__ */ d(y, {
|
|
336
|
+
variant: "secondary",
|
|
337
|
+
className: "text-xs",
|
|
338
|
+
children: e
|
|
339
|
+
}, e)), e.tags.length > 3 ? /* @__PURE__ */ f("span", {
|
|
340
|
+
className: "text-xs text-text-secondary",
|
|
341
|
+
children: ["+", e.tags.length - 3]
|
|
342
|
+
}) : null]
|
|
343
|
+
}) : null
|
|
344
|
+
]
|
|
345
|
+
})]
|
|
346
|
+
}, e.id))
|
|
347
|
+
})
|
|
348
|
+
]
|
|
349
|
+
})]
|
|
350
|
+
})
|
|
351
|
+
]
|
|
352
|
+
})
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
//#endregion
|
|
356
|
+
export { A as default };
|
|
357
|
+
|
|
358
|
+
//# sourceMappingURL=DiscoveryPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DiscoveryPage.js","names":[],"sources":["../../../../src/cosmicintersection/pages/discovery/DiscoveryPage.tsx"],"sourcesContent":["/**\n * DiscoveryPage — faceted full-text discovery over the federated dataset catalog\n * (mockup 05).\n *\n * A prominent debounced search bar drives the backend `q` full-text argument on\n * `cosListDatasets`; the matched datasets form a \"pool\" (capped at a generous\n * limit) from which every facet count and the result grid are derived — so counts\n * and results always agree and nothing is fabricated. Facets are multi-select and\n * cross-filtered (each group's counts reflect the other selected groups), matching\n * the standard faceted-search behaviour:\n * - Instrument → distinct source types (via cosListSources), mapped from each\n * dataset's sourceId\n * - Access → CosAccessLevel enum\n * - Format → distinct formats present in the pool\n * - Mission/Survey → collections (via cosListCollections)\n *\n * Everything renders from live backend data with honest loading / empty / error\n * states. Tokens only — no raw palette colours.\n */\nimport { useEffect, useMemo, useState } from 'react';\nimport {\n ArrowUpDown,\n Database,\n FileText,\n Rows3,\n Search,\n SlidersHorizontal,\n Table as TableIcon,\n X,\n} from 'lucide-react';\nimport {\n Badge,\n Button,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCosCollectionsQuery,\n useCosDatasetsQuery,\n useCosSourcesQuery,\n type CosAccessLevel,\n type CosDataset,\n} from '../../api/cosmicApi';\nimport {\n EmptyState,\n ErrorState,\n LoadingState,\n PageLayout,\n StatusBadge,\n formatEnumLabel,\n} from '../../components';\nimport { FacetPanel, type FacetGroup } from '../../components/FacetPanel';\nimport { useCosmicIntersection } from '../../context/CosmicIntersectionContext';\nimport { cn } from '../../utils/cn';\n\n/** Generous ceiling for the search pool — facet counts reflect this window. */\nconst POOL_LIMIT = 200;\nconst SEARCH_DEBOUNCE_MS = 300;\n\nconst ACCESS_LEVELS: CosAccessLevel[] = ['PUBLIC', 'WORKSPACE', 'RESTRICTED', 'EMBARGOED'];\n\ntype SortKey = 'relevance' | 'name' | 'updated' | 'size';\n\nconst SORT_LABELS: Record<SortKey, string> = {\n relevance: 'Best match',\n name: 'Name A–Z',\n updated: 'Recently updated',\n size: 'Largest first',\n};\n\n/** BigInt byte string → human-readable size, or an em dash when absent/zero. */\nfunction formatBytes(raw: string | null | undefined): string {\n if (!raw) {\n return '—';\n }\n const bytes = Number(raw);\n if (!Number.isFinite(bytes) || bytes <= 0) {\n return '—';\n }\n const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;\n}\n\nfunction formatRows(raw: string | null | undefined): string {\n const rows = Number(raw ?? 0);\n return Number.isFinite(rows) && rows > 0 ? `${rows.toLocaleString()} rows` : 'no rows';\n}\n\nexport default function DiscoveryPage() {\n const { navigate } = useCosmicIntersection();\n\n const [searchInput, setSearchInput] = useState('');\n const [q, setQ] = useState('');\n const [selected, setSelected] = useState<Record<string, string[]>>({});\n const [sort, setSort] = useState<SortKey>('relevance');\n const [facetsOpen, setFacetsOpen] = useState(false);\n\n // Debounce the raw input into the backend `q` argument.\n useEffect(() => {\n const handle = window.setTimeout(() => setQ(searchInput.trim()), SEARCH_DEBOUNCE_MS);\n return () => window.clearTimeout(handle);\n }, [searchInput]);\n\n const poolQuery = useCosDatasetsQuery({\n q: q || undefined,\n pagination: { limit: POOL_LIMIT, offset: 0 },\n });\n const sourcesQuery = useCosSourcesQuery({ pagination: { limit: POOL_LIMIT, offset: 0 } });\n const collectionsQuery = useCosCollectionsQuery();\n\n const pool = useMemo(() => poolQuery.data?.cosListDatasets?.items ?? [], [poolQuery.data]);\n const poolHasMore = Boolean(poolQuery.data?.cosListDatasets?.pageInfo?.hasMore);\n const sources = useMemo(\n () => sourcesQuery.data?.cosListSources?.items ?? [],\n [sourcesQuery.data]\n );\n const collections = useMemo(\n () => collectionsQuery.data?.cosListCollections ?? [],\n [collectionsQuery.data]\n );\n\n // sourceId → source type, so a dataset can be bucketed by instrument type.\n const sourceTypeById = useMemo(() => {\n const map = new Map<string, string>();\n for (const source of sources) {\n map.set(source.id, source.type);\n }\n return map;\n }, [sources]);\n\n const collectionNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const collection of collections) {\n map.set(collection.id, collection.name);\n }\n return map;\n }, [collections]);\n\n // Per-group predicates — a dataset \"matches\" a group if the group has no\n // selection or the dataset's value is among the selected values.\n const matchers = useMemo(() => {\n const instrument = selected.instrument ?? [];\n const access = selected.access ?? [];\n const format = selected.format ?? [];\n const mission = selected.mission ?? [];\n return {\n instrument: (d: CosDataset) => {\n if (instrument.length === 0) return true;\n const type = d.sourceId ? sourceTypeById.get(d.sourceId) : undefined;\n return type ? instrument.includes(type) : false;\n },\n access: (d: CosDataset) => access.length === 0 || access.includes(d.accessLevel),\n format: (d: CosDataset) =>\n format.length === 0 || (d.format ? format.includes(d.format) : false),\n mission: (d: CosDataset) =>\n mission.length === 0 || (d.collectionId ? mission.includes(d.collectionId) : false),\n } as const;\n }, [selected, sourceTypeById]);\n\n // Cross-filtered count: items passing every OTHER group, then the predicate.\n const countFor = useMemo(() => {\n return (groupKey: keyof typeof matchers, predicate: (d: CosDataset) => boolean): number => {\n let total = 0;\n for (const dataset of pool) {\n let ok = true;\n for (const key of Object.keys(matchers) as (keyof typeof matchers)[]) {\n if (key === groupKey) continue;\n if (!matchers[key](dataset)) {\n ok = false;\n break;\n }\n }\n if (ok && predicate(dataset)) {\n total += 1;\n }\n }\n return total;\n };\n }, [pool, matchers]);\n\n const facetGroups = useMemo<FacetGroup[]>(() => {\n // Universe of option values from the unfiltered pool (stable while toggling).\n const instrumentTypes = new Set<string>();\n const formats = new Set<string>();\n const missionIds = new Set<string>();\n for (const dataset of pool) {\n const type = dataset.sourceId ? sourceTypeById.get(dataset.sourceId) : undefined;\n if (type) instrumentTypes.add(type);\n if (dataset.format) formats.add(dataset.format);\n if (dataset.collectionId) missionIds.add(dataset.collectionId);\n }\n\n const instrument: FacetGroup = {\n key: 'instrument',\n label: 'Instrument',\n options: [...instrumentTypes]\n .map((type) => ({\n value: type,\n label: formatEnumLabel(type),\n count: countFor('instrument', (d) =>\n d.sourceId ? sourceTypeById.get(d.sourceId) === type : false\n ),\n }))\n .sort((a, b) => b.count - a.count || a.label.localeCompare(b.label)),\n };\n\n const access: FacetGroup = {\n key: 'access',\n label: 'Access',\n options: ACCESS_LEVELS.map((level) => ({\n value: level,\n label: formatEnumLabel(level),\n count: countFor('access', (d) => d.accessLevel === level),\n })),\n };\n\n const format: FacetGroup = {\n key: 'format',\n label: 'Format',\n options: [...formats]\n .sort((a, b) => a.localeCompare(b))\n .map((fmt) => ({\n value: fmt,\n label: fmt,\n count: countFor('format', (d) => d.format === fmt),\n })),\n };\n\n const mission: FacetGroup = {\n key: 'mission',\n label: 'Mission / Survey',\n options: [...missionIds]\n .map((id) => ({\n value: id,\n label: collectionNameById.get(id) ?? 'Unknown collection',\n count: countFor('mission', (d) => d.collectionId === id),\n }))\n .sort((a, b) => b.count - a.count || a.label.localeCompare(b.label)),\n };\n\n return [instrument, access, format, mission];\n }, [pool, sourceTypeById, collectionNameById, countFor]);\n\n // Displayed results: the pool filtered by all selected facets, then sorted.\n const results = useMemo(() => {\n const filtered = pool.filter(\n (dataset) =>\n matchers.instrument(dataset) &&\n matchers.access(dataset) &&\n matchers.format(dataset) &&\n matchers.mission(dataset)\n );\n const sorted = [...filtered];\n switch (sort) {\n case 'name':\n sorted.sort((a, b) => a.name.localeCompare(b.name));\n break;\n case 'updated':\n sorted.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());\n break;\n case 'size':\n sorted.sort((a, b) => Number(b.sizeBytes) - Number(a.sizeBytes));\n break;\n case 'relevance':\n default:\n break; // preserve backend order (relevance when `q` is present)\n }\n return sorted;\n }, [pool, matchers, sort]);\n\n const totalSelected = Object.values(selected).reduce((sum, values) => sum + values.length, 0);\n\n const handleToggle = (groupKey: string, value: string) => {\n setSelected((prev) => {\n const current = prev[groupKey] ?? [];\n const next = current.includes(value)\n ? current.filter((item) => item !== value)\n : [...current, value];\n return { ...prev, [groupKey]: next };\n });\n };\n\n const handleClear = () => setSelected({});\n\n const loading = poolQuery.loading && pool.length === 0;\n const error = poolQuery.error;\n\n const facetPanel = (\n <FacetPanel\n groups={facetGroups}\n selected={selected}\n onToggle={handleToggle}\n onClear={handleClear}\n />\n );\n\n return (\n <PageLayout\n title=\"Discovery\"\n description=\"Search and facet the federated science catalog — full-text across datasets, refined by instrument, access, format and mission.\"\n actions={\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('datasets')}>\n <TableIcon className=\"size-4\" />\n Table view\n </Button>\n }\n >\n <div className=\"space-y-5\">\n {/* Search bar */}\n <div className=\"relative\">\n <Search\n className=\"pointer-events-none absolute left-4 top-1/2 size-5 -translate-y-1/2 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <Input\n type=\"search\"\n value={searchInput}\n onChange={(event) => setSearchInput(event.target.value)}\n placeholder=\"Search datasets by name, description or tag…\"\n aria-label=\"Search datasets\"\n className=\"h-12 rounded-card pl-12 pr-11 text-base\"\n />\n {searchInput ? (\n <button\n type=\"button\"\n onClick={() => setSearchInput('')}\n aria-label=\"Clear search\"\n className=\"absolute right-3 top-1/2 flex size-7 -translate-y-1/2 items-center justify-center rounded-full text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus\"\n >\n <X className=\"size-4\" />\n </button>\n ) : null}\n </div>\n\n {/* Mobile facet toggle */}\n <div className=\"flex items-center justify-between gap-3 lg:hidden\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setFacetsOpen((open) => !open)}\n aria-expanded={facetsOpen}\n >\n <SlidersHorizontal className=\"size-4\" />\n Filters{totalSelected > 0 ? ` (${totalSelected})` : ''}\n </Button>\n </div>\n\n <div className=\"flex flex-col gap-6 lg:flex-row\">\n {/* Facets — sticky sidebar on desktop, collapsible on mobile */}\n <aside\n className={cn(\n 'shrink-0 lg:block lg:w-60 lg:border-r lg:border-border-seam lg:pr-6',\n facetsOpen ? 'block' : 'hidden'\n )}\n >\n <div className=\"lg:sticky lg:top-0\">{facetPanel}</div>\n </aside>\n\n {/* Results */}\n <div className=\"min-w-0 flex-1 space-y-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {results.length}\n </span>\n <span className=\"text-sm text-text-secondary\">\n {results.length === 1 ? 'dataset' : 'datasets'}\n {q ? ` matching “${q}”` : ''}\n </span>\n </div>\n <label className=\"flex items-center gap-2 text-sm\">\n <ArrowUpDown className=\"size-4 text-text-secondary\" aria-hidden=\"true\" />\n <Select value={sort} onValueChange={(value) => setSort(value as SortKey)}>\n <SelectTrigger aria-label=\"Sort results\" className=\"w-48\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {(Object.keys(SORT_LABELS) as SortKey[]).map((key) => (\n <SelectItem key={key} value={key}>\n {SORT_LABELS[key]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {poolHasMore ? (\n <p className=\"rounded-card border border-border-default bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n Showing facets and results for the first {POOL_LIMIT} matches. Refine your search to\n narrow the set.\n </p>\n ) : null}\n\n {loading ? (\n <LoadingState variant=\"rows\" rows={6} label=\"Searching the catalog…\" />\n ) : error ? (\n <ErrorState\n message=\"We could not run that search. Please try again.\"\n onRetry={() => void poolQuery.refetch()}\n />\n ) : results.length === 0 ? (\n <EmptyState\n icon={Search}\n title={q || totalSelected > 0 ? 'No datasets match' : 'Start exploring'}\n description={\n q || totalSelected > 0\n ? 'Try a broader search term or clear some facets.'\n : 'Search by name, description or tag, or pick a facet to browse the catalog.'\n }\n action={\n totalSelected > 0 ? (\n <Button type=\"button\" variant=\"outline\" onClick={handleClear}>\n Clear filters\n </Button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3\">\n {results.map((dataset) => (\n <button\n key={dataset.id}\n type=\"button\"\n onClick={() => navigate(`datasets/${dataset.id}`)}\n className=\"group flex flex-col overflow-hidden rounded-card border border-border-default bg-bg-elevated text-left outline-none transition-colors hover:border-border-strong hover:bg-bg-sunken focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <div className=\"h-1 w-full bg-gradient-to-r from-accent-blue via-accent-purple to-accent-teal\" />\n <div className=\"flex flex-1 flex-col gap-3 p-4\">\n <h3 className=\"line-clamp-2 text-sm font-semibold text-text-primary\">\n {dataset.name}\n </h3>\n\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center gap-1\">\n <FileText className=\"size-3.5\" aria-hidden=\"true\" />\n {dataset.format || 'No format'}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Database className=\"size-3.5\" aria-hidden=\"true\" />\n {formatBytes(dataset.sizeBytes)}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Rows3 className=\"size-3.5\" aria-hidden=\"true\" />\n {formatRows(dataset.rowCount)}\n </span>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={dataset.status} />\n <StatusBadge status={dataset.accessLevel} />\n </div>\n\n {dataset.tags.length > 0 ? (\n <div className=\"mt-auto flex flex-wrap gap-1 pt-1\">\n {dataset.tags.slice(0, 3).map((tag) => (\n <Badge key={tag} variant=\"secondary\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n {dataset.tags.length > 3 ? (\n <span className=\"text-xs text-text-secondary\">\n +{dataset.tags.length - 3}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|