Seia Sorter
"Add-on" for https://ba-sort.netlify.app/
What does it do?
- You can import a previous sorter and it'll automatically handle the already sorted students to restore your previous sorter positions, allowing you to focus only on sorting new students.
- To import a previous sorter, copy the text obtained from the "Generate Text List" button, which appears after you finish a sorter, to the clipboard and click the "Import Text List" from the home page. The import works on exports made without this script.
- In the results screen, you can now drag students to swap their positions around.
- You don't have to start over to do this, if you have a sorter URL or a finished sorter you can just load it and then apply the script.
- Get a random finished sorter if you'd rather just re-order students instead of 1v1s. Or just for fun.
- Use the "Out" option to permanently filter out a student so you don't see them again during the sorter.
- If you enable this then whenever the student shows up the sorter will auto-pick the other option for you, or any if they're both excluded.
Notes
- Do not run the script mid sorting, either do it before you start or after you finish.
- Starting from an import will disable the undo and save functions while sorting the new students.
- Swapping a student that is in a tie with others will permanently remove the tie from that cell.
- Generated URLs will not reflect changes to swapped student positions, always use the "Generate Text List" to save it!
- Careful as using the "Out" option to exclude a student will disable the "Undo" option permanently.
How do I enable it?
- Copy the code below
- Go to https://ba-sort.netlify.app/
- Open your browser's developer tools
- Navigate to the console and paste it there
- Some browsers don't allow for immediate pasting you might need to type allow pasting first
Copy the code below by clicking the copy icon on the right.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 | let importedSortedList = [], newComparisons = {}, filterOut = [];
const startingLoadButton = document.querySelector('.starting.load.button');
const startingButton = document.querySelector('.starting.start.button');
document.querySelector('.image.selector > select').value = 10;
const originalResult = result;
result = function(imageNum = 10) {
originalResult(imageNum);
document.querySelectorAll('.result').forEach(item => {
item.setAttribute('draggable', true);
item.addEventListener('dragstart', dragStart);
item.addEventListener('dragover', dragOver);
item.addEventListener('drop', drop);
});
surroundWithSeias(document.querySelector('.finished.getimg.button'));
surroundWithSeias(document.querySelector('.finished.list.button'));
document.querySelector('.left.button.modified').style.display = 'none';
document.querySelector('.right.button.modified').style.display = 'none';
}
function surroundWithSeias(div) {
div.style.display = 'flex';
div.style.justifyContent = 'center';
div.style.alignItems = 'center';
if (!div.classList.contains('modified')) {
let leftImg = document.createElement('img');
let rightImg = document.createElement('img');
leftImg.src = rightImg.src = 'https://files.catbox.moe/ph1rgd.gif';
leftImg.style.width = rightImg.style.width = '25px';
leftImg.style.height = rightImg.style.height = '25px';
let textSpan = document.createElement('span');
textSpan.textContent = div.textContent;
div.textContent = '';
div.appendChild(leftImg);
div.appendChild(textSpan);
div.appendChild(rightImg);
div.classList.add('modified');
}
}
if(startingButton.style.display !== 'none') {
const seiaLoadButton = document.createElement('div');
seiaLoadButton.className = 'seia load button';
seiaLoadButton.style.cssText = 'display: flex; justify-content: center; align-items: center;';
seiaLoadButton.innerHTML = '<img src="https://files.catbox.moe/ph1rgd.gif" style="width: 25px; height: 25px; padding: 5px;">Import<br>Text List<img src="https://files.catbox.moe/ph1rgd.gif" style="width: 25px; height: 25px; padding: 5px;">';
startingLoadButton.parentNode.insertBefore(seiaLoadButton, startingLoadButton.nextSibling);
seiaLoadButton.addEventListener('click', importSortedListFromClipboard);
function disableButton(button) {
button.style.textDecoration = 'line-through';
button.style.fontWeight = 'bold';
button.style.color = 'red';
button.classList.add("disabled");
button.style.pointerEvents = 'none';
}
let isRemovalTriggered = false;
function enableRemoval() {
if (isRemovalTriggered) return;
isRemovalTriggered = true;
disableButton(document.getElementsByClassName('sorting undo button')[0]);
disableButton(document.getElementsByClassName('sorting save button')[0]);
}
function addFilterOutButtons() {
// Find the element with classes "right sort text"
const targetElement = document.querySelector('.right.sort.text');
// Create the new div element
const removeButtonLeft = document.createElement('div');
removeButtonLeft.className = 'left button';
removeButtonLeft.style.display = 'block';
removeButtonLeft.textContent = 'Out';
removeButtonLeft.addEventListener('click', () => {
enableRemoval();
const toExclude = characterDataToSort[sortedIndexList[leftIndex][leftInnerIndex]].name;
filterOut.push(toExclude);
pick('right');
});
// Insert the new element after the target element
if (targetElement) {
targetElement.insertAdjacentElement('afterend', removeButtonLeft);
}
// Create the new div element
const removeButtonRight = document.createElement('div');
removeButtonRight.className = 'right button';
removeButtonRight.style.display = 'block';
removeButtonRight.textContent = 'Out';
removeButtonRight.addEventListener('click', () => {
enableRemoval();
const toExclude = characterDataToSort[sortedIndexList[rightIndex][rightInnerIndex]].name;
filterOut.push(toExclude);
pick('left');
});
// Insert the new element after the target element
if (removeButtonLeft) {
removeButtonLeft.insertAdjacentElement('afterend', removeButtonRight);
}
surroundWithSeias(removeButtonLeft);
surroundWithSeias(removeButtonRight);
}
const customStart = function () {
seiaLoadButton.style.display = 'none';
addFilterOutButtons();
start();
}
startingButton.removeEventListener('click', start);
startingButton.addEventListener('click', customStart);
async function importSortedListFromClipboard() {
try {
const clipboardText = await navigator.clipboard.readText();
const lines = clipboardText.split(/\r?\n/);
importedSortedList = lines
.map(line => {
const match = line.match(/^(\d+)\.\s*(.+)$/);
return match ? { position: match[1], name: match[2].trim() } : null;
})
.filter(item => item !== null);
if (importedSortedList.length === 0) {
throw new Error('No valid data found');
}
importedSortedList.sort((a, b) => parseInt(a.position) - parseInt(b.position));
alert('Sorted list imported successfully!');
if (importedSortedList) {
disableButton(document.getElementsByClassName('sorting undo button')[0]);
disableButton(document.getElementsByClassName('sorting save button')[0]);
}
customStart();
} catch (err) {
alert('Failed to import data. Make sure the clipboard contains valid list data.');
}
}
const originalLoadProgress = loadProgress;
loadProgress = function () {
customStart();
const choiceSelection = ['left', 'right'];
const imgButton = document.querySelector('.finished.getimg.button');
function pickNext() {
while(!loading) {
pick(choiceSelection[Math.floor(Math.random() * 2)]);
}
if (imgButton.style.display === '' || imgButton.style === 'none') {
setTimeout(pickNext, 50);
}
}
pickNext();
}
startingLoadButton.removeEventListener('click', originalLoadProgress);
startingLoadButton.addEventListener('click', loadProgress);
surroundWithSeias(startingLoadButton);
startingLoadButton.querySelector('span').innerHTML = "Random<br>Finish";
} else {
result();
}
document.getElementsByClassName('finished save button')[0].innerHTML = `Generate <span style="color: red; text-decoration: underline;">Original</span><br>Result URL`;
function makePickDecision(leftName, rightName) {
if (newComparisons[`${leftName}-${rightName}`] === 'left') return 'left';
if (newComparisons[`${leftName}-${rightName}`] === 'right') return 'right';
if (newComparisons[`${rightName}-${leftName}`] === 'left') return 'right';
if (newComparisons[`${rightName}-${leftName}`] === 'right') return 'left';
const leftItem = importedSortedList.find(item => item.name === leftName);
const rightItem = importedSortedList.find(item => item.name === rightName);
if (leftItem && rightItem) {
const leftPosition = parseInt(leftItem.position);
const rightPosition = parseInt(rightItem.position);
if (leftPosition < rightPosition) return 'left';
if (rightPosition < leftPosition) return 'right';
if (leftPosition === rightPosition) return 'tie';
} else if (filterOut.length > 0 && filterOut.find(item => item === leftName)) {
return 'right';
} else if (filterOut.length > 0 && filterOut.find(item => item === rightName)) {
return 'left';
}
return null;
}
function updateNewComparisons(leftName, rightName, pickResult) {
if (pickResult === 'left' || pickResult === 'right') {
newComparisons[`${leftName}-${rightName}`] = pickResult;
}
propagateDecision(leftName, rightName, pickResult);
}
function propagateDecision(leftName, rightName, pickResult) {
if (pickResult === 'tie') return;
const better = pickResult === 'left' ? leftName : rightName;
const worse = pickResult === 'left' ? rightName : leftName;
for (let comparison in newComparisons) {
const [itemA, itemB] = comparison.split('-');
if (itemA === worse && newComparisons[comparison] === 'left') {
newComparisons[`${better}-${itemB}`] = 'left';
} else if (itemB === worse && newComparisons[comparison] === 'right') {
newComparisons[`${itemA}-${better}`] = 'left';
}
}
const worseItem = importedSortedList.find(item => item.name === worse);
if (worseItem) {
const worsePosition = parseInt(worseItem.position);
importedSortedList.forEach(item => {
if (parseInt(item.position) > worsePosition) {
newComparisons[`${better}-${item.name}`] = 'left';
}
});
}
}
const originalDisplay = display;
display = function () {
originalDisplay();
const leftName = characterDataToSort[sortedIndexList[leftIndex][leftInnerIndex]].name;
const rightName = characterDataToSort[sortedIndexList[rightIndex][rightInnerIndex]].name;
const decision = makePickDecision(leftName, rightName);
if (decision) {
pick(decision);
}
}
// Function to check if a name is in the imported sorted list
function isNameInImportedList(name) {
return importedSortedList.some(item => item.name === name);
}
const originalPick = pick;
pick = function (sortType) {
const leftName = characterDataToSort[sortedIndexList[leftIndex][leftInnerIndex]].name;
const rightName = characterDataToSort[sortedIndexList[rightIndex][rightInnerIndex]].name;
originalPick(sortType);
if (!isNameInImportedList(leftName) || !isNameInImportedList(rightName)) {
updateNewComparisons(leftName, rightName, sortType);
}
}
function onItemsSwapped(fromHtml, toHtml) {
let fromElement = document.createElement('div');
let toElement = document.createElement('div');
fromElement.innerHTML = fromHtml;
toElement.innerHTML = toHtml;
let fromName = fromElement.querySelector('.right').textContent.trim();
let toName = toElement.querySelector('.right').textContent.trim();
// Find the indices in sortedIndexList[0] that match the swapped names
let fromIndex = sortedIndexList[0].findIndex(id => characterDataToSort[id]['name'].startsWith(fromName));
let toIndex = sortedIndexList[0].findIndex(id => characterDataToSort[id]['name'].startsWith(toName));
// Swap the elements in sortedIndexList[0]
if (fromIndex !== -1 && toIndex !== -1) {
[sortedIndexList[0][fromIndex], sortedIndexList[0][toIndex]] = [sortedIndexList[0][toIndex], sortedIndexList[0][fromIndex]];
}
// Refresh the result display
const imageSelection = document.querySelector('.image.selector > select');
const imageNum = imageSelection.options[imageSelection.selectedIndex].value;
result(Number(imageNum));
}
let draggedItem = null;
function dragStart(e) {
draggedItem = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
}
function dragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
}
function drop(e) {
e.preventDefault();
if (draggedItem !== this) {
let fromHtml = draggedItem.outerHTML;
let toHtml = this.outerHTML;
let tempContent = this.innerHTML;
let tempClass = this.className;
this.innerHTML = draggedItem.innerHTML;
this.className = draggedItem.className;
draggedItem.innerHTML = tempContent;
draggedItem.className = tempClass;
let thisLeft = this.querySelector('.left');
let draggedLeft = draggedItem.querySelector('.left');
if (thisLeft && draggedLeft) {
let tempNumber = thisLeft.textContent;
thisLeft.textContent = draggedLeft.textContent;
draggedLeft.textContent = tempNumber;
}
onItemsSwapped(fromHtml, toHtml);
}
}
const originalGenerateImage = generateImage;
generateImage = function () {
const timeFinished = timestamp + timeTaken;
const tzoffset = (new Date()).getTimezoneOffset() * 60000;
const filename = 'seia-sort-' + (new Date(timeFinished - tzoffset)).toISOString().slice(0, -5).replace('T', '(') + ').png';
html2canvas(document.querySelector('.results'), {width: document.querySelector('.results').scrollWidth}).then(canvas => {
canvas.toBlob(function(blob) {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
});
});
}
document.querySelector('.finished.getimg.button').addEventListener('click', generateImage);
document.querySelector('.finished.getimg.button').removeEventListener('click', originalGenerateImage);
const originalgenerateTextList = generateTextList;
generateTextList = function () {
let names = sortedIndexList[0].map((characterId) => characterDataToSort[characterId]['name']);
let positions = Array.from(document.querySelectorAll('.result:not(:first-child) .left')).map(el => el.textContent);
let data = positions.reduce((str, position, index) => {
str += `${position}. ${names[index]}<br>`;
return str;
}, '');
const oWindow = window.open("", "", "height=640,width=480");
oWindow.document.write(data);
}
document.querySelector('.finished.list.button').addEventListener('click', generateTextList);
document.querySelector('.finished.list.button').removeEventListener('click', originalgenerateTextList);
|