A Neptunia Character Sorter
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'master' of https://github.com/execfera/charasort

+86 -23
+7
LICENSE.md
··· 1 + Copyright 2018 Frelia (@execfera) 2 + 3 + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 + 5 + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 + 7 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+2 -2
README.md
··· 21 21 This is a list of things you need to change for your sorter, for each file. 22 22 23 23 * `index.html` 24 - * Sorter name: Change under `starting start button`. 24 + * Sorter name: Change under `starting start button` and the `<title>` tags. 25 25 * Starting banner images: 120px x 180px, under `left sort image` and `right sort image`. 26 26 * OpenGraph tags: `og:site_name`, `og:description` and `og:image` will show up on embeds when linked to social media such as Facebook, Twitter and Discord. 27 27 * Sorter info: Insert whatever you like under the `info` tag. ··· 86 86 87 87 * `name`: The name of the option to be displayed. **Required.** 88 88 * `key`: A shorthand reference, used to refer to it in the character data. **Required.** 89 - * `tooltip`: Some optional information that appears when you hover over the option. 89 + * `tooltip`: Some optional information that appears when you hover over the option. If not provided, defaults to the option's name. 90 90 * `checked`: If set to `true`, this option will be checked when your sorter starts. If not provided, defaults to `false`. 91 91 92 92 Example:
-1
index.html
··· 60 60 61 61 <div class="info"> 62 62 <a href="mailto:contact@jamessliu.com">Contact</a> | <a href="https://github.com/james7132/charasort/">Source Code</a> | <a class="clearsave">Clear Save Data</a> 63 - 64 63 <br><br> 65 64 66 65 <p>Sorter for Neptunia characters. Pick your sources, and hit the Start button.</p>
+1 -1
src/css/styles.css
··· 159 159 cursor: pointer; 160 160 } 161 161 162 - label.tooltip:hover { 162 + label:hover { 163 163 color: #990000; 164 164 } 165 165
+76 -19
src/js/main.js
··· 50 50 let finalCharacters = []; 51 51 let loading = false; 52 52 let totalBattles = 0; 53 - let storedSaveType = localStorage.getItem('saveType'); 53 + let sorterURL = window.location.host + window.location.pathname; 54 + let storedSaveType = localStorage.getItem(`${sorterURL}_saveType`); 54 55 55 56 /** Initialize script. */ 56 57 function init() { 58 + 57 59 /** Define button behavior. */ 58 60 document.querySelector('.starting.start.button').addEventListener('click', start); 59 61 document.querySelector('.starting.load.button').addEventListener('click', loadProgress); ··· 104 106 document.querySelector('.image.selector').insertAdjacentElement('beforeend', document.createElement('select')); 105 107 106 108 /** Initialize image quantity selector for results. */ 107 - for (let i = 1; i <= 10; i++) { 109 + for (let i = 0; i <= 10; i++) { 108 110 const select = document.createElement('option'); 109 111 select.value = i; 110 112 select.text = i; ··· 138 140 characterDataToSort = characterData.slice(0); 139 141 140 142 /** Check selected options and convert to boolean array form. */ 143 + optTaken = []; 144 + 141 145 options.forEach(opt => { 142 146 if ('sub' in opt) { 143 147 if (!document.getElementById(`cbgroup-${opt.key}`).checked) optTaken.push(false); ··· 180 184 return subList; 181 185 }, []); 182 186 characterDataToSort = characterDataToSort.filter(char => { 183 - return char.opts[opt.key].some(key => subArray.includes(key)); 187 + if (!(opt.key in char.opts)) console.warn(`Warning: ${opt.key} not set for ${char.name}.`); 188 + return opt.key in char.opts && char.opts[opt.key].some(key => subArray.includes(key)); 184 189 }); 185 190 } 186 191 } else if (optTaken[index]) { ··· 188 193 } 189 194 }); 190 195 196 + if (characterDataToSort.length < 2) { 197 + alert('Cannot sort with less than two characters. Please reselect.'); 198 + return; 199 + } 200 + 191 201 /** Shuffle character array with timestamp seed. */ 192 202 timestamp = timestamp || new Date().getTime(); 193 203 if (new Date(timestamp) < new Date(currentVersion)) { timeError = true; } ··· 271 281 const leftChar = characterDataToSort[leftCharIndex]; 272 282 const rightChar = characterDataToSort[rightCharIndex]; 273 283 284 + const charNameDisp = name => { 285 + const charName = reduceTextWidth(name, 'Arial 12.8px', 220); 286 + const charTooltip = name !== charName ? name : ''; 287 + return `<p title="${charTooltip}">${charName}</p>`; 288 + }; 289 + 274 290 progressBar(`Battle No. ${battleNo}`, percent); 275 291 276 292 document.querySelector('.left.sort.image').src = leftChar.img; 277 293 document.querySelector('.right.sort.image').src = rightChar.img; 278 294 279 - document.querySelector('.left.sort.text > p').innerHTML = leftChar.name; 280 - document.querySelector('.right.sort.text > p').innerHTML = rightChar.name; 295 + 296 + 297 + document.querySelector('.left.sort.text').innerHTML = charNameDisp(leftChar.name); 298 + document.querySelector('.right.sort.text').innerHTML = charNameDisp(rightChar.name); 281 299 282 300 /** Autopick if choice has been given. */ 283 301 if (choices.length !== battleNo - 1) { ··· 461 479 document.querySelector('.info').style.display = 'none'; 462 480 463 481 const header = '<div class="result head"><div class="left">Order</div><div class="right">Name</div></div>'; 464 - const timeStr = `This sorter was completed on ${new Date(timestamp + timeTaken).toString()} and took ${msToReadableTime(timeTaken)}.`; 465 - const imgRes = (char, num) => `<div class="result image"><div class="left"><span>${num}</span></div><div class="right"><img src="${char.img}"><div>${char.name}</div></div></div>`; 466 - const res = (char, num) => `<div class="result"><div class="left">${num}</div><div class="right">${char.name}</div></div>`; 482 + const timeStr = `This sorter was completed on ${new Date(timestamp + timeTaken).toString()} and took ${msToReadableTime(timeTaken)}. <a href="${location.protocol}//${sorterURL}">Do another sorter?</a>`; 483 + const imgRes = (char, num) => { 484 + const charName = reduceTextWidth(char.name, 'Arial 12px', 160); 485 + const charTooltip = char.name !== charName ? char.name : ''; 486 + return `<div class="result image"><div class="left"><span>${num}</span></div><div class="right"><img src="${char.img}"><div><span title="${charTooltip}">${charName}</span></div></div></div>`; 487 + } 488 + const res = (char, num) => { 489 + const charName = reduceTextWidth(char.name, 'Arial 12px', 160); 490 + const charTooltip = char.name !== charName ? char.name : ''; 491 + return `<div class="result"><div class="left">${num}</div><div class="right"><span title="${charTooltip}">${charName}</span></div></div>`; 492 + } 467 493 468 494 let rankNum = 1; 469 495 let tiedRankNum = 1; ··· 527 553 function saveProgress(saveType) { 528 554 const saveData = generateSavedata(); 529 555 530 - localStorage.setItem('saveData', saveData); 531 - localStorage.setItem('saveType', saveType); 556 + localStorage.setItem(`${sorterURL}_saveData`, saveData); 557 + localStorage.setItem(`${sorterURL}_saveType`, saveType); 532 558 533 559 if (saveType !== 'Autosave') { 534 - const saveURL = `${window.location.origin}${window.location.pathname}?${saveData}`; 560 + const saveURL = `${location.protocol}//${sorterURL}?${saveData}`; 535 561 const inProgressText = 'You may click Load Progress after this to resume, or use this URL.'; 536 562 const finishedText = 'You may use this URL to share this result, or click Load Last Result to view it again.'; 537 563 ··· 543 569 * Load progress from local browser storage. 544 570 */ 545 571 function loadProgress() { 546 - const saveData = localStorage.getItem('saveData'); 572 + const saveData = localStorage.getItem(`${sorterURL}_saveData`); 547 573 548 574 if (saveData) decodeQuery(saveData); 549 575 } ··· 554 580 function clearProgress() { 555 581 storedSaveType = ''; 556 582 557 - localStorage.removeItem('saveData'); 558 - localStorage.removeItem('saveType'); 583 + localStorage.removeItem(`${sorterName}_saveData`); 584 + localStorage.removeItem(`${sorterName}_saveType`); 559 585 560 586 document.querySelectorAll('.starting.start.button').forEach(el => el.style['grid-row'] = 'span 6'); 561 587 document.querySelectorAll('.starting.load.button').forEach(el => el.style.display = 'none'); ··· 569 595 html2canvas(document.querySelector('.results')).then(canvas => { 570 596 const dataURL = canvas.toDataURL(); 571 597 const imgButton = document.querySelector('.finished.getimg.button'); 598 + const resetButton = document.createElement('a'); 572 599 573 600 imgButton.removeEventListener('click', generateImage); 574 601 imgButton.innerHTML = ''; 575 - imgButton.insertAdjacentHTML('beforeend', `<a href="${dataURL}" download="${filename}">Download Image</a>`); 602 + imgButton.insertAdjacentHTML('beforeend', `<a href="${dataURL}" download="${filename}">Download Image</a><br><br>`); 603 + 604 + resetButton.insertAdjacentText('beforeend', 'Reset'); 605 + resetButton.addEventListener('click', (event) => { 606 + imgButton.addEventListener('click', generateImage); 607 + imgButton.innerHTML = 'Generate Image'; 608 + event.stopPropagation(); 609 + }); 610 + imgButton.insertAdjacentElement('beforeend', resetButton); 576 611 }); 577 612 } 578 613 ··· 613 648 /** Populate option list. */ 614 649 function populateOptions() { 615 650 const optList = document.querySelector('.options'); 616 - const optInsert = (name, id, tooltip = '', checked = true, disabled = false) => { 617 - return `<div><label class="${tooltip?'tooltip':''}" title="${tooltip}"><input id="cb-${id}" type="checkbox" ${checked?'checked':''} ${disabled?'disabled':''}> ${name}</label></div>`; 651 + const optInsert = (name, id, tooltip, checked = true, disabled = false) => { 652 + return `<div><label title="${tooltip?tooltip:name}"><input id="cb-${id}" type="checkbox" ${checked?'checked':''} ${disabled?'disabled':''}> ${name}</label></div>`; 618 653 }; 619 - const optInsertLarge = (name, id, tooltip = '', checked = true) => { 620 - return `<div class="large option"><label class="${tooltip?'tooltip':''}" title="${tooltip}"><input id="cbgroup-${id}" type="checkbox" ${checked?'checked':''}> ${name}</label></div>`; 654 + const optInsertLarge = (name, id, tooltip, checked = true) => { 655 + return `<div class="large option"><label title="${tooltip?tooltip:name}"><input id="cbgroup-${id}" type="checkbox" ${checked?'checked':''}> ${name}</label></div>`; 621 656 }; 622 657 623 658 /** Clear out any previous options. */ ··· 786 821 if (minutes) content.push(minutes + " minute" + (minutes > 1 ? "s" : "")); 787 822 if (t) content.push(t + " second" + (t > 1 ? "s" : "")); 788 823 return content.slice(0,3).join(', '); 824 + } 825 + 826 + /** 827 + * Reduces text to a certain rendered width. 828 + * 829 + * @param {string} text Text to reduce. 830 + * @param {string} font Font applied to text. Example "12px Arial". 831 + * @param {number} width Width of desired width in px. 832 + */ 833 + function reduceTextWidth(text, font, width) { 834 + const canvas = reduceTextWidth.canvas || (reduceTextWidth.canvas = document.createElement("canvas")); 835 + const context = canvas.getContext("2d"); 836 + context.font = font; 837 + if (context.measureText(text).width < width * 0.8) { 838 + return text; 839 + } else { 840 + let reducedText = text; 841 + while (context.measureText(reducedText).width + context.measureText('..').width > width * 0.8) { 842 + reducedText = reducedText.slice(0, -1); 843 + } 844 + return reducedText + '..'; 845 + } 789 846 } 790 847 791 848 window.onload = init;