Body.vue 4.7 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-upperMobileSecondaryStickyFold sm:top-upperSecondaryStickyFold"
  5. >
  6. <span class="flex items-center">
  7. <label class="font-semibold text-secondaryLight">
  8. {{ $t("request.content_type") }}
  9. </label>
  10. <tippy
  11. ref="contentTypeOptions"
  12. interactive
  13. trigger="click"
  14. theme="popover"
  15. arrow
  16. >
  17. <template #trigger>
  18. <span class="select-wrapper">
  19. <ButtonSecondary
  20. :label="contentType || $t('state.none').toLowerCase()"
  21. class="pr-8 ml-2 rounded-none"
  22. />
  23. </span>
  24. </template>
  25. <div class="flex flex-col" role="menu">
  26. <SmartItem
  27. :label="$t('state.none').toLowerCase()"
  28. :info-icon="contentType === null ? 'done' : ''"
  29. :active-info-icon="contentType === null"
  30. @click.native="
  31. () => {
  32. contentType = null
  33. $refs.contentTypeOptions.tippy().hide()
  34. }
  35. "
  36. />
  37. <SmartItem
  38. v-for="(contentTypeItem, index) in validContentTypes"
  39. :key="`contentTypeItem-${index}`"
  40. :label="contentTypeItem"
  41. :info-icon="contentTypeItem === contentType ? 'done' : ''"
  42. :active-info-icon="contentTypeItem === contentType"
  43. @click.native="
  44. () => {
  45. contentType = contentTypeItem
  46. $refs.contentTypeOptions.tippy().hide()
  47. }
  48. "
  49. />
  50. </div>
  51. </tippy>
  52. <ButtonSecondary
  53. v-tippy="{ theme: 'tooltip', allowHTML: true }"
  54. :title="$t('request.override_help')"
  55. :label="
  56. overridenContentType
  57. ? `${$t('request.overriden')}: ${overridenContentType}`
  58. : $t('request.override')
  59. "
  60. :svg="overridenContentType ? 'info' : 'refresh-cw'"
  61. :class="[
  62. '!px-1 !py-0.5',
  63. {
  64. 'text-yellow-500 hover:text-yellow-500': overridenContentType,
  65. },
  66. ]"
  67. filled
  68. outline
  69. @click.native="contentTypeOverride('headers')"
  70. />
  71. </span>
  72. </div>
  73. <HttpBodyParameters v-if="contentType === 'multipart/form-data'" />
  74. <HttpURLEncodedParams
  75. v-else-if="contentType === 'application/x-www-form-urlencoded'"
  76. />
  77. <HttpRawBody v-else-if="contentType !== null" :content-type="contentType" />
  78. <div
  79. v-if="contentType == null"
  80. class="flex flex-col items-center justify-center p-4 text-secondaryLight"
  81. >
  82. <img
  83. :src="`/images/states/${$colorMode.value}/upload_single_file.svg`"
  84. loading="lazy"
  85. class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
  86. :alt="`${$t('empty.body')}`"
  87. />
  88. <span class="pb-4 text-center">{{ $t("empty.body") }}</span>
  89. <ButtonSecondary
  90. outline
  91. :label="`${$t('app.documentation')}`"
  92. to="https://docs.hoppscotch.io/features/body"
  93. blank
  94. svg="external-link"
  95. reverse
  96. class="mb-4"
  97. />
  98. </div>
  99. </div>
  100. </template>
  101. <script setup lang="ts">
  102. import { computed } from "@nuxtjs/composition-api"
  103. import { pipe } from "fp-ts/function"
  104. import * as A from "fp-ts/Array"
  105. import * as O from "fp-ts/Option"
  106. import { RequestOptionTabs } from "./RequestOptions.vue"
  107. import { useStream } from "~/helpers/utils/composables"
  108. import { knownContentTypes } from "~/helpers/utils/contenttypes"
  109. import {
  110. restContentType$,
  111. restHeaders$,
  112. setRESTContentType,
  113. setRESTHeaders,
  114. addRESTHeader,
  115. } from "~/newstore/RESTSession"
  116. const emit = defineEmits<{
  117. (e: "change-tab", value: string): void
  118. }>()
  119. const validContentTypes = Object.keys(knownContentTypes)
  120. const contentType = useStream(restContentType$, null, setRESTContentType)
  121. // The functional headers list (the headers actually in the system)
  122. const headers = useStream(restHeaders$, [], setRESTHeaders)
  123. const overridenContentType = computed(() =>
  124. pipe(
  125. headers.value,
  126. A.findLast((h) => h.key.toLowerCase() === "content-type" && h.active),
  127. O.map((h) => h.value),
  128. O.getOrElse(() => "")
  129. )
  130. )
  131. const contentTypeOverride = (tab: RequestOptionTabs) => {
  132. emit("change-tab", tab)
  133. if (!isContentTypeAlreadyExist()) {
  134. addRESTHeader({
  135. key: "Content-Type",
  136. value: "",
  137. active: true,
  138. })
  139. }
  140. }
  141. const isContentTypeAlreadyExist = () => {
  142. return pipe(
  143. headers.value,
  144. A.some((e) => e.key.toLowerCase() === "content-type")
  145. )
  146. }
  147. </script>