From 175ccf6eaee19da399454faabea193aad870904b Mon Sep 17 00:00:00 2001 From: cryptogopher Date: Fri, 5 Apr 2024 02:18:05 +0200 Subject: [PATCH] Allow Unit rebase to top-level Outline base Unit row during drag Closes #25 --- app/assets/stylesheets/application.css | 11 ++++++ app/views/units/_unit.html.erb | 9 +++-- app/views/units/index.html.erb | 49 ++++++++++++++++++++++++-- config/locales/en.yml | 1 + 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index a3e1ff9..673149d 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -376,6 +376,17 @@ table.items td.actions { gap: 0.4em; justify-content: end; } +table.items tr.dropzone { + position: relative; +} +table.items tr.dropzone::after { + content: ''; + inset: 1px 0 0 0; + position: absolute; + outline: dashed 2px #009ade; + outline-offset: -1px; + z-index: var(--z-index-table-row-outline); +} table.items td.handle { cursor: move; } diff --git a/app/views/units/_unit.html.erb b/app/views/units/_unit.html.erb index 5ac00eb..f970250 100644 --- a/app/views/units/_unit.html.erb +++ b/app/views/units/_unit.html.erb @@ -1,5 +1,8 @@ -<%= tag.tr id: dom_id(unit), ondragover: "dragOver(event)", ondrop: "drop(event)", - data: {drag_path: unit_path(unit), drop_id: unit.base_id || unit.id} do %> +<%= tag.tr id: dom_id(unit), + ondragstart: 'dragStart(event)', ondragend: 'dragEnd(event)', + ondragover: 'dragOver(event)', ondrop: 'drop(event)', + ondragenter: 'dragEnter(event)', ondragleave: 'dragLeave(event)', + data: {drag_path: rebase_unit_path(unit), drop_id: dom_id(unit.base || unit)} do %> <%= link_to unit.symbol, edit_unit_path(unit), id: dom_id(unit, :edit), @@ -20,7 +23,7 @@ method: :delete %> <% if unit.movable? %> - ⠿ + ⠿ <% else %> <% end %> diff --git a/app/views/units/index.html.erb b/app/views/units/index.html.erb index 774d42c..8ad076d 100644 --- a/app/views/units/index.html.erb +++ b/app/views/units/index.html.erb @@ -18,6 +18,12 @@ <% end %> + <%= tag.tr id: 'unit_', hidden: true, + ondragover: 'dragOver(event)', ondrop: 'drop(event)', + ondragenter: 'dragEnter(event)', ondragleave: 'dragLeave(event)', + data: {drop_id: 'unit_'} do %> + <%= t '.top_level_drop' %> + <% end %> <%= render(@units) || render_no_items %> @@ -31,17 +37,54 @@ } } + var lastEnterTime; function dragStart(event) { - var row = event.target.closest("tr"); + lastEnterTime = event.timeStamp; + var row = event.currentTarget; + row.closest("table").querySelectorAll("thead tr").forEach((tr) => { + tr.toggleAttribute("hidden"); + }); event.dataTransfer.setData("text/plain", row.getAttribute("data-drag-path")); var rowRectangle = row.getBoundingClientRect(); event.dataTransfer.setDragImage(row, event.x - rowRectangle.left, event.y - rowRectangle.top); - event.dataTransfer.dropEffect = "none"; + event.dataTransfer.dropEffect = "move"; + } + + /* + * Drag tracking assumptions (based on FF 122.0 experience): + * * Enter/Leave events at the same timeStamp may not be logically ordered + * (e.g. E -> E -> L, not E -> L -> E), + * * not every Enter event has corresponding Leave event, especially during + * rapid pointer moves + * NOTE: sometimes Leave is not emitted when pointer goes fast over table + * and outside. This should probably be fixed in browser, than patched here. + */ + function dragEnter(event) { + //console.log(event.timeStamp + " " + event.type + ": " + event.currentTarget.id); + dragLeave(event); + lastEnterTime = event.timeStamp; + const id = event.currentTarget.getAttribute("data-drop-id"); + document.getElementById(id).classList.add("dropzone"); } function dragOver(event) { event.preventDefault(); - event.dataTransfer.dropEffect = "move"; + } + + function dragLeave(event) { + //console.log(event.timeStamp + " " + event.type + ": " + event.currentTarget.id); + // Leave has been accounted for by Enter at the same timestamp, processed earlier + if (event.timeStamp <= lastEnterTime) return; + event.currentTarget.closest("table").querySelectorAll(".dropzone").forEach((tr) => { + tr.classList.remove("dropzone"); + }) + } + + function dragEnd(event) { + dragLeave(event); + event.currentTarget.closest("table").querySelectorAll("thead tr").forEach((tr) => { + tr.toggleAttribute("hidden"); + }); } function drop(event) { diff --git a/config/locales/en.yml b/config/locales/en.yml index 34343d0..3123008 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -53,6 +53,7 @@ en: index: add_unit: Add unit no_items: There are no configured units. You can try to import some defaults. + top_level_drop: Drop here to reposition into top-level unit new: none: none create: