Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
433 changes: 433 additions & 0 deletions src/components/CollectionSchemaDialog.tsx

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from './Button'
export * from './ContactShow'
export * from './WellShow'
export * from './ClearableSelect'
export * from './CollectionSchemaDialog'
export * from './ChipWithExplain'
export * from './ConfirmDialog'
export * from './Controlled'
Expand Down
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './useAll'
export * from './useAllNotes'
export * from './useDebounce'
export * from './useElevation'
export * from './useCollectionSchema'
export * from './useGisArtifacts'
export * from './useLayer'
export * from './useLexicon'
Expand Down
32 changes: 32 additions & 0 deletions src/hooks/useCollectionSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useQuery } from '@tanstack/react-query'
import { fetcher } from '@/providers/ocotillo-data-provider'
import {
type CollectionSchema,
zCollectionSchema,
} from '@/utils/collectionSchema'

/**
* Fetches the JSON Schema an OGC collection publishes for its features.
*
* `?f=json` is passed explicitly: the schema path is content-negotiated and
* serves HTML by default, so relying on the Accept header risks parsing an
* HTML page as JSON.
*
* Schemas change only when the collection's shape changes, so this caches for
* the session rather than refetching each time the modal opens.
*/
export const useCollectionSchema = (
collectionId: string | undefined,
options?: { enabled?: boolean }
) =>
useQuery<CollectionSchema>({
queryKey: ['ogcapi-collection-schema', collectionId],
enabled: (options?.enabled ?? true) && Boolean(collectionId),
staleTime: Number.POSITIVE_INFINITY,
queryFn: async () => {
const response = await fetcher(
`ogcapi/collections/${encodeURIComponent(collectionId as string)}/schema?f=json`
)
return zCollectionSchema.parse(response.data)
},
})
162 changes: 118 additions & 44 deletions src/pages/ocotillo/collections/list.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
ArrowOutward,
DataObject,
ElectricBolt,
Opacity,
OpenInNew,
Expand Down Expand Up @@ -37,6 +38,7 @@ import { ErrorComponent } from '@refinedev/mui'
import { useQuery } from '@tanstack/react-query'
import { Fragment, useState } from 'react'
import { Link as RouterLink } from 'react-router'
import { CollectionSchemaDialog } from '@/components/CollectionSchemaDialog'
import {
GisConnectionsPanel,
GisLayerDownloads,
Expand All @@ -59,6 +61,17 @@ import {

type CollectionsView = 'cards' | 'table'

// Desktop-GIS connection and layer downloads are hidden on this page for now.
// The catalogue data still loads (the collection index keys off it), so
// flipping this back to `true` restores the panel, the table column, and the
// per-card download links together.
const SHOW_DESKTOP_GIS = false

type SchemaDialogTarget = {
collectionId?: string
title: string
}

type CollectionGroupKey =
| 'groundwater'
| 'surfaceWater'
Expand Down Expand Up @@ -401,6 +414,18 @@ export const CollectionsPage = () => {
const dataProvider = useDataProvider()
const { canViewAmp } = useAccessCapabilities()
const [view, setView] = useState<CollectionsView>('table')
// The target outlives `isSchemaOpen` on purpose: MUI keeps the dialog mounted
// through its closing transition, and clearing the target on close would
// flash an empty schema shell on the way out.
const [schemaTarget, setSchemaTarget] = useState<SchemaDialogTarget | null>(
null
)
const [isSchemaOpen, setIsSchemaOpen] = useState(false)

const openSchema = (target: SchemaDialogTarget) => {
setSchemaTarget(target)
setIsSchemaOpen(true)
}
const { data: gisCatalog } = useGisArtifacts({
enabled: access?.can === true,
})
Expand Down Expand Up @@ -563,7 +588,7 @@ export const CollectionsPage = () => {
</Box>
</Paper>

{gisCatalog ? (
{SHOW_DESKTOP_GIS && gisCatalog ? (
<GisConnectionsPanel
catalog={gisCatalog}
canViewInternal={canViewAmp}
Expand All @@ -573,6 +598,7 @@ export const CollectionsPage = () => {
{view === 'table' ? (
<CollectionsTable
rows={buildCollectionRows(groups, gisLayersByCollection)}
onOpenSchema={openSchema}
/>
) : (
<Grid container spacing={3}>
Expand Down Expand Up @@ -637,6 +663,7 @@ export const CollectionsPage = () => {
gisLayer={gisLayersByCollection.get(
collectionIdOf(collection) ?? ''
)}
onOpenSchema={openSchema}
index={index}
/>
)
Expand All @@ -653,23 +680,32 @@ export const CollectionsPage = () => {
)}
</Stack>
</Box>

<CollectionSchemaDialog
open={isSchemaOpen}
onClose={() => setIsSchemaOpen(false)}
collectionId={schemaTarget?.collectionId}
title={schemaTarget?.title ?? ''}
/>
</Container>
)
}

const CollectionsTable = ({
rows,
onOpenSchema,
}: {
rows: CollectionsTableRow<CollectionGroupKey>[]
onOpenSchema: (target: SchemaDialogTarget) => void
}) => (
<TableContainer component={Paper} variant="outlined" sx={{ borderRadius: 3 }}>
<Table size="small" aria-label="Published OGC datasets">
<TableHead>
<TableRow>
<TableCell>Dataset</TableCell>
<TableCell>Description</TableCell>
<TableCell>Desktop GIS</TableCell>
<TableCell align="right">Map</TableCell>
{SHOW_DESKTOP_GIS ? <TableCell>Desktop GIS</TableCell> : null}
<TableCell align="right">Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
Expand All @@ -684,7 +720,7 @@ const CollectionsTable = ({
{startsGroup ? (
<TableRow>
<TableCell
colSpan={4}
colSpan={SHOW_DESKTOP_GIS ? 4 : 3}
sx={{
py: 0.75,
bgcolor: alpha(style.accent, 0.16),
Expand Down Expand Up @@ -727,29 +763,54 @@ const CollectionsTable = ({
</Typography>
)}
</TableCell>
<TableCell sx={{ minWidth: 200 }}>
{row.gisLayer ? (
<GisLayerDownloads layer={row.gisLayer} />
) : (
<Typography variant="caption" color="text.secondary">
</Typography>
)}
</TableCell>
{SHOW_DESKTOP_GIS ? (
<TableCell sx={{ minWidth: 200 }}>
{row.gisLayer ? (
<GisLayerDownloads layer={row.gisLayer} />
) : (
<Typography variant="caption" color="text.secondary">
</Typography>
)}
</TableCell>
) : null}
<TableCell align="right">
<Button
component={RouterLink}
to={`/ocotillo/map?layer=${encodeURIComponent(row.layerKey)}`}
size="small"
variant="outlined"
endIcon={<ArrowOutward fontSize="small" />}
sx={{
borderColor: alpha(style.accent, 0.28),
color: style.accent,
}}
<Stack
direction="row"
spacing={1}
justifyContent="flex-end"
flexWrap="wrap"
useFlexGap
>
Open Map
</Button>
<Button
size="small"
variant="text"
startIcon={<DataObject fontSize="small" />}
disabled={!row.id}
onClick={() =>
onOpenSchema({
collectionId: row.id,
title: row.title,
})
}
sx={{ color: style.accent }}
>
Schema
</Button>
<Button
component={RouterLink}
to={`/ocotillo/map?layer=${encodeURIComponent(row.layerKey)}`}
size="small"
variant="outlined"
endIcon={<ArrowOutward fontSize="small" />}
sx={{
borderColor: alpha(style.accent, 0.28),
color: style.accent,
}}
>
Open Map
</Button>
</Stack>
</TableCell>
</TableRow>
</Fragment>
Expand Down Expand Up @@ -813,12 +874,14 @@ const CollectionRow = ({
groupKey,
displayLabel,
gisLayer,
onOpenSchema,
}: {
collection: OgcCollectionRecord
layerKey: string
groupKey: CollectionGroupKey
displayLabel?: string
gisLayer?: GisLayer
onOpenSchema: (target: SchemaDialogTarget) => void
index: number
}) => {
const style = GROUP_STYLES[groupKey]
Expand Down Expand Up @@ -882,24 +945,35 @@ const CollectionRow = ({
</Typography>
) : null}
</Stack>
<Button
component={RouterLink}
to={`/ocotillo/map?layer=${encodeURIComponent(layerKey)}`}
size="small"
variant="outlined"
endIcon={<ArrowOutward fontSize="small" />}
sx={{
flexShrink: 0,
borderColor: alpha(style.accent, 0.28),
color: style.accent,
'&:hover': {
borderColor: alpha(style.accent, 0.5),
backgroundColor: alpha(style.accent, 0.06),
},
}}
>
Open Map
</Button>
<Stack direction="row" spacing={1} sx={{ flexShrink: 0 }}>
<Button
size="small"
variant="text"
startIcon={<DataObject fontSize="small" />}
disabled={!id}
onClick={() => onOpenSchema({ collectionId: id, title })}
sx={{ color: style.accent }}
>
Schema
</Button>
<Button
component={RouterLink}
to={`/ocotillo/map?layer=${encodeURIComponent(layerKey)}`}
size="small"
variant="outlined"
endIcon={<ArrowOutward fontSize="small" />}
sx={{
borderColor: alpha(style.accent, 0.28),
color: style.accent,
'&:hover': {
borderColor: alpha(style.accent, 0.5),
backgroundColor: alpha(style.accent, 0.06),
},
}}
>
Open Map
</Button>
</Stack>
</Stack>
{description ? (
<Typography variant="body2" color="text.secondary">
Expand All @@ -910,7 +984,7 @@ const CollectionRow = ({
No published description.
</Typography>
)}
{gisLayer ? (
{SHOW_DESKTOP_GIS && gisLayer ? (
<Stack spacing={0.5}>
<Typography variant="caption" color="text.secondary">
Open in desktop GIS
Expand Down
Loading
Loading