Skip to content

Commit

Permalink
Improve snapping behavior and add undo in EditableTemplate (#6687)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Apr 8, 2024
1 parent 4a2cfc1 commit 94b8f51
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 12 deletions.
Binary file added doc/_static/images/builder_undo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions doc/how_to/notebook/layout_builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ You might want to reorder the cards for a more logical flow. To do this, drag th

![Builder Rearrange View](../../_static/images/builder_rearrange.png)

### Undo

You might want to undo previous remove, resize and reorder actions:

![Builder Rearrange View](../../_static/images/builder_undo.png)

### Resetting Layout

If you're not satisfied with the changes, you can start over by clicking the reset button at the top right.
Expand Down
12 changes: 8 additions & 4 deletions examples/reference/templates/EditableTemplate.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,19 @@
"\n",
"<img src=\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48cGF0aCBmaWxsPSJub25lIiBkPSJNMCAwaDI0djI0SDB6Ii8+PHBhdGggZD0iTTE2IDEzbDYuOTY0IDQuMDYyLTIuOTczLjg1IDIuMTI1IDMuNjgxLTEuNzMyIDEtMi4xMjUtMy42OC0yLjIyMyAyLjE1TDE2IDEzem0tMi03aDJ2Mmg1YTEgMSAwIDAgMSAxIDF2NGgtMnYtM0gxMHYxMGg0djJIOWExIDEgMCAwIDEtMS0xdi01SDZ2LTJoMlY5YTEgMSAwIDAgMSAxLTFoNVY2ek00IDE0djJIMnYtMmgyem0wLTR2Mkgydi0yaDJ6bTAtNHYySDJWNmgyem0wLTR2MkgyVjJoMnptNCAwdjJINlYyaDJ6bTQgMHYyaC0yVjJoMnptNCAwdjJoLTJWMmgyeiIvPjwvc3ZnPg==\"></img>\n",
"\n",
"In edit mode we additionally have reset and save icons in the header:\n",
"In edit mode we additionally have undo, reset and save icons in the header:\n",
"\n",
"- Save: Persists the current layout into the browser storage overriding the layout persisted on the server.\n",
"- Undo: Undo previous delete, resize and re-arrange actions.\n",
"\n",
"<img src=\"data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMDAwMDAwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0cHgiIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDQwNy4wOTYgNDA3LjA5NiIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8Zz4NCiAgPGc+DQogICAgPHBhdGggZD0iTTQwMi4xMTUsODQuMDA4TDMyMy4wODgsNC45ODFDMzE5Ljg5OSwxLjc5MiwzMTUuNTc0LDAsMzExLjA2MywwSDE3LjAwNUM3LjYxMywwLDAsNy42MTQsMCwxNy4wMDV2MzczLjA4Ng0KCSAgICAgYzAsOS4zOTIsNy42MTMsMTcuMDA1LDE3LjAwNSwxNy4wMDVoMzczLjA4NmM5LjM5MiwwLDE3LjAwNS03LjYxMywxNy4wMDUtMTcuMDA1Vjk2LjAzMg0KCSAgICAgQzQwNy4wOTYsOTEuNTIzLDQwNS4zMDUsODcuMTk3LDQwMi4xMTUsODQuMDA4eiBNMzAwLjY2NCwxNjMuNTY3SDY3LjEyOVYzOC44NjJoMjMzLjUzNVYxNjMuNTY3eiIvPg0KICAgIDxwYXRoIGQ9Ik0yMTQuMDUxLDE0OC4xNmg0My4wOGMzLjEzMSwwLDUuNjY4LTIuNTM4LDUuNjY4LTUuNjY5VjU5LjU4NGMwLTMuMTMtMi41MzctNS42NjgtNS42NjgtNS42NjhoLTQzLjA4DQoJICAgICBjLTMuMTMxLDAtNS42NjgsMi41MzgtNS42NjgsNS42Njh2ODIuOTA3QzIwOC4zODMsMTQ1LjYyMiwyMTAuOTIsMTQ4LjE2LDIxNC4wNTEsMTQ4LjE2eiIvPg0KICA8L2c+DQo8L2c+DQo8L3N2Zz4NCg==\"></img>\n",
"<img src=\"data:image/svg+xml;base64,PHN2ZyAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgd2lkdGg9IjI0IiAgaGVpZ2h0PSIyNCIgIHZpZXdCb3g9IjAgMCAyNCAyNCIgIGZpbGw9Im5vbmUiICBzdHJva2U9ImN1cnJlbnRDb2xvciIgIHN0cm9rZS13aWR0aD0iMiIgIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgIHN0cm9rZS1saW5lam9pbj0icm91bmQiICBjbGFzcz0iaWNvbiBpY29uLXRhYmxlciBpY29ucy10YWJsZXItb3V0bGluZSBpY29uLXRhYmxlci1hcnJvdy1iYWNrLXVwIj48cGF0aCBzdHJva2U9Im5vbmUiIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNOSAxNGwtNCAtNGw0IC00IiAvPjxwYXRoIGQ9Ik01IDEwaDExYTQgNCAwIDEgMSAwIDhoLTEiIC8+PC9zdmc+\" width=\"24\" height=\"24\"></img>\n",
"\n",
"- Reset: Resets the arrangement and order of the individual components to the default.\n",
"\n",
"<img src=\"data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMDAwMDAwIiB3aWR0aD0iMjRweCIgaGVpZ2h0PSIyNHB4IiB2aWV3Qm94PSIwIDAgMTkyMCAxOTIwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPg0KICAgIDxwYXRoIGQ9Ik05NjAgMHYyMTMuMzMzYzQxMS42MjcgMCA3NDYuNjY3IDMzNC45MzQgNzQ2LjY2NyA3NDYuNjY3UzEzNzEuNjI3IDE3MDYuNjY3IDk2MCAxNzA2LjY2NyAyMTMuMzMzIDEzNzEuNzMzIDIxMy4zMzMgOTYwYzAtMTk3LjAxMyA3OC40LTM4Mi41MDcgMjEzLjMzNC01MjAuNzQ3djI1NC4wOEg2NDBWMTA2LjY2N0g1My4zMzNWMzIwaDE5MS4wNEM4OC42NCA0OTQuMDggMCA3MjAuOTYgMCA5NjBjMCA1MjkuMjggNDMwLjYxMyA5NjAgOTYwIDk2MHM5NjAtNDMwLjcyIDk2MC05NjBTMTQ4OS4zODcgMCA5NjAgMCIgZmlsbC1ydWxlPSJldmVub2RkIi8+DQo8L3N2Zz4NCg==\" width=\"24\" height=\"24\"></img>"
"<img src=\"data:image/svg+xml;base64,PHN2ZyAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgd2lkdGg9IjI0IiAgaGVpZ2h0PSIyNCIgIHZpZXdCb3g9IjAgMCAyNCAyNCIgIGZpbGw9Im5vbmUiICBzdHJva2U9ImN1cnJlbnRDb2xvciIgIHN0cm9rZS13aWR0aD0iMiIgIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgIHN0cm9rZS1saW5lam9pbj0icm91bmQiICBjbGFzcz0iaWNvbiBpY29uLXRhYmxlciBpY29ucy10YWJsZXItb3V0bGluZSBpY29uLXRhYmxlci1yZXN0b3JlIj48cGF0aCBzdHJva2U9Im5vbmUiIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMy4wNiAxM2E5IDkgMCAxIDAgLjQ5IC00LjA4NyIgLz48cGF0aCBkPSJNMyA0LjAwMXY1aDUiIC8+PHBhdGggZD0iTTEyIDEybS0xIDBhMSAxIDAgMSAwIDIgMGExIDEgMCAxIDAgLTIgMCIgLz48L3N2Zz4=\" width=\"24\" height=\"24\"></img>\n",
"\n",
"- Save: Persists the current layout into the browser storage overriding the layout persisted on the server.\n",
"\n",
"<img src=\"data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMDAwMDAwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0cHgiIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDQwNy4wOTYgNDA3LjA5NiIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8Zz4NCiAgPGc+DQogICAgPHBhdGggZD0iTTQwMi4xMTUsODQuMDA4TDMyMy4wODgsNC45ODFDMzE5Ljg5OSwxLjc5MiwzMTUuNTc0LDAsMzExLjA2MywwSDE3LjAwNUM3LjYxMywwLDAsNy42MTQsMCwxNy4wMDV2MzczLjA4Ng0KCSAgICAgYzAsOS4zOTIsNy42MTMsMTcuMDA1LDE3LjAwNSwxNy4wMDVoMzczLjA4NmM5LjM5MiwwLDE3LjAwNS03LjYxMywxNy4wMDUtMTcuMDA1Vjk2LjAzMg0KCSAgICAgQzQwNy4wOTYsOTEuNTIzLDQwNS4zMDUsODcuMTk3LDQwMi4xMTUsODQuMDA4eiBNMzAwLjY2NCwxNjMuNTY3SDY3LjEyOVYzOC44NjJoMjMzLjUzNVYxNjMuNTY3eiIvPg0KICAgIDxwYXRoIGQ9Ik0yMTQuMDUxLDE0OC4xNmg0My4wOGMzLjEzMSwwLDUuNjY4LTIuNTM4LDUuNjY4LTUuNjY5VjU5LjU4NGMwLTMuMTMtMi41MzctNS42NjgtNS42NjgtNS42NjhoLTQzLjA4DQoJICAgICBjLTMuMTMxLDAtNS42NjgsMi41MzgtNS42NjgsNS42Njh2ODIuOTA3QzIwOC4zODMsMTQ1LjYyMiwyMTAuOTIsMTQ4LjE2LDIxNC4wNTEsMTQ4LjE2eiIvPg0KICA8L2c+DQo8L2c+DQo8L3N2Zz4NCg==\"></img>\n"
]
}
],
Expand Down
2 changes: 1 addition & 1 deletion panel/io/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ def _render_template(self, doc, path):
reset = 'reset' in state.session_args
if not (editable or persist):
state.template.editable = False
state.template.title = os.path.splitext(os.path.basename(path))[0].title()
state.template.title = os.path.splitext(os.path.basename(path))[0].replace('_', ' ').title()

layouts, outputs, cells = {}, {}, {}
for cell_id, objects in state._cell_outputs.items():
Expand Down
15 changes: 11 additions & 4 deletions panel/template/editable/editable.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
background-color: currentColor;
cursor: pointer;
mask-size: cover;
height: 18px;
width: 18px;
height: 20px;
width: 20px;
margin-left: 10px;
-webkit-mask-size: cover;
}
Expand All @@ -22,8 +22,15 @@
}

#grid-reset {
mask-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjZmZmZmZmIiB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAxOTIwIDE5MjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+DQogICAgPHBhdGggZD0iTTk2MCAwdjIxMy4zMzNjNDExLjYyNyAwIDc0Ni42NjcgMzM0LjkzNCA3NDYuNjY3IDc0Ni42NjdTMTM3MS42MjcgMTcwNi42NjcgOTYwIDE3MDYuNjY3IDIxMy4zMzMgMTM3MS43MzMgMjEzLjMzMyA5NjBjMC0xOTcuMDEzIDc4LjQtMzgyLjUwNyAyMTMuMzM0LTUyMC43NDd2MjU0LjA4SDY0MFYxMDYuNjY3SDUzLjMzM1YzMjBoMTkxLjA0Qzg4LjY0IDQ5NC4wOCAwIDcyMC45NiAwIDk2MGMwIDUyOS4yOCA0MzAuNjEzIDk2MCA5NjAgOTYwczk2MC00MzAuNzIgOTYwLTk2MFMxNDg5LjM4NyAwIDk2MCAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz4NCjwvc3ZnPg0K');
-webkit-mask-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjZmZmZmZmIiB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAxOTIwIDE5MjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+DQogICAgPHBhdGggZD0iTTk2MCAwdjIxMy4zMzNjNDExLjYyNyAwIDc0Ni42NjcgMzM0LjkzNCA3NDYuNjY3IDc0Ni42NjdTMTM3MS42MjcgMTcwNi42NjcgOTYwIDE3MDYuNjY3IDIxMy4zMzMgMTM3MS43MzMgMjEzLjMzMyA5NjBjMC0xOTcuMDEzIDc4LjQtMzgyLjUwNyAyMTMuMzM0LTUyMC43NDd2MjU0LjA4SDY0MFYxMDYuNjY3SDUzLjMzM1YzMjBoMTkxLjA0Qzg4LjY0IDQ5NC4wOCAwIDcyMC45NiAwIDk2MGMwIDUyOS4yOCA0MzAuNjEzIDk2MCA5NjAgOTYwczk2MC00MzAuNzIgOTYwLTk2MFMxNDg5LjM4NyAwIDk2MCAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz4NCjwvc3ZnPg0K');
mask-image: url('data:image/svg+xml;base64,PHN2ZyAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgd2lkdGg9IjI0IiAgaGVpZ2h0PSIyNCIgIHZpZXdCb3g9IjAgMCAyNCAyNCIgIGZpbGw9Im5vbmUiICBzdHJva2U9ImN1cnJlbnRDb2xvciIgIHN0cm9rZS13aWR0aD0iMiIgIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgIHN0cm9rZS1saW5lam9pbj0icm91bmQiICBjbGFzcz0iaWNvbiBpY29uLXRhYmxlciBpY29ucy10YWJsZXItb3V0bGluZSBpY29uLXRhYmxlci1yZXN0b3JlIj48cGF0aCBzdHJva2U9Im5vbmUiIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMy4wNiAxM2E5IDkgMCAxIDAgLjQ5IC00LjA4NyIgLz48cGF0aCBkPSJNMyA0LjAwMXY1aDUiIC8+PHBhdGggZD0iTTEyIDEybS0xIDBhMSAxIDAgMSAwIDIgMGExIDEgMCAxIDAgLTIgMCIgLz48L3N2Zz4=');
-webkit-mask-image: url('data:image/svg+xml;base64,PHN2ZyAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgd2lkdGg9IjI0IiAgaGVpZ2h0PSIyNCIgIHZpZXdCb3g9IjAgMCAyNCAyNCIgIGZpbGw9Im5vbmUiICBzdHJva2U9ImN1cnJlbnRDb2xvciIgIHN0cm9rZS13aWR0aD0iMiIgIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgIHN0cm9rZS1saW5lam9pbj0icm91bmQiICBjbGFzcz0iaWNvbiBpY29uLXRhYmxlciBpY29ucy10YWJsZXItb3V0bGluZSBpY29uLXRhYmxlci1yZXN0b3JlIj48cGF0aCBzdHJva2U9Im5vbmUiIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMy4wNiAxM2E5IDkgMCAxIDAgLjQ5IC00LjA4NyIgLz48cGF0aCBkPSJNMyA0LjAwMXY1aDUiIC8+PHBhdGggZD0iTTEyIDEybS0xIDBhMSAxIDAgMSAwIDIgMGExIDEgMCAxIDAgLTIgMCIgLz48L3N2Zz4=');
}

#grid-undo {
mask-image: url('data:image/svg+xml;base64,PHN2ZyAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgd2lkdGg9IjI0IiAgaGVpZ2h0PSIyNCIgIHZpZXdCb3g9IjAgMCAyNCAyNCIgIGZpbGw9Im5vbmUiICBzdHJva2U9ImN1cnJlbnRDb2xvciIgIHN0cm9rZS13aWR0aD0iMiIgIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgIHN0cm9rZS1saW5lam9pbj0icm91bmQiICBjbGFzcz0iaWNvbiBpY29uLXRhYmxlciBpY29ucy10YWJsZXItb3V0bGluZSBpY29uLXRhYmxlci1hcnJvdy1iYWNrLXVwIj48cGF0aCBzdHJva2U9Im5vbmUiIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNOSAxNGwtNCAtNGw0IC00IiAvPjxwYXRoIGQ9Ik01IDEwaDExYTQgNCAwIDEgMSAwIDhoLTEiIC8+PC9zdmc+');
-webkit-mask-image: url('data:image/svg+xml;base64,PHN2ZyAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgd2lkdGg9IjI0IiAgaGVpZ2h0PSIyNCIgIHZpZXdCb3g9IjAgMCAyNCAyNCIgIGZpbGw9Im5vbmUiICBzdHJva2U9ImN1cnJlbnRDb2xvciIgIHN0cm9rZS13aWR0aD0iMiIgIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgIHN0cm9rZS1saW5lam9pbj0icm91bmQiICBjbGFzcz0iaWNvbiBpY29uLXRhYmxlciBpY29ucy10YWJsZXItb3V0bGluZSBpY29uLXRhYmxlci1hcnJvdy1iYWNrLXVwIj48cGF0aCBzdHJva2U9Im5vbmUiIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNOSAxNGwtNCAtNGw0IC00IiAvPjxwYXRoIGQ9Ik01IDEwaDExYTQgNCAwIDEgMSAwIDhoLTEiIC8+PC9zdmc+');
height: 25px;
width: 25px;
}

#grid-save {
Expand Down
67 changes: 64 additions & 3 deletions panel/template/editable/editable.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
{% block header_end %}
{% if editable %}
<div id="header-indicators">
<div id="grid-undo" class="header-icon disabled-button" title="Undo the last action"></div>
<div id="grid-reset" class="header-icon" title="Reset the Layout"></div>
{% if local_save %}
<div id="grid-save" class="header-icon disabled-button" title="Save the Layout to Local Storage"></div>
Expand Down Expand Up @@ -170,7 +171,21 @@
dragEnabled: {{ editable|json }},
dragHandle: '.muuri-handle.drag',
layout: {fillGaps: true}
});
}).on('dragInit', function () {
undo_stack.push({action: "drag", items: grid.getItems()})
}).on('dragEnd', function () {
const last_event = undo_stack.pop()
if (last_event.action == "move") {
undo_stack.push(last_event)
}
}).on('move', function () {
const last_event = undo_stack.pop()
if (last_event.action === "drag") {
const event = {...last_event, action: "move"}
undo_stack.push(event)
document.getElementById('grid-undo').classList.remove('disabled-button')
}
})
for (const spec of layout) {
const el = document.querySelector(`[data-id="${spec.id}"]`);
if (el && spec.width && spec.height) {
Expand All @@ -196,6 +211,28 @@

// Set up reset, hide and resize actions
{% if editable %}
const undo_stack = []
const undo_button = document.getElementById("grid-undo")
undo_button.addEventListener("click", function() {
const action = undo_stack.pop()
if (!action) {
return
}
if (undo_stack.length === 0) {
undo_button.classList.add('disabled-button')
}
switch(action.action) {
case "hide":
grid.show([action.item], {instant: true});
case "resize":
resize_item(action.item.getElement(), action.width, action.height, false);
grid.refreshItems()
grid.layout()
case "move":
grid.sort(action.items)
}
})

const reset_button = document.getElementById("grid-reset")
reset_button.addEventListener("click", function() {
if (window.localStorage) {
Expand All @@ -209,7 +246,10 @@

for (const handle of document.querySelectorAll('.muuri-handle.delete')) {
handle.addEventListener("click", (event) => {
grid.hide([grid.getItem(event.target.parentElement)], {instant: true});
const item = grid.getItem(event.target.parentElement);
grid.hide([item], {instant: true});
undo_stack.push({action: "hide", item: item})
undo_button.classList.remove('disabled-button')
{% if local_save %}
document.getElementById('grid-save').classList.remove('disabled-button')
{% endif %}
Expand Down Expand Up @@ -243,6 +283,24 @@
// resize from all edges and corners
edges: { right: '.muuri-handle.resize', bottom: '.muuri-handle.resize' },
listeners: {
start (event) {
const el = event.target
const item = grid.getItem(el);
let height = el.style.height.slice(null, -2);
if (!height) {
const {top} = item.getMargin();
height = item.getHeight()-top;
} else {
height = parseFloat(height);
}
let width;
if (el.style.width.length) {
width = parseFloat(el.style.width.split('(')[1].split('%')[0]);
} else {
width = 100;
}
undo_stack.push({action: "resize", item, width, height})
},
move (event) {
const item = grid.getItem(event.target);
const {top, bottom} = item.getMargin();
Expand All @@ -253,12 +311,14 @@
resize_item(event.target, width, height, false);
grid.refreshItems()
grid.layout()
window.dispatchEvent(new Event('resize'));
},
end (event) {
event.target.style.removeProperty('z-index')
grid.refreshItems()
grid.layout()
window.dispatchEvent(new Event('resize'));
undo_button.classList.remove('disabled-button')
{% if local_save %}
save_button.classList.remove('disabled-button')
{% endif %}
Expand All @@ -276,13 +336,14 @@
const target = {range: 25}
const grid_bbox = grid.getElement().getBoundingClientRect()
for (const item of grid.getItems()) {
if (item === interaction.element) {
if ((item.getElement() === interaction.element) && item.isVisible()) {
continue
}
const {top, left} = item.getPosition();
const margin = item.getMargin()
const bottom = (top + item.getHeight()) + margin.top + margin.bottom;
const right = (left + item.getWidth()) + margin.left + margin.right;

if (Math.abs(right - x) < target.range) {
target.x = right + margin.right
}
Expand Down
57 changes: 57 additions & 0 deletions panel/tests/ui/template/test_editabletemplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,24 @@ def test_editable_template_delete_item(page):
wait_until(lambda: tmpl.layout.get(id(md2), {}).get('visible') == False, page)


def test_editable_template_undo_delete_item(page):
tmpl = EditableTemplate()
md1 = Markdown('1')
md2 = Markdown('2')

tmpl.main[:] = [md1, md2]

serve_component(page, tmpl)

page.locator(".muuri-handle.delete").nth(1).click()

wait_until(lambda: tmpl.layout.get(id(md2), {}).get('visible') == False, page)

page.locator('#grid-undo').click()

wait_until(lambda: tmpl.layout.get(id(md2), {}).get('visible') == True, page)


def test_editable_template_drag_item(page):
tmpl = EditableTemplate()
md1 = Markdown('1')
Expand All @@ -160,6 +178,24 @@ def test_editable_template_drag_item(page):

wait_until(lambda: list(tmpl.layout) == [id(md2), id(md1)], page)

def test_editable_template_undo_drag_item(page):
tmpl = EditableTemplate()
md1 = Markdown('1')
md2 = Markdown('2')

tmpl.main[:] = [md1, md2]

serve_component(page, tmpl)

md2_handle = page.locator(".muuri-handle.drag").nth(1)

md2_handle.drag_to(md2_handle, target_position={'x': 0, 'y': -50}, force=True)

wait_until(lambda: list(tmpl.layout) == [id(md2), id(md1)], page)

page.locator('#grid-undo').click()

wait_until(lambda: list(tmpl.layout) == [id(md1), id(md2)], page)

def test_editable_template_resize_item(page):
md1 = Markdown('1')
Expand All @@ -177,3 +213,24 @@ def test_editable_template_resize_item(page):
md2_handle.drag_to(md2_handle, target_position={'x': -50, 'y': -30}, force=True)

wait_until(lambda: tmpl.layout.get(id(md2), {}).get('width') < 45, page)

def test_editable_template_undo_resize_item(page):
md1 = Markdown('1')
md2 = Markdown('2')

tmpl = EditableTemplate(layout={id(md2): {'width': 50, 'height': 80}})

tmpl.main[:] = [md1, md2]

serve_component(page, tmpl)

md2_handle = page.locator(".muuri-handle.resize").nth(1)

md2_handle.hover()
md2_handle.drag_to(md2_handle, target_position={'x': -50, 'y': -30}, force=True)

wait_until(lambda: tmpl.layout.get(id(md2), {}).get('width') < 45, page)

page.locator('#grid-undo').click()

wait_until(lambda: tmpl.layout.get(id(md2), {}).get('width') == 50, page)

0 comments on commit 94b8f51

Please sign in to comment.