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

View File

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

View File

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

View File

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

View File

@@ -4,61 +4,65 @@
class="h-1"
:class="[
{
'bg-accentDark': ordering,
'bg-accentDark': isReorderable,
},
]"
@drop="dropEvent"
@dragover.prevent="ordering = true"
@dragleave="ordering = false"
@dragend="ordering = false"
@dragleave="resetDragState"
@dragend="resetDragState"
></div>
<div
class="flex items-stretch group"
:draggable="!hasNoTeamAccess"
@drop="dropEvent"
@dragstart="dragStart"
@dragover.prevent="dragging = true"
@dragleave="dragging = false"
@dragend="dragging = false"
@dragover="handleDragOver($event)"
@dragleave="resetDragState"
@dragend="resetDragState"
@contextmenu.prevent="options?.tippy.show()"
>
<span
class="flex items-center justify-center w-16 px-2 truncate cursor-pointer"
:class="requestLabelColor"
<div
class="flex items-center justify-center flex-1 min-w-0 cursor-pointer pointer-events-auto"
@click="selectRequest()"
>
<component
:is="IconCheckCircle"
v-if="isSelected"
class="svg-icons"
:class="{ 'text-accent': isSelected }"
/>
<HoppSmartSpinner v-else-if="isRequestLoading" />
<span v-else class="font-semibold truncate text-tiny">
{{ request.method }}
</span>
</span>
<span
class="flex items-center flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
@click="selectRequest()"
>
<span class="truncate" :class="{ 'text-accent': isSelected }">
{{ request.name }}
<span
class="flex items-center justify-center w-16 px-2 truncate pointer-events-none"
:class="requestLabelColor"
>
<component
:is="IconCheckCircle"
v-if="isSelected"
class="svg-icons"
:class="{ 'text-accent': isSelected }"
/>
<HoppSmartSpinner v-else-if="isRequestLoading" />
<span v-else class="font-semibold truncate text-tiny">
{{ request.method }}
</span>
</span>
<span
v-if="isActive"
v-tippy="{ theme: 'tooltip' }"
class="relative h-1.5 w-1.5 flex flex-shrink-0 mx-3"
:title="`${t('collection.request_in_use')}`"
class="flex items-center flex-1 min-w-0 py-2 pr-2 pointer-events-none transition group-hover:text-secondaryDark"
>
<span
class="absolute inline-flex flex-shrink-0 w-full h-full bg-green-500 rounded-full opacity-75 animate-ping"
>
<span class="truncate" :class="{ 'text-accent': isSelected }">
{{ request.name }}
</span>
<span
class="relative inline-flex flex-shrink-0 rounded-full h-1.5 w-1.5 bg-green-500"
></span>
v-if="isActive"
v-tippy="{ theme: 'tooltip' }"
class="relative h-1.5 w-1.5 flex flex-shrink-0 mx-3"
:title="`${t('collection.request_in_use')}`"
>
<span
class="absolute inline-flex flex-shrink-0 w-full h-full bg-green-500 rounded-full opacity-75 animate-ping"
>
</span>
<span
class="relative inline-flex flex-shrink-0 rounded-full h-1.5 w-1.5 bg-green-500"
></span>
</span>
</span>
</span>
</div>
<div v-if="!hasNoTeamAccess" class="flex">
<HoppButtonSecondary
v-if="!saveRequest"
@@ -151,6 +155,11 @@ import { TippyComponent } from "vue-tippy"
import { pipe } from "fp-ts/function"
import * as RR from "fp-ts/ReadonlyRecord"
import * as O from "fp-ts/Option"
import {
changeCurrentReorderStatus,
currentReorderingStatus$,
} from "~/newstore/reordering"
import { useReadonlyStream } from "~/composables/stream"
type CollectionType = "my-collections" | "team-collections"
@@ -167,6 +176,11 @@ const props = defineProps({
default: "",
required: false,
},
parentID: {
type: String as PropType<string | null>,
default: null,
required: true,
},
collectionsType: {
type: String as PropType<CollectionType>,
default: "my-collections",
@@ -222,6 +236,12 @@ const duplicate = ref<HTMLButtonElement | null>(null)
const dragging = ref(false)
const ordering = ref(false)
const currentReorderingStatus = useReadonlyStream(currentReorderingStatus$, {
type: "collection",
id: "",
parentID: "",
})
const requestMethodLabels = {
get: "text-green-500",
post: "text-yellow-500",
@@ -255,13 +275,41 @@ const dragStart = ({ dataTransfer }: DragEvent) => {
if (dataTransfer) {
emit("drag-request", dataTransfer)
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) => {
if (e.dataTransfer) {
e.stopPropagation()
ordering.value = !ordering.value
resetDragState()
emit("update-request-order", e.dataTransfer)
}
}
@@ -273,4 +321,9 @@ const isRequestLoading = computed(() => {
return false
}
})
const resetDragState = () => {
dragging.value = false
ordering.value = false
}
</script>

View File

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

View File

@@ -209,7 +209,7 @@ import {
setRESTRequest,
setRESTSaveContext,
} from "~/newstore/RESTSession"
import { cloneDeep } from "lodash-es"
import { cloneDeep, isEqual } from "lodash-es"
import { GQLError } from "~/helpers/backend/GQLClient"
import {
createNewRootCollection,
@@ -470,62 +470,60 @@ const filteredCollections = computed(() => {
return filteredCollections
})
const isSelected = computed(() => {
return ({
collectionIndex,
folderPath,
requestIndex,
collectionID,
folderID,
requestID,
}: {
collectionIndex?: number | undefined
folderPath?: string | undefined
requestIndex?: number | undefined
collectionID?: string | undefined
folderID?: string | undefined
requestID?: string | undefined
}) => {
if (collectionIndex !== undefined) {
return (
props.picked &&
props.picked.pickedType === "my-collection" &&
props.picked.collectionIndex === collectionIndex
)
} else if (requestIndex !== undefined && folderPath !== undefined) {
return (
props.picked &&
props.picked.pickedType === "my-request" &&
props.picked.folderPath === folderPath &&
props.picked.requestIndex === requestIndex
)
} else if (folderPath !== undefined) {
return (
props.picked &&
props.picked.pickedType === "my-folder" &&
props.picked.folderPath === folderPath
)
} else if (collectionID !== undefined) {
return (
props.picked &&
props.picked.pickedType === "teams-collection" &&
props.picked.collectionID === collectionID
)
} else if (requestID !== undefined) {
return (
props.picked &&
props.picked.pickedType === "teams-request" &&
props.picked.requestID === requestID
)
} else if (folderID !== undefined) {
return (
props.picked &&
props.picked.pickedType === "teams-folder" &&
props.picked.folderID === folderID
)
}
const isSelected = ({
collectionIndex,
folderPath,
requestIndex,
collectionID,
folderID,
requestID,
}: {
collectionIndex?: number | undefined
folderPath?: string | undefined
requestIndex?: number | undefined
collectionID?: string | undefined
folderID?: string | undefined
requestID?: string | undefined
}) => {
if (collectionIndex !== undefined) {
return (
props.picked &&
props.picked.pickedType === "my-collection" &&
props.picked.collectionIndex === collectionIndex
)
} else if (requestIndex !== undefined && folderPath !== undefined) {
return (
props.picked &&
props.picked.pickedType === "my-request" &&
props.picked.folderPath === folderPath &&
props.picked.requestIndex === requestIndex
)
} else if (folderPath !== undefined) {
return (
props.picked &&
props.picked.pickedType === "my-folder" &&
props.picked.folderPath === folderPath
)
} else if (collectionID !== undefined) {
return (
props.picked &&
props.picked.pickedType === "teams-collection" &&
props.picked.collectionID === collectionID
)
} else if (requestID !== undefined) {
return (
props.picked &&
props.picked.pickedType === "teams-request" &&
props.picked.requestID === requestID
)
} else if (folderID !== undefined) {
return (
props.picked &&
props.picked.pickedType === "teams-folder" &&
props.picked.folderID === folderID
)
}
})
}
const modalLoadingState = ref(false)
const exportLoading = ref(false)
@@ -1023,7 +1021,7 @@ const onRemoveCollection = () => {
if (collectionIndex === null) return
if (
isSelected.value({
isSelected({
collectionIndex,
})
) {
@@ -1040,7 +1038,7 @@ const onRemoveCollection = () => {
if (!collectionID) return
if (
isSelected.value({
isSelected({
collectionID,
})
) {
@@ -1067,7 +1065,7 @@ const onRemoveFolder = () => {
if (!folderPath) return
if (
isSelected.value({
isSelected({
folderPath,
})
) {
@@ -1084,7 +1082,7 @@ const onRemoveFolder = () => {
if (!collectionID) return
if (
isSelected.value({
isSelected({
collectionID,
})
) {
@@ -1118,7 +1116,7 @@ const onRemoveRequest = () => {
if (folderPath === null || requestIndex === null) return
if (
isSelected.value({
isSelected({
folderPath,
requestIndex,
})
@@ -1136,7 +1134,7 @@ const onRemoveRequest = () => {
if (!requestID) return
if (
isSelected.value({
isSelected({
requestID,
})
) {
@@ -1342,12 +1340,10 @@ const discardRequestChange = () => {
* @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])
}
})
const pathToLastIndex = (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
@@ -1363,7 +1359,7 @@ const dropRequest = (payload: {
if (collectionsType.value.type === "my-collections" && folderPath) {
moveRESTRequest(
folderPath,
pathToIndex.value(requestIndex),
pathToLastIndex(requestIndex),
destinationCollectionIndex
)
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
* to a different collection or folder
@@ -1408,6 +1441,15 @@ const dropCollection = (payload: {
if (!collectionIndexDragged || !destinationCollectionIndex) return
if (collectionIndexDragged === destinationCollectionIndex) return
if (collectionsType.value.type === "my-collections") {
if (
checkIfCollectionIsAParentOfTheChildren(
collectionIndexDragged,
destinationCollectionIndex
)
) {
toast.error(`${t("team.parent_coll_move")}`)
return
}
moveRESTFolder(collectionIndexDragged, destinationCollectionIndex)
draggingToRoot.value = false
toast.success(`${t("collection.moved")}`)
@@ -1445,12 +1487,10 @@ const dropCollection = (payload: {
* @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
}
})
const isAlreadyInRoot = (id: string) => {
const indexPath = pathToIndex(id)
return indexPath.length === 1
}
/**
* This function is called when the user drops the collection
@@ -1463,7 +1503,7 @@ const dropToRoot = ({ dataTransfer }: DragEvent) => {
if (!collectionIndexDragged) return
if (collectionsType.value.type === "my-collections") {
// check if the collection is already in the root
if (isAlreadyInRoot.value(collectionIndexDragged)) {
if (isAlreadyInRoot(collectionIndexDragged)) {
toast.error(`${t("collection.invalid_root_move")}`)
} else {
moveRESTFolder(collectionIndexDragged, null)
@@ -1506,26 +1546,25 @@ const dropToRoot = ({ dataTransfer }: DragEvent) => {
* @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))
const isSameSameParent = (draggedItem: string, destinationItem: string) => {
const draggedItemIndex = pathToIndex(draggedItem)
const destinationItemIndex = pathToIndex(destinationItem)
// 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]
) {
// length of 1 means the request is in the root
if (draggedItemIndex.length === 1 && destinationItemIndex.length === 1) {
return true
} else if (draggedItemIndex.length === destinationItemIndex.length) {
const dragedItemParent = draggedItemIndex.slice(0, -1)
const destinationItemParent = destinationItemIndex.slice(0, -1)
if (isEqual(dragedItemParent, destinationItemParent)) {
return true
} else {
return false
}
} else {
return false
}
)
}
/**
* 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 (collectionsType.value.type === "my-collections") {
if (!isSameSameParent.value(dragedRequestIndex, destinationRequestIndex)) {
if (!isSameSameParent(dragedRequestIndex, destinationRequestIndex)) {
toast.error(`${t("collection.different_parent")}`)
} else {
updateRESTRequestOrder(
pathToIndex.value(dragedRequestIndex),
pathToIndex.value(destinationRequestIndex),
pathToLastIndex(dragedRequestIndex),
pathToLastIndex(destinationRequestIndex),
destinationCollectionIndex
)
toast.success(`${t("request.order_changed")}`)
@@ -1608,9 +1647,7 @@ const updateCollectionOrder = (payload: {
if (dragedCollectionIndex === destinationCollectionIndex) return
if (collectionsType.value.type === "my-collections") {
if (
!isSameSameParent.value(dragedCollectionIndex, destinationCollectionIndex)
) {
if (!isSameSameParent(dragedCollectionIndex, destinationCollectionIndex)) {
toast.error(`${t("collection.different_parent")}`)
} else {
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(
dragedRequestID: string,
destinationRequestID: string,
@@ -570,10 +579,7 @@ export default class NewTeamCollectionAdapter {
if (requestIndex === -1) return
const request = collection.requests[requestIndex]
collection.requests.splice(requestIndex, 1)
collection.requests.splice(destinationIndex, 0, request)
this.reorderItems(collection.requests, requestIndex, destinationIndex)
this.collections$.next(tree)
}
@@ -600,10 +606,7 @@ export default class NewTeamCollectionAdapter {
// If the collection index is not found, don't update
if (collectionIndex === -1) return
const collection = coll.children[collectionIndex]
coll.children.splice(collectionIndex, 1)
coll.children.splice(destinationIndex, 0, collection)
this.reorderItems(coll.children, collectionIndex, destinationIndex)
} else {
// If the collection has no parent collection, it is a root collection
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 (collectionIndex === -1) return
const collection = tree[collectionIndex]
tree.splice(collectionIndex, 1)
tree.splice(destinationIndex, 0, collection)
this.reorderItems(tree, collectionIndex, destinationIndex)
}
this.collections$.next(tree)

View File

@@ -31,7 +31,7 @@ const defaultGraphqlCollectionState = {
type RESTCollectionStoreType = typeof defaultRESTCollectionState
type GraphqlCollectionStoreType = typeof defaultGraphqlCollectionState
function navigateToFolderWithIndexPath(
export function navigateToFolderWithIndexPath(
collections: HoppCollection<HoppRESTRequest | HoppGQLRequest>[],
indexPaths: number[]
) {
@@ -45,6 +45,15 @@ function navigateToFolderWithIndexPath(
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({
setCollections(
_: RESTCollectionStoreType,
@@ -88,12 +97,15 @@ const restCollectionDispatchers = defineDispatchers({
{ state }: RESTCollectionStoreType,
{
collectionIndex,
collection,
}: { collectionIndex: number; collection: HoppCollection<any> }
partialCollection,
}: {
collectionIndex: number
partialCollection: Partial<HoppCollection<any>>
}
) {
return {
state: state.map((col, index) =>
index === collectionIndex ? collection : col
index === collectionIndex ? { ...col, ...partialCollection } : col
),
}
},
@@ -295,18 +307,14 @@ const restCollectionDispatchers = defineDispatchers({
)
if (containingFolder === null) {
const [removed] = newState.splice(folderIndex, 1)
newState.splice(destinationFolderIndex, 0, removed)
reorderItems(newState, folderIndex, destinationFolderIndex)
return {
state: newState,
}
}
const [removed] = containingFolder.folders.splice(folderIndex, 1)
containingFolder.folders.splice(destinationFolderIndex, 0, removed)
reorderItems(containingFolder.folders, folderIndex, destinationFolderIndex)
return {
state: newState,
@@ -480,9 +488,7 @@ const restCollectionDispatchers = defineDispatchers({
return {}
}
const [removed] = targetLocation.requests.splice(requestIndex, 1)
targetLocation.requests.splice(destinationRequestIndex, 0, removed)
reorderItems(targetLocation.requests, requestIndex, destinationRequestIndex)
return {
state: newState,
@@ -821,13 +827,13 @@ export function getRESTCollection(collectionIndex: number) {
export function editRESTCollection(
collectionIndex: number,
collection: HoppCollection<HoppRESTRequest>
partialCollection: Partial<HoppCollection<HoppRESTRequest>>
) {
restCollectionStore.dispatch({
dispatcher: "editCollection",
payload: {
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-plugin-prettier: ^4.2.1
express: ^4.17.1
express-session: ^1.17.3
fp-ts: ^2.13.1
graphql: ^15.5.0
graphql-query-complexity: ^0.12.0
@@ -130,6 +131,7 @@ importers:
cookie: 0.5.0
cookie-parser: 1.4.6
express: 4.18.2
express-session: 1.17.3
fp-ts: 2.13.1
graphql: 15.8.0
graphql-query-complexity: 0.12.0_graphql@15.8.0
@@ -296,6 +298,7 @@ importers:
axios: ^0.21.4
buffer: ^6.0.3
cross-env: ^7.0.3
dotenv: ^16.0.3
eslint: ^8.24.0
eslint-plugin-prettier: ^4.2.1
eslint-plugin-vue: ^9.5.1
@@ -471,6 +474,7 @@ importers:
'@vue/eslint-config-typescript': 11.0.1_kpxf5iryryrlim2ejhkirkiuey
'@vue/runtime-core': 3.2.39
cross-env: 7.0.3
dotenv: 16.0.3
eslint: 8.24.0
eslint-plugin-prettier: 4.2.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
chalk: 4.1.2
debug: 4.3.4
dotenv: 16.0.1
dotenv: 16.0.3
graphql: 15.8.0
graphql-request: 4.3.0_graphql@15.8.0
http-proxy-agent: 5.0.0
@@ -4681,7 +4685,7 @@ packages:
'@types/jsonwebtoken': 9.0.1
chalk: 4.1.2
debug: 4.3.4
dotenv: 16.0.1
dotenv: 16.0.3
graphql: 16.6.0
graphql-request: 5.1.0_graphql@16.6.0
http-proxy-agent: 5.0.0
@@ -10063,13 +10067,18 @@ packages:
dev: false
/cookie-signature/1.0.6:
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
resolution: {integrity: sha1-4wOogrNCzD7oylE6eZmXNNqzriw=}
/cookie/0.4.1:
resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==}
engines: {node: '>= 0.6'}
dev: false
/cookie/0.4.2:
resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
engines: {node: '>= 0.6'}
dev: false
/cookie/0.5.0:
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
engines: {node: '>= 0.6'}
@@ -10603,8 +10612,8 @@ packages:
is-obj: 2.0.0
dev: true
/dotenv/16.0.1:
resolution: {integrity: sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==}
/dotenv/16.0.3:
resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
engines: {node: '>=12'}
dev: true
@@ -12062,6 +12071,22 @@ packages:
raw-body: 2.5.1
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:
resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==}
engines: {node: '>= 0.10.0'}
@@ -16028,6 +16053,11 @@ packages:
dependencies:
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:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
@@ -16844,6 +16874,11 @@ packages:
resolution: {integrity: sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==}
dev: false
/random-bytes/1.0.0:
resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==}
engines: {node: '>= 0.8'}
dev: false
/randombytes/2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
dependencies:
@@ -18834,6 +18869,13 @@ packages:
resolution: {integrity: sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==}
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:
resolution: {integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==}
dev: false