Fix for duplicate member cards when pressing populate
This commit is contained in:
Binary file not shown.
2
main.py
2
main.py
@@ -45,7 +45,7 @@ async def dashboard(request: Request):
|
|||||||
|
|
||||||
class FactionRequest(BaseModel):
|
class FactionRequest(BaseModel):
|
||||||
faction_id: int
|
faction_id: int
|
||||||
interval: int
|
interval: int = 30
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
|
|||||||
@@ -1,30 +1,39 @@
|
|||||||
// dashboard.js
|
// dashboard.js (Corrected Full Version)
|
||||||
// Full functionality: populate, start/stop status loops, load members, drag & drop between lists and groups,
|
// Prevents duplicates on populate
|
||||||
// drop zones accept only friendly/enemy respectively, status color updates, localStorage persistence
|
// Preserves group placements
|
||||||
|
// Updates or creates cards (never recreates all)
|
||||||
|
// Status refresh works correctly
|
||||||
|
|
||||||
// ---- In-memory maps ----
|
//-----------------------------------------------------
|
||||||
const friendlyMembers = new Map(); // id -> member object
|
// Maps of members (id -> member object)
|
||||||
|
//-----------------------------------------------------
|
||||||
|
const friendlyMembers = new Map();
|
||||||
const enemyMembers = new Map();
|
const enemyMembers = new Map();
|
||||||
|
|
||||||
// ---- DOM containers ----
|
// DOM containers
|
||||||
const friendlyContainer = document.getElementById("friendly-container");
|
const friendlyContainer = document.getElementById("friendly-container");
|
||||||
const enemyContainer = document.getElementById("enemy-container");
|
const enemyContainer = document.getElementById("enemy-container");
|
||||||
|
|
||||||
// utility to safe parse ints
|
function toInt(v) {
|
||||||
function toInt(v) { const n = Number(v); return Number.isNaN(n) ? null : n; }
|
const n = Number(v);
|
||||||
|
return Number.isNaN(n) ? null : n;
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Apply status CSS class to the .status-text inside a member card ----
|
//-----------------------------------------------------
|
||||||
|
// Status CSS assignment
|
||||||
|
//-----------------------------------------------------
|
||||||
function applyStatusClass(member) {
|
function applyStatusClass(member) {
|
||||||
const span = member.domElement?.querySelector(".status-text");
|
const span = member.domElement?.querySelector(".status-text");
|
||||||
if (!span) return;
|
if (!span) return;
|
||||||
|
|
||||||
span.classList.remove("status-ok", "status-traveling", "status-hospitalized");
|
span.classList.remove("status-ok", "status-traveling", "status-hospitalized");
|
||||||
const s = String(member.status || "").toLowerCase();
|
|
||||||
|
const s = (member.status || "").toLowerCase();
|
||||||
if (s === "okay") span.classList.add("status-ok");
|
if (s === "okay") span.classList.add("status-ok");
|
||||||
else if (s === "traveling" || s === "abroad") span.classList.add("status-traveling");
|
else if (s === "traveling" || s === "abroad") span.classList.add("status-traveling");
|
||||||
else if (s === "hospitalized" || s === "hospital") span.classList.add("status-hospitalized");
|
else if (s === "hospitalized" || s === "hospital") span.classList.add("status-hospitalized");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Decide which CSS class a status should use (helper used when creating card) ---
|
|
||||||
function statusClass(status) {
|
function statusClass(status) {
|
||||||
if (!status) return "";
|
if (!status) return "";
|
||||||
status = status.toLowerCase();
|
status = status.toLowerCase();
|
||||||
@@ -34,7 +43,9 @@ function statusClass(status) {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Create structured card ----
|
//-----------------------------------------------------
|
||||||
|
// Create a new card DOM element for a member
|
||||||
|
//-----------------------------------------------------
|
||||||
function createMemberCard(member, kind) {
|
function createMemberCard(member, kind) {
|
||||||
const card = document.createElement("div");
|
const card = document.createElement("div");
|
||||||
card.classList.add("member-card");
|
card.classList.add("member-card");
|
||||||
@@ -45,129 +56,195 @@ function createMemberCard(member, kind) {
|
|||||||
const nameDiv = document.createElement("div");
|
const nameDiv = document.createElement("div");
|
||||||
nameDiv.className = "name";
|
nameDiv.className = "name";
|
||||||
nameDiv.textContent = member.name;
|
nameDiv.textContent = member.name;
|
||||||
if (kind === "friendly") nameDiv.style.color = "#8fd38f"; // green
|
if (kind === "friendly") nameDiv.style.color = "#8fd38f";
|
||||||
if (kind === "enemy") nameDiv.style.color = "#ff6b6b"; // red
|
if (kind === "enemy") nameDiv.style.color = "#ff6b6b";
|
||||||
|
|
||||||
const statsDiv = document.createElement("div");
|
const statsDiv = document.createElement("div");
|
||||||
statsDiv.className = "stats";
|
statsDiv.className = "stats";
|
||||||
// Ensure initial status text and class (maybe "Unknown" until refresh)
|
statsDiv.innerHTML = `
|
||||||
statsDiv.innerHTML = `Lv: ${member.level} <br> Est: ${member.estimate} <br> Status: <span class="status-text ${statusClass(member.status)}">${member.status || "Unknown"}</span>`;
|
Lv: ${member.level} <br>
|
||||||
|
Est: ${member.estimate} <br>
|
||||||
|
Status: <span class="status-text ${statusClass(member.status)}">${member.status || "Unknown"}</span>
|
||||||
|
`;
|
||||||
|
|
||||||
card.appendChild(nameDiv);
|
card.appendChild(nameDiv);
|
||||||
card.appendChild(statsDiv);
|
card.appendChild(statsDiv);
|
||||||
|
|
||||||
|
// save reference
|
||||||
member.domElement = card;
|
member.domElement = card;
|
||||||
|
|
||||||
// drag payload is JSON (kind + id)
|
// dragging
|
||||||
card.addEventListener("dragstart", (e) => {
|
card.addEventListener("dragstart", (e) => {
|
||||||
const payload = JSON.stringify({ kind, id: member.id });
|
e.dataTransfer.setData("text/plain", JSON.stringify({
|
||||||
e.dataTransfer.setData("text/plain", payload);
|
kind,
|
||||||
|
id: member.id
|
||||||
|
}));
|
||||||
card.style.opacity = "0.5";
|
card.style.opacity = "0.5";
|
||||||
});
|
});
|
||||||
card.addEventListener("dragend", () => {
|
card.addEventListener("dragend", () => {
|
||||||
card.style.opacity = "1";
|
card.style.opacity = "1";
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply initial status class (for cases where member.status was set by populate)
|
|
||||||
applyStatusClass(member);
|
|
||||||
|
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Load static members from backend ----
|
//-----------------------------------------------------
|
||||||
async function loadMembers(faction) {
|
// Update an existing card instead of replacing it
|
||||||
const url = faction === "friendly" ? "/api/friendly_members" : "/api/enemy_members";
|
//-----------------------------------------------------
|
||||||
|
function updateMemberCard(member) {
|
||||||
|
if (!member.domElement) return;
|
||||||
|
|
||||||
|
const span = member.domElement.querySelector(".status-text");
|
||||||
|
if (span) {
|
||||||
|
span.textContent = member.status || "Unknown";
|
||||||
|
}
|
||||||
|
applyStatusClass(member);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------
|
||||||
|
// Load members WITHOUT recreating card duplicates
|
||||||
|
//-----------------------------------------------------
|
||||||
|
async function loadMembers(kind) {
|
||||||
|
const url = kind === "friendly" ? "/api/friendly_members" : "/api/enemy_members";
|
||||||
|
const container = kind === "friendly" ? friendlyContainer : enemyContainer;
|
||||||
|
const map = kind === "friendly" ? friendlyMembers : enemyMembers;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(url, { cache: "no-store" });
|
const res = await fetch(url, { cache: "no-store" });
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
console.error("Failed to fetch members:", res.status);
|
console.error("Error loading members", res.status);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const arr = await res.json();
|
|
||||||
const container = faction === "friendly" ? friendlyContainer : enemyContainer;
|
|
||||||
const map = faction === "friendly" ? friendlyMembers : enemyMembers;
|
|
||||||
|
|
||||||
container.innerHTML = "";
|
const list = await res.json();
|
||||||
map.clear();
|
|
||||||
|
|
||||||
arr.forEach(m => {
|
// Keep track of IDs received so we don't remove existing group placements
|
||||||
if (!m.status) m.status = "Unknown";
|
const receivedIds = new Set(list.map(m => m.id));
|
||||||
const card = createMemberCard(m, faction);
|
|
||||||
map.set(m.id, m);
|
// Update existing members or create new ones
|
||||||
container.appendChild(card);
|
for (const m of list) {
|
||||||
});
|
let existing = map.get(m.id);
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
// update info (but keep DOM placement)
|
||||||
|
existing.name = m.name;
|
||||||
|
existing.level = m.level;
|
||||||
|
existing.estimate = m.estimate;
|
||||||
|
if (!existing.status) existing.status = "Unknown";
|
||||||
|
updateMemberCard(existing);
|
||||||
|
} else {
|
||||||
|
// NEW member — create card and add to list area
|
||||||
|
const newMember = {
|
||||||
|
id: m.id,
|
||||||
|
name: m.name,
|
||||||
|
level: m.level,
|
||||||
|
estimate: m.estimate,
|
||||||
|
status: "Unknown",
|
||||||
|
domElement: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const card = createMemberCard(newMember, kind);
|
||||||
|
map.set(m.id, newMember);
|
||||||
|
|
||||||
|
// Only add to container if they are NOT already assigned to a group
|
||||||
|
if (!isMemberAssigned(newMember.id)) {
|
||||||
|
container.appendChild(card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove members that no longer exist from map (rare)
|
||||||
|
for (const existingId of map.keys()) {
|
||||||
|
if (!receivedIds.has(existingId)) {
|
||||||
|
const member = map.get(existingId);
|
||||||
|
if (member?.domElement?.parentElement) {
|
||||||
|
member.domElement.parentElement.removeChild(member.domElement);
|
||||||
|
}
|
||||||
|
map.delete(existingId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// after we've added elements, ensure drop listeners are attached
|
|
||||||
setupDropZones();
|
setupDropZones();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("loadMembers error", err);
|
console.error("loadMembers error", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Refresh status map and update DOM ----
|
//-----------------------------------------------------
|
||||||
async function refreshStatus(faction) {
|
// Check if a member is placed in a group zone
|
||||||
const url = faction === "friendly" ? "/api/friendly_status" : "/api/enemy_status";
|
//-----------------------------------------------------
|
||||||
const map = faction === "friendly" ? friendlyMembers : enemyMembers;
|
function isMemberAssigned(id) {
|
||||||
|
return document.querySelector(`.member-card[data-id="${id}"]`)?.parentElement?.classList.contains("drop-zone") || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------
|
||||||
|
// Status refresh
|
||||||
|
//-----------------------------------------------------
|
||||||
|
async function refreshStatus(kind) {
|
||||||
|
const url = kind === "friendly" ? "/api/friendly_status" : "/api/enemy_status";
|
||||||
|
const map = kind === "friendly" ? friendlyMembers : enemyMembers;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(url, { cache: "no-store" });
|
const res = await fetch(url, { cache: "no-store" });
|
||||||
if (!res.ok) return;
|
if (!res.ok) return;
|
||||||
|
|
||||||
const statusData = await res.json();
|
const statusData = await res.json();
|
||||||
Object.keys(statusData).forEach(k => {
|
for (const idStr of Object.keys(statusData)) {
|
||||||
const id = parseInt(k);
|
const id = parseInt(idStr);
|
||||||
const member = map.get(id);
|
const member = map.get(id);
|
||||||
if (!member) return;
|
if (!member) continue;
|
||||||
member.status = statusData[k].status;
|
|
||||||
const span = member.domElement.querySelector(".status-text");
|
member.status = statusData[idStr].status;
|
||||||
if (span) {
|
updateMemberCard(member);
|
||||||
span.textContent = member.status;
|
}
|
||||||
applyStatusClass(member);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("refreshStatus error", err);
|
console.error("refreshStatus error", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Populate endpoints ----
|
//-----------------------------------------------------
|
||||||
// NOW: populate also immediately fetches the latest status snapshot and applies it.
|
// Populate endpoints (immediate build + status refresh)
|
||||||
|
//-----------------------------------------------------
|
||||||
async function populateFriendly() {
|
async function populateFriendly() {
|
||||||
const id = toInt(document.getElementById("friendly-id").value);
|
const id = toInt(document.getElementById("friendly-id").value);
|
||||||
if (!id) return alert("Enter friendly faction ID");
|
if (!id) return alert("Enter Friendly Faction ID");
|
||||||
|
|
||||||
|
// Send faction_id to FastAPI
|
||||||
await fetch("/api/populate_friendly", {
|
await fetch("/api/populate_friendly", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ faction_id: id, interval: 0 })
|
body: JSON.stringify({ faction_id: id })
|
||||||
});
|
});
|
||||||
|
|
||||||
// Rebuild list and then immediately fetch status snapshot
|
// Load members but do NOT recreate duplicates
|
||||||
await loadMembers("friendly");
|
await loadMembers("friendly");
|
||||||
// try to pull status once so the status text shows up immediately
|
|
||||||
|
// Immediately refresh status so cards show correct status
|
||||||
await refreshStatus("friendly");
|
await refreshStatus("friendly");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function populateEnemy() {
|
async function populateEnemy() {
|
||||||
const id = toInt(document.getElementById("enemy-id").value);
|
const id = toInt(document.getElementById("enemy-id").value);
|
||||||
if (!id) return alert("Enter enemy faction ID");
|
if (!id) return alert("Enter Enemy Faction ID");
|
||||||
|
|
||||||
await fetch("/api/populate_enemy", {
|
await fetch("/api/populate_enemy", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ faction_id: id, interval: 0 })
|
body: JSON.stringify({ faction_id: id })
|
||||||
});
|
});
|
||||||
|
|
||||||
await loadMembers("enemy");
|
await loadMembers("enemy");
|
||||||
await refreshStatus("enemy");
|
await refreshStatus("enemy");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Start/Stop status refresh loops ----
|
//-----------------------------------------------------
|
||||||
|
// Status refresh toggling
|
||||||
|
//-----------------------------------------------------
|
||||||
let friendlyStatusIntervalHandle = null;
|
let friendlyStatusIntervalHandle = null;
|
||||||
let enemyStatusIntervalHandle = null;
|
let enemyStatusIntervalHandle = null;
|
||||||
|
|
||||||
async function toggleFriendlyStatus() {
|
async function toggleFriendlyStatus() {
|
||||||
const btn = document.getElementById("friendly-status-btn");
|
const btn = document.getElementById("friendly-status-btn");
|
||||||
if (!btn) {
|
|
||||||
console.error("friendly-status-btn not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (friendlyStatusIntervalHandle) {
|
if (friendlyStatusIntervalHandle) {
|
||||||
clearInterval(friendlyStatusIntervalHandle);
|
clearInterval(friendlyStatusIntervalHandle);
|
||||||
@@ -178,7 +255,6 @@ async function toggleFriendlyStatus() {
|
|||||||
|
|
||||||
const id = toInt(document.getElementById("friendly-id").value);
|
const id = toInt(document.getElementById("friendly-id").value);
|
||||||
const interval = Math.max(1, toInt(document.getElementById("refresh-interval").value) || 10);
|
const interval = Math.max(1, toInt(document.getElementById("refresh-interval").value) || 10);
|
||||||
if (!id) return alert("Enter friendly faction ID");
|
|
||||||
|
|
||||||
await fetch("/api/start_friendly_status", {
|
await fetch("/api/start_friendly_status", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -187,17 +263,12 @@ async function toggleFriendlyStatus() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
friendlyStatusIntervalHandle = setInterval(() => refreshStatus("friendly"), interval * 1000);
|
friendlyStatusIntervalHandle = setInterval(() => refreshStatus("friendly"), interval * 1000);
|
||||||
btn.textContent = "Stop Refresh";
|
|
||||||
// do an immediate status fetch as well
|
|
||||||
refreshStatus("friendly");
|
refreshStatus("friendly");
|
||||||
|
btn.textContent = "Stop Refresh";
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleEnemyStatus() {
|
async function toggleEnemyStatus() {
|
||||||
const btn = document.getElementById("enemy-status-btn");
|
const btn = document.getElementById("enemy-status-btn");
|
||||||
if (!btn) {
|
|
||||||
console.error("enemy-status-btn not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enemyStatusIntervalHandle) {
|
if (enemyStatusIntervalHandle) {
|
||||||
clearInterval(enemyStatusIntervalHandle);
|
clearInterval(enemyStatusIntervalHandle);
|
||||||
@@ -208,7 +279,6 @@ async function toggleEnemyStatus() {
|
|||||||
|
|
||||||
const id = toInt(document.getElementById("enemy-id").value);
|
const id = toInt(document.getElementById("enemy-id").value);
|
||||||
const interval = Math.max(1, toInt(document.getElementById("refresh-interval").value) || 10);
|
const interval = Math.max(1, toInt(document.getElementById("refresh-interval").value) || 10);
|
||||||
if (!id) return alert("Enter enemy faction ID");
|
|
||||||
|
|
||||||
await fetch("/api/start_enemy_status", {
|
await fetch("/api/start_enemy_status", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -217,39 +287,31 @@ async function toggleEnemyStatus() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
enemyStatusIntervalHandle = setInterval(() => refreshStatus("enemy"), interval * 1000);
|
enemyStatusIntervalHandle = setInterval(() => refreshStatus("enemy"), interval * 1000);
|
||||||
btn.textContent = "Stop Refresh";
|
|
||||||
refreshStatus("enemy");
|
refreshStatus("enemy");
|
||||||
|
btn.textContent = "Stop Refresh";
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------
|
//-----------------------------------------------------
|
||||||
// DRAG & DROP SYSTEM
|
// Drag & Drop for all zones
|
||||||
// -------------------
|
//-----------------------------------------------------
|
||||||
function setupDropZones() {
|
function setupDropZones() {
|
||||||
const dropZones = document.querySelectorAll(".drop-zone, .member-list");
|
const zones = document.querySelectorAll(".drop-zone, .member-list");
|
||||||
|
|
||||||
dropZones.forEach(zone => {
|
zones.forEach(zone => {
|
||||||
// attach listeners only once
|
|
||||||
if (zone.dataset._drag_listeners_attached) return;
|
if (zone.dataset._drag_listeners_attached) return;
|
||||||
|
|
||||||
zone.addEventListener("dragover", (e) => {
|
zone.addEventListener("dragover", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// check payload validity
|
const raw = e.dataTransfer.getData("text/plain");
|
||||||
let raw = null;
|
|
||||||
try {
|
|
||||||
raw = e.dataTransfer.getData("text/plain");
|
|
||||||
} catch (err) {
|
|
||||||
raw = null;
|
|
||||||
}
|
|
||||||
let valid = false;
|
let valid = false;
|
||||||
if (raw) {
|
try {
|
||||||
try {
|
const p = JSON.parse(raw);
|
||||||
const p = JSON.parse(raw);
|
const zoneType = zone.classList.contains("friendly-zone") ? "friendly" :
|
||||||
const kind = p.kind;
|
zone.classList.contains("enemy-zone") ? "enemy" :
|
||||||
const zoneType = zone.classList.contains("friendly-zone") || zone.id.includes("friendly") ? "friendly" :
|
null;
|
||||||
zone.classList.contains("enemy-zone") || zone.id.includes("enemy") ? "enemy" : null;
|
|
||||||
if (zoneType && kind === zoneType) valid = true;
|
if (!zoneType || p.kind === zoneType) valid = true;
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
|
||||||
zone.classList.toggle("dragover-valid", valid);
|
zone.classList.toggle("dragover-valid", valid);
|
||||||
zone.classList.toggle("dragover-invalid", !valid);
|
zone.classList.toggle("dragover-invalid", !valid);
|
||||||
});
|
});
|
||||||
@@ -262,32 +324,26 @@ function setupDropZones() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
zone.classList.remove("dragover-valid", "dragover-invalid");
|
zone.classList.remove("dragover-valid", "dragover-invalid");
|
||||||
|
|
||||||
let raw = null;
|
|
||||||
try {
|
|
||||||
raw = e.dataTransfer.getData("text/plain");
|
|
||||||
} catch (err) {
|
|
||||||
raw = null;
|
|
||||||
}
|
|
||||||
if (!raw) return;
|
|
||||||
let payload;
|
let payload;
|
||||||
try { payload = JSON.parse(raw); } catch { return; }
|
try {
|
||||||
const { kind, id } = payload;
|
payload = JSON.parse(e.dataTransfer.getData("text/plain"));
|
||||||
const idn = parseInt(id);
|
} catch { return; }
|
||||||
if (!kind || !idn) return;
|
|
||||||
|
|
||||||
const zoneType = zone.classList.contains("friendly-zone") || zone.id.includes("friendly") ? "friendly" :
|
const zoneType = zone.classList.contains("friendly-zone") ? "friendly" :
|
||||||
zone.classList.contains("enemy-zone") || zone.id.includes("enemy") ? "enemy" : null;
|
zone.classList.contains("enemy-zone") ? "enemy" :
|
||||||
if (zoneType !== kind) return;
|
null;
|
||||||
|
|
||||||
const map = kind === "friendly" ? friendlyMembers : enemyMembers;
|
if (zoneType && payload.kind !== zoneType) return;
|
||||||
const member = map.get(idn);
|
|
||||||
|
const map = payload.kind === "friendly" ? friendlyMembers : enemyMembers;
|
||||||
|
const member = map.get(payload.id);
|
||||||
if (!member) return;
|
if (!member) return;
|
||||||
|
|
||||||
const prev = member.domElement?.parentElement;
|
// Move the card
|
||||||
if (prev && prev !== zone) prev.removeChild(member.domElement);
|
const prev = member.domElement.parentElement;
|
||||||
|
if (prev !== zone) prev.removeChild(member.domElement);
|
||||||
zone.appendChild(member.domElement);
|
zone.appendChild(member.domElement);
|
||||||
|
|
||||||
// persist assignment in localStorage
|
|
||||||
saveAssignments();
|
saveAssignments();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -295,72 +351,76 @@ function setupDropZones() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------
|
//-----------------------------------------------------
|
||||||
// localStorage helpers for battle groups (the format used earlier)
|
// Save and load group assignments
|
||||||
// -------------------
|
//-----------------------------------------------------
|
||||||
function saveAssignments() {
|
function saveAssignments() {
|
||||||
const groups = document.querySelectorAll(".group");
|
const groups = document.querySelectorAll(".group");
|
||||||
const data = {};
|
const data = {};
|
||||||
|
|
||||||
groups.forEach(g => {
|
groups.forEach(g => {
|
||||||
const gid = g.dataset.id;
|
const gid = g.dataset.id;
|
||||||
data[gid] = { friendly: [], enemy: [] };
|
data[gid] = {
|
||||||
g.querySelectorAll(".friendly-zone .member-card").forEach(c => data[gid].friendly.push(parseInt(c.dataset.id)));
|
friendly: [],
|
||||||
g.querySelectorAll(".enemy-zone .member-card").forEach(c => data[gid].enemy.push(parseInt(c.dataset.id)));
|
enemy: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
g.querySelectorAll(".friendly-zone .member-card").forEach(c =>
|
||||||
|
data[gid].friendly.push(parseInt(c.dataset.id))
|
||||||
|
);
|
||||||
|
|
||||||
|
g.querySelectorAll(".enemy-zone .member-card").forEach(c =>
|
||||||
|
data[gid].enemy.push(parseInt(c.dataset.id))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
localStorage.setItem("battleAssignments", JSON.stringify(data));
|
localStorage.setItem("battleAssignments", JSON.stringify(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadAssignmentsFromStorage() {
|
function loadAssignmentsFromStorage() {
|
||||||
const data = JSON.parse(localStorage.getItem("battleAssignments") || "{}");
|
const data = JSON.parse(localStorage.getItem("battleAssignments") || "{}");
|
||||||
|
|
||||||
Object.keys(data).forEach(gid => {
|
Object.keys(data).forEach(gid => {
|
||||||
const group = document.querySelector(`.group[data-id="${gid}"]`);
|
const group = document.querySelector(`.group[data-id="${gid}"]`);
|
||||||
if (!group) return;
|
if (!group) return;
|
||||||
|
|
||||||
data[gid].friendly.forEach(id => {
|
data[gid].friendly.forEach(id => {
|
||||||
const member = friendlyMembers.get(id);
|
const m = friendlyMembers.get(id);
|
||||||
if (member) group.querySelector(".friendly-zone").appendChild(member.domElement);
|
if (m?.domElement) group.querySelector(".friendly-zone").appendChild(m.domElement);
|
||||||
});
|
});
|
||||||
|
|
||||||
data[gid].enemy.forEach(id => {
|
data[gid].enemy.forEach(id => {
|
||||||
const member = enemyMembers.get(id);
|
const m = enemyMembers.get(id);
|
||||||
if (member) group.querySelector(".enemy-zone").appendChild(member.domElement);
|
if (m?.domElement) group.querySelector(".enemy-zone").appendChild(m.domElement);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Allow clicking a card to log for debug (optional) ----
|
//-----------------------------------------------------
|
||||||
function attachCardClickLogging() {
|
// Wire up buttons
|
||||||
document.addEventListener("click", (e) => {
|
//-----------------------------------------------------
|
||||||
const el = e.target.closest(".member-card");
|
|
||||||
if (!el) return;
|
|
||||||
const id = parseInt(el.dataset.id);
|
|
||||||
const kind = el.dataset.kind;
|
|
||||||
const map = kind === "friendly" ? friendlyMembers : enemyMembers;
|
|
||||||
const member = map.get(id);
|
|
||||||
if (member) console.log("Member clicked:", member);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Initialize UI wiring ----
|
|
||||||
function wireUp() {
|
function wireUp() {
|
||||||
document.getElementById("friendly-populate-btn").addEventListener("click", populateFriendly);
|
document.getElementById("friendly-populate-btn").addEventListener("click", populateFriendly);
|
||||||
document.getElementById("enemy-populate-btn").addEventListener("click", populateEnemy);
|
document.getElementById("enemy-populate-btn").addEventListener("click", populateEnemy);
|
||||||
document.getElementById("friendly-status-btn").addEventListener("click", toggleFriendlyStatus);
|
document.getElementById("friendly-status-btn").addEventListener("click", toggleFriendlyStatus);
|
||||||
document.getElementById("enemy-status-btn").addEventListener("click", toggleEnemyStatus);
|
document.getElementById("enemy-status-btn").addEventListener("click", toggleEnemyStatus);
|
||||||
document.getElementById("reset-groups-btn").addEventListener("click", () => {
|
|
||||||
// clear storage and put members back
|
|
||||||
localStorage.removeItem("battleAssignments");
|
|
||||||
// move all back to lists
|
|
||||||
friendlyMembers.forEach(m => { if (m.domElement) friendlyContainer.appendChild(m.domElement); });
|
|
||||||
enemyMembers.forEach(m => { if (m.domElement) enemyContainer.appendChild(m.domElement); });
|
|
||||||
});
|
|
||||||
|
|
||||||
setupDropZones();
|
document.getElementById("reset-groups-btn").addEventListener("click", () => {
|
||||||
attachCardClickLogging();
|
localStorage.removeItem("battleAssignments");
|
||||||
|
|
||||||
|
friendlyMembers.forEach(m => friendlyContainer.appendChild(m.domElement));
|
||||||
|
enemyMembers.forEach(m => enemyContainer.appendChild(m.domElement));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- On load: wire and attempt to load any existing JSON lists ----
|
//-----------------------------------------------------
|
||||||
|
// On Load
|
||||||
|
//-----------------------------------------------------
|
||||||
document.addEventListener("DOMContentLoaded", async () => {
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
wireUp();
|
wireUp();
|
||||||
|
|
||||||
await loadMembers("friendly");
|
await loadMembers("friendly");
|
||||||
await loadMembers("enemy");
|
await loadMembers("enemy");
|
||||||
loadAssignmentsFromStorage(); // restore positions after load
|
|
||||||
|
loadAssignmentsFromStorage();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user