Improve dataset selection ergonomics
This commit is contained in:
@@ -385,6 +385,19 @@ function App(): JSX.Element {
|
||||
|
||||
const selectedArea = areas.find((area) => area.id === selectedMapAreaId) ?? null
|
||||
const activeWorkspaceItem = workspaceNavItems.find((item) => item.key === activeWorkspace) ?? workspaceNavItems[0]
|
||||
const openDatasetInMap = (dataset: DatasetCreateResponse) => {
|
||||
if (selectedProjectId) {
|
||||
loadDatasetDetails(selectedProjectId, dataset)
|
||||
}
|
||||
setMapLayerVisible(true)
|
||||
setActiveWorkspace('map')
|
||||
}
|
||||
const openDatasetExport = (dataset: DatasetCreateResponse) => {
|
||||
if (selectedProjectId) {
|
||||
loadDatasetDetails(selectedProjectId, dataset)
|
||||
}
|
||||
setActiveWorkspace('exports')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="app-shell workbench-shell">
|
||||
@@ -510,6 +523,7 @@ function App(): JSX.Element {
|
||||
|
||||
<DatasetPanel
|
||||
selectedProjectId={selectedProjectId}
|
||||
selectedDatasetId={selectedDatasetId}
|
||||
areas={areas}
|
||||
datasets={datasets}
|
||||
datasetForm={datasetForm}
|
||||
@@ -518,6 +532,8 @@ function App(): JSX.Element {
|
||||
onUploadDataset={uploadDataset}
|
||||
onLoadDatasetDetails={loadDatasetDetails}
|
||||
onRefreshMetadata={refreshMetadata}
|
||||
onOpenDatasetInMap={openDatasetInMap}
|
||||
onOpenDatasetExport={openDatasetExport}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -705,6 +721,11 @@ function App(): JSX.Element {
|
||||
segmentationRuns={segmentationRuns}
|
||||
selectedSegmentationRunId={selectedSegmentationRunId}
|
||||
segmentationItems={segmentationItems}
|
||||
onOpenDataWorkspace={() => setActiveWorkspace('data')}
|
||||
onOpenMapWorkspace={() => setActiveWorkspace('map')}
|
||||
onOpenQualityWorkspace={() => setActiveWorkspace('analysis')}
|
||||
onOpenExportsWorkspace={() => setActiveWorkspace('exports')}
|
||||
onOpenAiWorkspace={() => setActiveWorkspace('ai')}
|
||||
datasetDetailProps={{
|
||||
areas,
|
||||
availableVectorTargets,
|
||||
|
||||
@@ -15,6 +15,7 @@ interface DatasetFormState {
|
||||
|
||||
interface DatasetPanelProps {
|
||||
selectedProjectId: string | null
|
||||
selectedDatasetId: string | null
|
||||
areas: AreaRead[]
|
||||
datasets: DatasetCreateResponse[]
|
||||
datasetForm: DatasetFormState
|
||||
@@ -23,6 +24,8 @@ interface DatasetPanelProps {
|
||||
onUploadDataset: (event: FormEvent) => void
|
||||
onLoadDatasetDetails: (projectId: string, dataset: DatasetCreateResponse) => void
|
||||
onRefreshMetadata: (datasetId: string) => void
|
||||
onOpenDatasetInMap: (dataset: DatasetCreateResponse) => void
|
||||
onOpenDatasetExport: (dataset: DatasetCreateResponse) => void
|
||||
}
|
||||
|
||||
function formatBytes(value: number | null | undefined): string {
|
||||
@@ -52,6 +55,7 @@ function formatBounds(bounds: Record<string, number> | null | undefined): string
|
||||
|
||||
export function DatasetPanel({
|
||||
selectedProjectId,
|
||||
selectedDatasetId,
|
||||
areas,
|
||||
datasets,
|
||||
datasetForm,
|
||||
@@ -60,6 +64,8 @@ export function DatasetPanel({
|
||||
onUploadDataset,
|
||||
onLoadDatasetDetails,
|
||||
onRefreshMetadata,
|
||||
onOpenDatasetInMap,
|
||||
onOpenDatasetExport,
|
||||
}: DatasetPanelProps) {
|
||||
const readyDatasets = datasets.filter((dataset) => dataset.status === 'ready').length
|
||||
|
||||
@@ -124,7 +130,7 @@ export function DatasetPanel({
|
||||
{datasets.length === 0 ? <p>No datasets yet</p> : null}
|
||||
<ul className="dataset-list">
|
||||
{datasets.map((dataset) => (
|
||||
<li className="dataset-card" key={dataset.id}>
|
||||
<li className={dataset.id === selectedDatasetId ? 'dataset-card dataset-card-active' : 'dataset-card'} key={dataset.id}>
|
||||
<div className="dataset-card-header">
|
||||
<div>
|
||||
<strong>{dataset.name}</strong>
|
||||
@@ -152,6 +158,17 @@ export function DatasetPanel({
|
||||
>
|
||||
Select / details
|
||||
</button>
|
||||
<button className="secondary-action" type="button" onClick={() => onOpenDatasetInMap(dataset)}>
|
||||
Open in map
|
||||
</button>
|
||||
<button
|
||||
className="secondary-action"
|
||||
type="button"
|
||||
onClick={() => onOpenDatasetExport(dataset)}
|
||||
disabled={!(dataset.dataset_type === 'vector' || dataset.dataset_type === 'geojson')}
|
||||
>
|
||||
Export / QA
|
||||
</button>
|
||||
<button
|
||||
className="secondary-action"
|
||||
type="button"
|
||||
|
||||
@@ -30,6 +30,11 @@ interface WorkbenchInspectorProps {
|
||||
selectedSegmentationRunId: string
|
||||
segmentationItems: SegmentationRead[]
|
||||
datasetDetailProps: DatasetDetailPanelProps
|
||||
onOpenDataWorkspace: () => void
|
||||
onOpenMapWorkspace: () => void
|
||||
onOpenQualityWorkspace: () => void
|
||||
onOpenExportsWorkspace: () => void
|
||||
onOpenAiWorkspace: () => void
|
||||
}
|
||||
|
||||
function formatValue(value: string | number | null | undefined): string {
|
||||
@@ -64,6 +69,11 @@ export function WorkbenchInspector({
|
||||
selectedSegmentationRunId,
|
||||
segmentationItems,
|
||||
datasetDetailProps,
|
||||
onOpenDataWorkspace,
|
||||
onOpenMapWorkspace,
|
||||
onOpenQualityWorkspace,
|
||||
onOpenExportsWorkspace,
|
||||
onOpenAiWorkspace,
|
||||
}: WorkbenchInspectorProps): JSX.Element {
|
||||
const [activeTab, setActiveTab] = useState<InspectorTab>('context')
|
||||
const selectedDetectionRun = useMemo(
|
||||
@@ -117,6 +127,11 @@ export function WorkbenchInspector({
|
||||
<InspectorField label="Status" value={selectedProject?.status} />
|
||||
<InspectorField label="AOIs" value={areas.length} />
|
||||
<InspectorField label="Datasets" value={datasetsCount} />
|
||||
<div className="button-row">
|
||||
<button type="button" className="secondary-action" onClick={onOpenDataWorkspace}>
|
||||
Open data setup
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="inspector-card">
|
||||
<h3>Active AOI</h3>
|
||||
@@ -124,6 +139,11 @@ export function WorkbenchInspector({
|
||||
<InspectorField label="Area m2" value={selectedArea?.area_m2} />
|
||||
<InspectorField label="CRS" value={selectedArea?.original_crs} />
|
||||
<InspectorField label="Geometry" value={selectedArea?.geometry?.type} />
|
||||
<div className="button-row">
|
||||
<button type="button" className="secondary-action" onClick={onOpenMapWorkspace}>
|
||||
Open map
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="inspector-card">
|
||||
<h3>Map feature</h3>
|
||||
@@ -138,6 +158,17 @@ export function WorkbenchInspector({
|
||||
|
||||
{activeTab === 'dataset' ? (
|
||||
<div className="inspector-tab-panel">
|
||||
<div className="inspector-action-bar">
|
||||
<button type="button" className="secondary-action" onClick={onOpenDataWorkspace}>
|
||||
Data catalog
|
||||
</button>
|
||||
<button type="button" className="secondary-action" onClick={onOpenMapWorkspace}>
|
||||
Map layer
|
||||
</button>
|
||||
<button type="button" className="secondary-action" onClick={onOpenExportsWorkspace}>
|
||||
Export
|
||||
</button>
|
||||
</div>
|
||||
<DatasetDetailPanel {...datasetDetailProps} />
|
||||
</div>
|
||||
) : null}
|
||||
@@ -151,6 +182,11 @@ export function WorkbenchInspector({
|
||||
<InspectorField label="Score" value={latestQualityCheck?.score} />
|
||||
<InspectorField label="Metrics" value={latestQualityCheck?.metrics.length} />
|
||||
<InspectorField label="Reference" value={latestQualityCheck?.reference_dataset_id} />
|
||||
<div className="button-row">
|
||||
<button type="button" className="secondary-action" onClick={onOpenQualityWorkspace}>
|
||||
Open QA/QC
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="inspector-card">
|
||||
<h3>Latest export</h3>
|
||||
@@ -159,6 +195,11 @@ export function WorkbenchInspector({
|
||||
<InspectorField label="Status" value={latestPersistedExport?.status ?? latestExport?.status} />
|
||||
<InspectorField label="Path" value={latestPersistedExport?.storage_path ?? latestExport?.path} />
|
||||
<InspectorField label="Export count" value={exports.length} />
|
||||
<div className="button-row">
|
||||
<button type="button" className="secondary-action" onClick={onOpenExportsWorkspace}>
|
||||
Open exports
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -171,6 +212,11 @@ export function WorkbenchInspector({
|
||||
<InspectorField label="Status" value={selectedDetectionRun?.status} />
|
||||
<InspectorField label="Model" value={selectedDetectionRun?.model_name} />
|
||||
<InspectorField label="Detections loaded" value={detectionItems.length} />
|
||||
<div className="button-row">
|
||||
<button type="button" className="secondary-action" onClick={onOpenAiWorkspace}>
|
||||
Open AI Labs
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="inspector-card">
|
||||
<h3>Segmentation run</h3>
|
||||
|
||||
@@ -913,6 +913,11 @@ button.entity-card {
|
||||
padding: 0.85rem;
|
||||
}
|
||||
|
||||
.dataset-card-active {
|
||||
border-color: rgba(15, 118, 110, 0.5);
|
||||
box-shadow: inset 4px 0 0 var(--accent);
|
||||
}
|
||||
|
||||
.dataset-card-header,
|
||||
.model-card,
|
||||
.quality-check-header,
|
||||
@@ -1167,6 +1172,25 @@ button.entity-card {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.inspector-action-bar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0.4rem;
|
||||
position: sticky;
|
||||
top: 6.3rem;
|
||||
z-index: 2;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 0.45rem;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.inspector-action-bar button {
|
||||
min-height: 2.15rem;
|
||||
padding: 0.34rem 0.42rem;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.inspector-card {
|
||||
display: grid;
|
||||
gap: 0.55rem;
|
||||
@@ -1286,7 +1310,8 @@ button.entity-card {
|
||||
.lab-form-grid,
|
||||
.quality-summary-grid,
|
||||
.quality-score-row,
|
||||
.export-action-grid {
|
||||
.export-action-grid,
|
||||
.inspector-action-bar {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user