ImportExport.vue 7.1 KB

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