chore: merge hoppscotch/staging into self-hosted/main

This commit is contained in:
Andrew Bastin
2023-04-05 16:37:44 +05:30
12 changed files with 470 additions and 167 deletions

View File

@@ -17,27 +17,13 @@
/> />
<!-- <AppGitHubStarButton class="mt-1.5 transition" /> --> <!-- <AppGitHubStarButton class="mt-1.5 transition" /> -->
</div> </div>
<div class="inline-flex items-center justify-center flex-1 space-x-2"> <div class="inline-flex items-center space-x-2">
<AppNavigation v-if="mdAndLarger" />
<div
class="bg-primaryDark max-w-128 text-secondaryLight justify-between cursor-pointer rounded border border-dividerDark hover:border-dividerDark hover:bg-primaryLight hover:text-secondary focus-visible:border-dividerDark focus-visible:bg-primaryLight focus-visible:text-secondary focus:outline-none transition flex flex-1 items-center px-2 py-1.25"
tabindex="0"
@click="invokeAction('modals.search.toggle')"
>
<span class="inline-flex">
<icon-lucide-search class="mr-2 svg-icons" />
{{ t("app.search") }}
</span>
<kbd class="shortcut-key">/</kbd>
</div>
<HoppButtonSecondary <HoppButtonSecondary
v-tippy="{ theme: 'tooltip', allowHTML: true }" v-tippy="{ theme: 'tooltip', allowHTML: true }"
:title="`${ :title="`${t('app.search')} <kbd>/</kbd>`"
mdAndLarger ? t('support.title') : t('app.options') :icon="IconSearch"
} <kbd>?</kbd>`"
:icon="IconHelpCircle"
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark" class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
@click="invokeAction('modals.support.toggle')" @click="invokeAction('modals.search.toggle')"
/> />
<HoppButtonSecondary <HoppButtonSecondary
v-if="showInstallButton" v-if="showInstallButton"
@@ -47,8 +33,15 @@
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark" class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
@click="installPWA()" @click="installPWA()"
/> />
</div> <HoppButtonSecondary
<div class="inline-flex items-center justify-end flex-1 space-x-2"> v-tippy="{ theme: 'tooltip', allowHTML: true }"
:title="`${
mdAndLarger ? t('support.title') : t('app.options')
} <kbd>?</kbd>`"
:icon="IconLifeBuoy"
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
@click="invokeAction('modals.support.toggle')"
/>
<div <div
v-if="currentUser === null" v-if="currentUser === null"
class="inline-flex items-center space-x-2" class="inline-flex items-center space-x-2"
@@ -242,7 +235,8 @@ import IconSettings from "~icons/lucide/settings"
import IconDownload from "~icons/lucide/download" import IconDownload from "~icons/lucide/download"
import IconUploadCloud from "~icons/lucide/upload-cloud" import IconUploadCloud from "~icons/lucide/upload-cloud"
import IconUserPlus from "~icons/lucide/user-plus" import IconUserPlus from "~icons/lucide/user-plus"
import IconHelpCircle from "~icons/lucide/help-circle" import IconLifeBuoy from "~icons/lucide/life-buoy"
import IconSearch from "~icons/lucide/search"
import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core" import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
import { pwaDefferedPrompt, installPWA } from "@modules/pwa" import { pwaDefferedPrompt, installPWA } from "@modules/pwa"
import { platform } from "~/platform" import { platform } from "~/platform"

View File

@@ -1,33 +0,0 @@
<template>
<div class="flex items-center justify-between">
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip', delay: [500, 20], allowHTML: true }"
:title="`${t(
'action.go_back'
)} <kbd>${getSpecialKey()}</kbd><kbd>←</kbd>`"
:icon="IconArrowLeft"
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
@click="router.go(-1)"
/>
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip', delay: [500, 20], allowHTML: true }"
:title="`${t(
'action.go_forward'
)} <kbd>${getSpecialKey()}</kbd><kbd>→</kbd>`"
:icon="IconArrowRight"
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
@click="router.go(1)"
/>
</div>
</template>
<script setup lang="ts">
import { useI18n } from "@composables/i18n"
import { useRouter } from "vue-router"
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
import IconArrowLeft from "~icons/lucide/arrow-left"
import IconArrowRight from "~icons/lucide/arrow-right"
const t = useI18n()
const router = useRouter()
</script>

View File

@@ -165,6 +165,20 @@
</div> </div>
</div> </div>
</div> </div>
<div
v-if="isLastItem"
class="w-full transition"
:class="[
{
'bg-accentDark': isLastItemReorderable,
'h-1 ': isLastItem,
},
]"
@drop="updateLastItemOrder"
@dragover.prevent="orderingLastItem = true"
@dragleave="orderingLastItem = false"
@dragend="resetDragState"
></div>
</div> </div>
</template> </template>
@@ -249,6 +263,11 @@ const props = defineProps({
default: () => [], default: () => [],
required: false, required: false,
}, },
isLastItem: {
type: Boolean,
default: false,
required: false,
},
}) })
const emit = defineEmits<{ const emit = defineEmits<{
@@ -262,6 +281,7 @@ const emit = defineEmits<{
(event: "drag-event", payload: DataTransfer): void (event: "drag-event", payload: DataTransfer): void
(event: "dragging", payload: boolean): void (event: "dragging", payload: boolean): void
(event: "update-collection-order", payload: DataTransfer): void (event: "update-collection-order", payload: DataTransfer): void
(event: "update-last-collection-order", payload: DataTransfer): void
}>() }>()
const tippyActions = ref<TippyComponent | null>(null) const tippyActions = ref<TippyComponent | null>(null)
@@ -274,6 +294,7 @@ const options = ref<TippyComponent | null>(null)
const dragging = ref(false) const dragging = ref(false)
const ordering = ref(false) const ordering = ref(false)
const orderingLastItem = ref(false)
const dropItemID = ref("") const dropItemID = ref("")
const currentReorderingStatus = useReadonlyStream(currentReorderingStatus$, { const currentReorderingStatus = useReadonlyStream(currentReorderingStatus$, {
@@ -333,6 +354,14 @@ const isReorderable = computed(() => {
isSameParent.value isSameParent.value
) )
}) })
const isLastItemReorderable = computed(() => {
return (
orderingLastItem.value &&
notSameDestination.value &&
!isRequestDragging.value &&
isSameParent.value
)
})
const dragStart = ({ dataTransfer }: DragEvent) => { const dragStart = ({ dataTransfer }: DragEvent) => {
if (dataTransfer) { if (dataTransfer) {
@@ -350,17 +379,35 @@ const dragStart = ({ dataTransfer }: DragEvent) => {
// Trigger the re-ordering event when a collection is dragged over another collection's top section // Trigger the re-ordering event when a collection is dragged over another collection's top section
const handleDragOver = (e: DragEvent) => { const handleDragOver = (e: DragEvent) => {
dragging.value = true dragging.value = true
if (e.offsetY < 10 && notSameDestination.value) { if (
e.offsetY < 10 &&
notSameDestination.value &&
!isRequestDragging.value &&
isSameParent.value
) {
ordering.value = true ordering.value = true
dragging.value = false dragging.value = false
orderingLastItem.value = false
} else if (
e.offsetY > 18 &&
notSameDestination.value &&
!isRequestDragging.value &&
isSameParent.value
) {
orderingLastItem.value = true
dragging.value = false
ordering.value = false
} else { } else {
ordering.value = false ordering.value = false
orderingLastItem.value = false
} }
} }
const handelDrop = (e: DragEvent) => { const handelDrop = (e: DragEvent) => {
if (ordering.value) { if (ordering.value) {
orderUpdateCollectionEvent(e) orderUpdateCollectionEvent(e)
} else if (orderingLastItem.value) {
updateLastItemOrder(e)
} else { } else {
dropEvent(e) dropEvent(e)
} }
@@ -382,6 +429,14 @@ const orderUpdateCollectionEvent = (e: DragEvent) => {
} }
} }
const updateLastItemOrder = (e: DragEvent) => {
if (e.dataTransfer) {
e.stopPropagation()
emit("update-last-collection-order", e.dataTransfer)
resetDragState()
}
}
const notSameDestination = computed(() => { const notSameDestination = computed(() => {
return dropItemID.value !== props.id return dropItemID.value !== props.id
}) })
@@ -397,5 +452,6 @@ const isCollLoading = computed(() => {
const resetDragState = () => { const resetDragState = () => {
dragging.value = false dragging.value = false
ordering.value = false ordering.value = false
orderingLastItem.value = false
} }
</script> </script>

View File

@@ -43,6 +43,7 @@
:data="node.data.data.data" :data="node.data.data.data"
:collections-type="collectionsType.type" :collections-type="collectionsType.type"
:is-open="isOpen" :is-open="isOpen"
:is-last-item="node.data.isLastItem"
:is-selected=" :is-selected="
isSelected({ isSelected({
collectionIndex: parseInt(node.id), collectionIndex: parseInt(node.id),
@@ -77,7 +78,18 @@
@remove-collection="emit('remove-collection', node.id)" @remove-collection="emit('remove-collection', node.id)"
@drop-event="dropEvent($event, node.id)" @drop-event="dropEvent($event, node.id)"
@drag-event="dragEvent($event, node.id)" @drag-event="dragEvent($event, node.id)"
@update-collection-order="updateCollectionOrder($event, node.id)" @update-collection-order="
updateCollectionOrder($event, {
destinationCollectionIndex: node.id,
destinationCollectionParentIndex: node.data.data.parentIndex,
})
"
@update-last-collection-order="
updateCollectionOrder($event, {
destinationCollectionIndex: null,
destinationCollectionParentIndex: node.data.data.parentIndex,
})
"
@dragging=" @dragging="
(isDraging) => highlightChildren(isDraging ? node.id : null) (isDraging) => highlightChildren(isDraging ? node.id : null)
" "
@@ -99,6 +111,7 @@
:data="node.data.data.data" :data="node.data.data.data"
:collections-type="collectionsType.type" :collections-type="collectionsType.type"
:is-open="isOpen" :is-open="isOpen"
:is-last-item="node.data.isLastItem"
:is-selected=" :is-selected="
isSelected({ isSelected({
folderPath: node.id, folderPath: node.id,
@@ -133,7 +146,18 @@
@remove-collection="emit('remove-folder', node.id)" @remove-collection="emit('remove-folder', node.id)"
@drop-event="dropEvent($event, node.id)" @drop-event="dropEvent($event, node.id)"
@drag-event="dragEvent($event, node.id)" @drag-event="dragEvent($event, node.id)"
@update-collection-order="updateCollectionOrder($event, node.id)" @update-collection-order="
updateCollectionOrder($event, {
destinationCollectionIndex: node.id,
destinationCollectionParentIndex: node.data.data.parentIndex,
})
"
@update-last-collection-order="
updateCollectionOrder($event, {
destinationCollectionIndex: null,
destinationCollectionParentIndex: node.data.data.parentIndex,
})
"
@dragging=" @dragging="
(isDraging) => highlightChildren(isDraging ? node.id : null) (isDraging) => highlightChildren(isDraging ? node.id : null)
" "
@@ -155,6 +179,7 @@
:parent-i-d="node.data.data.parentIndex" :parent-i-d="node.data.data.parentIndex"
:collections-type="collectionsType.type" :collections-type="collectionsType.type"
:save-request="saveRequest" :save-request="saveRequest"
:is-last-item="node.data.isLastItem"
:is-active=" :is-active="
isActiveRequest( isActiveRequest(
node.data.data.parentIndex, node.data.data.parentIndex,
@@ -209,6 +234,12 @@
requestIndex: node.id, requestIndex: node.id,
}) })
" "
@update-last-request-order="
updateRequestOrder($event, {
folderPath: node.data.data.parentIndex,
requestIndex: null,
})
"
/> />
</template> </template>
<template #emptyNode="{ node }"> <template #emptyNode="{ node }">
@@ -305,6 +336,7 @@ import { currentActiveTab } from "~/helpers/rest/tab"
export type Collection = { export type Collection = {
type: "collections" type: "collections"
isLastItem: boolean
data: { data: {
parentIndex: null parentIndex: null
data: HoppCollection<HoppRESTRequest> data: HoppCollection<HoppRESTRequest>
@@ -313,6 +345,7 @@ export type Collection = {
type Folder = { type Folder = {
type: "folders" type: "folders"
isLastItem: boolean
data: { data: {
parentIndex: string parentIndex: string
data: HoppCollection<HoppRESTRequest> data: HoppCollection<HoppRESTRequest>
@@ -321,6 +354,7 @@ type Folder = {
type Requests = { type Requests = {
type: "requests" type: "requests"
isLastItem: boolean
data: { data: {
parentIndex: string parentIndex: string
data: HoppRESTRequest data: HoppRESTRequest
@@ -450,7 +484,7 @@ const emit = defineEmits<{
event: "update-request-order", event: "update-request-order",
payload: { payload: {
dragedRequestIndex: string dragedRequestIndex: string
destinationRequestIndex: string destinationRequestIndex: string | null
destinationCollectionIndex: string destinationCollectionIndex: string
} }
): void ): void
@@ -458,7 +492,10 @@ const emit = defineEmits<{
event: "update-collection-order", event: "update-collection-order",
payload: { payload: {
dragedCollectionIndex: string dragedCollectionIndex: string
destinationCollectionIndex: string destinationCollection: {
destinationCollectionIndex: string | null
destinationCollectionParentIndex: string | null
}
} }
): void ): void
(event: "select", payload: Picked | null): void (event: "select", payload: Picked | null): void
@@ -589,7 +626,7 @@ const updateRequestOrder = (
{ {
folderPath, folderPath,
requestIndex, requestIndex,
}: { folderPath: string | null; requestIndex: string } }: { folderPath: string | null; requestIndex: string | null }
) => { ) => {
if (!folderPath) return if (!folderPath) return
const dragedRequestIndex = dataTransfer.getData("requestIndex") const dragedRequestIndex = dataTransfer.getData("requestIndex")
@@ -605,13 +642,16 @@ const updateRequestOrder = (
const updateCollectionOrder = ( const updateCollectionOrder = (
dataTransfer: DataTransfer, dataTransfer: DataTransfer,
destinationCollectionIndex: string destinationCollection: {
destinationCollectionIndex: string | null
destinationCollectionParentIndex: string | null
}
) => { ) => {
const dragedCollectionIndex = dataTransfer.getData("collectionIndex") const dragedCollectionIndex = dataTransfer.getData("collectionIndex")
emit("update-collection-order", { emit("update-collection-order", {
dragedCollectionIndex, dragedCollectionIndex,
destinationCollectionIndex, destinationCollection,
}) })
} }
@@ -641,6 +681,7 @@ class MyCollectionsAdapter implements SmartTreeAdapter<MyCollectionNode> {
id: index.toString(), id: index.toString(),
data: { data: {
type: "collections", type: "collections",
isLastItem: index === this.data.value.length - 1,
data: { data: {
parentIndex: null, parentIndex: null,
data: item, data: item,
@@ -662,23 +703,25 @@ class MyCollectionsAdapter implements SmartTreeAdapter<MyCollectionNode> {
if (item) { if (item) {
const data = [ const data = [
...item.folders.map((item, index) => ({ ...item.folders.map((folder, index) => ({
id: `${id}/${index}`, id: `${id}/${index}`,
data: { data: {
isLastItem: index === item.folders.length - 1,
type: "folders", type: "folders",
data: { data: {
parentIndex: id, parentIndex: id,
data: item, data: folder,
}, },
}, },
})), })),
...item.requests.map((item, index) => ({ ...item.requests.map((requests, index) => ({
id: `${id}/${index}`, id: `${id}/${index}`,
data: { data: {
isLastItem: index === item.requests.length - 1,
type: "requests", type: "requests",
data: { data: {
parentIndex: id, parentIndex: id,
data: item, data: requests,
}, },
}, },
})), })),

View File

@@ -1,13 +1,13 @@
<template> <template>
<div class="flex flex-col"> <div class="flex flex-col">
<div <div
class="h-1" class="h-1 w-full transition"
:class="[ :class="[
{ {
'bg-accentDark': isReorderable, 'bg-accentDark': isReorderable,
}, },
]" ]"
@drop="dropEvent" @drop="updateRequestOrder"
@dragover.prevent="ordering = true" @dragover.prevent="ordering = true"
@dragleave="resetDragState" @dragleave="resetDragState"
@dragend="resetDragState" @dragend="resetDragState"
@@ -15,7 +15,7 @@
<div <div
class="flex items-stretch group" class="flex items-stretch group"
:draggable="!hasNoTeamAccess" :draggable="!hasNoTeamAccess"
@drop="dropEvent" @drop="handelDrop"
@dragstart="dragStart" @dragstart="dragStart"
@dragover="handleDragOver($event)" @dragover="handleDragOver($event)"
@dragleave="resetDragState" @dragleave="resetDragState"
@@ -138,6 +138,19 @@
</span> </span>
</div> </div>
</div> </div>
<div
class="w-full transition"
:class="[
{
'bg-accentDark': isLastItemReorderable,
'h-1 ': isLastItem,
},
]"
@drop="handelDrop"
@dragover.prevent="orderingLastItem = true"
@dragleave="resetDragState"
@dragend="resetDragState"
></div>
</div> </div>
</template> </template>
@@ -214,6 +227,11 @@ const props = defineProps({
default: () => [], default: () => [],
required: false, required: false,
}, },
isLastItem: {
type: Boolean,
default: false,
required: false,
},
}) })
const emit = defineEmits<{ const emit = defineEmits<{
@@ -223,6 +241,7 @@ const emit = defineEmits<{
(event: "select-request"): void (event: "select-request"): void
(event: "drag-request", payload: DataTransfer): void (event: "drag-request", payload: DataTransfer): void
(event: "update-request-order", payload: DataTransfer): void (event: "update-request-order", payload: DataTransfer): void
(event: "update-last-request-order", payload: DataTransfer): void
}>() }>()
const tippyActions = ref<TippyComponent | null>(null) const tippyActions = ref<TippyComponent | null>(null)
@@ -233,6 +252,7 @@ const duplicate = ref<HTMLButtonElement | null>(null)
const dragging = ref(false) const dragging = ref(false)
const ordering = ref(false) const ordering = ref(false)
const orderingLastItem = ref(false)
const currentReorderingStatus = useReadonlyStream(currentReorderingStatus$, { const currentReorderingStatus = useReadonlyStream(currentReorderingStatus$, {
type: "collection", type: "collection",
@@ -269,6 +289,10 @@ const dragStart = ({ dataTransfer }: DragEvent) => {
} }
} }
const isSameRequest = computed(() => {
return currentReorderingStatus.value.id === props.requestID
})
const isCollectionDragging = computed(() => { const isCollectionDragging = computed(() => {
return currentReorderingStatus.value.type === "collection" return currentReorderingStatus.value.type === "collection"
}) })
@@ -278,7 +302,18 @@ const isSameParent = computed(() => {
}) })
const isReorderable = computed(() => { const isReorderable = computed(() => {
return ordering.value && !isCollectionDragging.value && isSameParent.value return (
ordering.value &&
!isCollectionDragging.value &&
isSameParent.value &&
!isSameRequest.value
)
})
const isLastItemReorderable = computed(() => {
return (
orderingLastItem.value && isSameParent.value && !isCollectionDragging.value
)
}) })
// Trigger the re-ordering event when a request is dragged over another request's top section // Trigger the re-ordering event when a request is dragged over another request's top section
@@ -287,12 +322,28 @@ const handleDragOver = (e: DragEvent) => {
if (e.offsetY < 10) { if (e.offsetY < 10) {
ordering.value = true ordering.value = true
dragging.value = false dragging.value = false
orderingLastItem.value = false
} else if (e.offsetY > 18) {
orderingLastItem.value = true
dragging.value = false
ordering.value = false
} else { } else {
ordering.value = false ordering.value = false
orderingLastItem.value = false
} }
} }
const dropEvent = (e: DragEvent) => { const handelDrop = (e: DragEvent) => {
if (ordering.value) {
updateRequestOrder(e)
} else if (orderingLastItem.value) {
updateLastItemOrder(e)
} else {
updateRequestOrder(e)
}
}
const updateRequestOrder = (e: DragEvent) => {
if (e.dataTransfer) { if (e.dataTransfer) {
e.stopPropagation() e.stopPropagation()
resetDragState() resetDragState()
@@ -300,6 +351,14 @@ const dropEvent = (e: DragEvent) => {
} }
} }
const updateLastItemOrder = (e: DragEvent) => {
if (e.dataTransfer) {
e.stopPropagation()
resetDragState()
emit("update-last-request-order", e.dataTransfer)
}
}
const isRequestLoading = computed(() => { const isRequestLoading = computed(() => {
if (props.requestMoveLoading.length > 0 && props.requestID) { if (props.requestMoveLoading.length > 0 && props.requestID) {
return props.requestMoveLoading.includes(props.requestID) return props.requestMoveLoading.includes(props.requestID)
@@ -311,5 +370,6 @@ const isRequestLoading = computed(() => {
const resetDragState = () => { const resetDragState = () => {
dragging.value = false dragging.value = false
ordering.value = false ordering.value = false
orderingLastItem.value = false
} }
</script> </script>

View File

@@ -60,6 +60,7 @@
:export-loading="exportLoading" :export-loading="exportLoading"
:has-no-team-access="hasNoTeamAccess" :has-no-team-access="hasNoTeamAccess"
:collection-move-loading="collectionMoveLoading" :collection-move-loading="collectionMoveLoading"
:is-last-item="node.data.isLastItem"
:is-selected=" :is-selected="
isSelected({ isSelected({
collectionID: node.id, collectionID: node.id,
@@ -95,7 +96,16 @@
@drop-event="dropEvent($event, node.id)" @drop-event="dropEvent($event, node.id)"
@drag-event="dragEvent($event, node.id)" @drag-event="dragEvent($event, node.id)"
@update-collection-order=" @update-collection-order="
updateCollectionOrder($event, node.data.data.data.id) updateCollectionOrder($event, {
destinationCollectionIndex: node.data.data.data.id,
destinationCollectionParentIndex: node.data.data.parentIndex,
})
"
@update-last-collection-order="
updateCollectionOrder($event, {
destinationCollectionIndex: null,
destinationCollectionParentIndex: node.data.data.parentIndex,
})
" "
@dragging=" @dragging="
(isDraging) => (isDraging) =>
@@ -122,6 +132,7 @@
:export-loading="exportLoading" :export-loading="exportLoading"
:has-no-team-access="hasNoTeamAccess" :has-no-team-access="hasNoTeamAccess"
:collection-move-loading="collectionMoveLoading" :collection-move-loading="collectionMoveLoading"
:is-last-item="node.data.isLastItem"
:is-selected=" :is-selected="
isSelected({ isSelected({
folderID: node.data.data.data.id, folderID: node.data.data.data.id,
@@ -159,7 +170,16 @@
@drop-event="dropEvent($event, node.data.data.data.id)" @drop-event="dropEvent($event, node.data.data.data.id)"
@drag-event="dragEvent($event, node.data.data.data.id)" @drag-event="dragEvent($event, node.data.data.data.id)"
@update-collection-order=" @update-collection-order="
updateCollectionOrder($event, node.data.data.data.id) updateCollectionOrder($event, {
destinationCollectionIndex: node.data.data.data.id,
destinationCollectionParentIndex: node.data.data.parentIndex,
})
"
@update-last-collection-order="
updateCollectionOrder($event, {
destinationCollectionIndex: null,
destinationCollectionParentIndex: node.data.data.parentIndex,
})
" "
@dragging=" @dragging="
(isDraging) => (isDraging) =>
@@ -186,6 +206,7 @@
:is-active="isActiveRequest(node.data.data.data.id)" :is-active="isActiveRequest(node.data.data.data.id)"
:has-no-team-access="hasNoTeamAccess" :has-no-team-access="hasNoTeamAccess"
:request-move-loading="requestMoveLoading" :request-move-loading="requestMoveLoading"
:is-last-item="node.data.isLastItem"
:is-selected=" :is-selected="
isSelected({ isSelected({
requestID: node.data.data.data.id, requestID: node.data.data.data.id,
@@ -231,6 +252,12 @@
requestIndex: node.data.data.data.id, requestIndex: node.data.data.data.id,
}) })
" "
@update-last-request-order="
updateRequestOrder($event, {
folderPath: node.data.data.parentIndex,
requestIndex: null,
})
"
/> />
</template> </template>
<template #emptyNode="{ node }"> <template #emptyNode="{ node }">
@@ -461,7 +488,7 @@ const emit = defineEmits<{
event: "update-request-order", event: "update-request-order",
payload: { payload: {
dragedRequestIndex: string dragedRequestIndex: string
destinationRequestIndex: string destinationRequestIndex: string | null
destinationCollectionIndex: string destinationCollectionIndex: string
} }
): void ): void
@@ -469,7 +496,10 @@ const emit = defineEmits<{
event: "update-collection-order", event: "update-collection-order",
payload: { payload: {
dragedCollectionIndex: string dragedCollectionIndex: string
destinationCollectionIndex: string destinationCollection: {
destinationCollectionIndex: string | null
destinationCollectionParentIndex: string | null
}
} }
): void ): void
(event: "select", payload: Picked | null): void (event: "select", payload: Picked | null): void
@@ -597,7 +627,7 @@ const updateRequestOrder = (
{ {
folderPath, folderPath,
requestIndex, requestIndex,
}: { folderPath: string | null; requestIndex: string } }: { folderPath: string | null; requestIndex: string | null }
) => { ) => {
if (!folderPath) return if (!folderPath) return
const dragedRequestIndex = dataTransfer.getData("requestIndex") const dragedRequestIndex = dataTransfer.getData("requestIndex")
@@ -613,17 +643,21 @@ const updateRequestOrder = (
const updateCollectionOrder = ( const updateCollectionOrder = (
dataTransfer: DataTransfer, dataTransfer: DataTransfer,
destinationCollectionIndex: string destinationCollection: {
destinationCollectionIndex: string | null
destinationCollectionParentIndex: string | null
}
) => { ) => {
const dragedCollectionIndex = dataTransfer.getData("collectionIndex") const dragedCollectionIndex = dataTransfer.getData("collectionIndex")
emit("update-collection-order", { emit("update-collection-order", {
dragedCollectionIndex, dragedCollectionIndex,
destinationCollectionIndex, destinationCollection,
}) })
} }
type TeamCollections = { type TeamCollections = {
isLastItem: boolean
type: "collections" type: "collections"
data: { data: {
parentIndex: null parentIndex: null
@@ -632,6 +666,7 @@ type TeamCollections = {
} }
type TeamFolder = { type TeamFolder = {
isLastItem: boolean
type: "folders" type: "folders"
data: { data: {
parentIndex: string parentIndex: string
@@ -640,6 +675,7 @@ type TeamFolder = {
} }
type TeamRequests = { type TeamRequests = {
isLastItem: boolean
type: "requests" type: "requests"
data: { data: {
parentIndex: string parentIndex: string
@@ -679,9 +715,10 @@ class TeamCollectionsAdapter implements SmartTreeAdapter<TeamCollectionNode> {
status: "loading", status: "loading",
} }
} else { } else {
const data = this.data.value.map((item) => ({ const data = this.data.value.map((item, index) => ({
id: item.id, id: item.id,
data: { data: {
isLastItem: index === this.data.value.length - 1,
type: "collections", type: "collections",
data: { data: {
parentIndex: null, parentIndex: null,
@@ -709,9 +746,10 @@ class TeamCollectionsAdapter implements SmartTreeAdapter<TeamCollectionNode> {
if (items) { if (items) {
const data = [ const data = [
...(items.children ...(items.children
? items.children.map((item) => ({ ? items.children.map((item, index) => ({
id: `${id}/${item.id}`, id: `${id}/${item.id}`,
data: { data: {
isLastItem: index === items.children.length - 1,
type: "folders", type: "folders",
data: { data: {
parentIndex: parsedID, parentIndex: parsedID,
@@ -721,9 +759,10 @@ class TeamCollectionsAdapter implements SmartTreeAdapter<TeamCollectionNode> {
})) }))
: []), : []),
...(items.requests ...(items.requests
? items.requests.map((item) => ({ ? items.requests.map((item, index) => ({
id: `${id}/${item.id}`, id: `${id}/${item.id}`,
data: { data: {
isLastItem: index === items.requests.length - 1,
type: "requests", type: "requests",
data: { data: {
parentIndex: parsedID, parentIndex: parsedID,

View File

@@ -2,7 +2,8 @@
<div <div
:class="{ :class="{
'rounded border border-divider': saveRequest, 'rounded border border-divider': saveRequest,
'bg-primaryDark': draggingToRoot, 'bg-primaryDark':
draggingToRoot && currentReorderingStatus.type !== 'request',
}" }"
class="flex-1" class="flex-1"
@drop.prevent="dropToRoot" @drop.prevent="dropToRoot"
@@ -85,7 +86,9 @@
/> />
<div <div
class="hidden bg-primaryDark flex-col flex-1 items-center py-15 justify-center px-4 text-secondaryLight" class="hidden bg-primaryDark flex-col flex-1 items-center py-15 justify-center px-4 text-secondaryLight"
:class="{ '!flex': draggingToRoot }" :class="{
'!flex': draggingToRoot && currentReorderingStatus.type !== 'request',
}"
> >
<component :is="IconListEnd" class="svg-icons !w-8 !h-8" /> <component :is="IconListEnd" class="svg-icons !w-8 !h-8" />
</div> </div>
@@ -232,6 +235,7 @@ import {
resolveSaveContextOnCollectionReorder, resolveSaveContextOnCollectionReorder,
updateSaveContextForAffectedRequests, updateSaveContextForAffectedRequests,
} from "~/helpers/collection/collection" } from "~/helpers/collection/collection"
import { currentReorderingStatus$ } from "~/newstore/reordering"
const t = useI18n() const t = useI18n()
const toast = useToast() const toast = useToast()
@@ -396,6 +400,12 @@ watch(
} }
) )
const currentReorderingStatus = useReadonlyStream(currentReorderingStatus$, {
type: "collection",
id: "",
parentID: "",
})
const hasTeamWriteAccess = computed(() => { const hasTeamWriteAccess = computed(() => {
if (!collectionsType.value.selectedTeam) return false if (!collectionsType.value.selectedTeam) return false
@@ -1525,13 +1535,34 @@ const dropToRoot = ({ dataTransfer }: DragEvent) => {
/** /**
* Used to check if the request/collection is being moved to the same parent since reorder is only allowed within the same parent * Used to check if the request/collection is being moved to the same parent since reorder is only allowed within the same parent
* @param draggedReq - path index of the dragged request * @param draggedItem - path index of the dragged request
* @param destinationReq - path index of the destination request * @param destinationItem - path index of the destination request
* @param destinationCollectionIndex - index of the destination collection
* @returns boolean - true if the request is being moved to the same parent * @returns boolean - true if the request is being moved to the same parent
*/ */
const isSameSameParent = (draggedItem: string, destinationItem: string) => { const isSameSameParent = (
const draggedItemIndex = pathToIndex(draggedItem) draggedItemPath: string,
const destinationItemIndex = pathToIndex(destinationItem) destinationItemPath: string | null,
destinationCollectionIndex: string | null
) => {
const draggedItemIndex = pathToIndex(draggedItemPath)
// if the destinationItemPath and destinationCollectionIndex is null, it means the request is being moved to the root
if (destinationItemPath === null && destinationCollectionIndex === null) {
return draggedItemIndex.length === 1
} else if (
destinationItemPath === null &&
destinationCollectionIndex !== null &&
draggedItemIndex.length === 1
) {
return draggedItemIndex[0] === destinationCollectionIndex
} else if (destinationItemPath === null && draggedItemIndex.length !== 1) {
const dragedItemParent = draggedItemIndex.slice(0, -1)
return dragedItemParent[0] === destinationCollectionIndex
} else {
if (destinationItemPath === null) return false
const destinationItemIndex = pathToIndex(destinationItemPath)
// length of 1 means the request is in the root // length of 1 means the request is in the root
if (draggedItemIndex.length === 1 && destinationItemIndex.length === 1) { if (draggedItemIndex.length === 1 && destinationItemIndex.length === 1) {
@@ -1547,6 +1578,7 @@ const isSameSameParent = (draggedItem: string, destinationItem: string) => {
} else { } else {
return false return false
} }
}
} }
/** /**
@@ -1556,7 +1588,7 @@ const isSameSameParent = (draggedItem: string, destinationItem: string) => {
*/ */
const updateRequestOrder = (payload: { const updateRequestOrder = (payload: {
dragedRequestIndex: string dragedRequestIndex: string
destinationRequestIndex: string destinationRequestIndex: string | null
destinationCollectionIndex: string destinationCollectionIndex: string
}) => { }) => {
const { const {
@@ -1565,29 +1597,28 @@ const updateRequestOrder = (payload: {
destinationCollectionIndex, destinationCollectionIndex,
} = payload } = payload
if ( if (!dragedRequestIndex || !destinationCollectionIndex) return
!dragedRequestIndex ||
!destinationRequestIndex ||
!destinationCollectionIndex
)
return
if (dragedRequestIndex === destinationRequestIndex) return if (dragedRequestIndex === destinationRequestIndex) return
if (collectionsType.value.type === "my-collections") { if (collectionsType.value.type === "my-collections") {
if (!isSameSameParent(dragedRequestIndex, destinationRequestIndex)) { if (
!isSameSameParent(
dragedRequestIndex,
destinationRequestIndex,
destinationCollectionIndex
)
) {
toast.error(`${t("collection.different_parent")}`) toast.error(`${t("collection.different_parent")}`)
} else { } else {
updateRESTRequestOrder( updateRESTRequestOrder(
pathToLastIndex(dragedRequestIndex), pathToLastIndex(dragedRequestIndex),
pathToLastIndex(destinationRequestIndex), destinationRequestIndex
? pathToLastIndex(destinationRequestIndex)
: null,
destinationCollectionIndex destinationCollectionIndex
) )
resolveSaveContextOnRequestReorder({
lastIndex: pathToLastIndex(dragedRequestIndex),
newIndex: pathToLastIndex(destinationRequestIndex),
folderPath: destinationCollectionIndex,
})
toast.success(`${t("request.order_changed")}`) toast.success(`${t("request.order_changed")}`)
} }
} else if (hasTeamWriteAccess.value) { } else if (hasTeamWriteAccess.value) {
@@ -1628,14 +1659,25 @@ const updateRequestOrder = (payload: {
*/ */
const updateCollectionOrder = (payload: { const updateCollectionOrder = (payload: {
dragedCollectionIndex: string dragedCollectionIndex: string
destinationCollectionIndex: string destinationCollection: {
destinationCollectionIndex: string | null
destinationCollectionParentIndex: string | null
}
}) => { }) => {
const { dragedCollectionIndex, destinationCollectionIndex } = payload const { dragedCollectionIndex, destinationCollection } = payload
if (!dragedCollectionIndex || !destinationCollectionIndex) return const { destinationCollectionIndex, destinationCollectionParentIndex } =
destinationCollection
if (!dragedCollectionIndex) return
if (dragedCollectionIndex === destinationCollectionIndex) return if (dragedCollectionIndex === destinationCollectionIndex) return
if (collectionsType.value.type === "my-collections") { if (collectionsType.value.type === "my-collections") {
if (!isSameSameParent(dragedCollectionIndex, destinationCollectionIndex)) { if (
!isSameSameParent(
dragedCollectionIndex,
destinationCollectionIndex,
destinationCollectionParentIndex
)
) {
toast.error(`${t("collection.different_parent")}`) toast.error(`${t("collection.different_parent")}`)
} else { } else {
updateRESTCollectionOrder( updateRESTCollectionOrder(
@@ -1644,7 +1686,9 @@ const updateCollectionOrder = (payload: {
) )
resolveSaveContextOnCollectionReorder({ resolveSaveContextOnCollectionReorder({
lastIndex: pathToLastIndex(dragedCollectionIndex), lastIndex: pathToLastIndex(dragedCollectionIndex),
newIndex: pathToLastIndex(destinationCollectionIndex), newIndex: pathToLastIndex(
destinationCollectionIndex ? destinationCollectionIndex : ""
),
folderPath: dragedCollectionIndex.split("/").slice(0, -1).join("/"), folderPath: dragedCollectionIndex.split("/").slice(0, -1).join("/"),
}) })
toast.success(`${t("collection.order_changed")}`) toast.success(`${t("collection.order_changed")}`)

View File

@@ -1,3 +1,3 @@
mutation UpdateCollectionOrder($collectionID: ID!, $destCollID: ID!) { mutation UpdateCollectionOrder($collectionID: ID!, $destCollID: ID) {
updateCollectionOrder(collectionID: $collectionID, destCollID: $destCollID) updateCollectionOrder(collectionID: $collectionID, destCollID: $destCollID)
} }

View File

@@ -103,7 +103,7 @@ export const moveRESTTeamCollection = (
export const updateOrderRESTTeamCollection = ( export const updateOrderRESTTeamCollection = (
collectionID: string, collectionID: string,
destCollID: string destCollID: string | null
) => ) =>
runMutation< runMutation<
UpdateCollectionOrderMutation, UpdateCollectionOrderMutation,

View File

@@ -84,7 +84,7 @@ export const moveRESTTeamRequest = (collectionID: string, requestID: string) =>
export const updateOrderRESTTeamRequest = ( export const updateOrderRESTTeamRequest = (
requestID: string, requestID: string,
nextRequestID: string, nextRequestID: string | null,
collectionID: string collectionID: string
) => ) =>
runMutation< runMutation<

View File

@@ -558,11 +558,30 @@ export default class NewTeamCollectionAdapter {
public updateRequestOrder( public updateRequestOrder(
dragedRequestID: string, dragedRequestID: string,
destinationRequestID: string, destinationRequestID: string | null,
destinationCollectionID: string destinationCollectionID: string
) { ) {
const tree = this.collections$.value const tree = this.collections$.value
// If the destination request is null, then it is the last request in the collection
if (destinationRequestID === null) {
const collection = findCollInTree(tree, destinationCollectionID)
if (!collection) return // Ignore order update
// Collection is not expanded
if (!collection.requests) return
const requestIndex = collection.requests.findIndex(
(req) => req.id === dragedRequestID
)
// If the collection index is not found, don't update
if (requestIndex === -1) return
// Move the request to the end of the requests
collection.requests.push(collection.requests.splice(requestIndex, 1)[0])
} else {
// Find collection in tree, don't attempt if no collection is found // Find collection in tree, don't attempt if no collection is found
const collection = findCollInTree(tree, destinationCollectionID) const collection = findCollInTree(tree, destinationCollectionID)
if (!collection) return // Ignore order update if (!collection) return // Ignore order update
@@ -580,16 +599,39 @@ export default class NewTeamCollectionAdapter {
if (requestIndex === -1) return if (requestIndex === -1) return
this.reorderItems(collection.requests, requestIndex, destinationIndex) this.reorderItems(collection.requests, requestIndex, destinationIndex)
}
this.collections$.next(tree) this.collections$.next(tree)
} }
public updateCollectionOrder = ( public updateCollectionOrder = (
collectionID: string, collectionID: string,
destinationCollectionID: string destinationCollectionID: string | null
) => { ) => {
const tree = this.collections$.value const tree = this.collections$.value
// If the destination collection is null, then it is the last collection in the tree
if (destinationCollectionID === null) {
const collLast = findParentOfColl(tree, collectionID)
if (collLast && collLast.children) {
const collectionIndex = collLast.children.findIndex(
(coll) => coll.id === collectionID
)
// reorder the collection to the end of the collections
collLast.children.push(collLast.children.splice(collectionIndex, 1)[0])
} else {
const collectionIndex = tree.findIndex(
(coll) => coll.id === collectionID
)
// If the collection index is not found, don't update
if (collectionIndex === -1) return
// reorder the collection to the end of the collections in the root
tree.push(tree.splice(collectionIndex, 1)[0])
}
} else {
// Find collection in tree // Find collection in tree
const coll = findParentOfColl(tree, destinationCollectionID) const coll = findParentOfColl(tree, destinationCollectionID)
@@ -609,7 +651,9 @@ export default class NewTeamCollectionAdapter {
this.reorderItems(coll.children, collectionIndex, destinationIndex) this.reorderItems(coll.children, collectionIndex, destinationIndex)
} else { } else {
// If the collection has no parent collection, it is a root collection // If the collection has no parent collection, it is a root collection
const collectionIndex = tree.findIndex((coll) => coll.id === collectionID) const collectionIndex = tree.findIndex(
(coll) => coll.id === collectionID
)
const destinationIndex = tree.findIndex( const destinationIndex = tree.findIndex(
(coll) => coll.id === destinationCollectionID (coll) => coll.id === destinationCollectionID
@@ -620,6 +664,7 @@ export default class NewTeamCollectionAdapter {
this.reorderItems(tree, collectionIndex, destinationIndex) this.reorderItems(tree, collectionIndex, destinationIndex)
} }
}
this.collections$.next(tree) this.collections$.next(tree)
} }
@@ -821,12 +866,10 @@ export default class NewTeamCollectionAdapter {
const { request } = requestOrderUpdated const { request } = requestOrderUpdated
const { nextRequest } = requestOrderUpdated const { nextRequest } = requestOrderUpdated
if (!nextRequest) return
this.updateRequestOrder( this.updateRequestOrder(
request.id, request.id,
nextRequest.id, nextRequest ? nextRequest.id : null,
nextRequest.collectionID nextRequest ? nextRequest.collectionID : request.collectionID
) )
} }
) )
@@ -851,9 +894,10 @@ export default class NewTeamCollectionAdapter {
const { collection } = collectionOrderUpdated const { collection } = collectionOrderUpdated
const { nextCollection } = collectionOrderUpdated const { nextCollection } = collectionOrderUpdated
if (!nextCollection) return this.updateCollectionOrder(
collection.id,
this.updateCollectionOrder(collection.id, nextCollection.id) nextCollection ? nextCollection.id : null
)
} }
) )
} }

View File

@@ -8,6 +8,7 @@ import {
import DispatchingStore, { defineDispatchers } from "./DispatchingStore" import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
import { cloneDeep } from "lodash-es" import { cloneDeep } from "lodash-es"
import { getTabRefWithSaveContext } from "~/helpers/rest/tab" import { getTabRefWithSaveContext } from "~/helpers/rest/tab"
import { resolveSaveContextOnRequestReorder } from "~/helpers/collection/request"
const defaultRESTCollectionState = { const defaultRESTCollectionState = {
state: [ state: [
@@ -288,18 +289,49 @@ const restCollectionDispatchers = defineDispatchers({
destinationCollectionIndex, destinationCollectionIndex,
}: { }: {
collectionIndex: string collectionIndex: string
destinationCollectionIndex: string destinationCollectionIndex: string | null
} }
) { ) {
const newState = state const newState = state
const indexPaths = collectionIndex.split("/").map((x) => parseInt(x)) const indexPaths = collectionIndex.split("/").map((x) => parseInt(x))
if (indexPaths.length === 0) {
console.log("Given path too short. Skipping request.")
return {}
}
// Reordering the collection to the last position
if (destinationCollectionIndex === null) {
const folderIndex = indexPaths.pop() as number
const containingFolder = navigateToFolderWithIndexPath(
newState,
indexPaths
)
if (containingFolder === null) {
newState.push(newState.splice(folderIndex, 1)[0])
return {
state: newState,
}
}
// Pushing the folder to the end of the array (last position)
containingFolder.folders.push(
containingFolder.folders.splice(folderIndex, 1)[0]
)
return {
state: newState,
}
}
const destinationIndexPaths = destinationCollectionIndex const destinationIndexPaths = destinationCollectionIndex
.split("/") .split("/")
.map((x) => parseInt(x)) .map((x) => parseInt(x))
if (indexPaths.length === 0 || destinationIndexPaths.length === 0) { if (destinationIndexPaths.length === 0) {
console.log("Given path too short. Skipping request.") console.log("Given path too short. Skipping request.")
return {} return {}
} }
@@ -487,7 +519,7 @@ const restCollectionDispatchers = defineDispatchers({
destinationCollectionPath, destinationCollectionPath,
}: { }: {
requestIndex: number requestIndex: number
destinationRequestIndex: number destinationRequestIndex: number | null
destinationCollectionPath: string destinationCollectionPath: string
} }
) { ) {
@@ -511,8 +543,32 @@ const restCollectionDispatchers = defineDispatchers({
return {} return {}
} }
// if the destination is null, we are moving to the end of the list
if (destinationRequestIndex === null) {
// move to the end of the list
targetLocation.requests.push(
targetLocation.requests.splice(requestIndex, 1)[0]
)
resolveSaveContextOnRequestReorder({
lastIndex: requestIndex,
newIndex: targetLocation.requests.length,
folderPath: destinationCollectionPath,
})
return {
state: newState,
}
}
reorderItems(targetLocation.requests, requestIndex, destinationRequestIndex) reorderItems(targetLocation.requests, requestIndex, destinationRequestIndex)
resolveSaveContextOnRequestReorder({
lastIndex: requestIndex,
newIndex: destinationRequestIndex,
folderPath: destinationCollectionPath,
})
return { return {
state: newState, state: newState,
} }
@@ -967,7 +1023,7 @@ export function moveRESTRequest(
export function updateRESTRequestOrder( export function updateRESTRequestOrder(
requestIndex: number, requestIndex: number,
destinationRequestIndex: number, destinationRequestIndex: number | null,
destinationCollectionPath: string destinationCollectionPath: string
) { ) {
restCollectionStore.dispatch({ restCollectionStore.dispatch({
@@ -982,7 +1038,7 @@ export function updateRESTRequestOrder(
export function updateRESTCollectionOrder( export function updateRESTCollectionOrder(
collectionIndex: string, collectionIndex: string,
destinationCollectionIndex: string destinationCollectionIndex: string | null
) { ) {
restCollectionStore.dispatch({ restCollectionStore.dispatch({
dispatcher: "updateCollectionOrder", dispatcher: "updateCollectionOrder",