diff options
| author | Pinapelz <yukais@pinapelz.com> | 2025-02-06 02:46:23 -0800 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2025-02-06 02:46:23 -0800 |
| commit | 35d6d27a7e9bc741e6b3648b6573369411fd7861 (patch) | |
| tree | a47768e78c5df0d84ab887f87ee6e68a3959eca0 | |
| parent | 716f1fab54ef6c6b3a70f9cd4b2b70f7e9cdd7a8 (diff) | |
add image pop-up modal
| -rw-r--r-- | static/index.js | 77 | ||||
| -rw-r--r-- | static/styles.css | 68 | ||||
| -rw-r--r-- | templates/index.html | 16 |
3 files changed, 139 insertions, 22 deletions
diff --git a/static/index.js b/static/index.js index bc850e8..ce54149 100644 --- a/static/index.js +++ b/static/index.js @@ -17,7 +17,6 @@ const MAX_NAME_LEN = 200; const DEFAULT_TIERS = ['S','A','B','C','D','E','F']; const TIER_COLORS = [ - // from S to F '#ff6666', '#f0a731', '#f4d95b', @@ -55,14 +54,11 @@ function reset_row(row) { }); } -// Removes all rows from the tierlist, alongside their content. -// Also empties the untiered images. function hard_reset_list() { tierlist_div.innerHTML = ''; untiered_images.innerHTML = ''; } -// Places back all the tierlist content into the untiered pool. function soft_reset_list() { tierlist_div.querySelectorAll('.row').forEach(reset_row); unsaved_changes = true; @@ -84,7 +80,6 @@ window.addEventListener('load', () => { bind_title_events(); document.getElementById('load-img-input').addEventListener('input', (evt) => { - // @Speed: maybe we can do some async stuff to optimize this let images = document.querySelector('.images'); for (let file of evt.target.files) { let reader = new FileReader(); @@ -97,7 +92,6 @@ window.addEventListener('load', () => { } }); - // Allow copy-pasting image from clipboard document.onpaste = (evt) => { let clip_data = evt.clipboardData || evt.originalEvent.clipboardData; let items = clip_data.items; @@ -159,6 +153,43 @@ window.addEventListener('load', () => { }); void try_load_tierlist_json(); + + const modal = document.getElementById('image-modal'); + const modalImg = document.getElementById('modal-img'); + 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; + modal.style.display = 'flex'; + modalImg.src = img.src; + modalDesc.value = img.dataset.description || ''; + } + + modalClose.addEventListener('click', () => { + modal.style.display = 'none'; + }); + + modalSave.addEventListener('click', () => { + if (currentModalImage) { + currentModalImage.dataset.description = modalDesc.value; + } + modal.style.display = 'none'; + }); + + window.addEventListener('click', (evt) => { + if (evt.target == modal) { + modal.style.display = 'none'; + } + }); + + tierlist_div.addEventListener('click', (evt) => { + if (evt.target.tagName.toUpperCase() === 'IMG') { + showModal(evt.target); + } + }); }); function create_img_with_src(src) { @@ -167,7 +198,7 @@ function create_img_with_src(src) { img.style.userSelect = 'none'; img.classList.add('draggable'); img.draggable = true; - img.ondragstart = "event.dataTransfer.setData('text/plain', null)"; + img.ondragstart = function(evt) { evt.dataTransfer.setData('text/plain', null); }; img.addEventListener('mousedown', (evt) => { dragged_image = evt.target; dragged_image.classList.add("dragged"); @@ -198,7 +229,10 @@ function save_tierlist(filename) { }); serialized_tierlist.rows[i].imgs = []; row.querySelectorAll('img').forEach((img) => { - serialized_tierlist.rows[i].imgs.push(img.src); + serialized_tierlist.rows[i].imgs.push({ + src: img.src, + description: img.dataset.description || '' + }); }); }); @@ -206,7 +240,10 @@ function save_tierlist(filename) { if (untiered_imgs.length > 0) { serialized_tierlist.untiered = []; untiered_imgs.forEach((img) => { - serialized_tierlist.untiered.push(img.src); + serialized_tierlist.untiered.push({ + src: img.src, + description: img.dataset.description || '' + }); }); } @@ -219,8 +256,9 @@ function load_tierlist(serialized_tierlist) { let ser_row = serialized_tierlist.rows[idx]; let elem = add_row(idx, ser_row.name); - for (let img_src of ser_row.imgs ?? []) { - let img = create_img_with_src(img_src); + for (let img_obj of ser_row.imgs ?? []) { + let img = create_img_with_src(img_obj.src); + img.dataset.description = img_obj.description || ''; let td = document.createElement('span'); td.classList.add('item'); td.appendChild(img); @@ -234,8 +272,9 @@ function load_tierlist(serialized_tierlist) { if (serialized_tierlist.untiered) { let images = document.querySelector('.images'); - for (let img_src of serialized_tierlist.untiered) { - let img = create_img_with_src(img_src); + for (let img_obj of serialized_tierlist.untiered) { + let img = create_img_with_src(img_obj.src); + img.dataset.description = img_obj.description || ''; images.appendChild(img); } } @@ -276,7 +315,6 @@ function make_accept_drop(elem) { let dragged_image_parent = dragged_image.parentNode; if (dragged_image_parent.tagName.toUpperCase() === 'SPAN' && dragged_image_parent.classList.contains('item')) { - // We were already in a tier let containing_tr = dragged_image_parent.parentNode; containing_tr.removeChild(dragged_image_parent); } else { @@ -287,11 +325,14 @@ function make_accept_drop(elem) { td.appendChild(dragged_image); let items_container = elem.querySelector('.items'); if (!items_container) { - // Quite lazy hack for <section class='images'> items_container = elem; } items_container.appendChild(td); - + if (elem !== untiered_images) { + dragged_image.draggable = false; + } else { + dragged_image.draggable = true; + } unsaved_changes = true; }); } @@ -457,7 +498,6 @@ function bind_trash_events() { if (dragged_image_parent.tagName.toUpperCase() === 'SPAN' && dragged_image_parent.classList.contains('item')) { - // We were already in a tier let containing_tr = dragged_image_parent.parentNode; containing_tr.removeChild(dragged_image_parent); } @@ -492,7 +532,6 @@ function is_url (str) { } } -// Fetches a tierlist JSON file from the 'url' query parameter and loads it 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)) { @@ -503,4 +542,4 @@ async function try_load_tierlist_json () { load_tierlist(result); } catch (e) { console.error(e); } } -}
\ No newline at end of file +} diff --git a/static/styles.css b/static/styles.css index a0cfef2..6f2d5f3 100644 --- a/static/styles.css +++ b/static/styles.css @@ -242,4 +242,70 @@ img.draggable.dragged, .button:active { font-size: small; color: #ccc; padding: 0px 10px; -}
\ No newline at end of file +} + +.modal { + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.6); + display: none; + align-items: center; + justify-content: center; +} + +.modal-content { + background-color: white; + padding: 30px; + border-radius: 10px; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.5); + width: 90%; + max-width: 800px; + text-align: center; +} + +.close { + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; + cursor: pointer; +} + +.close:hover { + color: black; +} + +#modal-img { + max-width: 100%; + max-height: 40vh; + border-radius: 5px; + margin-bottom: 10px; +} + +#modal-description { + width: 100%; + height: 25vh; + padding: 10px; + margin-top: 10px; + border: 1px solid #ccc; + border-radius: 5px; + font-size: 14px; +} + +#modal-save { + background: #4CAF50; + color: white; + border: none; + padding: 10px 20px; + cursor: pointer; + margin-top: 10px; + border-radius: 5px; +} + +#modal-save:hover { + background: #45a049; +} diff --git a/templates/index.html b/templates/index.html index 15ee038..4b98bdc 100644 --- a/templates/index.html +++ b/templates/index.html @@ -4,7 +4,7 @@ <meta charset='utf-8' lang='en'/> <title>Offline TierMaker</title> <link rel="stylesheet" href="/static/styles.css"> - <script src="/static/index.js"></script> + <script src="/static/index.js" defer></script> </head> <body> <div class='title'> @@ -20,24 +20,28 @@ <div class='buttons-container'> <div class='button'> <label for='load-img-input'> + Add <img src='plus.png' title='Add images'/> </label> <input id='load-img-input' type='file' accept='image/*' multiple/> </div> <div class='button'> <label for='reset-list-input'> + R <img src='reset.png' title='Reset list'> </label> <input id='reset-list-input' type='button' /> </div> <div class='button'> <label for='export-input'> + E <img src='export.png' title='Export'> </label> <input id='export-input' type='button' /> </div> <div class='button'> <label for='import-input'> + I <img src='import.png' title='Import'/> </label> <input id='import-input' type='file' accept='.json' multiple/> @@ -49,7 +53,7 @@ </div> </section> <div class="top-container"> - <img id='trash' src='trash_bin.png' title='Delete image (drag it over here)'></img> + <img id='trash' src='trash_bin.png' title='Delete image (drag it over here)'> <div class='button'> <label for='toggle-layout'> <img src='toggle_layout.png' title='Toggle Layout'/> @@ -57,5 +61,13 @@ <input id='toggle-layout' type='button'/> </div> </div> + <div id="image-modal" class="modal"> + <div class="modal-content"> + <span class="close">×</span> + <img id="modal-img" src="" alt="Image preview" /> + <textarea id="modal-description" placeholder="Enter description here"></textarea> + <button id="modal-save">Save</button> + </div> + </div> </body> </html>
\ No newline at end of file |
