This commit is contained in:
@@ -334,6 +334,10 @@
|
||||
"maps.sitesDialog.empty": "Chưa có site.",
|
||||
"maps.sitesDialog.deleteConfirm": "Xóa site \"{name}\"?",
|
||||
"maps.deleteConfirm": "Xóa map \"{name}\"?",
|
||||
"maps.deleteDialog.title": "Xóa map?",
|
||||
"maps.deleteDialog.text": "Xóa map \"{name}\"? Hành động không hoàn tác.",
|
||||
"maps.deleteDialog.activeWarning": "Map này đang là map hoạt động trên robot.",
|
||||
"maps.deleteForbidden": "Bạn không thể xóa map thuộc nhóm người dùng khác.",
|
||||
"maps.error.nameEmpty": "Tên map không được để trống.",
|
||||
"maps.error.noImage": "Map chưa có ảnh — upload PNG trước khi kích hoạt.",
|
||||
"maps.error.pngOnly": "Chỉ chấp nhận file PNG.",
|
||||
@@ -948,6 +952,10 @@
|
||||
"maps.sitesDialog.empty": "No sites yet.",
|
||||
"maps.sitesDialog.deleteConfirm": "Delete site \"{name}\"?",
|
||||
"maps.deleteConfirm": "Delete map \"{name}\"?",
|
||||
"maps.deleteDialog.title": "Delete map?",
|
||||
"maps.deleteDialog.text": "Delete map \"{name}\"? This cannot be undone.",
|
||||
"maps.deleteDialog.activeWarning": "This map is currently active on the robot.",
|
||||
"maps.deleteForbidden": "You cannot delete a map from another user group.",
|
||||
"maps.error.nameEmpty": "Map name is required.",
|
||||
"maps.error.noImage": "Map has no image — upload a PNG before activating.",
|
||||
"maps.error.pngOnly": "Only PNG files are accepted.",
|
||||
|
||||
@@ -1264,6 +1264,18 @@
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<dialog id="mapsDeleteDialog" class="mapsMirDialog">
|
||||
<div class="mapsMirDialogPanel">
|
||||
<h2 class="mapsMirDialogTitle" data-i18n="maps.deleteDialog.title">Delete map?</h2>
|
||||
<p id="mapsDeleteDialogText" class="mapsMirDialogText"></p>
|
||||
<p id="mapsDeleteDialogActiveWarn" class="mapsMirDialogHint" hidden data-i18n="maps.deleteDialog.activeWarning">This map is currently active on the robot.</p>
|
||||
<div class="mapsMirDialogFooter">
|
||||
<button type="button" class="mapsMirBtn mapsMirBtn--outline" id="mapsDeleteNoBtn" data-i18n="common.no">No</button>
|
||||
<button type="button" class="mapsMirBtn mapsMirBtn--danger" id="mapsDeleteYesBtn" data-i18n="common.delete">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog id="mapEditorMenuDialog" class="mapsMirDialog mapsMirDialog--mapMenu">
|
||||
<h2 class="mapsMirDialogTitle mapsMirMapMenuTitle" data-i18n="maps.menu.title">Upload, download and record maps</h2>
|
||||
<div class="mapsMirMapMenuGrid">
|
||||
|
||||
62
www/maps.js
62
www/maps.js
@@ -25,8 +25,11 @@
|
||||
const sitesDialogEl = el("mapsSitesDialog");
|
||||
const sitesListEl = el("mapsSitesList");
|
||||
const siteFormDialogEl = el("mapsSiteFormDialog");
|
||||
const deleteDialogEl = el("mapsDeleteDialog");
|
||||
const createSiteSelectEl = el("mapsCreateSite");
|
||||
|
||||
let deleteDialogResolve = null;
|
||||
|
||||
const SITE_ICONS = {
|
||||
chevron: `<svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true"><path d="M4 3l3 4-3 4M8 3l3 4-3 4" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>`,
|
||||
edit: `<svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true"><path d="M9.5 2.5l2 2L5 11H3v-2L9.5 2.5z" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>`,
|
||||
@@ -48,6 +51,19 @@
|
||||
return window.AuthApp?.canWrite?.("maps") ?? true;
|
||||
}
|
||||
|
||||
function currentUser() {
|
||||
return window.AuthApp?.getUser?.() || null;
|
||||
}
|
||||
|
||||
function canDeleteMap(map) {
|
||||
if (!canWrite() || !map) return false;
|
||||
const user = currentUser();
|
||||
if (!user) return true;
|
||||
const mapGroup = map.created_by_group;
|
||||
if (mapGroup) return mapGroup === user.group_id;
|
||||
return true;
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str)
|
||||
.replace(/&/g, "&")
|
||||
@@ -216,7 +232,7 @@
|
||||
? `<div class="mapsMirRowActions">
|
||||
<button type="button" class="mapsMirIconBtn" data-edit="${map.id}" data-i18n-title="common.edit" title="${escapeHtml(t("common.edit"))}">${ICONS.edit}</button>
|
||||
<button type="button" class="mapsMirIconBtn" data-view="${map.id}" data-i18n-title="maps.view" title="${escapeHtml(t("maps.view"))}">${ICONS.view}</button>
|
||||
<button type="button" class="mapsMirIconBtn mapsMirIconBtn--danger" data-delete="${map.id}" data-i18n-title="common.delete" title="${escapeHtml(t("common.delete"))}">${ICONS.delete}</button>
|
||||
${canDeleteMap(map) ? `<button type="button" class="mapsMirIconBtn mapsMirIconBtn--danger" data-delete="${map.id}" data-i18n-title="common.delete" title="${escapeHtml(t("common.delete"))}">${ICONS.delete}</button>` : ""}
|
||||
</div>`
|
||||
: `<div class="mapsMirRowActions">
|
||||
<button type="button" class="mapsMirIconBtn" data-view="${map.id}" data-i18n-title="maps.view" title="${escapeHtml(t("maps.view"))}">${ICONS.view}</button>
|
||||
@@ -310,11 +326,31 @@
|
||||
});
|
||||
}
|
||||
|
||||
function confirmDeleteMap(map) {
|
||||
return new Promise((resolve) => {
|
||||
deleteDialogResolve = resolve;
|
||||
const textEl = el("mapsDeleteDialogText");
|
||||
const activeWarnEl = el("mapsDeleteDialogActiveWarn");
|
||||
if (textEl) textEl.textContent = t("maps.deleteDialog.text", { name: map.name || map.id });
|
||||
if (activeWarnEl) {
|
||||
const isActive = map.id === store.activeMapId;
|
||||
activeWarnEl.hidden = !isActive;
|
||||
if (isActive) activeWarnEl.textContent = t("maps.deleteDialog.activeWarning");
|
||||
}
|
||||
deleteDialogEl?.showModal();
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteMapFromList(mapId) {
|
||||
const map = findMap(mapId);
|
||||
if (!map) return;
|
||||
if (!confirm(t("maps.deleteConfirm", { name: map.name }))) return;
|
||||
await api(`/api/maps/${encodeURIComponent(map.id)}`, { method: "DELETE" });
|
||||
if (!map || !canDeleteMap(map)) return;
|
||||
if (!(await confirmDeleteMap(map))) return;
|
||||
try {
|
||||
await api(`/api/maps/${encodeURIComponent(map.id)}`, { method: "DELETE" });
|
||||
} catch (e) {
|
||||
alert(e.message || t("maps.deleteForbidden"));
|
||||
return;
|
||||
}
|
||||
store.maps = store.maps.filter((m) => m.id !== map.id);
|
||||
if (store.activeMapId === map.id) store.activeMapId = null;
|
||||
renderList();
|
||||
@@ -465,6 +501,8 @@
|
||||
name,
|
||||
site_id,
|
||||
created_by: user?.display_name || user?.username || "",
|
||||
created_by_user: user?.id || "",
|
||||
created_by_group: user?.group_id || "",
|
||||
resolution: 0.05,
|
||||
origin_x: 0,
|
||||
origin_y: 0,
|
||||
@@ -515,6 +553,22 @@
|
||||
el("mapsCreateForm")?.addEventListener("submit", (evt) => {
|
||||
createMap(evt).catch((e) => alert(e.message));
|
||||
});
|
||||
el("mapsDeleteYesBtn")?.addEventListener("click", () => {
|
||||
deleteDialogEl?.close();
|
||||
deleteDialogResolve?.(true);
|
||||
deleteDialogResolve = null;
|
||||
});
|
||||
el("mapsDeleteNoBtn")?.addEventListener("click", () => {
|
||||
deleteDialogEl?.close();
|
||||
deleteDialogResolve?.(false);
|
||||
deleteDialogResolve = null;
|
||||
});
|
||||
deleteDialogEl?.addEventListener("cancel", (evt) => {
|
||||
evt.preventDefault();
|
||||
deleteDialogEl.close();
|
||||
deleteDialogResolve?.(false);
|
||||
deleteDialogResolve = null;
|
||||
});
|
||||
el("mapsSiteForm")?.addEventListener("submit", (evt) => {
|
||||
saveSite(evt).catch((e) => alert(e.message));
|
||||
});
|
||||
|
||||
@@ -3259,6 +3259,14 @@ body.auth-readonly-integrations .integrationToolbar .btn.primary { pointer-event
|
||||
|
||||
.mapsMirBtn--outline:hover { background: #f5f5f5; }
|
||||
|
||||
.mapsMirBtn--danger {
|
||||
background: #c0392b;
|
||||
color: #fff;
|
||||
border: 1px solid #c0392b;
|
||||
}
|
||||
|
||||
.mapsMirBtn--danger:hover { background: #a93226; }
|
||||
|
||||
.mapsMirActiveHint {
|
||||
margin-bottom: 12px;
|
||||
padding: 8px 12px;
|
||||
|
||||
Reference in New Issue
Block a user