Body.vue 5.4 KB

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