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
<pclass="status" role="status" data-toolbox-votes-status>Loading Toolbox vote totals.</p>
47+
<pclass="status" role="status" data-toolbox-votes-drag-status>Drag/drop is available when sorted by Order.</p>
4748
<divclass="content-cluster" aria-label="Selected Toolbox vote item">
4849
<spanclass="pill">Selected Order: <spandata-toolbox-votes-selected-order>None</span></span>
4950
<spanclass="pill">Selected Group: <spandata-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-
<thscope="col">Tool</th>
58-
<thscope="col">Order</th>
59-
<thscope="col">Group</th>
60-
<thscope="col">Path</th>
61-
<thscope="col">State</th>
62-
<thscope="col">Up</th>
63-
<thscope="col">Down</th>
64-
<thscope="col">Current User Vote</th>
58+
<thscope="col"data-toolbox-votes-sort-header="toolName"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="toolName">Tool</button></th>
59+
<thscope="col"data-toolbox-votes-sort-header="order"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="order">Order</button></th>
60+
<thscope="col"data-toolbox-votes-sort-header="group"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="group">Group</button></th>
61+
<thscope="col"data-toolbox-votes-sort-header="path"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="path">Path</button></th>
62+
<thscope="col"data-toolbox-votes-sort-header="releaseChannelLabel"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="releaseChannelLabel">State</button></th>
63+
<thscope="col"data-toolbox-votes-sort-header="up"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="up">Votes Up</button></th>
64+
<thscope="col"data-toolbox-votes-sort-header="down"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="down">Votes Down</button></th>
65+
<thscope="col"data-toolbox-votes-sort-header="currentUserVote"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="currentUserVote">Current User Vote</button></th>
6566
</tr>
6667
</thead>
6768
<tbodydata-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
conststatus=document.querySelector("[data-toolbox-votes-status]");
7+
constdragStatus=document.querySelector("[data-toolbox-votes-drag-status]");
78
constbody=document.querySelector("[data-toolbox-votes-body]");
89
constselectedOrder=document.querySelector("[data-toolbox-votes-selected-order]");
910
constselectedGroup=document.querySelector("[data-toolbox-votes-selected-group]");
1011
constselectedPath=document.querySelector("[data-toolbox-votes-selected-path]");
12+
constsortButtons=Array.from(document.querySelectorAll("[data-toolbox-votes-sort]"));
13+
constsortHeaders=Array.from(document.querySelectorAll("[data-toolbox-votes-sort-header]"));
14+
constsortButtonLabels=newMap(sortButtons.map((button)=>[
15+
button.dataset.toolboxVotesSort,
16+
button.textContent.trim(),
17+
]));
18+
19+
constSORT_TYPES=Object.freeze({
20+
down: "number",
21+
order: "number",
22+
up: "number",
23+
});
1124

1225
letselectedToolId="";
1326
letsnapshotRows=[];
27+
letdraggedToolId="";
28+
letsortState={
29+
direction: "asc",
30+
key: "order",
31+
};
1432

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

39+
functionsortLabel(key){
40+
returnsortButtonLabels.get(key)||key;
41+
}
42+
43+
functionisOrderSort(){
44+
returnsortState.key==="order";
45+
}
46+
47+
functionwholeNumberOrder(value){
48+
constorder=Number(value);
49+
returnMath.max(1,Math.round(Number.isFinite(order) ? order : 1));
50+
}
51+
2152
functiontableCell(value){
2253
constcell=document.createElement("td");
2354
cell.textContent=value===null||value===undefined ? "" : String(value);
2455
returncell;
2556
}
2657

58+
functiontoolNameCell(voteRow){
59+
constcell=document.createElement("td");
60+
constlink=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+
returncell;
66+
}
67+
68+
functionorderCell(voteRow){
69+
constcell=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+
returncell;
75+
}
76+
77+
functionsortValue(voteRow,key){
78+
if(key==="currentUserVote"){
79+
returnvoteRow.currentUserVote||"None";
80+
}
81+
if(key==="order"){
82+
returnwholeNumberOrder(voteRow.order);
83+
}
84+
returnvoteRow[key]??"";
85+
}
86+
87+
functionsortedRows(){
88+
constdirection=sortState.direction==="asc" ? 1 : -1;
89+
return[...snapshotRows].sort((left,right)=>{
90+
constleftValue=sortValue(left,sortState.key);
91+
constrightValue=sortValue(right,sortState.key);
92+
if(SORT_TYPES[sortState.key]==="number"){
93+
return(Number(leftValue)-Number(rightValue))*direction;
94+
}
95+
returnString(leftValue).localeCompare(String(rightValue))*direction;
96+
});
97+
}
98+
99+
functionupdateSortHeaders(){
100+
sortHeaders.forEach((header)=>{
101+
constselected=header.dataset.toolboxVotesSortHeader===sortState.key;
102+
header.setAttribute("aria-sort",selected ? (sortState.direction==="asc" ? "ascending" : "descending") : "none");
103+
constbutton=header.querySelector("[data-toolbox-votes-sort]");
104+
if(!button){
105+
return;
106+
}
107+
constbaseLabel=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+
functionupdateDragStatus(){
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
functionsetSelectedDetails(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-
functionorderCell(voteRow){
49-
constcell=document.createElement("td");
50-
constinput=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-
returncell;
146+
functionorderedToolIdsAfterDrop(targetToolId,event){
147+
constdisplayToolIds=sortedRows().map((row)=>row.toolId).filter((toolId)=>toolId!==draggedToolId);
148+
consttargetIndex=displayToolIds.indexOf(targetToolId);
149+
if(targetIndex===-1){
150+
returnnull;
151+
}
152+
consttargetRow=event.currentTarget;
153+
consttargetBox=targetRow.getBoundingClientRect();
154+
constinsertAfterTarget=event.clientY>targetBox.top+(targetBox.height/2);
155+
displayToolIds.splice(targetIndex+(insertAfterTarget ? 1 : 0),0,draggedToolId);
156+
returnsortState.direction==="desc" ? displayToolIds.reverse() : displayToolIds;
157+
}
158+
159+
functionhandleDragStart(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+
functionhandleDragOver(event){
172+
if(!isOrderSort()||!draggedToolId){
173+
return;
174+
}
175+
event.preventDefault();
176+
event.dataTransfer.dropEffect="move";
177+
}
178+
179+
functionhandleDrop(event,targetToolId){
180+
if(!isOrderSort()||!draggedToolId||draggedToolId===targetToolId){
181+
draggedToolId="";
182+
return;
183+
}
184+
event.preventDefault();
185+
constorderedToolIds=orderedToolIdsAfterDrop(targetToolId,event);
186+
if(!orderedToolIds){
187+
draggedToolId="";
188+
return;
189+
}
190+
constmovedToolId=draggedToolId;
191+
try{
192+
constsnapshot=reorderToolboxVoteRows(orderedToolIds);
193+
renderSnapshot(snapshot,"Toolbox vote order updated. Rows were renumbered with whole-number order values.");
194+
selectRow(movedToolId);
195+
}catch(error){
196+
constmessage=errorinstanceofError ? 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
functionrenderRows(rows){
68204
if(!body){
69205
return;
70206
}
71207
snapshotRows=rows;
72-
if(!rows.length){
208+
updateSortHeaders();
209+
updateDragStatus();
210+
constdisplayRows=sortedRows();
211+
if(!displayRows.length){
73212
constrow=document.createElement("tr");
74213
constcell=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+
constallowDrag=isOrderSort();
223+
body.replaceChildren(...displayRows.map((voteRow)=>{
84224
constrow=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
returnrow;
100253
}));
101254

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

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

110-
functionupdateOrder(voteRow,value){
111-
try{
112-
constsnapshot=updateToolboxVoteOrder(voteRow.toolId,Number(value));
113-
renderSnapshot(snapshot,`${voteRow.toolName} order updated to ${Number(value)}.`);
114-
selectRow(voteRow.toolId);
115-
}catch(error){
116-
constmessage=errorinstanceofError ? error.message : String(error||"Toolbox vote order unavailable.");
117-
setStatus(`Toolbox vote order could not be updated. ${message}`);
118-
}
119-
}
120-
121264
functionrenderToolboxVotes(){
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+
constnextKey=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)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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
<pclass="status" role="status" data-toolbox-votes-status>Loading Toolbox vote totals.</p>
47+
<pclass="status" role="status" data-toolbox-votes-drag-status>Drag/drop is available when sorted by Order.</p>
4748
<divclass="content-cluster" aria-label="Selected Toolbox vote item">
4849
<spanclass="pill">Selected Order: <spandata-toolbox-votes-selected-order>None</span></span>
4950
<spanclass="pill">Selected Group: <spandata-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-
<thscope="col">Tool</th>
58-
<thscope="col">Order</th>
59-
<thscope="col">Group</th>
60-
<thscope="col">Path</th>
61-
<thscope="col">State</th>
62-
<thscope="col">Up</th>
63-
<thscope="col">Down</th>
64-
<thscope="col">Current User Vote</th>
58+
<thscope="col"data-toolbox-votes-sort-header="toolName"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="toolName">Tool</button></th>
59+
<thscope="col"data-toolbox-votes-sort-header="order"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="order">Order</button></th>
60+
<thscope="col"data-toolbox-votes-sort-header="group"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="group">Group</button></th>
61+
<thscope="col"data-toolbox-votes-sort-header="path"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="path">Path</button></th>
62+
<thscope="col"data-toolbox-votes-sort-header="releaseChannelLabel"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="releaseChannelLabel">State</button></th>
63+
<thscope="col"data-toolbox-votes-sort-header="up"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="up">Votes Up</button></th>
64+
<thscope="col"data-toolbox-votes-sort-header="down"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="down">Votes Down</button></th>
65+
<thscope="col"data-toolbox-votes-sort-header="currentUserVote"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="currentUserVote">Current User Vote</button></th>
6566
</tr>
6667
</thead>
6768
<tbodydata-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
conststatus=document.querySelector("[data-toolbox-votes-status]");
7+
constdragStatus=document.querySelector("[data-toolbox-votes-drag-status]");
78
constbody=document.querySelector("[data-toolbox-votes-body]");
89
constselectedOrder=document.querySelector("[data-toolbox-votes-selected-order]");
910
constselectedGroup=document.querySelector("[data-toolbox-votes-selected-group]");
1011
constselectedPath=document.querySelector("[data-toolbox-votes-selected-path]");
12+
constsortButtons=Array.from(document.querySelectorAll("[data-toolbox-votes-sort]"));
13+
constsortHeaders=Array.from(document.querySelectorAll("[data-toolbox-votes-sort-header]"));
14+
constsortButtonLabels=newMap(sortButtons.map((button)=>[
15+
button.dataset.toolboxVotesSort,
16+
button.textContent.trim(),
17+
]));
18+
19+
constSORT_TYPES=Object.freeze({
20+
down: "number",
21+
order: "number",
22+
up: "number",
23+
});
1124

1225
letselectedToolId="";
1326
letsnapshotRows=[];
27+
letdraggedToolId="";
28+
letsortState={
29+
direction: "asc",
30+
key: "order",
31+
};
1432

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

39+
functionsortLabel(key){
40+
returnsortButtonLabels.get(key)||key;
41+
}
42+
43+
functionisOrderSort(){
44+
returnsortState.key==="order";
45+
}
46+
47+
functionwholeNumberOrder(value){
48+
constorder=Number(value);
49+
returnMath.max(1,Math.round(Number.isFinite(order) ? order : 1));
50+
}
51+
2152
functiontableCell(value){
2253
constcell=document.createElement("td");
2354
cell.textContent=value===null||value===undefined ? "" : String(value);
2455
returncell;
2556
}
2657

58+
functiontoolNameCell(voteRow){
59+
constcell=document.createElement("td");
60+
constlink=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+
returncell;
66+
}
67+
68+
functionorderCell(voteRow){
69+
constcell=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+
returncell;
75+
}
76+
77+
functionsortValue(voteRow,key){
78+
if(key==="currentUserVote"){
79+
returnvoteRow.currentUserVote||"None";
80+
}
81+
if(key==="order"){
82+
returnwholeNumberOrder(voteRow.order);
83+
}
84+
returnvoteRow[key]??"";
85+
}
86+
87+
functionsortedRows(){
88+
constdirection=sortState.direction==="asc" ? 1 : -1;
89+
return[...snapshotRows].sort((left,right)=>{
90+
constleftValue=sortValue(left,sortState.key);
91+
constrightValue=sortValue(right,sortState.key);
92+
if(SORT_TYPES[sortState.key]==="number"){
93+
return(Number(leftValue)-Number(rightValue))*direction;
94+
}
95+
returnString(leftValue).localeCompare(String(rightValue))*direction;
96+
});
97+
}
98+
99+
functionupdateSortHeaders(){
100+
sortHeaders.forEach((header)=>{
101+
constselected=header.dataset.toolboxVotesSortHeader===sortState.key;
102+
header.setAttribute("aria-sort",selected ? (sortState.direction==="asc" ? "ascending" : "descending") : "none");
103+
constbutton=header.querySelector("[data-toolbox-votes-sort]");
104+
if(!button){
105+
return;
106+
}
107+
constbaseLabel=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+
functionupdateDragStatus(){
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
functionsetSelectedDetails(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-
functionorderCell(voteRow){
49-
constcell=document.createElement("td");
50-
constinput=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-
returncell;
146+
functionorderedToolIdsAfterDrop(targetToolId,event){
147+
constdisplayToolIds=sortedRows().map((row)=>row.toolId).filter((toolId)=>toolId!==draggedToolId);
148+
consttargetIndex=displayToolIds.indexOf(targetToolId);
149+
if(targetIndex===-1){
150+
returnnull;
151+
}
152+
consttargetRow=event.currentTarget;
153+
consttargetBox=targetRow.getBoundingClientRect();
154+
constinsertAfterTarget=event.clientY>targetBox.top+(targetBox.height/2);
155+
displayToolIds.splice(targetIndex+(insertAfterTarget ? 1 : 0),0,draggedToolId);
156+
returnsortState.direction==="desc" ? displayToolIds.reverse() : displayToolIds;
157+
}
158+
159+
functionhandleDragStart(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+
functionhandleDragOver(event){
172+
if(!isOrderSort()||!draggedToolId){
173+
return;
174+
}
175+
event.preventDefault();
176+
event.dataTransfer.dropEffect="move";
177+
}
178+
179+
functionhandleDrop(event,targetToolId){
180+
if(!isOrderSort()||!draggedToolId||draggedToolId===targetToolId){
181+
draggedToolId="";
182+
return;
183+
}
184+
event.preventDefault();
185+
constorderedToolIds=orderedToolIdsAfterDrop(targetToolId,event);
186+
if(!orderedToolIds){
187+
draggedToolId="";
188+
return;
189+
}
190+
constmovedToolId=draggedToolId;
191+
try{
192+
constsnapshot=reorderToolboxVoteRows(orderedToolIds);
193+
renderSnapshot(snapshot,"Toolbox vote order updated. Rows were renumbered with whole-number order values.");
194+
selectRow(movedToolId);
195+
}catch(error){
196+
constmessage=errorinstanceofError ? 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
functionrenderRows(rows){
68204
if(!body){
69205
return;
70206
}
71207
snapshotRows=rows;
72-
if(!rows.length){
208+
updateSortHeaders();
209+
updateDragStatus();
210+
constdisplayRows=sortedRows();
211+
if(!displayRows.length){
73212
constrow=document.createElement("tr");
74213
constcell=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+
constallowDrag=isOrderSort();
223+
body.replaceChildren(...displayRows.map((voteRow)=>{
84224
constrow=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
returnrow;
100253
}));
101254

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

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

110-
functionupdateOrder(voteRow,value){
111-
try{
112-
constsnapshot=updateToolboxVoteOrder(voteRow.toolId,Number(value));
113-
renderSnapshot(snapshot,`${voteRow.toolName} order updated to ${Number(value)}.`);
114-
selectRow(voteRow.toolId);
115-
}catch(error){
116-
constmessage=errorinstanceofError ? error.message : String(error||"Toolbox vote order unavailable.");
117-
setStatus(`Toolbox vote order could not be updated. ${message}`);
118-
}
119-
}
120-
121264
functionrenderToolboxVotes(){
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+
constnextKey=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)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
<pclass="status" role="status" data-toolbox-votes-status>Loading Toolbox vote totals.</p>
47+
<pclass="status" role="status" data-toolbox-votes-drag-status>Drag/drop is available when sorted by Order.</p>
4748
<divclass="content-cluster" aria-label="Selected Toolbox vote item">
4849
<spanclass="pill">Selected Order: <spandata-toolbox-votes-selected-order>None</span></span>
4950
<spanclass="pill">Selected Group: <spandata-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-
<thscope="col">Tool</th>
58-
<thscope="col">Order</th>
59-
<thscope="col">Group</th>
60-
<thscope="col">Path</th>
61-
<thscope="col">State</th>
62-
<thscope="col">Up</th>
63-
<thscope="col">Down</th>
64-
<thscope="col">Current User Vote</th>
58+
<thscope="col"data-toolbox-votes-sort-header="toolName"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="toolName">Tool</button></th>
59+
<thscope="col"data-toolbox-votes-sort-header="order"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="order">Order</button></th>
60+
<thscope="col"data-toolbox-votes-sort-header="group"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="group">Group</button></th>
61+
<thscope="col"data-toolbox-votes-sort-header="path"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="path">Path</button></th>
62+
<thscope="col"data-toolbox-votes-sort-header="releaseChannelLabel"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="releaseChannelLabel">State</button></th>
63+
<thscope="col"data-toolbox-votes-sort-header="up"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="up">Votes Up</button></th>
64+
<thscope="col"data-toolbox-votes-sort-header="down"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="down">Votes Down</button></th>
65+
<thscope="col"data-toolbox-votes-sort-header="currentUserVote"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="currentUserVote">Current User Vote</button></th>
6566
</tr>
6667
</thead>
6768
<tbodydata-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
conststatus=document.querySelector("[data-toolbox-votes-status]");
7+
constdragStatus=document.querySelector("[data-toolbox-votes-drag-status]");
78
constbody=document.querySelector("[data-toolbox-votes-body]");
89
constselectedOrder=document.querySelector("[data-toolbox-votes-selected-order]");
910
constselectedGroup=document.querySelector("[data-toolbox-votes-selected-group]");
1011
constselectedPath=document.querySelector("[data-toolbox-votes-selected-path]");
12+
constsortButtons=Array.from(document.querySelectorAll("[data-toolbox-votes-sort]"));
13+
constsortHeaders=Array.from(document.querySelectorAll("[data-toolbox-votes-sort-header]"));
14+
constsortButtonLabels=newMap(sortButtons.map((button)=>[
15+
button.dataset.toolboxVotesSort,
16+
button.textContent.trim(),
17+
]));
18+
19+
constSORT_TYPES=Object.freeze({
20+
down: "number",
21+
order: "number",
22+
up: "number",
23+
});
1124

1225
letselectedToolId="";
1326
letsnapshotRows=[];
27+
letdraggedToolId="";
28+
letsortState={
29+
direction: "asc",
30+
key: "order",
31+
};
1432

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

39+
functionsortLabel(key){
40+
returnsortButtonLabels.get(key)||key;
41+
}
42+
43+
functionisOrderSort(){
44+
returnsortState.key==="order";
45+
}
46+
47+
functionwholeNumberOrder(value){
48+
constorder=Number(value);
49+
returnMath.max(1,Math.round(Number.isFinite(order) ? order : 1));
50+
}
51+
2152
functiontableCell(value){
2253
constcell=document.createElement("td");
2354
cell.textContent=value===null||value===undefined ? "" : String(value);
2455
returncell;
2556
}
2657

58+
functiontoolNameCell(voteRow){
59+
constcell=document.createElement("td");
60+
constlink=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+
returncell;
66+
}
67+
68+
functionorderCell(voteRow){
69+
constcell=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+
returncell;
75+
}
76+
77+
functionsortValue(voteRow,key){
78+
if(key==="currentUserVote"){
79+
returnvoteRow.currentUserVote||"None";
80+
}
81+
if(key==="order"){
82+
returnwholeNumberOrder(voteRow.order);
83+
}
84+
returnvoteRow[key]??"";
85+
}
86+
87+
functionsortedRows(){
88+
constdirection=sortState.direction==="asc" ? 1 : -1;
89+
return[...snapshotRows].sort((left,right)=>{
90+
constleftValue=sortValue(left,sortState.key);
91+
constrightValue=sortValue(right,sortState.key);
92+
if(SORT_TYPES[sortState.key]==="number"){
93+
return(Number(leftValue)-Number(rightValue))*direction;
94+
}
95+
returnString(leftValue).localeCompare(String(rightValue))*direction;
96+
});
97+
}
98+
99+
functionupdateSortHeaders(){
100+
sortHeaders.forEach((header)=>{
101+
constselected=header.dataset.toolboxVotesSortHeader===sortState.key;
102+
header.setAttribute("aria-sort",selected ? (sortState.direction==="asc" ? "ascending" : "descending") : "none");
103+
constbutton=header.querySelector("[data-toolbox-votes-sort]");
104+
if(!button){
105+
return;
106+
}
107+
constbaseLabel=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+
functionupdateDragStatus(){
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
functionsetSelectedDetails(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-
functionorderCell(voteRow){
49-
constcell=document.createElement("td");
50-
constinput=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-
returncell;
146+
functionorderedToolIdsAfterDrop(targetToolId,event){
147+
constdisplayToolIds=sortedRows().map((row)=>row.toolId).filter((toolId)=>toolId!==draggedToolId);
148+
consttargetIndex=displayToolIds.indexOf(targetToolId);
149+
if(targetIndex===-1){
150+
returnnull;
151+
}
152+
consttargetRow=event.currentTarget;
153+
consttargetBox=targetRow.getBoundingClientRect();
154+
constinsertAfterTarget=event.clientY>targetBox.top+(targetBox.height/2);
155+
displayToolIds.splice(targetIndex+(insertAfterTarget ? 1 : 0),0,draggedToolId);
156+
returnsortState.direction==="desc" ? displayToolIds.reverse() : displayToolIds;
157+
}
158+
159+
functionhandleDragStart(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+
functionhandleDragOver(event){
172+
if(!isOrderSort()||!draggedToolId){
173+
return;
174+
}
175+
event.preventDefault();
176+
event.dataTransfer.dropEffect="move";
177+
}
178+
179+
functionhandleDrop(event,targetToolId){
180+
if(!isOrderSort()||!draggedToolId||draggedToolId===targetToolId){
181+
draggedToolId="";
182+
return;
183+
}
184+
event.preventDefault();
185+
constorderedToolIds=orderedToolIdsAfterDrop(targetToolId,event);
186+
if(!orderedToolIds){
187+
draggedToolId="";
188+
return;
189+
}
190+
constmovedToolId=draggedToolId;
191+
try{
192+
constsnapshot=reorderToolboxVoteRows(orderedToolIds);
193+
renderSnapshot(snapshot,"Toolbox vote order updated. Rows were renumbered with whole-number order values.");
194+
selectRow(movedToolId);
195+
}catch(error){
196+
constmessage=errorinstanceofError ? 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
functionrenderRows(rows){
68204
if(!body){
69205
return;
70206
}
71207
snapshotRows=rows;
72-
if(!rows.length){
208+
updateSortHeaders();
209+
updateDragStatus();
210+
constdisplayRows=sortedRows();
211+
if(!displayRows.length){
73212
constrow=document.createElement("tr");
74213
constcell=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+
constallowDrag=isOrderSort();
223+
body.replaceChildren(...displayRows.map((voteRow)=>{
84224
constrow=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
returnrow;
100253
}));
101254

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

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

110-
functionupdateOrder(voteRow,value){
111-
try{
112-
constsnapshot=updateToolboxVoteOrder(voteRow.toolId,Number(value));
113-
renderSnapshot(snapshot,`${voteRow.toolName} order updated to ${Number(value)}.`);
114-
selectRow(voteRow.toolId);
115-
}catch(error){
116-
constmessage=errorinstanceofError ? error.message : String(error||"Toolbox vote order unavailable.");
117-
setStatus(`Toolbox vote order could not be updated. ${message}`);
118-
}
119-
}
120-
121264
functionrenderToolboxVotes(){
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+
constnextKey=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)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
<pclass="status" role="status" data-toolbox-votes-status>Loading Toolbox vote totals.</p>
47+
<pclass="status" role="status" data-toolbox-votes-drag-status>Drag/drop is available when sorted by Order.</p>
4748
<divclass="content-cluster" aria-label="Selected Toolbox vote item">
4849
<spanclass="pill">Selected Order: <spandata-toolbox-votes-selected-order>None</span></span>
4950
<spanclass="pill">Selected Group: <spandata-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-
<thscope="col">Tool</th>
58-
<thscope="col">Order</th>
59-
<thscope="col">Group</th>
60-
<thscope="col">Path</th>
61-
<thscope="col">State</th>
62-
<thscope="col">Up</th>
63-
<thscope="col">Down</th>
64-
<thscope="col">Current User Vote</th>
58+
<thscope="col"data-toolbox-votes-sort-header="toolName"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="toolName">Tool</button></th>
59+
<thscope="col"data-toolbox-votes-sort-header="order"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="order">Order</button></th>
60+
<thscope="col"data-toolbox-votes-sort-header="group"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="group">Group</button></th>
61+
<thscope="col"data-toolbox-votes-sort-header="path"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="path">Path</button></th>
62+
<thscope="col"data-toolbox-votes-sort-header="releaseChannelLabel"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="releaseChannelLabel">State</button></th>
63+
<thscope="col"data-toolbox-votes-sort-header="up"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="up">Votes Up</button></th>
64+
<thscope="col"data-toolbox-votes-sort-header="down"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="down">Votes Down</button></th>
65+
<thscope="col"data-toolbox-votes-sort-header="currentUserVote"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="currentUserVote">Current User Vote</button></th>
6566
</tr>
6667
</thead>
6768
<tbodydata-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
conststatus=document.querySelector("[data-toolbox-votes-status]");
7+
constdragStatus=document.querySelector("[data-toolbox-votes-drag-status]");
78
constbody=document.querySelector("[data-toolbox-votes-body]");
89
constselectedOrder=document.querySelector("[data-toolbox-votes-selected-order]");
910
constselectedGroup=document.querySelector("[data-toolbox-votes-selected-group]");
1011
constselectedPath=document.querySelector("[data-toolbox-votes-selected-path]");
12+
constsortButtons=Array.from(document.querySelectorAll("[data-toolbox-votes-sort]"));
13+
constsortHeaders=Array.from(document.querySelectorAll("[data-toolbox-votes-sort-header]"));
14+
constsortButtonLabels=newMap(sortButtons.map((button)=>[
15+
button.dataset.toolboxVotesSort,
16+
button.textContent.trim(),
17+
]));
18+
19+
constSORT_TYPES=Object.freeze({
20+
down: "number",
21+
order: "number",
22+
up: "number",
23+
});
1124

1225
letselectedToolId="";
1326
letsnapshotRows=[];
27+
letdraggedToolId="";
28+
letsortState={
29+
direction: "asc",
30+
key: "order",
31+
};
1432

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

39+
functionsortLabel(key){
40+
returnsortButtonLabels.get(key)||key;
41+
}
42+
43+
functionisOrderSort(){
44+
returnsortState.key==="order";
45+
}
46+
47+
functionwholeNumberOrder(value){
48+
constorder=Number(value);
49+
returnMath.max(1,Math.round(Number.isFinite(order) ? order : 1));
50+
}
51+
2152
functiontableCell(value){
2253
constcell=document.createElement("td");
2354
cell.textContent=value===null||value===undefined ? "" : String(value);
2455
returncell;
2556
}
2657

58+
functiontoolNameCell(voteRow){
59+
constcell=document.createElement("td");
60+
constlink=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+
returncell;
66+
}
67+
68+
functionorderCell(voteRow){
69+
constcell=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+
returncell;
75+
}
76+
77+
functionsortValue(voteRow,key){
78+
if(key==="currentUserVote"){
79+
returnvoteRow.currentUserVote||"None";
80+
}
81+
if(key==="order"){
82+
returnwholeNumberOrder(voteRow.order);
83+
}
84+
returnvoteRow[key]??"";
85+
}
86+
87+
functionsortedRows(){
88+
constdirection=sortState.direction==="asc" ? 1 : -1;
89+
return[...snapshotRows].sort((left,right)=>{
90+
constleftValue=sortValue(left,sortState.key);
91+
constrightValue=sortValue(right,sortState.key);
92+
if(SORT_TYPES[sortState.key]==="number"){
93+
return(Number(leftValue)-Number(rightValue))*direction;
94+
}
95+
returnString(leftValue).localeCompare(String(rightValue))*direction;
96+
});
97+
}
98+
99+
functionupdateSortHeaders(){
100+
sortHeaders.forEach((header)=>{
101+
constselected=header.dataset.toolboxVotesSortHeader===sortState.key;
102+
header.setAttribute("aria-sort",selected ? (sortState.direction==="asc" ? "ascending" : "descending") : "none");
103+
constbutton=header.querySelector("[data-toolbox-votes-sort]");
104+
if(!button){
105+
return;
106+
}
107+
constbaseLabel=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+
functionupdateDragStatus(){
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
functionsetSelectedDetails(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-
functionorderCell(voteRow){
49-
constcell=document.createElement("td");
50-
constinput=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-
returncell;
146+
functionorderedToolIdsAfterDrop(targetToolId,event){
147+
constdisplayToolIds=sortedRows().map((row)=>row.toolId).filter((toolId)=>toolId!==draggedToolId);
148+
consttargetIndex=displayToolIds.indexOf(targetToolId);
149+
if(targetIndex===-1){
150+
returnnull;
151+
}
152+
consttargetRow=event.currentTarget;
153+
consttargetBox=targetRow.getBoundingClientRect();
154+
constinsertAfterTarget=event.clientY>targetBox.top+(targetBox.height/2);
155+
displayToolIds.splice(targetIndex+(insertAfterTarget ? 1 : 0),0,draggedToolId);
156+
returnsortState.direction==="desc" ? displayToolIds.reverse() : displayToolIds;
157+
}
158+
159+
functionhandleDragStart(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+
functionhandleDragOver(event){
172+
if(!isOrderSort()||!draggedToolId){
173+
return;
174+
}
175+
event.preventDefault();
176+
event.dataTransfer.dropEffect="move";
177+
}
178+
179+
functionhandleDrop(event,targetToolId){
180+
if(!isOrderSort()||!draggedToolId||draggedToolId===targetToolId){
181+
draggedToolId="";
182+
return;
183+
}
184+
event.preventDefault();
185+
constorderedToolIds=orderedToolIdsAfterDrop(targetToolId,event);
186+
if(!orderedToolIds){
187+
draggedToolId="";
188+
return;
189+
}
190+
constmovedToolId=draggedToolId;
191+
try{
192+
constsnapshot=reorderToolboxVoteRows(orderedToolIds);
193+
renderSnapshot(snapshot,"Toolbox vote order updated. Rows were renumbered with whole-number order values.");
194+
selectRow(movedToolId);
195+
}catch(error){
196+
constmessage=errorinstanceofError ? 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
functionrenderRows(rows){
68204
if(!body){
69205
return;
70206
}
71207
snapshotRows=rows;
72-
if(!rows.length){
208+
updateSortHeaders();
209+
updateDragStatus();
210+
constdisplayRows=sortedRows();
211+
if(!displayRows.length){
73212
constrow=document.createElement("tr");
74213
constcell=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+
constallowDrag=isOrderSort();
223+
body.replaceChildren(...displayRows.map((voteRow)=>{
84224
constrow=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
returnrow;
100253
}));
101254

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

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

110-
functionupdateOrder(voteRow,value){
111-
try{
112-
constsnapshot=updateToolboxVoteOrder(voteRow.toolId,Number(value));
113-
renderSnapshot(snapshot,`${voteRow.toolName} order updated to ${Number(value)}.`);
114-
selectRow(voteRow.toolId);
115-
}catch(error){
116-
constmessage=errorinstanceofError ? error.message : String(error||"Toolbox vote order unavailable.");
117-
setStatus(`Toolbox vote order could not be updated. ${message}`);
118-
}
119-
}
120-
121264
functionrenderToolboxVotes(){
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+
constnextKey=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)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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
<pclass="status" role="status" data-toolbox-votes-status>Loading Toolbox vote totals.</p>
47+
<pclass="status" role="status" data-toolbox-votes-drag-status>Drag/drop is available when sorted by Order.</p>
4748
<divclass="content-cluster" aria-label="Selected Toolbox vote item">
4849
<spanclass="pill">Selected Order: <spandata-toolbox-votes-selected-order>None</span></span>
4950
<spanclass="pill">Selected Group: <spandata-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-
<thscope="col">Tool</th>
58-
<thscope="col">Order</th>
59-
<thscope="col">Group</th>
60-
<thscope="col">Path</th>
61-
<thscope="col">State</th>
62-
<thscope="col">Up</th>
63-
<thscope="col">Down</th>
64-
<thscope="col">Current User Vote</th>
58+
<thscope="col"data-toolbox-votes-sort-header="toolName"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="toolName">Tool</button></th>
59+
<thscope="col"data-toolbox-votes-sort-header="order"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="order">Order</button></th>
60+
<thscope="col"data-toolbox-votes-sort-header="group"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="group">Group</button></th>
61+
<thscope="col"data-toolbox-votes-sort-header="path"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="path">Path</button></th>
62+
<thscope="col"data-toolbox-votes-sort-header="releaseChannelLabel"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="releaseChannelLabel">State</button></th>
63+
<thscope="col"data-toolbox-votes-sort-header="up"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="up">Votes Up</button></th>
64+
<thscope="col"data-toolbox-votes-sort-header="down"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="down">Votes Down</button></th>
65+
<thscope="col"data-toolbox-votes-sort-header="currentUserVote"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="currentUserVote">Current User Vote</button></th>
6566
</tr>
6667
</thead>
6768
<tbodydata-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
conststatus=document.querySelector("[data-toolbox-votes-status]");
7+
constdragStatus=document.querySelector("[data-toolbox-votes-drag-status]");
78
constbody=document.querySelector("[data-toolbox-votes-body]");
89
constselectedOrder=document.querySelector("[data-toolbox-votes-selected-order]");
910
constselectedGroup=document.querySelector("[data-toolbox-votes-selected-group]");
1011
constselectedPath=document.querySelector("[data-toolbox-votes-selected-path]");
12+
constsortButtons=Array.from(document.querySelectorAll("[data-toolbox-votes-sort]"));
13+
constsortHeaders=Array.from(document.querySelectorAll("[data-toolbox-votes-sort-header]"));
14+
constsortButtonLabels=newMap(sortButtons.map((button)=>[
15+
button.dataset.toolboxVotesSort,
16+
button.textContent.trim(),
17+
]));
18+
19+
constSORT_TYPES=Object.freeze({
20+
down: "number",
21+
order: "number",
22+
up: "number",
23+
});
1124

1225
letselectedToolId="";
1326
letsnapshotRows=[];
27+
letdraggedToolId="";
28+
letsortState={
29+
direction: "asc",
30+
key: "order",
31+
};
1432

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

39+
functionsortLabel(key){
40+
returnsortButtonLabels.get(key)||key;
41+
}
42+
43+
functionisOrderSort(){
44+
returnsortState.key==="order";
45+
}
46+
47+
functionwholeNumberOrder(value){
48+
constorder=Number(value);
49+
returnMath.max(1,Math.round(Number.isFinite(order) ? order : 1));
50+
}
51+
2152
functiontableCell(value){
2253
constcell=document.createElement("td");
2354
cell.textContent=value===null||value===undefined ? "" : String(value);
2455
returncell;
2556
}
2657

58+
functiontoolNameCell(voteRow){
59+
constcell=document.createElement("td");
60+
constlink=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+
returncell;
66+
}
67+
68+
functionorderCell(voteRow){
69+
constcell=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+
returncell;
75+
}
76+
77+
functionsortValue(voteRow,key){
78+
if(key==="currentUserVote"){
79+
returnvoteRow.currentUserVote||"None";
80+
}
81+
if(key==="order"){
82+
returnwholeNumberOrder(voteRow.order);
83+
}
84+
returnvoteRow[key]??"";
85+
}
86+
87+
functionsortedRows(){
88+
constdirection=sortState.direction==="asc" ? 1 : -1;
89+
return[...snapshotRows].sort((left,right)=>{
90+
constleftValue=sortValue(left,sortState.key);
91+
constrightValue=sortValue(right,sortState.key);
92+
if(SORT_TYPES[sortState.key]==="number"){
93+
return(Number(leftValue)-Number(rightValue))*direction;
94+
}
95+
returnString(leftValue).localeCompare(String(rightValue))*direction;
96+
});
97+
}
98+
99+
functionupdateSortHeaders(){
100+
sortHeaders.forEach((header)=>{
101+
constselected=header.dataset.toolboxVotesSortHeader===sortState.key;
102+
header.setAttribute("aria-sort",selected ? (sortState.direction==="asc" ? "ascending" : "descending") : "none");
103+
constbutton=header.querySelector("[data-toolbox-votes-sort]");
104+
if(!button){
105+
return;
106+
}
107+
constbaseLabel=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+
functionupdateDragStatus(){
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
functionsetSelectedDetails(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-
functionorderCell(voteRow){
49-
constcell=document.createElement("td");
50-
constinput=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-
returncell;
146+
functionorderedToolIdsAfterDrop(targetToolId,event){
147+
constdisplayToolIds=sortedRows().map((row)=>row.toolId).filter((toolId)=>toolId!==draggedToolId);
148+
consttargetIndex=displayToolIds.indexOf(targetToolId);
149+
if(targetIndex===-1){
150+
returnnull;
151+
}
152+
consttargetRow=event.currentTarget;
153+
consttargetBox=targetRow.getBoundingClientRect();
154+
constinsertAfterTarget=event.clientY>targetBox.top+(targetBox.height/2);
155+
displayToolIds.splice(targetIndex+(insertAfterTarget ? 1 : 0),0,draggedToolId);
156+
returnsortState.direction==="desc" ? displayToolIds.reverse() : displayToolIds;
157+
}
158+
159+
functionhandleDragStart(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+
functionhandleDragOver(event){
172+
if(!isOrderSort()||!draggedToolId){
173+
return;
174+
}
175+
event.preventDefault();
176+
event.dataTransfer.dropEffect="move";
177+
}
178+
179+
functionhandleDrop(event,targetToolId){
180+
if(!isOrderSort()||!draggedToolId||draggedToolId===targetToolId){
181+
draggedToolId="";
182+
return;
183+
}
184+
event.preventDefault();
185+
constorderedToolIds=orderedToolIdsAfterDrop(targetToolId,event);
186+
if(!orderedToolIds){
187+
draggedToolId="";
188+
return;
189+
}
190+
constmovedToolId=draggedToolId;
191+
try{
192+
constsnapshot=reorderToolboxVoteRows(orderedToolIds);
193+
renderSnapshot(snapshot,"Toolbox vote order updated. Rows were renumbered with whole-number order values.");
194+
selectRow(movedToolId);
195+
}catch(error){
196+
constmessage=errorinstanceofError ? 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
functionrenderRows(rows){
68204
if(!body){
69205
return;
70206
}
71207
snapshotRows=rows;
72-
if(!rows.length){
208+
updateSortHeaders();
209+
updateDragStatus();
210+
constdisplayRows=sortedRows();
211+
if(!displayRows.length){
73212
constrow=document.createElement("tr");
74213
constcell=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+
constallowDrag=isOrderSort();
223+
body.replaceChildren(...displayRows.map((voteRow)=>{
84224
constrow=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
returnrow;
100253
}));
101254

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

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

110-
functionupdateOrder(voteRow,value){
111-
try{
112-
constsnapshot=updateToolboxVoteOrder(voteRow.toolId,Number(value));
113-
renderSnapshot(snapshot,`${voteRow.toolName} order updated to ${Number(value)}.`);
114-
selectRow(voteRow.toolId);
115-
}catch(error){
116-
constmessage=errorinstanceofError ? error.message : String(error||"Toolbox vote order unavailable.");
117-
setStatus(`Toolbox vote order could not be updated. ${message}`);
118-
}
119-
}
120-
121264
functionrenderToolboxVotes(){
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+
constnextKey=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)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
<pclass="status" role="status" data-toolbox-votes-status>Loading Toolbox vote totals.</p>
47+
<pclass="status" role="status" data-toolbox-votes-drag-status>Drag/drop is available when sorted by Order.</p>
4748
<divclass="content-cluster" aria-label="Selected Toolbox vote item">
4849
<spanclass="pill">Selected Order: <spandata-toolbox-votes-selected-order>None</span></span>
4950
<spanclass="pill">Selected Group: <spandata-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-
<thscope="col">Tool</th>
58-
<thscope="col">Order</th>
59-
<thscope="col">Group</th>
60-
<thscope="col">Path</th>
61-
<thscope="col">State</th>
62-
<thscope="col">Up</th>
63-
<thscope="col">Down</th>
64-
<thscope="col">Current User Vote</th>
58+
<thscope="col"data-toolbox-votes-sort-header="toolName"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="toolName">Tool</button></th>
59+
<thscope="col"data-toolbox-votes-sort-header="order"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="order">Order</button></th>
60+
<thscope="col"data-toolbox-votes-sort-header="group"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="group">Group</button></th>
61+
<thscope="col"data-toolbox-votes-sort-header="path"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="path">Path</button></th>
62+
<thscope="col"data-toolbox-votes-sort-header="releaseChannelLabel"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="releaseChannelLabel">State</button></th>
63+
<thscope="col"data-toolbox-votes-sort-header="up"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="up">Votes Up</button></th>
64+
<thscope="col"data-toolbox-votes-sort-header="down"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="down">Votes Down</button></th>
65+
<thscope="col"data-toolbox-votes-sort-header="currentUserVote"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="currentUserVote">Current User Vote</button></th>
6566
</tr>
6667
</thead>
6768
<tbodydata-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
conststatus=document.querySelector("[data-toolbox-votes-status]");
7+
constdragStatus=document.querySelector("[data-toolbox-votes-drag-status]");
78
constbody=document.querySelector("[data-toolbox-votes-body]");
89
constselectedOrder=document.querySelector("[data-toolbox-votes-selected-order]");
910
constselectedGroup=document.querySelector("[data-toolbox-votes-selected-group]");
1011
constselectedPath=document.querySelector("[data-toolbox-votes-selected-path]");
12+
constsortButtons=Array.from(document.querySelectorAll("[data-toolbox-votes-sort]"));
13+
constsortHeaders=Array.from(document.querySelectorAll("[data-toolbox-votes-sort-header]"));
14+
constsortButtonLabels=newMap(sortButtons.map((button)=>[
15+
button.dataset.toolboxVotesSort,
16+
button.textContent.trim(),
17+
]));
18+
19+
constSORT_TYPES=Object.freeze({
20+
down: "number",
21+
order: "number",
22+
up: "number",
23+
});
1124

1225
letselectedToolId="";
1326
letsnapshotRows=[];
27+
letdraggedToolId="";
28+
letsortState={
29+
direction: "asc",
30+
key: "order",
31+
};
1432

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

39+
functionsortLabel(key){
40+
returnsortButtonLabels.get(key)||key;
41+
}
42+
43+
functionisOrderSort(){
44+
returnsortState.key==="order";
45+
}
46+
47+
functionwholeNumberOrder(value){
48+
constorder=Number(value);
49+
returnMath.max(1,Math.round(Number.isFinite(order) ? order : 1));
50+
}
51+
2152
functiontableCell(value){
2253
constcell=document.createElement("td");
2354
cell.textContent=value===null||value===undefined ? "" : String(value);
2455
returncell;
2556
}
2657

58+
functiontoolNameCell(voteRow){
59+
constcell=document.createElement("td");
60+
constlink=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+
returncell;
66+
}
67+
68+
functionorderCell(voteRow){
69+
constcell=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+
returncell;
75+
}
76+
77+
functionsortValue(voteRow,key){
78+
if(key==="currentUserVote"){
79+
returnvoteRow.currentUserVote||"None";
80+
}
81+
if(key==="order"){
82+
returnwholeNumberOrder(voteRow.order);
83+
}
84+
returnvoteRow[key]??"";
85+
}
86+
87+
functionsortedRows(){
88+
constdirection=sortState.direction==="asc" ? 1 : -1;
89+
return[...snapshotRows].sort((left,right)=>{
90+
constleftValue=sortValue(left,sortState.key);
91+
constrightValue=sortValue(right,sortState.key);
92+
if(SORT_TYPES[sortState.key]==="number"){
93+
return(Number(leftValue)-Number(rightValue))*direction;
94+
}
95+
returnString(leftValue).localeCompare(String(rightValue))*direction;
96+
});
97+
}
98+
99+
functionupdateSortHeaders(){
100+
sortHeaders.forEach((header)=>{
101+
constselected=header.dataset.toolboxVotesSortHeader===sortState.key;
102+
header.setAttribute("aria-sort",selected ? (sortState.direction==="asc" ? "ascending" : "descending") : "none");
103+
constbutton=header.querySelector("[data-toolbox-votes-sort]");
104+
if(!button){
105+
return;
106+
}
107+
constbaseLabel=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+
functionupdateDragStatus(){
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
functionsetSelectedDetails(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-
functionorderCell(voteRow){
49-
constcell=document.createElement("td");
50-
constinput=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-
returncell;
146+
functionorderedToolIdsAfterDrop(targetToolId,event){
147+
constdisplayToolIds=sortedRows().map((row)=>row.toolId).filter((toolId)=>toolId!==draggedToolId);
148+
consttargetIndex=displayToolIds.indexOf(targetToolId);
149+
if(targetIndex===-1){
150+
returnnull;
151+
}
152+
consttargetRow=event.currentTarget;
153+
consttargetBox=targetRow.getBoundingClientRect();
154+
constinsertAfterTarget=event.clientY>targetBox.top+(targetBox.height/2);
155+
displayToolIds.splice(targetIndex+(insertAfterTarget ? 1 : 0),0,draggedToolId);
156+
returnsortState.direction==="desc" ? displayToolIds.reverse() : displayToolIds;
157+
}
158+
159+
functionhandleDragStart(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+
functionhandleDragOver(event){
172+
if(!isOrderSort()||!draggedToolId){
173+
return;
174+
}
175+
event.preventDefault();
176+
event.dataTransfer.dropEffect="move";
177+
}
178+
179+
functionhandleDrop(event,targetToolId){
180+
if(!isOrderSort()||!draggedToolId||draggedToolId===targetToolId){
181+
draggedToolId="";
182+
return;
183+
}
184+
event.preventDefault();
185+
constorderedToolIds=orderedToolIdsAfterDrop(targetToolId,event);
186+
if(!orderedToolIds){
187+
draggedToolId="";
188+
return;
189+
}
190+
constmovedToolId=draggedToolId;
191+
try{
192+
constsnapshot=reorderToolboxVoteRows(orderedToolIds);
193+
renderSnapshot(snapshot,"Toolbox vote order updated. Rows were renumbered with whole-number order values.");
194+
selectRow(movedToolId);
195+
}catch(error){
196+
constmessage=errorinstanceofError ? 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
functionrenderRows(rows){
68204
if(!body){
69205
return;
70206
}
71207
snapshotRows=rows;
72-
if(!rows.length){
208+
updateSortHeaders();
209+
updateDragStatus();
210+
constdisplayRows=sortedRows();
211+
if(!displayRows.length){
73212
constrow=document.createElement("tr");
74213
constcell=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+
constallowDrag=isOrderSort();
223+
body.replaceChildren(...displayRows.map((voteRow)=>{
84224
constrow=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
returnrow;
100253
}));
101254

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

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

110-
functionupdateOrder(voteRow,value){
111-
try{
112-
constsnapshot=updateToolboxVoteOrder(voteRow.toolId,Number(value));
113-
renderSnapshot(snapshot,`${voteRow.toolName} order updated to ${Number(value)}.`);
114-
selectRow(voteRow.toolId);
115-
}catch(error){
116-
constmessage=errorinstanceofError ? error.message : String(error||"Toolbox vote order unavailable.");
117-
setStatus(`Toolbox vote order could not be updated. ${message}`);
118-
}
119-
}
120-
121264
functionrenderToolboxVotes(){
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+
constnextKey=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)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
<pclass="status" role="status" data-toolbox-votes-status>Loading Toolbox vote totals.</p>
47+
<pclass="status" role="status" data-toolbox-votes-drag-status>Drag/drop is available when sorted by Order.</p>
4748
<divclass="content-cluster" aria-label="Selected Toolbox vote item">
4849
<spanclass="pill">Selected Order: <spandata-toolbox-votes-selected-order>None</span></span>
4950
<spanclass="pill">Selected Group: <spandata-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-
<thscope="col">Tool</th>
58-
<thscope="col">Order</th>
59-
<thscope="col">Group</th>
60-
<thscope="col">Path</th>
61-
<thscope="col">State</th>
62-
<thscope="col">Up</th>
63-
<thscope="col">Down</th>
64-
<thscope="col">Current User Vote</th>
58+
<thscope="col"data-toolbox-votes-sort-header="toolName"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="toolName">Tool</button></th>
59+
<thscope="col"data-toolbox-votes-sort-header="order"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="order">Order</button></th>
60+
<thscope="col"data-toolbox-votes-sort-header="group"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="group">Group</button></th>
61+
<thscope="col"data-toolbox-votes-sort-header="path"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="path">Path</button></th>
62+
<thscope="col"data-toolbox-votes-sort-header="releaseChannelLabel"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="releaseChannelLabel">State</button></th>
63+
<thscope="col"data-toolbox-votes-sort-header="up"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="up">Votes Up</button></th>
64+
<thscope="col"data-toolbox-votes-sort-header="down"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="down">Votes Down</button></th>
65+
<thscope="col"data-toolbox-votes-sort-header="currentUserVote"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="currentUserVote">Current User Vote</button></th>
6566
</tr>
6667
</thead>
6768
<tbodydata-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
conststatus=document.querySelector("[data-toolbox-votes-status]");
7+
constdragStatus=document.querySelector("[data-toolbox-votes-drag-status]");
78
constbody=document.querySelector("[data-toolbox-votes-body]");
89
constselectedOrder=document.querySelector("[data-toolbox-votes-selected-order]");
910
constselectedGroup=document.querySelector("[data-toolbox-votes-selected-group]");
1011
constselectedPath=document.querySelector("[data-toolbox-votes-selected-path]");
12+
constsortButtons=Array.from(document.querySelectorAll("[data-toolbox-votes-sort]"));
13+
constsortHeaders=Array.from(document.querySelectorAll("[data-toolbox-votes-sort-header]"));
14+
constsortButtonLabels=newMap(sortButtons.map((button)=>[
15+
button.dataset.toolboxVotesSort,
16+
button.textContent.trim(),
17+
]));
18+
19+
constSORT_TYPES=Object.freeze({
20+
down: "number",
21+
order: "number",
22+
up: "number",
23+
});
1124

1225
letselectedToolId="";
1326
letsnapshotRows=[];
27+
letdraggedToolId="";
28+
letsortState={
29+
direction: "asc",
30+
key: "order",
31+
};
1432

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

39+
functionsortLabel(key){
40+
returnsortButtonLabels.get(key)||key;
41+
}
42+
43+
functionisOrderSort(){
44+
returnsortState.key==="order";
45+
}
46+
47+
functionwholeNumberOrder(value){
48+
constorder=Number(value);
49+
returnMath.max(1,Math.round(Number.isFinite(order) ? order : 1));
50+
}
51+
2152
functiontableCell(value){
2253
constcell=document.createElement("td");
2354
cell.textContent=value===null||value===undefined ? "" : String(value);
2455
returncell;
2556
}
2657

58+
functiontoolNameCell(voteRow){
59+
constcell=document.createElement("td");
60+
constlink=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+
returncell;
66+
}
67+
68+
functionorderCell(voteRow){
69+
constcell=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+
returncell;
75+
}
76+
77+
functionsortValue(voteRow,key){
78+
if(key==="currentUserVote"){
79+
returnvoteRow.currentUserVote||"None";
80+
}
81+
if(key==="order"){
82+
returnwholeNumberOrder(voteRow.order);
83+
}
84+
returnvoteRow[key]??"";
85+
}
86+
87+
functionsortedRows(){
88+
constdirection=sortState.direction==="asc" ? 1 : -1;
89+
return[...snapshotRows].sort((left,right)=>{
90+
constleftValue=sortValue(left,sortState.key);
91+
constrightValue=sortValue(right,sortState.key);
92+
if(SORT_TYPES[sortState.key]==="number"){
93+
return(Number(leftValue)-Number(rightValue))*direction;
94+
}
95+
returnString(leftValue).localeCompare(String(rightValue))*direction;
96+
});
97+
}
98+
99+
functionupdateSortHeaders(){
100+
sortHeaders.forEach((header)=>{
101+
constselected=header.dataset.toolboxVotesSortHeader===sortState.key;
102+
header.setAttribute("aria-sort",selected ? (sortState.direction==="asc" ? "ascending" : "descending") : "none");
103+
constbutton=header.querySelector("[data-toolbox-votes-sort]");
104+
if(!button){
105+
return;
106+
}
107+
constbaseLabel=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+
functionupdateDragStatus(){
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
functionsetSelectedDetails(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-
functionorderCell(voteRow){
49-
constcell=document.createElement("td");
50-
constinput=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-
returncell;
146+
functionorderedToolIdsAfterDrop(targetToolId,event){
147+
constdisplayToolIds=sortedRows().map((row)=>row.toolId).filter((toolId)=>toolId!==draggedToolId);
148+
consttargetIndex=displayToolIds.indexOf(targetToolId);
149+
if(targetIndex===-1){
150+
returnnull;
151+
}
152+
consttargetRow=event.currentTarget;
153+
consttargetBox=targetRow.getBoundingClientRect();
154+
constinsertAfterTarget=event.clientY>targetBox.top+(targetBox.height/2);
155+
displayToolIds.splice(targetIndex+(insertAfterTarget ? 1 : 0),0,draggedToolId);
156+
returnsortState.direction==="desc" ? displayToolIds.reverse() : displayToolIds;
157+
}
158+
159+
functionhandleDragStart(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+
functionhandleDragOver(event){
172+
if(!isOrderSort()||!draggedToolId){
173+
return;
174+
}
175+
event.preventDefault();
176+
event.dataTransfer.dropEffect="move";
177+
}
178+
179+
functionhandleDrop(event,targetToolId){
180+
if(!isOrderSort()||!draggedToolId||draggedToolId===targetToolId){
181+
draggedToolId="";
182+
return;
183+
}
184+
event.preventDefault();
185+
constorderedToolIds=orderedToolIdsAfterDrop(targetToolId,event);
186+
if(!orderedToolIds){
187+
draggedToolId="";
188+
return;
189+
}
190+
constmovedToolId=draggedToolId;
191+
try{
192+
constsnapshot=reorderToolboxVoteRows(orderedToolIds);
193+
renderSnapshot(snapshot,"Toolbox vote order updated. Rows were renumbered with whole-number order values.");
194+
selectRow(movedToolId);
195+
}catch(error){
196+
constmessage=errorinstanceofError ? 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
functionrenderRows(rows){
68204
if(!body){
69205
return;
70206
}
71207
snapshotRows=rows;
72-
if(!rows.length){
208+
updateSortHeaders();
209+
updateDragStatus();
210+
constdisplayRows=sortedRows();
211+
if(!displayRows.length){
73212
constrow=document.createElement("tr");
74213
constcell=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+
constallowDrag=isOrderSort();
223+
body.replaceChildren(...displayRows.map((voteRow)=>{
84224
constrow=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
returnrow;
100253
}));
101254

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

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

110-
functionupdateOrder(voteRow,value){
111-
try{
112-
constsnapshot=updateToolboxVoteOrder(voteRow.toolId,Number(value));
113-
renderSnapshot(snapshot,`${voteRow.toolName} order updated to ${Number(value)}.`);
114-
selectRow(voteRow.toolId);
115-
}catch(error){
116-
constmessage=errorinstanceofError ? error.message : String(error||"Toolbox vote order unavailable.");
117-
setStatus(`Toolbox vote order could not be updated. ${message}`);
118-
}
119-
}
120-
121264
functionrenderToolboxVotes(){
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+
constnextKey=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)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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
<pclass="status" role="status" data-toolbox-votes-status>Loading Toolbox vote totals.</p>
47+
<pclass="status" role="status" data-toolbox-votes-drag-status>Drag/drop is available when sorted by Order.</p>
4748
<divclass="content-cluster" aria-label="Selected Toolbox vote item">
4849
<spanclass="pill">Selected Order: <spandata-toolbox-votes-selected-order>None</span></span>
4950
<spanclass="pill">Selected Group: <spandata-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-
<thscope="col">Tool</th>
58-
<thscope="col">Order</th>
59-
<thscope="col">Group</th>
60-
<thscope="col">Path</th>
61-
<thscope="col">State</th>
62-
<thscope="col">Up</th>
63-
<thscope="col">Down</th>
64-
<thscope="col">Current User Vote</th>
58+
<thscope="col"data-toolbox-votes-sort-header="toolName"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="toolName">Tool</button></th>
59+
<thscope="col"data-toolbox-votes-sort-header="order"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="order">Order</button></th>
60+
<thscope="col"data-toolbox-votes-sort-header="group"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="group">Group</button></th>
61+
<thscope="col"data-toolbox-votes-sort-header="path"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="path">Path</button></th>
62+
<thscope="col"data-toolbox-votes-sort-header="releaseChannelLabel"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="releaseChannelLabel">State</button></th>
63+
<thscope="col"data-toolbox-votes-sort-header="up"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="up">Votes Up</button></th>
64+
<thscope="col"data-toolbox-votes-sort-header="down"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="down">Votes Down</button></th>
65+
<thscope="col"data-toolbox-votes-sort-header="currentUserVote"><buttonclass="btn btn--compact" type="button" data-toolbox-votes-sort="currentUserVote">Current User Vote</button></th>
6566
</tr>
6667
</thead>
6768
<tbodydata-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
conststatus=document.querySelector("[data-toolbox-votes-status]");
7+
constdragStatus=document.querySelector("[data-toolbox-votes-drag-status]");
78
constbody=document.querySelector("[data-toolbox-votes-body]");
89
constselectedOrder=document.querySelector("[data-toolbox-votes-selected-order]");
910
constselectedGroup=document.querySelector("[data-toolbox-votes-selected-group]");
1011
constselectedPath=document.querySelector("[data-toolbox-votes-selected-path]");
12+
constsortButtons=Array.from(document.querySelectorAll("[data-toolbox-votes-sort]"));
13+
constsortHeaders=Array.from(document.querySelectorAll("[data-toolbox-votes-sort-header]"));
14+
constsortButtonLabels=newMap(sortButtons.map((button)=>[
15+
button.dataset.toolboxVotesSort,
16+
button.textContent.trim(),
17+
]));
18+
19+
constSORT_TYPES=Object.freeze({
20+
down: "number",
21+
order: "number",
22+
up: "number",
23+
});
1124

1225
letselectedToolId="";
1326
letsnapshotRows=[];
27+
letdraggedToolId="";
28+
letsortState={
29+
direction: "asc",
30+
key: "order",
31+
};
1432

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

39+
functionsortLabel(key){
40+
returnsortButtonLabels.get(key)||key;
41+
}
42+
43+
functionisOrderSort(){
44+
returnsortState.key==="order";
45+
}
46+
47+
functionwholeNumberOrder(value){
48+
constorder=Number(value);
49+
returnMath.max(1,Math.round(Number.isFinite(order) ? order : 1));
50+
}
51+
2152
functiontableCell(value){
2253
constcell=document.createElement("td");
2354
cell.textContent=value===null||value===undefined ? "" : String(value);
2455
returncell;
2556
}
2657

58+
functiontoolNameCell(voteRow){
59+
constcell=document.createElement("td");
60+
constlink=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+
returncell;
66+
}
67+
68+
functionorderCell(voteRow){
69+
constcell=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+
returncell;
75+
}
76+
77+
functionsortValue(voteRow,key){
78+
if(key==="currentUserVote"){
79+
returnvoteRow.currentUserVote||"None";
80+
}
81+
if(key==="order"){
82+
returnwholeNumberOrder(voteRow.order);
83+
}
84+
returnvoteRow[key]??"";
85+
}
86+
87+
functionsortedRows(){
88+
constdirection=sortState.direction==="asc" ? 1 : -1;
89+
return[...snapshotRows].sort((left,right)=>{
90+
constleftValue=sortValue(left,sortState.key);
91+
constrightValue=sortValue(right,sortState.key);
92+
if(SORT_TYPES[sortState.key]==="number"){
93+
return(Number(leftValue)-Number(rightValue))*direction;
94+
}
95+
returnString(leftValue).localeCompare(String(rightValue))*direction;
96+
});
97+
}
98+
99+
functionupdateSortHeaders(){
100+
sortHeaders.forEach((header)=>{
101+
constselected=header.dataset.toolboxVotesSortHeader===sortState.key;
102+
header.setAttribute("aria-sort",selected ? (sortState.direction==="asc" ? "ascending" : "descending") : "none");
103+
constbutton=header.querySelector("[data-toolbox-votes-sort]");
104+
if(!button){
105+
return;
106+
}
107+
constbaseLabel=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+
functionupdateDragStatus(){
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
functionsetSelectedDetails(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-
functionorderCell(voteRow){
49-
constcell=document.createElement("td");
50-
constinput=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-
returncell;
146+
functionorderedToolIdsAfterDrop(targetToolId,event){
147+
constdisplayToolIds=sortedRows().map((row)=>row.toolId).filter((toolId)=>toolId!==draggedToolId);
148+
consttargetIndex=displayToolIds.indexOf(targetToolId);
149+
if(targetIndex===-1){
150+
returnnull;
151+
}
152+
consttargetRow=event.currentTarget;
153+
consttargetBox=targetRow.getBoundingClientRect();
154+
constinsertAfterTarget=event.clientY>targetBox.top+(targetBox.height/2);
155+
displayToolIds.splice(targetIndex+(insertAfterTarget ? 1 : 0),0,draggedToolId);
156+
returnsortState.direction==="desc" ? displayToolIds.reverse() : displayToolIds;
157+
}
158+
159+
functionhandleDragStart(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+
functionhandleDragOver(event){
172+
if(!isOrderSort()||!draggedToolId){
173+
return;
174+
}
175+
event.preventDefault();
176+
event.dataTransfer.dropEffect="move";
177+
}
178+
179+
functionhandleDrop(event,targetToolId){
180+
if(!isOrderSort()||!draggedToolId||draggedToolId===targetToolId){
181+
draggedToolId="";
182+
return;
183+
}
184+
event.preventDefault();
185+
constorderedToolIds=orderedToolIdsAfterDrop(targetToolId,event);
186+
if(!orderedToolIds){
187+
draggedToolId="";
188+
return;
189+
}
190+
constmovedToolId=draggedToolId;
191+
try{
192+
constsnapshot=reorderToolboxVoteRows(orderedToolIds);
193+
renderSnapshot(snapshot,"Toolbox vote order updated. Rows were renumbered with whole-number order values.");
194+
selectRow(movedToolId);
195+
}catch(error){
196+
constmessage=errorinstanceofError ? 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
functionrenderRows(rows){
68204
if(!body){
69205
return;
70206
}
71207
snapshotRows=rows;
72-
if(!rows.length){
208+
updateSortHeaders();
209+
updateDragStatus();
210+
constdisplayRows=sortedRows();
211+
if(!displayRows.length){
73212
constrow=document.createElement("tr");
74213
constcell=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+
constallowDrag=isOrderSort();
223+
body.replaceChildren(...displayRows.map((voteRow)=>{
84224
constrow=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
returnrow;
100253
}));
101254

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

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

110-
functionupdateOrder(voteRow,value){
111-
try{
112-
constsnapshot=updateToolboxVoteOrder(voteRow.toolId,Number(value));
113-
renderSnapshot(snapshot,`${voteRow.toolName} order updated to ${Number(value)}.`);
114-
selectRow(voteRow.toolId);
115-
}catch(error){
116-
constmessage=errorinstanceofError ? error.message : String(error||"Toolbox vote order unavailable.");
117-
setStatus(`Toolbox vote order could not be updated. ${message}`);
118-
}
119-
}
120-
121264
functionrenderToolboxVotes(){
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+
constnextKey=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)