Skip to content

Commit e8e158e

Browse files
committed
fix(ui): Sidebar > drag-reorder > item pops out of folder when dropped in nested list's padding - fallback resolved to top-level list instead of the enclosing folder + socket drops on bottom half now insert after instead of before + block dropping a folder into its own or a descendant's nested list (it became its own parent and vanished from the tree)
1 parent 77c76cf commit e8e158e

7 files changed

Lines changed: 377 additions & 69 deletions

File tree

packages/ui/package-lock.json

Lines changed: 131 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"@vitejs/plugin-vue": "^5.1.4",
7373
"eslint": "^8.28.0",
7474
"eslint-plugin-vue": "^9.7.0",
75+
"happy-dom": "^20.9.0",
7576
"rollup-plugin-copy": "^3.4.0",
7677
"start-server-and-test": "2.0.4",
7778
"vite": "^5.4.9",

packages/ui/src/components/Sidebar.vue

Lines changed: 31 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import CollectionRunnerModal from './modals/CollectionRunnerModal.vue'
6464
import CollectionRunnerProgressModal from './modals/CollectionRunnerProgressModal.vue'
6565
import { mapState } from 'vuex'
6666
import { flattenTree, exportRestfoxCollection, generateNewIdsForTree, deepClone } from '@/helpers'
67+
import { resolveSidebarDropTarget } from '@/utils/sidebar-drop'
6768
import { generateCode } from '@/utils/generate-code'
6869
import AddGraphQLRequestModal from '@/components/modals/AddGraphQLRequestModal.vue'
6970
@@ -470,29 +471,6 @@ export default {
470471
this.enableOptionsForEmptyContextMenu = true
471472
this.showContextMenu = true
472473
},
473-
getDropTargetElementContainer(event) {
474-
let container
475-
const sidebarItem = event.target.closest('.sidebar-item')
476-
const sidebarListContainer = event.target.closest('.sidebar-list-container')
477-
if (sidebarItem) {
478-
container = {
479-
type: 'sidebar-item',
480-
element: sidebarItem
481-
}
482-
} else if (sidebarListContainer) {
483-
const innerList = sidebarListContainer.querySelector('.sidebar-list')
484-
if (!innerList) {
485-
return
486-
}
487-
container = {
488-
type: 'sidebar-list',
489-
element: innerList
490-
}
491-
} else {
492-
return null
493-
}
494-
return container
495-
},
496474
dragStart(event) {
497475
if(this.collectionFilter) { // disable drag functionality if collection is being filtered
498476
return
@@ -517,12 +495,12 @@ export default {
517495
if(!this.draggedSidebarElement) {
518496
return
519497
}
520-
const container = this.getDropTargetElementContainer(event)
521-
if(!container) {
498+
const target = resolveSidebarDropTarget(event.target)
499+
if(!target) {
522500
return
523501
}
524502
525-
const elementToDropOn = container.element
503+
const { element: elementToDropOn } = target
526504
const rect = elementToDropOn.getBoundingClientRect()
527505
const offset = rect.top + document.body.scrollTop
528506
const elementHeight = parseFloat(getComputedStyle(elementToDropOn, null).height.replace('px', ''))
@@ -535,7 +513,7 @@ export default {
535513
elementToDropOn.style.backgroundColor = ''
536514
} else {
537515
this.sidebarItemCursorPosition = 'bottom'
538-
if(elementToDropOn.dataset.type === 'request_group') {
516+
if(target.type === 'request_group') {
539517
elementToDropOn.style.borderTop = ''
540518
elementToDropOn.style.borderBottom = ''
541519
elementToDropOn.style.backgroundColor = 'var(--drop-target-background-color)'
@@ -547,54 +525,49 @@ export default {
547525
}
548526
event.preventDefault()
549527
},
528+
clearDropTargetStyling(element) {
529+
element.style.borderTop = ''
530+
element.style.borderBottom = ''
531+
element.style.backgroundColor = ''
532+
},
550533
dragLeave(event) {
551534
if(!this.draggedSidebarElement) {
552535
return
553536
}
554-
const container = this.getDropTargetElementContainer(event)
555-
if(!container) {
537+
const target = resolveSidebarDropTarget(event.target)
538+
if(!target) {
556539
return
557540
}
558-
const elementToDropOn = container.element
559-
if(elementToDropOn) {
560-
elementToDropOn.style.borderBottom = ''
561-
elementToDropOn.style.borderTop = ''
562-
elementToDropOn.style.backgroundColor = ''
563-
}
541+
this.clearDropTargetStyling(target.element)
564542
},
565543
drop(event) {
566544
if(!this.draggedSidebarElement) {
567545
return
568546
}
569547
event.preventDefault()
570-
const container = this.getDropTargetElementContainer(event)
571-
if(!container) {
548+
const target = resolveSidebarDropTarget(event.target)
549+
if(!target) {
572550
return
573551
}
574552
575-
const elementToDropOn = container.element
576-
if(elementToDropOn) {
577-
elementToDropOn.style.borderTop = ''
578-
elementToDropOn.style.borderBottom = ''
579-
elementToDropOn.style.backgroundColor = ''
553+
this.clearDropTargetStyling(target.element)
580554
581-
this.$store.dispatch('reorderCollectionItem', {
582-
from: {
583-
parentId: this.draggedSidebarElement.dataset.parentId,
584-
id: this.draggedSidebarElement.dataset.id
585-
},
586-
to: {
587-
parentId: elementToDropOn.dataset.parentId,
588-
id: elementToDropOn.dataset.id,
589-
type: elementToDropOn.dataset.type ?? container.type,
590-
cursorPosition: this.sidebarItemCursorPosition
591-
}
592-
})
555+
this.$store.dispatch('reorderCollectionItem', {
556+
from: {
557+
parentId: this.draggedSidebarElement.dataset.parentId,
558+
id: this.draggedSidebarElement.dataset.id
559+
},
560+
to: {
561+
parentId: target.parentId,
562+
id: target.id,
563+
type: target.type,
564+
cursorPosition: this.sidebarItemCursorPosition
565+
}
566+
})
593567
594-
this.draggedSidebarElement.style.backgroundColor = ''
595-
this.draggedSidebarElement.style.opacity = ''
596-
this.draggedSidebarElement = null
597-
}
568+
this.draggedSidebarElement.style.backgroundColor = ''
569+
this.draggedSidebarElement.style.opacity = ''
570+
this.draggedSidebarElement = null
598571
},
599572
async updateCollectionItem(collectionItem) {
600573
const result = await this.$store.dispatch('updateCollectionItem', {

packages/ui/src/components/SidebarItem.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
:selected-option="currentEnvironment.name"
6666
@click="selectFolderEnv"
6767
/>
68-
<div class="sidebar-list" v-if="'children' in sidebarItem && sidebarItem.children.length && getSidebarItemExpandedState(sidebarItem)">
68+
<div class="sidebar-list" :data-parent-id="sidebarItem._id" v-if="'children' in sidebarItem && sidebarItem.children.length && getSidebarItemExpandedState(sidebarItem)">
6969
<template v-for="sidebarItem1 in sidebarItem.children" :key="sidebarItem1._id">
7070
<SidebarItem :sidebar-item="sidebarItem1" />
7171
</template>

packages/ui/src/store.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import {
6666
PluginTestResult,
6767
} from './global'
6868
import * as queryParamsSync from '@/utils/query-params-sync'
69+
import { computeTargetIndex, isDropIntoOwnSubtree } from '@/utils/sidebar-drop'
6970

7071
async function loadResponses(state: State, tabId: string) {
7172
if(tabId in state.responses) {
@@ -1086,6 +1087,11 @@ export const store = createStore<State>({
10861087
return
10871088
}
10881089

1090+
// don't allow an item to be dropped into its own nested list or a descendant's nested list
1091+
if(isDropIntoOwnSubtree(context.state.collectionTree, payload.from.id, payload.to.parentId)) {
1092+
return
1093+
}
1094+
10891095
let targetKey = 'parentId'
10901096
if(payload.to.type === 'request_group' && payload.to.cursorPosition === 'bottom') { // dropping an item into a folder, bottom = pink highlight
10911097
targetKey = 'id'
@@ -1117,16 +1123,11 @@ export const store = createStore<State>({
11171123
sourceParentCollection.splice(sourceIndex, 1)
11181124
sourceItem.parentId = payload.to[targetKey] ?? null
11191125

1120-
let targetIndex = targetParentCollection.findIndex(item => item._id === payload.to.id)
1121-
if(payload.to.type === 'request_group' && payload.to.cursorPosition === 'bottom') { // dropping an item into a folder, bottom = pink highlight
1122-
targetIndex = 0
1123-
}
1124-
if(payload.to.type === 'request' && payload.to.cursorPosition === 'bottom') {
1125-
targetIndex++
1126-
}
1127-
if(payload.to.type === 'sidebar-list' && payload.to.cursorPosition === 'bottom') {
1128-
targetIndex = targetParentCollection.length
1129-
}
1126+
const targetIndex = computeTargetIndex(targetParentCollection, {
1127+
id: payload.to.id,
1128+
type: payload.to.type,
1129+
cursorPosition: payload.to.cursorPosition,
1130+
})
11301131
targetParentCollection.splice(targetIndex, 0, sourceItem)
11311132

11321133
const collectionItemIndex = context.state.collection.findIndex(item => item._id === sourceItem._id)

0 commit comments

Comments
 (0)