ImportExport.vue 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. <template>
  2. <SmartModal
  3. v-if="show"
  4. :title="`${$t('modal.import_export')} ${$t('environment.title')}`"
  5. max-width="sm:max-w-md"
  6. @close="hideModal"
  7. >
  8. <template #actions>
  9. <span>
  10. <tippy ref="options" interactive trigger="click" theme="popover" arrow>
  11. <template #trigger>
  12. <ButtonSecondary
  13. v-tippy="{ theme: 'tooltip' }"
  14. :title="$t('action.more')"
  15. svg="more-vertical"
  16. />
  17. </template>
  18. <SmartItem
  19. icon="assignment_returned"
  20. :label="$t('import.from_gist')"
  21. @click.native="
  22. readEnvironmentGist
  23. $refs.options.tippy().hide()
  24. "
  25. />
  26. <span
  27. v-tippy="{ theme: 'tooltip' }"
  28. :title="
  29. !currentUser
  30. ? $t('export.require_github')
  31. : currentUser.provider !== 'github.com'
  32. ? $t('export.require_github')
  33. : null
  34. "
  35. >
  36. <SmartItem
  37. :disabled="
  38. !currentUser
  39. ? true
  40. : currentUser.provider !== 'github.com'
  41. ? true
  42. : false
  43. "
  44. icon="assignment_turned_in"
  45. :label="$t('export.create_secret_gist')"
  46. @click.native="
  47. createEnvironmentGist
  48. $refs.options.tippy().hide()
  49. "
  50. />
  51. </span>
  52. </tippy>
  53. </span>
  54. </template>
  55. <template #body>
  56. <div class="flex flex-col space-y-2">
  57. <SmartItem
  58. v-tippy="{ theme: 'tooltip' }"
  59. :title="$t('action.replace_current')"
  60. svg="file"
  61. :label="$t('action.replace_json')"
  62. @click.native="openDialogChooseFileToReplaceWith"
  63. />
  64. <input
  65. ref="inputChooseFileToReplaceWith"
  66. class="input"
  67. type="file"
  68. accept="application/json"
  69. @change="replaceWithJSON"
  70. />
  71. <SmartItem
  72. v-tippy="{ theme: 'tooltip' }"
  73. :title="$t('action.preserve_current')"
  74. svg="folder-plus"
  75. :label="$t('import.json')"
  76. @click.native="openDialogChooseFileToImportFrom"
  77. />
  78. <input
  79. ref="inputChooseFileToImportFrom"
  80. class="input"
  81. type="file"
  82. accept="application/json"
  83. @change="importFromJSON"
  84. />
  85. <SmartItem
  86. v-tippy="{ theme: 'tooltip' }"
  87. :title="$t('action.download_file')"
  88. svg="download"
  89. :label="$t('export.as_json')"
  90. @click.native="exportJSON"
  91. />
  92. </div>
  93. </template>
  94. </SmartModal>
  95. </template>
  96. <script>
  97. import { defineComponent } from "@nuxtjs/composition-api"
  98. import { currentUser$ } from "~/helpers/fb/auth"
  99. import { useReadonlyStream } from "~/helpers/utils/composables"
  100. import {
  101. environments$,
  102. replaceEnvironments,
  103. appendEnvironments,
  104. } from "~/newstore/environments"
  105. export default defineComponent({
  106. props: {
  107. show: Boolean,
  108. },
  109. setup() {
  110. return {
  111. environments: useReadonlyStream(environments$, []),
  112. currentUser: useReadonlyStream(currentUser$, null),
  113. }
  114. },
  115. computed: {
  116. environmentJson() {
  117. return JSON.stringify(this.environments, null, 2)
  118. },
  119. },
  120. methods: {
  121. async createEnvironmentGist() {
  122. await this.$axios
  123. .$post(
  124. "https://api.github.com/gists",
  125. {
  126. files: {
  127. "hoppscotch-environments.json": {
  128. content: this.environmentJson,
  129. },
  130. },
  131. },
  132. {
  133. headers: {
  134. Authorization: `token ${this.currentUser.accessToken}`,
  135. Accept: "application/vnd.github.v3+json",
  136. },
  137. }
  138. )
  139. .then((res) => {
  140. this.$toast.success(this.$t("export.gist_created"))
  141. window.open(res.html_url)
  142. })
  143. .catch((e) => {
  144. this.$toast.error(this.$t("error.something_went_wrong"))
  145. console.error(e)
  146. })
  147. },
  148. async readEnvironmentGist() {
  149. const gist = prompt(this.$t("import.gist_url"))
  150. if (!gist) return
  151. await this.$axios
  152. .$get(`https://api.github.com/gists/${gist.split("/").pop()}`, {
  153. headers: {
  154. Accept: "application/vnd.github.v3+json",
  155. },
  156. })
  157. .then(({ files }) => {
  158. const environments = JSON.parse(Object.values(files)[0].content)
  159. replaceEnvironments(environments)
  160. this.fileImported()
  161. })
  162. .catch((e) => {
  163. this.failedImport()
  164. console.error(e)
  165. })
  166. },
  167. hideModal() {
  168. this.$emit("hide-modal")
  169. },
  170. openDialogChooseFileToReplaceWith() {
  171. this.$refs.inputChooseFileToReplaceWith.click()
  172. },
  173. openDialogChooseFileToImportFrom() {
  174. this.$refs.inputChooseFileToImportFrom.click()
  175. },
  176. replaceWithJSON() {
  177. const reader = new FileReader()
  178. reader.onload = ({ target }) => {
  179. const content = target.result
  180. const environments = JSON.parse(content)
  181. replaceEnvironments(environments)
  182. }
  183. reader.readAsText(this.$refs.inputChooseFileToReplaceWith.files[0])
  184. this.fileImported()
  185. this.$refs.inputChooseFileToReplaceWith.value = ""
  186. },
  187. importFromJSON() {
  188. const reader = new FileReader()
  189. reader.onload = ({ target }) => {
  190. const content = target.result
  191. const importFileObj = JSON.parse(content)
  192. if (
  193. importFileObj._postman_variable_scope === "environment" ||
  194. importFileObj._postman_variable_scope === "globals"
  195. ) {
  196. this.importFromPostman(importFileObj)
  197. } else {
  198. this.importFromHoppscotch(importFileObj)
  199. }
  200. }
  201. reader.readAsText(this.$refs.inputChooseFileToImportFrom.files[0])
  202. this.$refs.inputChooseFileToImportFrom.value = ""
  203. },
  204. importFromHoppscotch(environments) {
  205. appendEnvironments(environments)
  206. this.fileImported()
  207. },
  208. importFromPostman({ name, values }) {
  209. const environment = { name, variables: [] }
  210. values.forEach(({ key, value }) =>
  211. environment.variables.push({ key, value })
  212. )
  213. const environments = [environment]
  214. this.importFromHoppscotch(environments)
  215. },
  216. exportJSON() {
  217. const dataToWrite = this.environmentJson
  218. const file = new Blob([dataToWrite], { type: "application/json" })
  219. const a = document.createElement("a")
  220. const url = URL.createObjectURL(file)
  221. a.href = url
  222. // TODO get uri from meta
  223. a.download = `${url.split("/").pop().split("#")[0].split("?")[0]}.json`
  224. document.body.appendChild(a)
  225. a.click()
  226. this.$toast.success(this.$t("state.download_started"))
  227. setTimeout(() => {
  228. document.body.removeChild(a)
  229. URL.revokeObjectURL(url)
  230. }, 1000)
  231. },
  232. fileImported() {
  233. this.$toast.success(this.$t("state.file_imported"))
  234. },
  235. },
  236. })
  237. </script>