feat: active tab no longer resets after request (#2917)
Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com> Closes https://github.com/hoppscotch/hoppscotch/issues/2080
This commit is contained in:
@@ -3,40 +3,32 @@
|
|||||||
<HttpResponseMeta :response="response" />
|
<HttpResponseMeta :response="response" />
|
||||||
<LensesResponseBodyRenderer
|
<LensesResponseBodyRenderer
|
||||||
v-if="!loading && hasResponse"
|
v-if="!loading && hasResponse"
|
||||||
|
v-model:selected-tab-preference="selectedTabPreference"
|
||||||
:response="response"
|
:response="response"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, defineComponent, watch } from "vue"
|
import { ref, computed, watch } from "vue"
|
||||||
import { startPageProgress, completePageProgress } from "@modules/loadingbar"
|
import { startPageProgress, completePageProgress } from "@modules/loadingbar"
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
import { restResponse$ } from "~/newstore/RESTSession"
|
import { restResponse$ } from "~/newstore/RESTSession"
|
||||||
|
|
||||||
export default defineComponent({
|
const selectedTabPreference = ref<string | null>(null)
|
||||||
setup() {
|
|
||||||
const response = useReadonlyStream(restResponse$, null)
|
|
||||||
|
|
||||||
const hasResponse = computed(
|
const response = useReadonlyStream(restResponse$, null)
|
||||||
() =>
|
|
||||||
response.value?.type === "success" || response.value?.type === "fail"
|
|
||||||
)
|
|
||||||
|
|
||||||
const loading = computed(
|
const hasResponse = computed(
|
||||||
() => response.value === null || response.value.type === "loading"
|
() => response.value?.type === "success" || response.value?.type === "fail"
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(response, () => {
|
const loading = computed(
|
||||||
if (response.value?.type === "loading") startPageProgress()
|
() => response.value === null || response.value.type === "loading"
|
||||||
else completePageProgress()
|
)
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
watch(response, () => {
|
||||||
hasResponse,
|
if (response.value?.type === "loading") startPageProgress()
|
||||||
response,
|
else completePageProgress()
|
||||||
loading,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ const t = useI18n()
|
|||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
response: HoppRESTResponse
|
response: HoppRESTResponse | null
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,6 +118,7 @@ const props = defineProps<{
|
|||||||
*/
|
*/
|
||||||
const readableResponseSize = computed(() => {
|
const readableResponseSize = computed(() => {
|
||||||
if (
|
if (
|
||||||
|
props.response === null ||
|
||||||
props.response.type === "loading" ||
|
props.response.type === "loading" ||
|
||||||
props.response.type === "network_fail" ||
|
props.response.type === "network_fail" ||
|
||||||
props.response.type === "script_fail" ||
|
props.response.type === "script_fail" ||
|
||||||
@@ -135,6 +136,7 @@ const readableResponseSize = computed(() => {
|
|||||||
|
|
||||||
const statusCategory = computed(() => {
|
const statusCategory = computed(() => {
|
||||||
if (
|
if (
|
||||||
|
props.response === null ||
|
||||||
props.response.type === "loading" ||
|
props.response.type === "loading" ||
|
||||||
props.response.type === "network_fail" ||
|
props.response.type === "network_fail" ||
|
||||||
props.response.type === "script_fail" ||
|
props.response.type === "script_fail" ||
|
||||||
|
|||||||
@@ -27,18 +27,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import IconCopy from "~icons/lucide/copy"
|
import IconCopy from "~icons/lucide/copy"
|
||||||
import IconCheck from "~icons/lucide/check"
|
import IconCheck from "~icons/lucide/check"
|
||||||
import { HoppRESTHeader } from "@hoppscotch/data"
|
|
||||||
import { refAutoReset } from "@vueuse/core"
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
|
import type { HoppRESTResponseHeader } from "~/helpers/types/HoppRESTResponse"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
headers: Array<HoppRESTHeader>
|
headers: HoppRESTResponseHeader[]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const copyIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
|
const copyIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
import IconCopy from "~icons/lucide/copy"
|
import IconCopy from "~icons/lucide/copy"
|
||||||
import IconCheck from "~icons/lucide/check"
|
import IconCheck from "~icons/lucide/check"
|
||||||
import { refAutoReset } from "@vueuse/core"
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import type { HoppRESTHeader } from "@hoppscotch/data"
|
import type { HoppRESTResponseHeader } from "~/helpers/types/HoppRESTResponse"
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
@@ -40,7 +40,7 @@ const t = useI18n()
|
|||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
header: HoppRESTHeader
|
header: HoppRESTResponseHeader
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const copyIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
|
const copyIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
|
||||||
|
|||||||
@@ -11,16 +11,16 @@
|
|||||||
:label="t(lens.lensName)"
|
:label="t(lens.lensName)"
|
||||||
class="flex flex-col flex-1 w-full h-full"
|
class="flex flex-col flex-1 w-full h-full"
|
||||||
>
|
>
|
||||||
<component :is="lens.renderer" :response="response" />
|
<component :is="lensRendererFor(lens.renderer)" :response="response" />
|
||||||
</SmartTab>
|
</SmartTab>
|
||||||
<SmartTab
|
<SmartTab
|
||||||
v-if="headerLength"
|
v-if="maybeHeaders"
|
||||||
id="headers"
|
id="headers"
|
||||||
:label="t('response.headers')"
|
:label="t('response.headers')"
|
||||||
:info="`${headerLength}`"
|
:info="`${maybeHeaders.length}`"
|
||||||
class="flex flex-col flex-1"
|
class="flex flex-col flex-1"
|
||||||
>
|
>
|
||||||
<LensesHeadersRenderer :headers="response.headers" />
|
<LensesHeadersRenderer :headers="maybeHeaders" />
|
||||||
</SmartTab>
|
</SmartTab>
|
||||||
<SmartTab
|
<SmartTab
|
||||||
id="results"
|
id="results"
|
||||||
@@ -42,54 +42,77 @@
|
|||||||
</SmartTabs>
|
</SmartTabs>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from "vue"
|
import { computed, ref, watch } from "vue"
|
||||||
import { getSuitableLenses, getLensRenderers } from "~/helpers/lenses/lenses"
|
import {
|
||||||
|
getSuitableLenses,
|
||||||
|
getLensRenderers,
|
||||||
|
Lens,
|
||||||
|
} from "~/helpers/lenses/lenses"
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import type { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||||
import { restTestResults$ } from "~/newstore/RESTSession"
|
import { restTestResults$ } from "~/newstore/RESTSession"
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps<{
|
||||||
components: {
|
response: HoppRESTResponse | null
|
||||||
// Lens Renderers
|
selectedTabPreference: string | null
|
||||||
...getLensRenderers(),
|
}>()
|
||||||
},
|
|
||||||
props: {
|
|
||||||
response: { type: Object, default: () => ({}) },
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const testResults = useReadonlyStream(restTestResults$, null)
|
|
||||||
|
|
||||||
return {
|
const emit = defineEmits<{
|
||||||
testResults,
|
(e: "update:selectedTabPreference", newTab: string): void
|
||||||
t: useI18n(),
|
}>()
|
||||||
|
|
||||||
|
const allLensRenderers = getLensRenderers()
|
||||||
|
|
||||||
|
function lensRendererFor(name: string) {
|
||||||
|
return allLensRenderers[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
const testResults = useReadonlyStream(restTestResults$, null)
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
|
const selectedLensTab = ref("")
|
||||||
|
|
||||||
|
const maybeHeaders = computed(() => {
|
||||||
|
if (
|
||||||
|
!props.response ||
|
||||||
|
!(props.response.type === "success" || props.response.type === "fail")
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
return props.response.headers
|
||||||
|
})
|
||||||
|
|
||||||
|
const validLenses = computed(() => {
|
||||||
|
if (!props.response) return []
|
||||||
|
return getSuitableLenses(props.response)
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
validLenses,
|
||||||
|
(newLenses: Lens[]) => {
|
||||||
|
if (newLenses.length === 0) return
|
||||||
|
|
||||||
|
const validRenderers = [
|
||||||
|
...newLenses.map((x) => x.renderer),
|
||||||
|
"headers",
|
||||||
|
"results",
|
||||||
|
]
|
||||||
|
|
||||||
|
if (
|
||||||
|
props.selectedTabPreference &&
|
||||||
|
validRenderers.includes(props.selectedTabPreference)
|
||||||
|
) {
|
||||||
|
selectedLensTab.value = props.selectedTabPreference
|
||||||
|
} else {
|
||||||
|
selectedLensTab.value = newLenses[0].renderer
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
{ immediate: true }
|
||||||
return {
|
)
|
||||||
selectedLensTab: "",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
headerLength() {
|
|
||||||
if (!this.response || !this.response.headers) return 0
|
|
||||||
|
|
||||||
return Object.keys(this.response.headers).length
|
watch(selectedLensTab, (newLensID) => {
|
||||||
},
|
emit("update:selectedTabPreference", newLensID)
|
||||||
validLenses() {
|
|
||||||
if (!this.response) return []
|
|
||||||
|
|
||||||
return getSuitableLenses(this.response)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
validLenses: {
|
|
||||||
handler(newValue) {
|
|
||||||
if (newValue.length === 0) return
|
|
||||||
this.selectedLensTab = newValue[0].renderer
|
|
||||||
},
|
|
||||||
immediate: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
|
||||||
|
export type HoppRESTResponseHeader = { key: string; value: string }
|
||||||
|
|
||||||
export type HoppRESTResponse =
|
export type HoppRESTResponse =
|
||||||
| { type: "loading"; req: HoppRESTRequest }
|
| { type: "loading"; req: HoppRESTRequest }
|
||||||
| {
|
| {
|
||||||
type: "fail"
|
type: "fail"
|
||||||
headers: { key: string; value: string }[]
|
headers: HoppRESTResponseHeader[]
|
||||||
body: ArrayBuffer
|
body: ArrayBuffer
|
||||||
statusCode: number
|
statusCode: number
|
||||||
|
|
||||||
@@ -27,7 +29,7 @@ export type HoppRESTResponse =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "success"
|
type: "success"
|
||||||
headers: { key: string; value: string }[]
|
headers: HoppRESTResponseHeader[]
|
||||||
body: ArrayBuffer
|
body: ArrayBuffer
|
||||||
statusCode: number
|
statusCode: number
|
||||||
meta: {
|
meta: {
|
||||||
|
|||||||
Reference in New Issue
Block a user