Headers.vue 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. <template>
  2. <AppSection label="headers">
  3. <div
  4. class="
  5. bg-primary
  6. border-b border-dividerLight
  7. flex flex-1
  8. top-upperSecondaryStickyFold
  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.header_list") }}
  18. </label>
  19. <div class="flex">
  20. <ButtonSecondary
  21. v-tippy="{ theme: 'tooltip' }"
  22. to="https://docs.hoppscotch.io/features/headers"
  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. :disabled="bulkMode"
  32. @click.native="clearContent"
  33. />
  34. <ButtonSecondary
  35. v-tippy="{ theme: 'tooltip' }"
  36. :title="$t('state.bulk_mode')"
  37. svg="edit"
  38. :class="{ '!text-accent': bulkMode }"
  39. @click.native="bulkMode = !bulkMode"
  40. />
  41. <ButtonSecondary
  42. v-tippy="{ theme: 'tooltip' }"
  43. :title="$t('add.new')"
  44. svg="plus"
  45. :disabled="bulkMode"
  46. @click.native="addHeader"
  47. />
  48. </div>
  49. </div>
  50. <div v-if="bulkMode" class="flex">
  51. <textarea-autosize
  52. v-model="bulkHeaders"
  53. v-focus
  54. name="bulk-headers"
  55. class="
  56. bg-transparent
  57. border-b border-dividerLight
  58. flex
  59. font-mono
  60. flex-1
  61. py-2
  62. px-4
  63. whitespace-pre
  64. resize-y
  65. overflow-auto
  66. "
  67. rows="10"
  68. :placeholder="$t('state.bulk_mode_placeholder')"
  69. />
  70. </div>
  71. <div v-else>
  72. <div
  73. v-for="(header, index) in headers$"
  74. :key="`header-${index}`"
  75. class="divide-x divide-dividerLight border-b border-dividerLight flex"
  76. >
  77. <SmartAutoComplete
  78. :placeholder="$t('count.header', { count: index + 1 })"
  79. :source="commonHeaders"
  80. :spellcheck="false"
  81. :value="header.key"
  82. autofocus
  83. styles="
  84. bg-transparent
  85. flex
  86. flex-1
  87. py-1
  88. px-4
  89. truncate
  90. "
  91. :class="{ '!flex flex-1': EXPERIMENTAL_URL_BAR_ENABLED }"
  92. @input="
  93. updateHeader(index, {
  94. key: $event,
  95. value: header.value,
  96. active: header.active,
  97. })
  98. "
  99. />
  100. <SmartEnvInput
  101. v-if="EXPERIMENTAL_URL_BAR_ENABLED"
  102. v-model="header.value"
  103. :placeholder="$t('count.value', { count: index + 1 })"
  104. styles="
  105. bg-transparent
  106. flex
  107. flex-1
  108. py-1
  109. px-4
  110. "
  111. @change="
  112. updateHeader(index, {
  113. key: header.key,
  114. value: $event,
  115. active: header.active,
  116. })
  117. "
  118. />
  119. <input
  120. v-else
  121. class="bg-transparent flex flex-1 py-2 px-4"
  122. :placeholder="$t('count.value', { count: index + 1 })"
  123. :name="'value' + index"
  124. :value="header.value"
  125. @change="
  126. updateHeader(index, {
  127. key: header.key,
  128. value: $event.target.value,
  129. active: header.active,
  130. })
  131. "
  132. />
  133. <span>
  134. <ButtonSecondary
  135. v-tippy="{ theme: 'tooltip' }"
  136. :title="
  137. header.hasOwnProperty('active')
  138. ? header.active
  139. ? $t('action.turn_off')
  140. : $t('action.turn_on')
  141. : $t('action.turn_off')
  142. "
  143. :svg="
  144. header.hasOwnProperty('active')
  145. ? header.active
  146. ? 'check-circle'
  147. : 'circle'
  148. : 'check-circle'
  149. "
  150. color="green"
  151. @click.native="
  152. updateHeader(index, {
  153. key: header.key,
  154. value: header.value,
  155. active: header.hasOwnProperty('active')
  156. ? !header.active
  157. : false,
  158. })
  159. "
  160. />
  161. </span>
  162. <span>
  163. <ButtonSecondary
  164. v-tippy="{ theme: 'tooltip' }"
  165. :title="$t('action.remove')"
  166. svg="trash"
  167. color="red"
  168. @click.native="deleteHeader(index)"
  169. />
  170. </span>
  171. </div>
  172. <div
  173. v-if="headers$.length === 0"
  174. class="
  175. flex flex-col
  176. text-secondaryLight
  177. p-4
  178. items-center
  179. justify-center
  180. "
  181. >
  182. <span class="text-center pb-4">
  183. {{ $t("empty.headers") }}
  184. </span>
  185. <ButtonSecondary
  186. filled
  187. :label="$t('add.new')"
  188. svg="plus"
  189. @click.native="addHeader"
  190. />
  191. </div>
  192. </div>
  193. </AppSection>
  194. </template>
  195. <script lang="ts">
  196. import {
  197. defineComponent,
  198. ref,
  199. useContext,
  200. watch,
  201. } from "@nuxtjs/composition-api"
  202. import {
  203. restHeaders$,
  204. addRESTHeader,
  205. updateRESTHeader,
  206. deleteRESTHeader,
  207. deleteAllRESTHeaders,
  208. setRESTHeaders,
  209. } from "~/newstore/RESTSession"
  210. import { commonHeaders } from "~/helpers/headers"
  211. import { useSetting } from "~/newstore/settings"
  212. import { useReadonlyStream } from "~/helpers/utils/composables"
  213. import { HoppRESTHeader } from "~/helpers/types/HoppRESTRequest"
  214. export default defineComponent({
  215. setup() {
  216. const {
  217. $toast,
  218. app: { i18n },
  219. } = useContext()
  220. const t = i18n.t.bind(i18n)
  221. const bulkMode = ref(false)
  222. const bulkHeaders = ref("")
  223. watch(bulkHeaders, () => {
  224. try {
  225. const transformation = bulkHeaders.value.split("\n").map((item) => ({
  226. key: item.substring(0, item.indexOf(":")).trim().replace(/^\/\//, ""),
  227. value: item.substring(item.indexOf(":") + 1).trim(),
  228. active: !item.trim().startsWith("//"),
  229. }))
  230. setRESTHeaders(transformation)
  231. } catch (e) {
  232. $toast.error(t("error.something_went_wrong").toString(), {
  233. icon: "error_outline",
  234. })
  235. console.error(e)
  236. }
  237. })
  238. return {
  239. headers$: useReadonlyStream(restHeaders$, []),
  240. EXPERIMENTAL_URL_BAR_ENABLED: useSetting("EXPERIMENTAL_URL_BAR_ENABLED"),
  241. bulkMode,
  242. bulkHeaders,
  243. }
  244. },
  245. data() {
  246. return {
  247. commonHeaders,
  248. }
  249. },
  250. watch: {
  251. headers$: {
  252. handler(newValue) {
  253. if (
  254. (newValue[newValue.length - 1]?.key !== "" ||
  255. newValue[newValue.length - 1]?.value !== "") &&
  256. newValue.length
  257. )
  258. this.addHeader()
  259. },
  260. deep: true,
  261. },
  262. },
  263. // mounted() {
  264. // if (!this.headers$?.length) {
  265. // this.addHeader()
  266. // }
  267. // },
  268. methods: {
  269. addHeader() {
  270. addRESTHeader({ key: "", value: "", active: true })
  271. },
  272. updateHeader(index: number, item: HoppRESTHeader) {
  273. updateRESTHeader(index, item)
  274. },
  275. deleteHeader(index: number) {
  276. deleteRESTHeader(index)
  277. },
  278. clearContent() {
  279. deleteAllRESTHeaders()
  280. },
  281. },
  282. })
  283. </script>