Polish operation form readability
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-06-20 02:39:44 +02:00
parent 4e6ba707cf
commit b15b123173
8 changed files with 315 additions and 121 deletions
+2
View File
@@ -16,6 +16,8 @@ Export Preview shows a compact JSON summary before the payload and keeps large J
Primary workbench navigation, overview shortcuts, inspector tabs and dataset action buttons now share visible keyboard focus styling. Inspector tabs are also bound to tab panels with ARIA metadata.
Raster and vector operation panels use structured group headings, compact helper text, field grids, action rows and inline error blocks so dense GIS controls remain scannable in the inspector.
## Scope implemented
- API client layer (`src/services/api`)
- Project and area list/create flows
@@ -133,29 +133,39 @@ export function RasterControls({
</div>
) : null}
<div className="dataset-tool-group">
<label>
Reproject CRS
<input
value={rasterReprojectCrs}
onChange={(event) => onSetRasterReprojectCrs(event.target.value)}
placeholder="EPSG:31370"
/>
</label>
<label>
Resampling
<select value={rasterReprojectResampling} onChange={(event) => onSetRasterReprojectResampling(event.target.value)}>
<option value="nearest">nearest</option>
<option value="bilinear">bilinear</option>
<option value="cubic">cubic</option>
</select>
</label>
<button type="button" onClick={onRunRasterReproject}>
Reproject raster
</button>
<h4 className="dataset-tool-heading">Reproject raster</h4>
<p className="dataset-tool-helper">Create a derived raster in a target CRS with the selected resampling method.</p>
<div className="dataset-tool-grid">
<label className="dataset-tool-field">
<span className="dataset-tool-label">Reproject CRS</span>
<input
value={rasterReprojectCrs}
onChange={(event) => onSetRasterReprojectCrs(event.target.value)}
placeholder="EPSG:31370"
/>
<span className="dataset-tool-helper">Target CRS for the derived raster artifact.</span>
</label>
<label className="dataset-tool-field">
<span className="dataset-tool-label">Resampling</span>
<select value={rasterReprojectResampling} onChange={(event) => onSetRasterReprojectResampling(event.target.value)}>
<option value="nearest">nearest</option>
<option value="bilinear">bilinear</option>
<option value="cubic">cubic</option>
</select>
<span className="dataset-tool-helper">Nearest preserves classes; bilinear/cubic smooth continuous rasters.</span>
</label>
</div>
<div className="dataset-tool-action-row">
<button type="button" onClick={onRunRasterReproject}>
Reproject raster
</button>
</div>
</div>
<div className="dataset-tool-group">
<label>
Clip area
<h4 className="dataset-tool-heading">Clip raster</h4>
<p className="dataset-tool-helper">Clip the selected raster to an existing project area.</p>
<label className="dataset-tool-field">
<span className="dataset-tool-label">Clip area</span>
<select value={selectedClipAreaId} onChange={(event) => onSetSelectedClipAreaId(event.target.value)}>
{areas.map((area) => (
<option key={area.id} value={area.id}>
@@ -164,43 +174,54 @@ export function RasterControls({
))}
</select>
</label>
<button type="button" onClick={onRunRasterClip} disabled={areas.length === 0}>
Clip raster by area
</button>
{areas.length === 0 ? <p className="error">Create an area before raster clipping.</p> : null}
<div className="dataset-tool-action-row">
<button type="button" onClick={onRunRasterClip} disabled={areas.length === 0}>
Clip raster by area
</button>
</div>
{areas.length === 0 ? <p className="dataset-tool-error">Create an area before raster clipping.</p> : null}
</div>
<div className="dataset-tool-group">
<label>
Tile size
<input
type="number"
min={1}
value={rasterTileSize}
onChange={(event) => onSetRasterTileSize(Number(event.target.value))}
/>
</label>
<label>
Overlap
<input
type="number"
min={0}
value={rasterTileOverlap}
onChange={(event) => onSetRasterTileOverlap(Number(event.target.value))}
/>
</label>
<label>
Tile basename
<input
value={rasterTileOutputName}
onChange={(event) => onSetRasterTileOutputName(event.target.value)}
placeholder="optional"
/>
</label>
<button type="button" onClick={onRunRasterTile} disabled={!isRasterTileInputValid}>
Generate tiles
</button>
<h4 className="dataset-tool-heading">Generate tiles</h4>
<p className="dataset-tool-helper">Create a tile manifest for downstream detection or segmentation runs.</p>
<div className="dataset-tool-grid">
<label className="dataset-tool-field">
<span className="dataset-tool-label">Tile size</span>
<input
type="number"
min={1}
value={rasterTileSize}
onChange={(event) => onSetRasterTileSize(Number(event.target.value))}
/>
<span className="dataset-tool-helper">{'Tile size must be > 0.'}</span>
</label>
<label className="dataset-tool-field">
<span className="dataset-tool-label">Overlap</span>
<input
type="number"
min={0}
value={rasterTileOverlap}
onChange={(event) => onSetRasterTileOverlap(Number(event.target.value))}
/>
<span className="dataset-tool-helper">Overlap must be smaller than tile size.</span>
</label>
<label className="dataset-tool-field">
<span className="dataset-tool-label">Tile basename</span>
<input
value={rasterTileOutputName}
onChange={(event) => onSetRasterTileOutputName(event.target.value)}
placeholder="optional"
/>
<span className="dataset-tool-helper">Optional artifact name prefix for generated tiles.</span>
</label>
</div>
<div className="dataset-tool-action-row">
<button type="button" onClick={onRunRasterTile} disabled={!isRasterTileInputValid}>
Generate tiles
</button>
</div>
{!isRasterTileInputValid ? (
<p className="error">
<p className="dataset-tool-error">
Tile size must be {'>'} 0 and overlap must be {'>='} 0 and smaller than tile size.
</p>
) : null}
@@ -209,46 +230,61 @@ export function RasterControls({
<div>
<p>Use available band indexes from the raster file (1-based).</p>
<div className="dataset-tool-group">
<p>NDVI</p>
<label>
Nir band
<input type="number" min={1} value={ndviNirBand} onChange={(event) => onSetNdviNirBand(Number(event.target.value))} />
</label>
<label>
Red band
<input type="number" min={1} value={ndviRedBand} onChange={(event) => onSetNdviRedBand(Number(event.target.value))} />
</label>
<button type="button" onClick={onRunRasterNdvi}>
Compute NDVI
</button>
<h4 className="dataset-tool-heading">NDVI</h4>
<p className="dataset-tool-helper">Vegetation index from NIR and red bands.</p>
<div className="dataset-tool-grid">
<label className="dataset-tool-field">
<span className="dataset-tool-label">NIR band</span>
<input type="number" min={1} value={ndviNirBand} onChange={(event) => onSetNdviNirBand(Number(event.target.value))} />
</label>
<label className="dataset-tool-field">
<span className="dataset-tool-label">Red band</span>
<input type="number" min={1} value={ndviRedBand} onChange={(event) => onSetNdviRedBand(Number(event.target.value))} />
</label>
</div>
<div className="dataset-tool-action-row">
<button type="button" onClick={onRunRasterNdvi}>
Compute NDVI
</button>
</div>
</div>
<div className="dataset-tool-group">
<p>NDWI</p>
<label>
Nir band
<input type="number" min={1} value={ndwiNirBand} onChange={(event) => onSetNdwiNirBand(Number(event.target.value))} />
</label>
<label>
Green band
<input type="number" min={1} value={ndwiGreenBand} onChange={(event) => onSetNdwiGreenBand(Number(event.target.value))} />
</label>
<button type="button" onClick={onRunRasterNdwi}>
Compute NDWI
</button>
<h4 className="dataset-tool-heading">NDWI</h4>
<p className="dataset-tool-helper">Water index from NIR and green bands.</p>
<div className="dataset-tool-grid">
<label className="dataset-tool-field">
<span className="dataset-tool-label">NIR band</span>
<input type="number" min={1} value={ndwiNirBand} onChange={(event) => onSetNdwiNirBand(Number(event.target.value))} />
</label>
<label className="dataset-tool-field">
<span className="dataset-tool-label">Green band</span>
<input type="number" min={1} value={ndwiGreenBand} onChange={(event) => onSetNdwiGreenBand(Number(event.target.value))} />
</label>
</div>
<div className="dataset-tool-action-row">
<button type="button" onClick={onRunRasterNdwi}>
Compute NDWI
</button>
</div>
</div>
<div className="dataset-tool-group">
<p>NDBI</p>
<label>
Swir band
<input type="number" min={1} value={ndbiSwirBand} onChange={(event) => onSetNdbiSwirBand(Number(event.target.value))} />
</label>
<label>
Nir band
<input type="number" min={1} value={ndbiNirBand} onChange={(event) => onSetNdbiNirBand(Number(event.target.value))} />
</label>
<button type="button" onClick={onRunRasterNdbi}>
Compute NDBI
</button>
<h4 className="dataset-tool-heading">NDBI</h4>
<p className="dataset-tool-helper">Built-up index from SWIR and NIR bands.</p>
<div className="dataset-tool-grid">
<label className="dataset-tool-field">
<span className="dataset-tool-label">SWIR band</span>
<input type="number" min={1} value={ndbiSwirBand} onChange={(event) => onSetNdbiSwirBand(Number(event.target.value))} />
</label>
<label className="dataset-tool-field">
<span className="dataset-tool-label">NIR band</span>
<input type="number" min={1} value={ndbiNirBand} onChange={(event) => onSetNdbiNirBand(Number(event.target.value))} />
</label>
</div>
<div className="dataset-tool-action-row">
<button type="button" onClick={onRunRasterNdbi}>
Compute NDBI
</button>
</div>
</div>
</div>
</div>
@@ -27,38 +27,57 @@ export function VectorControls({
<div className="dataset-tool-panel vector-tool-panel">
<h3>Vector operations</h3>
<div className="dataset-tool-group">
<label>
Clip area
<select value={selectedClipAreaId} onChange={(event) => onSetSelectedClipAreaId(event.target.value)}>
{areas.map((area) => (
<option key={area.id} value={area.id}>
{area.name}
</option>
))}
</select>
</label>
<button type="button" onClick={onRunVectorClip} disabled={areas.length === 0}>
Run clip
</button>
<h4 className="dataset-tool-heading">Clip vector</h4>
<p className="dataset-tool-helper">Clip features to the selected project area.</p>
<div className="dataset-tool-grid">
<label className="dataset-tool-field">
<span className="dataset-tool-label">Clip area</span>
<select value={selectedClipAreaId} onChange={(event) => onSetSelectedClipAreaId(event.target.value)}>
{areas.map((area) => (
<option key={area.id} value={area.id}>
{area.name}
</option>
))}
</select>
</label>
</div>
<div className="dataset-tool-action-row">
<button type="button" onClick={onRunVectorClip} disabled={areas.length === 0}>
Run clip
</button>
</div>
</div>
<button type="button" onClick={onRunVectorBuffer}>
Run buffer (25m)
</button>
<div className="dataset-tool-group">
<label>
Intersect target
<select value={selectedIntersectTargetId} onChange={(event) => onSetSelectedIntersectTargetId(event.target.value)}>
<option value="">auto first vector</option>
{availableVectorTargets.map((target) => (
<option key={target.id} value={target.id}>
{target.name}
</option>
))}
</select>
</label>
<button type="button" onClick={onRunVectorIntersect}>
Run intersect
</button>
<h4 className="dataset-tool-heading">Buffer vector</h4>
<p className="dataset-tool-helper">Create a 25m buffer using the existing vector operation defaults.</p>
<div className="dataset-tool-action-row">
<button type="button" onClick={onRunVectorBuffer}>
Run buffer (25m)
</button>
</div>
</div>
<div className="dataset-tool-group">
<h4 className="dataset-tool-heading">Intersect vector</h4>
<p className="dataset-tool-helper">Intersect with another persisted vector dataset.</p>
<div className="dataset-tool-grid">
<label className="dataset-tool-field">
<span className="dataset-tool-label">Intersect target</span>
<select value={selectedIntersectTargetId} onChange={(event) => onSetSelectedIntersectTargetId(event.target.value)}>
<option value="">auto first vector</option>
{availableVectorTargets.map((target) => (
<option key={target.id} value={target.id}>
{target.name}
</option>
))}
</select>
<span className="dataset-tool-helper">Leave automatic to use the first available vector target.</span>
</label>
</div>
<div className="dataset-tool-action-row">
<button type="button" onClick={onRunVectorIntersect}>
Run intersect
</button>
</div>
</div>
</div>
)
+53
View File
@@ -2148,16 +2148,69 @@ button.entity-card {
background: #ffffff;
}
.dataset-tool-heading {
margin: 0;
font-size: 0.92rem;
line-height: 1.25;
}
.dataset-tool-helper {
margin: 0;
color: var(--muted);
font-size: 0.82rem;
line-height: 1.38;
overflow-wrap: anywhere;
}
.dataset-tool-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
gap: 0.52rem;
}
.dataset-tool-group label {
min-width: 0;
}
.dataset-tool-field {
display: grid;
gap: 0.22rem;
min-width: 0;
}
.dataset-tool-label {
color: var(--text);
font-size: 0.78rem;
font-weight: 800;
line-height: 1.25;
}
.dataset-tool-group input,
.dataset-tool-group select {
width: 100%;
min-width: 0;
}
.dataset-tool-error {
margin: 0;
border: 1px solid rgba(153, 27, 27, 0.24);
border-radius: 8px;
padding: 0.52rem 0.62rem;
background: rgba(153, 27, 27, 0.07);
color: var(--danger);
font-size: 0.82rem;
font-weight: 750;
line-height: 1.35;
overflow-wrap: anywhere;
}
.dataset-tool-action-row {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
align-items: center;
}
.dataset-tool-group button {
width: fit-content;
max-width: 100%;