feat: added reordering and moving for collection (#2916)

This commit is contained in:
Nivedin
2023-02-24 19:09:07 +05:30
committed by GitHub
parent dcd441f15e
commit 4ca6e9ec3a
24 changed files with 1721 additions and 359 deletions

View File

@@ -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")
}
}