modify sidebar
This commit is contained in:
67
spec/feat-history-ui.md
Normal file
67
spec/feat-history-ui.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# Feature Upgrade: History UI Overhaul
|
||||||
|
|
||||||
|
**Context:**
|
||||||
|
We are polishing the "Sidebar" component of the "LiteRequest" application.
|
||||||
|
**Goal:** Improve the information density of the History list and add management features (Delete/Clear All).
|
||||||
|
|
||||||
|
## 1. UI/UX Changes
|
||||||
|
|
||||||
|
### 1.1 History Item Layout (Sidebar.vue)
|
||||||
|
* **Current State:** Shows Method, URL, and Timestamp.
|
||||||
|
* **New Layout:**
|
||||||
|
* **Remove:** Completely remove the Date/Time display to save space.
|
||||||
|
* **Flex Row:** The item should be a flex container (`flex items-center justify-between`).
|
||||||
|
* **Left Side (Info):**
|
||||||
|
* **Method Badge:** (Existing) e.g., "GET".
|
||||||
|
* **Status Badge (New):** Add a badge immediately after the Method.
|
||||||
|
* *Content:* The HTTP Status Code (e.g., 200, 404, 500).
|
||||||
|
* *Style:* Small, pill-shaped, lighter opacity background.
|
||||||
|
* *Color Logic:*
|
||||||
|
* 2xx: Text Green / Bg Green-500/20
|
||||||
|
* 3xx: Text Blue / Bg Blue-500/20
|
||||||
|
* 4xx: Text Orange / Bg Orange-500/20
|
||||||
|
* 5xx: Text Red / Bg Red-500/20
|
||||||
|
* **URL:** Truncate text if it's too long (`truncate` class).
|
||||||
|
* **Right Side (Actions):**
|
||||||
|
* **Delete Button (New):** A small "Trash" icon (`Trash2` from lucide-vue-next).
|
||||||
|
* *Behavior:* Visible on hover (group-hover) or always visible with low opacity.
|
||||||
|
* *Interaction:* Clicking this **must not** trigger the "Load Request" action of the list item (use `.stop` modifier).
|
||||||
|
|
||||||
|
### 1.2 "Clear All" Functionality
|
||||||
|
* **Location:** Add a "Clear" or "Trash" icon button in the **Header** of the Sidebar (next to the "History" tab label or the "LiteRequest" title).
|
||||||
|
* **Style:** Subtle button, turns red on hover.
|
||||||
|
* **Tooltip (Optional):** "Clear All History".
|
||||||
|
|
||||||
|
## 2. Logic & State Management
|
||||||
|
|
||||||
|
### 2.1 Store Updates (`stores/requestStore.ts`)
|
||||||
|
* Add two new actions:
|
||||||
|
1. `deleteHistoryItem(id: string)`: Removes a specific item from the `history` array by ID.
|
||||||
|
2. `clearHistory()`: Empties the `history` array completely.
|
||||||
|
* **Persistence:** Ensure `localStorage` is updated immediately after these actions.
|
||||||
|
|
||||||
|
## 3. Implementation Details
|
||||||
|
|
||||||
|
### A. Sidebar Component (`Sidebar.vue`)
|
||||||
|
* **Status Badge Implementation:**
|
||||||
|
* Create a helper function `getStatusColor(code)` inside the component to return the correct Tailwind classes.
|
||||||
|
* **Event Handling:**
|
||||||
|
* `<button @click.stop="store.deleteHistoryItem(item.id)">`
|
||||||
|
* The `.stop` modifier is critical to prevent opening the request while trying to delete it.
|
||||||
|
|
||||||
|
### B. Icons
|
||||||
|
* Import `Trash2` (for single item) and `Trash` (for clear all) from `lucide-vue-next`.
|
||||||
|
|
||||||
|
## 4. Implementation Steps for AI
|
||||||
|
|
||||||
|
Please generate the code updates in the following order:
|
||||||
|
|
||||||
|
1. **Store:** Add the `deleteHistoryItem` and `clearHistory` actions to `requestStore.ts`.
|
||||||
|
2. **Sidebar (Script):** Update the `<script setup>` to include the new icons and the status color helper function.
|
||||||
|
3. **Sidebar (Template):**
|
||||||
|
* Show the updated **Header** section (with "Clear All" button).
|
||||||
|
* Show the updated **History List Item** structure (Method + Status + URL + Delete Button).
|
||||||
|
|
||||||
|
---
|
||||||
|
**Instruction to AI:**
|
||||||
|
Generate the code to apply these UI improvements and functional additions to the History module.
|
||||||
58
src/App.vue
58
src/App.vue
@@ -1,71 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRequestStore } from './stores/requestStore';
|
|
||||||
import RequestPanel from './components/RequestPanel.vue';
|
import RequestPanel from './components/RequestPanel.vue';
|
||||||
import ResponsePanel from './components/ResponsePanel.vue';
|
import ResponsePanel from './components/ResponsePanel.vue';
|
||||||
import MethodBadge from './components/MethodBadge.vue';
|
import Sidebar from './components/Sidebar.vue';
|
||||||
import SettingsModal from './components/SettingsModal.vue';
|
import SettingsModal from './components/SettingsModal.vue';
|
||||||
import { History, Layers, Zap, Settings } from 'lucide-vue-next';
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
const store = useRequestStore();
|
|
||||||
const showSettings = ref(false);
|
const showSettings = ref(false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex h-screen w-full bg-slate-950 text-slate-200 font-sans overflow-hidden">
|
<div class="flex h-screen w-full bg-slate-950 text-slate-200 font-sans overflow-hidden">
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside class="w-64 flex-shrink-0 border-r border-slate-800 flex flex-col bg-slate-950">
|
<Sidebar @open-settings="showSettings = true" />
|
||||||
<!-- Header -->
|
|
||||||
<div class="h-14 flex items-center px-4 border-b border-slate-800 gap-2">
|
|
||||||
<div class="bg-indigo-500/20 p-1.5 rounded-lg">
|
|
||||||
<Zap class="w-5 h-5 text-indigo-400" />
|
|
||||||
</div>
|
|
||||||
<span class="font-bold text-slate-100 tracking-tight">LiteRequest</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Nav Tabs -->
|
|
||||||
<div class="flex p-2 gap-1">
|
|
||||||
<button class="flex-1 flex items-center justify-center gap-2 py-1.5 text-xs font-medium bg-slate-900 text-slate-200 rounded border border-slate-800 shadow-sm">
|
|
||||||
<History class="w-3.5 h-3.5" /> History
|
|
||||||
</button>
|
|
||||||
<button class="flex-1 flex items-center justify-center gap-2 py-1.5 text-xs font-medium text-slate-500 hover:bg-slate-900 hover:text-slate-300 rounded transition-colors">
|
|
||||||
<Layers class="w-3.5 h-3.5" /> Collections
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- History List -->
|
|
||||||
<div class="flex-1 overflow-y-auto">
|
|
||||||
<div v-if="store.history.length === 0" class="p-8 text-center text-slate-600 text-xs">
|
|
||||||
No history yet. Make a request!
|
|
||||||
</div>
|
|
||||||
<div v-else class="flex flex-col">
|
|
||||||
<button
|
|
||||||
v-for="item in store.history"
|
|
||||||
:key="item.timestamp"
|
|
||||||
@click="store.loadRequest(item)"
|
|
||||||
class="text-left px-3 py-3 border-b border-slate-800/50 hover:bg-slate-900 transition-colors group flex flex-col gap-1.5"
|
|
||||||
>
|
|
||||||
<div class="flex items-center gap-2 overflow-hidden w-full">
|
|
||||||
<MethodBadge :method="item.method" />
|
|
||||||
<span class="text-xs text-slate-400 truncate font-mono opacity-75">{{ new Date(item.timestamp).toLocaleTimeString() }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="text-sm text-slate-300 truncate px-1 font-medium" :title="item.url">
|
|
||||||
{{ item.url || 'No URL' }}
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Settings -->
|
|
||||||
<div class="p-2 border-t border-slate-800">
|
|
||||||
<button
|
|
||||||
@click="showSettings = true"
|
|
||||||
class="w-full flex items-center gap-2 px-3 py-2 text-xs font-medium text-slate-400 hover:text-slate-200 hover:bg-slate-900 rounded transition-colors"
|
|
||||||
>
|
|
||||||
<Settings class="w-4 h-4" /> Settings
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<!-- Main Workspace -->
|
<!-- Main Workspace -->
|
||||||
<main class="flex-1 flex flex-col min-w-0 bg-slate-900">
|
<main class="flex-1 flex flex-col min-w-0 bg-slate-900">
|
||||||
|
|||||||
104
src/components/Sidebar.vue
Normal file
104
src/components/Sidebar.vue
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useRequestStore } from '../stores/requestStore';
|
||||||
|
import MethodBadge from './MethodBadge.vue';
|
||||||
|
import { Zap, Settings, Trash, Trash2, Plus } from 'lucide-vue-next';
|
||||||
|
|
||||||
|
const emit = defineEmits(['open-settings']);
|
||||||
|
const store = useRequestStore();
|
||||||
|
|
||||||
|
const getStatusColor = (code?: number) => {
|
||||||
|
if (!code) return 'text-slate-500 bg-slate-800/50';
|
||||||
|
if (code >= 200 && code < 300) return 'text-emerald-400 bg-emerald-500/10';
|
||||||
|
if (code >= 300 && code < 400) return 'text-blue-400 bg-blue-500/10';
|
||||||
|
if (code >= 400 && code < 500) return 'text-amber-400 bg-amber-500/10';
|
||||||
|
if (code >= 500) return 'text-rose-400 bg-rose-500/10';
|
||||||
|
return 'text-slate-400 bg-slate-500/10';
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<aside class="w-64 flex-shrink-0 border-r border-slate-800 flex flex-col bg-slate-950">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="h-14 flex items-center justify-between px-4 border-b border-slate-800">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="bg-indigo-500/20 p-1.5 rounded-lg">
|
||||||
|
<Zap class="w-5 h-5 text-indigo-400" />
|
||||||
|
</div>
|
||||||
|
<span class="font-bold text-slate-100 tracking-tight">LiteRequest</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Action Buttons -->
|
||||||
|
<div class="flex p-2 gap-2 border-b border-slate-800/50">
|
||||||
|
<button
|
||||||
|
@click="store.resetActiveRequest()"
|
||||||
|
class="flex-1 flex items-center justify-center gap-2 py-1.5 text-xs font-medium bg-indigo-600 hover:bg-indigo-500 text-white rounded transition-all shadow-lg shadow-indigo-900/20"
|
||||||
|
>
|
||||||
|
<Plus class="w-3.5 h-3.5" /> New Request
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="store.clearHistory()"
|
||||||
|
class="flex-none flex items-center justify-center gap-2 px-3 py-1.5 text-xs font-medium bg-slate-800 hover:bg-red-500/10 hover:text-red-400 text-slate-400 rounded transition-all border border-slate-700 hover:border-red-500/50"
|
||||||
|
title="Clear History"
|
||||||
|
>
|
||||||
|
<Trash class="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- History List -->
|
||||||
|
<div class="flex-1 overflow-y-auto">
|
||||||
|
<div v-if="store.history.length === 0" class="p-8 text-center text-slate-600 text-xs">
|
||||||
|
No history yet. Make a request!
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex flex-col">
|
||||||
|
<button
|
||||||
|
v-for="item in store.history"
|
||||||
|
:key="item.id"
|
||||||
|
@click="store.loadRequest(item)"
|
||||||
|
class="group flex flex-col text-left px-3 py-2 border-b border-slate-800/50 hover:bg-slate-900 transition-colors w-full"
|
||||||
|
>
|
||||||
|
<!-- First Row: Method, Status, Delete Button -->
|
||||||
|
<div class="flex items-center justify-between w-full">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<MethodBadge :method="item.method" />
|
||||||
|
|
||||||
|
<!-- Status Badge -->
|
||||||
|
<span
|
||||||
|
v-if="item.response?.status"
|
||||||
|
class="text-[10px] font-mono font-bold px-1.5 py-0.5 rounded-full"
|
||||||
|
:class="getStatusColor(item.response?.status)"
|
||||||
|
>
|
||||||
|
{{ item.response.status }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Delete Button -->
|
||||||
|
<button
|
||||||
|
@click.stop="store.deleteHistoryItem(item.id)"
|
||||||
|
class="opacity-0 group-hover:opacity-100 transition-opacity p-1.5 text-slate-500 hover:text-red-400 hover:bg-slate-800 rounded-md cursor-pointer flex-shrink-0"
|
||||||
|
>
|
||||||
|
<Trash2 class="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Second Row: URL -->
|
||||||
|
<div class="mt-1">
|
||||||
|
<span class="text-xs text-slate-400 truncate font-medium block" :title="item.url">
|
||||||
|
{{ item.url || '/' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Settings -->
|
||||||
|
<div class="p-2 border-t border-slate-800">
|
||||||
|
<button
|
||||||
|
@click="emit('open-settings')"
|
||||||
|
class="w-full flex items-center gap-2 px-3 py-2 text-xs font-medium text-slate-400 hover:text-slate-200 hover:bg-slate-900 rounded transition-colors"
|
||||||
|
>
|
||||||
|
<Settings class="w-4 h-4" /> Settings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</template>
|
||||||
@@ -85,6 +85,35 @@ export const useRequestStore = defineStore('request', () => {
|
|||||||
history.value = [];
|
history.value = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteHistoryItem = (id: string) => {
|
||||||
|
const index = history.value.findIndex(item => item.id === id);
|
||||||
|
if (index !== -1) {
|
||||||
|
history.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetActiveRequest = () => {
|
||||||
|
activeRequest.value = {
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
method: 'GET',
|
||||||
|
url: '',
|
||||||
|
params: [
|
||||||
|
{ id: crypto.randomUUID(), key: '', value: '', enabled: true }
|
||||||
|
],
|
||||||
|
headers: [
|
||||||
|
{ id: crypto.randomUUID(), key: '', value: '', enabled: true }
|
||||||
|
],
|
||||||
|
body: '',
|
||||||
|
auth: {
|
||||||
|
type: 'none',
|
||||||
|
basic: { username: '', password: '' },
|
||||||
|
bearer: { token: '' },
|
||||||
|
apiKey: { key: '', value: '', addTo: 'header' }
|
||||||
|
},
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const loadRequest = (req: RequestData) => {
|
const loadRequest = (req: RequestData) => {
|
||||||
// Deep copy
|
// Deep copy
|
||||||
const loaded = JSON.parse(JSON.stringify(req));
|
const loaded = JSON.parse(JSON.stringify(req));
|
||||||
@@ -118,6 +147,8 @@ export const useRequestStore = defineStore('request', () => {
|
|||||||
activeRequest,
|
activeRequest,
|
||||||
addToHistory,
|
addToHistory,
|
||||||
clearHistory,
|
clearHistory,
|
||||||
loadRequest
|
deleteHistoryItem,
|
||||||
|
loadRequest,
|
||||||
|
resetActiveRequest
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user