This commit is contained in:
2026-05-26 17:01:23 +05:30
parent f83724951f
commit 4ee278a47e
2 changed files with 116 additions and 46 deletions

View File

@@ -565,6 +565,13 @@ function AppShell({
const [showInScope, setShowInScope] = useState<boolean>(true);
const [visibilityOpen, setVisibilityOpen] = useState<boolean>(false);
const [allVideoNames, setAllVideoNames] = useState<string[]>([]);
// Some Save dialogs (notably GTK on Linux) hide the file-filter dropdown,
// so users couldn't tell they were even picking a format. We surface the
// choice directly in the side panel — this is the format the next Export
// click will use.
const [exportFormat, setExportFormat] = useState<
"source" | "geojson" | "kml" | "csv"
>("source");
const DUP_EPS_KEY = "kml_map_tool.dup_eps_m";
const [dupEpsM, setDupEpsM] = useState<number>(() => {
if (typeof window === "undefined") return 3;
@@ -3103,31 +3110,36 @@ function AppShell({
async function handleExport() {
if (busy) return;
try {
// The dropdown next to the button is the source of truth for format.
// We seed the Save dialog with the matching extension so the user
// doesn't have to hunt for the file-filter menu inside it.
const extByFmt: Record<typeof exportFormat, string> = {
source: "json",
geojson: "geojson",
kml: "kml",
csv: "csv",
};
const wantExt = extByFmt[exportFormat];
const picked = await save({
filters: [
{ name: "Source JSON (round-trip)", extensions: ["json"] },
{ name: "GeoJSON", extensions: ["geojson"] },
{ name: "KML", extensions: ["kml"] },
{ name: "CSV", extensions: ["csv"] },
],
defaultPath: `assets.${wantExt}`,
filters: [{ name: exportFormat.toUpperCase(), extensions: [wantExt] }],
});
if (typeof picked !== "string") return;
const lower = picked.toLowerCase();
const fmt = lower.endsWith(".kml")
? "kml"
: lower.endsWith(".csv")
? "csv"
: lower.endsWith(".json")
? "source"
: "geojson";
// Auto-append the chosen extension if the user typed a bare name.
let outPath = picked;
if (!outPath.toLowerCase().endsWith(`.${wantExt}`)) {
outPath = `${outPath}.${wantExt}`;
}
setBusy(true);
const count = await invoke<number>("export_assets", {
path: picked,
format: fmt,
path: outPath,
format: exportFormat,
});
setStatus(`Exported ${count} asset(s) to ${picked}`);
setStatus(`Exported ${count} asset(s) to ${outPath}`);
showToast(`Exported ${count} asset(s) (${exportFormat})`);
} catch (e) {
setStatus(`Export failed: ${e}`);
showToast(`Export failed: ${e}`);
} finally {
setBusy(false);
}
@@ -6165,15 +6177,40 @@ function AppShell({
</button>
</div>
)}
<button
className="primary-btn"
style={{ marginTop: 6, background: "#2c3e50" }}
onClick={handleExport}
disabled={busy}
title="Export all live assets. Pick .json for round-trip (re-importable), .geojson / .kml for GIS, .csv for spreadsheet."
<div
style={{
display: "flex",
gap: 6,
marginTop: 6,
alignItems: "stretch",
}}
>
Export data
</button>
<select
value={exportFormat}
onChange={(e) =>
setExportFormat(
e.target.value as "source" | "geojson" | "kml" | "csv",
)
}
disabled={busy}
title="File format for the next export"
style={{ fontSize: 12, padding: "2px 4px" }}
>
<option value="source">JSON (round-trip + bbox)</option>
<option value="geojson">GeoJSON</option>
<option value="kml">KML</option>
<option value="csv">CSV</option>
</select>
<button
className="primary-btn"
style={{ background: "#2c3e50", flex: 1 }}
onClick={handleExport}
disabled={busy}
title="Export all live assets in the selected format. JSON is the only re-importable format and includes bbox overlays."
>
Export data
</button>
</div>
<h3 style={{ marginTop: 16 }}>Layers</h3>
<div className="layer-toggles">
{(