Skip to content

Commit 088f894

Browse files
committed
Update TopToolbar.tsx
1 parent a7e89eb commit 088f894

1 file changed

Lines changed: 166 additions & 45 deletions

File tree

apps/geolibre-desktop/src/components/layout/TopToolbar.tsx

Lines changed: 166 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import {
3333
setMapillaryLabels,
3434
setEarthdataGisLabels,
3535
setOpenAerialMapLabels,
36+
setArcGisHubLabels,
37+
setOpenDataCatalogLabels,
3638
setHuggingFaceLabels,
3739
setSourceCoopLabels,
3840
setReverseGeocodeLabels,
@@ -45,6 +47,7 @@ import {
4547
PRECIPITATION_PLUGIN_ID,
4648
REVERSE_GEOCODE_PLUGIN_ID,
4749
EFFECTS_PLUGIN_ID,
50+
openRightPanel,
4851
} from "@geolibre/plugins";
4952
import { Button, cn, Input } from "@geolibre/ui";
5053
import {
@@ -82,7 +85,7 @@ import {
8285
ZoomIn,
8386
ZoomOut,
8487
} from "lucide-react";
85-
import { useEffect, useMemo, useRef, useState } from "react";
88+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
8689
import { useTranslation } from "react-i18next";
8790
import { createAppAPI, getPluginManager, usePluginRegistry } from "../../hooks/usePlugins";
8891
import { useConsentGatedActions } from "../../hooks/useConsentGatedActions";
@@ -172,10 +175,12 @@ interface TopToolbarProps {
172175
// instance — two would not coordinate their in-flight "open recent" aborts.
173176
projectFiles: ProjectFileActions;
174177
onOpenDiagnostics: () => void;
178+
onOpenProjectHistory: () => void;
175179
onToggleThemeMode: () => void;
176180
// Opens the Offline Basemap Extract panel, mounted in DesktopShell over the
177181
// map so it can stay non-modal (the map is interactive for drawing a bbox).
178182
onOpenBasemapExtract: () => void;
183+
viewer?: boolean;
179184
}
180185

181186
export function TopToolbar({
@@ -189,8 +194,10 @@ export function TopToolbar({
189194
collaboration,
190195
projectFiles,
191196
onOpenDiagnostics,
197+
onOpenProjectHistory,
192198
onToggleThemeMode,
193199
onOpenBasemapExtract,
200+
viewer = false,
194201
}: TopToolbarProps) {
195202
const { t, i18n } = useTranslation();
196203
// The reverse-geocode plugin lives in the framework-agnostic plugins package
@@ -299,6 +306,52 @@ export function TopToolbar({
299306
metaSource: t("openAerialMap.metaSource"),
300307
metaRaw: t("openAerialMap.metaRaw"),
301308
});
309+
setArcGisHubLabels({
310+
hint: t("arcgisHub.hint"),
311+
searchPlaceholder: t("arcgisHub.searchPlaceholder"),
312+
search: t("arcgisHub.search"),
313+
searchCurrentView: t("arcgisHub.searchCurrentView"),
314+
enterKeyword: t("arcgisHub.enterKeyword"),
315+
loadMore: t("arcgisHub.loadMore"),
316+
searching: t("arcgisHub.searching"),
317+
loadingMore: t("arcgisHub.loadingMore"),
318+
noResults: t("arcgisHub.noResults"),
319+
searchError: t("arcgisHub.searchError"),
320+
showing: (shown, total) => t("arcgisHub.showing", { shown, total }),
321+
noDescription: t("arcgisHub.noDescription"),
322+
add: t("arcgisHub.add"),
323+
adding: (title) => t("arcgisHub.adding", { title }),
324+
added: (title) => t("arcgisHub.added", { title }),
325+
addError: t("arcgisHub.addError"),
326+
zoom: t("arcgisHub.zoom"),
327+
download: t("arcgisHub.download"),
328+
preparing: (title) => t("arcgisHub.preparing", { title }),
329+
downloading: (completed, total, title) =>
330+
t("arcgisHub.downloading", { completed, total, title }),
331+
downloadStarted: (title) => t("arcgisHub.downloadStarted", { title }),
332+
downloadFirstLayer: (title, layerCount) =>
333+
t("arcgisHub.downloadFirstLayer", { title, layerCount }),
334+
downloadError: t("arcgisHub.downloadError"),
335+
details: t("arcgisHub.details"),
336+
});
337+
setOpenDataCatalogLabels({
338+
socrataHint: t("openDataCatalogs.socrataHint"),
339+
ckanHint: t("openDataCatalogs.ckanHint"),
340+
searchPlaceholder: (name) => t("openDataCatalogs.searchPlaceholder", { name }),
341+
search: t("openDataCatalogs.search"),
342+
enterKeyword: t("openDataCatalogs.enterKeyword"),
343+
loadMore: t("openDataCatalogs.loadMore"),
344+
searching: t("openDataCatalogs.searching"),
345+
noResults: t("openDataCatalogs.noResults"),
346+
showing: (shown, total) => t("openDataCatalogs.showing", { shown, total }),
347+
noDescription: t("openDataCatalogs.noDescription"),
348+
add: t("openDataCatalogs.add"),
349+
details: t("openDataCatalogs.details"),
350+
adding: (title) => t("openDataCatalogs.adding", { title }),
351+
added: (title) => t("openDataCatalogs.added", { title }),
352+
addError: t("openDataCatalogs.addError"),
353+
searchError: t("openDataCatalogs.searchError"),
354+
});
302355
setEarthdataGisLabels({
303356
hint: t("earthdataGis.hint"),
304357
searchPlaceholder: t("earthdataGis.searchPlaceholder"),
@@ -964,6 +1017,17 @@ export function TopToolbar({
9641017
),
9651018
);
9661019
const [addDataKind, setAddDataKind] = useState<AddDataKind | null>(null);
1020+
const [addDataTargetGroupId, setAddDataTargetGroupId] = useState<string | null>(null);
1021+
const addDataInitialLayerIdsRef = useRef<Set<string>>(new Set());
1022+
// Every path that opens the dialog outside the OPEN_ADD_DATA_EVENT listener
1023+
// (the Add Data menu, the command palette, the 3D-model button) is ungrouped,
1024+
// so it must drop any group target a previous open left behind — otherwise
1025+
// this session's layers would be swept into that stale, unrelated group when
1026+
// the dialog closes. Only the listener sets a target, and it sets both.
1027+
const openAddDataKind = useCallback((kind: AddDataKind) => {
1028+
setAddDataTargetGroupId(null);
1029+
setAddDataKind(kind);
1030+
}, []);
9671031
// PostgreSQL prefill (saved connection / clicked table) from the Browser panel.
9681032
const [addDataPostgres, setAddDataPostgres] = useState<OpenAddDataPostgres | undefined>(
9691033
undefined,
@@ -986,6 +1050,10 @@ export function TopToolbar({
9861050
// open a dialog whose backing service is compiled out.
9871051
if (detail?.kind && !masHidesDataSource(detail.kind)) {
9881052
setAddDataPostgres(detail.postgres);
1053+
setAddDataTargetGroupId(detail.groupId ?? null);
1054+
addDataInitialLayerIdsRef.current = new Set(
1055+
useAppStore.getState().layers.map((layer) => layer.id),
1056+
);
9891057
setAddDataKind(detail.kind);
9901058
}
9911059
};
@@ -1180,7 +1248,7 @@ export function TopToolbar({
11801248
id: `add.${kind}`,
11811249
title: t("toolbar.command.addLayer", { name: t(titleKey) }),
11821250
group: t("toolbar.commandGroup.addData"),
1183-
run: () => setAddDataKind(kind),
1251+
run: () => openAddDataKind(kind),
11841252
}),
11851253
),
11861254
{
@@ -1509,6 +1577,14 @@ export function TopToolbar({
15091577
icon: Crosshair,
15101578
run: () => setSetViewOpen(true),
15111579
},
1580+
{
1581+
id: "view.comments",
1582+
title: "View Comments",
1583+
group: t("toolbar.commandGroup.view"),
1584+
keywords: "comments review threads notes annotations pins",
1585+
icon: MessageSquare,
1586+
run: () => openRightPanel("comments"),
1587+
},
15121588
{
15131589
id: "view.theme",
15141590
title:
@@ -1629,10 +1705,27 @@ export function TopToolbar({
16291705
},
16301706
];
16311707

1708+
// The viewer preset hides every authoring menu, so the surfaces that reach
1709+
// those commands without a menu go with them: the command palette
1710+
// (Ctrl/Cmd+K) and the cheat sheet (?) are not mounted, and the Help menu
1711+
// drops its entries for them (see `viewer` on HelpMenu). Otherwise a
1712+
// `layout=viewer` embed would still answer Ctrl+N with "New Project", or
1713+
// overwrite the host's project on Ctrl+S — exactly what the read-only chrome
1714+
// promises it cannot do.
1715+
//
1716+
// The shortcut layer is narrowed rather than switched off, because the View
1717+
// menu *does* stay visible in this mode: `view.*` is camera and theme work
1718+
// only, so dropping its keys would leave those items clickable but silently
1719+
// keyless. Every command carrying a `shortcut` is either `view.*` or
1720+
// `project.*`, so this is the whole authoring keyboard surface.
1721+
const shortcutCommands = useMemo(
1722+
() => (viewer ? commands.filter((command) => command.id.startsWith("view.")) : commands),
1723+
[commands, viewer],
1724+
);
16321725
useGlobalShortcuts({
1633-
commands,
1634-
onOpenPalette: () => setCommandPaletteOpen(true),
1635-
onOpenShortcuts: () => setShortcutsOpen(true),
1726+
commands: shortcutCommands,
1727+
onOpenPalette: viewer ? undefined : () => setCommandPaletteOpen(true),
1728+
onOpenShortcuts: viewer ? undefined : () => setShortcutsOpen(true),
16361729
});
16371730

16381731
const toolbarButtonSize = compact ? "icon" : "sm";
@@ -1672,7 +1765,7 @@ export function TopToolbar({
16721765
<Map className="h-4 w-4" />
16731766
{showProjectInfo ? <span className="hidden sm:inline">{appTitle}</span> : null}
16741767
</span>
1675-
{isMenuVisible(uiProfile, "project") && (
1768+
{!viewer && isMenuVisible(uiProfile, "project") && (
16761769
<ProjectMenu
16771770
chrome={chrome}
16781771
collaborationEnabled={collaboration.enabled}
@@ -1681,11 +1774,13 @@ export function TopToolbar({
16811774
onOpenFromUrl={() => projectFiles.setProjectUrlDialogOpen(true)}
16821775
onOpenGallery={() => setGalleryDialogOpen(true)}
16831776
onImportQgisProject={() => void projectFiles.handleImportQgisProject()}
1777+
onImportArcgisProject={() => void projectFiles.handleImportArcgisProject()}
16841778
onOpenRecent={(path) => {
16851779
void projectFiles.handleOpenRecent(path).then((error) => {
16861780
if (error) projectFiles.setActionError(error);
16871781
});
16881782
}}
1783+
onOpenHistory={onOpenProjectHistory}
16891784
onSave={() => void projectFiles.handleSave()}
16901785
onSaveAs={() => void projectFiles.handleSaveAs()}
16911786
onDuplicate={() => projectFiles.handleDuplicate()}
@@ -1697,7 +1792,7 @@ export function TopToolbar({
16971792
onOpenOfflineBasemap={onOpenBasemapExtract}
16981793
/>
16991794
)}
1700-
{isMenuVisible(uiProfile, "edit") && (
1795+
{!viewer && isMenuVisible(uiProfile, "edit") && (
17011796
<EditMenu chrome={chrome} mapControllerRef={mapControllerRef} />
17021797
)}
17031798
{isMenuVisible(uiProfile, "view") && (
@@ -1741,20 +1836,20 @@ export function TopToolbar({
17411836
onSaveCurrentProject={projectFiles.handleSave}
17421837
onProjectCreated={resetRuntimeControlsForNewProject}
17431838
/>
1744-
{isMenuVisible(uiProfile, "addData") && (
1839+
{!viewer && isMenuVisible(uiProfile, "addData") && (
17451840
<AddDataMenu
17461841
chrome={chrome}
17471842
addLayer={addLayer}
17481843
osmPbfBusy={osmPbf.busy}
1749-
onSetAddDataKind={setAddDataKind}
1844+
onSetAddDataKind={openAddDataKind}
17501845
onAddGltfModel={() => {
17511846
setAddDataDeckVizKind("scenegraph");
1752-
setAddDataKind("deckgl-viz");
1847+
openAddDataKind("deckgl-viz");
17531848
}}
17541849
onOpenOsmPbfDialog={() => osmPbf.setDialogOpen(true)}
17551850
/>
17561851
)}
1757-
{isMenuVisible(uiProfile, "processing") && (
1852+
{!viewer && isMenuVisible(uiProfile, "processing") && (
17581853
<ProcessingMenu
17591854
chrome={chrome}
17601855
earthEnginePanel={panels.earthEngine}
@@ -1766,6 +1861,7 @@ export function TopToolbar({
17661861
{isMenuVisible(uiProfile, "controls") && (
17671862
<ControlsMenu
17681863
chrome={chrome}
1864+
viewer={viewer}
17691865
controlsVisible={controlsVisible}
17701866
panels={panels}
17711867
effectsActive={isActive(EFFECTS_PLUGIN_ID)}
@@ -1790,7 +1886,7 @@ export function TopToolbar({
17901886
onOpenRecordVideo={() => setRecordVideoOpen(true)}
17911887
/>
17921888
)}
1793-
{isMenuVisible(uiProfile, "plugins") && (
1889+
{!viewer && isMenuVisible(uiProfile, "plugins") && (
17941890
<PluginsMenu
17951891
chrome={chrome}
17961892
appApi={appApi}
@@ -1805,18 +1901,20 @@ export function TopToolbar({
18051901
{/* Top-level toolbar menus registered by built-in plugins via
18061902
app.registerToolbarMenu(); external plugin menus render after Help
18071903
(below). Renders nothing when none exist. */}
1808-
<PluginToolbarMenus chrome={chrome} placement="builtin" />
1809-
<SettingsDialog
1810-
buttonClassName={toolbarButtonClass}
1811-
buttonSize={toolbarButtonSize}
1812-
iconClassName={toolbarIconClassName}
1813-
mapControllerRef={mapControllerRef}
1814-
showLabels={showLabels}
1815-
onOpenManagePlugins={() => setManagePluginsOpen(true)}
1816-
profilePlugins={profilePlugins}
1817-
themeMode={themeMode}
1818-
onToggleThemeMode={onToggleThemeMode}
1819-
/>
1904+
{!viewer ? <PluginToolbarMenus chrome={chrome} placement="builtin" /> : null}
1905+
{!viewer ? (
1906+
<SettingsDialog
1907+
buttonClassName={toolbarButtonClass}
1908+
buttonSize={toolbarButtonSize}
1909+
iconClassName={toolbarIconClassName}
1910+
mapControllerRef={mapControllerRef}
1911+
showLabels={showLabels}
1912+
onOpenManagePlugins={() => setManagePluginsOpen(true)}
1913+
profilePlugins={profilePlugins}
1914+
themeMode={themeMode}
1915+
onToggleThemeMode={onToggleThemeMode}
1916+
/>
1917+
) : null}
18201918
{/* No plugin marketplace in the Mac App Store build (all its entry
18211919
points are hidden too; this keeps the install surface out of the
18221920
bundle). */}
@@ -1832,16 +1930,24 @@ export function TopToolbar({
18321930
onOpenChange={setPrintLayoutOpen}
18331931
mapControllerRef={mapControllerRef}
18341932
/>
1835-
<FieldCollectionDialog
1836-
open={fieldCollectionOpen}
1837-
onOpenChange={setFieldCollectionOpen}
1838-
mapControllerRef={mapControllerRef}
1839-
/>
1840-
<GpsTrackingDialog
1841-
open={gpsTrackingOpen}
1842-
onOpenChange={setGpsTrackingOpen}
1843-
mapControllerRef={mapControllerRef}
1844-
/>
1933+
{/* Field Collection and GPS Tracking add features and layers to the
1934+
project, so they follow the Controls menu entries that open them out
1935+
of the read-only viewer preset. Record Tour and Record Video below
1936+
only read the map, so they stay. */}
1937+
{!viewer && (
1938+
<FieldCollectionDialog
1939+
open={fieldCollectionOpen}
1940+
onOpenChange={setFieldCollectionOpen}
1941+
mapControllerRef={mapControllerRef}
1942+
/>
1943+
)}
1944+
{!viewer && (
1945+
<GpsTrackingDialog
1946+
open={gpsTrackingOpen}
1947+
onOpenChange={setGpsTrackingOpen}
1948+
mapControllerRef={mapControllerRef}
1949+
/>
1950+
)}
18451951
<RecordTourDialog
18461952
open={recordTourOpen}
18471953
onOpenChange={setRecordTourOpen}
@@ -1896,6 +2002,7 @@ export function TopToolbar({
18962002
{isMenuVisible(uiProfile, "help") && (
18972003
<HelpMenu
18982004
chrome={chrome}
2005+
viewer={viewer}
18992006
diagnosticsErrorCount={diagnosticsErrorCount}
19002007
onOpenCommandPalette={() => setCommandPaletteOpen(true)}
19012008
onOpenShortcuts={() => setShortcutsOpen(true)}
@@ -1909,15 +2016,25 @@ export function TopToolbar({
19092016
)}
19102017
{/* External plugin toolbar menus render after Help so third-party menus
19112018
sit at the end of the banner, past the built-in menus. */}
1912-
<PluginToolbarMenus chrome={chrome} placement="external" />
2019+
{!viewer ? <PluginToolbarMenus chrome={chrome} placement="external" /> : null}
19132020
<AddDataDialog
19142021
kind={addDataKind}
19152022
mapControllerRef={mapControllerRef}
19162023
initialDeckVizKind={addDataDeckVizKind}
19172024
initialPostgres={addDataPostgres}
19182025
onOpenChange={(open: boolean) => {
19192026
if (!open) {
2027+
if (addDataTargetGroupId) {
2028+
const state = useAppStore.getState();
2029+
const addedIds = state.layers
2030+
.filter((layer) => !addDataInitialLayerIdsRef.current.has(layer.id))
2031+
.map((layer) => layer.id);
2032+
if (addedIds.length > 0) {
2033+
state.moveLayersToGroup(addedIds, addDataTargetGroupId);
2034+
}
2035+
}
19202036
setAddDataKind(null);
2037+
setAddDataTargetGroupId(null);
19212038
setAddDataDeckVizKind(undefined);
19222039
setAddDataPostgres(undefined);
19232040
}
@@ -1933,16 +2050,20 @@ export function TopToolbar({
19332050
renderTrigger={false}
19342051
onOpenChange={setAboutOpen}
19352052
/>
1936-
<CommandPalette
1937-
open={commandPaletteOpen}
1938-
commands={commands}
1939-
onOpenChange={setCommandPaletteOpen}
1940-
/>
1941-
<KeyboardShortcutsDialog
1942-
open={shortcutsOpen}
1943-
commands={commands}
1944-
onOpenChange={setShortcutsOpen}
1945-
/>
2053+
{!viewer && (
2054+
<CommandPalette
2055+
open={commandPaletteOpen}
2056+
commands={commands}
2057+
onOpenChange={setCommandPaletteOpen}
2058+
/>
2059+
)}
2060+
{!viewer && (
2061+
<KeyboardShortcutsDialog
2062+
open={shortcutsOpen}
2063+
commands={commands}
2064+
onOpenChange={setShortcutsOpen}
2065+
/>
2066+
)}
19462067
<div className="ms-auto flex min-w-0 items-center gap-1.5 text-xs text-muted-foreground">
19472068
<Button
19482069
aria-label={

0 commit comments

Comments
 (0)