CodegenModal.vue 5.2 KB

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