diff options
| author | Pinapelz <yukais@pinapelz.com> | 2025-02-08 21:40:01 -0800 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2025-02-08 21:40:01 -0800 |
| commit | 4c41076570073e8b960742b6e1c2231fecfa6cce (patch) | |
| tree | d9c0412334eaf963bf9bcb32f019c1303a1e478c /index.js | |
| parent | da16c5147e37165e420a5205adc46dcfe66baa17 (diff) | |
remove rust portion as its no longer necessary
Diffstat (limited to 'index.js')
| -rw-r--r-- | index.js | 659 |
1 files changed, 659 insertions, 0 deletions
diff --git a/index.js b/index.js new file mode 100644 index 0000000..366a0b5 --- /dev/null +++ b/index.js @@ -0,0 +1,659 @@ +/* + Offline Tierlist Maker + Copyright (C) 2022 silverweed + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. +*/ + +'use strict'; + +const MAX_NAME_LEN = 200; +const DEFAULT_TIERS = ['S', 'A', 'B', 'C', 'D', 'E', 'F']; +const TIER_COLORS = [ + '#ff6666', + '#f0a731', + '#f4d95b', + '#66ff66', + '#58c8f4', + '#5b76f4', + '#f45bed' +]; + +let unique_id = 0; + +let unsaved_changes = false; + +const LAYOUT_HORIZONTAL = 0; +const LAYOUT_VERTICAL = 1; +let cur_layout = LAYOUT_HORIZONTAL; + +// Contains [[header, input, label]] +let all_headers = []; +let headers_orig_min_width; + +// DOM elems +let untiered_images; +let tierlist_div; +let dragged_image; + +function reset_row(row) { + row.querySelectorAll('span.item').forEach((item) => { + for (let i = 0; i < item.children.length; ++i) { + let img = item.children[i]; + item.removeChild(img); + untiered_images.appendChild(img); + } + item.parentNode.removeChild(item); + }); +} + +function hard_reset_list() { + tierlist_div.innerHTML = ''; + untiered_images.innerHTML = ''; +} + +function soft_reset_list() { + tierlist_div.querySelectorAll('.row').forEach(reset_row); + unsaved_changes = true; +} + +window.addEventListener('load', () => { + untiered_images = document.querySelector('.images'); + tierlist_div = document.querySelector('.tierlist'); + + for (let i = 0; i < DEFAULT_TIERS.length; ++i) { + add_row(i, DEFAULT_TIERS[i]); + } + recompute_header_colors(); + + headers_orig_min_width = all_headers[0][0].clientWidth; + + make_accept_drop(document.querySelector('.images')); + + bind_title_events(); + + document.getElementById('load-img-input').addEventListener('input', (evt) => { + let images = document.querySelector('.images'); + for (let file of evt.target.files) { + let reader = new FileReader(); + reader.addEventListener('load', (load_evt) => { + let img = create_img_with_src(load_evt.target.result); + images.appendChild(img); + unsaved_changes = true; + }); + reader.readAsDataURL(file); + } + }); + + document.onpaste = (evt) => { + let clip_data = evt.clipboardData || evt.originalEvent.clipboardData; + let items = clip_data.items; + let images = document.querySelector('.images'); + for (let item of items) { + if (item.kind === 'file') { + let blob = item.getAsFile(); + let reader = new FileReader(); + reader.onload = (load_evt) => { + let img = create_img_with_src(load_evt.target.result); + images.appendChild(img); + unsaved_changes = true; + }; + reader.readAsDataURL(blob); + } + } + }; + + document.getElementById('reset-list-input').addEventListener('click', () => { + if (confirm('Reset Tierlist? (this will place all images back in the pool)')) { + soft_reset_list(); + } + }); + + document.getElementById('export-input').addEventListener('click', () => { + let name = prompt('Please give a name to the file. This will export your tiers as a JSON file so you can continue working on it later'); + if (name) { + save_tierlist(`${name}.json`); + } + }); + + document.getElementById('export-input-html').addEventListener('click', () => { + let name = prompt('This will export your tiers as a single interactive HTML file. Please give a name to the file'); + if (name) { + save_tierlist_with_template(`${name}.html`); + } + }); + + document.getElementById('import-input').addEventListener('input', (evt) => { + if (!evt.target.files) { + return; + } + let file = evt.target.files[0]; + let reader = new FileReader(); + reader.addEventListener('load', (load_evt) => { + let raw = load_evt.target.result; + let parsed = JSON.parse(raw); + if (!parsed) { + alert("Failed to parse data"); + return; + } + hard_reset_list(); + load_tierlist(parsed); + }); + reader.readAsText(file); + }); + + bind_trash_events(); + bind_toggle_layout_events(); + + window.addEventListener('beforeunload', (evt) => { + if (!unsaved_changes) return null; + var msg = "You have unsaved changes. Leave anyway?"; + (evt || window.event).returnValue = msg; + return msg; + }); + + void try_load_tierlist_json(); + + const modal = document.getElementById('image-modal'); + const modalImg = document.getElementById('modal-img'); + const modalTitle = document.getElementById('modal-title'); + const modalDesc = document.getElementById('modal-description'); + const modalSave = document.getElementById('modal-save'); + const modalClose = document.querySelector('.modal .close'); + let currentModalImage = null; + + function showModal(img) { + currentModalImage = img; + document.body.style.overflow = 'hidden'; + modal.style.display = 'flex'; + modalImg.src = img.src; + modalTitle.value = img.dataset.title || ''; + modalDesc.value = img.dataset.description || ''; + } + + modalClose.addEventListener('click', () => { + modal.style.display = 'none'; + document.body.style.overflow = 'hidden'; + }); + + modalSave.addEventListener('click', () => { + if (currentModalImage) { + currentModalImage.dataset.title = modalTitle.value; + currentModalImage.dataset.description = modalDesc.value; + } + modal.style.display = 'none'; + document.body.style.overflow = 'hidden'; + }); + + window.addEventListener('click', (evt) => { + if (evt.target == modal) { + modal.style.display = 'none'; + document.body.style.overflow = 'hidden'; + } + }); + + tierlist_div.addEventListener('click', (evt) => { + if (evt.target.tagName.toUpperCase() === 'IMG') { + showModal(evt.target); + } + }); +}); + +function create_img_with_src(src) { + let img = document.createElement('img'); + img.src = src; + img.style.userSelect = 'none'; + img.classList.add('draggable'); + img.draggable = true; + + img.addEventListener("dragstart", (evt) => { + evt.dataTransfer.setData("text/plain", null); + dragged_image = evt.target; + dragged_image.classList.add("dragged"); + }); + + img.addEventListener("mouseenter", (evt) => { + const title = evt.target.dataset.title; + if (title) { + evt.target.title = title; + } + }); + + img.addEventListener("dragend", (evt) => { + if (dragged_image) { + dragged_image.classList.remove("dragged"); + } + dragged_image = null; + }); + + return img; +} + + +function save(filename, text) { + unsaved_changes = false; + + var el = document.createElement('a'); + el.setAttribute('href', 'data:text/html;charset=utf-8,' + encodeURIComponent(text)); + el.setAttribute('download', filename); + el.style.display = 'none'; + document.body.appendChild(el); + el.click(); + document.body.removeChild(el); +} + +function save_tierlist(filename) { + let serialized_tierlist = { + title: document.querySelector('.title-label').innerText, + rows: [], + }; + tierlist_div.querySelectorAll('.row').forEach((row, i) => { + serialized_tierlist.rows.push({ + name: row.querySelector('.header label').innerText.substr(0, MAX_NAME_LEN) + }); + serialized_tierlist.rows[i].imgs = []; + row.querySelectorAll('img').forEach((img) => { + serialized_tierlist.rows[i].imgs.push({ + src: img.src, + title: img.dataset.title || '', + description: img.dataset.description || '' + }); + }); + }); + + let untiered_imgs = document.querySelectorAll('.images img'); + if (untiered_imgs.length > 0) { + serialized_tierlist.untiered = []; + untiered_imgs.forEach((img) => { + serialized_tierlist.untiered.push({ + src: img.src, + title: img.dataset.title || '', + description: img.dataset.description || '' + }); + }); + } + + save(filename, JSON.stringify(serialized_tierlist)); +} +async function save_tierlist_with_template(filename) { + let serialized_tierlist = { + title: document.querySelector('.title-label').innerText, + rows: [] + }; + + tierlist_div.querySelectorAll('.row').forEach((row, i) => { + serialized_tierlist.rows.push({ + name: row.querySelector('.header').innerText.substr(0, MAX_NAME_LEN) + }); + serialized_tierlist.rows[i].imgs = []; + row.querySelectorAll('img').forEach((img) => { + serialized_tierlist.rows[i].imgs.push({ + src: img.src, + title: img.dataset.title || '', + description: img.dataset.description || '' + }); + }); + }); + + let untiered_imgs = document.querySelectorAll('.images img'); + if (untiered_imgs.length > 0) { + serialized_tierlist.untiered = []; + untiered_imgs.forEach((img) => { + serialized_tierlist.untiered.push({ + src: img.src, + title: img.dataset.title || '', + description: img.dataset.description || '' + }); + }); + } + + try { + // Fetch resources + let [templateResponse, jsResponse, cssResponse] = await Promise.all([ + fetch('/tiers'), + fetch('/tiers.js'), + fetch('/tiers.css') + ]); + + let [templateHTML, scriptContent, styleContent] = await Promise.all([ + templateResponse.text(), + jsResponse.text(), + cssResponse.text() + ]); + + // Inject the EMBEDDED_JSON inside a script tag + let jsonScript = `<script> + const EMBEDDED_JSON = ${JSON.stringify(serialized_tierlist, null, 4)}; + window.addEventListener('load', () => { + hard_reset_list(); + load_tierlist(EMBEDDED_JSON); + }); + </script>`; + + let inlineScript = `<script>\n${scriptContent}\n</script>`; + let inlineCSS = `<style>\n${styleContent}\n</style>`; + + templateHTML = templateHTML.replace("</head>", inlineCSS + "\n</head>"); + let updatedHTML = templateHTML.replace("</body>", jsonScript + "\n" + inlineScript + "\n</body>"); + save(filename, updatedHTML); + } catch (e) { + console.error("Error fetching resources:", e); + } +} + + + +function load_tierlist(serialized_tierlist) { + document.querySelector('.title-label').innerText = serialized_tierlist.title; + for (let idx in serialized_tierlist.rows) { + let ser_row = serialized_tierlist.rows[idx]; + let elem = add_row(idx, ser_row.name); + + for (let img_obj of ser_row.imgs ?? []) { + let img = create_img_with_src(img_obj.src); + img.dataset.title = img_obj.title || ''; + img.dataset.description = img_obj.description || ''; + let td = document.createElement('span'); + td.classList.add('item'); + td.appendChild(img); + let items_container = elem.querySelector('.items'); + items_container.appendChild(td); + } + + elem.querySelector('label').innerText = ser_row.name; + } + recompute_header_colors(); + + if (serialized_tierlist.untiered) { + let images = document.querySelector('.images'); + for (let img_obj of serialized_tierlist.untiered) { + let img = create_img_with_src(img_obj.src); + img.dataset.title = img_obj.title || ''; + img.dataset.description = img_obj.description || ''; + images.appendChild(img); + } + } + + resize_headers(); + + unsaved_changes = false; +} + +function end_drag(evt) { + if (dragged_image) { + dragged_image.classList.remove("dragged"); + } + dragged_image = null; +} + +window.addEventListener('mouseup', end_drag); +window.addEventListener('dragend', end_drag); + + +function make_accept_drop(elem) { + elem.classList.add('droppable'); + + elem.addEventListener('dragenter', (evt) => { + evt.preventDefault(); + evt.target.classList.add('drag-entered'); + }); + + elem.addEventListener('dragleave', (evt) => { + evt.target.classList.remove('drag-entered'); + }); + + elem.addEventListener('dragover', (evt) => { + evt.preventDefault(); + }); + + elem.addEventListener('drop', (evt) => { + evt.preventDefault(); + evt.target.classList.remove('drag-entered'); + + if (!dragged_image) { + return; + } + + let dragged_image_parent = dragged_image.parentNode; + if (dragged_image_parent.tagName.toUpperCase() === 'SPAN' && + dragged_image_parent.classList.contains('item')) { + let containing_tr = dragged_image_parent.parentNode; + containing_tr.removeChild(dragged_image_parent); + } else { + dragged_image_parent.removeChild(dragged_image); + } + + let td = document.createElement('span'); + td.classList.add('item'); + td.appendChild(dragged_image); + let items_container = elem.querySelector('.items'); + + if (!items_container) { + items_container = elem; + } + items_container.appendChild(td); + + dragged_image.draggable = true; + dragged_image.classList.remove("dragged"); + + dragged_image.addEventListener("dragstart", (e) => { + e.dataTransfer.setData("text/plain", null); + dragged_image.classList.add("dragged"); + }); + + unsaved_changes = true; + }); +} + + +function enable_edit_on_click(container, input, label) { + function change_label(evt) { + input.style.display = 'none'; + label.innerText = input.value; + label.style.display = 'inline'; + unsaved_changes = true; + } + + input.addEventListener('change', change_label); + input.addEventListener('focusout', change_label); + + container.addEventListener('click', (evt) => { + label.style.display = 'none'; + input.value = label.innerText.substr(0, MAX_NAME_LEN); + input.style.display = 'inline'; + input.select(); + }); +} + +function bind_title_events() { + let title_label = document.querySelector('.title-label'); + let title_input = document.getElementById('title-input'); + let title = document.querySelector('.title'); + + enable_edit_on_click(title, title_input, title_label); +} + +function create_label_input(row, row_idx, row_name) { + let input = document.createElement('input'); + input.id = `input-tier-${unique_id++}`; + input.type = 'text'; + input.addEventListener('change', resize_headers); + let label = document.createElement('label'); + label.htmlFor = input.id; + label.innerText = row_name; + + let header = row.querySelector('.header'); + all_headers.splice(row_idx, 0, [header, input, label]); + header.appendChild(label); + header.appendChild(input); + + enable_edit_on_click(header, input, label); +} + +function resize_headers() { + let max_width = headers_orig_min_width; + for (let [other_header, _i, label] of all_headers) { + max_width = Math.max(max_width, label.clientWidth); + } + + for (let [other_header, _i2, _l2] of all_headers) { + other_header.style.minWidth = `${max_width}px`; + } +} + +function add_row(index, name) { + let div = document.createElement('div'); + let header = document.createElement('span'); + let items = document.createElement('span'); + div.classList.add('row'); + header.classList.add('header'); + items.classList.add('items'); + div.appendChild(header); + div.appendChild(items); + let row_buttons = document.createElement('div'); + row_buttons.classList.add('row-buttons'); + let btn_plus_up = document.createElement('input'); + btn_plus_up.type = "button"; + btn_plus_up.value = '+'; + btn_plus_up.title = "Add row above"; + btn_plus_up.addEventListener('click', (evt) => { + let parent_div = evt.target.parentNode.parentNode; + let rows = Array.from(tierlist_div.children); + let idx = rows.indexOf(parent_div); + console.assert(idx >= 0); + add_row(idx, ''); + recompute_header_colors(); + }); + let btn_rm = document.createElement('input'); + btn_rm.type = "button"; + btn_rm.value = '-'; + btn_rm.title = "Remove row"; + btn_rm.addEventListener('click', (evt) => { + let rows = Array.from(tierlist_div.querySelectorAll('.row')); + if (rows.length < 2) return; + let parent_div = evt.target.parentNode.parentNode; + let idx = rows.indexOf(parent_div); + console.assert(idx >= 0); + if (rows[idx].querySelectorAll('img').length === 0 || + confirm(`Remove tier ${rows[idx].querySelector('.header label').innerText}? (This will move back all its content to the untiered pool)`)) { + rm_row(idx); + } + recompute_header_colors(); + }); + let btn_plus_down = document.createElement('input'); + btn_plus_down.type = "button"; + btn_plus_down.value = '+'; + btn_plus_down.title = "Add row below"; + btn_plus_down.addEventListener('click', (evt) => { + let parent_div = evt.target.parentNode.parentNode; + let rows = Array.from(tierlist_div.children); + let idx = rows.indexOf(parent_div); + console.assert(idx >= 0); + add_row(idx + 1, name); + recompute_header_colors(); + }); + row_buttons.appendChild(btn_plus_up); + row_buttons.appendChild(btn_rm); + row_buttons.appendChild(btn_plus_down); + div.appendChild(row_buttons); + + let rows = tierlist_div.children; + if (index === rows.length) { + tierlist_div.appendChild(div); + } else { + let nxt_child = rows[index]; + tierlist_div.insertBefore(div, nxt_child); + } + + make_accept_drop(div); + create_label_input(div, index, name); + + return div; +} + +function rm_row(idx) { + let row = tierlist_div.children[idx]; + reset_row(row); + tierlist_div.removeChild(row); +} + +function recompute_header_colors() { + tierlist_div.querySelectorAll('.row').forEach((row, row_idx) => { + let color = TIER_COLORS[row_idx % TIER_COLORS.length]; + row.querySelector('.header').style.backgroundColor = color; + }); +} + +function bind_trash_events() { + let trash = document.getElementById('trash'); + trash.classList.add('droppable'); + trash.addEventListener('dragenter', (evt) => { + evt.preventDefault(); + evt.target.src = '/img/trash-2.svg'; + }); + trash.addEventListener('dragexit', (evt) => { + evt.preventDefault(); + evt.target.src = '/img/trash.svg'; + }); + trash.addEventListener('dragover', (evt) => { + evt.preventDefault(); + }); + trash.addEventListener('drop', (evt) => { + evt.preventDefault(); + evt.target.src = '/img/trash.svg'; + if (dragged_image) { + let dragged_image_parent = dragged_image.parentNode; + if (dragged_image_parent.tagName.toUpperCase() === 'SPAN' && + dragged_image_parent.classList.contains('item')) { + let containing_tr = dragged_image_parent.parentNode; + containing_tr.removeChild(dragged_image_parent); + } + dragged_image.remove(); + } + }); +} + +function bind_toggle_layout_events() { + let toggle = document.getElementById('toggle-layout'); + toggle.addEventListener('click', () => { + set_layout((cur_layout + 1) % 2); + }); +} + +function set_layout(layout) { + let main = document.getElementsByClassName("main-content")[0]; + if (layout === LAYOUT_VERTICAL) { + main.classList.add("vertical"); + } else { + main.classList.remove("vertical"); + } + cur_layout = layout; +} + +function is_url(str) { + try { + new URL(str); + return true; + } catch (e) { + return false; + } +} + +async function try_load_tierlist_json() { + const load_from_url = new URLSearchParams(window.location.search).get('url'); + if (load_from_url !== null && is_url(load_from_url)) { + try { + let result = await fetch(load_from_url); + result = await result.json(); + hard_reset_list(); + load_tierlist(result); + } catch (e) { console.error(e); } + } +} |
