Alt Tag Seia Sorter Alt Tag

"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.

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);
Edit Report
Pub: 30 Sep 2024 15:05 UTC
Edit: 27 Nov 2024 14:10 UTC
Views: 685