123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559 |
- <template>
- <SmartModal
- v-if="show"
- :title="`${$t('modal.import_export')} ${$t('modal.collections')}`"
- @close="hideModal"
- >
- <template #actions>
- <ButtonSecondary
- v-if="mode == 'import_from_my_collections'"
- v-tippy="{ theme: 'tooltip' }"
- :title="$t('action.go_back')"
- class="rounded"
- svg="arrow-left"
- @click.native="
- mode = 'import_export'
- mySelectedCollectionID = undefined
- "
- />
- <span>
- <tippy
- v-if="
- mode == 'import_export' && collectionsType.type == 'my-collections'
- "
- ref="options"
- interactive
- trigger="click"
- theme="popover"
- arrow
- >
- <template #trigger>
- <ButtonSecondary
- v-tippy="{ theme: 'tooltip' }"
- :title="$t('action.more')"
- class="rounded"
- svg="more-vertical"
- />
- </template>
- <SmartItem
- icon="assignment_returned"
- :label="$t('import.from_gist')"
- @click.native="
- readCollectionGist
- $refs.options.tippy().hide()
- "
- />
- <span
- v-tippy="{ theme: 'tooltip' }"
- :title="
- !currentUser
- ? $t('export.require_github')
- : currentUser.provider !== 'github.com'
- ? $t('export.require_github')
- : null
- "
- >
- <SmartItem
- :disabled="
- !currentUser
- ? true
- : currentUser.provider !== 'github.com'
- ? true
- : false
- "
- icon="assignment_turned_in"
- :label="$t('export.create_secret_gist')"
- @click.native="
- createCollectionGist()
- $refs.options.tippy().hide()
- "
- />
- </span>
- </tippy>
- </span>
- </template>
- <template #body>
- <div v-if="mode == 'import_export'" class="flex flex-col space-y-2">
- <SmartItem
- v-tippy="{ theme: 'tooltip' }"
- :title="$t('action.replace_current')"
- svg="file"
- :label="$t('action.replace_json')"
- @click.native="openDialogChooseFileToReplaceWith"
- />
- <input
- ref="inputChooseFileToReplaceWith"
- class="input"
- type="file"
- style="display: none"
- accept="application/json"
- @change="replaceWithJSON"
- />
- <SmartItem
- v-tippy="{ theme: 'tooltip' }"
- :title="$t('action.preserve_current')"
- svg="folder-plus"
- :label="$t('import.json')"
- @click.native="openDialogChooseFileToImportFrom"
- />
- <input
- ref="inputChooseFileToImportFrom"
- class="input"
- type="file"
- style="display: none"
- accept="application/json"
- @change="importFromJSON"
- />
- <SmartItem
- v-if="collectionsType.type == 'team-collections'"
- v-tippy="{ theme: 'tooltip' }"
- :title="$t('action.preserve_current')"
- svg="user"
- :label="$t('import.from_my_collections')"
- @click.native="mode = 'import_from_my_collections'"
- />
- <SmartItem
- v-tippy="{ theme: 'tooltip' }"
- :title="$t('action.download_file')"
- svg="download"
- :label="$t('export.as_json')"
- @click.native="exportJSON"
- />
- </div>
- <div
- v-if="mode == 'import_from_my_collections'"
- class="flex flex-col px-2"
- >
- <div class="select-wrapper">
- <select
- type="text"
- autocomplete="off"
- class="select"
- autofocus
- @change="
- ($event) => {
- mySelectedCollectionID = $event.target.value
- }
- "
- >
- <option
- :key="undefined"
- :value="undefined"
- hidden
- disabled
- selected
- >
- Select Collection
- </option>
- <option
- v-for="(collection, index) in myCollections"
- :key="`collection-${index}`"
- :value="index"
- >
- {{ collection.name }}
- </option>
- </select>
- </div>
- </div>
- </template>
- <template #footer>
- <div v-if="mode == 'import_from_my_collections'">
- <span>
- <ButtonPrimary
- :disabled="mySelectedCollectionID == undefined"
- svg="folder-plus"
- :label="$t('import.title')"
- @click.native="importFromMyCollections"
- />
- </span>
- </div>
- </template>
- </SmartModal>
- </template>
- <script>
- import { defineComponent } from "@nuxtjs/composition-api"
- import { currentUser$ } from "~/helpers/fb/auth"
- import * as teamUtils from "~/helpers/teams/utils"
- import { useReadonlyStream } from "~/helpers/utils/composables"
- import {
- restCollections$,
- setRESTCollections,
- appendRESTCollections,
- } from "~/newstore/collections"
- export default defineComponent({
- props: {
- show: Boolean,
- collectionsType: { type: Object, default: () => {} },
- },
- setup() {
- return {
- myCollections: useReadonlyStream(restCollections$, []),
- currentUser: useReadonlyStream(currentUser$, null),
- }
- },
- data() {
- return {
- showJsonCode: false,
- mode: "import_export",
- mySelectedCollectionID: undefined,
- collectionJson: "",
- }
- },
- methods: {
- async createCollectionGist() {
- this.getJSONCollection()
- await this.$axios
- .$post(
- "https://api.github.com/gists",
- {
- files: {
- "hoppscotch-collections.json": {
- content: this.collectionJson,
- },
- },
- },
- {
- headers: {
- Authorization: `token ${this.currentUser.accessToken}`,
- Accept: "application/vnd.github.v3+json",
- },
- }
- )
- .then((res) => {
- this.$toast.success(this.$t("export.gist_created"), {
- icon: "done",
- })
- window.open(res.html_url)
- })
- .catch((e) => {
- this.$toast.error(this.$t("error.something_went_wrong"), {
- icon: "error_outline",
- })
- console.error(e)
- })
- },
- async readCollectionGist() {
- const gist = prompt(this.$t("import.gist_url"))
- if (!gist) return
- await this.$axios
- .$get(`https://api.github.com/gists/${gist.split("/").pop()}`, {
- headers: {
- Accept: "application/vnd.github.v3+json",
- },
- })
- .then(({ files }) => {
- const collections = JSON.parse(Object.values(files)[0].content)
- setRESTCollections(collections)
- this.fileImported()
- })
- .catch((e) => {
- this.failedImport()
- console.error(e)
- })
- },
- hideModal() {
- this.mode = "import_export"
- this.mySelectedCollectionID = undefined
- this.$emit("hide-modal")
- },
- openDialogChooseFileToReplaceWith() {
- this.$refs.inputChooseFileToReplaceWith.click()
- },
- openDialogChooseFileToImportFrom() {
- this.$refs.inputChooseFileToImportFrom.click()
- },
- replaceWithJSON() {
- const reader = new FileReader()
- reader.onload = ({ target }) => {
- const content = target.result
- let 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 if (
- collections.info &&
- collections.info.schema.includes("v2.1.0")
- ) {
- collections = [this.parsePostmanCollection(collections)]
- } else {
- this.failedImport()
- }
- if (this.collectionsType.type === "team-collections") {
- teamUtils
- .replaceWithJSON(
- this.$apollo,
- collections,
- this.collectionsType.selectedTeam.id
- )
- .then((status) => {
- if (status) {
- this.fileImported()
- } else {
- this.failedImport()
- }
- })
- .catch((e) => {
- console.error(e)
- this.failedImport()
- })
- } else {
- setRESTCollections(collections)
- this.fileImported()
- }
- }
- reader.readAsText(this.$refs.inputChooseFileToReplaceWith.files[0])
- this.$refs.inputChooseFileToReplaceWith.value = ""
- },
- importFromJSON() {
- const reader = new FileReader()
- reader.onload = ({ target }) => {
- const content = target.result
- let 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 if (
- collections.info &&
- collections.info.schema.includes("v2.1.0")
- ) {
- // replace the variables, postman uses {{var}}, Hoppscotch uses <<var>>
- collections = JSON.parse(
- content.replaceAll(/{{([a-z]+)}}/gi, "<<$1>>")
- )
- collections = [this.parsePostmanCollection(collections)]
- } else {
- this.failedImport()
- return
- }
- if (this.collectionsType.type === "team-collections") {
- teamUtils
- .importFromJSON(
- this.$apollo,
- collections,
- this.collectionsType.selectedTeam.id
- )
- .then((status) => {
- if (status) {
- this.$emit("update-team-collections")
- this.fileImported()
- } else {
- this.failedImport()
- }
- })
- .catch((e) => {
- console.error(e)
- this.failedImport()
- })
- } else {
- appendRESTCollections(collections)
- this.fileImported()
- }
- }
- reader.readAsText(this.$refs.inputChooseFileToImportFrom.files[0])
- this.$refs.inputChooseFileToImportFrom.value = ""
- },
- importFromMyCollections() {
- teamUtils
- .importFromMyCollections(
- this.$apollo,
- this.mySelectedCollectionID,
- this.collectionsType.selectedTeam.id
- )
- .then((success) => {
- if (success) {
- this.fileImported()
- this.$emit("update-team-collections")
- } else {
- this.failedImport()
- }
- })
- .catch((e) => {
- console.error(e)
- this.failedImport()
- })
- },
- async getJSONCollection() {
- if (this.collectionsType.type === "my-collections") {
- this.collectionJson = JSON.stringify(this.myCollections, null, 2)
- } else {
- this.collectionJson = await teamUtils.exportAsJSON(
- this.$apollo,
- this.collectionsType.selectedTeam.id
- )
- }
- return this.collectionJson
- },
- exportJSON() {
- this.getJSONCollection()
- const dataToWrite = this.collectionJson
- const file = new Blob([dataToWrite], { type: "application/json" })
- const a = document.createElement("a")
- const url = URL.createObjectURL(file)
- a.href = url
- // TODO get uri from meta
- a.download = `${url.split("/").pop().split("#")[0].split("?")[0]}`
- document.body.appendChild(a)
- a.click()
- this.$toast.success(this.$t("state.download_started"), {
- icon: "downloading",
- })
- setTimeout(() => {
- document.body.removeChild(a)
- URL.revokeObjectURL(url)
- }, 1000)
- },
- fileImported() {
- this.$toast.success(this.$t("state.file_imported"), {
- icon: "folder_shared",
- })
- },
- failedImport() {
- this.$toast.error(this.$t("import.failed"), {
- icon: "error_outline",
- })
- },
- parsePostmanCollection({ info, name, item }) {
- const hoppscotchCollection = {
- name: "",
- folders: [],
- requests: [],
- }
- hoppscotchCollection.name = info ? info.name : name
- if (item && item.length > 0) {
- for (const collectionItem of item) {
- if (collectionItem.request) {
- if (
- Object.prototype.hasOwnProperty.call(
- hoppscotchCollection,
- "folders"
- )
- ) {
- hoppscotchCollection.name = info ? info.name : name
- hoppscotchCollection.requests.push(
- this.parsePostmanRequest(collectionItem)
- )
- } else {
- hoppscotchCollection.name = name || ""
- hoppscotchCollection.requests.push(
- this.parsePostmanRequest(collectionItem)
- )
- }
- } else if (this.hasFolder(collectionItem)) {
- hoppscotchCollection.folders.push(
- this.parsePostmanCollection(collectionItem)
- )
- } else {
- hoppscotchCollection.requests.push(
- this.parsePostmanRequest(collectionItem)
- )
- }
- }
- }
- return hoppscotchCollection
- },
- parsePostmanRequest({ name, request }) {
- const pwRequest = {
- url: "",
- path: "",
- method: "",
- auth: "",
- httpUser: "",
- httpPassword: "",
- passwordFieldType: "password",
- bearerToken: "",
- headers: [],
- params: [],
- bodyParams: [],
- rawParams: "",
- rawInput: false,
- contentType: "",
- requestType: "",
- name: "",
- }
- pwRequest.name = name
- if (request.url) {
- const requestObjectUrl = request.url.raw.match(
- /^(.+:\/\/[^/]+|{[^/]+})(\/[^?]+|).*$/
- )
- if (requestObjectUrl) {
- pwRequest.url = requestObjectUrl[1]
- pwRequest.path = requestObjectUrl[2] ? requestObjectUrl[2] : ""
- }
- }
- pwRequest.method = request.method
- const itemAuth = request.auth ? request.auth : ""
- const authType = itemAuth ? itemAuth.type : ""
- if (authType === "basic") {
- pwRequest.auth = "Basic Auth"
- pwRequest.httpUser =
- itemAuth.basic[0].key === "username"
- ? itemAuth.basic[0].value
- : itemAuth.basic[1].value
- pwRequest.httpPassword =
- itemAuth.basic[0].key === "password"
- ? itemAuth.basic[0].value
- : itemAuth.basic[1].value
- } else if (authType === "oauth2") {
- pwRequest.auth = "OAuth 2.0"
- pwRequest.bearerToken =
- itemAuth.oauth2[0].key === "accessToken"
- ? itemAuth.oauth2[0].value
- : itemAuth.oauth2[1].value
- } else if (authType === "bearer") {
- pwRequest.auth = "Bearer Token"
- pwRequest.bearerToken = itemAuth.bearer[0].value
- }
- const requestObjectHeaders = request.header
- if (requestObjectHeaders) {
- pwRequest.headers = requestObjectHeaders
- for (const header of pwRequest.headers) {
- delete header.name
- delete header.type
- }
- }
- if (request.url) {
- const requestObjectParams = request.url.query
- if (requestObjectParams) {
- pwRequest.params = requestObjectParams
- for (const param of pwRequest.params) {
- delete param.disabled
- }
- }
- }
- if (request.body) {
- if (request.body.mode === "urlencoded") {
- const params = request.body.urlencoded
- pwRequest.bodyParams = params || []
- for (const param of pwRequest.bodyParams) {
- delete param.type
- }
- } else if (request.body.mode === "raw") {
- pwRequest.rawInput = true
- pwRequest.rawParams = request.body.raw
- }
- }
- return pwRequest
- },
- hasFolder(item) {
- return Object.prototype.hasOwnProperty.call(item, "item")
- },
- },
- })
- </script>
|