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

This commit is contained in:
Andrew Bastin
2023-03-15 11:32:08 +05:30
11 changed files with 448 additions and 195 deletions

View File

@@ -31,9 +31,9 @@
"@codemirror/state": "^6.1.0", "@codemirror/state": "^6.1.0",
"@codemirror/view": "^6.0.2", "@codemirror/view": "^6.0.2",
"@hoppscotch/codemirror-lang-graphql": "workspace:^0.2.0", "@hoppscotch/codemirror-lang-graphql": "workspace:^0.2.0",
"@hoppscotch/ui": "workspace:^0.0.1",
"@hoppscotch/data": "workspace:^0.4.4", "@hoppscotch/data": "workspace:^0.4.4",
"@hoppscotch/js-sandbox": "workspace:^2.1.0", "@hoppscotch/js-sandbox": "workspace:^2.1.0",
"@hoppscotch/ui": "workspace:^0.0.1",
"@hoppscotch/vue-toasted": "^0.1.0", "@hoppscotch/vue-toasted": "^0.1.0",
"@lezer/highlight": "^1.0.0", "@lezer/highlight": "^1.0.0",
"@sentry/tracing": "^7.13.0", "@sentry/tracing": "^7.13.0",
@@ -125,6 +125,7 @@
"@vue/eslint-config-typescript": "^11.0.1", "@vue/eslint-config-typescript": "^11.0.1",
"@vue/runtime-core": "^3.2.39", "@vue/runtime-core": "^3.2.39",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"dotenv": "^16.0.3",
"eslint": "^8.24.0", "eslint": "^8.24.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.5.1", "eslint-plugin-vue": "^9.5.1",

View File

@@ -89,7 +89,6 @@ declare module '@vue/runtime-core' {
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner'] HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab'] HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab']
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs'] HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
HttpAuthorization: typeof import('./components/http/Authorization.vue')['default'] HttpAuthorization: typeof import('./components/http/Authorization.vue')['default']
HttpBody: typeof import('./components/http/Body.vue')['default'] HttpBody: typeof import('./components/http/Body.vue')['default']
HttpBodyParameters: typeof import('./components/http/BodyParameters.vue')['default'] HttpBodyParameters: typeof import('./components/http/BodyParameters.vue')['default']

View File

@@ -4,7 +4,7 @@
class="h-1 w-full transition" class="h-1 w-full transition"
:class="[ :class="[
{ {
'bg-accentDark': ordering && notSameDestination, 'bg-accentDark': isReorderable,
}, },
]" ]"
@drop="orderUpdateCollectionEvent" @drop="orderUpdateCollectionEvent"
@@ -20,18 +20,26 @@
}" }"
></div> ></div>
<div <div
class="flex items-stretch group relative z-3" class="flex items-stretch group relative z-3 cursor-pointer pointer-events-auto"
:draggable="!hasNoTeamAccess" :draggable="!hasNoTeamAccess"
@dragstart="dragStart" @dragstart="dragStart"
@drop="dropEvent" @drop="handelDrop($event)"
@dragover="dragging = true" @dragover="handleDragOver($event)"
@dragleave="dragging = false" @dragleave="resetDragState"
@dragend="resetDragState" @dragend="
() => {
resetDragState()
dropItemID = ''
}
"
@contextmenu.prevent="options?.tippy.show()" @contextmenu.prevent="options?.tippy.show()"
> >
<span <div
class="flex items-center justify-center px-4 cursor-pointer" class="flex items-center justify-center flex-1 min-w-0"
@click="emit('toggle-children')" @click="emit('toggle-children')"
>
<span
class="flex items-center justify-center px-4 pointer-events-none"
> >
<HoppSmartSpinner v-if="isCollLoading" /> <HoppSmartSpinner v-if="isCollLoading" />
<component <component
@@ -42,13 +50,13 @@
/> />
</span> </span>
<span <span
class="flex flex-1 min-w-0 py-2 pr-2 transition cursor-pointer group-hover:text-secondaryDark" class="flex flex-1 min-w-0 py-2 pr-2 transition pointer-events-none group-hover:text-secondaryDark"
@click="emit('toggle-children')"
> >
<span class="truncate" :class="{ 'text-accent': isSelected }"> <span class="truncate" :class="{ 'text-accent': isSelected }">
{{ collectionName }} {{ collectionName }}
</span> </span>
</span> </span>
</div>
<div v-if="!hasNoTeamAccess" class="flex"> <div v-if="!hasNoTeamAccess" class="flex">
<HoppButtonSecondary <HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
@@ -175,6 +183,11 @@ import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
import { useI18n } from "@composables/i18n" import { useI18n } from "@composables/i18n"
import { TippyComponent } from "vue-tippy" import { TippyComponent } from "vue-tippy"
import { TeamCollection } from "~/helpers/teams/TeamCollection" import { TeamCollection } from "~/helpers/teams/TeamCollection"
import {
changeCurrentReorderStatus,
currentReorderingStatus$,
} from "~/newstore/reordering"
import { useReadonlyStream } from "~/composables/stream"
type CollectionType = "my-collections" | "team-collections" type CollectionType = "my-collections" | "team-collections"
type FolderType = "collection" | "folder" type FolderType = "collection" | "folder"
@@ -187,6 +200,11 @@ const props = defineProps({
default: "", default: "",
required: true, required: true,
}, },
parentID: {
type: String as PropType<string | null>,
default: null,
required: true,
},
data: { data: {
type: Object as PropType<HoppCollection<HoppRESTRequest> | TeamCollection>, type: Object as PropType<HoppCollection<HoppRESTRequest> | TeamCollection>,
default: () => ({}), default: () => ({}),
@@ -258,6 +276,12 @@ const dragging = ref(false)
const ordering = ref(false) const ordering = ref(false)
const dropItemID = ref("") const dropItemID = ref("")
const currentReorderingStatus = useReadonlyStream(currentReorderingStatus$, {
type: "collection",
id: "",
parentID: "",
})
// Used to determine if the collection is being dragged to a different destination // Used to determine if the collection is being dragged to a different destination
// This is used to make the highlight effect work // This is used to make the highlight effect work
watch( watch(
@@ -293,11 +317,52 @@ watch(
} }
) )
const isRequestDragging = computed(() => {
return currentReorderingStatus.value.type === "request"
})
const isSameParent = computed(() => {
return currentReorderingStatus.value.parentID === props.parentID
})
const isReorderable = computed(() => {
return (
ordering.value &&
notSameDestination.value &&
!isRequestDragging.value &&
isSameParent.value
)
})
const dragStart = ({ dataTransfer }: DragEvent) => { const dragStart = ({ dataTransfer }: DragEvent) => {
if (dataTransfer) { if (dataTransfer) {
emit("drag-event", dataTransfer) emit("drag-event", dataTransfer)
dropItemID.value = dataTransfer.getData("collectionIndex") dropItemID.value = dataTransfer.getData("collectionIndex")
dragging.value = !dragging.value dragging.value = !dragging.value
changeCurrentReorderStatus({
type: "collection",
id: props.id,
parentID: props.parentID,
})
}
}
// Trigger the re-ordering event when a collection is dragged over another collection's top section
const handleDragOver = (e: DragEvent) => {
dragging.value = true
if (e.offsetY < 10 && notSameDestination.value) {
ordering.value = true
dragging.value = false
} else {
ordering.value = false
}
}
const handelDrop = (e: DragEvent) => {
if (ordering.value) {
orderUpdateCollectionEvent(e)
} else {
dropEvent(e)
} }
} }
@@ -305,8 +370,7 @@ const dropEvent = (e: DragEvent) => {
if (e.dataTransfer) { if (e.dataTransfer) {
e.stopPropagation() e.stopPropagation()
emit("drop-event", e.dataTransfer) emit("drop-event", e.dataTransfer)
dragging.value = !dragging.value resetDragState()
dropItemID.value = ""
} }
} }
@@ -314,8 +378,7 @@ const orderUpdateCollectionEvent = (e: DragEvent) => {
if (e.dataTransfer) { if (e.dataTransfer) {
e.stopPropagation() e.stopPropagation()
emit("update-collection-order", e.dataTransfer) emit("update-collection-order", e.dataTransfer)
ordering.value = !ordering.value resetDragState()
dropItemID.value = ""
} }
} }
@@ -334,6 +397,5 @@ const isCollLoading = computed(() => {
const resetDragState = () => { const resetDragState = () => {
dragging.value = false dragging.value = false
ordering.value = false ordering.value = false
dropItemID.value = ""
} }
</script> </script>

View File

@@ -39,6 +39,7 @@
<CollectionsCollection <CollectionsCollection
v-if="node.data.type === 'collections'" v-if="node.data.type === 'collections'"
:id="node.id" :id="node.id"
:parent-i-d="node.data.data.parentIndex"
:data="node.data.data.data" :data="node.data.data.data"
:collections-type="collectionsType.type" :collections-type="collectionsType.type"
:is-open="isOpen" :is-open="isOpen"
@@ -94,6 +95,7 @@
<CollectionsCollection <CollectionsCollection
v-if="node.data.type === 'folders'" v-if="node.data.type === 'folders'"
:id="node.id" :id="node.id"
:parent-i-d="node.data.data.parentIndex"
:data="node.data.data.data" :data="node.data.data.data"
:collections-type="collectionsType.type" :collections-type="collectionsType.type"
:is-open="isOpen" :is-open="isOpen"
@@ -149,6 +151,8 @@
<CollectionsRequest <CollectionsRequest
v-if="node.data.type === 'requests'" v-if="node.data.type === 'requests'"
:request="node.data.data.data" :request="node.data.data.data"
:request-i-d="node.id"
:parent-i-d="node.data.data.parentIndex"
:collections-type="collectionsType.type" :collections-type="collectionsType.type"
:save-request="saveRequest" :save-request="saveRequest"
:is-active=" :is-active="

View File

@@ -4,27 +4,31 @@
class="h-1" class="h-1"
:class="[ :class="[
{ {
'bg-accentDark': ordering, 'bg-accentDark': isReorderable,
}, },
]" ]"
@drop="dropEvent" @drop="dropEvent"
@dragover.prevent="ordering = true" @dragover.prevent="ordering = true"
@dragleave="ordering = false" @dragleave="resetDragState"
@dragend="ordering = false" @dragend="resetDragState"
></div> ></div>
<div <div
class="flex items-stretch group" class="flex items-stretch group"
:draggable="!hasNoTeamAccess" :draggable="!hasNoTeamAccess"
@drop="dropEvent"
@dragstart="dragStart" @dragstart="dragStart"
@dragover.prevent="dragging = true" @dragover="handleDragOver($event)"
@dragleave="dragging = false" @dragleave="resetDragState"
@dragend="dragging = false" @dragend="resetDragState"
@contextmenu.prevent="options?.tippy.show()" @contextmenu.prevent="options?.tippy.show()"
> >
<span <div
class="flex items-center justify-center w-16 px-2 truncate cursor-pointer" class="flex items-center justify-center flex-1 min-w-0 cursor-pointer pointer-events-auto"
:class="requestLabelColor"
@click="selectRequest()" @click="selectRequest()"
>
<span
class="flex items-center justify-center w-16 px-2 truncate pointer-events-none"
:class="requestLabelColor"
> >
<component <component
:is="IconCheckCircle" :is="IconCheckCircle"
@@ -38,8 +42,7 @@
</span> </span>
</span> </span>
<span <span
class="flex items-center flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark" class="flex items-center flex-1 min-w-0 py-2 pr-2 pointer-events-none transition group-hover:text-secondaryDark"
@click="selectRequest()"
> >
<span class="truncate" :class="{ 'text-accent': isSelected }"> <span class="truncate" :class="{ 'text-accent': isSelected }">
{{ request.name }} {{ request.name }}
@@ -59,6 +62,7 @@
></span> ></span>
</span> </span>
</span> </span>
</div>
<div v-if="!hasNoTeamAccess" class="flex"> <div v-if="!hasNoTeamAccess" class="flex">
<HoppButtonSecondary <HoppButtonSecondary
v-if="!saveRequest" v-if="!saveRequest"
@@ -151,6 +155,11 @@ import { TippyComponent } from "vue-tippy"
import { pipe } from "fp-ts/function" import { pipe } from "fp-ts/function"
import * as RR from "fp-ts/ReadonlyRecord" import * as RR from "fp-ts/ReadonlyRecord"
import * as O from "fp-ts/Option" import * as O from "fp-ts/Option"
import {
changeCurrentReorderStatus,
currentReorderingStatus$,
} from "~/newstore/reordering"
import { useReadonlyStream } from "~/composables/stream"
type CollectionType = "my-collections" | "team-collections" type CollectionType = "my-collections" | "team-collections"
@@ -167,6 +176,11 @@ const props = defineProps({
default: "", default: "",
required: false, required: false,
}, },
parentID: {
type: String as PropType<string | null>,
default: null,
required: true,
},
collectionsType: { collectionsType: {
type: String as PropType<CollectionType>, type: String as PropType<CollectionType>,
default: "my-collections", default: "my-collections",
@@ -222,6 +236,12 @@ const duplicate = ref<HTMLButtonElement | null>(null)
const dragging = ref(false) const dragging = ref(false)
const ordering = ref(false) const ordering = ref(false)
const currentReorderingStatus = useReadonlyStream(currentReorderingStatus$, {
type: "collection",
id: "",
parentID: "",
})
const requestMethodLabels = { const requestMethodLabels = {
get: "text-green-500", get: "text-green-500",
post: "text-yellow-500", post: "text-yellow-500",
@@ -255,13 +275,41 @@ const dragStart = ({ dataTransfer }: DragEvent) => {
if (dataTransfer) { if (dataTransfer) {
emit("drag-request", dataTransfer) emit("drag-request", dataTransfer)
dragging.value = !dragging.value dragging.value = !dragging.value
changeCurrentReorderStatus({
type: "request",
id: props.requestID,
parentID: props.parentID,
})
}
}
const isCollectionDragging = computed(() => {
return currentReorderingStatus.value.type === "collection"
})
const isSameParent = computed(() => {
return currentReorderingStatus.value.parentID === props.parentID
})
const isReorderable = computed(() => {
return ordering.value && !isCollectionDragging.value && isSameParent.value
})
// Trigger the re-ordering event when a request is dragged over another request's top section
const handleDragOver = (e: DragEvent) => {
dragging.value = true
if (e.offsetY < 10) {
ordering.value = true
dragging.value = false
} else {
ordering.value = false
} }
} }
const dropEvent = (e: DragEvent) => { const dropEvent = (e: DragEvent) => {
if (e.dataTransfer) { if (e.dataTransfer) {
e.stopPropagation() e.stopPropagation()
ordering.value = !ordering.value resetDragState()
emit("update-request-order", e.dataTransfer) emit("update-request-order", e.dataTransfer)
} }
} }
@@ -273,4 +321,9 @@ const isRequestLoading = computed(() => {
return false return false
} }
}) })
const resetDragState = () => {
dragging.value = false
ordering.value = false
}
</script> </script>

View File

@@ -53,6 +53,7 @@
<CollectionsCollection <CollectionsCollection
v-if="node.data.type === 'collections'" v-if="node.data.type === 'collections'"
:id="node.data.data.data.id" :id="node.data.data.data.id"
:parent-i-d="node.data.data.parentIndex"
:data="node.data.data.data" :data="node.data.data.data"
:collections-type="collectionsType.type" :collections-type="collectionsType.type"
:is-open="isOpen" :is-open="isOpen"
@@ -114,6 +115,7 @@
<CollectionsCollection <CollectionsCollection
v-if="node.data.type === 'folders'" v-if="node.data.type === 'folders'"
:id="node.data.data.data.id" :id="node.data.data.data.id"
:parent-i-d="node.data.data.parentIndex"
:data="node.data.data.data" :data="node.data.data.data"
:collections-type="collectionsType.type" :collections-type="collectionsType.type"
:is-open="isOpen" :is-open="isOpen"
@@ -178,6 +180,7 @@
v-if="node.data.type === 'requests'" v-if="node.data.type === 'requests'"
:request="node.data.data.data.request" :request="node.data.data.data.request"
:request-i-d="node.data.data.data.id" :request-i-d="node.data.data.data.id"
:parent-i-d="node.data.data.parentIndex"
:collections-type="collectionsType.type" :collections-type="collectionsType.type"
:duplicate-loading="duplicateLoading" :duplicate-loading="duplicateLoading"
:is-active="isActiveRequest(node.data.data.data.id)" :is-active="isActiveRequest(node.data.data.data.id)"

View File

@@ -209,7 +209,7 @@ import {
setRESTRequest, setRESTRequest,
setRESTSaveContext, setRESTSaveContext,
} from "~/newstore/RESTSession" } from "~/newstore/RESTSession"
import { cloneDeep } from "lodash-es" import { cloneDeep, isEqual } from "lodash-es"
import { GQLError } from "~/helpers/backend/GQLClient" import { GQLError } from "~/helpers/backend/GQLClient"
import { import {
createNewRootCollection, createNewRootCollection,
@@ -470,8 +470,7 @@ const filteredCollections = computed(() => {
return filteredCollections return filteredCollections
}) })
const isSelected = computed(() => { const isSelected = ({
return ({
collectionIndex, collectionIndex,
folderPath, folderPath,
requestIndex, requestIndex,
@@ -525,7 +524,6 @@ const isSelected = computed(() => {
) )
} }
} }
})
const modalLoadingState = ref(false) const modalLoadingState = ref(false)
const exportLoading = ref(false) const exportLoading = ref(false)
@@ -1023,7 +1021,7 @@ const onRemoveCollection = () => {
if (collectionIndex === null) return if (collectionIndex === null) return
if ( if (
isSelected.value({ isSelected({
collectionIndex, collectionIndex,
}) })
) { ) {
@@ -1040,7 +1038,7 @@ const onRemoveCollection = () => {
if (!collectionID) return if (!collectionID) return
if ( if (
isSelected.value({ isSelected({
collectionID, collectionID,
}) })
) { ) {
@@ -1067,7 +1065,7 @@ const onRemoveFolder = () => {
if (!folderPath) return if (!folderPath) return
if ( if (
isSelected.value({ isSelected({
folderPath, folderPath,
}) })
) { ) {
@@ -1084,7 +1082,7 @@ const onRemoveFolder = () => {
if (!collectionID) return if (!collectionID) return
if ( if (
isSelected.value({ isSelected({
collectionID, collectionID,
}) })
) { ) {
@@ -1118,7 +1116,7 @@ const onRemoveRequest = () => {
if (folderPath === null || requestIndex === null) return if (folderPath === null || requestIndex === null) return
if ( if (
isSelected.value({ isSelected({
folderPath, folderPath,
requestIndex, requestIndex,
}) })
@@ -1136,7 +1134,7 @@ const onRemoveRequest = () => {
if (!requestID) return if (!requestID) return
if ( if (
isSelected.value({ isSelected({
requestID, requestID,
}) })
) { ) {
@@ -1342,12 +1340,10 @@ const discardRequestChange = () => {
* @param path The path of the request * @param path The path of the request
* @returns The index of the request * @returns The index of the request
*/ */
const pathToIndex = computed(() => { const pathToLastIndex = (path: string) => {
return (path: string) => {
const pathArr = path.split("/") const pathArr = path.split("/")
return parseInt(pathArr[pathArr.length - 1]) return parseInt(pathArr[pathArr.length - 1])
} }
})
/** /**
* This function is called when the user drops the request inside a collection * This function is called when the user drops the request inside a collection
@@ -1363,7 +1359,7 @@ const dropRequest = (payload: {
if (collectionsType.value.type === "my-collections" && folderPath) { if (collectionsType.value.type === "my-collections" && folderPath) {
moveRESTRequest( moveRESTRequest(
folderPath, folderPath,
pathToIndex.value(requestIndex), pathToLastIndex(requestIndex),
destinationCollectionIndex destinationCollectionIndex
) )
toast.success(`${t("request.moved")}`) toast.success(`${t("request.moved")}`)
@@ -1395,6 +1391,43 @@ const dropRequest = (payload: {
} }
} }
/**
* @param path The path of the collection or request
* @returns The index of the collection or request
*/
const pathToIndex = (path: string) => {
const pathArr = path.split("/")
return pathArr
}
/**
* Used to check if the collection exist as the parent of the childrens
* @param collectionIndexDragged The index of the collection dragged
* @param destinationCollectionIndex The index of the destination collection
* @returns True if the collection exist as the parent of the childrens
*/
const checkIfCollectionIsAParentOfTheChildren = (
collectionIndexDragged: string,
destinationCollectionIndex: string
) => {
const collectionDraggedPath = pathToIndex(collectionIndexDragged)
const destinationCollectionPath = pathToIndex(destinationCollectionIndex)
if (collectionDraggedPath.length < destinationCollectionPath.length) {
const slicedDestinationCollectionPath = destinationCollectionPath.slice(
0,
collectionDraggedPath.length
)
if (isEqual(slicedDestinationCollectionPath, collectionDraggedPath)) {
return true
} else {
return false
}
}
return false
}
/** /**
* This function is called when the user moves the collection * This function is called when the user moves the collection
* to a different collection or folder * to a different collection or folder
@@ -1408,6 +1441,15 @@ const dropCollection = (payload: {
if (!collectionIndexDragged || !destinationCollectionIndex) return if (!collectionIndexDragged || !destinationCollectionIndex) return
if (collectionIndexDragged === destinationCollectionIndex) return if (collectionIndexDragged === destinationCollectionIndex) return
if (collectionsType.value.type === "my-collections") { if (collectionsType.value.type === "my-collections") {
if (
checkIfCollectionIsAParentOfTheChildren(
collectionIndexDragged,
destinationCollectionIndex
)
) {
toast.error(`${t("team.parent_coll_move")}`)
return
}
moveRESTFolder(collectionIndexDragged, destinationCollectionIndex) moveRESTFolder(collectionIndexDragged, destinationCollectionIndex)
draggingToRoot.value = false draggingToRoot.value = false
toast.success(`${t("collection.moved")}`) toast.success(`${t("collection.moved")}`)
@@ -1445,12 +1487,10 @@ const dropCollection = (payload: {
* @param id - path of the collection * @param id - path of the collection
* @returns boolean - true if the collection is already in the root * @returns boolean - true if the collection is already in the root
*/ */
const isAlreadyInRoot = computed(() => { const isAlreadyInRoot = (id: string) => {
return (id: string) => { const indexPath = pathToIndex(id)
const indexPath = id.split("/").map((i) => parseInt(i))
return indexPath.length === 1 return indexPath.length === 1
} }
})
/** /**
* This function is called when the user drops the collection * This function is called when the user drops the collection
@@ -1463,7 +1503,7 @@ const dropToRoot = ({ dataTransfer }: DragEvent) => {
if (!collectionIndexDragged) return if (!collectionIndexDragged) return
if (collectionsType.value.type === "my-collections") { if (collectionsType.value.type === "my-collections") {
// check if the collection is already in the root // check if the collection is already in the root
if (isAlreadyInRoot.value(collectionIndexDragged)) { if (isAlreadyInRoot(collectionIndexDragged)) {
toast.error(`${t("collection.invalid_root_move")}`) toast.error(`${t("collection.invalid_root_move")}`)
} else { } else {
moveRESTFolder(collectionIndexDragged, null) moveRESTFolder(collectionIndexDragged, null)
@@ -1506,26 +1546,25 @@ const dropToRoot = ({ dataTransfer }: DragEvent) => {
* @param destinationReq - path index of the destination request * @param destinationReq - path index of the destination request
* @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 = computed( const isSameSameParent = (draggedItem: string, destinationItem: string) => {
() => (draggedReq: string, destinationReq: string) => { const draggedItemIndex = pathToIndex(draggedItem)
const draggedReqIndex = draggedReq.split("/").map((i) => parseInt(i)) const destinationItemIndex = pathToIndex(destinationItem)
const destinationReqIndex = destinationReq
.split("/")
.map((i) => parseInt(i))
// length of 1 means the request is in the root // length of 1 means the request is in the root
if (draggedReqIndex.length === 1 && destinationReqIndex.length === 1) { if (draggedItemIndex.length === 1 && destinationItemIndex.length === 1) {
return true return true
} else if ( } else if (draggedItemIndex.length === destinationItemIndex.length) {
draggedReqIndex[draggedReqIndex.length - 2] === const dragedItemParent = draggedItemIndex.slice(0, -1)
destinationReqIndex[destinationReqIndex.length - 2] const destinationItemParent = destinationItemIndex.slice(0, -1)
) { if (isEqual(dragedItemParent, destinationItemParent)) {
return true return true
} else { } else {
return false return false
} }
} else {
return false
}
} }
)
/** /**
* This function is called when the user updates the request order in a collection * This function is called when the user updates the request order in a collection
@@ -1553,12 +1592,12 @@ const updateRequestOrder = (payload: {
if (dragedRequestIndex === destinationRequestIndex) return if (dragedRequestIndex === destinationRequestIndex) return
if (collectionsType.value.type === "my-collections") { if (collectionsType.value.type === "my-collections") {
if (!isSameSameParent.value(dragedRequestIndex, destinationRequestIndex)) { if (!isSameSameParent(dragedRequestIndex, destinationRequestIndex)) {
toast.error(`${t("collection.different_parent")}`) toast.error(`${t("collection.different_parent")}`)
} else { } else {
updateRESTRequestOrder( updateRESTRequestOrder(
pathToIndex.value(dragedRequestIndex), pathToLastIndex(dragedRequestIndex),
pathToIndex.value(destinationRequestIndex), pathToLastIndex(destinationRequestIndex),
destinationCollectionIndex destinationCollectionIndex
) )
toast.success(`${t("request.order_changed")}`) toast.success(`${t("request.order_changed")}`)
@@ -1608,9 +1647,7 @@ const updateCollectionOrder = (payload: {
if (dragedCollectionIndex === destinationCollectionIndex) return if (dragedCollectionIndex === destinationCollectionIndex) return
if (collectionsType.value.type === "my-collections") { if (collectionsType.value.type === "my-collections") {
if ( if (!isSameSameParent(dragedCollectionIndex, destinationCollectionIndex)) {
!isSameSameParent.value(dragedCollectionIndex, destinationCollectionIndex)
) {
toast.error(`${t("collection.different_parent")}`) toast.error(`${t("collection.different_parent")}`)
} else { } else {
updateRESTCollectionOrder( updateRESTCollectionOrder(

View File

@@ -547,6 +547,15 @@ export default class NewTeamCollectionAdapter {
) )
} }
private reorderItems = (array: unknown[], from: number, to: number) => {
const item = array.splice(from, 1)[0]
if (from < to) {
array.splice(to - 1, 0, item)
} else {
array.splice(to, 0, item)
}
}
public updateRequestOrder( public updateRequestOrder(
dragedRequestID: string, dragedRequestID: string,
destinationRequestID: string, destinationRequestID: string,
@@ -570,10 +579,7 @@ export default class NewTeamCollectionAdapter {
if (requestIndex === -1) return if (requestIndex === -1) return
const request = collection.requests[requestIndex] this.reorderItems(collection.requests, requestIndex, destinationIndex)
collection.requests.splice(requestIndex, 1)
collection.requests.splice(destinationIndex, 0, request)
this.collections$.next(tree) this.collections$.next(tree)
} }
@@ -600,10 +606,7 @@ export default class NewTeamCollectionAdapter {
// If the collection index is not found, don't update // If the collection index is not found, don't update
if (collectionIndex === -1) return if (collectionIndex === -1) return
const collection = coll.children[collectionIndex] this.reorderItems(coll.children, collectionIndex, destinationIndex)
coll.children.splice(collectionIndex, 1)
coll.children.splice(destinationIndex, 0, collection)
} 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)
@@ -615,10 +618,7 @@ export default class NewTeamCollectionAdapter {
// If the collection index is not found, don't update // If the collection index is not found, don't update
if (collectionIndex === -1) return if (collectionIndex === -1) return
const collection = tree[collectionIndex] this.reorderItems(tree, collectionIndex, destinationIndex)
tree.splice(collectionIndex, 1)
tree.splice(destinationIndex, 0, collection)
} }
this.collections$.next(tree) this.collections$.next(tree)

View File

@@ -31,7 +31,7 @@ const defaultGraphqlCollectionState = {
type RESTCollectionStoreType = typeof defaultRESTCollectionState type RESTCollectionStoreType = typeof defaultRESTCollectionState
type GraphqlCollectionStoreType = typeof defaultGraphqlCollectionState type GraphqlCollectionStoreType = typeof defaultGraphqlCollectionState
function navigateToFolderWithIndexPath( export function navigateToFolderWithIndexPath(
collections: HoppCollection<HoppRESTRequest | HoppGQLRequest>[], collections: HoppCollection<HoppRESTRequest | HoppGQLRequest>[],
indexPaths: number[] indexPaths: number[]
) { ) {
@@ -45,6 +45,15 @@ function navigateToFolderWithIndexPath(
return target !== undefined ? target : null return target !== undefined ? target : null
} }
function reorderItems(array: unknown[], from: number, to: number) {
const item = array.splice(from, 1)[0]
if (from < to) {
array.splice(to - 1, 0, item)
} else {
array.splice(to, 0, item)
}
}
const restCollectionDispatchers = defineDispatchers({ const restCollectionDispatchers = defineDispatchers({
setCollections( setCollections(
_: RESTCollectionStoreType, _: RESTCollectionStoreType,
@@ -88,12 +97,15 @@ const restCollectionDispatchers = defineDispatchers({
{ state }: RESTCollectionStoreType, { state }: RESTCollectionStoreType,
{ {
collectionIndex, collectionIndex,
collection, partialCollection,
}: { collectionIndex: number; collection: HoppCollection<any> } }: {
collectionIndex: number
partialCollection: Partial<HoppCollection<any>>
}
) { ) {
return { return {
state: state.map((col, index) => state: state.map((col, index) =>
index === collectionIndex ? collection : col index === collectionIndex ? { ...col, ...partialCollection } : col
), ),
} }
}, },
@@ -295,18 +307,14 @@ const restCollectionDispatchers = defineDispatchers({
) )
if (containingFolder === null) { if (containingFolder === null) {
const [removed] = newState.splice(folderIndex, 1) reorderItems(newState, folderIndex, destinationFolderIndex)
newState.splice(destinationFolderIndex, 0, removed)
return { return {
state: newState, state: newState,
} }
} }
const [removed] = containingFolder.folders.splice(folderIndex, 1) reorderItems(containingFolder.folders, folderIndex, destinationFolderIndex)
containingFolder.folders.splice(destinationFolderIndex, 0, removed)
return { return {
state: newState, state: newState,
@@ -480,9 +488,7 @@ const restCollectionDispatchers = defineDispatchers({
return {} return {}
} }
const [removed] = targetLocation.requests.splice(requestIndex, 1) reorderItems(targetLocation.requests, requestIndex, destinationRequestIndex)
targetLocation.requests.splice(destinationRequestIndex, 0, removed)
return { return {
state: newState, state: newState,
@@ -821,13 +827,13 @@ export function getRESTCollection(collectionIndex: number) {
export function editRESTCollection( export function editRESTCollection(
collectionIndex: number, collectionIndex: number,
collection: HoppCollection<HoppRESTRequest> partialCollection: Partial<HoppCollection<HoppRESTRequest>>
) { ) {
restCollectionStore.dispatch({ restCollectionStore.dispatch({
dispatcher: "editCollection", dispatcher: "editCollection",
payload: { payload: {
collectionIndex, collectionIndex,
collection, partialCollection: partialCollection,
}, },
}) })
} }

View File

@@ -0,0 +1,46 @@
import { distinctUntilChanged, pluck } from "rxjs"
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
type ReorderingItem =
| { type: "collection"; id: string; parentID: string | null }
| { type: "request"; id: string; parentID: string | null }
type CurrentReorderingState = {
currentReorderingItem: ReorderingItem
}
const initialState: CurrentReorderingState = {
currentReorderingItem: {
type: "collection",
id: "",
parentID: "",
},
}
const dispatchers = defineDispatchers({
changeCurrentReorderStatus(
_,
{ reorderItem }: { reorderItem: ReorderingItem }
) {
return {
currentReorderingItem: reorderItem,
}
},
})
export const currentReorderStore = new DispatchingStore(
initialState,
dispatchers
)
export const currentReorderingStatus$ = currentReorderStore.subject$.pipe(
pluck("currentReorderingItem"),
distinctUntilChanged()
)
export function changeCurrentReorderStatus(reorderItem: ReorderingItem) {
currentReorderStore.dispatch({
dispatcher: "changeCurrentReorderStatus",
payload: { reorderItem },
})
}

52
pnpm-lock.yaml generated
View File

@@ -84,6 +84,7 @@ importers:
eslint-config-prettier: ^8.5.0 eslint-config-prettier: ^8.5.0
eslint-plugin-prettier: ^4.2.1 eslint-plugin-prettier: ^4.2.1
express: ^4.17.1 express: ^4.17.1
express-session: ^1.17.3
fp-ts: ^2.13.1 fp-ts: ^2.13.1
graphql: ^15.5.0 graphql: ^15.5.0
graphql-query-complexity: ^0.12.0 graphql-query-complexity: ^0.12.0
@@ -130,6 +131,7 @@ importers:
cookie: 0.5.0 cookie: 0.5.0
cookie-parser: 1.4.6 cookie-parser: 1.4.6
express: 4.18.2 express: 4.18.2
express-session: 1.17.3
fp-ts: 2.13.1 fp-ts: 2.13.1
graphql: 15.8.0 graphql: 15.8.0
graphql-query-complexity: 0.12.0_graphql@15.8.0 graphql-query-complexity: 0.12.0_graphql@15.8.0
@@ -296,6 +298,7 @@ importers:
axios: ^0.21.4 axios: ^0.21.4
buffer: ^6.0.3 buffer: ^6.0.3
cross-env: ^7.0.3 cross-env: ^7.0.3
dotenv: ^16.0.3
eslint: ^8.24.0 eslint: ^8.24.0
eslint-plugin-prettier: ^4.2.1 eslint-plugin-prettier: ^4.2.1
eslint-plugin-vue: ^9.5.1 eslint-plugin-vue: ^9.5.1
@@ -471,6 +474,7 @@ importers:
'@vue/eslint-config-typescript': 11.0.1_kpxf5iryryrlim2ejhkirkiuey '@vue/eslint-config-typescript': 11.0.1_kpxf5iryryrlim2ejhkirkiuey
'@vue/runtime-core': 3.2.39 '@vue/runtime-core': 3.2.39
cross-env: 7.0.3 cross-env: 7.0.3
dotenv: 16.0.3
eslint: 8.24.0 eslint: 8.24.0
eslint-plugin-prettier: 4.2.1_eslint@8.24.0 eslint-plugin-prettier: 4.2.1_eslint@8.24.0
eslint-plugin-vue: 9.5.1_eslint@8.24.0 eslint-plugin-vue: 9.5.1_eslint@8.24.0
@@ -4647,7 +4651,7 @@ packages:
'@types/jsonwebtoken': 8.5.9 '@types/jsonwebtoken': 8.5.9
chalk: 4.1.2 chalk: 4.1.2
debug: 4.3.4 debug: 4.3.4
dotenv: 16.0.1 dotenv: 16.0.3
graphql: 15.8.0 graphql: 15.8.0
graphql-request: 4.3.0_graphql@15.8.0 graphql-request: 4.3.0_graphql@15.8.0
http-proxy-agent: 5.0.0 http-proxy-agent: 5.0.0
@@ -4681,7 +4685,7 @@ packages:
'@types/jsonwebtoken': 9.0.1 '@types/jsonwebtoken': 9.0.1
chalk: 4.1.2 chalk: 4.1.2
debug: 4.3.4 debug: 4.3.4
dotenv: 16.0.1 dotenv: 16.0.3
graphql: 16.6.0 graphql: 16.6.0
graphql-request: 5.1.0_graphql@16.6.0 graphql-request: 5.1.0_graphql@16.6.0
http-proxy-agent: 5.0.0 http-proxy-agent: 5.0.0
@@ -10063,13 +10067,18 @@ packages:
dev: false dev: false
/cookie-signature/1.0.6: /cookie-signature/1.0.6:
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} resolution: {integrity: sha1-4wOogrNCzD7oylE6eZmXNNqzriw=}
/cookie/0.4.1: /cookie/0.4.1:
resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==} resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
dev: false dev: false
/cookie/0.4.2:
resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
engines: {node: '>= 0.6'}
dev: false
/cookie/0.5.0: /cookie/0.5.0:
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@@ -10603,8 +10612,8 @@ packages:
is-obj: 2.0.0 is-obj: 2.0.0
dev: true dev: true
/dotenv/16.0.1: /dotenv/16.0.3:
resolution: {integrity: sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==} resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
dev: true dev: true
@@ -12062,6 +12071,22 @@ packages:
raw-body: 2.5.1 raw-body: 2.5.1
dev: false dev: false
/express-session/1.17.3:
resolution: {integrity: sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==}
engines: {node: '>= 0.8.0'}
dependencies:
cookie: 0.4.2
cookie-signature: 1.0.6
debug: 2.6.9
depd: 2.0.0
on-headers: 1.0.2
parseurl: 1.3.3
safe-buffer: 5.2.1
uid-safe: 2.1.5
transitivePeerDependencies:
- supports-color
dev: false
/express/4.18.2: /express/4.18.2:
resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==}
engines: {node: '>= 0.10.0'} engines: {node: '>= 0.10.0'}
@@ -16028,6 +16053,11 @@ packages:
dependencies: dependencies:
ee-first: 1.1.1 ee-first: 1.1.1
/on-headers/1.0.2:
resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
engines: {node: '>= 0.8'}
dev: false
/once/1.4.0: /once/1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies: dependencies:
@@ -16844,6 +16874,11 @@ packages:
resolution: {integrity: sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==} resolution: {integrity: sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==}
dev: false dev: false
/random-bytes/1.0.0:
resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==}
engines: {node: '>= 0.8'}
dev: false
/randombytes/2.1.0: /randombytes/2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
dependencies: dependencies:
@@ -18834,6 +18869,13 @@ packages:
resolution: {integrity: sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==} resolution: {integrity: sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==}
dev: true dev: true
/uid-safe/2.1.5:
resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==}
engines: {node: '>= 0.8'}
dependencies:
random-bytes: 1.0.0
dev: false
/uid2/0.0.4: /uid2/0.0.4:
resolution: {integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==} resolution: {integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==}
dev: false dev: false