Skip to content

Commit 80a2bb4

Browse files
committed
Fix Tools Voting links and consolidate Theme V2 color SSoT - PR_26160_061-toolbox-voting-and-color-ssot
1 parent b48ea86 commit 80a2bb4

14 files changed

Lines changed: 658 additions & 413 deletions

admin/tool-votes.html

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ <h2>Vote Review</h2>
4444
</div>
4545
<p>Vote totals and order values are read from the server API data source used by Toolbox tiles.</p>
4646
<p class="status" role="status" data-toolbox-votes-status>Loading Toolbox vote totals.</p>
47+
<p class="status" role="status" data-toolbox-votes-drag-status>Drag/drop is available when sorted by Order.</p>
4748
<div class="content-cluster" aria-label="Selected Toolbox vote item">
4849
<span class="pill">Selected Order: <span data-toolbox-votes-selected-order>None</span></span>
4950
<span class="pill">Selected Group: <span data-toolbox-votes-selected-group>None</span></span>
@@ -54,14 +55,14 @@ <h2>Vote Review</h2>
5455
<caption>Toolbox Vote Totals</caption>
5556
<thead>
5657
<tr>
57-
<th scope="col">Tool</th>
58-
<th scope="col">Order</th>
59-
<th scope="col">Group</th>
60-
<th scope="col">Path</th>
61-
<th scope="col">State</th>
62-
<th scope="col">Up</th>
63-
<th scope="col">Down</th>
64-
<th scope="col">Current User Vote</th>
58+
<th scope="col" data-toolbox-votes-sort-header="toolName"><button class="btn btn--compact" type="button" data-toolbox-votes-sort="toolName">Tool</button></th>
59+
<th scope="col" data-toolbox-votes-sort-header="order"><button class="btn btn--compact" type="button" data-toolbox-votes-sort="order">Order</button></th>
60+
<th scope="col" data-toolbox-votes-sort-header="group"><button class="btn btn--compact" type="button" data-toolbox-votes-sort="group">Group</button></th>
61+
<th scope="col" data-toolbox-votes-sort-header="path"><button class="btn btn--compact" type="button" data-toolbox-votes-sort="path">Path</button></th>
62+
<th scope="col" data-toolbox-votes-sort-header="releaseChannelLabel"><button class="btn btn--compact" type="button" data-toolbox-votes-sort="releaseChannelLabel">State</button></th>
63+
<th scope="col" data-toolbox-votes-sort-header="up"><button class="btn btn--compact" type="button" data-toolbox-votes-sort="up">Votes Up</button></th>
64+
<th scope="col" data-toolbox-votes-sort-header="down"><button class="btn btn--compact" type="button" data-toolbox-votes-sort="down">Votes Down</button></th>
65+
<th scope="col" data-toolbox-votes-sort-header="currentUserVote"><button class="btn btn--compact" type="button" data-toolbox-votes-sort="currentUserVote">Current User Vote</button></th>
6566
</tr>
6667
</thead>
6768
<tbody data-toolbox-votes-body></tbody>

admin/tool-votes.js

Lines changed: 195 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,130 @@
11
import {
22
readToolboxVoteSnapshot,
3-
updateToolboxVoteOrder,
3+
reorderToolboxVoteRows,
44
} from "../src/engine/api/toolbox-votes-api-client.js";
55

66
const status = document.querySelector("[data-toolbox-votes-status]");
7+
const dragStatus = document.querySelector("[data-toolbox-votes-drag-status]");
78
const body = document.querySelector("[data-toolbox-votes-body]");
89
const selectedOrder = document.querySelector("[data-toolbox-votes-selected-order]");
910
const selectedGroup = document.querySelector("[data-toolbox-votes-selected-group]");
1011
const selectedPath = document.querySelector("[data-toolbox-votes-selected-path]");
12+
const sortButtons = Array.from(document.querySelectorAll("[data-toolbox-votes-sort]"));
13+
const sortHeaders = Array.from(document.querySelectorAll("[data-toolbox-votes-sort-header]"));
14+
const sortButtonLabels = new Map(sortButtons.map((button) => [
15+
button.dataset.toolboxVotesSort,
16+
button.textContent.trim(),
17+
]));
18+
19+
const SORT_TYPES = Object.freeze({
20+
down: "number",
21+
order: "number",
22+
up: "number",
23+
});
1124

1225
let selectedToolId = "";
1326
let snapshotRows = [];
27+
let draggedToolId = "";
28+
let sortState = {
29+
direction: "asc",
30+
key: "order",
31+
};
1432

1533
function setStatus(message) {
1634
if (status) {
1735
status.textContent = message;
1836
}
1937
}
2038

39+
function sortLabel(key) {
40+
return sortButtonLabels.get(key) || key;
41+
}
42+
43+
function isOrderSort() {
44+
return sortState.key === "order";
45+
}
46+
47+
function wholeNumberOrder(value) {
48+
const order = Number(value);
49+
return Math.max(1, Math.round(Number.isFinite(order) ? order : 1));
50+
}
51+
2152
function tableCell(value) {
2253
const cell = document.createElement("td");
2354
cell.textContent = value === null || value === undefined ? "" : String(value);
2455
return cell;
2556
}
2657

58+
function toolNameCell(voteRow) {
59+
const cell = document.createElement("td");
60+
const link = document.createElement("a");
61+
link.href = voteRow.path || "#";
62+
link.textContent = voteRow.toolName;
63+
link.setAttribute("aria-label", `Open ${voteRow.toolName}`);
64+
cell.append(link);
65+
return cell;
66+
}
67+
68+
function orderCell(voteRow) {
69+
const cell = tableCell(wholeNumberOrder(voteRow.order));
70+
cell.dataset.toolboxVotesOrder = voteRow.toolId;
71+
cell.title = isOrderSort()
72+
? "Drag this row to update Toolbox order."
73+
: "Sort by Order to enable drag/drop ordering.";
74+
return cell;
75+
}
76+
77+
function sortValue(voteRow, key) {
78+
if (key === "currentUserVote") {
79+
return voteRow.currentUserVote || "None";
80+
}
81+
if (key === "order") {
82+
return wholeNumberOrder(voteRow.order);
83+
}
84+
return voteRow[key] ?? "";
85+
}
86+
87+
function sortedRows() {
88+
const direction = sortState.direction === "asc" ? 1 : -1;
89+
return [...snapshotRows].sort((left, right) => {
90+
const leftValue = sortValue(left, sortState.key);
91+
const rightValue = sortValue(right, sortState.key);
92+
if (SORT_TYPES[sortState.key] === "number") {
93+
return (Number(leftValue) - Number(rightValue)) * direction;
94+
}
95+
return String(leftValue).localeCompare(String(rightValue)) * direction;
96+
});
97+
}
98+
99+
function updateSortHeaders() {
100+
sortHeaders.forEach((header) => {
101+
const selected = header.dataset.toolboxVotesSortHeader === sortState.key;
102+
header.setAttribute("aria-sort", selected ? (sortState.direction === "asc" ? "ascending" : "descending") : "none");
103+
const button = header.querySelector("[data-toolbox-votes-sort]");
104+
if (!button) {
105+
return;
106+
}
107+
const baseLabel = sortButtonLabels.get(button.dataset.toolboxVotesSort) || button.textContent.trim();
108+
button.textContent = selected ? `${baseLabel} ${sortState.direction === "asc" ? "↑" : "↓"}` : baseLabel;
109+
button.classList.toggle("primary", selected);
110+
button.setAttribute("aria-pressed", String(selected));
111+
});
112+
}
113+
114+
function updateDragStatus() {
115+
if (!dragStatus) {
116+
return;
117+
}
118+
if (isOrderSort()) {
119+
dragStatus.textContent = "Drag/drop row ordering is enabled while sorted by Order.";
120+
return;
121+
}
122+
dragStatus.textContent = `Drag/drop row ordering is disabled while sorted by ${sortLabel(sortState.key)}. Sort by Order to reorder.`;
123+
}
124+
27125
function setSelectedDetails(voteRow) {
28126
if (selectedOrder) {
29-
selectedOrder.textContent = voteRow ? String(voteRow.order) : "None";
127+
selectedOrder.textContent = voteRow ? String(wholeNumberOrder(voteRow.order)) : "None";
30128
}
31129
if (selectedGroup) {
32130
selectedGroup.textContent = voteRow?.group || "None";
@@ -45,31 +143,72 @@ function selectRow(toolId) {
45143
});
46144
}
47145

48-
function orderCell(voteRow) {
49-
const cell = document.createElement("td");
50-
const input = document.createElement("input");
51-
input.type = "number";
52-
input.min = "0";
53-
input.step = "1";
54-
input.value = String(voteRow.order);
55-
input.dataset.toolboxVotesOrderInput = voteRow.toolId;
56-
input.setAttribute("aria-label", `Order for ${voteRow.toolName}`);
57-
input.addEventListener("change", () => {
58-
updateOrder(voteRow, input.value);
59-
});
60-
input.addEventListener("focus", () => {
61-
selectRow(voteRow.toolId);
62-
});
63-
cell.append(input);
64-
return cell;
146+
function orderedToolIdsAfterDrop(targetToolId, event) {
147+
const displayToolIds = sortedRows().map((row) => row.toolId).filter((toolId) => toolId !== draggedToolId);
148+
const targetIndex = displayToolIds.indexOf(targetToolId);
149+
if (targetIndex === -1) {
150+
return null;
151+
}
152+
const targetRow = event.currentTarget;
153+
const targetBox = targetRow.getBoundingClientRect();
154+
const insertAfterTarget = event.clientY > targetBox.top + (targetBox.height / 2);
155+
displayToolIds.splice(targetIndex + (insertAfterTarget ? 1 : 0), 0, draggedToolId);
156+
return sortState.direction === "desc" ? displayToolIds.reverse() : displayToolIds;
157+
}
158+
159+
function handleDragStart(event, voteRow) {
160+
if (!isOrderSort()) {
161+
event.preventDefault();
162+
updateDragStatus();
163+
return;
164+
}
165+
draggedToolId = voteRow.toolId;
166+
selectRow(voteRow.toolId);
167+
event.dataTransfer.effectAllowed = "move";
168+
event.dataTransfer.setData("text/plain", voteRow.toolId);
169+
}
170+
171+
function handleDragOver(event) {
172+
if (!isOrderSort() || !draggedToolId) {
173+
return;
174+
}
175+
event.preventDefault();
176+
event.dataTransfer.dropEffect = "move";
177+
}
178+
179+
function handleDrop(event, targetToolId) {
180+
if (!isOrderSort() || !draggedToolId || draggedToolId === targetToolId) {
181+
draggedToolId = "";
182+
return;
183+
}
184+
event.preventDefault();
185+
const orderedToolIds = orderedToolIdsAfterDrop(targetToolId, event);
186+
if (!orderedToolIds) {
187+
draggedToolId = "";
188+
return;
189+
}
190+
const movedToolId = draggedToolId;
191+
try {
192+
const snapshot = reorderToolboxVoteRows(orderedToolIds);
193+
renderSnapshot(snapshot, "Toolbox vote order updated. Rows were renumbered with whole-number order values.");
194+
selectRow(movedToolId);
195+
} catch (error) {
196+
const message = error instanceof Error ? error.message : String(error || "Toolbox vote row order unavailable.");
197+
setStatus(`Toolbox vote rows could not be reordered. ${message}`);
198+
} finally {
199+
draggedToolId = "";
200+
}
65201
}
66202

67203
function renderRows(rows) {
68204
if (!body) {
69205
return;
70206
}
71207
snapshotRows = rows;
72-
if (!rows.length) {
208+
updateSortHeaders();
209+
updateDragStatus();
210+
const displayRows = sortedRows();
211+
if (!displayRows.length) {
73212
const row = document.createElement("tr");
74213
const cell = document.createElement("td");
75214
cell.colSpan = 8;
@@ -80,14 +219,28 @@ function renderRows(rows) {
80219
return;
81220
}
82221

83-
body.replaceChildren(...rows.map((voteRow) => {
222+
const allowDrag = isOrderSort();
223+
body.replaceChildren(...displayRows.map((voteRow) => {
84224
const row = document.createElement("tr");
85225
row.dataset.toolboxVotesToolId = voteRow.toolId;
226+
row.dataset.toolboxVotesDrag = allowDrag ? "enabled" : "disabled";
227+
row.setAttribute("aria-disabled", String(!allowDrag));
228+
row.setAttribute("draggable", String(allowDrag));
229+
row.title = allowDrag
230+
? "Drag this row to reorder Toolbox tools."
231+
: `Drag/drop disabled while sorted by ${sortLabel(sortState.key)}.`;
86232
row.addEventListener("click", () => {
87233
selectRow(voteRow.toolId);
88234
});
235+
row.addEventListener("dragstart", (event) => {
236+
handleDragStart(event, voteRow);
237+
});
238+
row.addEventListener("dragover", handleDragOver);
239+
row.addEventListener("drop", (event) => {
240+
handleDrop(event, voteRow.toolId);
241+
});
89242
row.append(
90-
tableCell(voteRow.toolName),
243+
toolNameCell(voteRow),
91244
orderCell(voteRow),
92245
tableCell(voteRow.group),
93246
tableCell(voteRow.path),
@@ -99,25 +252,15 @@ function renderRows(rows) {
99252
return row;
100253
}));
101254

102-
selectRow(selectedToolId || rows[0].toolId);
255+
const selectedRow = snapshotRows.find((row) => row.toolId === selectedToolId);
256+
selectRow(selectedRow ? selectedToolId : displayRows[0].toolId);
103257
}
104258

105259
function renderSnapshot(snapshot, message) {
106260
renderRows(snapshot.rows || []);
107261
setStatus(message || `Showing Toolbox vote totals for ${snapshot.currentUserName || "current session"}.`);
108262
}
109263

110-
function updateOrder(voteRow, value) {
111-
try {
112-
const snapshot = updateToolboxVoteOrder(voteRow.toolId, Number(value));
113-
renderSnapshot(snapshot, `${voteRow.toolName} order updated to ${Number(value)}.`);
114-
selectRow(voteRow.toolId);
115-
} catch (error) {
116-
const message = error instanceof Error ? error.message : String(error || "Toolbox vote order unavailable.");
117-
setStatus(`Toolbox vote order could not be updated. ${message}`);
118-
}
119-
}
120-
121264
function renderToolboxVotes() {
122265
if (window.GameFoundrySessionGuard?.blocked) {
123266
return;
@@ -131,4 +274,22 @@ function renderToolboxVotes() {
131274
}
132275
}
133276

277+
sortButtons.forEach((button) => {
278+
button.addEventListener("click", () => {
279+
const nextKey = button.dataset.toolboxVotesSort;
280+
if (sortState.key === nextKey) {
281+
sortState = {
282+
direction: sortState.direction === "asc" ? "desc" : "asc",
283+
key: nextKey,
284+
};
285+
} else {
286+
sortState = {
287+
direction: "asc",
288+
key: nextKey,
289+
};
290+
}
291+
renderRows(snapshotRows);
292+
});
293+
});
294+
134295
renderToolboxVotes();

assets/theme-v2/css/colors.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
--purple: #b877ff;
1313
--green: #7dd957;
1414
--pink: #ff4f8b;
15-
--red: #ff4d4d;
15+
--red: #ff2d2d;
1616
--deep-red: #ff5757;
1717
--mint: #22c55e;
1818
--white: #ffffff;

0 commit comments

Comments
 (0)