Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve snapping behavior and add undo in EditableTemplate #6687

Merged
merged 6 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Loading