Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9317111
feat(processing): add spatial workflow canvas
giswqs Aug 17, 2026
180ecb5
Address Claude review feedback
giswqs Aug 17, 2026
5c079cf
Address CodeRabbit review feedback
giswqs Aug 17, 2026
022d8b1
Address review feedback
giswqs Aug 17, 2026
83bf41f
Address review feedback
giswqs Aug 17, 2026
931b5c1
Document the decorative pipeline node name
giswqs Aug 18, 2026
88ea7c3
Keep non-Latin model names in the exported filename
giswqs Aug 18, 2026
e0618f0
Scope the wider processing dialog to the Models tab
giswqs Aug 18, 2026
1a14d52
Require a string pipeline name on import
giswqs Aug 18, 2026
d0a2a3c
feat(processing): rework the canvas into an ArcGIS-style Model Builder
giswqs Aug 18, 2026
b32e8c1
fix(processing): make Whitebox raster nodes actually runnable
giswqs Aug 18, 2026
8806ec9
Address review feedback
giswqs Aug 18, 2026
bfef5ef
Address review feedback
giswqs Aug 18, 2026
3538385
Address review feedback
giswqs Aug 18, 2026
9656354
Address review feedback
giswqs Aug 18, 2026
9835360
Merge origin/main into fix/issue-1982-spatial-workflow-modeler
giswqs Aug 20, 2026
3be06dc
Address review feedback and move Model Builder up a level
giswqs Aug 20, 2026
e63744b
Address Claude review feedback
giswqs Aug 20, 2026
70b7ac8
Confirm before discarding an unsaved model
giswqs Aug 20, 2026
d20c85b
Wrap the Arrange layout, resize the log pane, keep intermediate results
giswqs Aug 20, 2026
1683f26
Name kept results after their tool, and let the panel minimize
giswqs Aug 20, 2026
b71ca2e
Fit the Model Builder to small screens
giswqs Aug 20, 2026
ef8ebbd
Label the ports on a multi-input node
giswqs Aug 20, 2026
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
43 changes: 37 additions & 6 deletions apps/geolibre-desktop/src/components/layout/DesktopShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,16 +263,30 @@
}),
);

const ModelBuilderDialog = lazy(() =>
import("../processing/ModelBuilderDialog")
const BatchToolsDialog = lazy(() =>
import("../processing/BatchToolsDialog")
.then((module) => ({
default: module.ModelBuilderDialog,
default: module.BatchToolsDialog,
}))
.catch((error) => {
// Same chunk-load fallback rationale as ProcessingDialog above.
console.error("Failed to load ModelBuilderDialog", error);
console.error("Failed to load BatchToolsDialog", error);
const Fallback = (() =>
null) as unknown as typeof import("../processing/ModelBuilderDialog").ModelBuilderDialog;
null) as unknown as typeof import("../processing/BatchToolsDialog").BatchToolsDialog;
return { default: Fallback };
}),
);

const ModelBuilderPanel = lazy(() =>
import("../processing/model-builder/ModelBuilderPanel")
.then((module) => ({
default: module.ModelBuilderPanel,
}))
.catch((error) => {
// Same chunk-load fallback rationale as ProcessingDialog above.
console.error("Failed to load ModelBuilderPanel", error);
const Fallback = (() =>
null) as unknown as typeof import("../processing/model-builder/ModelBuilderPanel").ModelBuilderPanel;
return { default: Fallback };
}),
);
Expand Down Expand Up @@ -1764,7 +1778,7 @@
disposed = true;
unlisten?.();
};
}, [

Check warning on line 1781 in apps/geolibre-desktop/src/components/layout/DesktopShell.tsx

View workflow job for this annotation

GitHub Actions / Build and test

React Hook useEffect has a missing dependency: 't'. Either include it or remove the dependency array
clearDropMessageLater,
finishDrop,
addDroppedRasters,
Expand Down Expand Up @@ -1912,7 +1926,7 @@
clearDropMessageLater();
}
},
[

Check warning on line 1929 in apps/geolibre-desktop/src/components/layout/DesktopShell.tsx

View workflow job for this annotation

GitHub Actions / Build and test

React Hook useCallback has a missing dependency: 't'. Either include it or remove the dependency array
clearDropMessageLater,
finishDrop,
addDroppedRasters,
Expand Down Expand Up @@ -2456,6 +2470,23 @@
>
<FloatingPanels />
</SectionErrorBoundary>
{/* Mounted inside the map area (like FloatingPanels) so the canvas
floats over the map and drag-clamps to it, not to the whole
window — the user keeps their layers in view while building. */}
<SectionErrorBoundary label="Model Builder" displayName={t("shell.section.modelBuilder")}>
<Suspense fallback={null}>
<ModelBuilderPanel
mapControllerRef={mapControllerRef}
onAddRaster={async (bytes, name, fileName) => {
// Same Uint8Array -> BlobPart cast as ProcessingDialog below.
const file = new File([bytes as BlobPart], fileName ?? `${name}.tif`, {
type: "image/tiff",
});
await addRasterToMap(createAppAPI(mapControllerRef), file, { name });
}}
/>
</Suspense>
</SectionErrorBoundary>
{/* Mounted here (inside the map area, like FloatingPanels) so the
selection panels anchor to the map canvas's top-left corner and
drag-clamp to the map, not the whole window (#1314). */}
Expand Down Expand Up @@ -2737,7 +2768,7 @@
<NetworkToolsDialog mapControllerRef={mapControllerRef} />
</Suspense>
<Suspense fallback={null}>
<ModelBuilderDialog mapControllerRef={mapControllerRef} />
<BatchToolsDialog mapControllerRef={mapControllerRef} />
</Suspense>
<Suspense fallback={null}>
<StatisticsToolsDialog mapControllerRef={mapControllerRef} />
Expand Down
12 changes: 11 additions & 1 deletion apps/geolibre-desktop/src/components/layout/TopToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import {
Save,
Sparkles,
Sun,
Layers,
Workflow,
Wrench,
ZoomIn,
Expand Down Expand Up @@ -957,6 +958,7 @@ export function TopToolbar({
const setVectorToolOpen = useAppStore((s) => s.setVectorToolOpen);
const setGeocodeOpen = useAppStore((s) => s.setGeocodeOpen);
const setModelBuilderOpen = useAppStore((s) => s.setModelBuilderOpen);
const setBatchToolsOpen = useAppStore((s) => s.setBatchToolsOpen);
const setStyleManagerOpen = useAppStore((s) => s.setStyleManagerOpen);
const setRasterToolOpen = useAppStore((s) => s.setRasterToolOpen);
const setSegmentationOpen = useAppStore((s) => s.setSegmentationOpen);
Expand Down Expand Up @@ -1433,10 +1435,18 @@ export function TopToolbar({
id: "proc.modelBuilder",
title: t("toolbar.command.modelBuilder"),
group: t("toolbar.commandGroup.processing"),
keywords: "batch model pipeline chain modeler workflow graphical",
keywords: "model builder pipeline chain modeler workflow graph canvas node",
icon: Workflow,
run: () => setModelBuilderOpen(true),
},
{
id: "proc.batchTools",
title: t("toolbar.command.batchTools"),
group: t("toolbar.commandGroup.processing"),
keywords: "batch bulk many layers repeat vector tool",
icon: Layers,
run: () => setBatchToolsOpen(true),
},
// The Mac App Store build omits AI Segmentation: it is sidecar-only (the
// App Sandbox forbids the sidecar) and has no client-side fallback.
...(IS_MAS_BUILD
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export function ProcessingMenu({
const setVectorToolOpen = useAppStore((s) => s.setVectorToolOpen);
const setStatisticsToolOpen = useAppStore((s) => s.setStatisticsToolOpen);
const setGeocodeOpen = useAppStore((s) => s.setGeocodeOpen);
const setBatchToolsOpen = useAppStore((s) => s.setBatchToolsOpen);
const setModelBuilderOpen = useAppStore((s) => s.setModelBuilderOpen);
const setRasterToolOpen = useAppStore((s) => s.setRasterToolOpen);
const setSegmentationOpen = useAppStore((s) => s.setSegmentationOpen);
Expand Down Expand Up @@ -92,8 +93,8 @@ export function ProcessingMenu({

// Section visibility, so dividers never render with nothing on one side when a
// UI profile (or mobile) hides whole sections. `showGeolibreTools` are the
// client tool submenus; `showGeolibreActions` are geocode/model-builder/
// segmentation below the in-submenu divider.
// client tool submenus; `showGeolibreActions` are geocode/batch/segmentation
// below the in-submenu divider.
const showGeolibreTools =
(!mobile && show("processing.conversion")) ||
show("processing.vector") ||
Expand All @@ -102,12 +103,13 @@ export function ProcessingMenu({
(!mobile && show("processing.raster"));
const showGeolibreActions =
show("processing.geocode") ||
show("processing.modelBuilder") ||
show("processing.batchTools") ||
(!mobile && show("processing.segmentation")) ||
show("processing.objectDetection") ||
show("processing.segmentEverything");
const showGeolibre = showGeolibreTools || showGeolibreActions;
const showWorkspacesOrServices =
show("processing.modelBuilder") ||
show("processing.history") ||
show("processing.sqlWorkspace") ||
show("processing.pythonConsole") ||
Expand Down Expand Up @@ -498,9 +500,9 @@ export function ProcessingMenu({
{t("toolbar.item.geocode")}
</DropdownMenuItem>
)}
{show("processing.modelBuilder") && (
<DropdownMenuItem onSelect={() => setModelBuilderOpen(true)}>
{t("toolbar.item.modelBuilder")}
{show("processing.batchTools") && (
<DropdownMenuItem onSelect={() => setBatchToolsOpen(true)}>
{t("toolbar.item.batchTools")}
</DropdownMenuItem>
)}
{!mobile && show("processing.segmentation") && (
Expand Down Expand Up @@ -528,6 +530,16 @@ export function ProcessingMenu({
{/* Divide the tool-category submenus (Whitebox, GeoLibre) from the
workspaces and consoles below. Only when both sides are present. */}
{(showWhitebox || showGeolibre) && showWorkspacesOrServices && <DropdownMenuSeparator />}
{/* Model Builder sits at the top level rather than inside the GeoLibre
Toolbox submenu: it is a canvas that composes tools from every
toolbox (Whitebox raster and GeoLibre vector alike), so filing it
under one of them would misdescribe its reach. It heads the
workspaces block with its SQL/Python/notebook/dashboard siblings. */}
{show("processing.modelBuilder") && (
<DropdownMenuItem onSelect={() => setModelBuilderOpen(true)}>
{t("toolbar.item.modelBuilder")}
</DropdownMenuItem>
)}
{show("processing.sqlWorkspace") && (
<DropdownMenuItem onSelect={() => setSqlWorkspaceOpen(true)}>
{t("toolbar.command.sqlWorkspace")}
Expand Down
Loading
Loading