RawBody.vue 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. <template>
  2. <div class="flex flex-col flex-1">
  3. <div
  4. class="sticky z-10 flex items-center justify-between pl-4 border-b bg-primary border-dividerLight top-upperMobileRawStickyFold sm:top-upperMobileRawTertiaryStickyFold"
  5. >
  6. <label class="font-semibold text-secondaryLight">
  7. {{ t("request.raw_body") }}
  8. </label>
  9. <div class="flex">
  10. <ButtonSecondary
  11. v-tippy="{ theme: 'tooltip' }"
  12. to="https://docs.hoppscotch.io/features/body"
  13. blank
  14. :title="t('app.wiki')"
  15. svg="help-circle"
  16. />
  17. <ButtonSecondary
  18. v-tippy="{ theme: 'tooltip' }"
  19. :title="t('state.linewrap')"
  20. :class="{ '!text-accent': linewrapEnabled }"
  21. svg="wrap-text"
  22. @click.native.prevent="linewrapEnabled = !linewrapEnabled"
  23. />
  24. <ButtonSecondary
  25. v-tippy="{ theme: 'tooltip' }"
  26. :title="t('action.clear')"
  27. svg="trash-2"
  28. @click.native="clearContent"
  29. />
  30. <ButtonSecondary
  31. v-if="contentType && contentType.endsWith('json')"
  32. ref="prettifyRequest"
  33. v-tippy="{ theme: 'tooltip' }"
  34. :title="t('action.prettify')"
  35. :svg="prettifyIcon"
  36. @click.native="prettifyRequestBody"
  37. />
  38. <label for="payload">
  39. <ButtonSecondary
  40. v-tippy="{ theme: 'tooltip' }"
  41. :title="t('import.title')"
  42. svg="file-plus"
  43. @click.native="$refs.payload.click()"
  44. />
  45. </label>
  46. <input
  47. ref="payload"
  48. class="input"
  49. name="payload"
  50. type="file"
  51. @change="uploadPayload"
  52. />
  53. </div>
  54. </div>
  55. <div ref="rawBodyParameters" class="flex flex-col flex-1"></div>
  56. </div>
  57. </template>
  58. <script setup lang="ts">
  59. import { computed, reactive, Ref, ref } from "@nuxtjs/composition-api"
  60. import * as TO from "fp-ts/TaskOption"
  61. import { pipe } from "fp-ts/function"
  62. import { HoppRESTReqBody, ValidContentTypes } from "@hoppscotch/data"
  63. import { refAutoReset } from "@vueuse/core"
  64. import { useCodemirror } from "~/helpers/editor/codemirror"
  65. import { getEditorLangForMimeType } from "~/helpers/editorutils"
  66. import { pluckRef, useI18n, useToast } from "~/helpers/utils/composables"
  67. import { isJSONContentType } from "~/helpers/utils/contenttypes"
  68. import { useRESTRequestBody } from "~/newstore/RESTSession"
  69. import jsonLinter from "~/helpers/editor/linting/json"
  70. import { readFileAsText } from "~/helpers/functional/files"
  71. type PossibleContentTypes = Exclude<
  72. ValidContentTypes,
  73. "multipart/form-data" | "application/x-www-form-urlencoded"
  74. >
  75. const t = useI18n()
  76. const props = defineProps<{
  77. contentType: PossibleContentTypes
  78. }>()
  79. const toast = useToast()
  80. const rawParamsBody = pluckRef(
  81. useRESTRequestBody() as Ref<
  82. HoppRESTReqBody & {
  83. contentType: PossibleContentTypes
  84. }
  85. >,
  86. "body"
  87. )
  88. const prettifyIcon = refAutoReset<"wand" | "check" | "info">("wand", 1000)
  89. const rawInputEditorLang = computed(() =>
  90. getEditorLangForMimeType(props.contentType)
  91. )
  92. const langLinter = computed(() =>
  93. isJSONContentType(props.contentType) ? jsonLinter : null
  94. )
  95. const linewrapEnabled = ref(true)
  96. const rawBodyParameters = ref<any | null>(null)
  97. useCodemirror(
  98. rawBodyParameters,
  99. rawParamsBody,
  100. reactive({
  101. extendedEditorConfig: {
  102. lineWrapping: linewrapEnabled,
  103. mode: rawInputEditorLang,
  104. placeholder: t("request.raw_body").toString(),
  105. },
  106. linter: langLinter,
  107. completer: null,
  108. environmentHighlights: true,
  109. })
  110. )
  111. const clearContent = () => {
  112. rawParamsBody.value = ""
  113. }
  114. const uploadPayload = async (e: InputEvent) => {
  115. await pipe(
  116. (e.target as HTMLInputElement).files?.[0],
  117. TO.of,
  118. TO.chain(TO.fromPredicate((f): f is File => f !== undefined)),
  119. TO.chain(readFileAsText),
  120. TO.matchW(
  121. () => toast.error(`${t("action.choose_file")}`),
  122. (result) => {
  123. rawParamsBody.value = result
  124. toast.success(`${t("state.file_imported")}`)
  125. }
  126. )
  127. )()
  128. }
  129. const prettifyRequestBody = () => {
  130. try {
  131. const jsonObj = JSON.parse(rawParamsBody.value)
  132. rawParamsBody.value = JSON.stringify(jsonObj, null, 2)
  133. prettifyIcon.value = "check"
  134. } catch (e) {
  135. console.error(e)
  136. prettifyIcon.value = "info"
  137. toast.error(`${t("error.json_prettify_invalid_body")}`)
  138. }
  139. }
  140. </script>