SaveRequest.vue 10 KB

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