From 18e26a3b68ad1a855f6d2312be8efd450a54c62c Mon Sep 17 00:00:00 2001 From: Peter Knut Date: Fri, 18 Oct 2024 16:27:38 +0200 Subject: [PATCH] Add drag-n-drop moving of rows in table editing --- adminer/include/adminer.inc.php | 4 +- adminer/include/editing.inc.php | 23 ++++++++--- adminer/include/functions.inc.php | 10 ++--- adminer/static/default.css | 15 ++++--- adminer/static/editing.js | 23 ++--------- adminer/static/functions.js | 68 ++++++++++++++++++++++++------- 6 files changed, 91 insertions(+), 52 deletions(-) diff --git a/adminer/include/adminer.inc.php b/adminer/include/adminer.inc.php index 1f4331fd..9149c0bd 100644 --- a/adminer/include/adminer.inc.php +++ b/adminer/include/adminer.inc.php @@ -418,7 +418,7 @@ class Adminer { ); echo "
", - "="; + ""; if ($functions || $grouping) { echo "", diff --git a/adminer/include/editing.inc.php b/adminer/include/editing.inc.php index 687d6f98..e5d8fb54 100644 --- a/adminer/include/editing.inc.php +++ b/adminer/include/editing.inc.php @@ -288,7 +288,14 @@ function edit_fields(array $fields, array $collations, $type = "TABLE", $foreign ?> - + "; + } + if ($type == "PROCEDURE") { + echo ""; + } + ?> @@ -308,9 +315,9 @@ function edit_fields(array $fields, array $collations, $type = "TABLE", $foreign " . script("row_count = " . count($fields) . ";"); ?> - \n"; + foreach ($fields as $i => $field) { $i++; $orig = $field[($_POST ? "orig" : "field")]; @@ -319,6 +326,9 @@ function edit_fields(array $fields, array $collations, $type = "TABLE", $foreign $style = $display ? "" : "style='display: none;'"; echo "\n"; + if (support("move_col")) { + echo ""; + } if ($type == "PROCEDURE") { echo "", html_select("fields[$i][inout]", explode("|", $inout), $field["inout"]), "\n"; } @@ -353,14 +363,17 @@ function edit_fields(array $fields, array $collations, $type = "TABLE", $foreign echo ""; if (support("move_col")) { echo " ", - " ", - " "; + " ", + " "; } if ($orig == "" || support("drop_col")) { echo ""; } echo "\n\n"; } + + echo ""; + echo script("mixin(qs('#edit-fields tbody'), {onclick: editingClick, onkeydown: editingKeydown, oninput: editingInput}); initSortable('#edit-fields tbody');"); } /** Move fields up and down or add field diff --git a/adminer/include/functions.inc.php b/adminer/include/functions.inc.php index 7e183d6b..c6248716 100644 --- a/adminer/include/functions.inc.php +++ b/adminer/include/functions.inc.php @@ -250,11 +250,11 @@ function confirm($message = "", $selector = "qsl('input')") { return script("$selector.onclick = function () { return confirm('" . ($message ? js_escape($message) : lang('Are you sure?')) . "'); };", ""); } -/** Print header for hidden fieldset (close by
) -* @param string -* @param string -* @param bool -*/ +/** + * Prints header for hidden fieldset (close by ) + * @param $id string + * @param $legend string + */ function print_fieldset($id, $legend, $visible = false, $sortable = false) { echo "
"; echo "$legend"; diff --git a/adminer/static/default.css b/adminer/static/default.css index 41c081a8..ecd994d6 100644 --- a/adminer/static/default.css +++ b/adminer/static/default.css @@ -10,7 +10,7 @@ h3 { font-weight: normal; font-size: 130%; margin: 1em 0 0; } form { margin: 0; } td table { width: 100%; margin: 0; } table { margin: 1em 20px 0 0; border-collapse: collapse; font-size: 90%; } -td, th { border: 1px solid #999; padding: .2em .3em; } +td, th { box-sizing: border-box; border: 1px solid #999; padding: .2em .3em; } th { background: #eee; text-align: left; } thead th { text-align: center; padding: .2em .5em; } thead td, thead th { background: #ddf; } /* position: sticky; causes Firefox to lose borders */ @@ -72,12 +72,15 @@ p.nowrap { white-space: pre; } .logout { margin-top: .5em; position: absolute; top: 0; right: 0; } .loadmore { margin-left: 1ex; } .tables-filter { padding: .8em 1em 0; } -.handle { display: inline-block; width: 18px; height: 18px; padding-right: 5px; vertical-align: middle; overflow: hidden; font-size: 130%; text-align: center; line-height: 16px; opacity: 0.2; cursor: grab; } -.sortable { position: relative; } -.sortable .no-sort .handle { opacity: 0; cursor: default; } +.handle { cursor: grab; vertical-align: middle; } +.handle:before { content: "="; display: inline-block; width: 18px; height: 18px; overflow: hidden; font-size: 130%; text-align: center; line-height: 16px; opacity: 0.2; } +span.handle { display: inline-block; width: 18px; height: 18px; padding-right: .3em; } +th.handle:before { vertical-align: middle; } +.no-sort .handle { cursor: default; opacity: 0; } .placeholder { opacity: 0; } -.dragging { position: absolute; z-index: 1; } -.dragging .handle { cursor: grabbing; } +.dragging { position: absolute; margin: 0; background: #fff; } +.dragging * { cursor: grabbing; } +table.dragging { background: #eee; } /* .edit used in designs */ #menu { position: absolute; margin: 10px 0 0; padding: 0 0 30px 0; top: 2em; left: 0; width: 19em; } #menu p, #logins, #tables { padding: .8em 1em; margin: 0; border-bottom: 1px solid #ccc; } diff --git a/adminer/static/editing.js b/adminer/static/editing.js index 54ae65c3..244458ee 100644 --- a/adminer/static/editing.js +++ b/adminer/static/editing.js @@ -253,10 +253,6 @@ function editingClick(event) { var name = el.name; if (/^add\[/.test(name)) { editingAddRow.call(el, 1); - } else if (/^up\[/.test(name)) { - editingMoveRow.call(el, 1); - } else if (/^down\[/.test(name)) { - editingMoveRow.call(el); } else if (/^drop_col\[/.test(name)) { editingRemoveRow.call(el, 'fields\$1[field]'); } else { @@ -353,7 +349,10 @@ function editingAddRow(focus) { } } tags[0].oninput = editingNameChange; + + initSortableRow(row2); row.parentNode.insertBefore(row2, row.nextSibling); + if (focus) { input.oninput = editingNameChange; input.focus(); @@ -375,22 +374,6 @@ function editingRemoveRow(name) { return false; } -/** Move table row for field -* @param [boolean] -* @return boolean false for success -* @this HTMLInputElement -*/ -function editingMoveRow(up){ - var row = parentTag(this, 'tr'); - if (!('nextElementSibling' in row)) { - return true; - } - row.parentNode.insertBefore(row, up - ? row.previousElementSibling - : row.nextElementSibling ? row.nextElementSibling.nextElementSibling : row.parentNode.firstChild); - return false; -} - var lastType = ''; /** Clear length and hide collation or unsigned diff --git a/adminer/static/functions.js b/adminer/static/functions.js index 41ae672d..82a544a0 100644 --- a/adminer/static/functions.js +++ b/adminer/static/functions.js @@ -544,8 +544,8 @@ function selectSearchSearch() { // Sorting. (function() { - let placeholderRow, draggingRow, nextRow; - let startY, maxY; + let placeholderRow, nextRow, dragHelper; + let startY, minY, maxY; /** * Initializes sortable list of DIV elements. @@ -576,27 +576,52 @@ function selectSearchSearch() { event.preventDefault(); const parent = row.parentNode; - startY = event.clientY - row.offsetTop; - maxY = parent.offsetHeight - row.offsetHeight; + startY = event.clientY - getOffsetTop(row); + minY = getOffsetTop(parent); + maxY = minY + parent.offsetHeight - row.offsetHeight; placeholderRow = row.cloneNode(true); placeholderRow.classList.add("placeholder"); - placeholderRow.style.top = (event.clientY - startY) + "px"; parent.insertBefore(placeholderRow, row); - draggingRow = row; - draggingRow.classList.add("dragging"); - parent.insertBefore(draggingRow, parent.firstChild); + nextRow = row.nextElementSibling; - nextRow = placeholderRow.nextElementSibling; + let top = event.clientY - startY; + let left = getOffsetLeft(row); + let width = row.getBoundingClientRect().width; - updateSorting(event); + if (row.tagName === "TR") { + const firstChild = row.firstElementChild; + const borderWidth = (firstChild.offsetWidth - firstChild.clientWidth) / 2; + const borderHeight = (firstChild.offsetHeight - firstChild.clientHeight) / 2; + + minY -= borderHeight; + maxY -= borderHeight; + top -= borderHeight; + left -= borderWidth; + width += 2 * borderWidth; + + for (const child of row.children) { + child.style.width = child.getBoundingClientRect().width + "px"; + } + + dragHelper = document.createElement("table"); + dragHelper.appendChild(row); + } else { + dragHelper = row; + } + + dragHelper.style.top = `${top}px`; + dragHelper.style.left = `${left}px`; + dragHelper.style.width = `${width}px`; + dragHelper.classList.add("dragging"); + document.body.appendChild(dragHelper); window.addEventListener("mousemove", updateSorting); window.addEventListener("mouseup", () => { - draggingRow.classList.remove("dragging"); - parent.insertBefore(draggingRow, placeholderRow); + dragHelper.classList.remove("dragging"); + parent.insertBefore(dragHelper.tagName === "TABLE" ? dragHelper.firstChild : dragHelper, placeholderRow); placeholderRow.remove(); window.removeEventListener("mousemove", updateSorting); @@ -605,8 +630,11 @@ function selectSearchSearch() { }; function updateSorting(event) { - let top = Math.min(Math.max(event.clientY - startY, 0), maxY); - draggingRow.style.top = top + "px"; + let top = Math.min(Math.max(event.clientY - startY, minY), maxY); + dragHelper.style.top = `${top}px`; + + const parent = placeholderRow.parentNode; + top = top - minY + parent.offsetTop; let sibling; if (top > placeholderRow.offsetTop + placeholderRow.offsetHeight / 2) { @@ -1149,6 +1177,18 @@ function cloneNode(el) { return el2; } +function getOffsetTop(element) { + let box = element.getBoundingClientRect(); + + return box.top + window.scrollY; +} + +function getOffsetLeft(element) { + let box = element.getBoundingClientRect(); + + return box.left + window.scrollX; +} + oninput = function (event) { var target = event.target; var maxLength = target.getAttribute('data-maxlength');