Files
LiteRequest/src/components/ResponsePanel.vue
Julian Freeman f9a4c0e64a fix 2
2026-04-19 09:14:26 -04:00

138 lines
4.9 KiB
Vue

<script setup lang="ts">
import { computed, defineAsyncComponent, ref } from 'vue';
import { useRequestStore } from '../stores/requestStore';
import { Clipboard, Check } from 'lucide-vue-next';
const CodeEditor = defineAsyncComponent(() => import('./CodeEditor.vue'));
const store = useRequestStore();
const isCopied = ref(false);
const activeTab = ref<'body' | 'headers'>('body');
const isJsonResponse = computed(() => {
const response = store.activeRequest.response;
if (!response) return false;
const contentType = response.headers['content-type'] || response.headers['Content-Type'] || '';
if (contentType.toLowerCase().includes('json')) {
return true;
}
try {
JSON.parse(response.body);
return true;
} catch {
return false;
}
});
const formattedHeaders = computed(() => {
const response = store.activeRequest.response;
if (!response) return '';
return Object.entries(response.headers)
.sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
.map(([key, value]) => `${key}: ${value}`)
.join('\n');
});
const formattedBody = computed(() => {
if (!store.activeRequest.response) return '';
if (!isJsonResponse.value) return store.activeRequest.response.body;
try {
return JSON.stringify(JSON.parse(store.activeRequest.response.body), null, 2);
} catch {
return store.activeRequest.response.body;
}
});
const displayedContent = computed(() => (
activeTab.value === 'headers' ? formattedHeaders.value : formattedBody.value
));
const statusColor = computed(() => {
const s = store.activeRequest.response?.status || 0;
if (s >= 200 && s < 300) return 'text-emerald-400';
if (s >= 400) return 'text-rose-400';
return 'text-amber-400';
});
const copyToClipboard = async () => {
if (!displayedContent.value) return;
try {
await navigator.clipboard.writeText(displayedContent.value);
isCopied.value = true;
setTimeout(() => {
isCopied.value = false;
}, 2000);
} catch (err) {
console.error('Failed to copy:', err);
}
};
</script>
<template>
<div class="flex flex-col h-full border-t border-slate-800 bg-slate-900">
<div v-if="store.activeRequest.response" class="flex flex-col h-full">
<!-- Meta Bar -->
<div class="px-4 py-2 border-b border-slate-800 flex justify-between text-xs items-center bg-slate-950/50">
<div class="flex gap-4 items-center">
<div class="font-mono">
Status: <span :class="['font-bold', statusColor]">{{ store.activeRequest.response.status }}</span>
</div>
<div class="text-slate-500">
Time: <span class="text-slate-300">{{ store.activeRequest.response.time }}ms</span>
</div>
<div class="text-slate-500">
Size: <span class="text-slate-300">{{ (store.activeRequest.response.size / 1024).toFixed(2) }} KB</span>
</div>
</div>
<div class="flex items-center gap-2">
<div class="flex rounded-lg border border-slate-800 bg-slate-900 p-0.5">
<button
@click="activeTab = 'body'"
class="rounded-md px-2.5 py-1 transition-colors"
:class="activeTab === 'body' ? 'bg-indigo-500/15 text-indigo-300' : 'text-slate-400 hover:text-slate-200'"
>
Body
</button>
<button
@click="activeTab = 'headers'"
class="rounded-md px-2.5 py-1 transition-colors"
:class="activeTab === 'headers' ? 'bg-indigo-500/15 text-indigo-300' : 'text-slate-400 hover:text-slate-200'"
>
Headers
</button>
</div>
<button
@click="copyToClipboard"
class="p-1.5 text-slate-400 hover:text-indigo-400 hover:bg-indigo-500/10 rounded transition-colors"
:title="activeTab === 'headers' ? 'Copy Headers' : 'Copy Body'"
>
<Check v-if="isCopied" class="w-3.5 h-3.5" />
<Clipboard v-else class="w-3.5 h-3.5" />
</button>
</div>
</div>
<!-- Output -->
<div class="flex-1 overflow-hidden">
<CodeEditor
:model-value="displayedContent"
:language="activeTab === 'body' && isJsonResponse ? 'json' : 'text'"
:read-only="true"
/>
</div>
</div>
<div v-else-if="store.requestError" class="flex-1 flex items-center justify-center p-6">
<div class="max-w-2xl rounded-xl border border-rose-500/30 bg-rose-500/10 p-4 text-sm text-rose-200">
{{ store.requestError }}
</div>
</div>
<div v-else class="flex-1 flex items-center justify-center text-slate-600 text-sm">
No response yet
</div>
</div>
</template>