Skip to content

Commit 83bb2e6

Browse files
feat(map): WIP GeoJSON overlay, cross-project map mgmt, download history (#129)
In-progress work preserved as draft PR. Includes GeoJSONOverlay component, useAllMaps hook, DownloadHistory screen, and MapScreen authoring/mgmt wiring. Not yet CI-green or review-complete. Refs #129
1 parent 7e01946 commit 83bb2e6

11 files changed

Lines changed: 979 additions & 35 deletions

File tree

src/hooks/useAllMaps.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { useQuery } from '@tanstack/react-query';
2+
3+
import type { Project, SavedMap } from '@/lib/db';
4+
import { getDb } from '@/lib/db';
5+
6+
export const ALL_MAPS_QUERY_KEY = ['maps', 'all'] as const;
7+
8+
export interface SavedMapWithProject extends SavedMap {
9+
/** Origin project name, or `null` if the project no longer exists. */
10+
originProjectName: string | null;
11+
}
12+
13+
/**
14+
* Query every saved map across all projects, sorted by `updatedAt` desc.
15+
* Each map is joined with its origin project name for cross-project display.
16+
*
17+
* Used by the "All projects" view in MapScreen (#16).
18+
*/
19+
export function useAllMaps() {
20+
return useQuery({
21+
queryKey: ALL_MAPS_QUERY_KEY,
22+
queryFn: async (): Promise<SavedMapWithProject[]> => {
23+
const db = getDb();
24+
const [maps, projects] = await Promise.all([
25+
db.maps.toArray(),
26+
db.projects.toArray(),
27+
]);
28+
const projectMap = new Map<string, Project>();
29+
for (const project of projects) {
30+
projectMap.set(project.localId, project);
31+
}
32+
const enriched = maps.map((map) => ({
33+
...map,
34+
originProjectName: projectMap.get(map.projectLocalId)?.name ?? null,
35+
}));
36+
return enriched.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
37+
},
38+
});
39+
}

src/hooks/useMaps.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
22

3+
import { ALL_MAPS_QUERY_KEY } from '@/hooks/useAllMaps';
34
import type { SavedMap } from '@/lib/db';
45
import { getDb } from '@/lib/db';
56
import type { DownloadProgress } from '@/lib/map/smp-download';
@@ -39,6 +40,7 @@ export function useCreateMap() {
3940
void queryClient.invalidateQueries({
4041
queryKey: mapsQueryKey(map.projectLocalId),
4142
});
43+
void queryClient.invalidateQueries({ queryKey: ALL_MAPS_QUERY_KEY });
4244
},
4345
});
4446
}
@@ -56,6 +58,7 @@ export function useRenameMap(projectLocalId: string | null) {
5658
queryKey: mapsQueryKey(projectLocalId),
5759
});
5860
void queryClient.invalidateQueries({ queryKey: ['map', mapId] });
61+
void queryClient.invalidateQueries({ queryKey: ALL_MAPS_QUERY_KEY });
5962
},
6063
});
6164
}
@@ -92,6 +95,7 @@ export function useDeleteMap(projectLocalId: string | null) {
9295
});
9396
void queryClient.invalidateQueries({ queryKey: ['map', mapId] });
9497
void queryClient.invalidateQueries({ queryKey: ['projects'] });
98+
void queryClient.invalidateQueries({ queryKey: ALL_MAPS_QUERY_KEY });
9599
},
96100
});
97101
}
@@ -143,11 +147,13 @@ export function useDownloadMap() {
143147
queryKey: mapsQueryKey(map.projectLocalId),
144148
});
145149
void queryClient.invalidateQueries({ queryKey: ['map', map.id] });
150+
void queryClient.invalidateQueries({ queryKey: ALL_MAPS_QUERY_KEY });
146151
},
147152
onError: (_error, { map }) => {
148153
void queryClient.invalidateQueries({
149154
queryKey: mapsQueryKey(map.projectLocalId),
150155
});
156+
void queryClient.invalidateQueries({ queryKey: ALL_MAPS_QUERY_KEY });
151157
},
152158
});
153159
}

src/i18n/messages/en.json

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,6 +1217,27 @@
12171217
"map.download.unknownError": {
12181218
"defaultMessage": "Unknown error"
12191219
},
1220+
"map.history.downloaded": {
1221+
"defaultMessage": "Downloaded"
1222+
},
1223+
"map.history.empty": {
1224+
"defaultMessage": "No downloads yet"
1225+
},
1226+
"map.history.retry": {
1227+
"defaultMessage": "Retry"
1228+
},
1229+
"map.history.size": {
1230+
"defaultMessage": "Size: {size}"
1231+
},
1232+
"map.history.statusError": {
1233+
"defaultMessage": "Failed"
1234+
},
1235+
"map.history.statusReady": {
1236+
"defaultMessage": "Ready"
1237+
},
1238+
"map.history.title": {
1239+
"defaultMessage": "Download history"
1240+
},
12201241
"map.nameDialog.cancel": {
12211242
"defaultMessage": "Cancel"
12221243
},
@@ -1241,6 +1262,36 @@
12411262
"map.nameDialog.title": {
12421263
"defaultMessage": "Save map"
12431264
},
1265+
"map.overlay.clear": {
1266+
"defaultMessage": "Clear all overlays"
1267+
},
1268+
"map.overlay.drop": {
1269+
"defaultMessage": "Drop a .geojson file to show it as a reference layer"
1270+
},
1271+
"map.overlay.dropActive": {
1272+
"defaultMessage": "Drop the file to add the overlay"
1273+
},
1274+
"map.overlay.empty": {
1275+
"defaultMessage": "No reference layers added"
1276+
},
1277+
"map.overlay.hidden": {
1278+
"defaultMessage": "Hidden"
1279+
},
1280+
"map.overlay.invalid": {
1281+
"defaultMessage": "Could not read “{name}”. Not valid GeoJSON."
1282+
},
1283+
"map.overlay.remove": {
1284+
"defaultMessage": "Remove overlay"
1285+
},
1286+
"map.overlay.title": {
1287+
"defaultMessage": "Reference layers"
1288+
},
1289+
"map.overlay.tooLarge": {
1290+
"defaultMessage": "“{name}” is {size}. Large files may slow down the editor."
1291+
},
1292+
"map.overlay.toggle": {
1293+
"defaultMessage": "Toggle overlay visibility"
1294+
},
12441295
"map.saved.activeError": {
12451296
"defaultMessage": "Could not update active map. Please try again."
12461297
},
@@ -1274,6 +1325,9 @@
12741325
"map.saved.noProjectLink": {
12751326
"defaultMessage": "Go to Home"
12761327
},
1328+
"map.saved.originProject": {
1329+
"defaultMessage": "Origin project"
1330+
},
12771331
"map.saved.removeActive": {
12781332
"defaultMessage": "Remove active"
12791333
},
@@ -1319,6 +1373,12 @@
13191373
"map.saved.untitledProject": {
13201374
"defaultMessage": "Untitled Project"
13211375
},
1376+
"map.scope.allProjects": {
1377+
"defaultMessage": "All projects"
1378+
},
1379+
"map.scope.thisProject": {
1380+
"defaultMessage": "This project"
1381+
},
13221382
"map.stylePicker.customMode": {
13231383
"defaultMessage": "Custom URL"
13241384
},

src/i18n/messages/es.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,13 @@
374374
"map.download.storageWarning": "Espacio de almacenamiento insuficiente. {available} disponibles, {estimated} estimados.",
375375
"map.download.tryAnyway": "Intentar de todos modos",
376376
"map.download.unknownError": "Error desconocido",
377+
"map.history.downloaded": "Descargado",
378+
"map.history.empty": "Aún no hay descargas",
379+
"map.history.retry": "Reintentar",
380+
"map.history.size": "Tamaño: {size}",
381+
"map.history.statusError": "Fallido",
382+
"map.history.statusReady": "Listo",
383+
"map.history.title": "Historial de descargas",
377384
"map.nameDialog.cancel": "Cancelar",
378385
"map.nameDialog.description": "Guarde esta configuración de mapa como borrador. Luego puede descargarlo como un archivo .smp.",
379386
"map.nameDialog.nameLabel": "Nombre del mapa",
@@ -382,6 +389,16 @@
382389
"map.nameDialog.saveDraft": "Guardar borrador",
383390
"map.nameDialog.saveError": "No se pudo guardar el mapa. Inténtelo de nuevo.",
384391
"map.nameDialog.title": "Guardar mapa",
392+
"map.overlay.clear": "Limpiar todas las capas",
393+
"map.overlay.drop": "Suelte un archivo .geojson para mostrarlo como capa de referencia",
394+
"map.overlay.dropActive": "Suelte el archivo para añadir la capa",
395+
"map.overlay.empty": "No hay capas de referencia añadidas",
396+
"map.overlay.hidden": "Oculto",
397+
"map.overlay.invalid": "No se pudo leer “{name}”. GeoJSON no válido.",
398+
"map.overlay.remove": "Quitar capa",
399+
"map.overlay.title": "Capas de referencia",
400+
"map.overlay.tooLarge": "“{name}” es {size}. Los archivos grandes pueden ralentizar el editor.",
401+
"map.overlay.toggle": "Alternar visibilidad de la capa",
385402
"map.saved.activeError": "No se pudo actualizar el mapa activo. Inténtelo de nuevo.",
386403
"map.saved.canvasAria": "Lienzo de autoría de mapa",
387404
"map.saved.closeSettings": "Cerrar configuración del mapa",
@@ -393,6 +410,7 @@
393410
"map.saved.empty": "Aún no hay mapas guardados",
394411
"map.saved.noProject": "Seleccione un proyecto desde Inicio para crear mapas",
395412
"map.saved.noProjectLink": "Ir a Inicio",
413+
"map.saved.originProject": "Proyecto de origen",
396414
"map.saved.removeActive": "Quitar activo",
397415
"map.saved.rename": "Renombrar",
398416
"map.saved.renameDialog.description": "Ingrese un nuevo nombre para este mapa guardado.",
@@ -408,6 +426,8 @@
408426
"map.saved.statusReady": "Listo",
409427
"map.saved.title": "Mapas guardados",
410428
"map.saved.untitledProject": "Proyecto sin título",
429+
"map.scope.allProjects": "Todos los proyectos",
430+
"map.scope.thisProject": "Este proyecto",
411431
"map.stylePicker.customMode": "URL personalizada",
412432
"map.stylePicker.customUrlHostnameWarning": "Este proveedor de teselas puede no estar disponible para descargas offline. Solo los proveedores compatibles funcionan con la generación de paquetes de mapas.",
413433
"map.stylePicker.customUrlLabel": "URL personalizada",

src/i18n/messages/pt.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,13 @@
374374
"map.download.storageWarning": "Espaço de armazenamento insuficiente. {available} disponível, {estimated} estimado.",
375375
"map.download.tryAnyway": "Tentar mesmo assim",
376376
"map.download.unknownError": "Erro desconhecido",
377+
"map.history.downloaded": "Baixado",
378+
"map.history.empty": "Ainda não há downloads",
379+
"map.history.retry": "Tentar novamente",
380+
"map.history.size": "Tamanho: {size}",
381+
"map.history.statusError": "Falhou",
382+
"map.history.statusReady": "Pronto",
383+
"map.history.title": "Histórico de downloads",
377384
"map.nameDialog.cancel": "Cancelar",
378385
"map.nameDialog.description": "Salve esta configuração de mapa como rascunho. Depois você pode baixá-lo como um arquivo .smp.",
379386
"map.nameDialog.nameLabel": "Nome do mapa",
@@ -382,6 +389,16 @@
382389
"map.nameDialog.saveDraft": "Salvar rascunho",
383390
"map.nameDialog.saveError": "Não foi possível salvar o mapa. Tente novamente.",
384391
"map.nameDialog.title": "Salvar mapa",
392+
"map.overlay.clear": "Limpar todas as camadas",
393+
"map.overlay.drop": "Solte um arquivo .geojson para mostrá-lo como camada de referência",
394+
"map.overlay.dropActive": "Solte o arquivo para adicionar a camada",
395+
"map.overlay.empty": "Nenhuma camada de referência adicionada",
396+
"map.overlay.hidden": "Oculto",
397+
"map.overlay.invalid": "Não foi possível ler “{name}”. GeoJSON inválido.",
398+
"map.overlay.remove": "Remover camada",
399+
"map.overlay.title": "Camadas de referência",
400+
"map.overlay.tooLarge": "“{name}” tem {size}. Arquivos grandes podem lentear o editor.",
401+
"map.overlay.toggle": "Alternar visibilidade da camada",
385402
"map.saved.activeError": "Não foi possível atualizar o mapa ativo. Tente novamente.",
386403
"map.saved.canvasAria": "Tela de autoria de mapa",
387404
"map.saved.closeSettings": "Fechar configurações do mapa",
@@ -393,6 +410,7 @@
393410
"map.saved.empty": "Ainda não há mapas salvos",
394411
"map.saved.noProject": "Selecione um projeto no Início para criar mapas",
395412
"map.saved.noProjectLink": "Ir para o Início",
413+
"map.saved.originProject": "Projeto de origem",
396414
"map.saved.removeActive": "Remover ativo",
397415
"map.saved.rename": "Renomear",
398416
"map.saved.renameDialog.description": "Insira um novo nome para este mapa salvo.",
@@ -408,6 +426,8 @@
408426
"map.saved.statusReady": "Pronto",
409427
"map.saved.title": "Mapas salvos",
410428
"map.saved.untitledProject": "Projeto sem título",
429+
"map.scope.allProjects": "Todos os projetos",
430+
"map.scope.thisProject": "Este projeto",
411431
"map.stylePicker.customMode": "URL personalizada",
412432
"map.stylePicker.customUrlHostnameWarning": "Este provedor de mosaicos pode não estar disponível para downloads offline. Somente provedores compatíveis funcionam com a geração de pacotes de mapas.",
413433
"map.stylePicker.customUrlLabel": "URL personalizada",

0 commit comments

Comments
 (0)