Files
hoppscotch/packages/hoppscotch-common/src/components/collections/graphql/ImportExport.vue

300 lines
7.5 KiB
Vue

<template>
<HoppSmartModal
v-if="show"
dialog
:title="`${t('modal.collections')}`"
styles="sm:max-w-md"
@close="hideModal"
>
<template #actions>
<span>
<tippy interactive trigger="click" theme="popover">
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="t('action.more')"
:icon="IconMoreVertical"
:on-shown="() => tippyActions.focus()"
/>
<template #content="{ hide }">
<div
ref="tippyActions"
class="flex flex-col focus:outline-none"
tabindex="0"
@keyup.escape="hide()"
>
<HoppSmartItem
:icon="IconGithub"
:label="t('import.from_gist')"
@click="
() => {
readCollectionGist()
hide()
}
"
/>
<span
v-tippy="{ theme: 'tooltip' }"
:title="
!currentUser
? `${t('export.require_github')}`
: currentUser.provider !== 'github.com'
? `${t('export.require_github')}`
: undefined
"
>
<HoppSmartItem
:disabled="
!currentUser
? true
: currentUser.provider !== 'github.com'
? true
: false
"
:icon="IconGithub"
:label="t('export.create_secret_gist')"
@click="
() => {
createCollectionGist()
hide()
}
"
/>
</span>
</div>
</template>
</tippy>
</span>
</template>
<template #body>
<div class="flex flex-col space-y-2">
<HoppSmartItem
:icon="IconFolderPlus"
:label="t('import.from_json')"
@click="openDialogChooseFileToImportFrom"
/>
<input
ref="inputChooseFileToImportFrom"
class="input"
type="file"
accept="application/json"
@change="importFromJSON"
/>
<hr />
<HoppSmartItem
v-tippy="{ theme: 'tooltip' }"
:title="t('action.download_file')"
:icon="IconDownload"
:label="t('export.as_json')"
@click="exportJSON"
/>
</div>
</template>
</HoppSmartModal>
</template>
<script setup lang="ts">
import axios from "axios"
import IconMoreVertical from "~icons/lucide/more-vertical"
import IconFolderPlus from "~icons/lucide/folder-plus"
import IconDownload from "~icons/lucide/download"
import IconGithub from "~icons/lucide/github"
import { computed, ref } from "vue"
import { platform } from "~/platform"
import { useI18n } from "@composables/i18n"
import { useReadonlyStream } from "@composables/stream"
import { useToast } from "@composables/toast"
import {
graphqlCollections$,
setGraphqlCollections,
appendGraphqlCollections,
} from "~/newstore/collections"
defineProps<{
show: boolean
}>()
const emit = defineEmits<{
(e: "hide-modal"): void
}>()
const toast = useToast()
const t = useI18n()
const collections = useReadonlyStream(graphqlCollections$, [])
const currentUser = useReadonlyStream(
platform.auth.getCurrentUserStream(),
platform.auth.getCurrentUser()
)
// Template refs
const tippyActions = ref<any | null>(null)
const inputChooseFileToImportFrom = ref<HTMLInputElement>()
const collectionJson = computed(() => {
return JSON.stringify(collections.value, null, 2)
})
const createCollectionGist = async () => {
if (!currentUser.value) {
toast.error(t("profile.no_permission").toString())
return
}
try {
const res = await axios.post(
"https://api.github.com/gists",
{
files: {
"hoppscotch-collections.json": {
content: collectionJson.value,
},
},
},
{
headers: {
Authorization: `token ${currentUser.value.accessToken}`,
Accept: "application/vnd.github.v3+json",
},
}
)
toast.success(t("export.gist_created").toString())
window.open(res.data.html_url)
} catch (e) {
toast.error(t("error.something_went_wrong").toString())
console.error(e)
}
}
const fileImported = () => {
toast.success(t("state.file_imported").toString())
}
const failedImport = () => {
toast.error(t("import.failed").toString())
}
const readCollectionGist = async () => {
const gist = prompt(t("import.gist_url").toString())
if (!gist) return
try {
const { files } = (await axios.get(
`https://api.github.com/gists/${gist.split("/").pop()}`,
{
headers: {
Accept: "application/vnd.github.v3+json",
},
}
)) as {
files: {
[fileName: string]: {
content: any
}
}
}
const collections = JSON.parse(Object.values(files)[0].content)
setGraphqlCollections(collections)
fileImported()
} catch (e) {
failedImport()
console.error(e)
}
}
const hideModal = () => {
emit("hide-modal")
}
const openDialogChooseFileToImportFrom = () => {
if (inputChooseFileToImportFrom.value)
inputChooseFileToImportFrom.value.click()
}
const importFromJSON = () => {
if (!inputChooseFileToImportFrom.value) return
if (
!inputChooseFileToImportFrom.value.files ||
inputChooseFileToImportFrom.value.files.length === 0
) {
toast.show(t("action.choose_file").toString())
return
}
const reader = new FileReader()
reader.onload = ({ target }) => {
const content = target!.result as string | null
if (!content) {
toast.show(t("action.choose_file").toString())
return
}
const collections = JSON.parse(content)
if (collections[0]) {
const [name, folders, requests] = Object.keys(collections[0])
if (name === "name" && folders === "folders" && requests === "requests") {
// Do nothing
}
} else {
failedImport()
return
}
appendGraphqlCollections(collections)
platform.analytics?.logEvent({
type: "HOPP_IMPORT_COLLECTION",
importer: "json",
workspaceType: "personal",
platform: "gql",
})
fileImported()
}
reader.readAsText(inputChooseFileToImportFrom.value.files[0])
inputChooseFileToImportFrom.value.value = ""
}
const exportJSON = async () => {
const dataToWrite = collectionJson.value
const parsedCollections = JSON.parse(dataToWrite)
if (!parsedCollections.length) {
return toast.error(t("error.no_collections_to_export"))
}
const file = new Blob([dataToWrite], { type: "application/json" })
const url = URL.createObjectURL(file)
const filename = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
URL.revokeObjectURL(url)
const result = await platform.io.saveFileWithDialog({
data: dataToWrite,
contentType: "application/json",
suggestedFilename: filename,
filters: [
{
name: "Hoppscotch Collection JSON file",
extensions: ["json"],
},
],
})
if (result.type === "unknown" || result.type === "saved") {
platform?.analytics?.logEvent({
type: "HOPP_EXPORT_COLLECTION",
exporter: "json",
platform: "gql",
})
toast.success(t("state.download_started").toString())
}
}
</script>