Show what's actually cached per-country, with a delete button per row
The only visibility into downloaded data was the whole offline.pmtiles row, and the only delete was "everything" (whole file + whole cache). GET /api/offline-tiles/cache lists what's really in data/geofabrik_cache/ right now — distinct from offline.pmtiles' own meta.sources, which only reflects the last successful build and can disagree with both the current selection and the cache (ticked-but- not-Applied, or built-from-but-since-unticked). The region picker now shows each cached country's size next to its checkbox, with its own delete button (DELETE /api/offline-tiles/cache/:code, already added but unused by the UI until now) — removing just that entry rather than clearing the whole cache.
This commit is contained in:
1 parent
d09d953cbf
commit
78362d51f2
7 files changed
+158
-6
No files matched your search
@@ -416,11 +416,20 @@ html, body {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.offline-data-panel__region-list li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.offline-data-panel__region-list label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 8px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -428,6 +437,34 @@ html, body {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.offline-data-panel__cache-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding-right: 8px;
|
||||
color: #888;
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.offline-data-panel__cache-delete {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0 2px;
|
||||
color: #a33;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.offline-data-panel__cache-delete:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.offline-data-panel__cache-delete:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Same rounded-square badge ratio as icons.js's own MAP_MARKER_ICON
|
||||
(rx 5 of a 22-wide square) — a consistent frame rather than however
|
||||
each flag's own artwork happens to fill an unbordered square. */
|
||||
|
||||
@@ -81,26 +81,72 @@ class RegionPicker {
|
||||
container.appendChild(section);
|
||||
|
||||
let data;
|
||||
let cache;
|
||||
try {
|
||||
data = await (await fetch("/api/offline-regions")).json();
|
||||
[data, cache] = await Promise.all([
|
||||
fetch("/api/offline-regions").then((r) => r.json()),
|
||||
fetch("/api/offline-tiles/cache").then((r) => r.json()),
|
||||
]);
|
||||
} catch {
|
||||
this._list.innerHTML = `<li class="offline-data-panel__region-status">Could not load the region list.</li>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const selected = new Set(data.selected);
|
||||
const cached = new Map(cache.extracts.map((e) => [e.code, e]));
|
||||
this._list.innerHTML = data.available
|
||||
.map(
|
||||
(region) => `
|
||||
.map((region) => {
|
||||
const extract = cached.get(region.code);
|
||||
// Downloaded-but-not-(yet)-built is a real state (ticked, not
|
||||
// Applied yet) as is built-but-no-longer-selected (unticked since)
|
||||
// — this badge reflects the cache on disk, independent of either.
|
||||
const cacheInfo = extract
|
||||
? `
|
||||
<span class="offline-data-panel__cache-info">
|
||||
${formatBytes(extract.size)}
|
||||
${
|
||||
isLoggedIn()
|
||||
? `<button type="button" class="offline-data-panel__cache-delete" data-code="${escapeHtml(region.code)}" data-name="${escapeHtml(region.name)}" title="Delete downloaded data for ${escapeHtml(region.name)}">✕</button>`
|
||||
: ""
|
||||
}
|
||||
</span>`
|
||||
: "";
|
||||
return `
|
||||
<li>
|
||||
<label>
|
||||
<input type="checkbox" value="${escapeHtml(region.code)}" ${selected.has(region.code) ? "checked" : ""}>
|
||||
<img class="offline-data-panel__flag" src="${flagIconUrl(region.code)}" alt="" width="18" height="18">
|
||||
${escapeHtml(region.name)}
|
||||
</label>
|
||||
</li>`,
|
||||
)
|
||||
${cacheInfo}
|
||||
</li>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
this._list.querySelectorAll(".offline-data-panel__cache-delete").forEach((button) => {
|
||||
button.addEventListener("click", () => this._deleteCache(button));
|
||||
});
|
||||
}
|
||||
|
||||
// Removes just this row's cache badge on success, rather than re-fetching
|
||||
// and re-rendering the whole list — that would also overwrite whatever
|
||||
// unticked/ticked-but-not-yet-Applied state the user has pending.
|
||||
async _deleteCache(button) {
|
||||
const code = button.dataset.code;
|
||||
if (!window.confirm(`Delete downloaded data for ${button.dataset.name}? This can't be undone.`)) return;
|
||||
|
||||
button.disabled = true;
|
||||
try {
|
||||
const response = await fetch(`/api/offline-tiles/cache/${code}`, { method: "DELETE" });
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (!response.ok) throw new Error(data.error || `could not delete: ${response.status}`);
|
||||
} catch (err) {
|
||||
window.alert(err.message);
|
||||
button.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
button.closest(".offline-data-panel__cache-info").remove();
|
||||
}
|
||||
|
||||
async _apply() {
|
||||
|
||||
Reference in new issue
Block a user