feat: request variables (#3825)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
This commit is contained in:
@@ -150,7 +150,7 @@
|
||||
<div v-else class="flex flex-1 border-b border-dividerLight">
|
||||
<div class="w-2/3 border-r border-dividerLight">
|
||||
<div v-if="auth.authType === 'basic'">
|
||||
<HttpAuthorizationBasic v-model="auth" />
|
||||
<HttpAuthorizationBasic v-model="auth" :envs="envs" />
|
||||
</div>
|
||||
<div v-if="auth.authType === 'inherit'" class="p-4">
|
||||
<span v-if="inheritedProperties?.auth">
|
||||
@@ -169,17 +169,26 @@
|
||||
</div>
|
||||
<div v-if="auth.authType === 'bearer'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="auth.token" placeholder="Token" />
|
||||
<SmartEnvInput
|
||||
v-model="auth.token"
|
||||
placeholder="Token"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="auth.authType === 'oauth-2'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="auth.token" placeholder="Token" />
|
||||
<SmartEnvInput
|
||||
v-model="auth.token"
|
||||
placeholder="Token"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<HttpOAuth2Authorization v-model="auth" />
|
||||
<HttpOAuth2Authorization v-model="auth" :envs="envs" />
|
||||
</div>
|
||||
<div v-if="auth.authType === 'api-key'">
|
||||
<HttpAuthorizationApiKey v-model="auth" />
|
||||
<HttpAuthorizationApiKey v-model="auth" :envs="envs" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -215,6 +224,7 @@ import { useColorMode } from "@composables/theming"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { onMounted } from "vue"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
import { AggregateEnvironment } from "~/newstore/environments"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
@@ -225,6 +235,7 @@ const props = defineProps<{
|
||||
isCollectionProperty?: boolean
|
||||
isRootCollection?: boolean
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
envs?: AggregateEnvironment[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -100,10 +100,12 @@
|
||||
<HttpBodyParameters
|
||||
v-if="body.contentType === 'multipart/form-data'"
|
||||
v-model="body"
|
||||
:envs="envs"
|
||||
/>
|
||||
<HttpURLEncodedParams
|
||||
v-else-if="body.contentType === 'application/x-www-form-urlencoded'"
|
||||
v-model="body"
|
||||
:envs="envs"
|
||||
/>
|
||||
<HttpRawBody v-else-if="body.contentType !== null" v-model="body" />
|
||||
<HoppSmartPlaceholder
|
||||
@@ -141,6 +143,7 @@ import IconExternalLink from "~icons/lucide/external-link"
|
||||
import IconInfo from "~icons/lucide/info"
|
||||
import IconRefreshCW from "~icons/lucide/refresh-cw"
|
||||
import { RESTOptionTabs } from "./RequestOptions.vue"
|
||||
import { AggregateEnvironment } from "~/newstore/environments"
|
||||
|
||||
const colorMode = useColorMode()
|
||||
const t = useI18n()
|
||||
@@ -148,6 +151,7 @@ const t = useI18n()
|
||||
const props = defineProps<{
|
||||
body: HoppRESTReqBody
|
||||
headers: HoppRESTHeader[]
|
||||
envs?: AggregateEnvironment[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -65,6 +65,8 @@
|
||||
<SmartEnvInput
|
||||
v-model="entry.key"
|
||||
:placeholder="`${t('count.parameter', { count: index + 1 })}`"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
@change="
|
||||
updateBodyParam(index, {
|
||||
key: $event,
|
||||
@@ -87,6 +89,8 @@
|
||||
<SmartEnvInput
|
||||
v-model="entry.value"
|
||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
@change="
|
||||
updateBodyParam(index, {
|
||||
key: entry.key,
|
||||
@@ -190,11 +194,13 @@ import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { AggregateEnvironment } from "~/newstore/environments"
|
||||
|
||||
type Body = HoppRESTReqBody & { contentType: "multipart/form-data" }
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Body
|
||||
envs: AggregateEnvironment[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -188,11 +188,25 @@ const copyCodeIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
|
||||
|
||||
const requestCode = computed(() => {
|
||||
const aggregateEnvs = getAggregateEnvs()
|
||||
const requestVariables = request.value.requestVariables.map(
|
||||
(requestVariable) => {
|
||||
if (requestVariable.active)
|
||||
return {
|
||||
key: requestVariable.key,
|
||||
value: requestVariable.value,
|
||||
secret: false,
|
||||
}
|
||||
return {}
|
||||
}
|
||||
)
|
||||
const env: Environment = {
|
||||
v: 1,
|
||||
id: "env",
|
||||
name: "Env",
|
||||
variables: aggregateEnvs,
|
||||
variables: [
|
||||
...(requestVariables as Environment["variables"]),
|
||||
...aggregateEnvs,
|
||||
],
|
||||
}
|
||||
const effectiveRequest = getEffectiveRESTRequest(request.value, env)
|
||||
|
||||
@@ -212,6 +226,12 @@ const requestCode = computed(() => {
|
||||
active: true,
|
||||
})),
|
||||
endpoint: effectiveRequest.effectiveFinalURL,
|
||||
requestVariables: effectiveRequest.effectiveFinalRequestVariables.map(
|
||||
(requestVariable) => ({
|
||||
...requestVariable,
|
||||
active: true,
|
||||
})
|
||||
),
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
@click="clearContent()"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="bulkMode"
|
||||
v-if="bulkHeaders"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('state.linewrap')"
|
||||
:class="{ '!text-accent': WRAP_LINES }"
|
||||
@@ -92,6 +92,8 @@
|
||||
:auto-complete-source="commonHeaders"
|
||||
:env-index="index"
|
||||
:inspection-results="getInspectorResult(headerKeyResults, index)"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
@change="
|
||||
updateHeader(index, {
|
||||
id: header.id,
|
||||
@@ -108,6 +110,8 @@
|
||||
getInspectorResult(headerValueResults, index)
|
||||
"
|
||||
:env-index="index"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
@change="
|
||||
updateHeader(index, {
|
||||
id: header.id,
|
||||
@@ -329,7 +333,11 @@ import {
|
||||
getComputedHeaders,
|
||||
getComputedAuthHeaders,
|
||||
} from "~/helpers/utils/EffectiveURL"
|
||||
import { aggregateEnvs$, getAggregateEnvs } from "~/newstore/environments"
|
||||
import {
|
||||
AggregateEnvironment,
|
||||
aggregateEnvs$,
|
||||
getAggregateEnvs,
|
||||
} from "~/newstore/environments"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { useService } from "dioc/vue"
|
||||
import { InspectionService, InspectorResult } from "~/services/inspection"
|
||||
@@ -359,6 +367,7 @@ const props = defineProps<{
|
||||
modelValue: HoppRESTRequest
|
||||
isCollectionProperty?: boolean
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
envs?: AggregateEnvironment[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -6,31 +6,52 @@
|
||||
:styles="
|
||||
hasAccessTokenOrAuthURL ? 'pointer-events-none opacity-70' : ''
|
||||
"
|
||||
:auto-complete-env="true"
|
||||
placeholder="OpenID Connect Discovery URL"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="authURL"
|
||||
placeholder="Authorization URL"
|
||||
:auto-complete-env="true"
|
||||
:styles="hasOIDCURL ? 'pointer-events-none opacity-70' : ''"
|
||||
></SmartEnvInput>
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="accessTokenURL"
|
||||
placeholder="Access Token URL"
|
||||
:auto-complete-env="true"
|
||||
:styles="hasOIDCURL ? 'pointer-events-none opacity-70' : ''"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="clientID" placeholder="Client ID" />
|
||||
<SmartEnvInput
|
||||
v-model="clientID"
|
||||
:auto-complete-env="true"
|
||||
placeholder="Client ID"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="clientSecret" placeholder="Client Secret" />
|
||||
<SmartEnvInput
|
||||
v-model="clientSecret"
|
||||
:auto-complete-env="true"
|
||||
placeholder="Client Secret"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="scope" placeholder="Scope" />
|
||||
<SmartEnvInput
|
||||
v-model="scope"
|
||||
:auto-complete-env="true"
|
||||
placeholder="Scope"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<HoppButtonSecondary
|
||||
@@ -62,6 +83,7 @@ const toast = useToast()
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: HoppRESTAuthOAuth2 | HoppGQLAuthOAuth2
|
||||
envs?: AggregateEnvironment[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -87,6 +87,8 @@
|
||||
:inspection-results="
|
||||
getInspectorResult(parameterKeyResults, index)
|
||||
"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
@change="
|
||||
updateParam(index, {
|
||||
id: param.id,
|
||||
@@ -102,6 +104,8 @@
|
||||
:inspection-results="
|
||||
getInspectorResult(parameterValueResults, index)
|
||||
"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
@change="
|
||||
updateParam(index, {
|
||||
id: param.id,
|
||||
@@ -209,6 +213,7 @@ import { InspectionService, InspectorResult } from "~/services/inspection"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { useNestedSetting } from "~/composables/settings"
|
||||
import { toggleNestedSetting } from "~/newstore/settings"
|
||||
import { AggregateEnvironment } from "~/newstore/environments"
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
@@ -242,6 +247,7 @@ useCodemirror(
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: HoppRESTParam[]
|
||||
envs?: AggregateEnvironment[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
v-model="tab.document.request.endpoint"
|
||||
:placeholder="`${t('request.url')}`"
|
||||
:auto-complete-source="userHistories"
|
||||
:auto-complete-env="true"
|
||||
:inspection-results="tabResults"
|
||||
@paste="onPasteUrl($event)"
|
||||
@enter="newSendRequest"
|
||||
|
||||
@@ -5,48 +5,51 @@
|
||||
render-inactive-tabs
|
||||
>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('params') : true"
|
||||
v-if="properties?.includes('params') ?? true"
|
||||
:id="'params'"
|
||||
:label="`${t('tab.parameters')}`"
|
||||
:info="`${newActiveParamsCount$}`"
|
||||
:info="`${newActiveParamsCount}`"
|
||||
>
|
||||
<HttpParameters v-model="request.params" />
|
||||
<HttpParameters v-model="request.params" :envs="envs" />
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('bodyParams') : true"
|
||||
v-if="properties?.includes('bodyParams') ?? true"
|
||||
:id="'bodyParams'"
|
||||
:label="`${t('tab.body')}`"
|
||||
>
|
||||
<HttpBody
|
||||
v-model:headers="request.headers"
|
||||
v-model:body="request.body"
|
||||
:envs="envs"
|
||||
@change-tab="changeOptionTab"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('headers') : true"
|
||||
v-if="properties?.includes('headers') ?? true"
|
||||
:id="'headers'"
|
||||
:label="`${t('tab.headers')}`"
|
||||
:info="`${newActiveHeadersCount$}`"
|
||||
:info="`${newActiveHeadersCount}`"
|
||||
>
|
||||
<HttpHeaders
|
||||
v-model="request"
|
||||
:inherited-properties="inheritedProperties"
|
||||
:envs="envs"
|
||||
@change-tab="changeOptionTab"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('authorization') : true"
|
||||
v-if="properties?.includes('authorization') ?? true"
|
||||
:id="'authorization'"
|
||||
:label="`${t('tab.authorization')}`"
|
||||
>
|
||||
<HttpAuthorization
|
||||
v-model="request.auth"
|
||||
:inherited-properties="inheritedProperties"
|
||||
:envs="envs"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('preRequestScript') : true"
|
||||
v-if="properties?.includes('preRequestScript') ?? true"
|
||||
:id="'preRequestScript'"
|
||||
:label="`${t('tab.pre_request_script')}`"
|
||||
:indicator="
|
||||
@@ -58,7 +61,7 @@
|
||||
<HttpPreRequestScript v-model="request.preRequestScript" />
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('tests') : true"
|
||||
v-if="properties?.includes('tests') ?? true"
|
||||
:id="'tests'"
|
||||
:label="`${t('tab.tests')}`"
|
||||
:indicator="
|
||||
@@ -67,6 +70,15 @@
|
||||
>
|
||||
<HttpTests v-model="request.testScript" />
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="properties?.includes('requestVariables') ?? true"
|
||||
:id="'requestVariables'"
|
||||
:label="`${t('tab.variables')}`"
|
||||
:info="`${newActiveRequestVariablesCount}`"
|
||||
:align-last="true"
|
||||
>
|
||||
<HttpRequestVariables v-model="request.requestVariables" />
|
||||
</HoppSmartTab>
|
||||
</HoppSmartTabs>
|
||||
</template>
|
||||
|
||||
@@ -77,6 +89,7 @@ import { useVModel } from "@vueuse/core"
|
||||
import { computed } from "vue"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
import { AggregateEnvironment } from "~/newstore/environments"
|
||||
|
||||
const VALID_OPTION_TABS = [
|
||||
"params",
|
||||
@@ -85,6 +98,7 @@ const VALID_OPTION_TABS = [
|
||||
"authorization",
|
||||
"preRequestScript",
|
||||
"tests",
|
||||
"requestVariables",
|
||||
] as const
|
||||
|
||||
export type RESTOptionTabs = (typeof VALID_OPTION_TABS)[number]
|
||||
@@ -98,6 +112,7 @@ const props = withDefaults(
|
||||
optionTab: RESTOptionTabs
|
||||
properties?: string[]
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
envs?: AggregateEnvironment[]
|
||||
}>(),
|
||||
{
|
||||
optionTab: "params",
|
||||
@@ -116,22 +131,27 @@ const changeOptionTab = (e: RESTOptionTabs) => {
|
||||
selectedOptionTab.value = e
|
||||
}
|
||||
|
||||
const newActiveParamsCount$ = computed(() => {
|
||||
const e = request.value.params.filter(
|
||||
(x) => x.active && (x.key !== "" || x.value !== "")
|
||||
const newActiveParamsCount = computed(() => {
|
||||
const count = request.value.params.filter(
|
||||
(x) => x.active && (x.key || x.value)
|
||||
).length
|
||||
|
||||
if (e === 0) return null
|
||||
return `${e}`
|
||||
return count ? count : null
|
||||
})
|
||||
|
||||
const newActiveHeadersCount$ = computed(() => {
|
||||
const e = request.value.headers.filter(
|
||||
(x) => x.active && (x.key !== "" || x.value !== "")
|
||||
const newActiveHeadersCount = computed(() => {
|
||||
const count = request.value.headers.filter(
|
||||
(x) => x.active && (x.key || x.value)
|
||||
).length
|
||||
|
||||
if (e === 0) return null
|
||||
return `${e}`
|
||||
return count ? count : null
|
||||
})
|
||||
|
||||
const newActiveRequestVariablesCount = computed(() => {
|
||||
const count = request.value.requestVariables.filter(
|
||||
(x) => x.active && (x.key || x.value)
|
||||
).length
|
||||
return count ? count : null
|
||||
})
|
||||
|
||||
defineActionHandler("request.open-tab", ({ tab }) => {
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
<template>
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div
|
||||
class="sticky top-upperMobileSecondaryStickyFold z-10 flex flex-shrink-0 items-center justify-between overflow-x-auto border-b border-dividerLight bg-primary pl-4 sm:top-upperSecondaryStickyFold"
|
||||
>
|
||||
<label class="truncate font-semibold text-secondaryLight">
|
||||
{{ t("request.request_variables") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
to="https://docs.hoppscotch.io/documentation/features/rest-api-testing"
|
||||
blank
|
||||
:title="t('app.wiki')"
|
||||
:icon="IconHelpCircle"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.clear_all')"
|
||||
:icon="IconTrash2"
|
||||
@click="clearContent()"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="bulkVariables"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('state.linewrap')"
|
||||
:class="{ '!text-accent': WRAP_LINES }"
|
||||
:icon="IconWrapText"
|
||||
@click.prevent="
|
||||
toggleNestedSetting('WRAP_LINES', 'httpRequestVariables')
|
||||
"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('state.bulk_mode')"
|
||||
:icon="IconEdit"
|
||||
:class="{ '!text-accent': bulkMode }"
|
||||
@click="bulkMode = !bulkMode"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('add.new')"
|
||||
:icon="IconPlus"
|
||||
:disabled="bulkMode"
|
||||
@click="addVariable"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="bulkMode" class="h-full relative">
|
||||
<div ref="bulkEditor" class="absolute inset-0"></div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<draggable
|
||||
v-model="workingRequestVariables"
|
||||
item-key="id"
|
||||
animation="250"
|
||||
handle=".draggable-handle"
|
||||
draggable=".draggable-content"
|
||||
ghost-class="cursor-move"
|
||||
chosen-class="bg-primaryLight"
|
||||
drag-class="cursor-grabbing"
|
||||
>
|
||||
<template #item="{ element: variable, index }">
|
||||
<div
|
||||
class="draggable-content group flex divide-x divide-dividerLight border-b border-dividerLight"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{
|
||||
theme: 'tooltip',
|
||||
delay: [500, 20],
|
||||
content:
|
||||
index !== workingRequestVariables?.length - 1
|
||||
? t('action.drag_to_reorder')
|
||||
: null,
|
||||
}"
|
||||
:icon="IconGripVertical"
|
||||
class="opacity-0"
|
||||
:class="{
|
||||
'draggable-handle cursor-grab group-hover:opacity-100':
|
||||
index !== workingRequestVariables?.length - 1,
|
||||
}"
|
||||
tabindex="-1"
|
||||
/>
|
||||
<SmartEnvInput
|
||||
v-model="variable.key"
|
||||
:placeholder="`${t('count.variable', { count: index + 1 })}`"
|
||||
@change="
|
||||
updateVariable(index, {
|
||||
id: variable.id,
|
||||
key: $event,
|
||||
value: variable.value,
|
||||
active: variable.active,
|
||||
})
|
||||
"
|
||||
/>
|
||||
<SmartEnvInput
|
||||
v-model="variable.value"
|
||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||
@change="
|
||||
updateVariable(index, {
|
||||
id: variable.id,
|
||||
key: variable.key,
|
||||
value: $event,
|
||||
active: variable.active,
|
||||
})
|
||||
"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="
|
||||
variable.hasOwnProperty('active')
|
||||
? variable.active
|
||||
? t('action.turn_off')
|
||||
: t('action.turn_on')
|
||||
: t('action.turn_off')
|
||||
"
|
||||
:icon="
|
||||
variable.hasOwnProperty('active')
|
||||
? variable.active
|
||||
? IconCheckCircle
|
||||
: IconCircle
|
||||
: IconCheckCircle
|
||||
"
|
||||
color="green"
|
||||
@click="
|
||||
updateVariable(index, {
|
||||
id: variable.id,
|
||||
key: variable.key,
|
||||
value: variable.value,
|
||||
active: variable.hasOwnProperty('active')
|
||||
? !variable.active
|
||||
: false,
|
||||
})
|
||||
"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.remove')"
|
||||
:icon="IconTrash"
|
||||
color="red"
|
||||
@click="deleteVariable(index)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
<HoppSmartPlaceholder
|
||||
v-if="workingRequestVariables.length === 0"
|
||||
:src="`/images/states/${colorMode.value}/add_files.svg`"
|
||||
:alt="`${t('empty.request_variables')}`"
|
||||
:text="t('empty.request_variables')"
|
||||
>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
:label="`${t('add.new')}`"
|
||||
:icon="IconPlus"
|
||||
filled
|
||||
@click="addVariable"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { throwError } from "@functional/error"
|
||||
import {
|
||||
HoppRESTRequestVariable,
|
||||
RawKeyValueEntry,
|
||||
parseRawKeyValueEntriesE,
|
||||
rawKeyValueEntriesToString,
|
||||
} from "@hoppscotch/data"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import * as A from "fp-ts/Array"
|
||||
import * as E from "fp-ts/Either"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as RA from "fp-ts/ReadonlyArray"
|
||||
import { flow, pipe } from "fp-ts/function"
|
||||
import { cloneDeep, isEqual } from "lodash-es"
|
||||
import { reactive, ref, watch } from "vue"
|
||||
import draggable from "vuedraggable-es"
|
||||
|
||||
import { useCodemirror } from "~/composables/codemirror"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import { useNestedSetting } from "~/composables/settings"
|
||||
import { useColorMode } from "~/composables/theming"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import linter from "~/helpers/editor/linting/rawKeyValue"
|
||||
import { objRemoveKey } from "~/helpers/functional/object"
|
||||
import { toggleNestedSetting } from "~/newstore/settings"
|
||||
import IconCheckCircle from "~icons/lucide/check-circle"
|
||||
import IconCircle from "~icons/lucide/circle"
|
||||
import IconEdit from "~icons/lucide/edit"
|
||||
import IconGripVertical from "~icons/lucide/grip-vertical"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconTrash from "~icons/lucide/trash"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconWrapText from "~icons/lucide/wrap-text"
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const bulkMode = ref(false)
|
||||
const bulkEditor = ref<any | null>(null)
|
||||
const bulkVariables = ref("")
|
||||
|
||||
const WRAP_LINES = useNestedSetting("WRAP_LINES", "httpRequestVariables")
|
||||
|
||||
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: HoppRESTRequestVariable[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: Array<HoppRESTRequestVariable>): void
|
||||
}>()
|
||||
|
||||
// The functional requestVariable list (the requestVariable actually applied to the session)
|
||||
const requestVariables = useVModel(props, "modelValue", emit)
|
||||
|
||||
useCodemirror(
|
||||
bulkEditor,
|
||||
bulkVariables,
|
||||
reactive({
|
||||
extendedEditorConfig: {
|
||||
mode: "text/x-yaml",
|
||||
placeholder: `${t("state.bulk_mode_placeholder")}`,
|
||||
lineWrapping: WRAP_LINES,
|
||||
},
|
||||
linter,
|
||||
completer: null,
|
||||
environmentHighlights: true,
|
||||
})
|
||||
)
|
||||
|
||||
const idTicker = ref(0)
|
||||
|
||||
const workingRequestVariables = ref<
|
||||
Array<HoppRESTRequestVariable & { id: number }>
|
||||
>([
|
||||
{
|
||||
id: idTicker.value++,
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
},
|
||||
])
|
||||
|
||||
// Sync logic between params and working/bulk params
|
||||
watch(
|
||||
requestVariables,
|
||||
(newRequestVariableList) => {
|
||||
// Sync should overwrite working params
|
||||
const filteredWorkingRequestVariables: HoppRESTRequestVariable[] = pipe(
|
||||
workingRequestVariables.value,
|
||||
A.filterMap(
|
||||
flow(
|
||||
O.fromPredicate((e) => e.key !== ""),
|
||||
O.map(objRemoveKey("id"))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
const filteredBulkRequestVariables = pipe(
|
||||
parseRawKeyValueEntriesE(bulkVariables.value),
|
||||
E.map(
|
||||
flow(
|
||||
RA.filter((e) => e.key !== ""),
|
||||
RA.toArray
|
||||
)
|
||||
),
|
||||
E.getOrElse(() => [] as RawKeyValueEntry[])
|
||||
)
|
||||
|
||||
if (!isEqual(newRequestVariableList, filteredWorkingRequestVariables)) {
|
||||
workingRequestVariables.value = pipe(
|
||||
newRequestVariableList,
|
||||
A.map((x) => ({ id: idTicker.value++, ...x }))
|
||||
)
|
||||
}
|
||||
|
||||
if (!isEqual(newRequestVariableList, filteredBulkRequestVariables)) {
|
||||
bulkVariables.value = rawKeyValueEntriesToString(newRequestVariableList)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(workingRequestVariables, (newWorkingRequestVariables) => {
|
||||
const fixedRequestVariables = pipe(
|
||||
newWorkingRequestVariables,
|
||||
A.filterMap(
|
||||
flow(
|
||||
O.fromPredicate((e) => e.key !== ""),
|
||||
O.map(objRemoveKey("id"))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if (!isEqual(requestVariables.value, fixedRequestVariables)) {
|
||||
requestVariables.value = cloneDeep(fixedRequestVariables)
|
||||
}
|
||||
})
|
||||
|
||||
watch(bulkVariables, (newBulkParams) => {
|
||||
const filteredBulkRequestVariables = pipe(
|
||||
parseRawKeyValueEntriesE(newBulkParams),
|
||||
E.map(
|
||||
flow(
|
||||
RA.filter((e) => e.key !== ""),
|
||||
RA.toArray
|
||||
)
|
||||
),
|
||||
E.getOrElse(() => [] as RawKeyValueEntry[])
|
||||
)
|
||||
|
||||
if (!isEqual(requestVariables.value, filteredBulkRequestVariables)) {
|
||||
requestVariables.value = filteredBulkRequestVariables
|
||||
}
|
||||
})
|
||||
|
||||
// Rule: Working Request variable always have last element is always an empty param
|
||||
watch(workingRequestVariables, (variableList) => {
|
||||
if (
|
||||
variableList.length > 0 &&
|
||||
variableList[variableList.length - 1].key !== ""
|
||||
) {
|
||||
workingRequestVariables.value.push({
|
||||
id: idTicker.value++,
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const addVariable = () => {
|
||||
workingRequestVariables.value.push({
|
||||
id: idTicker.value++,
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
})
|
||||
}
|
||||
|
||||
const updateVariable = (index: number, variable: any & { id: number }) => {
|
||||
workingRequestVariables.value = workingRequestVariables.value.map((h, i) =>
|
||||
i === index ? variable : h
|
||||
)
|
||||
}
|
||||
|
||||
const deleteVariable = (index: number) => {
|
||||
const requestVariablesBeforeDeletion = cloneDeep(
|
||||
workingRequestVariables.value
|
||||
)
|
||||
|
||||
if (
|
||||
!(
|
||||
requestVariablesBeforeDeletion.length > 0 &&
|
||||
index === requestVariablesBeforeDeletion.length - 1
|
||||
)
|
||||
) {
|
||||
if (deletionToast.value) {
|
||||
deletionToast.value.goAway(0)
|
||||
deletionToast.value = null
|
||||
}
|
||||
|
||||
deletionToast.value = toast.success(`${t("state.deleted")}`, {
|
||||
action: [
|
||||
{
|
||||
text: `${t("action.undo")}`,
|
||||
onClick: (_, toastObject) => {
|
||||
workingRequestVariables.value = requestVariablesBeforeDeletion
|
||||
toastObject.goAway(0)
|
||||
deletionToast.value = null
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
onComplete: () => {
|
||||
deletionToast.value = null
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
workingRequestVariables.value = pipe(
|
||||
workingRequestVariables.value,
|
||||
A.deleteAt(index),
|
||||
O.getOrElseW(() =>
|
||||
throwError("Working Request Variable Deletion Out of Bounds")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const clearContent = () => {
|
||||
// set params list to the initial state
|
||||
workingRequestVariables.value = [
|
||||
{
|
||||
id: idTicker.value++,
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
},
|
||||
]
|
||||
|
||||
bulkVariables.value = ""
|
||||
}
|
||||
</script>
|
||||
@@ -21,7 +21,7 @@
|
||||
@click="clearContent()"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="bulkMode"
|
||||
v-if="bulkUrlEncodedParams"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('state.linewrap')"
|
||||
:class="{ '!text-accent': WRAP_LINES }"
|
||||
@@ -84,6 +84,8 @@
|
||||
<SmartEnvInput
|
||||
v-model="param.key"
|
||||
:placeholder="`${t('count.parameter', { count: index + 1 })}`"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
@change="
|
||||
updateUrlEncodedParam(index, {
|
||||
id: param.id,
|
||||
@@ -96,6 +98,8 @@
|
||||
<SmartEnvInput
|
||||
v-model="param.value"
|
||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
@change="
|
||||
updateUrlEncodedParam(index, {
|
||||
id: param.id,
|
||||
@@ -200,6 +204,7 @@ import { throwError } from "~/helpers/functional/error"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { useNestedSetting } from "~/composables/settings"
|
||||
import { toggleNestedSetting } from "~/newstore/settings"
|
||||
import { AggregateEnvironment } from "~/newstore/environments"
|
||||
|
||||
type Body = HoppRESTReqBody & {
|
||||
contentType: "application/x-www-form-urlencoded"
|
||||
@@ -207,6 +212,7 @@ type Body = HoppRESTReqBody & {
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Body
|
||||
envs: AggregateEnvironment[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
<template>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="auth.key" placeholder="Key" />
|
||||
<SmartEnvInput
|
||||
v-model="auth.key"
|
||||
:auto-complete-env="true"
|
||||
placeholder="Key"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="auth.value" placeholder="Value" />
|
||||
<SmartEnvInput
|
||||
v-model="auth.value"
|
||||
:auto-complete-env="true"
|
||||
placeholder="Value"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center border-b border-dividerLight">
|
||||
<span class="flex items-center">
|
||||
@@ -65,11 +75,13 @@ import { useI18n } from "@composables/i18n"
|
||||
import { HoppRESTAuthAPIKey } from "@hoppscotch/data"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { ref } from "vue"
|
||||
import { AggregateEnvironment } from "~/newstore/environments"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: HoppRESTAuthAPIKey
|
||||
envs?: AggregateEnvironment[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -3,12 +3,16 @@
|
||||
<SmartEnvInput
|
||||
v-model="auth.username"
|
||||
:placeholder="t('authorization.username')"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="auth.password"
|
||||
:placeholder="t('authorization.password')"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -17,11 +21,13 @@
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { HoppRESTAuthBasic } from "@hoppscotch/data"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { AggregateEnvironment } from "~/newstore/environments"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: HoppRESTAuthBasic
|
||||
envs?: AggregateEnvironment[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
Reference in New Issue
Block a user