ImportExport.vue 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  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. window.open(res.html_url)
  143. })
  144. .catch((e) => {
  145. this.$toast.error(this.$t("error.something_went_wrong"))
  146. console.error(e)
  147. })
  148. },
  149. async readEnvironmentGist() {
  150. const gist = prompt(this.$t("import.gist_url"))
  151. if (!gist) return
  152. await this.$axios
  153. .$get(`https://api.github.com/gists/${gist.split("/").pop()}`, {
  154. headers: {
  155. Accept: "application/vnd.github.v3+json",
  156. },
  157. })
  158. .then(({ files }) => {
  159. const environments = JSON.parse(Object.values(files)[0].content)
  160. replaceEnvironments(environments)
  161. this.fileImported()
  162. })
  163. .catch((e) => {
  164. this.failedImport()
  165. console.error(e)
  166. })
  167. },
  168. hideModal() {
  169. this.$emit("hide-modal")
  170. },
  171. openDialogChooseFileToReplaceWith() {
  172. this.$refs.inputChooseFileToReplaceWith.click()
  173. },
  174. openDialogChooseFileToImportFrom() {
  175. this.$refs.inputChooseFileToImportFrom.click()
  176. },
  177. replaceWithJSON() {
  178. const reader = new FileReader()
  179. reader.onload = ({ target }) => {
  180. const content = target.result
  181. const environments = JSON.parse(content)
  182. replaceEnvironments(environments)
  183. }
  184. reader.readAsText(this.$refs.inputChooseFileToReplaceWith.files[0])
  185. this.fileImported()
  186. this.$refs.inputChooseFileToReplaceWith.value = ""
  187. },
  188. importFromJSON() {
  189. const reader = new FileReader()
  190. reader.onload = ({ target }) => {
  191. const content = target.result
  192. const importFileObj = JSON.parse(content)
  193. if (
  194. importFileObj._postman_variable_scope === "environment" ||
  195. importFileObj._postman_variable_scope === "globals"
  196. ) {
  197. this.importFromPostman(importFileObj)
  198. } else {
  199. this.importFromHoppscotch(importFileObj)
  200. }
  201. }
  202. reader.readAsText(this.$refs.inputChooseFileToImportFrom.files[0])
  203. this.$refs.inputChooseFileToImportFrom.value = ""
  204. },
  205. importFromHoppscotch(environments) {
  206. appendEnvironments(environments)
  207. this.fileImported()
  208. },
  209. importFromPostman({ name, values }) {
  210. const environment = { name, variables: [] }
  211. values.forEach(({ key, value }) =>
  212. environment.variables.push({ key, value })
  213. )
  214. const environments = [environment]
  215. this.importFromHoppscotch(environments)
  216. },
  217. exportJSON() {
  218. const dataToWrite = this.environmentJson
  219. const file = new Blob([dataToWrite], { type: "application/json" })
  220. const a = document.createElement("a")
  221. const url = URL.createObjectURL(file)
  222. a.href = url
  223. // TODO get uri from meta
  224. a.download = `${url.split("/").pop().split("#")[0].split("?")[0]}.json`
  225. document.body.appendChild(a)
  226. a.click()
  227. this.$toast.success(this.$t("state.download_started"))
  228. setTimeout(() => {
  229. document.body.removeChild(a)
  230. URL.revokeObjectURL(url)
  231. }, 1000)
  232. },
  233. fileImported() {
  234. this.$toast.success(this.$t("state.file_imported"))
  235. },
  236. },
  237. })
  238. </script>