improve response panel

This commit is contained in:
Julian Freeman
2025-12-01 11:03:12 -04:00
parent 8fc4dbbf93
commit 4d20f2075f
3 changed files with 141 additions and 15 deletions

View File

@@ -0,0 +1,59 @@
# UI Improvement: Resizable Panels & Copy Action
**Context:**
We are polishing the "Main Workspace" area of the LiteRequest application.
**Goal:**
1. Add a **"Copy Body" button** to the Response Panel for quick access to the result.
2. Make the vertical split between the **Request Panel** (top) and **Response Panel** (bottom) resizable by the user.
## 1. Feature: Response Copy Button
### 1.1 UI Changes
* **Location:** Inside the `ResponsePanel.vue`, in the **Meta Bar / Header** (the row displaying Status Code and Time), aligned to the right.
* **Icon:** Use the `Clipboard` icon from `lucide-vue-next`.
* **Interaction:**
* **Default:** Shows the `Clipboard` icon.
* **On Click:** Copies the content of the Response Body to the system clipboard.
* **Feedback:** After clicking, change the icon to `Check` (Checkmark) for 2 seconds to indicate success, then revert to `Clipboard`.
* **Style:** Small, ghost-style button (transparent background, changing color on hover).
### 1.2 Logic Implementation
* Use the browser API: `navigator.clipboard.writeText(props.body)`.
* Use a local ref `isCopied` (boolean) to handle the icon toggle state.
* Use `setTimeout` to reset `isCopied` back to `false` after 2000ms.
## 2. Feature: Resizable Split Pane
### 2.1 UI Changes
* **Location:** The parent component that contains `<RequestPanel />` and `<ResponsePanel />` (likely `App.vue` or `MainWorkspace.vue`).
* **Visuals:** Insert a **Resizer Handle** (a thin horizontal bar) between the two panels.
* *Height:* 4px - 6px.
* *Color:* `bg-slate-800` (hover: `bg-indigo-500`).
* *Cursor:* `cursor-row-resize`.
### 2.2 Logic Implementation
* **State:** Use a `ref` for `topPanelHeight` (initially `50%` or `50vh`).
* **Drag Logic:**
1. **`onMouseDown`** (on the Resizer): Set a `isDragging` flag to true. Add `mousemove` and `mouseup` event listeners to the `window` (global).
2. **`onMouseMove`** (on Window): Calculate the new percentage or pixel height based on `e.clientY` relative to the window height. Update `topPanelHeight`.
3. **`onMouseUp`** (on Window): Set `isDragging` to false. Remove the window event listeners.
* **Layout Application:**
* Apply `:style="{ height: topPanelHeight }"` to the **Request Panel** container.
* Apply `flex-1` (or calculated remaining height) to the **Response Panel** container.
* Ensure `min-height` is set (e.g., 200px) on both panels to prevent them from collapsing completely.
## 3. Implementation Steps for AI
Please generate the code updates in the following order:
1. **ResponsePanel.vue:**
* Update the template to include the Copy Button in the header.
* Add the script logic for `copyToClipboard`.
2. **Main Layout (App.vue or similar):**
* Refactor the template to wrap RequestPanel and ResponsePanel in the resizable structure.
* Implement the `useDraggable` logic (or manual event handlers) in `<script setup>`.
* Add the `<div class="resizer ...">` element styles.
---
**Instruction to AI:**
Generate the specific code to add the copy functionality and the resizable split view logic.

View File

@@ -3,9 +3,41 @@ import RequestPanel from './components/RequestPanel.vue';
import ResponsePanel from './components/ResponsePanel.vue';
import Sidebar from './components/Sidebar.vue';
import SettingsModal from './components/SettingsModal.vue';
import { ref } from 'vue';
import { ref, onUnmounted } from 'vue';
const showSettings = ref(false);
const topPanelHeight = ref(50); // Percentage
const isDragging = ref(false);
const startDrag = () => {
isDragging.value = true;
window.addEventListener('mousemove', onDrag);
window.addEventListener('mouseup', stopDrag);
};
const onDrag = (e: MouseEvent) => {
if (!isDragging.value) return;
const containerHeight = window.innerHeight;
let newPercentage = (e.clientY / containerHeight) * 100;
// Clamp between 20% and 80%
if (newPercentage < 20) newPercentage = 20;
if (newPercentage > 80) newPercentage = 80;
topPanelHeight.value = newPercentage;
};
const stopDrag = () => {
isDragging.value = false;
window.removeEventListener('mousemove', onDrag);
window.removeEventListener('mouseup', stopDrag);
};
onUnmounted(() => {
window.removeEventListener('mousemove', onDrag);
window.removeEventListener('mouseup', stopDrag);
});
</script>
<template>
@@ -14,11 +46,20 @@ const showSettings = ref(false);
<Sidebar @open-settings="showSettings = true" />
<!-- Main Workspace -->
<main class="flex-1 flex flex-col min-w-0 bg-slate-900">
<div class="flex-1 min-h-0">
<main class="flex-1 flex flex-col min-w-0 bg-slate-900 h-full relative">
<!-- Request Panel (Top) -->
<div class="min-h-0" :style="{ height: topPanelHeight + '%' }">
<RequestPanel />
</div>
<div class="h-1/2 border-t border-slate-800 min-h-0">
<!-- Resizer Handle -->
<div
class="h-1 bg-slate-800 hover:bg-indigo-500 cursor-row-resize transition-colors z-10 flex-shrink-0"
@mousedown="startDrag"
></div>
<!-- Response Panel (Bottom) -->
<div class="flex-1 min-h-0 overflow-hidden">
<ResponsePanel />
</div>
</main>

View File

@@ -1,14 +1,16 @@
<script setup lang="ts">
import { computed } from 'vue';
import { computed, ref } from 'vue';
import { useRequestStore } from '../stores/requestStore';
import { useSettingsStore } from '../stores/settingsStore';
import { Codemirror } from 'vue-codemirror';
import { json } from '@codemirror/lang-json';
import { oneDark } from '@codemirror/theme-one-dark';
import { EditorView } from '@codemirror/view'; // Import EditorView
import { EditorView } from '@codemirror/view';
import { Clipboard, Check } from 'lucide-vue-next';
const store = useRequestStore();
const settings = useSettingsStore();
const isCopied = ref(false);
const extensions = computed(() => {
const theme = EditorView.theme({
@@ -43,13 +45,27 @@ const statusColor = computed(() => {
if (s >= 400) return 'text-rose-400';
return 'text-amber-400';
});
const copyToClipboard = async () => {
if (!formattedBody.value) return;
try {
await navigator.clipboard.writeText(formattedBody.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 gap-4 text-xs items-center bg-slate-950/50">
<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>
@@ -61,6 +77,16 @@ const statusColor = computed(() => {
</div>
</div>
<button
@click="copyToClipboard"
class="p-1.5 text-slate-400 hover:text-indigo-400 hover:bg-indigo-500/10 rounded transition-colors"
title="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>
<!-- Output -->
<div class="flex-1 overflow-hidden">
<Codemirror