BodyParameters.vue 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. <template>
  2. <AppSection label="bodyParameters">
  3. <div
  4. class="bg-primary border-b border-dividerLight flex flex-1 top-upperTertiaryStickyFold pl-4 z-10 sticky items-center justify-between"
  5. >
  6. <label class="font-semibold text-secondaryLight">
  7. {{ $t("request.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('action.clear_all')"
  20. svg="trash-2"
  21. @click.native="clearContent"
  22. />
  23. <ButtonSecondary
  24. v-tippy="{ theme: 'tooltip' }"
  25. :title="$t('add.new')"
  26. svg="plus"
  27. @click.native="addBodyParam"
  28. />
  29. </div>
  30. </div>
  31. <div
  32. v-for="(param, index) in bodyParams"
  33. :key="`param-${index}`"
  34. class="divide-dividerLight divide-x border-b border-dividerLight flex"
  35. >
  36. <SmartEnvInput
  37. v-model="param.key"
  38. :placeholder="`${$t('count.parameter', { count: index + 1 })}`"
  39. styles="
  40. bg-transparent
  41. flex
  42. flex-1
  43. py-1
  44. px-4
  45. "
  46. @change="
  47. updateBodyParam(index, {
  48. key: $event,
  49. value: param.value,
  50. active: param.active,
  51. isFile: param.isFile,
  52. })
  53. "
  54. />
  55. <div v-if="param.isFile" class="file-chips-container hide-scrollbar">
  56. <div class="space-x-2 file-chips-wrapper">
  57. <SmartDeletableChip
  58. v-for="(file, fileIndex) in param.value"
  59. :key="`param-${index}-file-${fileIndex}`"
  60. @chip-delete="chipDelete(index, fileIndex)"
  61. >
  62. {{ file.name }}
  63. </SmartDeletableChip>
  64. </div>
  65. </div>
  66. <span v-else class="flex flex-1">
  67. <SmartEnvInput
  68. v-model="param.value"
  69. :placeholder="`${$t('count.value', { count: index + 1 })}`"
  70. styles="
  71. bg-transparent
  72. flex
  73. flex-1
  74. py-1
  75. px-4
  76. "
  77. @change="
  78. updateBodyParam(index, {
  79. key: param.key,
  80. value: $event,
  81. active: param.active,
  82. isFile: param.isFile,
  83. })
  84. "
  85. />
  86. </span>
  87. <span>
  88. <label for="attachment" class="p-0">
  89. <ButtonSecondary
  90. class="w-full"
  91. svg="paperclip"
  92. @click.native="$refs.attachment[index].click()"
  93. />
  94. </label>
  95. <input
  96. ref="attachment"
  97. class="input"
  98. name="attachment"
  99. type="file"
  100. multiple
  101. @change="setRequestAttachment(index, param, $event)"
  102. />
  103. </span>
  104. <span>
  105. <ButtonSecondary
  106. v-tippy="{ theme: 'tooltip' }"
  107. :title="
  108. param.hasOwnProperty('active')
  109. ? param.active
  110. ? $t('action.turn_off')
  111. : $t('action.turn_on')
  112. : $t('action.turn_off')
  113. "
  114. :svg="
  115. param.hasOwnProperty('active')
  116. ? param.active
  117. ? 'check-circle'
  118. : 'circle'
  119. : 'check-circle'
  120. "
  121. color="green"
  122. @click.native="
  123. updateBodyParam(index, {
  124. key: param.key,
  125. value: param.value,
  126. active: param.hasOwnProperty('active') ? !param.active : false,
  127. isFile: param.isFile,
  128. })
  129. "
  130. />
  131. </span>
  132. <span>
  133. <ButtonSecondary
  134. v-tippy="{ theme: 'tooltip' }"
  135. :title="$t('action.remove')"
  136. svg="trash"
  137. color="red"
  138. @click.native="deleteBodyParam(index)"
  139. />
  140. </span>
  141. </div>
  142. <div
  143. v-if="bodyParams.length === 0"
  144. class="flex flex-col text-secondaryLight p-4 items-center justify-center"
  145. >
  146. <img
  147. :src="`/images/states/${$colorMode.value}/upload_single_file.svg`"
  148. loading="lazy"
  149. class="flex-col object-contain object-center h-16 my-4 w-16 inline-flex"
  150. :alt="$t('empty.body')"
  151. />
  152. <span class="text-center pb-4">
  153. {{ $t("empty.body") }}
  154. </span>
  155. <ButtonSecondary
  156. :label="`${$t('add.new')}`"
  157. filled
  158. svg="plus"
  159. class="mb-4"
  160. @click.native="addBodyParam"
  161. />
  162. </div>
  163. </AppSection>
  164. </template>
  165. <script lang="ts">
  166. import { defineComponent, onMounted, Ref, watch } from "@nuxtjs/composition-api"
  167. import { FormDataKeyValue } from "@hoppscotch/data"
  168. import { pluckRef } from "~/helpers/utils/composables"
  169. import {
  170. addFormDataEntry,
  171. deleteAllFormDataEntries,
  172. deleteFormDataEntry,
  173. updateFormDataEntry,
  174. useRESTRequestBody,
  175. } from "~/newstore/RESTSession"
  176. export default defineComponent({
  177. setup() {
  178. const bodyParams = pluckRef<any, any>(useRESTRequestBody(), "body") as Ref<
  179. FormDataKeyValue[]
  180. >
  181. const addBodyParam = () => {
  182. addFormDataEntry({ key: "", value: "", active: true, isFile: false })
  183. }
  184. const updateBodyParam = (index: number, entry: FormDataKeyValue) => {
  185. updateFormDataEntry(index, entry)
  186. }
  187. const deleteBodyParam = (index: number) => {
  188. deleteFormDataEntry(index)
  189. }
  190. const clearContent = () => {
  191. deleteAllFormDataEntries()
  192. }
  193. const chipDelete = (paramIndex: number, fileIndex: number) => {
  194. const entry = bodyParams.value[paramIndex]
  195. if (entry.isFile) {
  196. entry.value.splice(fileIndex, 1)
  197. if (entry.value.length === 0) {
  198. updateFormDataEntry(paramIndex, {
  199. ...entry,
  200. isFile: false,
  201. value: "",
  202. })
  203. return
  204. }
  205. }
  206. updateFormDataEntry(paramIndex, entry)
  207. }
  208. const setRequestAttachment = (
  209. index: number,
  210. entry: FormDataKeyValue,
  211. event: InputEvent
  212. ) => {
  213. const fileEntry: FormDataKeyValue = {
  214. ...entry,
  215. isFile: true,
  216. value: Array.from((event.target as HTMLInputElement).files!),
  217. }
  218. updateFormDataEntry(index, fileEntry)
  219. }
  220. watch(
  221. bodyParams,
  222. () => {
  223. if (
  224. bodyParams.value.length > 0 &&
  225. (bodyParams.value[bodyParams.value.length - 1].key !== "" ||
  226. bodyParams.value[bodyParams.value.length - 1].value !== "")
  227. )
  228. addBodyParam()
  229. },
  230. { deep: true }
  231. )
  232. onMounted(() => {
  233. if (!bodyParams.value?.length) {
  234. addBodyParam()
  235. }
  236. })
  237. return {
  238. bodyParams,
  239. addBodyParam,
  240. updateBodyParam,
  241. deleteBodyParam,
  242. clearContent,
  243. setRequestAttachment,
  244. chipDelete,
  245. }
  246. },
  247. })
  248. </script>
  249. <style scoped lang="scss">
  250. .file-chips-container {
  251. @apply flex flex-1;
  252. @apply whitespace-nowrap;
  253. @apply overflow-auto;
  254. @apply bg-transparent;
  255. .file-chips-wrapper {
  256. @apply flex;
  257. @apply px-4;
  258. @apply py-1;
  259. @apply w-0;
  260. }
  261. }
  262. </style>