Details.vue 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. <template>
  2. <SmartModal
  3. v-if="show"
  4. dialog
  5. :title="t(`environment.${action}`)"
  6. @close="hideModal"
  7. >
  8. <template #body>
  9. <div class="flex flex-col px-2">
  10. <div class="relative flex">
  11. <input
  12. id="selectLabelEnvEdit"
  13. v-model="name"
  14. v-focus
  15. class="input floating-input"
  16. placeholder=" "
  17. type="text"
  18. autocomplete="off"
  19. :disabled="editingEnvironmentIndex === 'Global'"
  20. @keyup.enter="saveEnvironment"
  21. />
  22. <label for="selectLabelEnvEdit">
  23. {{ t("action.label") }}
  24. </label>
  25. </div>
  26. <div class="flex items-center justify-between flex-1">
  27. <label for="variableList" class="p-4">
  28. {{ t("environment.variable_list") }}
  29. </label>
  30. <div class="flex">
  31. <ButtonSecondary
  32. v-tippy="{ theme: 'tooltip' }"
  33. :title="t('action.clear_all')"
  34. :svg="clearIcon"
  35. @click.native="clearContent()"
  36. />
  37. <ButtonSecondary
  38. v-tippy="{ theme: 'tooltip' }"
  39. svg="plus"
  40. :title="t('add.new')"
  41. @click.native="addEnvironmentVariable"
  42. />
  43. </div>
  44. </div>
  45. <div
  46. v-if="evnExpandError"
  47. class="w-full px-4 py-2 mb-2 overflow-auto font-mono text-red-400 whitespace-normal rounded bg-primaryLight"
  48. >
  49. {{ t("environment.nested_overflow") }}
  50. </div>
  51. <div class="border rounded divide-y divide-dividerLight border-divider">
  52. <div
  53. v-for="({ id, env }, index) in vars"
  54. :key="`variable-${id}-${index}`"
  55. class="flex divide-x divide-dividerLight"
  56. >
  57. <input
  58. v-model="env.key"
  59. class="flex flex-1 px-4 py-2 bg-transparent"
  60. :placeholder="`${t('count.variable', { count: index + 1 })}`"
  61. :name="'param' + index"
  62. />
  63. <SmartEnvInput
  64. v-model="env.value"
  65. :placeholder="`${t('count.value', { count: index + 1 })}`"
  66. :envs="liveEnvs"
  67. :name="'value' + index"
  68. />
  69. <div class="flex">
  70. <ButtonSecondary
  71. id="variable"
  72. v-tippy="{ theme: 'tooltip' }"
  73. :title="t('action.remove')"
  74. svg="trash"
  75. color="red"
  76. @click.native="removeEnvironmentVariable(index)"
  77. />
  78. </div>
  79. </div>
  80. <div
  81. v-if="vars.length === 0"
  82. class="flex flex-col items-center justify-center p-4 text-secondaryLight"
  83. >
  84. <img
  85. :src="`/images/states/${$colorMode.value}/blockchain.svg`"
  86. loading="lazy"
  87. class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
  88. :alt="`${t('empty.environments')}`"
  89. />
  90. <span class="pb-4 text-center">
  91. {{ t("empty.environments") }}
  92. </span>
  93. <ButtonSecondary
  94. :label="`${t('add.new')}`"
  95. filled
  96. class="mb-4"
  97. @click.native="addEnvironmentVariable"
  98. />
  99. </div>
  100. </div>
  101. </div>
  102. </template>
  103. <template #footer>
  104. <span>
  105. <ButtonPrimary
  106. :label="`${t('action.save')}`"
  107. @click.native="saveEnvironment"
  108. />
  109. <ButtonSecondary
  110. :label="`${t('action.cancel')}`"
  111. @click.native="hideModal"
  112. />
  113. </span>
  114. </template>
  115. </SmartModal>
  116. </template>
  117. <script setup lang="ts">
  118. import clone from "lodash/clone"
  119. import { computed, ref, watch } from "@nuxtjs/composition-api"
  120. import * as E from "fp-ts/Either"
  121. import * as A from "fp-ts/Array"
  122. import * as O from "fp-ts/Option"
  123. import { pipe, flow } from "fp-ts/function"
  124. import { Environment, parseTemplateStringE } from "@hoppscotch/data"
  125. import { refAutoReset } from "@vueuse/core"
  126. import {
  127. createEnvironment,
  128. environments$,
  129. getEnvironment,
  130. getGlobalVariables,
  131. globalEnv$,
  132. setCurrentEnvironment,
  133. setGlobalEnvVariables,
  134. updateEnvironment,
  135. } from "~/newstore/environments"
  136. import {
  137. useReadonlyStream,
  138. useI18n,
  139. useToast,
  140. } from "~/helpers/utils/composables"
  141. type EnvironmentVariable = {
  142. id: number
  143. env: {
  144. key: string
  145. value: string
  146. }
  147. }
  148. const t = useI18n()
  149. const toast = useToast()
  150. const props = withDefaults(
  151. defineProps<{
  152. show: boolean
  153. action: "edit" | "new"
  154. editingEnvironmentIndex: number | "Global" | null
  155. envVars: () => Environment["variables"]
  156. }>(),
  157. {
  158. show: false,
  159. action: "edit",
  160. editingEnvironmentIndex: null,
  161. envVars: () => [],
  162. }
  163. )
  164. const emit = defineEmits<{
  165. (e: "hide-modal"): void
  166. }>()
  167. const idTicker = ref(0)
  168. const name = ref<string | null>(null)
  169. const vars = ref<EnvironmentVariable[]>([
  170. { id: idTicker.value++, env: { key: "", value: "" } },
  171. ])
  172. const clearIcon = refAutoReset<"trash-2" | "check">("trash-2", 1000)
  173. const globalVars = useReadonlyStream(globalEnv$, [])
  174. const workingEnv = computed(() => {
  175. if (props.editingEnvironmentIndex === "Global") {
  176. return {
  177. name: "Global",
  178. variables: getGlobalVariables(),
  179. } as Environment
  180. } else if (props.action === "new") {
  181. return {
  182. name: "",
  183. variables: props.envVars(),
  184. }
  185. } else if (props.editingEnvironmentIndex !== null) {
  186. return getEnvironment(props.editingEnvironmentIndex)
  187. } else {
  188. return null
  189. }
  190. })
  191. const envList = useReadonlyStream(environments$, []) || props.envVars()
  192. const evnExpandError = computed(() => {
  193. const variables = pipe(
  194. vars.value,
  195. A.map((e) => e.env)
  196. )
  197. return pipe(
  198. variables,
  199. A.exists(({ value }) => E.isLeft(parseTemplateStringE(value, variables)))
  200. )
  201. })
  202. const liveEnvs = computed(() => {
  203. if (evnExpandError) {
  204. return []
  205. }
  206. if (props.editingEnvironmentIndex === "Global") {
  207. return [...vars.value.map((x) => ({ ...x, source: name.value! }))]
  208. } else {
  209. return [
  210. ...vars.value.map((x) => ({ ...x, source: name.value! })),
  211. ...globalVars.value.map((x) => ({ ...x, source: "Global" })),
  212. ]
  213. }
  214. })
  215. watch(
  216. () => props.show,
  217. (show) => {
  218. if (show) {
  219. name.value = workingEnv.value?.name ?? null
  220. vars.value = pipe(
  221. workingEnv.value?.variables ?? [],
  222. A.map((e) => ({
  223. id: idTicker.value++,
  224. env: clone(e),
  225. }))
  226. )
  227. }
  228. }
  229. )
  230. const clearContent = () => {
  231. vars.value = [
  232. {
  233. id: idTicker.value++,
  234. env: {
  235. key: "",
  236. value: "",
  237. },
  238. },
  239. ]
  240. clearIcon.value = "check"
  241. toast.success(`${t("state.cleared")}`)
  242. }
  243. const addEnvironmentVariable = () => {
  244. vars.value.push({
  245. id: idTicker.value++,
  246. env: {
  247. key: "",
  248. value: "",
  249. },
  250. })
  251. }
  252. const removeEnvironmentVariable = (index: number) => {
  253. vars.value.splice(index, 1)
  254. }
  255. const saveEnvironment = () => {
  256. if (!name.value) {
  257. toast.error(`${t("environment.invalid_name")}`)
  258. return
  259. }
  260. const filterdVariables = pipe(
  261. vars.value,
  262. A.filterMap(
  263. flow(
  264. O.fromPredicate((e) => e.env.key !== ""),
  265. O.map((e) => e.env)
  266. )
  267. )
  268. )
  269. const environmentUpdated: Environment = {
  270. name: name.value,
  271. variables: filterdVariables,
  272. }
  273. if (props.action === "new") {
  274. // Creating a new environment
  275. createEnvironment(name.value)
  276. updateEnvironment(envList.value.length - 1, environmentUpdated)
  277. setCurrentEnvironment(envList.value.length - 1)
  278. toast.success(`${t("environment.created")}`)
  279. } else if (props.editingEnvironmentIndex === "Global") {
  280. // Editing the Global environment
  281. setGlobalEnvVariables(environmentUpdated.variables)
  282. toast.success(`${t("environment.updated")}`)
  283. } else if (props.editingEnvironmentIndex !== null) {
  284. // Editing an environment
  285. updateEnvironment(props.editingEnvironmentIndex, environmentUpdated)
  286. toast.success(`${t("environment.updated")}`)
  287. }
  288. hideModal()
  289. }
  290. const hideModal = () => {
  291. name.value = null
  292. emit("hide-modal")
  293. }
  294. </script>