Files
hoppscotch/packages/hoppscotch-common/src/components/collections/graphql/index.vue
2023-12-13 22:35:44 +05:30

624 lines
16 KiB
Vue

<template>
<div :class="{ 'rounded border border-divider': saveRequest }">
<div
class="sticky z-10 flex flex-shrink-0 flex-col overflow-x-auto rounded-t bg-primary"
:style="
saveRequest ? 'top: calc(-1 * var(--line-height-body))' : 'top: 0'
"
>
<input
v-model="filterText"
type="search"
autocomplete="off"
:placeholder="t('action.search')"
class="flex w-full bg-transparent px-4 py-2 h-8"
/>
<div
class="flex flex-1 flex-shrink-0 justify-between border-y border-dividerLight bg-primary"
>
<HoppButtonSecondary
:icon="IconPlus"
:label="t('action.new')"
class="!rounded-none"
@click="displayModalAdd(true)"
/>
<div class="flex">
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
to="https://docs.hoppscotch.io/documentation/features/collections"
blank
:title="t('app.wiki')"
:icon="IconHelpCircle"
/>
<HoppButtonSecondary
v-if="!saveRequest"
v-tippy="{ theme: 'tooltip' }"
:title="t('modal.import_export')"
:icon="IconImport"
@click="displayModalImportExport(true)"
/>
</div>
</div>
</div>
<div class="flex flex-col">
<CollectionsGraphqlCollection
v-for="(collection, index) in filteredCollections"
:key="`collection-${index}`"
:picked="picked"
:name="collection.name"
:collection-index="index"
:collection="collection"
:is-filtered="filterText.length > 0"
:save-request="saveRequest"
@edit-collection="editCollection(collection, index)"
@add-request="addRequest($event)"
@add-folder="addFolder($event)"
@edit-folder="editFolder($event)"
@edit-request="editRequest($event)"
@duplicate-request="duplicateRequest($event)"
@select-collection="$emit('use-collection', collection)"
@edit-properties="editProperties($event)"
@select="$emit('select', $event)"
@select-request="selectRequest($event)"
@drop-request="dropRequest($event)"
/>
</div>
<HoppSmartPlaceholder
v-if="collections.length === 0"
:src="`/images/states/${colorMode.value}/pack.svg`"
:alt="`${t('empty.collections')}`"
:text="t('empty.collections')"
>
<template #body>
<div class="flex flex-col items-center space-y-4">
<span class="text-center text-secondaryLight">
{{ t("collection.import_or_create") }}
</span>
<div class="flex flex-col items-stretch gap-4">
<HoppButtonPrimary
:icon="IconImport"
:label="t('import.title')"
filled
outline
@click="displayModalImportExport(true)"
/>
<HoppButtonSecondary
:label="t('add.new')"
filled
outline
:icon="IconPlus"
@click="displayModalAdd(true)"
/>
</div>
</div>
</template>
</HoppSmartPlaceholder>
<HoppSmartPlaceholder
v-if="!(filteredCollections.length !== 0 || collections.length === 0)"
:text="`${t('state.nothing_found')}${filterText}`"
>
<template #icon>
<icon-lucide-search class="svg-icons opacity-75" />
</template>
</HoppSmartPlaceholder>
<CollectionsGraphqlAdd
:show="showModalAdd"
@hide-modal="displayModalAdd(false)"
/>
<CollectionsGraphqlEdit
:show="showModalEdit"
:editing-collection="editingCollection"
:editing-collection-index="editingCollectionIndex"
:editing-collection-name="editingCollection ? editingCollection.name : ''"
@hide-modal="displayModalEdit(false)"
/>
<CollectionsGraphqlAddRequest
:show="showModalAddRequest"
:folder-path="editingFolderPath"
@add-request="onAddRequest($event)"
@hide-modal="displayModalAddRequest(false)"
/>
<CollectionsGraphqlAddFolder
:show="showModalAddFolder"
:folder-path="editingFolderPath"
@add-folder="onAddFolder($event)"
@hide-modal="displayModalAddFolder(false)"
/>
<CollectionsGraphqlEditFolder
:show="showModalEditFolder"
:collection-index="editingCollectionIndex"
:folder="editingFolder"
:folder-index="editingFolderIndex"
:folder-path="editingFolderPath"
:editing-folder-name="editingFolder ? editingFolder.name : ''"
@hide-modal="displayModalEditFolder(false)"
/>
<CollectionsGraphqlEditRequest
:show="showModalEditRequest"
:folder-path="editingFolderPath"
:request="editingRequest"
:request-index="editingRequestIndex"
:editing-request-name="editingRequest ? editingRequest.name : ''"
@hide-modal="displayModalEditRequest(false)"
/>
<CollectionsGraphqlImportExport
v-if="showModalImportExport"
@hide-modal="displayModalImportExport(false)"
/>
<CollectionsProperties
:show="showModalEditProperties"
:editing-properties="editingProperties"
@hide-modal="displayModalEditProperties(false)"
@set-collection-properties="setCollectionProperties"
/>
</div>
</template>
<script setup lang="ts">
import { nextTick, ref } from "vue"
import { clone, cloneDeep } from "lodash-es"
import {
graphqlCollections$,
addGraphqlFolder,
saveGraphqlRequestAs,
cascadeParentCollectionForHeaderAuth,
editGraphqlCollection,
editGraphqlFolder,
moveGraphqlRequest,
} from "~/newstore/collections"
import IconPlus from "~icons/lucide/plus"
import IconHelpCircle from "~icons/lucide/help-circle"
import IconImport from "~icons/lucide/folder-down"
import { useI18n } from "@composables/i18n"
import { useReadonlyStream } from "@composables/stream"
import { useColorMode } from "@composables/theming"
import { platform } from "~/platform"
import { useService } from "dioc/vue"
import { GQLTabService } from "~/services/tab/graphql"
import { computed } from "vue"
import {
HoppCollection,
HoppGQLRequest,
makeGQLRequest,
} from "@hoppscotch/data"
import { Picked } from "~/helpers/types/HoppPicked"
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
import { updateInheritedPropertiesForAffectedRequests } from "~/helpers/collection/collection"
import { useToast } from "~/composables/toast"
import { getRequestsByPath } from "~/helpers/collection/request"
const t = useI18n()
const toast = useToast()
defineProps<{
// Whether to activate the ability to pick items (activates 'select' events)
saveRequest: boolean
picked: Picked
}>()
const collections = useReadonlyStream(graphqlCollections$, [], "deep")
const colorMode = useColorMode()
const tabs = useService(GQLTabService)
const showModalAdd = ref(false)
const showModalEdit = ref(false)
const showModalImportExport = ref(false)
const showModalAddRequest = ref(false)
const showModalAddFolder = ref(false)
const showModalEditFolder = ref(false)
const showModalEditRequest = ref(false)
const showModalEditProperties = ref(false)
const editingCollection = ref<HoppCollection<HoppGQLRequest> | null>(null)
const editingCollectionIndex = ref<number | null>(null)
const editingFolder = ref<HoppCollection<HoppGQLRequest> | null>(null)
const editingFolderName = ref("")
const editingFolderIndex = ref<number | null>(null)
const editingFolderPath = ref("")
const editingRequest = ref<HoppGQLRequest | null>(null)
const editingRequestIndex = ref<number | null>(null)
const editingProperties = ref<{
collection: HoppCollection<HoppGQLRequest> | null
isRootCollection: boolean
path: string
inheritedProperties?: HoppInheritedProperty
}>({
collection: null,
isRootCollection: false,
path: "",
inheritedProperties: undefined,
})
const filterText = ref("")
const filteredCollections = computed(() => {
const collectionsClone = clone(collections.value)
if (!filterText.value) return collectionsClone
const filterTextLower = filterText.value.toLowerCase()
const filteredCollections = []
for (const collection of collectionsClone) {
const filteredRequests = []
const filteredFolders = []
for (const request of collection.requests) {
if (request.name.toLowerCase().includes(filterTextLower))
filteredRequests.push(request)
}
for (const folder of collection.folders) {
const filteredFolderRequests = []
for (const request of folder.requests) {
if (request.name.toLowerCase().includes(filterTextLower))
filteredFolderRequests.push(request)
}
if (filteredFolderRequests.length > 0) {
const filteredFolder = { ...folder }
filteredFolder.requests = filteredFolderRequests
filteredFolders.push(filteredFolder)
}
}
if (filteredRequests.length + filteredFolders.length > 0) {
const filteredCollection = { ...collection }
filteredCollection.requests = filteredRequests
filteredCollection.folders = filteredFolders
filteredCollections.push(filteredCollection)
}
}
return filteredCollections
})
const displayModalAdd = (shouldDisplay: boolean) => {
showModalAdd.value = shouldDisplay
}
const displayModalEdit = (shouldDisplay: boolean) => {
showModalEdit.value = shouldDisplay
if (!shouldDisplay) resetSelectedData()
}
const displayModalImportExport = (shouldDisplay: boolean) => {
showModalImportExport.value = shouldDisplay
}
const displayModalAddRequest = (shouldDisplay: boolean) => {
showModalAddRequest.value = shouldDisplay
if (!shouldDisplay) resetSelectedData()
}
const displayModalAddFolder = (shouldDisplay: boolean) => {
showModalAddFolder.value = shouldDisplay
if (!shouldDisplay) resetSelectedData()
}
const displayModalEditFolder = (shouldDisplay: boolean) => {
showModalEditFolder.value = shouldDisplay
if (!shouldDisplay) resetSelectedData()
}
const displayModalEditRequest = (shouldDisplay: boolean) => {
showModalEditRequest.value = shouldDisplay
if (!shouldDisplay) resetSelectedData()
}
const displayModalEditProperties = (show: boolean) => {
showModalEditProperties.value = show
if (!show) resetSelectedData()
}
const editCollection = (
collection: HoppCollection<HoppGQLRequest>,
collectionIndex: number
) => {
editingCollection.value = collection
editingCollectionIndex.value = collectionIndex
displayModalEdit(true)
}
const onAddRequest = ({
name,
path,
index,
}: {
name: string
path: string
index: number
}) => {
console.log("onAddRequest", name, path, index)
const newRequest = {
...tabs.currentActiveTab.value.document.request,
name,
}
saveGraphqlRequestAs(path, newRequest)
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
path,
"graphql"
)
tabs.createNewTab({
saveContext: {
originLocation: "user-collection",
folderPath: path,
requestIndex: index,
},
request: newRequest,
isDirty: false,
inheritedProperties: {
auth,
headers,
},
})
platform.analytics?.logEvent({
type: "HOPP_SAVE_REQUEST",
platform: "gql",
createdNow: true,
workspaceType: "personal",
})
displayModalAddRequest(false)
}
const addRequest = (payload: { path: string }) => {
const { path } = payload
editingFolderPath.value = path
displayModalAddRequest(true)
}
const onAddFolder = ({
name,
path,
}: {
name: string
path: string | undefined
}) => {
addGraphqlFolder(name, path ?? "0")
platform.analytics?.logEvent({
type: "HOPP_CREATE_COLLECTION",
isRootCollection: false,
platform: "gql",
workspaceType: "personal",
})
displayModalAddFolder(false)
}
const addFolder = (payload: { path: string }) => {
const { path } = payload
editingFolderPath.value = path
displayModalAddFolder(true)
}
const editFolder = (payload: {
folder: HoppCollection<HoppGQLRequest>
folderPath: string
}) => {
const { folder, folderPath } = payload
editingFolder.value = folder
editingFolderPath.value = folderPath
displayModalEditFolder(true)
}
const editRequest = (payload: {
collectionIndex: number
folderIndex: number
folderName: string
request: HoppGQLRequest
requestIndex: number
folderPath: string
}) => {
const {
collectionIndex,
folderIndex,
folderName,
request,
requestIndex,
folderPath,
} = payload
editingFolderPath.value = folderPath
editingCollectionIndex.value = collectionIndex
editingFolderIndex.value = folderIndex
editingFolderName.value = folderName
editingRequest.value = request
editingRequestIndex.value = requestIndex
displayModalEditRequest(true)
}
const duplicateRequest = ({
folderPath,
request,
}: {
folderPath: string
request: HoppGQLRequest
}) => {
saveGraphqlRequestAs(folderPath, {
...cloneDeep(request),
name: `${request.name} - ${t("action.duplicate")}`,
})
}
const selectRequest = ({
request,
folderPath,
requestIndex,
}: {
request: HoppGQLRequest
folderPath: string
requestIndex: number
}) => {
const possibleTab = tabs.getTabRefWithSaveContext({
originLocation: "user-collection",
folderPath: folderPath,
requestIndex: requestIndex,
})
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
folderPath,
"graphql"
)
// Switch to that request if that request is open
if (possibleTab) {
tabs.setActiveTab(possibleTab.value.id)
return
}
tabs.createNewTab({
saveContext: {
originLocation: "user-collection",
folderPath: folderPath,
requestIndex: requestIndex,
},
request: cloneDeep(
makeGQLRequest({
name: request.name,
url: request.url,
query: request.query,
headers: request.headers,
variables: request.variables,
auth: request.auth,
})
),
isDirty: false,
inheritedProperties: {
auth,
headers,
},
})
}
const dropRequest = ({
folderPath,
requestIndex,
collectionIndex,
}: {
folderPath: string
requestIndex: number
collectionIndex: number
}) => {
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
`${collectionIndex}`,
"graphql"
)
const possibleTab = tabs.getTabRefWithSaveContext({
originLocation: "user-collection",
folderPath,
requestIndex: Number(requestIndex),
})
if (possibleTab) {
possibleTab.value.document.saveContext = {
originLocation: "user-collection",
folderPath: `${collectionIndex}`,
requestIndex: getRequestsByPath(collections.value, `${collectionIndex}`)
.length,
}
possibleTab.value.document.inheritedProperties = {
auth,
headers,
}
}
moveGraphqlRequest(folderPath, requestIndex, `${collectionIndex}`)
toast.success(`${t("request.moved")}`)
}
/**
* 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 = (id: string) => {
const indexPath = id.split("/")
return indexPath.length === 1
}
const editProperties = ({
collectionIndex,
collection,
}: {
collectionIndex: string | null
collection: HoppCollection<HoppGQLRequest> | null
}) => {
if (collectionIndex === null || collection === null) return
const parentIndex = collectionIndex.split("/").slice(0, -1).join("/") // remove last folder to get parent folder
let inheritedProperties = {}
if (parentIndex) {
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
parentIndex,
"graphql"
)
inheritedProperties = {
auth,
headers,
} as HoppInheritedProperty
}
editingProperties.value = {
collection,
isRootCollection: isAlreadyInRoot(collectionIndex),
path: collectionIndex,
inheritedProperties,
}
displayModalEditProperties(true)
}
const setCollectionProperties = (newCollection: {
collection: HoppCollection<HoppGQLRequest>
path: string
isRootCollection: boolean
}) => {
const { collection, path, isRootCollection } = newCollection
if (isRootCollection) {
editGraphqlCollection(parseInt(path), collection)
} else {
editGraphqlFolder(path, collection)
}
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
path,
"graphql"
)
nextTick(() => {
updateInheritedPropertiesForAffectedRequests(
path,
{
auth,
headers,
},
"graphql"
)
})
displayModalEditProperties(false)
}
const resetSelectedData = () => {
editingCollection.value = null
editingCollectionIndex.value = null
editingFolder.value = null
editingFolderIndex.value = null
editingRequest.value = null
editingRequestIndex.value = null
}
</script>