SaveRequest.vue 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. <template>
  2. <SmartModal
  3. v-if="show"
  4. :title="`${t('collection.save_as')}`"
  5. @close="hideModal"
  6. >
  7. <template #body>
  8. <div class="flex flex-col px-2">
  9. <div class="flex relative">
  10. <input
  11. id="selectLabelSaveReq"
  12. v-model="requestName"
  13. v-focus
  14. class="input floating-input"
  15. placeholder=" "
  16. type="text"
  17. autocomplete="off"
  18. @keyup.enter="saveRequestAs"
  19. />
  20. <label for="selectLabelSaveReq">
  21. {{ t("request.name") }}
  22. </label>
  23. </div>
  24. <label class="p-4">
  25. {{ t("collection.select_location") }}
  26. </label>
  27. <CollectionsGraphql
  28. v-if="mode === 'graphql'"
  29. :doc="false"
  30. :show-coll-actions="false"
  31. :picked="picked"
  32. :saving-mode="true"
  33. @select="onSelect"
  34. />
  35. <Collections
  36. v-else
  37. :picked="picked"
  38. :save-request="true"
  39. @select="onSelect"
  40. @update-collection="updateColl"
  41. @update-coll-type="onUpdateCollType"
  42. />
  43. </div>
  44. </template>
  45. <template #footer>
  46. <span>
  47. <ButtonPrimary
  48. :label="`${t('action.save')}`"
  49. @click.native="saveRequestAs"
  50. />
  51. <ButtonSecondary
  52. :label="`${t('action.cancel')}`"
  53. @click.native="hideModal"
  54. />
  55. </span>
  56. </template>
  57. </SmartModal>
  58. </template>
  59. <script setup lang="ts">
  60. import { reactive, ref, watch } from "@nuxtjs/composition-api"
  61. import { HoppGQLRequest, isHoppRESTRequest } from "@hoppscotch/data"
  62. import cloneDeep from "lodash/cloneDeep"
  63. import {
  64. editGraphqlRequest,
  65. editRESTRequest,
  66. saveGraphqlRequestAs,
  67. saveRESTRequestAs,
  68. } from "~/newstore/collections"
  69. import { getGQLSession, useGQLRequestName } from "~/newstore/GQLSession"
  70. import {
  71. getRESTRequest,
  72. setRESTSaveContext,
  73. useRESTRequestName,
  74. } from "~/newstore/RESTSession"
  75. import * as teamUtils from "~/helpers/teams/utils"
  76. import { apolloClient } from "~/helpers/apollo"
  77. import { useI18n, useToast } from "~/helpers/utils/composables"
  78. const t = useI18n()
  79. type CollectionType =
  80. | {
  81. type: "my-collections"
  82. }
  83. | {
  84. type: "team-collections"
  85. // TODO: Figure this type out
  86. selectedTeam: {
  87. id: string
  88. }
  89. }
  90. type Picked =
  91. | {
  92. pickedType: "my-request"
  93. folderPath: string
  94. requestIndex: number
  95. }
  96. | {
  97. pickedType: "my-folder"
  98. folderPath: string
  99. }
  100. | {
  101. pickedType: "my-collection"
  102. collectionIndex: number
  103. }
  104. | {
  105. pickedType: "teams-request"
  106. requestID: string
  107. }
  108. | {
  109. pickedType: "teams-folder"
  110. folderID: string
  111. }
  112. | {
  113. pickedType: "teams-collection"
  114. collectionID: string
  115. }
  116. | {
  117. pickedType: "gql-my-request"
  118. folderPath: string
  119. requestIndex: number
  120. }
  121. | {
  122. pickedType: "gql-my-folder"
  123. folderPath: string
  124. }
  125. | {
  126. pickedType: "gql-my-collection"
  127. collectionIndex: number
  128. }
  129. const props = defineProps<{
  130. mode: "rest" | "graphql"
  131. show: boolean
  132. }>()
  133. const emit = defineEmits<{
  134. (e: "hide-modal"): void
  135. }>()
  136. const toast = useToast()
  137. // TODO: Use a better implementation with computed ?
  138. // This implementation can't work across updates to mode prop (which won't happen tho)
  139. const requestName =
  140. props.mode === "rest" ? useRESTRequestName() : useGQLRequestName()
  141. const requestData = reactive({
  142. name: requestName,
  143. collectionIndex: undefined as number | undefined,
  144. folderName: undefined as number | undefined,
  145. requestIndex: undefined as number | undefined,
  146. })
  147. const collectionsType = ref<CollectionType>({
  148. type: "my-collections",
  149. })
  150. // TODO: Figure this type out
  151. const picked = ref<Picked | null>(null)
  152. // Resets
  153. watch(
  154. () => requestData.collectionIndex,
  155. () => {
  156. requestData.folderName = undefined
  157. requestData.requestIndex = undefined
  158. }
  159. )
  160. watch(
  161. () => requestData.folderName,
  162. () => {
  163. requestData.requestIndex = undefined
  164. }
  165. )
  166. // All the methods
  167. const onUpdateCollType = (newCollType: CollectionType) => {
  168. collectionsType.value = newCollType
  169. }
  170. const onSelect = ({ picked: pickedVal }: { picked: Picked | null }) => {
  171. picked.value = pickedVal
  172. }
  173. const hideModal = () => {
  174. picked.value = null
  175. emit("hide-modal")
  176. }
  177. const saveRequestAs = async () => {
  178. if (!requestName.value) {
  179. toast.error(`${t("error.empty_req_name")}`)
  180. return
  181. }
  182. if (picked.value === null) {
  183. toast.error(`${t("collection.select")}`)
  184. return
  185. }
  186. // Clone Deep because objects are shared by reference so updating
  187. // just one bit will update other referenced shared instances
  188. const requestUpdated =
  189. props.mode === "rest"
  190. ? cloneDeep(getRESTRequest())
  191. : cloneDeep(getGQLSession().request)
  192. // // Filter out all REST file inputs
  193. // if (this.mode === "rest" && requestUpdated.bodyParams) {
  194. // requestUpdated.bodyParams = requestUpdated.bodyParams.map((param) =>
  195. // param?.value?.[0] instanceof File ? { ...param, value: "" } : param
  196. // )
  197. // }
  198. if (picked.value.pickedType === "my-request") {
  199. if (!isHoppRESTRequest(requestUpdated))
  200. throw new Error("requestUpdated is not a REST Request")
  201. editRESTRequest(
  202. picked.value.folderPath,
  203. picked.value.requestIndex,
  204. requestUpdated
  205. )
  206. setRESTSaveContext({
  207. originLocation: "user-collection",
  208. folderPath: picked.value.folderPath,
  209. requestIndex: picked.value.requestIndex,
  210. })
  211. requestSaved()
  212. } else if (picked.value.pickedType === "my-folder") {
  213. if (!isHoppRESTRequest(requestUpdated))
  214. throw new Error("requestUpdated is not a REST Request")
  215. const insertionIndex = saveRESTRequestAs(
  216. picked.value.folderPath,
  217. requestUpdated
  218. )
  219. setRESTSaveContext({
  220. originLocation: "user-collection",
  221. folderPath: picked.value.folderPath,
  222. requestIndex: insertionIndex,
  223. })
  224. requestSaved()
  225. } else if (picked.value.pickedType === "my-collection") {
  226. if (!isHoppRESTRequest(requestUpdated))
  227. throw new Error("requestUpdated is not a REST Request")
  228. const insertionIndex = saveRESTRequestAs(
  229. `${picked.value.collectionIndex}`,
  230. requestUpdated
  231. )
  232. setRESTSaveContext({
  233. originLocation: "user-collection",
  234. folderPath: `${picked.value.collectionIndex}`,
  235. requestIndex: insertionIndex,
  236. })
  237. requestSaved()
  238. } else if (picked.value.pickedType === "teams-request") {
  239. if (!isHoppRESTRequest(requestUpdated))
  240. throw new Error("requestUpdated is not a REST Request")
  241. if (collectionsType.value.type !== "team-collections")
  242. throw new Error("Collections Type mismatch")
  243. teamUtils
  244. .overwriteRequestTeams(
  245. apolloClient,
  246. JSON.stringify(requestUpdated),
  247. requestUpdated.name,
  248. picked.value.requestID
  249. )
  250. .then(() => {
  251. requestSaved()
  252. })
  253. .catch((error) => {
  254. toast.error(`${t("profile.no_permission")}`)
  255. throw new Error(error)
  256. })
  257. setRESTSaveContext({
  258. originLocation: "team-collection",
  259. requestID: picked.value.requestID,
  260. })
  261. } else if (picked.value.pickedType === "teams-folder") {
  262. if (!isHoppRESTRequest(requestUpdated))
  263. throw new Error("requestUpdated is not a REST Request")
  264. if (collectionsType.value.type !== "team-collections")
  265. throw new Error("Collections Type mismatch")
  266. try {
  267. const req = await teamUtils.saveRequestAsTeams(
  268. apolloClient,
  269. JSON.stringify(requestUpdated),
  270. requestUpdated.name,
  271. collectionsType.value.selectedTeam.id,
  272. picked.value.folderID
  273. )
  274. if (req && req.id) {
  275. setRESTSaveContext({
  276. originLocation: "team-collection",
  277. requestID: req.id,
  278. teamID: collectionsType.value.selectedTeam.id,
  279. collectionID: picked.value.folderID,
  280. })
  281. }
  282. requestSaved()
  283. } catch (error) {
  284. toast.error(`${t("profile.no_permission")}`)
  285. console.error(error)
  286. }
  287. } else if (picked.value.pickedType === "teams-collection") {
  288. if (!isHoppRESTRequest(requestUpdated))
  289. throw new Error("requestUpdated is not a REST Request")
  290. if (collectionsType.value.type !== "team-collections")
  291. throw new Error("Collections Type mismatch")
  292. try {
  293. const req = await teamUtils.saveRequestAsTeams(
  294. apolloClient,
  295. JSON.stringify(requestUpdated),
  296. requestUpdated.name,
  297. collectionsType.value.selectedTeam.id,
  298. picked.value.collectionID
  299. )
  300. if (req && req.id) {
  301. setRESTSaveContext({
  302. originLocation: "team-collection",
  303. requestID: req.id,
  304. teamID: collectionsType.value.selectedTeam.id,
  305. collectionID: picked.value.collectionID,
  306. })
  307. }
  308. requestSaved()
  309. } catch (error) {
  310. toast.error(`${t("profile.no_permission")}`)
  311. console.error(error)
  312. }
  313. } else if (picked.value.pickedType === "gql-my-request") {
  314. // TODO: Check for GQL request ?
  315. editGraphqlRequest(
  316. picked.value.folderPath,
  317. picked.value.requestIndex,
  318. requestUpdated as HoppGQLRequest
  319. )
  320. requestSaved()
  321. } else if (picked.value.pickedType === "gql-my-folder") {
  322. // TODO: Check for GQL request ?
  323. saveGraphqlRequestAs(
  324. picked.value.folderPath,
  325. requestUpdated as HoppGQLRequest
  326. )
  327. requestSaved()
  328. } else if (picked.value.pickedType === "gql-my-collection") {
  329. // TODO: Check for GQL request ?
  330. saveGraphqlRequestAs(
  331. `${picked.value.collectionIndex}`,
  332. requestUpdated as HoppGQLRequest
  333. )
  334. requestSaved()
  335. }
  336. }
  337. const requestSaved = () => {
  338. toast.success(`${t("request.added")}`)
  339. hideModal()
  340. }
  341. const updateColl = (ev: CollectionType["type"]) => {
  342. collectionsType.value.type = ev
  343. }
  344. </script>