feat: added reordering and moving for collection (#2916)
This commit is contained in:
@@ -1,5 +1,14 @@
|
||||
<template>
|
||||
<div :class="{ 'rounded border border-divider': saveRequest }">
|
||||
<div
|
||||
:class="{
|
||||
'rounded border border-divider': saveRequest,
|
||||
'bg-primaryDark': draggingToRoot,
|
||||
}"
|
||||
class="flex-1"
|
||||
@drop.prevent="dropToRoot"
|
||||
@dragover.prevent="draggingToRoot = true"
|
||||
@dragend="draggingToRoot = false"
|
||||
>
|
||||
<div
|
||||
class="sticky z-10 flex flex-col flex-shrink-0 overflow-x-auto rounded-t bg-primary"
|
||||
:style="
|
||||
@@ -44,6 +53,9 @@
|
||||
@export-data="exportData"
|
||||
@remove-collection="removeCollection"
|
||||
@remove-folder="removeFolder"
|
||||
@drop-collection="dropCollection"
|
||||
@update-request-order="updateRequestOrder"
|
||||
@update-collection-order="updateCollectionOrder"
|
||||
@edit-request="editRequest"
|
||||
@duplicate-request="duplicateRequest"
|
||||
@remove-request="removeRequest"
|
||||
@@ -83,6 +95,8 @@
|
||||
:duplicate-loading="duplicateLoading"
|
||||
:save-request="saveRequest"
|
||||
:picked="picked"
|
||||
:collection-move-loading="collectionMoveLoading"
|
||||
:request-move-loading="requestMoveLoading"
|
||||
@add-request="addRequest"
|
||||
@add-folder="addFolder"
|
||||
@edit-collection="editCollection"
|
||||
@@ -95,12 +109,22 @@
|
||||
@remove-request="removeRequest"
|
||||
@select-request="selectRequest"
|
||||
@select="selectPicked"
|
||||
@drop-request="dropRequest"
|
||||
@drop-collection="dropCollection"
|
||||
@update-request-order="updateRequestOrder"
|
||||
@update-collection-order="updateCollectionOrder"
|
||||
@expand-team-collection="expandTeamCollection"
|
||||
@display-modal-add="displayModalAdd(true)"
|
||||
@display-modal-import-export="displayModalImportExport(true)"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
</HoppSmartTabs>
|
||||
<div
|
||||
class="hidden bg-primaryDark flex-col flex-1 items-center py-15 justify-center px-4 text-secondaryLight"
|
||||
:class="{ '!flex': draggingToRoot }"
|
||||
>
|
||||
<component :is="IconListEnd" class="svg-icons !w-8 !h-8" />
|
||||
</div>
|
||||
<CollectionsAdd
|
||||
:show="showModalAdd"
|
||||
:loading-state="modalLoadingState"
|
||||
@@ -195,12 +219,15 @@ import {
|
||||
editRESTCollection,
|
||||
editRESTFolder,
|
||||
editRESTRequest,
|
||||
moveRESTFolder,
|
||||
moveRESTRequest,
|
||||
removeRESTCollection,
|
||||
removeRESTFolder,
|
||||
removeRESTRequest,
|
||||
restCollections$,
|
||||
saveRESTRequestAs,
|
||||
updateRESTRequestOrder,
|
||||
updateRESTCollectionOrder,
|
||||
} from "~/newstore/collections"
|
||||
import TeamCollectionAdapter from "~/helpers/teams/TeamCollectionAdapter"
|
||||
import {
|
||||
@@ -226,11 +253,15 @@ import {
|
||||
renameCollection,
|
||||
deleteCollection,
|
||||
importJSONToTeam,
|
||||
moveRESTTeamCollection,
|
||||
updateOrderRESTTeamCollection,
|
||||
} from "~/helpers/backend/mutations/TeamCollection"
|
||||
import {
|
||||
updateTeamRequest,
|
||||
createRequestInCollection,
|
||||
deleteTeamRequest,
|
||||
moveRESTTeamRequest,
|
||||
updateOrderRESTTeamRequest,
|
||||
} from "~/helpers/backend/mutations/TeamRequest"
|
||||
import { TeamCollection } from "~/helpers/teams/TeamCollection"
|
||||
import { Collection as NodeCollection } from "./MyCollections.vue"
|
||||
@@ -244,6 +275,7 @@ import * as E from "fp-ts/Either"
|
||||
import { platform } from "~/platform"
|
||||
import { createCollectionGists } from "~/helpers/gist"
|
||||
import { invokeAction } from "~/helpers/actions"
|
||||
import IconListEnd from "~icons/lucide/list-end"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -324,6 +356,11 @@ const currentUser = useReadonlyStream(
|
||||
)
|
||||
const myCollections = useReadonlyStream(restCollections$, [], "deep")
|
||||
|
||||
// Draging
|
||||
const draggingToRoot = ref(false)
|
||||
const collectionMoveLoading = ref<string[]>([])
|
||||
const requestMoveLoading = ref<string[]>([])
|
||||
|
||||
// Export - Import refs
|
||||
const collectionJSON = ref("")
|
||||
const exportingTeamCollections = ref(false)
|
||||
@@ -1333,16 +1370,314 @@ const discardRequestChange = () => {
|
||||
confirmChangeToRequest.value = false
|
||||
}
|
||||
|
||||
// Drag and drop functions
|
||||
/**
|
||||
* Used to get the index of the request from the path
|
||||
* @param path The path of the request
|
||||
* @returns The index of the request
|
||||
*/
|
||||
const pathToIndex = computed(() => {
|
||||
return (path: string) => {
|
||||
const pathArr = path.split("/")
|
||||
return parseInt(pathArr[pathArr.length - 1])
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* This function is called when the user drops the request inside a collection
|
||||
* @param payload Object that contains the folder path, request index and the destination collection index
|
||||
*/
|
||||
const dropRequest = (payload: {
|
||||
folderPath: string
|
||||
folderPath?: string | undefined
|
||||
requestIndex: string
|
||||
collectionIndex: string
|
||||
destinationCollectionIndex: string
|
||||
}) => {
|
||||
const { folderPath, requestIndex, collectionIndex } = payload
|
||||
moveRESTRequest(folderPath, parseInt(requestIndex), collectionIndex)
|
||||
const { folderPath, requestIndex, destinationCollectionIndex } = payload
|
||||
if (!requestIndex || !destinationCollectionIndex) return
|
||||
if (collectionsType.value.type === "my-collections" && folderPath) {
|
||||
moveRESTRequest(
|
||||
folderPath,
|
||||
pathToIndex.value(requestIndex),
|
||||
destinationCollectionIndex
|
||||
)
|
||||
toast.success(`${t("request.moved")}`)
|
||||
draggingToRoot.value = false
|
||||
} else if (hasTeamWriteAccess.value) {
|
||||
// add the request index to the loading array
|
||||
requestMoveLoading.value.push(requestIndex)
|
||||
|
||||
pipe(
|
||||
moveRESTTeamRequest(destinationCollectionIndex, requestIndex),
|
||||
TE.match(
|
||||
(err: GQLError<string>) => {
|
||||
toast.error(`${getErrorMessage(err)}`)
|
||||
requestMoveLoading.value.splice(
|
||||
requestMoveLoading.value.indexOf(requestIndex),
|
||||
1
|
||||
)
|
||||
},
|
||||
() => {
|
||||
// remove the request index from the loading array
|
||||
requestMoveLoading.value.splice(
|
||||
requestMoveLoading.value.indexOf(requestIndex),
|
||||
1
|
||||
)
|
||||
toast.success(`${t("request.moved")}`)
|
||||
}
|
||||
)
|
||||
)()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when the user moves the collection
|
||||
* to a different collection or folder
|
||||
* @param payload - object containing the collection index dragged and the destination collection index
|
||||
*/
|
||||
const dropCollection = (payload: {
|
||||
collectionIndexDragged: string
|
||||
destinationCollectionIndex: string
|
||||
}) => {
|
||||
const { collectionIndexDragged, destinationCollectionIndex } = payload
|
||||
if (!collectionIndexDragged || !destinationCollectionIndex) return
|
||||
if (collectionIndexDragged === destinationCollectionIndex) return
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
moveRESTFolder(collectionIndexDragged, destinationCollectionIndex)
|
||||
draggingToRoot.value = false
|
||||
toast.success(`${t("collection.moved")}`)
|
||||
} else if (hasTeamWriteAccess.value) {
|
||||
// add the collection index to the loading array
|
||||
collectionMoveLoading.value.push(collectionIndexDragged)
|
||||
pipe(
|
||||
moveRESTTeamCollection(
|
||||
collectionIndexDragged,
|
||||
destinationCollectionIndex
|
||||
),
|
||||
TE.match(
|
||||
(err: GQLError<string>) => {
|
||||
toast.error(`${getErrorMessage(err)}`)
|
||||
collectionMoveLoading.value.splice(
|
||||
collectionMoveLoading.value.indexOf(collectionIndexDragged),
|
||||
1
|
||||
)
|
||||
},
|
||||
() => {
|
||||
toast.success(`${t("collection.moved")}`)
|
||||
// remove the collection index from the loading array
|
||||
collectionMoveLoading.value.splice(
|
||||
collectionMoveLoading.value.indexOf(collectionIndexDragged),
|
||||
1
|
||||
)
|
||||
}
|
||||
)
|
||||
)()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the collection is already in the root
|
||||
* @param id - path of the collection
|
||||
* @returns boolean - true if the collection is already in the root
|
||||
*/
|
||||
const isAlreadyInRoot = computed(() => {
|
||||
return (id: string) => {
|
||||
const indexPath = id.split("/").map((i) => parseInt(i))
|
||||
return indexPath.length === 1
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* This function is called when the user drops the collection
|
||||
* to the root
|
||||
* @param payload - object containing the collection index dragged
|
||||
*/
|
||||
const dropToRoot = ({ dataTransfer }: DragEvent) => {
|
||||
if (dataTransfer) {
|
||||
const collectionIndexDragged = dataTransfer.getData("collectionIndex")
|
||||
if (!collectionIndexDragged) return
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
// check if the collection is already in the root
|
||||
if (isAlreadyInRoot.value(collectionIndexDragged)) {
|
||||
toast.error(`${t("collection.invalid_root_move")}`)
|
||||
} else {
|
||||
moveRESTFolder(collectionIndexDragged, null)
|
||||
toast.success(`${t("collection.moved")}`)
|
||||
}
|
||||
|
||||
draggingToRoot.value = false
|
||||
} else if (hasTeamWriteAccess.value) {
|
||||
// add the collection index to the loading array
|
||||
collectionMoveLoading.value.push(collectionIndexDragged)
|
||||
|
||||
// destination collection index is null since we are moving to root
|
||||
pipe(
|
||||
moveRESTTeamCollection(collectionIndexDragged, null),
|
||||
TE.match(
|
||||
(err: GQLError<string>) => {
|
||||
collectionMoveLoading.value.splice(
|
||||
collectionMoveLoading.value.indexOf(collectionIndexDragged),
|
||||
1
|
||||
)
|
||||
toast.error(`${getErrorMessage(err)}`)
|
||||
},
|
||||
() => {
|
||||
// remove the collection index from the loading array
|
||||
collectionMoveLoading.value.splice(
|
||||
collectionMoveLoading.value.indexOf(collectionIndexDragged),
|
||||
1
|
||||
)
|
||||
toast.success(`${t("collection.moved")}`)
|
||||
}
|
||||
)
|
||||
)()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 destinationReq - path index of the destination request
|
||||
* @returns boolean - true if the request is being moved to the same parent
|
||||
*/
|
||||
const isSameSameParent = computed(
|
||||
() => (draggedReq: string, destinationReq: string) => {
|
||||
const draggedReqIndex = draggedReq.split("/").map((i) => parseInt(i))
|
||||
const destinationReqIndex = destinationReq
|
||||
.split("/")
|
||||
.map((i) => parseInt(i))
|
||||
|
||||
// length of 1 means the request is in the root
|
||||
if (draggedReqIndex.length === 1 && destinationReqIndex.length === 1) {
|
||||
return true
|
||||
} else if (
|
||||
draggedReqIndex[draggedReqIndex.length - 2] ===
|
||||
destinationReqIndex[destinationReqIndex.length - 2]
|
||||
) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* This function is called when the user updates the request order in a collection
|
||||
* @param payload - object containing the request index dragged and the destination request index
|
||||
* with the destination collection index
|
||||
*/
|
||||
const updateRequestOrder = (payload: {
|
||||
dragedRequestIndex: string
|
||||
destinationRequestIndex: string
|
||||
destinationCollectionIndex: string
|
||||
}) => {
|
||||
const {
|
||||
dragedRequestIndex,
|
||||
destinationRequestIndex,
|
||||
destinationCollectionIndex,
|
||||
} = payload
|
||||
|
||||
if (
|
||||
!dragedRequestIndex ||
|
||||
!destinationRequestIndex ||
|
||||
!destinationCollectionIndex
|
||||
)
|
||||
return
|
||||
|
||||
if (dragedRequestIndex === destinationRequestIndex) return
|
||||
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
if (!isSameSameParent.value(dragedRequestIndex, destinationRequestIndex)) {
|
||||
toast.error(`${t("collection.different_parent")}`)
|
||||
} else {
|
||||
updateRESTRequestOrder(
|
||||
pathToIndex.value(dragedRequestIndex),
|
||||
pathToIndex.value(destinationRequestIndex),
|
||||
destinationCollectionIndex
|
||||
)
|
||||
toast.success(`${t("request.order_changed")}`)
|
||||
}
|
||||
} else if (hasTeamWriteAccess.value) {
|
||||
// add the request index to the loading array
|
||||
requestMoveLoading.value.push(dragedRequestIndex)
|
||||
|
||||
pipe(
|
||||
updateOrderRESTTeamRequest(
|
||||
dragedRequestIndex,
|
||||
destinationRequestIndex,
|
||||
destinationCollectionIndex
|
||||
),
|
||||
TE.match(
|
||||
(err: GQLError<string>) => {
|
||||
toast.error(`${getErrorMessage(err)}`)
|
||||
requestMoveLoading.value.splice(
|
||||
requestMoveLoading.value.indexOf(dragedRequestIndex),
|
||||
1
|
||||
)
|
||||
},
|
||||
() => {
|
||||
toast.success(`${t("request.order_changed")}`)
|
||||
|
||||
// remove the request index from the loading array
|
||||
requestMoveLoading.value.splice(
|
||||
requestMoveLoading.value.indexOf(dragedRequestIndex),
|
||||
1
|
||||
)
|
||||
}
|
||||
)
|
||||
)()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when the user updates the collection or folder order
|
||||
* @param payload - object containing the collection index dragged and the destination collection index
|
||||
*/
|
||||
const updateCollectionOrder = (payload: {
|
||||
dragedCollectionIndex: string
|
||||
destinationCollectionIndex: string
|
||||
}) => {
|
||||
const { dragedCollectionIndex, destinationCollectionIndex } = payload
|
||||
if (!dragedCollectionIndex || !destinationCollectionIndex) return
|
||||
if (dragedCollectionIndex === destinationCollectionIndex) return
|
||||
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
if (
|
||||
!isSameSameParent.value(dragedCollectionIndex, destinationCollectionIndex)
|
||||
) {
|
||||
toast.error(`${t("collection.different_parent")}`)
|
||||
} else {
|
||||
updateRESTCollectionOrder(
|
||||
dragedCollectionIndex,
|
||||
destinationCollectionIndex
|
||||
)
|
||||
toast.success(`${t("collection.order_changed")}`)
|
||||
}
|
||||
} else if (hasTeamWriteAccess.value) {
|
||||
collectionMoveLoading.value.push(dragedCollectionIndex)
|
||||
pipe(
|
||||
updateOrderRESTTeamCollection(
|
||||
dragedCollectionIndex,
|
||||
destinationCollectionIndex
|
||||
),
|
||||
TE.match(
|
||||
(err: GQLError<string>) => {
|
||||
toast.error(`${getErrorMessage(err)}`)
|
||||
collectionMoveLoading.value.splice(
|
||||
collectionMoveLoading.value.indexOf(dragedCollectionIndex),
|
||||
1
|
||||
)
|
||||
},
|
||||
() => {
|
||||
toast.success(`${t("collection.order_changed")}`)
|
||||
collectionMoveLoading.value.splice(
|
||||
collectionMoveLoading.value.indexOf(dragedCollectionIndex),
|
||||
1
|
||||
)
|
||||
}
|
||||
)
|
||||
)()
|
||||
}
|
||||
}
|
||||
// Import - Export Collection functions
|
||||
/**
|
||||
* Export the whole my collection or specific team collection to JSON
|
||||
@@ -1525,28 +1860,38 @@ const resetSelectedData = () => {
|
||||
}
|
||||
|
||||
const getErrorMessage = (err: GQLError<string>) => {
|
||||
console.error(err)
|
||||
if (err.type === "network_error") {
|
||||
console.error(err)
|
||||
return t("error.network_error")
|
||||
} else {
|
||||
switch (err.error) {
|
||||
case "team_coll/short_title":
|
||||
console.error(err)
|
||||
return t("collection.name_length_insufficient")
|
||||
case "team/invalid_coll_id":
|
||||
console.error(err)
|
||||
return t("team.invalid_id")
|
||||
case "bug/team_coll/no_coll_id":
|
||||
case "team_req/invalid_target_id":
|
||||
return t("team.invalid_coll_id")
|
||||
case "team/not_required_role":
|
||||
console.error(err)
|
||||
return t("profile.no_permission")
|
||||
case "team_req/not_required_role":
|
||||
console.error(err)
|
||||
return t("profile.no_permission")
|
||||
case "Forbidden resource":
|
||||
console.error(err)
|
||||
return t("profile.no_permission")
|
||||
case "team_req/not_found":
|
||||
return t("team.no_request_found")
|
||||
case "bug/team_req/no_req_id":
|
||||
return t("team.no_request_found")
|
||||
case "team/collection_is_parent_coll":
|
||||
return t("team.parent_coll_move")
|
||||
case "team/target_and_destination_collection_are_same":
|
||||
return t("team.same_target_destination")
|
||||
case "team/target_collection_is_already_root_collection":
|
||||
return t("collection.invalid_root_move")
|
||||
case "team_req/requests_not_from_same_collection":
|
||||
return t("request.different_collection")
|
||||
case "team/team_collections_have_different_parents":
|
||||
return t("collection.different_parent")
|
||||
default:
|
||||
console.error(err)
|
||||
return t("error.something_went_wrong")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user