|
1 | 1 | <template> |
2 | 2 | <div> |
3 | | - <div :style="depthMargin"> |
| 3 | + <div |
| 4 | + class="draggable-item" |
| 5 | + :style="depthMargin" |
| 6 | + :class="{ 'drag-over': dragOverCount > 0 }" |
| 7 | + @dragstart="onDragStart" |
| 8 | + @dragenter.prevent="onDragEnter" |
| 9 | + @dragleave.prevent="onDragLeave" |
| 10 | + @dragover.prevent |
| 11 | + @drop.prevent="onDrop" |
| 12 | + > |
4 | 13 | <!-- Checkbox --> |
5 | 14 | <div v-if="isSelectMode" class="select-input-wrapper"> |
6 | 15 | <input |
@@ -116,6 +125,7 @@ export default { |
116 | 125 | data() { |
117 | 126 | return { |
118 | 127 | isCollapsed: true, |
| 128 | + dragOverCount: 0, |
119 | 129 | }; |
120 | 130 | }, |
121 | 131 | computed: { |
@@ -187,6 +197,91 @@ export default { |
187 | 197 |
|
188 | 198 | window.localStorage.setItem("monitorCollapsed", JSON.stringify(storageObject)); |
189 | 199 | }, |
| 200 | + /** |
| 201 | + * Initializes the drag operation if the monitor is draggable. |
| 202 | + * @param {DragEvent} event - The dragstart event triggered by the browser. |
| 203 | + * @returns {void} This method does not return anything. |
| 204 | + */ |
| 205 | + onDragStart(event) { |
| 206 | + try { |
| 207 | + event.dataTransfer.setData("text/monitor-id", String(this.monitor.id)); |
| 208 | + event.dataTransfer.effectAllowed = "move"; |
| 209 | + } catch (e) { |
| 210 | + // ignore |
| 211 | + } |
| 212 | + }, |
| 213 | +
|
| 214 | + onDragEnter(event) { |
| 215 | + if (this.monitor.type !== "group") { |
| 216 | + return; |
| 217 | + } |
| 218 | +
|
| 219 | + this.dragOverCount++; |
| 220 | + }, |
| 221 | +
|
| 222 | + onDragLeave(event) { |
| 223 | + if (this.monitor.type !== "group") { |
| 224 | + return; |
| 225 | + } |
| 226 | +
|
| 227 | + this.dragOverCount = Math.max(0, this.dragOverCount - 1); |
| 228 | + }, |
| 229 | +
|
| 230 | + async onDrop(event) { |
| 231 | + this.dragOverCount = 0; |
| 232 | +
|
| 233 | + // Only groups accept drops |
| 234 | + if (this.monitor.type !== "group") { |
| 235 | + return; |
| 236 | + } |
| 237 | +
|
| 238 | + const draggedId = event.dataTransfer.getData("text/monitor-id"); |
| 239 | + if (!draggedId) { |
| 240 | + return; |
| 241 | + } |
| 242 | +
|
| 243 | + const draggedMonitorId = parseInt(draggedId); |
| 244 | + if (isNaN(draggedMonitorId) || draggedMonitorId === this.monitor.id) { |
| 245 | + return; |
| 246 | + } |
| 247 | +
|
| 248 | + const draggedMonitor = this.$root.monitorList[draggedMonitorId]; |
| 249 | + if (!draggedMonitor) { |
| 250 | + return; |
| 251 | + } |
| 252 | +
|
| 253 | + // Save original parent so we can revert locally if server returns error |
| 254 | + const originalParent = draggedMonitor.parent; |
| 255 | +
|
| 256 | + // Prepare a full monitor object (clone) and set new parent |
| 257 | + const monitorToSave = JSON.parse(JSON.stringify(draggedMonitor)); |
| 258 | + monitorToSave.parent = this.monitor.id; |
| 259 | +
|
| 260 | + // Optimistically update local state so UI updates immediately |
| 261 | + this.$root.monitorList[draggedMonitorId].parent = this.monitor.id; |
| 262 | +
|
| 263 | + // Send updated monitor state via socket |
| 264 | + try { |
| 265 | + this.$root.getSocket().emit("editMonitor", monitorToSave, (res) => { |
| 266 | + if (!res || !res.ok) { |
| 267 | + // Revert local change on error |
| 268 | + if (this.$root.monitorList[draggedMonitorId]) { |
| 269 | + this.$root.monitorList[draggedMonitorId].parent = originalParent; |
| 270 | + } |
| 271 | + if (res && res.msg) { |
| 272 | + this.$root.toastError(res.msg); |
| 273 | + } |
| 274 | + } else { |
| 275 | + this.$root.toastRes(res); |
| 276 | + } |
| 277 | + }); |
| 278 | + } catch (e) { |
| 279 | + // revert on exception |
| 280 | + if (this.$root.monitorList[draggedMonitorId]) { |
| 281 | + this.$root.monitorList[draggedMonitorId].parent = originalParent; |
| 282 | + } |
| 283 | + } |
| 284 | + }, |
190 | 285 | /** |
191 | 286 | * Get URL of monitor |
192 | 287 | * @param {number} id ID of monitor |
@@ -253,4 +348,35 @@ export default { |
253 | 348 | z-index: 15; |
254 | 349 | } |
255 | 350 |
|
| 351 | +.drag-over { |
| 352 | + border: 4px dashed $primary; |
| 353 | + border-radius: 0.5rem; |
| 354 | + background-color: $highlight-white; |
| 355 | +} |
| 356 | +
|
| 357 | +.dark { |
| 358 | + .drag-over { |
| 359 | + background-color: $dark-bg2; |
| 360 | + } |
| 361 | +} |
| 362 | +
|
| 363 | +/* -4px on all due to border-width */ |
| 364 | +.monitor-list .drag-over .item { |
| 365 | + padding: 9px 11px 6px 11px; |
| 366 | +} |
| 367 | +
|
| 368 | +.draggable-item { |
| 369 | + cursor: grab; |
| 370 | + position: relative; |
| 371 | +
|
| 372 | + /* We don't want the padding change due to the border animated */ |
| 373 | + .item { |
| 374 | + transition: none !important; |
| 375 | + } |
| 376 | +
|
| 377 | + &.dragging { |
| 378 | + cursor: grabbing; |
| 379 | + } |
| 380 | +} |
| 381 | +
|
256 | 382 | </style> |
0 commit comments