ImportExport.vue 7.2 KB

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