Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import {
} from "@geolibre/ui";
import { useRef } from "react";
import { useTranslation } from "react-i18next";
import type { ProjectFileActions } from "../../../hooks/useProjectFileActions";
import {
LARGE_EMBED_WARNING_BYTES,
type ProjectFileActions,
} from "../../../hooks/useProjectFileActions";
import { SaveTemplateDialog } from "../SaveTemplateDialog";

interface ProjectFileDialogsProps {
Expand Down Expand Up @@ -250,6 +253,16 @@ export function ProjectFileDialogs({ projectFiles }: ProjectFileDialogsProps) {
)}
</DialogDescription>
</DialogHeader>
{(projectFiles.embedVectorDataPrompt?.bytes ?? 0) >= LARGE_EMBED_WARNING_BYTES ? (
<p
role="alert"
className="rounded-md border border-amber-500/40 bg-amber-500/10 p-3 text-sm text-amber-700 dark:text-amber-300"
>
{t("toolbar.item.embedVectorLargeWarning", {
size: formatByteSize(projectFiles.embedVectorDataPrompt?.bytes ?? 0),
})}
</p>
) : null}
<div className="flex justify-end gap-2">
<Button
variant="outline"
Expand Down
55 changes: 46 additions & 9 deletions apps/geolibre-desktop/src/hooks/useProjectFileActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
serializeProject,
useAppStore,
type GeoLibreLayer,
type GeoLibreProject,
} from "@geolibre/core";
import {
addArcGISLayer,
Expand Down Expand Up @@ -58,6 +59,17 @@ export interface CredentialStripPrompt {
resolve: (choice: "strip" | "keep" | "cancel") => void;
}

/**
* Embedded-data size above which the save prompt warns that the project will be
* slow (or impossible) to reopen and points at PMTiles/FlatGeobuf instead.
*
* Embedded GeoJSON is parsed and held in memory in full when the project is
* reopened, so a browser tab can run out of memory and drop the layers with no
* error (GeoLibre#1829). 50 MB is well under that cliff while leaving ordinary
* projects unbothered.
*/
export const LARGE_EMBED_WARNING_BYTES = 50 * 1024 * 1024;

/**
* A pending "embed local vector data?" prompt, shown on the web when saving a
* project that has local-file Add Vector Layer layers whose data would
Expand Down Expand Up @@ -654,16 +666,44 @@ export function useProjectFileActions(mapControllerRef: MapControllerRef) {
comments: state.comments,
metadata: state.metadata,
});
// The serialized text is deliberately not returned: every caller
// re-serializes after redacting credentials, so producing it here doubled
// the peak memory of a save for a project embedding large vector layers
// (GeoLibre#1829).
return {
project,
defaultProjectName,
content: serializeProject(project),
// Expose the path read from this same snapshot so callers don't take a
// second `getState()` read that could be misread as a separate instant.
projectPath: state.projectPath,
};
};

// Serializing a project runs synchronously and throws `RangeError: Invalid
// string length` once the text passes V8's ~536 MB string cap, which a
// project embedding large vector layers can still reach. Unguarded, that
// throw escaped `void handleSave()` as an unhandled rejection and Save
// silently did nothing (GeoLibre#1829), so report it instead of returning
// text. Returns null when the project could not be serialized.
const serializeForSave = (project: GeoLibreProject): string | null => {
try {
return serializeProject(project);
} catch (error) {
console.error("Failed to serialize project", error);
// Only the string-length cap means "too large"; anything else is a real
// serialization bug and must not be filed under a size problem. Matching
// on `RangeError` alone was too broad — a stack overflow raises one too,
// and pointing that at PMTiles/FlatGeobuf would send the user chasing a
// size problem they do not have.
setActionError(
error instanceof RangeError && error.message.includes("Invalid string length")
Comment thread
giswqs marked this conversation as resolved.
Outdated
? t("toolbar.error.projectTooLargeToSave")
: t("toolbar.error.couldNotSaveProject"),
);
Comment thread
giswqs marked this conversation as resolved.
return null;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
};

// Ask whether to strip credentials (environment variables, geocoder keys,
// layer tokens) before writing the file. The promise resolves when the user
// picks an option in the dialog.
Expand Down Expand Up @@ -837,27 +877,24 @@ export function useProjectFileActions(mapControllerRef: MapControllerRef) {
// first, so the serialized content below reflects the user's choice.
const layersForSave = await resolveLayersForSave();
if (layersForSave === "cancel") return false;
const { project, defaultProjectName, content, projectPath } = buildCurrentProject(
const { project, defaultProjectName, projectPath } = buildCurrentProject(
undefined,
layersForSave.layers,
);
// Credentials are serialized in plain text for a local project that needs
// them. Make keeping them an explicit choice and use the same central
// redaction pass as every external egress.
let contentToSave = content;
let contentToSave: string | null;
const projectToEgress = excludeHiddenFieldsFromProject(project);
const redacted = redactProjectCredentials(projectToEgress);
if (redacted.redactedPaths.length > 0) {
const choice = await askStripCredentials(redacted.redactedCount);
if (choice === "cancel") return false;
if (choice === "strip") {
contentToSave = serializeProject(redacted.project);
} else {
contentToSave = serializeProject(projectToEgress);
}
contentToSave = serializeForSave(choice === "strip" ? redacted.project : projectToEgress);
} else {
contentToSave = serializeProject(projectToEgress);
contentToSave = serializeForSave(projectToEgress);
}
if (contentToSave === null) return false;
// Projects opened from a URL have no writable path, so both Save and
// Save As fall back to the save dialog for them.
const existingLocalPath = projectPath && !isHttpUrl(projectPath) ? projectPath : null;
Expand Down
2 changes: 2 additions & 0 deletions apps/geolibre-desktop/src/i18n/locales/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -2820,6 +2820,7 @@
"embedVectorTitle": "هل تريد تضمين البيانات المتجهة المحلية؟",
"embedVectorDesc": "يحتوي هذا المشروع على {{count}} من الطبقات المتجهة المحلية المحملة من جهازك. لا يستطيع المتصفح إعادة قراءتها عند إعادة فتح المشروع، لذا يجب تضمين بياناتها في ملف المشروع حتى يمكن استعادتها. يضيف التضمين نحو {{size}} إلى حجم الملف.",
"embedVectorDescDesktop": "يحتوي هذا المشروع على {{count}} من الطبقات المتجهة المحلية المحملة من جهازك. ضمِّن بياناتها لجعل المشروع مكتفيًا بذاته (يضيف نحو {{size}})، أو احفظ مراجع الملفات التي يعاد تحميلها من القرص عند إعادة فتح المشروع. تُبقي المراجع الملف صغيرًا لكنها تتعطل إذا نُقل ملف مصدر أو حُذف لاحقًا.",
"embedVectorLargeWarning": "تضمين حوالي {{size}} ينتج ملف مشروع كبير جدًا. قد تكون إعادة فتحه بطيئة أو قد تفشل تمامًا، لأنه يجب تحليل كل معلم والاحتفاظ به في الذاكرة دفعة واحدة. لبيانات بهذا الحجم، حوِّل الطبقات إلى PMTiles أو FlatGeobuf وأضفها كطبقات بعيدة بدلاً من تضمينها.",
"embedVectorEmbedButton": "تضمين البيانات",
"embedVectorReferenceButton": "حفظ مراجع الملفات",
"embedVectorSkipButton": "حفظ دون البيانات",
Expand Down Expand Up @@ -2951,6 +2952,7 @@
"couldNotOpenRecentProject": "تعذر فتح المشروع الأخير.",
"couldNotLoadRecentProject": "تعذر تحميل المشروع الأخير.",
"couldNotSaveProject": "تعذر حفظ المشروع.",
"projectTooLargeToSave": "هذا المشروع أكبر من أن يُحفَظ. حوِّل بياناته المتجهة المضمَّنة إلى PMTiles أو FlatGeobuf وأضفها كطبقات بعيدة بدلاً من تضمين المعالم.",
"couldNotExportHtml": "تعذر تصدير المشروع إلى HTML.",
"osmPbfNoFeatures": "لم يُعثر في ملف OSM PBF هذا على أي معالم نقاط أو خطوط أو مضلعات.",
"couldNotLoadOsmPbf": "تعذر تحميل ملف OSM PBF.",
Expand Down
2 changes: 2 additions & 0 deletions apps/geolibre-desktop/src/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -2633,6 +2633,7 @@
"embedVectorTitle": "Lokale Vektordaten einbetten?",
"embedVectorDesc": "Dieses Projekt enthält {{count}} lokale Vektorebene(n), die von Ihrem Computer geladen wurden. Der Browser kann sie beim erneuten Öffnen des Projekts nicht erneut einlesen, daher müssen ihre Daten in die Projektdatei eingebettet werden, damit sie wiederhergestellt werden können. Das Einbetten fügt der Datei etwa {{size}} hinzu.",
"embedVectorDescDesktop": "Dieses Projekt enthält {{count}} lokale Vektorebene(n), die von Ihrem Computer geladen wurden. Betten Sie ihre Daten ein, um das Projekt eigenständig zu machen (fügt etwa {{size}} hinzu), oder speichern Sie Dateiverweise, die beim erneuten Öffnen des Projekts von der Festplatte neu geladen werden. Verweise halten die Datei klein, funktionieren aber nicht mehr, wenn eine Quelldatei später verschoben oder gelöscht wird.",
"embedVectorLargeWarning": "Etwa {{size}} einzubetten ergibt eine sehr große Projektdatei. Das erneute Öffnen kann langsam sein oder ganz fehlschlagen, da jedes Objekt auf einmal geparst und im Speicher gehalten werden muss. Konvertieren Sie Daten dieser Größe in PMTiles oder FlatGeobuf und fügen Sie sie als Remote-Layer hinzu, statt sie einzubetten.",
"embedVectorEmbedButton": "Daten einbetten",
"embedVectorReferenceButton": "Dateiverweise speichern",
"embedVectorSkipButton": "Ohne Daten speichern",
Expand Down Expand Up @@ -2748,6 +2749,7 @@
"couldNotOpenRecentProject": "Das zuletzt verwendete Projekt konnte nicht geöffnet werden.",
"couldNotLoadRecentProject": "Das zuletzt verwendete Projekt konnte nicht geladen werden.",
"couldNotSaveProject": "Projekt konnte nicht gespeichert werden.",
"projectTooLargeToSave": "Dieses Projekt ist zu groß zum Speichern. Konvertieren Sie die eingebetteten Vektordaten in PMTiles oder FlatGeobuf und fügen Sie sie als Remote-Layer hinzu, statt die Objekte einzubetten.",
"couldNotExportHtml": "Projekt konnte nicht als HTML exportiert werden.",
"osmPbfNoFeatures": "In dieser OSM-PBF-Datei wurden keine Punkt-, Linien- oder Polygonobjekte gefunden.",
"couldNotLoadOsmPbf": "Die OSM-PBF-Datei konnte nicht geladen werden.",
Expand Down
2 changes: 2 additions & 0 deletions apps/geolibre-desktop/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2633,6 +2633,7 @@
"embedVectorTitle": "Embed local vector data?",
"embedVectorDesc": "This project has {{count}} local vector layer(s) loaded from your computer. The browser cannot re-read them when the project is reopened, so their data must be embedded in the project file to be restored. Embedding adds about {{size}} to the file.",
"embedVectorDescDesktop": "This project has {{count}} local vector layer(s) loaded from your computer. Embed their data to make the project self-contained (adds about {{size}}), or save file references that reload from disk when the project is reopened. References keep the file small but break if a source file is later moved or deleted.",
"embedVectorLargeWarning": "Embedding about {{size}} makes a very large project file. Reopening it may be slow or fail outright, because every feature has to be parsed and held in memory at once. For data this size, convert the layers to PMTiles or FlatGeobuf and add them as remote layers instead of embedding them.",
"embedVectorEmbedButton": "Embed data",
"embedVectorReferenceButton": "Save file references",
"embedVectorSkipButton": "Save without data",
Expand Down Expand Up @@ -2748,6 +2749,7 @@
"couldNotOpenRecentProject": "Could not open the recent project.",
"couldNotLoadRecentProject": "Could not load the recent project.",
"couldNotSaveProject": "Could not save the project.",
"projectTooLargeToSave": "This project is too large to save. Convert its embedded vector data to PMTiles or FlatGeobuf and add those as remote layers instead of embedding the features.",
"couldNotExportHtml": "Could not export the project to HTML.",
"osmPbfNoFeatures": "No point, line, or polygon features were found in this OSM PBF file.",
"couldNotLoadOsmPbf": "Could not load the OSM PBF file.",
Expand Down
2 changes: 2 additions & 0 deletions apps/geolibre-desktop/src/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -2633,6 +2633,7 @@
"embedVectorTitle": "¿Incrustar datos vectoriales locales?",
"embedVectorDesc": "Este proyecto tiene {{count}} capa(s) vectorial(es) local(es) cargadas desde su equipo. El navegador no puede volver a leerlas cuando se reabre el proyecto, así que sus datos deben incrustarse en el archivo del proyecto para poder restaurarse. Incrustarlas añade aproximadamente {{size}} al archivo.",
"embedVectorDescDesktop": "Este proyecto tiene {{count}} capa(s) vectorial(es) local(es) cargadas desde su equipo. Incruste sus datos para hacer el proyecto autónomo (añade aproximadamente {{size}}), o guarde referencias de archivo que se recarguen desde el disco al reabrir el proyecto. Las referencias mantienen el archivo pequeño, pero se rompen si el archivo de origen se mueve o elimina más adelante.",
"embedVectorLargeWarning": "Incrustar unos {{size}} genera un archivo de proyecto muy grande. Volver a abrirlo puede ser lento o fallar por completo, porque hay que analizar y mantener en memoria todas las entidades a la vez. Para datos de este tamaño, convierta las capas a PMTiles o FlatGeobuf y añádalas como capas remotas en lugar de incrustarlas.",
"embedVectorEmbedButton": "Incrustar datos",
"embedVectorReferenceButton": "Guardar referencias de archivo",
"embedVectorSkipButton": "Guardar sin datos",
Expand Down Expand Up @@ -2748,6 +2749,7 @@
"couldNotOpenRecentProject": "No se pudo abrir el proyecto reciente.",
"couldNotLoadRecentProject": "No se pudo cargar el proyecto reciente.",
"couldNotSaveProject": "No se pudo guardar el proyecto.",
"projectTooLargeToSave": "Este proyecto es demasiado grande para guardarlo. Convierta sus datos vectoriales incrustados a PMTiles o FlatGeobuf y añádalos como capas remotas en lugar de incrustar las entidades.",
"couldNotExportHtml": "No se pudo exportar el proyecto a HTML.",
"osmPbfNoFeatures": "No se encontraron entidades de puntos, líneas o polígonos en este archivo OSM PBF.",
"couldNotLoadOsmPbf": "No se pudo cargar el archivo OSM PBF.",
Expand Down
2 changes: 2 additions & 0 deletions apps/geolibre-desktop/src/i18n/locales/fa.json
Original file line number Diff line number Diff line change
Expand Up @@ -2633,6 +2633,7 @@
"embedVectorTitle": "دادهٔ برداری محلی جاسازی شود؟",
"embedVectorDesc": "این پروژه {{count}} لایهٔ برداری محلی دارد که از رایانهٔ شما بارگیری شده است. مرورگر هنگام باز شدن دوبارهٔ پروژه نمی‌تواند آن‌ها را دوباره بخواند، پس برای بازیابی باید داده‌شان در فایل پروژه جاسازی شود. جاسازی حدود {{size}} به فایل می‌افزاید.",
"embedVectorDescDesktop": "این پروژه {{count}} لایهٔ برداری محلی دارد که از رایانهٔ شما بارگیری شده است. داده‌شان را جاسازی کنید تا پروژه خودبسنده شود (حدود {{size}} می‌افزاید)، یا ارجاع‌های فایل را ذخیره کنید تا هنگام باز شدن دوبارهٔ پروژه از دیسک بارگیری شوند. ارجاع‌ها فایل را کوچک نگه می‌دارند اما اگر فایل مبدأ بعداً جابه‌جا یا حذف شود می‌شکنند.",
"embedVectorLargeWarning": "جاسازی حدود {{size}} یک فایل پروژهٔ بسیار بزرگ می‌سازد. بازکردن دوبارهٔ آن ممکن است کند باشد یا کاملاً شکست بخورد، چون همهٔ عارضه‌ها باید یکجا تجزیه و در حافظه نگهداری شوند. برای داده‌ای در این اندازه، لایه‌ها را به PMTiles یا FlatGeobuf تبدیل کنید و به‌جای جاسازی، آن‌ها را به‌صورت لایهٔ راه دور اضافه کنید.",
"embedVectorEmbedButton": "جاسازی داده",
"embedVectorReferenceButton": "ذخیرهٔ ارجاع‌های فایل",
"embedVectorSkipButton": "ذخیره بدون داده",
Expand Down Expand Up @@ -2748,6 +2749,7 @@
"couldNotOpenRecentProject": "باز کردن پروژهٔ اخیر ممکن نشد.",
"couldNotLoadRecentProject": "بارگیری پروژهٔ اخیر ممکن نشد.",
"couldNotSaveProject": "ذخیرهٔ پروژه ممکن نشد.",
"projectTooLargeToSave": "این پروژه برای ذخیره‌شدن بیش از حد بزرگ است. داده‌های برداری جاسازی‌شدهٔ آن را به PMTiles یا FlatGeobuf تبدیل کنید و به‌جای جاسازی عارضه‌ها، آن‌ها را به‌صورت لایهٔ راه دور اضافه کنید.",
"couldNotExportHtml": "خروجی پروژه به HTML ممکن نشد.",
"osmPbfNoFeatures": "در این فایل OSM PBF هیچ عارضهٔ نقطه‌ای، خطی یا چندضلعی یافت نشد.",
"couldNotLoadOsmPbf": "بارگیری فایل OSM PBF ممکن نشد.",
Expand Down
2 changes: 2 additions & 0 deletions apps/geolibre-desktop/src/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -2633,6 +2633,7 @@
"embedVectorTitle": "Intégrer les données vectorielles locales ?",
"embedVectorDesc": "Ce projet contient {{count}} couche(s) vectorielle(s) locale(s) chargée(s) depuis votre ordinateur. Le navigateur ne peut pas les relire lorsque le projet est rouvert, leurs données doivent donc être intégrées dans le fichier de projet pour être restaurées. L'intégration ajoute environ {{size}} au fichier.",
"embedVectorDescDesktop": "Ce projet contient {{count}} couche(s) vectorielle(s) locale(s) chargée(s) depuis votre ordinateur. Intégrez leurs données pour rendre le projet autonome (ajoute environ {{size}}), ou enregistrez des références de fichiers qui se rechargent depuis le disque lorsque le projet est rouvert. Les références gardent le fichier léger mais se rompent si un fichier source est ensuite déplacé ou supprimé.",
"embedVectorLargeWarning": "Incorporer environ {{size}} produit un fichier de projet très volumineux. Sa réouverture peut être lente, voire échouer, car chaque entité doit être analysée et conservée en mémoire en une seule fois. Pour des données de cette taille, convertissez les couches en PMTiles ou FlatGeobuf et ajoutez-les comme couches distantes plutôt que de les incorporer.",
"embedVectorEmbedButton": "Intégrer les données",
"embedVectorReferenceButton": "Enregistrer les références de fichiers",
"embedVectorSkipButton": "Enregistrer sans les données",
Expand Down Expand Up @@ -2748,6 +2749,7 @@
"couldNotOpenRecentProject": "Impossible d'ouvrir le projet récent.",
"couldNotLoadRecentProject": "Impossible de charger le projet récent.",
"couldNotSaveProject": "Impossible d'enregistrer le projet.",
"projectTooLargeToSave": "Ce projet est trop volumineux pour être enregistré. Convertissez ses données vectorielles incorporées en PMTiles ou FlatGeobuf et ajoutez-les comme couches distantes plutôt que d'incorporer les entités.",
"couldNotExportHtml": "Impossible d'exporter le projet en HTML.",
"osmPbfNoFeatures": "Aucune entité de point, ligne ou polygone n'a été trouvée dans ce fichier OSM PBF.",
"couldNotLoadOsmPbf": "Impossible de charger le fichier OSM PBF.",
Expand Down
Loading
Loading