From 7d0eb942b9cd0f5748d44697fa4e357ea0203931 Mon Sep 17 00:00:00 2001 From: Chris Malone Date: Fri, 26 Sep 2025 00:07:18 +1000 Subject: [PATCH] implemented drag and drop queue reordering --- wgp.py | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 161 insertions(+), 14 deletions(-) diff --git a/wgp.py b/wgp.py index 95ee8cd..a8f288d 100644 --- a/wgp.py +++ b/wgp.py @@ -888,6 +888,29 @@ def update_task_thumbnails(task, inputs): "end_image_data_base64": [pil_to_base64_uri(img, format="jpeg", quality=70) for img in end_image_data] if end_image_data != None else None }) +def move_task(queue, old_index_str, new_index_str): + try: + old_idx = int(old_index_str) + new_idx = int(new_index_str) + except (ValueError, IndexError): + return update_queue_data(queue) + + with lock: + old_idx += 1 + new_idx += 1 + + if not (0 < old_idx < len(queue)): + return update_queue_data(queue) + + item_to_move = queue.pop(old_idx) + if old_idx < new_idx: + new_idx -= 1 + clamped_new_idx = max(1, min(new_idx, len(queue))) + + queue.insert(clamped_new_idx, item_to_move) + + return update_queue_data(queue) + def move_up(queue, selected_indices): if not selected_indices or len(selected_indices) == 0: return update_queue_data(queue) @@ -1462,14 +1485,13 @@ def generate_queue_html(queue): + - - @@ -1505,22 +1527,20 @@ def generate_queue_html(queue): end_img_md = "" if end_img_uri: end_img_md = f'
{end_img_labels[0]}
' - - up_btn = f"""""" - down_btn = f"""""" + + drag_handle = f'' edit_btn = f"""""" remove_btn = f"""""" row_html = f""" - + + {drag_handle} - - @@ -6954,12 +6974,14 @@ def handle_queue_action(state, action_string): queue = gen.get("queue", []) try: - action, row_index_str = action_string.split('_') - row_index = int(row_index_str) + parts = action_string.split('_') + action = parts[0] + params = parts[1:] except (IndexError, ValueError): return gr.HTML(), gr.Tabs() if action == "edit": + row_index = int(params[0]) state["editing_task_index"] = row_index task_to_edit_index = row_index + 1 @@ -6971,11 +6993,12 @@ def handle_queue_action(state, action_string): gr.Warning("Task index out of bounds.") return update_queue_data(queue), gr.Tabs() - elif action == "up": - return move_up(queue, [row_index]), gr.Tabs() - elif action == "down": - return move_down(queue, [row_index]), gr.Tabs() + elif action == "move" and len(params) == 3 and params[1] == "to": + old_index_str, new_index_str = params[0], params[2] + return move_task(queue, old_index_str, new_index_str), gr.Tabs() + elif action == "remove": + row_index = int(params[0]) new_queue_data = remove_task(queue, [row_index]) gen["prompts_max"] = gen.get("prompts_max", 0) - 1 update_status(state) @@ -9582,6 +9605,20 @@ def create_ui(): display: block; margin: auto; } + #queue_html_container .drag-handle { + cursor: grab; + user-select: none; + } + #queue_html_container tr.dragging { + opacity: 0.5; + background: #2d3748; + } + #queue_html_container tr.drag-over-top { + border-top: 2px solid #4299e1; + } + #queue_html_container tr.drag-over-bottom { + border-bottom: 2px solid #4299e1; + } #image-modal-container { position: fixed; top: 0; @@ -9771,6 +9808,116 @@ def create_ui(): } }; + let draggedItem = null; + + function initializeQueueDragAndDrop() { + const queueTbody = document.querySelector('#queue_html_container table > tbody'); + if (!queueTbody || queueTbody.dataset.dndInitialized) { + return; + } + + queueTbody.dataset.dndInitialized = 'true'; + + queueTbody.addEventListener('dragstart', (e) => { + if (e.target.classList.contains('drag-handle')) { + draggedItem = e.target.closest('.draggable-row'); + if (draggedItem) { + setTimeout(() => { + draggedItem.classList.add('dragging'); + }, 0); + } + } + }); + + queueTbody.addEventListener('dragend', (e) => { + if (draggedItem) { + draggedItem.classList.remove('dragging'); + draggedItem = null; + document.querySelectorAll('.drag-over-top, .drag-over-bottom').forEach(el => { + el.classList.remove('drag-over-top', 'drag-over-bottom'); + }); + } + }); + + queueTbody.addEventListener('dragover', (e) => { + e.preventDefault(); + const targetRow = e.target.closest('.draggable-row'); + + // Clear previous indicators + document.querySelectorAll('.drag-over-top, .drag-over-bottom').forEach(el => { + el.classList.remove('drag-over-top', 'drag-over-bottom'); + }); + + if (targetRow && draggedItem && targetRow !== draggedItem) { + const rect = targetRow.getBoundingClientRect(); + const midpoint = rect.top + rect.height / 2; + + if (e.clientY < midpoint) { + targetRow.classList.add('drag-over-top'); + } else { + targetRow.classList.add('drag-over-bottom'); + } + } + }); + + queueTbody.addEventListener('dragleave', (e) => { + const relatedTarget = e.relatedTarget; + const queueTable = e.currentTarget.closest('table'); + if (queueTable && !queueTable.contains(relatedTarget)) { + document.querySelectorAll('.drag-over-top, .drag-over-bottom').forEach(el => { + el.classList.remove('drag-over-top', 'drag-over-bottom'); + }); + } + }); + + queueTbody.addEventListener('drop', (e) => { + e.preventDefault(); + const targetRow = e.target.closest('.draggable-row'); + + if (draggedItem && targetRow && targetRow !== draggedItem) { + const oldIndex = draggedItem.dataset.index; + let newIndex = parseInt(targetRow.dataset.index); + + // If dropping on the bottom half, the new index is after the target row + if (targetRow.classList.contains('drag-over-bottom')) { + newIndex++; + } + + if (oldIndex != newIndex) { + const action = `move_${oldIndex}_to_${newIndex}`; + window.updateAndTrigger(action); + } + } + + // Cleanup visual styles + document.querySelectorAll('.drag-over-top, .drag-over-bottom').forEach(el => { + el.classList.remove('drag-over-top', 'drag-over-bottom'); + }); + if (draggedItem) { + draggedItem.classList.remove('dragging'); + draggedItem = null; + } + }); + } + + const observer = new MutationObserver((mutationsList, observer) => { + for(const mutation of mutationsList) { + if (mutation.type === 'childList') { + const queueContainer = document.querySelector('#queue_html_container'); + if (queueContainer && queueContainer.querySelector('table > tbody')) { + initializeQueueDragAndDrop(); + } + } + } + }); + + const targetNode = document.querySelector('gradio-app'); + if (targetNode) { + observer.observe(targetNode, { childList: true, subtree: true }); + } + + setTimeout(initializeQueueDragAndDrop, 500); + // cancel wheel usage inside image editor const hit = n => n?.id === "img_editor" || n?.classList?.contains("wheel-pass"); addEventListener("wheel", e => {
Drag Qty Prompt Length Steps Start/Ref End
{item.get('repeats', "1")} {prompt_cell} {length} {num_steps} {start_img_md} {end_img_md}{up_btn}{down_btn} {edit_btn} {remove_btn}