Compare commits

..

4 Commits

Author SHA1 Message Date
Julian Freeman
799c65a3d7 fix bug 2025-12-01 08:54:10 -04:00
Julian Freeman
18f023e6e3 fix bug, in the middle 2025-12-01 08:51:17 -04:00
Julian Freeman
ee586ae06f add auth 2025-12-01 08:41:44 -04:00
Julian Freeman
c7c7b5fc4b add font settings (but font family doesn't work) 2025-12-01 08:13:27 -04:00
12 changed files with 633 additions and 9 deletions

61
spec/bug-fix-1.md Normal file
View File

@@ -0,0 +1,61 @@
# Bug Fixes & UI Polishing Tasks
**Context:**
We are refining the "LiteRequest" application. The core logic works, but there are specific UI bugs and a functional regression in the Settings module.
**Goal:** Fix visual inconsistencies and ensure the Settings (Font Size/Family) are correctly applied to the CodeMirror editor.
## Task List
### 1. Reorder Tabs in Request Panel
* **Issue:** The **"Auth"** tab is currently the first tab.
* **Fix:** Move the **"Auth"** tab to the **last position**.
* **Order:** `Params` -> `Headers` -> `Body` -> `Auth`.
* **Action:** Update `RequestPanel.vue` (both the tab navigation list and the conditional rendering of the content).
### 2. Fix Method Dropdown Styling (Dark Mode)
* **Issue:** The HTML `<select>` dropdown for HTTP Methods shows a white background when expanded, which clashes with the dark theme.
* **Fix:**
* Apply Tailwind classes `bg-slate-900` and `text-white` (or `text-slate-200`) explicitly to the `<option>` tags within the `<select>`.
* Ensure the `<select>` itself has `bg-slate-900`, `border-slate-700`, and `text-white`.
* *Note:* Browser native dropdowns are hard to style perfectly, but ensuring the background color is set on options fixes the glaring white box issue.
### 3. Fix Editor Background Color Mismatch
* **Issue:** The CodeMirror editor background is currently a generic gray (likely from a default theme), which looks disconnected from the App's `slate-900` / `slate-950` theme.
* **Fix:**
* In `Editor.vue`, modify the CodeMirror theme configuration.
* **Transparent Background:** Set the CodeMirror editor's background to `transparent` so it adopts the parent container's color.
* **Code Snippet Hint:**
```javascript
EditorView.theme({
"&": { backgroundColor: "transparent !important", height: "100%" },
".cm-gutters": { backgroundColor: "transparent !important" } // Match gutter too
})
```
* Ensure the parent container in `Editor.vue` has the correct Tailwind class (e.g., `bg-slate-900`).
### 4. Fix Font Settings Not Applying
* **Issue:** Changing Font Size or Family in Settings does not update the Editor view.
* **Root Cause:** CodeMirror 6 does not automatically react to CSS style changes on the parent container for everything (especially if a Theme is hardcoded).
* **Fix:**
* Update `Editor.vue`.
* Use a **Vue `watch` effect** to listen to changes in `settingsStore.editorFontSize` and `settingsStore.editorFontFamily`.
* **Implementation Strategy:**
* Instead of trying to update the theme, simply apply a reactive `style` object to the **wrapper `<div>`** of the CodeMirror component.
* **Crucial Step:** Ensure the CodeMirror theme allows inheritance.
* **Alternative (Better):** Re-dispatch a CodeMirror Effect or update the `EditorView` style via a `Compartment` if the wrapper style approach fails.
* *Simplified approach for Vue:* Bind `:style="{ fontSize: store.fontSize + 'px', fontFamily: store.fontFamily }"` to the outer div, and ensure the `.cm-editor` CSS allows inheritance (usually it does by default, but confirm standard text styles).
## Implementation Instructions for AI
Please generate the code corrections in the following order:
1. **`RequestPanel.vue`:** Show the updated template with the reordered Tabs.
2. **`RequestPanel.vue` (or wherever the Method Selector is):** Show the CSS/Tailwind fix for the Dropdown and Options.
3. **`Editor.vue`:**
* Show the updated CodeMirror configuration (making background transparent).
* Show the logical fix to apply Font Settings (using `watch` or reactive styles).
---
**Instruction to AI:**
Please generate the corrected code blocks for these specific files to resolve the visual bugs and settings regression.

76
spec/feature-auth.md Normal file
View File

@@ -0,0 +1,76 @@
# Feature Request: Authentication Support
**Context:**
We are upgrading the "LiteRequest" Tauri + Vue application. Currently, it supports Headers, Params, and Body.
**Goal:** Add a dedicated **"Auth"** tab in the Request Configuration area to support common authentication methods.
## 1. Functional Requirements
### 1.1 Supported Auth Types
The user should be able to select one of the following from a dropdown:
1. **No Auth** (Default)
2. **Basic Auth**: Inputs for `Username` and `Password`.
3. **Bearer Token**: Input for `Token` (e.g., JWT).
4. **API Key**: Inputs for `Key`, `Value`, and a selection for `Add To` ("Header" or "Query Params").
### 1.2 State Management
* Update `stores/requestStore.ts` to include an `auth` state object.
* **Structure:**
```typescript
state: {
// ... existing state
auth: {
type: 'none', // 'none' | 'basic' | 'bearer' | 'api_key'
basic: { username: '', password: '' },
bearer: { token: '' },
apiKey: { key: '', value: '', addTo: 'header' } // addTo: 'header' | 'query'
}
}
```
* **Persistence:** These values should be saved to `localStorage` along with the request history (careful note: in a real app we would encrypt this, but for this local MVP, plain text storage is acceptable).
## 2. UI/UX Design
### 2.1 Request Panel Update
* In `RequestPanel.vue`, add a new Tab labeled **"Auth"** (place it first, before "Params").
* **Layout:**
* **Type Selector:** A styled `<select>` or dropdown menu at the top of the tab content.
* **Dynamic Form:**
* If **No Auth**: Show a subtle message "This request does not use any authentication."
* If **Basic Auth**: Two fields (Username, Password). *Password field must use type="password" with a toggle to show/hide.*
* If **Bearer Token**: One large text area or input for the Token. Prefix it visually with "Bearer ".
* If **API Key**: Three inputs: Key (text), Value (text/password), Add To (Radio button or Select).
### 2.2 Aesthetic Guidelines
* Maintain the **Dark Mode** (Slate/Zinc) theme.
* Inputs should look identical to the existing inputs in the "Headers" tab (rounded, dark background, border-slate-700).
* Use `lucide-vue-next` icons (e.g., `ShieldCheck` icon for the Tab label).
## 3. Technical Implementation Details
### A. Frontend Logic (`requestStore.ts` & Component)
* The Frontend needs to package this Auth data and send it to the Rust backend.
* **Important:** Do *not* manually inject these into the `headers` list in the frontend UI (to avoid state sync issues). Pass the auth configuration as a separate object to the Rust command.
### B. Backend Logic (`main.rs`)
* Update the `execute_request` Tauri command signature to accept an `auth` argument.
* **Logic Update:**
* Use `reqwest` built-in methods:
* **Basic:** `.basic_auth(username, Some(password))`
* **Bearer:** `.bearer_auth(token)`
* **API Key:**
* If `addTo == "header"`: Manually `.header(key, value)`
* If `addTo == "query"`: Manually append to the query params.
## 4. Implementation Steps for AI
Please generate the code updates in the following order:
1. **Store Update:** Modify `stores/requestStore.ts` to add the `auth` state structure.
2. **UI Component:** Create a new component `components/AuthPanel.vue` (or code to be added inside `RequestPanel.vue`) that handles the form switching and inputs.
3. **Rust Backend:** Update `main.rs`. Define the `Auth` struct (using `serde::Deserialize`) and update the `execute_request` logic to apply the auth.
4. **Integration:** Show how to update the `execute_request` invocation in the frontend to pass this new data.
---
**Instruction to AI:**
Generate the code to implement these Authentication features. Ensure the UI matches the existing modern style (Tailwind CSS).

View File

@@ -0,0 +1,59 @@
# Feature Request: Editor Appearance Settings
**Context:**
We are continuing the development of the "LiteRequest" Tauri + Vue application. The application currently functions as a REST Client with a two-column layout.
**Goal:** Implement a "Settings" feature that allows users to customize the **Font Size** and **Font Family** of the CodeMirror editors (both Request Body and Response Output).
## 1. Functional Requirements
### 1.1 Settings Persistence
* Create a new Pinia store module (e.g., `stores/settingsStore.ts`) to manage application preferences.
* **State fields:**
* `editorFontSize`: Number (Default: 14).
* `editorFontFamily`: String (Default: "Fira Code, Consolas, monospace").
* **Persistence:** Use `localStorage` to save these preferences so they survive an app restart.
### 1.2 User Interface (Settings Modal)
* **Entry Point:** Add a "Settings" (Gear icon) button to the bottom of the **Sidebar**.
* **Modal Design:**
* Create a `SettingsModal.vue` component.
* Style: Centered modal with a dark backdrop (backdrop-blur). Matches the existing Slate/Zinc dark theme.
* **Controls:**
* **Font Size:** A number input or a range slider (e.g., 10px to 24px).
* **Font Family:** A text input (allowing users to type their installed fonts) OR a simple dropdown with common coding fonts.
* **Actions:** "Close" button (Changes should apply reactively/immediately, no need for a "Save" button).
### 1.3 Editor Integration
* Update the existing `Editor.vue` component (the CodeMirror wrapper).
* It must subscribe to the `settingsStore`.
* Apply the `fontSize` and `fontFamily` dynamically to the editor container or the CodeMirror instance.
## 2. Technical Implementation Details
### A. Store (`stores/settingsStore.ts`)
* Define the store using Pinia's Composition API.
* Use `useLocalStorage` from `@vueuse/core` (if available) or manual `localStorage.setItem` within a watcher.
### B. Sidebar Update (`Sidebar.vue`)
* Import `SettingsIcon` from `lucide-vue-next`.
* Add the button at the absolute bottom of the sidebar container.
* Add logic to toggle the visibility of the `SettingsModal`.
### C. Editor Update (`Editor.vue`)
* Bind the styles dynamically.
* *Hint for Implementation:* The easiest way to style CodeMirror 6 dynamically is to apply CSS variables to the wrapper `<div>` and ensure the CodeMirror theme uses those variables, or simply apply inline styles to the wrapper which CodeMirror usually inherits.
* Example: `<div :style="{ fontSize: settings.editorFontSize + 'px', fontFamily: settings.editorFontFamily }">`
## 3. Implementation Steps for AI
Please generate the code updates in the following order:
1. **Store:** Create `stores/settingsStore.ts`.
2. **Component (New):** Create `components/SettingsModal.vue`.
3. **Component (Update):** Show the modified code for `Sidebar.vue` (adding the button).
4. **Component (Update):** Show the modified code for `App.vue` (to include the Modal) or wherever the Modal should be placed.
5. **Component (Update):** Show the modified code for `Editor.vue` (applying the styles).
---
**Instruction to AI:**
Please generate the necessary code to implement these features based on the existing "LiteRequest" project structure. Focus on maintaining the modern dark aesthetic.

View File

@@ -10,6 +10,35 @@ struct HttpResponse {
time_elapsed: u128, // milliseconds
}
#[derive(Serialize, Deserialize, Debug)]
struct BasicAuth {
username: String,
password: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct BearerAuth {
token: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct ApiKeyAuth {
key: String,
value: String,
#[serde(rename = "addTo")]
add_to: String, // "header" or "query"
}
#[derive(Serialize, Deserialize, Debug)]
struct AuthConfig {
#[serde(rename = "type")]
auth_type: String, // "none", "basic", "bearer", "api_key"
basic: BasicAuth,
bearer: BearerAuth,
#[serde(rename = "apiKey")]
api_key: ApiKeyAuth,
}
#[tauri::command]
async fn execute_request(
method: String,
@@ -17,6 +46,7 @@ async fn execute_request(
headers: HashMap<String, String>,
body: Option<String>,
query_params: Option<HashMap<String, String>>,
auth: Option<AuthConfig>,
) -> Result<HttpResponse, String> {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(30))
@@ -37,6 +67,31 @@ async fn execute_request(
request_builder = request_builder.header(key, value);
}
// Add Auth
if let Some(auth_config) = auth {
match auth_config.auth_type.as_str() {
"basic" => {
request_builder = request_builder.basic_auth(
auth_config.basic.username,
Some(auth_config.basic.password),
);
}
"bearer" => {
request_builder = request_builder.bearer_auth(auth_config.bearer.token);
}
"api_key" => {
let key = auth_config.api_key.key;
let value = auth_config.api_key.value;
if auth_config.api_key.add_to == "header" {
request_builder = request_builder.header(key, value);
} else if auth_config.api_key.add_to == "query" {
request_builder = request_builder.query(&[(key, value)]);
}
}
_ => {} // "none" or unknown
}
}
// Add Body
if let Some(b) = body {
if !b.is_empty() {

View File

@@ -3,9 +3,12 @@ import { useRequestStore } from './stores/requestStore';
import RequestPanel from './components/RequestPanel.vue';
import ResponsePanel from './components/ResponsePanel.vue';
import MethodBadge from './components/MethodBadge.vue';
import { History, Layers, Zap } from 'lucide-vue-next';
import SettingsModal from './components/SettingsModal.vue';
import { History, Layers, Zap, Settings } from 'lucide-vue-next';
import { ref } from 'vue';
const store = useRequestStore();
const showSettings = ref(false);
</script>
<template>
@@ -52,6 +55,16 @@ const store = useRequestStore();
</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 -->
@@ -63,5 +76,7 @@ const store = useRequestStore();
<ResponsePanel />
</div>
</main>
<SettingsModal v-if="showSettings" @close="showSettings = false" />
</div>
</template>

View File

@@ -0,0 +1,153 @@
<script setup lang="ts">
import { useRequestStore } from '../stores/requestStore';
import { ShieldCheck, Key, User, Lock, Fingerprint } from 'lucide-vue-next';
import { ref } from 'vue';
const store = useRequestStore();
const authTypes = [
{ value: 'none', label: 'No Auth' },
{ value: 'basic', label: 'Basic Auth' },
{ value: 'bearer', label: 'Bearer Token' },
{ value: 'api_key', label: 'API Key' },
];
const showPassword = ref(false);
const showApiKey = ref(false);
</script>
<template>
<div class="h-full p-4 flex flex-col gap-6 overflow-y-auto text-slate-300">
<!-- Type Selector -->
<div class="flex flex-col gap-2">
<label class="text-xs font-bold uppercase text-slate-500 tracking-wider">Authentication Type</label>
<div class="relative">
<select
v-model="store.activeRequest.auth.type"
class="w-full bg-slate-950 border border-slate-800 rounded-lg px-4 py-2.5 text-sm text-slate-200 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 outline-none appearance-none cursor-pointer"
>
<option v-for="t in authTypes" :key="t.value" :value="t.value">{{ t.label }}</option>
</select>
<ShieldCheck class="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500 pointer-events-none" />
</div>
</div>
<!-- Dynamic Content -->
<div class="flex-1">
<!-- No Auth -->
<div v-if="store.activeRequest.auth.type === 'none'" class="flex flex-col items-center justify-center h-40 text-slate-600 gap-3">
<ShieldCheck class="w-12 h-12 opacity-20" />
<p class="text-sm">This request does not use any authentication.</p>
</div>
<!-- Basic Auth -->
<div v-else-if="store.activeRequest.auth.type === 'basic'" class="flex flex-col gap-4 animate-fade-in">
<div class="flex flex-col gap-1">
<label class="text-xs font-medium text-slate-400">Username</label>
<div class="relative">
<User class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500" />
<input
type="text"
v-model="store.activeRequest.auth.basic.username"
placeholder="username"
class="w-full bg-slate-950 border border-slate-800 rounded-lg pl-10 pr-3 py-2 text-sm text-slate-200 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 outline-none placeholder-slate-600"
/>
</div>
</div>
<div class="flex flex-col gap-1">
<label class="text-xs font-medium text-slate-400">Password</label>
<div class="relative">
<Lock class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500" />
<input
:type="showPassword ? 'text' : 'password'"
v-model="store.activeRequest.auth.basic.password"
placeholder="password"
class="w-full bg-slate-950 border border-slate-800 rounded-lg pl-10 pr-3 py-2 text-sm text-slate-200 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 outline-none placeholder-slate-600"
/>
<button
@click="showPassword = !showPassword"
class="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-slate-500 hover:text-indigo-400 font-medium"
>
{{ showPassword ? 'Hide' : 'Show' }}
</button>
</div>
</div>
</div>
<!-- Bearer Token -->
<div v-else-if="store.activeRequest.auth.type === 'bearer'" class="flex flex-col gap-4 animate-fade-in">
<div class="flex flex-col gap-1 h-full">
<label class="text-xs font-medium text-slate-400">Token</label>
<div class="relative flex-1 min-h-[120px]">
<div class="absolute top-3 left-3 text-xs font-mono text-slate-500 select-none">Bearer</div>
<textarea
v-model="store.activeRequest.auth.bearer.token"
placeholder="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
class="w-full h-full bg-slate-950 border border-slate-800 rounded-lg pl-14 pr-3 py-2 text-sm font-mono text-slate-200 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 outline-none resize-none placeholder-slate-700 leading-relaxed"
></textarea>
</div>
</div>
</div>
<!-- API Key -->
<div v-else-if="store.activeRequest.auth.type === 'api_key'" class="flex flex-col gap-4 animate-fade-in">
<div class="flex flex-col gap-1">
<label class="text-xs font-medium text-slate-400">Key</label>
<div class="relative">
<Key class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500" />
<input
type="text"
v-model="store.activeRequest.auth.apiKey.key"
placeholder="x-api-key"
class="w-full bg-slate-950 border border-slate-800 rounded-lg pl-10 pr-3 py-2 text-sm text-slate-200 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 outline-none placeholder-slate-600"
/>
</div>
</div>
<div class="flex flex-col gap-1">
<label class="text-xs font-medium text-slate-400">Value</label>
<div class="relative">
<Fingerprint class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500" />
<input
:type="showApiKey ? 'text' : 'password'"
v-model="store.activeRequest.auth.apiKey.value"
placeholder="value"
class="w-full bg-slate-950 border border-slate-800 rounded-lg pl-10 pr-3 py-2 text-sm text-slate-200 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 outline-none placeholder-slate-600"
/>
<button
@click="showApiKey = !showApiKey"
class="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-slate-500 hover:text-indigo-400 font-medium"
>
{{ showApiKey ? 'Hide' : 'Show' }}
</button>
</div>
</div>
<div class="flex flex-col gap-1">
<label class="text-xs font-medium text-slate-400">Add To</label>
<div class="flex gap-4 mt-1">
<label class="flex items-center gap-2 cursor-pointer group">
<input type="radio" v-model="store.activeRequest.auth.apiKey.addTo" value="header" class="accent-indigo-500" />
<span class="text-sm text-slate-300 group-hover:text-white">Header</span>
</label>
<label class="flex items-center gap-2 cursor-pointer group">
<input type="radio" v-model="store.activeRequest.auth.apiKey.addTo" value="query" class="accent-indigo-500" />
<span class="text-sm text-slate-300 group-hover:text-white">Query Params</span>
</label>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.animate-fade-in {
animation: fadeIn 0.2s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(5px); }
to { opacity: 1; transform: translateY(0); }
}
</style>

View File

@@ -1,18 +1,32 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useRequestStore } from '../stores/requestStore';
import { useSettingsStore } from '../stores/settingsStore';
import KeyValueEditor from './KeyValueEditor.vue';
import AuthPanel from './AuthPanel.vue';
import { invoke } from '@tauri-apps/api/core';
import { Codemirror } from 'vue-codemirror';
import { json } from '@codemirror/lang-json';
import { oneDark } from '@codemirror/theme-one-dark';
import { EditorView } from '@codemirror/view';
import { Play, Loader2 } from 'lucide-vue-next';
const store = useRequestStore();
const settings = useSettingsStore();
const activeTab = ref('params');
const isLoading = ref(false);
const extensions = [json(), oneDark];
const transparentTheme = EditorView.theme({
"&": {
backgroundColor: "transparent !important",
height: "100%"
},
".cm-gutters": {
backgroundColor: "transparent !important"
}
});
const extensions = [json(), oneDark, transparentTheme];
const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];
@@ -36,6 +50,7 @@ const executeRequest = async () => {
headers,
body: store.activeRequest.body || null,
queryParams: params,
auth: store.activeRequest.auth,
});
// Update Store
@@ -67,9 +82,9 @@ const executeRequest = async () => {
<div class="flex-1 flex items-center bg-slate-950 rounded-lg border border-slate-800 focus-within:border-indigo-500/50 focus-within:ring-1 focus-within:ring-indigo-500/50 transition-all overflow-hidden">
<select
v-model="store.activeRequest.method"
class="bg-transparent text-xs font-bold px-3 py-2 text-slate-300 border-r border-slate-800 focus:outline-none hover:bg-slate-900 cursor-pointer uppercase appearance-none"
class="bg-slate-900 text-xs font-bold px-3 py-2 text-white border-r border-slate-800 focus:outline-none hover:bg-slate-800 cursor-pointer uppercase appearance-none"
>
<option v-for="m in methods" :key="m" :value="m">{{ m }}</option>
<option v-for="m in methods" :key="m" :value="m" class="bg-slate-900 text-white">{{ m }}</option>
</select>
<input
type="text"
@@ -93,7 +108,7 @@ const executeRequest = async () => {
<!-- Configuration Tabs -->
<div class="flex border-b border-slate-800">
<button
v-for="tab in ['params', 'headers', 'body']"
v-for="tab in ['params', 'headers', 'body', 'auth']"
:key="tab"
@click="activeTab = tab"
class="px-4 py-2 text-xs font-medium uppercase tracking-wide border-b-2 transition-colors"
@@ -121,13 +136,22 @@ const executeRequest = async () => {
<Codemirror
v-model="store.activeRequest.body"
placeholder="Request Body (JSON)"
:style="{ height: '100%' }"
:style="{
height: '100%',
fontSize: settings.editorFontSize + 'px',
fontFamily: settings.editorFontFamily
}"
:autofocus="true"
:indent-with-tab="true"
:tab-size="2"
:extensions="extensions"
/>
</div>
<KeepAlive>
<AuthPanel
v-if="activeTab === 'auth'"
/>
</KeepAlive>
</div>
</div>
</template>

View File

@@ -1,14 +1,26 @@
<script setup lang="ts">
import { computed } 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 } from '@codemirror/view'; // Import EditorView
const store = useRequestStore();
const settings = useSettingsStore();
const extensions = [json(), oneDark, EditorView.editable.of(false)];
const transparentTheme = EditorView.theme({
"&": {
backgroundColor: "transparent !important",
height: "100%"
},
".cm-gutters": {
backgroundColor: "transparent !important"
}
});
const extensions = [json(), oneDark, transparentTheme, EditorView.editable.of(false)];
const formattedBody = computed(() => {
if (!store.activeRequest.response) return '';
@@ -48,7 +60,11 @@ const statusColor = computed(() => {
<div class="flex-1 overflow-hidden">
<Codemirror
:model-value="formattedBody"
:style="{ height: '100%' }"
:style="{
height: '100%',
fontSize: settings.editorFontSize + 'px',
fontFamily: settings.editorFontFamily
}"
:extensions="extensions"
/>
</div>

View File

@@ -0,0 +1,87 @@
<script setup lang="ts">
import { useSettingsStore } from '../stores/settingsStore';
import { X } from 'lucide-vue-next';
const emit = defineEmits(['close']);
const settings = useSettingsStore();
const fontOptions = [
"Fira Code, Consolas, monospace",
"Consolas, 'Courier New', monospace",
"'Courier New', Courier, monospace",
"Monaco, Menlo, 'Ubuntu Mono', monospace",
"Arial, sans-serif"
];
</script>
<template>
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm" @click.self="emit('close')">
<div class="bg-slate-900 border border-slate-700 rounded-xl shadow-2xl w-full max-w-md overflow-hidden animate-in fade-in zoom-in-95 duration-200">
<!-- Header -->
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-800 bg-slate-950/50">
<h2 class="text-lg font-semibold text-slate-100">Editor Settings</h2>
<button
@click="emit('close')"
class="text-slate-400 hover:text-white transition-colors p-1 hover:bg-slate-800 rounded-md"
>
<X class="w-5 h-5" />
</button>
</div>
<!-- Content -->
<div class="p-6 space-y-6">
<!-- Font Size -->
<div class="space-y-3">
<div class="flex justify-between items-center">
<label class="text-sm font-medium text-slate-300">Font Size</label>
<span class="text-xs font-mono text-slate-500 bg-slate-950 px-2 py-1 rounded border border-slate-800">
{{ settings.editorFontSize }}px
</span>
</div>
<input
type="range"
v-model.number="settings.editorFontSize"
min="10"
max="24"
step="1"
class="w-full h-2 bg-slate-800 rounded-lg appearance-none cursor-pointer accent-indigo-500 hover:accent-indigo-400"
>
</div>
<!-- Font Family -->
<div class="space-y-3">
<label class="text-sm font-medium text-slate-300">Font Family</label>
<div class="space-y-2">
<select
v-model="settings.editorFontFamily"
class="w-full bg-slate-950 border border-slate-700 text-slate-300 text-sm rounded-lg focus:ring-indigo-500 focus:border-indigo-500 block p-2.5"
>
<option v-for="font in fontOptions" :key="font" :value="font">
{{ font.split(',')[0].replace(/['"]/g, '') }}
</option>
<option value="custom">Custom...</option>
</select>
<input
v-if="settings.editorFontFamily === 'custom' || !fontOptions.includes(settings.editorFontFamily)"
type="text"
v-model="settings.editorFontFamily"
placeholder="e.g. 'JetBrains Mono', monospace"
class="w-full bg-slate-950 border border-slate-700 text-slate-300 text-sm rounded-lg focus:ring-indigo-500 focus:border-indigo-500 block p-2.5 font-mono"
>
</div>
</div>
</div>
<!-- Footer -->
<div class="px-6 py-4 bg-slate-950/50 border-t border-slate-800 flex justify-end">
<button
@click="emit('close')"
class="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-200 text-sm font-medium rounded-lg transition-colors border border-slate-700"
>
Done
</button>
</div>
</div>
</div>
</template>

View File

@@ -8,6 +8,13 @@ export interface KeyValue {
enabled: boolean;
}
export interface AuthState {
type: 'none' | 'basic' | 'bearer' | 'api_key';
basic: { username: '', password: '' };
bearer: { token: '' };
apiKey: { key: '', value: '', addTo: 'header' | 'query' };
}
export interface RequestData {
id: string;
method: string;
@@ -15,6 +22,7 @@ export interface RequestData {
params: KeyValue[];
headers: KeyValue[];
body: string;
auth: AuthState;
response?: {
status: number;
headers: Record<string, string>;
@@ -39,6 +47,12 @@ export const useRequestStore = defineStore('request', () => {
{ id: crypto.randomUUID(), key: '', value: '', enabled: true }
],
body: '',
auth: {
type: 'none',
basic: { username: '', password: '' },
bearer: { token: '' },
apiKey: { key: '', value: '', addTo: 'header' }
},
timestamp: Date.now(),
});
@@ -81,6 +95,16 @@ export const useRequestStore = defineStore('request', () => {
if (loaded.params.length === 0) loaded.params.push({ id: crypto.randomUUID(), key: '', value: '', enabled: true });
if (loaded.headers.length === 0) loaded.headers.push({ id: crypto.randomUUID(), key: '', value: '', enabled: true });
// Ensure auth object exists (migration for old history)
if (!loaded.auth) {
loaded.auth = {
type: 'none',
basic: { username: '', password: '' },
bearer: { token: '' },
apiKey: { key: '', value: '', addTo: 'header' }
};
}
activeRequest.value = loaded;
};

View File

@@ -0,0 +1,27 @@
import { defineStore } from 'pinia';
import { ref, watch } from 'vue';
export const useSettingsStore = defineStore('settings', () => {
// Initialize from localStorage or defaults
const editorFontSize = ref<number>(
Number(localStorage.getItem('editorFontSize')) || 14
);
const editorFontFamily = ref<string>(
localStorage.getItem('editorFontFamily') || 'Fira Code, Consolas, monospace'
);
// Watchers for persistence
watch(editorFontSize, (val) => {
localStorage.setItem('editorFontSize', val.toString());
});
watch(editorFontFamily, (val) => {
localStorage.setItem('editorFontFamily', val);
});
return {
editorFontSize,
editorFontFamily,
};
});

View File

@@ -9,3 +9,30 @@ body {
#app {
@apply h-full w-full;
}
/* Custom Scrollbar Styles */
/* For Webkit browsers (Chrome, Safari) */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #1e293b; /* slate-800 */
}
::-webkit-scrollbar-thumb {
background: #475569; /* slate-600 */
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #64748b; /* slate-500 */
}
/* For Firefox (requires 'scrollbar-width' and 'scrollbar-color' on elements) */
/* Applying to all elements for broader coverage */
* {
scrollbar-width: thin;
scrollbar-color: #475569 #1e293b; /* thumb track */
}