BodyParameters.vue 8.0 KB

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