CodegenModal.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. <template>
  2. <SmartModal
  3. v-if="show"
  4. dialog
  5. :title="`${t('request.generate_code')}`"
  6. @close="hideModal"
  7. >
  8. <template #body>
  9. <div class="flex flex-col px-2">
  10. <label for="requestType" class="px-4 pb-4">
  11. {{ t("request.choose_language") }}
  12. </label>
  13. <tippy ref="options" interactive trigger="click" theme="popover" arrow>
  14. <template #trigger>
  15. <span class="select-wrapper">
  16. <ButtonSecondary
  17. :label="
  18. CodegenDefinitions.find((x) => x.name === codegenType).caption
  19. "
  20. outline
  21. class="flex-1 pr-8"
  22. />
  23. </span>
  24. </template>
  25. <div class="flex flex-col space-y-2">
  26. <div class="sticky top-0">
  27. <input
  28. v-model="searchQuery"
  29. type="search"
  30. autocomplete="off"
  31. class="flex w-full p-4 py-2 !bg-popover input"
  32. :placeholder="`${t('action.search')}`"
  33. />
  34. </div>
  35. <div class="flex flex-col" role="menu">
  36. <SmartItem
  37. v-for="codegen in filteredCodegenDefinitions"
  38. :key="codegen.name"
  39. :label="codegen.caption"
  40. :info-icon="codegen.name === codegenType ? 'done' : ''"
  41. :active-info-icon="codegen.name === codegenType"
  42. @click.native="
  43. () => {
  44. codegenType = codegen.name
  45. options.tippy().hide()
  46. }
  47. "
  48. />
  49. </div>
  50. </div>
  51. </tippy>
  52. <div class="flex justify-between flex-1">
  53. <label for="generatedCode" class="p-4">
  54. {{ t("request.generated_code") }}
  55. </label>
  56. </div>
  57. <div
  58. v-if="errorState"
  59. class="w-full px-4 py-2 overflow-auto font-mono text-red-400 whitespace-normal rounded bg-primaryLight"
  60. >
  61. {{ t("error.something_went_wrong") }}
  62. </div>
  63. <div
  64. v-else-if="codegenType"
  65. ref="generatedCode"
  66. class="border rounded border-dividerLight"
  67. ></div>
  68. </div>
  69. </template>
  70. <template #footer>
  71. <span class="flex">
  72. <ButtonPrimary
  73. :label="`${t('action.copy')}`"
  74. :svg="copyIcon"
  75. @click.native="copyRequestCode"
  76. />
  77. <ButtonSecondary
  78. :label="`${t('action.dismiss')}`"
  79. @click.native="hideModal"
  80. />
  81. </span>
  82. </template>
  83. </SmartModal>
  84. </template>
  85. <script setup lang="ts">
  86. import { computed, ref, watch } from "@nuxtjs/composition-api"
  87. import * as O from "fp-ts/Option"
  88. import { Environment, makeRESTRequest } from "@hoppscotch/data"
  89. import { refAutoReset } from "@vueuse/core"
  90. import { useCodemirror } from "~/helpers/editor/codemirror"
  91. import { copyToClipboard } from "~/helpers/utils/clipboard"
  92. import {
  93. getEffectiveRESTRequest,
  94. resolvesEnvsInBody,
  95. } from "~/helpers/utils/EffectiveURL"
  96. import { getAggregateEnvs } from "~/newstore/environments"
  97. import { getRESTRequest } from "~/newstore/RESTSession"
  98. import { useI18n, useToast } from "~/helpers/utils/composables"
  99. import {
  100. CodegenDefinitions,
  101. CodegenName,
  102. generateCode,
  103. } from "~/helpers/new-codegen"
  104. const t = useI18n()
  105. const props = defineProps<{
  106. show: boolean
  107. }>()
  108. const emit = defineEmits<{
  109. (e: "hide-modal"): void
  110. }>()
  111. const toast = useToast()
  112. const options = ref<any | null>(null)
  113. const request = ref(getRESTRequest())
  114. const codegenType = ref<CodegenName>("shell-curl")
  115. const errorState = ref(false)
  116. const copyIcon = refAutoReset<"copy" | "check">("copy", 1000)
  117. const requestCode = computed(() => {
  118. const aggregateEnvs = getAggregateEnvs()
  119. const env: Environment = {
  120. name: "Env",
  121. variables: aggregateEnvs,
  122. }
  123. const effectiveRequest = getEffectiveRESTRequest(request.value, env)
  124. if (!props.show) return ""
  125. const result = generateCode(
  126. codegenType.value,
  127. makeRESTRequest({
  128. ...effectiveRequest,
  129. body: resolvesEnvsInBody(effectiveRequest.body, env),
  130. headers: effectiveRequest.effectiveFinalHeaders.map((header) => ({
  131. ...header,
  132. active: true,
  133. })),
  134. params: effectiveRequest.effectiveFinalParams.map((param) => ({
  135. ...param,
  136. active: true,
  137. })),
  138. endpoint: effectiveRequest.effectiveFinalURL,
  139. })
  140. )
  141. if (O.isSome(result)) {
  142. errorState.value = false
  143. return result.value
  144. } else {
  145. errorState.value = true
  146. return ""
  147. }
  148. })
  149. const generatedCode = ref<any | null>(null)
  150. useCodemirror(generatedCode, requestCode, {
  151. extendedEditorConfig: {
  152. mode: "text/plain",
  153. readOnly: true,
  154. },
  155. linter: null,
  156. completer: null,
  157. environmentHighlights: false,
  158. })
  159. watch(
  160. () => props.show,
  161. (goingToShow) => {
  162. if (goingToShow) {
  163. request.value = getRESTRequest()
  164. }
  165. }
  166. )
  167. const hideModal = () => emit("hide-modal")
  168. const copyRequestCode = () => {
  169. copyToClipboard(requestCode.value)
  170. copyIcon.value = "check"
  171. toast.success(`${t("state.copied_to_clipboard")}`)
  172. }
  173. const searchQuery = ref("")
  174. const filteredCodegenDefinitions = computed(() => {
  175. return CodegenDefinitions.filter((obj) =>
  176. Object.values(obj).some((val) => val.includes(searchQuery.value))
  177. )
  178. })
  179. </script>