Persistent Storage on reload, Start and Stop buttons
This commit is contained in:
Binary file not shown.
20
main.py
20
main.py
@@ -100,6 +100,26 @@ async def get_enemy_members():
|
|||||||
return json.load(f)
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# Status JSON endpoints
|
||||||
|
# =============================
|
||||||
|
@app.get("/api/friendly_status")
|
||||||
|
async def api_friendly_status():
|
||||||
|
path = Path("data/friendly_status.json")
|
||||||
|
if not path.exists():
|
||||||
|
return {}
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/enemy_status")
|
||||||
|
async def api_enemy_status():
|
||||||
|
path = Path("data/enemy_status.json")
|
||||||
|
if not path.exists():
|
||||||
|
return {}
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// dashboard.js
|
// dashboard.js
|
||||||
// Full functionality: populate, start status loops, load members, drag & drop between lists and groups,
|
// Full functionality: populate, start/stop status loops, load members, drag & drop between lists and groups,
|
||||||
// drop zones accept only friendly/enemy respectively, status color updates.
|
// drop zones accept only friendly/enemy respectively, status color updates, localStorage persistence
|
||||||
|
|
||||||
// ---- In-memory maps ----
|
// ---- In-memory maps ----
|
||||||
const friendlyMembers = new Map(); // id -> member object
|
const friendlyMembers = new Map(); // id -> member object
|
||||||
@@ -15,19 +15,17 @@ function toInt(v) { const n = Number(v); return Number.isNaN(n) ? null : n; }
|
|||||||
|
|
||||||
// ---- Create structured card ----
|
// ---- Create structured card ----
|
||||||
function createMemberCard(member, kind) {
|
function createMemberCard(member, kind) {
|
||||||
// member: { id, name, level, estimate, status }
|
|
||||||
// kind: "friendly" or "enemy" (for ARIA / dataset)
|
|
||||||
const card = document.createElement("div");
|
const card = document.createElement("div");
|
||||||
card.classList.add("member-card");
|
card.classList.add("member-card");
|
||||||
card.classList.add(kind);
|
|
||||||
card.setAttribute("draggable", "true");
|
card.setAttribute("draggable", "true");
|
||||||
card.dataset.id = member.id;
|
card.dataset.id = member.id;
|
||||||
card.dataset.kind = kind;
|
card.dataset.kind = kind;
|
||||||
|
|
||||||
// structured children
|
|
||||||
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 === "enemy") nameDiv.style.color = "#ff6b6b"; // red
|
||||||
|
|
||||||
const statsDiv = document.createElement("div");
|
const statsDiv = document.createElement("div");
|
||||||
statsDiv.className = "stats";
|
statsDiv.className = "stats";
|
||||||
@@ -36,21 +34,17 @@ function createMemberCard(member, kind) {
|
|||||||
card.appendChild(nameDiv);
|
card.appendChild(nameDiv);
|
||||||
card.appendChild(statsDiv);
|
card.appendChild(statsDiv);
|
||||||
|
|
||||||
// store back-reference
|
|
||||||
member.domElement = card;
|
member.domElement = card;
|
||||||
|
|
||||||
// drag events: encode type and id in dataTransfer
|
|
||||||
card.addEventListener("dragstart", (e) => {
|
card.addEventListener("dragstart", (e) => {
|
||||||
const payload = JSON.stringify({ kind, id: member.id });
|
const payload = JSON.stringify({ kind, id: member.id });
|
||||||
e.dataTransfer.setData("text/plain", payload);
|
e.dataTransfer.setData("text/plain", payload);
|
||||||
// visual
|
|
||||||
card.style.opacity = "0.5";
|
card.style.opacity = "0.5";
|
||||||
});
|
});
|
||||||
card.addEventListener("dragend", () => {
|
card.addEventListener("dragend", () => {
|
||||||
card.style.opacity = "1";
|
card.style.opacity = "1";
|
||||||
});
|
});
|
||||||
|
|
||||||
// initial color
|
|
||||||
applyStatusClass(member);
|
applyStatusClass(member);
|
||||||
|
|
||||||
return card;
|
return card;
|
||||||
@@ -67,20 +61,16 @@ function applyStatusClass(member) {
|
|||||||
else if (s === "hospitalized" || s === "hospital") span.classList.add("status-hospitalized");
|
else if (s === "hospitalized" || s === "hospital") span.classList.add("status-hospitalized");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Load static members from backend into the list containers ----
|
// ---- Load static members from backend ----
|
||||||
async function loadMembers(faction) {
|
async function loadMembers(faction) {
|
||||||
const url = faction === "friendly" ? "/api/friendly_members" : "/api/enemy_members";
|
const url = faction === "friendly" ? "/api/friendly_members" : "/api/enemy_members";
|
||||||
try {
|
try {
|
||||||
const res = await fetch(url, { cache: "no-store" });
|
const res = await fetch(url, { cache: "no-store" });
|
||||||
if (!res.ok) {
|
if (!res.ok) return console.error("Failed to fetch members:", res.status);
|
||||||
console.error("Failed to fetch members:", res.status);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const arr = await res.json();
|
const arr = await res.json();
|
||||||
const container = faction === "friendly" ? friendlyContainer : enemyContainer;
|
const container = faction === "friendly" ? friendlyContainer : enemyContainer;
|
||||||
const map = faction === "friendly" ? friendlyMembers : enemyMembers;
|
const map = faction === "friendly" ? friendlyMembers : enemyMembers;
|
||||||
|
|
||||||
// clear
|
|
||||||
container.innerHTML = "";
|
container.innerHTML = "";
|
||||||
map.clear();
|
map.clear();
|
||||||
|
|
||||||
@@ -95,21 +85,18 @@ async function loadMembers(faction) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Refresh status map and update DOM (does not touch static info or position) ----
|
// ---- Refresh status map and update DOM ----
|
||||||
async function refreshStatus(faction) {
|
async function refreshStatus(faction) {
|
||||||
const url = faction === "friendly" ? "/api/friendly_status" : "/api/enemy_status";
|
const url = faction === "friendly" ? "/api/friendly_status" : "/api/enemy_status";
|
||||||
const map = faction === "friendly" ? friendlyMembers : enemyMembers;
|
const map = faction === "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) return;
|
||||||
console.warn("Status fetch failed:", res.status);
|
const statusData = await res.json();
|
||||||
return;
|
|
||||||
}
|
|
||||||
const statusData = await res.json(); // { id: { status: "..."}, ... }
|
|
||||||
Object.keys(statusData).forEach(k => {
|
Object.keys(statusData).forEach(k => {
|
||||||
const id = parseInt(k);
|
const id = parseInt(k);
|
||||||
const member = map.get(id);
|
const member = map.get(id);
|
||||||
if (!member) return; // not loaded yet or moved elsewhere
|
if (!member) return;
|
||||||
member.status = statusData[k].status;
|
member.status = statusData[k].status;
|
||||||
const span = member.domElement.querySelector(".status-text");
|
const span = member.domElement.querySelector(".status-text");
|
||||||
span.textContent = member.status;
|
span.textContent = member.status;
|
||||||
@@ -120,7 +107,7 @@ async function refreshStatus(faction) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Populate endpoints: call backend to fetch static info (FFScouter) once ----
|
// ---- Populate endpoints ----
|
||||||
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");
|
||||||
@@ -143,11 +130,19 @@ async function populateEnemy() {
|
|||||||
await loadMembers("enemy");
|
await loadMembers("enemy");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Start status refresh loops on backend and JS-side polling ----
|
// ---- Start/Stop status refresh loops ----
|
||||||
let friendlyStatusIntervalHandle = null;
|
let friendlyStatusIntervalHandle = null;
|
||||||
let enemyStatusIntervalHandle = null;
|
let enemyStatusIntervalHandle = null;
|
||||||
|
|
||||||
async function startFriendlyStatus() {
|
async function toggleFriendlyStatus() {
|
||||||
|
const btn = document.getElementById("friendly-status-btn");
|
||||||
|
if (friendlyStatusIntervalHandle) {
|
||||||
|
clearInterval(friendlyStatusIntervalHandle);
|
||||||
|
friendlyStatusIntervalHandle = null;
|
||||||
|
btn.textContent = "Start Refresh";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
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");
|
if (!id) return alert("Enter friendly faction ID");
|
||||||
@@ -158,14 +153,20 @@ async function startFriendlyStatus() {
|
|||||||
body: JSON.stringify({ faction_id: id, interval })
|
body: JSON.stringify({ faction_id: id, interval })
|
||||||
});
|
});
|
||||||
|
|
||||||
// clear existing
|
|
||||||
if (friendlyStatusIntervalHandle) clearInterval(friendlyStatusIntervalHandle);
|
|
||||||
friendlyStatusIntervalHandle = setInterval(() => refreshStatus("friendly"), interval * 1000);
|
friendlyStatusIntervalHandle = setInterval(() => refreshStatus("friendly"), interval * 1000);
|
||||||
// immediate
|
btn.textContent = "Stop Refresh";
|
||||||
refreshStatus("friendly");
|
refreshStatus("friendly");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startEnemyStatus() {
|
async function toggleEnemyStatus() {
|
||||||
|
const btn = document.getElementById("enemy-status-btn");
|
||||||
|
if (enemyStatusIntervalHandle) {
|
||||||
|
clearInterval(enemyStatusIntervalHandle);
|
||||||
|
enemyStatusIntervalHandle = null;
|
||||||
|
btn.textContent = "Start Refresh";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
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");
|
if (!id) return alert("Enter enemy faction ID");
|
||||||
@@ -176,8 +177,8 @@ async function startEnemyStatus() {
|
|||||||
body: JSON.stringify({ faction_id: id, interval })
|
body: JSON.stringify({ faction_id: id, interval })
|
||||||
});
|
});
|
||||||
|
|
||||||
if (enemyStatusIntervalHandle) clearInterval(enemyStatusIntervalHandle);
|
|
||||||
enemyStatusIntervalHandle = setInterval(() => refreshStatus("enemy"), interval * 1000);
|
enemyStatusIntervalHandle = setInterval(() => refreshStatus("enemy"), interval * 1000);
|
||||||
|
btn.textContent = "Stop Refresh";
|
||||||
refreshStatus("enemy");
|
refreshStatus("enemy");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +189,6 @@ function setupDropZones() {
|
|||||||
dropZones.forEach(zone => {
|
dropZones.forEach(zone => {
|
||||||
zone.addEventListener("dragover", (e) => {
|
zone.addEventListener("dragover", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// peek at payload to determine if valid
|
|
||||||
const raw = e.dataTransfer.types.includes("text/plain") ? e.dataTransfer.getData("text/plain") : null;
|
const raw = e.dataTransfer.types.includes("text/plain") ? e.dataTransfer.getData("text/plain") : null;
|
||||||
let valid = false;
|
let valid = false;
|
||||||
if (raw) {
|
if (raw) {
|
||||||
@@ -219,25 +219,50 @@ function setupDropZones() {
|
|||||||
const idn = parseInt(id);
|
const idn = parseInt(id);
|
||||||
if (!kind || !idn) return;
|
if (!kind || !idn) return;
|
||||||
|
|
||||||
// Determine target zone type (friendly or enemy) for validation
|
|
||||||
const zoneType = zone.classList.contains("friendly-zone") || zone.id.includes("friendly") ? "friendly" :
|
const zoneType = zone.classList.contains("friendly-zone") || zone.id.includes("friendly") ? "friendly" :
|
||||||
zone.classList.contains("enemy-zone") || zone.id.includes("enemy") ? "enemy" : null;
|
zone.classList.contains("enemy-zone") || zone.id.includes("enemy") ? "enemy" : null;
|
||||||
if (zoneType !== kind) {
|
if (zoneType !== kind) return;
|
||||||
// invalid drop
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get member object from maps
|
|
||||||
const map = kind === "friendly" ? friendlyMembers : enemyMembers;
|
const map = kind === "friendly" ? friendlyMembers : enemyMembers;
|
||||||
const member = map.get(idn);
|
const member = map.get(idn);
|
||||||
if (!member) return;
|
if (!member) return;
|
||||||
|
|
||||||
// Remove from original parent if exists (so it moves)
|
|
||||||
const prev = member.domElement?.parentElement;
|
const prev = member.domElement?.parentElement;
|
||||||
if (prev && prev !== zone) prev.removeChild(member.domElement);
|
if (prev && prev !== zone) prev.removeChild(member.domElement);
|
||||||
|
|
||||||
// Append to target zone
|
|
||||||
zone.appendChild(member.domElement);
|
zone.appendChild(member.domElement);
|
||||||
|
|
||||||
|
// persist assignment in localStorage
|
||||||
|
saveAssignments();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Persist card assignments to localStorage ----
|
||||||
|
function saveAssignments() {
|
||||||
|
const groups = document.querySelectorAll(".group");
|
||||||
|
const data = {};
|
||||||
|
groups.forEach(g => {
|
||||||
|
const gid = g.dataset.id;
|
||||||
|
data[gid] = { friendly: [], 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Restore card assignments from localStorage ----
|
||||||
|
function loadAssignments() {
|
||||||
|
const data = JSON.parse(localStorage.getItem("battleAssignments") || "{}");
|
||||||
|
Object.keys(data).forEach(gid => {
|
||||||
|
const group = document.querySelector(`.group[data-id="${gid}"]`);
|
||||||
|
if (!group) return;
|
||||||
|
data[gid].friendly.forEach(id => {
|
||||||
|
const member = friendlyMembers.get(id);
|
||||||
|
if (member) group.querySelector(".friendly-zone").appendChild(member.domElement);
|
||||||
|
});
|
||||||
|
data[gid].enemy.forEach(id => {
|
||||||
|
const member = enemyMembers.get(id);
|
||||||
|
if (member) group.querySelector(".enemy-zone").appendChild(member.domElement);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -259,8 +284,8 @@ function attachCardClickLogging() {
|
|||||||
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", startFriendlyStatus);
|
document.getElementById("friendly-status-btn").addEventListener("click", toggleFriendlyStatus);
|
||||||
document.getElementById("enemy-status-btn").addEventListener("click", startEnemyStatus);
|
document.getElementById("enemy-status-btn").addEventListener("click", toggleEnemyStatus);
|
||||||
|
|
||||||
setupDropZones();
|
setupDropZones();
|
||||||
attachCardClickLogging();
|
attachCardClickLogging();
|
||||||
@@ -269,7 +294,7 @@ function wireUp() {
|
|||||||
// ---- On load: wire and attempt to load any existing JSON lists ----
|
// ---- On load: wire and attempt to load any existing JSON lists ----
|
||||||
document.addEventListener("DOMContentLoaded", async () => {
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
wireUp();
|
wireUp();
|
||||||
// attempt to load existing data (if files exist)
|
|
||||||
await loadMembers("friendly");
|
await loadMembers("friendly");
|
||||||
await loadMembers("enemy");
|
await loadMembers("enemy");
|
||||||
|
loadAssignments(); // restore positions after load
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,17 +7,27 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
|
<!-- Top bar: Title + interval + Reset button -->
|
||||||
<div class="top-bar">
|
<div class="top-bar">
|
||||||
<h1>War Dashboard</h1>
|
<h1>War Dashboard</h1>
|
||||||
|
|
||||||
|
<div class="top-controls">
|
||||||
|
<button id="reset-groups-btn" class="reset-btn">Reset Groups</button>
|
||||||
|
|
||||||
<div class="interval-box">
|
<div class="interval-box">
|
||||||
<label for="refresh-interval">Refresh Interval (seconds)</label>
|
<label for="refresh-interval">Refresh Interval (seconds)</label>
|
||||||
<input type="number" id="refresh-interval" value="10" min="1" />
|
<input type="number" id="refresh-interval" value="10" min="1" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="main-row">
|
<div class="main-row">
|
||||||
<!-- LEFT: faction lists stacked vertically -->
|
|
||||||
|
<!-- LEFT COLUMN -->
|
||||||
<div class="left-col">
|
<div class="left-col">
|
||||||
|
|
||||||
|
<!-- FRIENDLY -->
|
||||||
<div class="faction-card small">
|
<div class="faction-card small">
|
||||||
<h2>Friendly Faction</h2>
|
<h2>Friendly Faction</h2>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
@@ -25,9 +35,13 @@
|
|||||||
<button id="friendly-populate-btn">Populate</button>
|
<button id="friendly-populate-btn">Populate</button>
|
||||||
<button id="friendly-status-btn">Start Refresh</button>
|
<button id="friendly-status-btn">Start Refresh</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="friendly-container" class="member-list" aria-label="Friendly members"></div>
|
|
||||||
|
<div id="friendly-container"
|
||||||
|
class="member-list friendly-zone"
|
||||||
|
aria-label="Friendly members"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ENEMY -->
|
||||||
<div class="faction-card small">
|
<div class="faction-card small">
|
||||||
<h2>Enemy Faction</h2>
|
<h2>Enemy Faction</h2>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
@@ -35,15 +49,19 @@
|
|||||||
<button id="enemy-populate-btn">Populate</button>
|
<button id="enemy-populate-btn">Populate</button>
|
||||||
<button id="enemy-status-btn">Start Refresh</button>
|
<button id="enemy-status-btn">Start Refresh</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="enemy-container" class="member-list" aria-label="Enemy members"></div>
|
|
||||||
|
<div id="enemy-container"
|
||||||
|
class="member-list enemy-zone"
|
||||||
|
aria-label="Enemy members"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- RIGHT: 5 groups, each with friendly + enemy drop zones -->
|
<!-- RIGHT COLUMN: BATTLE GROUPS -->
|
||||||
<div class="right-col">
|
<div class="right-col">
|
||||||
<div class="groups-grid">
|
<div class="groups-grid">
|
||||||
<!-- Generate 5 groups -->
|
|
||||||
<div class="group" id="group-1">
|
<!-- Group 1 -->
|
||||||
|
<div class="group" id="group-1" data-id="1">
|
||||||
<div class="group-title">Group 1</div>
|
<div class="group-title">Group 1</div>
|
||||||
<div class="group-zones">
|
<div class="group-zones">
|
||||||
<div class="drop-zone friendly-zone" data-zone="friendly" data-group="1" id="group-1-friendly">
|
<div class="drop-zone friendly-zone" data-zone="friendly" data-group="1" id="group-1-friendly">
|
||||||
@@ -55,7 +73,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="group" id="group-2">
|
<!-- Group 2 -->
|
||||||
|
<div class="group" id="group-2" data-id="2">
|
||||||
<div class="group-title">Group 2</div>
|
<div class="group-title">Group 2</div>
|
||||||
<div class="group-zones">
|
<div class="group-zones">
|
||||||
<div class="drop-zone friendly-zone" data-zone="friendly" data-group="2" id="group-2-friendly">
|
<div class="drop-zone friendly-zone" data-zone="friendly" data-group="2" id="group-2-friendly">
|
||||||
@@ -67,7 +86,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="group" id="group-3">
|
<!-- Group 3 -->
|
||||||
|
<div class="group" id="group-3" data-id="3">
|
||||||
<div class="group-title">Group 3</div>
|
<div class="group-title">Group 3</div>
|
||||||
<div class="group-zones">
|
<div class="group-zones">
|
||||||
<div class="drop-zone friendly-zone" data-zone="friendly" data-group="3" id="group-3-friendly">
|
<div class="drop-zone friendly-zone" data-zone="friendly" data-group="3" id="group-3-friendly">
|
||||||
@@ -79,7 +99,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="group" id="group-4">
|
<!-- Group 4 -->
|
||||||
|
<div class="group" id="group-4" data-id="4">
|
||||||
<div class="group-title">Group 4</div>
|
<div class="group-title">Group 4</div>
|
||||||
<div class="group-zones">
|
<div class="group-zones">
|
||||||
<div class="drop-zone friendly-zone" data-zone="friendly" data-group="4" id="group-4-friendly">
|
<div class="drop-zone friendly-zone" data-zone="friendly" data-group="4" id="group-4-friendly">
|
||||||
@@ -91,7 +112,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="group" id="group-5">
|
<!-- Group 5 -->
|
||||||
|
<div class="group" id="group-5" data-id="5">
|
||||||
<div class="group-title">Group 5</div>
|
<div class="group-title">Group 5</div>
|
||||||
<div class="group-zones">
|
<div class="group-zones">
|
||||||
<div class="drop-zone friendly-zone" data-zone="friendly" data-group="5" id="group-5-friendly">
|
<div class="drop-zone friendly-zone" data-zone="friendly" data-group="5" id="group-5-friendly">
|
||||||
@@ -102,10 +124,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div> <!-- groups-grid -->
|
|
||||||
</div> <!-- right-col -->
|
</div>
|
||||||
</div> <!-- main-row -->
|
</div>
|
||||||
</div> <!-- container -->
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="/static/dashboard.js"></script>
|
<script src="/static/dashboard.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user