diff --git a/bun.lockb b/bun.lockb
index e0f6621..d29945f 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index ee6837c..0928bae 100644
--- a/package.json
+++ b/package.json
@@ -13,8 +13,12 @@
},
"license": "MIT",
"dependencies": {
+ "@codemirror/lang-html": "^6.4.11",
+ "@codemirror/lang-json": "^6.0.2",
+ "@codemirror/lang-xml": "^6.1.0",
"@tauri-apps/api": "^2",
- "@tauri-apps/plugin-opener": "^2"
+ "@tauri-apps/plugin-opener": "^2",
+ "svelte-codemirror-editor": "^2.1.0"
},
"devDependencies": {
"@internationalized/date": "^3.8.1",
diff --git a/src/lib/components/code-editor.svelte b/src/lib/components/code-editor.svelte
new file mode 100644
index 0000000..8a3596e
--- /dev/null
+++ b/src/lib/components/code-editor.svelte
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
diff --git a/src/lib/components/request-panel.svelte b/src/lib/components/request-panel.svelte
index 5cd2954..c74a4c8 100644
--- a/src/lib/components/request-panel.svelte
+++ b/src/lib/components/request-panel.svelte
@@ -2,20 +2,26 @@
import { Button } from "$lib/components/ui/button/index.js";
import { Input } from "$lib/components/ui/input/index.js";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
+ import * as Select from "$lib/components/ui/select/index.js";
+ import * as Tabs from "$lib/components/ui/tabs/index.js";
import SendIcon from "@lucide/svelte/icons/send";
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
- import type { Request, HttpMethod } from "$lib/types/request";
+ import XIcon from "@lucide/svelte/icons/x";
+ import CodeEditor from "./code-editor.svelte";
+ import VariableInput from "./variable-input.svelte";
+ import type { Request, HttpMethod, BodyType } from "$lib/types/request";
+ import type { ResolvedVariable } from "$lib/types/variable";
type Props = {
request: Request;
+ variables?: ResolvedVariable[];
onSend: (request: Request) => void;
onUpdate: (request: Request) => void;
};
- let { request, onSend, onUpdate }: Props = $props();
+ let { request, variables = [], onSend, onUpdate }: Props = $props();
let localRequest = $state({ ...request });
- let activeTab = $state<"params" | "headers" | "body">("params");
$effect(() => {
localRequest = { ...request };
@@ -30,6 +36,15 @@
"HEAD",
"OPTIONS",
];
+ const bodyTypes: { value: BodyType; label: string }[] = [
+ { value: "none", label: "None" },
+ { value: "json", label: "JSON" },
+ { value: "xml", label: "XML" },
+ { value: "text", label: "Text" },
+ { value: "html", label: "HTML" },
+ { value: "form-data", label: "Form Data" },
+ { value: "x-www-form-urlencoded", label: "URL Encoded" },
+ ];
function getMethodColor(method: string): string {
const colors: Record = {
@@ -49,15 +64,39 @@
onUpdate(localRequest);
}
- function handleUrlChange(e: Event) {
- const target = e.target as HTMLInputElement;
- localRequest.url = target.value;
+ function handleUrlChange(value: string) {
+ localRequest.url = value;
+ onUpdate(localRequest);
+ }
+
+ function handleBodyTypeChange(value: BodyType) {
+ localRequest.bodyType = value;
+ onUpdate(localRequest);
+ }
+
+ function handleBodyChange(value: string) {
+ localRequest.body = value;
onUpdate(localRequest);
}
function handleSend() {
onSend(localRequest);
}
+
+ function formatBody() {
+ if (localRequest.bodyType === "json") {
+ try {
+ localRequest.body = JSON.stringify(
+ JSON.parse(localRequest.body),
+ null,
+ 2
+ );
+ onUpdate(localRequest);
+ } catch {
+ // Invalid JSON, don't format
+ }
+ }
+ }
@@ -89,10 +128,11 @@
-
@@ -103,159 +143,229 @@
-
-
-
-
-
-
-
-
+
+
+
+ Params
+
+
+ Headers
+
+
+ Body
+
+
-
- {#if activeTab === "params"}
-
- {:else if activeTab === "headers"}
-
- {:else if activeTab === "body"}
-
+ >
+ Add Parameter
+
+
+
+
+
+
+ {#each localRequest.headers as header, i (i)}
+
+ {
+ localRequest.headers[i].key = value;
+ onUpdate(localRequest);
+ }}
+ />
+ {
+ localRequest.headers[i].value = value;
+ onUpdate(localRequest);
+ }}
+ />
+
+
+ {/each}
+
+
+
+
+
+
+ Content Type:
+ handleBodyTypeChange(value as BodyType)}
+ >
+
+ {bodyTypes.find((t) => t.value === localRequest.bodyType)?.label ||
+ "None"}
+
+
+ {#each bodyTypes as bodyType (bodyType.value)}
+ {bodyType.label}
+ {/each}
+
+
+ {#if localRequest.bodyType === "json"}
+
+ {/if}
+
+
+ {#if localRequest.bodyType === "none"}
+
+ This request does not have a body
+
+ {:else if localRequest.bodyType === "form-data" || localRequest.bodyType === "x-www-form-urlencoded"}
+
+ {#each localRequest.formData as item, i (i)}
+
+ {
+ localRequest.formData[i].key = value;
+ onUpdate(localRequest);
+ }}
+ />
+ {
+ localRequest.formData[i].value = value;
+ onUpdate(localRequest);
+ }}
+ />
+
+
+ {/each}
+
+
+ {:else}
+
+ {/if}
+
+
+
diff --git a/src/lib/components/response-panel.svelte b/src/lib/components/response-panel.svelte
new file mode 100644
index 0000000..299f493
--- /dev/null
+++ b/src/lib/components/response-panel.svelte
@@ -0,0 +1,176 @@
+
+
+
+ {#if loading}
+
+
+
+ Sending request...
+
+
+ {:else if response}
+
+
+ {response.status}
+ {response.statusText}
+
+
+ {formatDuration(response.duration)}
+
+
+ {formatSize(response.size)}
+
+
+
+
+
+
+
+
+ Body
+
+
+ Headers ({response.headers.length})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Name |
+ Value |
+
+
+
+ {#each response.headers as header (header.key)}
+
+ | {header.key} |
+ {header.value} |
+
+ {/each}
+
+
+
+
+
+ {:else}
+
+ Send a request to see the response
+
+ {/if}
+
diff --git a/src/lib/components/ui/badge/badge.svelte b/src/lib/components/ui/badge/badge.svelte
new file mode 100644
index 0000000..bfaa9c5
--- /dev/null
+++ b/src/lib/components/ui/badge/badge.svelte
@@ -0,0 +1,50 @@
+
+
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/badge/index.ts b/src/lib/components/ui/badge/index.ts
new file mode 100644
index 0000000..64e0aa9
--- /dev/null
+++ b/src/lib/components/ui/badge/index.ts
@@ -0,0 +1,2 @@
+export { default as Badge } from "./badge.svelte";
+export { badgeVariants, type BadgeVariant } from "./badge.svelte";
diff --git a/src/lib/components/ui/scroll-area/index.ts b/src/lib/components/ui/scroll-area/index.ts
new file mode 100644
index 0000000..e86a25b
--- /dev/null
+++ b/src/lib/components/ui/scroll-area/index.ts
@@ -0,0 +1,10 @@
+import Scrollbar from "./scroll-area-scrollbar.svelte";
+import Root from "./scroll-area.svelte";
+
+export {
+ Root,
+ Scrollbar,
+ //,
+ Root as ScrollArea,
+ Scrollbar as ScrollAreaScrollbar,
+};
diff --git a/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte b/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte
new file mode 100644
index 0000000..ff9d3cf
--- /dev/null
+++ b/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte
@@ -0,0 +1,31 @@
+
+
+
+ {@render children?.()}
+
+
diff --git a/src/lib/components/ui/scroll-area/scroll-area.svelte b/src/lib/components/ui/scroll-area/scroll-area.svelte
new file mode 100644
index 0000000..603d446
--- /dev/null
+++ b/src/lib/components/ui/scroll-area/scroll-area.svelte
@@ -0,0 +1,43 @@
+
+
+
+
+ {@render children?.()}
+
+ {#if orientation === "vertical" || orientation === "both"}
+
+ {/if}
+ {#if orientation === "horizontal" || orientation === "both"}
+
+ {/if}
+
+
diff --git a/src/lib/components/ui/select/index.ts b/src/lib/components/ui/select/index.ts
new file mode 100644
index 0000000..9e8d3e9
--- /dev/null
+++ b/src/lib/components/ui/select/index.ts
@@ -0,0 +1,37 @@
+import { Select as SelectPrimitive } from "bits-ui";
+
+import Group from "./select-group.svelte";
+import Label from "./select-label.svelte";
+import Item from "./select-item.svelte";
+import Content from "./select-content.svelte";
+import Trigger from "./select-trigger.svelte";
+import Separator from "./select-separator.svelte";
+import ScrollDownButton from "./select-scroll-down-button.svelte";
+import ScrollUpButton from "./select-scroll-up-button.svelte";
+import GroupHeading from "./select-group-heading.svelte";
+
+const Root = SelectPrimitive.Root;
+
+export {
+ Root,
+ Group,
+ Label,
+ Item,
+ Content,
+ Trigger,
+ Separator,
+ ScrollDownButton,
+ ScrollUpButton,
+ GroupHeading,
+ //
+ Root as Select,
+ Group as SelectGroup,
+ Label as SelectLabel,
+ Item as SelectItem,
+ Content as SelectContent,
+ Trigger as SelectTrigger,
+ Separator as SelectSeparator,
+ ScrollDownButton as SelectScrollDownButton,
+ ScrollUpButton as SelectScrollUpButton,
+ GroupHeading as SelectGroupHeading,
+};
diff --git a/src/lib/components/ui/select/select-content.svelte b/src/lib/components/ui/select/select-content.svelte
new file mode 100644
index 0000000..1a96d3c
--- /dev/null
+++ b/src/lib/components/ui/select/select-content.svelte
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+ {@render children?.()}
+
+
+
+
diff --git a/src/lib/components/ui/select/select-group-heading.svelte b/src/lib/components/ui/select/select-group-heading.svelte
new file mode 100644
index 0000000..1fab5f0
--- /dev/null
+++ b/src/lib/components/ui/select/select-group-heading.svelte
@@ -0,0 +1,21 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/select/select-group.svelte b/src/lib/components/ui/select/select-group.svelte
new file mode 100644
index 0000000..5454fdb
--- /dev/null
+++ b/src/lib/components/ui/select/select-group.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/lib/components/ui/select/select-item.svelte b/src/lib/components/ui/select/select-item.svelte
new file mode 100644
index 0000000..123a912
--- /dev/null
+++ b/src/lib/components/ui/select/select-item.svelte
@@ -0,0 +1,38 @@
+
+
+
+ {#snippet children({ selected, highlighted })}
+
+ {#if selected}
+
+ {/if}
+
+ {#if childrenProp}
+ {@render childrenProp({ selected, highlighted })}
+ {:else}
+ {label || value}
+ {/if}
+ {/snippet}
+
diff --git a/src/lib/components/ui/select/select-label.svelte b/src/lib/components/ui/select/select-label.svelte
new file mode 100644
index 0000000..4696025
--- /dev/null
+++ b/src/lib/components/ui/select/select-label.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/select/select-scroll-down-button.svelte b/src/lib/components/ui/select/select-scroll-down-button.svelte
new file mode 100644
index 0000000..3629205
--- /dev/null
+++ b/src/lib/components/ui/select/select-scroll-down-button.svelte
@@ -0,0 +1,20 @@
+
+
+
+
+
diff --git a/src/lib/components/ui/select/select-scroll-up-button.svelte b/src/lib/components/ui/select/select-scroll-up-button.svelte
new file mode 100644
index 0000000..1aa2300
--- /dev/null
+++ b/src/lib/components/ui/select/select-scroll-up-button.svelte
@@ -0,0 +1,20 @@
+
+
+
+
+
diff --git a/src/lib/components/ui/select/select-separator.svelte b/src/lib/components/ui/select/select-separator.svelte
new file mode 100644
index 0000000..0eac3eb
--- /dev/null
+++ b/src/lib/components/ui/select/select-separator.svelte
@@ -0,0 +1,18 @@
+
+
+
diff --git a/src/lib/components/ui/select/select-trigger.svelte b/src/lib/components/ui/select/select-trigger.svelte
new file mode 100644
index 0000000..d405187
--- /dev/null
+++ b/src/lib/components/ui/select/select-trigger.svelte
@@ -0,0 +1,29 @@
+
+
+
+ {@render children?.()}
+
+
diff --git a/src/lib/components/ui/spinner/index.ts b/src/lib/components/ui/spinner/index.ts
new file mode 100644
index 0000000..f8b1ced
--- /dev/null
+++ b/src/lib/components/ui/spinner/index.ts
@@ -0,0 +1 @@
+export { default as Spinner } from "./spinner.svelte";
diff --git a/src/lib/components/ui/spinner/spinner.svelte b/src/lib/components/ui/spinner/spinner.svelte
new file mode 100644
index 0000000..9b12131
--- /dev/null
+++ b/src/lib/components/ui/spinner/spinner.svelte
@@ -0,0 +1,14 @@
+
+
+
diff --git a/src/lib/components/ui/tabs/index.ts b/src/lib/components/ui/tabs/index.ts
new file mode 100644
index 0000000..12d4327
--- /dev/null
+++ b/src/lib/components/ui/tabs/index.ts
@@ -0,0 +1,16 @@
+import Root from "./tabs.svelte";
+import Content from "./tabs-content.svelte";
+import List from "./tabs-list.svelte";
+import Trigger from "./tabs-trigger.svelte";
+
+export {
+ Root,
+ Content,
+ List,
+ Trigger,
+ //
+ Root as Tabs,
+ Content as TabsContent,
+ List as TabsList,
+ Trigger as TabsTrigger,
+};
diff --git a/src/lib/components/ui/tabs/tabs-content.svelte b/src/lib/components/ui/tabs/tabs-content.svelte
new file mode 100644
index 0000000..340d65c
--- /dev/null
+++ b/src/lib/components/ui/tabs/tabs-content.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/src/lib/components/ui/tabs/tabs-list.svelte b/src/lib/components/ui/tabs/tabs-list.svelte
new file mode 100644
index 0000000..08932b6
--- /dev/null
+++ b/src/lib/components/ui/tabs/tabs-list.svelte
@@ -0,0 +1,20 @@
+
+
+
diff --git a/src/lib/components/ui/tabs/tabs-trigger.svelte b/src/lib/components/ui/tabs/tabs-trigger.svelte
new file mode 100644
index 0000000..dced992
--- /dev/null
+++ b/src/lib/components/ui/tabs/tabs-trigger.svelte
@@ -0,0 +1,20 @@
+
+
+
diff --git a/src/lib/components/ui/tabs/tabs.svelte b/src/lib/components/ui/tabs/tabs.svelte
new file mode 100644
index 0000000..ef6cada
--- /dev/null
+++ b/src/lib/components/ui/tabs/tabs.svelte
@@ -0,0 +1,19 @@
+
+
+
diff --git a/src/lib/components/ui/textarea/index.ts b/src/lib/components/ui/textarea/index.ts
new file mode 100644
index 0000000..ace797a
--- /dev/null
+++ b/src/lib/components/ui/textarea/index.ts
@@ -0,0 +1,7 @@
+import Root from "./textarea.svelte";
+
+export {
+ Root,
+ //
+ Root as Textarea,
+};
diff --git a/src/lib/components/ui/textarea/textarea.svelte b/src/lib/components/ui/textarea/textarea.svelte
new file mode 100644
index 0000000..7fcef1a
--- /dev/null
+++ b/src/lib/components/ui/textarea/textarea.svelte
@@ -0,0 +1,23 @@
+
+
+
diff --git a/src/lib/components/variable-input.svelte b/src/lib/components/variable-input.svelte
new file mode 100644
index 0000000..787a7e8
--- /dev/null
+++ b/src/lib/components/variable-input.svelte
@@ -0,0 +1,167 @@
+
+
+
+
+
+ {#if showSuggestions}
+
+ {#each filteredVariables as variable, i (variable.name)}
+
+ {/each}
+
+ {/if}
+
diff --git a/src/lib/services/collections.ts b/src/lib/services/collections.ts
index 9d3703d..92139da 100644
--- a/src/lib/services/collections.ts
+++ b/src/lib/services/collections.ts
@@ -17,7 +17,9 @@ const collections: Map = new Map([
url: "https://api.example.com/users",
headers: [],
params: [],
+ bodyType: "none",
body: "",
+ formData: [],
collectionId: "col-1",
workspaceId: "1",
},
@@ -30,7 +32,9 @@ const collections: Map = new Map([
{ key: "Content-Type", value: "application/json", enabled: true },
],
params: [],
+ bodyType: "json",
body: '{"name": "John", "email": "john@example.com"}',
+ formData: [],
collectionId: "col-1",
workspaceId: "1",
},
@@ -52,7 +56,9 @@ const collections: Map = new Map([
url: "https://api.example.com/products",
headers: [],
params: [{ key: "limit", value: "10", enabled: true }],
+ bodyType: "none",
body: "",
+ formData: [],
collectionId: "col-2",
workspaceId: "1",
},
@@ -71,7 +77,9 @@ const standaloneRequests: Map = new Map([
url: "https://api.example.com/health",
headers: [],
params: [],
+ bodyType: "none",
body: "",
+ formData: [],
collectionId: null,
workspaceId: "1",
},
diff --git a/src/lib/services/variables.ts b/src/lib/services/variables.ts
new file mode 100644
index 0000000..b87fac4
--- /dev/null
+++ b/src/lib/services/variables.ts
@@ -0,0 +1,199 @@
+import type {
+ Variable,
+ VariableScope,
+ ResolvedVariable,
+} from "$lib/types/variable";
+
+const variables: Map = new Map([
+ [
+ "var-1",
+ {
+ id: "var-1",
+ name: "BASE_URL",
+ value: "https://api.example.com",
+ scope: "global",
+ scopeId: null,
+ isSecret: false,
+ description: "Base URL for all API requests",
+ },
+ ],
+ [
+ "var-2",
+ {
+ id: "var-2",
+ name: "API_KEY",
+ value: "sk-1234567890",
+ scope: "global",
+ scopeId: null,
+ isSecret: true,
+ description: "API authentication key",
+ },
+ ],
+ [
+ "var-3",
+ {
+ id: "var-3",
+ name: "USER_ID",
+ value: "user-123",
+ scope: "workspace",
+ scopeId: "1",
+ isSecret: false,
+ description: "Default user ID for testing",
+ },
+ ],
+ [
+ "var-4",
+ {
+ id: "var-4",
+ name: "AUTH_TOKEN",
+ value: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
+ scope: "workspace",
+ scopeId: "1",
+ isSecret: true,
+ description: "Authentication token",
+ },
+ ],
+]);
+
+export async function get_all_variables(): Promise {
+ return [...variables.values()];
+}
+
+export async function get_variables_by_scope(
+ scope: VariableScope,
+ scopeId: string | null
+): Promise {
+ return [...variables.values()].filter(
+ (v) => v.scope === scope && v.scopeId === scopeId
+ );
+}
+
+export async function get_global_variables(): Promise {
+ return get_variables_by_scope("global", null);
+}
+
+export async function get_workspace_variables(
+ workspaceId: string
+): Promise {
+ return get_variables_by_scope("workspace", workspaceId);
+}
+
+export async function get_collection_variables(
+ collectionId: string
+): Promise {
+ return get_variables_by_scope("collection", collectionId);
+}
+
+export async function get_request_variables(
+ requestId: string
+): Promise {
+ return get_variables_by_scope("request", requestId);
+}
+
+export async function get_resolved_variables(
+ workspaceId: string,
+ collectionId: string | null,
+ requestId: string | null
+): Promise {
+ const resolved: Map = new Map();
+
+ const globalVars = await get_global_variables();
+ for (const v of globalVars) {
+ resolved.set(v.name, {
+ name: v.name,
+ value: v.value,
+ scope: v.scope,
+ isSecret: v.isSecret,
+ });
+ }
+
+ const workspaceVars = await get_workspace_variables(workspaceId);
+ for (const v of workspaceVars) {
+ resolved.set(v.name, {
+ name: v.name,
+ value: v.value,
+ scope: v.scope,
+ isSecret: v.isSecret,
+ });
+ }
+
+ if (collectionId) {
+ const collectionVars = await get_collection_variables(collectionId);
+ for (const v of collectionVars) {
+ resolved.set(v.name, {
+ name: v.name,
+ value: v.value,
+ scope: v.scope,
+ isSecret: v.isSecret,
+ });
+ }
+ }
+
+ if (requestId) {
+ const requestVars = await get_request_variables(requestId);
+ for (const v of requestVars) {
+ resolved.set(v.name, {
+ name: v.name,
+ value: v.value,
+ scope: v.scope,
+ isSecret: v.isSecret,
+ });
+ }
+ }
+
+ return [...resolved.values()];
+}
+
+export async function get_variable(id: string): Promise {
+ return variables.get(id);
+}
+
+export async function create_variable(
+ variable: Omit
+): Promise {
+ const newVariable: Variable = {
+ ...variable,
+ id: crypto.randomUUID(),
+ };
+ variables.set(newVariable.id, newVariable);
+ return newVariable;
+}
+
+export async function update_variable(
+ id: string,
+ updates: Partial>
+): Promise {
+ const variable = variables.get(id);
+ if (!variable) return false;
+
+ Object.assign(variable, updates);
+ return true;
+}
+
+export async function delete_variable(id: string): Promise {
+ return variables.delete(id);
+}
+
+export function interpolate_variables(
+ text: string,
+ resolvedVariables: ResolvedVariable[]
+): string {
+ let result = text;
+ for (const variable of resolvedVariables) {
+ const pattern = new RegExp(`\\{\\{\\s*${variable.name}\\s*\\}\\}`, "g");
+ result = result.replace(pattern, variable.value);
+ }
+ return result;
+}
+
+export function extract_variable_names(text: string): string[] {
+ const pattern = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
+ const matches: string[] = [];
+ let match;
+ while ((match = pattern.exec(text)) !== null) {
+ if (!matches.includes(match[1])) {
+ matches.push(match[1]);
+ }
+ }
+ return matches;
+}
diff --git a/src/lib/types/request.ts b/src/lib/types/request.ts
index 385fb37..6d47697 100644
--- a/src/lib/types/request.ts
+++ b/src/lib/types/request.ts
@@ -7,6 +7,15 @@ export type HttpMethod =
| "HEAD"
| "OPTIONS";
+export type BodyType =
+ | "none"
+ | "json"
+ | "xml"
+ | "text"
+ | "html"
+ | "form-data"
+ | "x-www-form-urlencoded";
+
export type RequestHeader = {
key: string;
value: string;
@@ -19,6 +28,13 @@ export type RequestParam = {
enabled: boolean;
};
+export type FormDataItem = {
+ key: string;
+ value: string;
+ type: "text" | "file";
+ enabled: boolean;
+};
+
export type Request = {
id: string;
name: string;
@@ -26,7 +42,9 @@ export type Request = {
url: string;
headers: RequestHeader[];
params: RequestParam[];
+ bodyType: BodyType;
body: string;
+ formData: FormDataItem[];
collectionId: string | null;
workspaceId: string;
};
diff --git a/src/lib/types/response.ts b/src/lib/types/response.ts
new file mode 100644
index 0000000..8b44542
--- /dev/null
+++ b/src/lib/types/response.ts
@@ -0,0 +1,14 @@
+export type ResponseHeader = {
+ key: string;
+ value: string;
+};
+
+export type Response = {
+ status: number;
+ statusText: string;
+ headers: ResponseHeader[];
+ body: string;
+ contentType: string;
+ duration: number;
+ size: number;
+};
diff --git a/src/lib/types/variable.ts b/src/lib/types/variable.ts
new file mode 100644
index 0000000..f217164
--- /dev/null
+++ b/src/lib/types/variable.ts
@@ -0,0 +1,18 @@
+export type VariableScope = "global" | "workspace" | "collection" | "request";
+
+export type Variable = {
+ id: string;
+ name: string;
+ value: string;
+ scope: VariableScope;
+ scopeId: string | null;
+ isSecret: boolean;
+ description?: string;
+};
+
+export type ResolvedVariable = {
+ name: string;
+ value: string;
+ scope: VariableScope;
+ isSecret: boolean;
+};
diff --git a/src/routes/workspaces/[id]/+page.svelte b/src/routes/workspaces/[id]/+page.svelte
index 8796d04..ace3479 100644
--- a/src/routes/workspaces/[id]/+page.svelte
+++ b/src/routes/workspaces/[id]/+page.svelte
@@ -21,9 +21,13 @@
update_request,
delete_request,
} from "$lib/services/collections";
+ import { get_resolved_variables } from "$lib/services/variables";
import type { Workspace } from "$lib/types/workspace";
import type { Collection } from "$lib/types/collection";
import type { Request, HttpMethod } from "$lib/types/request";
+ import type { Response } from "$lib/types/response";
+ import type { ResolvedVariable } from "$lib/types/variable";
+ import ResponsePanel from "$lib/components/response-panel.svelte";
const { params } = $props<{ params: { id: string } }>();
@@ -31,6 +35,9 @@
let collections = $state([]);
let standaloneRequests = $state([]);
let selectedRequest = $state(null);
+ let response = $state(null);
+ let loading = $state(false);
+ let resolvedVariables = $state([]);
let collectionDialogOpen = $state(false);
let collectionDialogMode = $state<"create" | "edit">("create");
@@ -59,6 +66,7 @@
workspace = await get_workspace(params.id);
collections = await get_collections_by_workspace(params.id);
standaloneRequests = await get_standalone_requests_by_workspace(params.id);
+ resolvedVariables = await get_resolved_variables(params.id, null, null);
}
function goBack() {
@@ -67,6 +75,7 @@
function handleRequestSelect(request: Request) {
selectedRequest = request;
+ response = null;
}
function handleCreateCollection() {
@@ -138,7 +147,9 @@
url: "",
headers: [],
params: [],
+ bodyType: "none",
body: "",
+ formData: [],
collectionId: requestCollectionId,
workspaceId: params.id,
});
@@ -169,8 +180,59 @@
deleteTarget = null;
}
- function handleSendRequest(request: Request) {
- console.log("Sending request:", request);
+ async function handleSendRequest(request: Request) {
+ loading = true;
+ response = null;
+
+ // Simulate API request (will be replaced with actual Tauri call later)
+ const startTime = Date.now();
+
+ try {
+ // Mock response for now
+ await new Promise((resolve) =>
+ setTimeout(resolve, 500 + Math.random() * 1000)
+ );
+
+ const mockBody = JSON.stringify(
+ {
+ success: true,
+ message: "This is a mock response",
+ data: {
+ id: 1,
+ name: "Example",
+ timestamp: new Date().toISOString(),
+ },
+ },
+ null,
+ 2
+ );
+
+ response = {
+ status: 200,
+ statusText: "OK",
+ headers: [
+ { key: "Content-Type", value: "application/json" },
+ { key: "X-Request-Id", value: crypto.randomUUID() },
+ { key: "Cache-Control", value: "no-cache" },
+ ],
+ body: mockBody,
+ contentType: "application/json",
+ duration: Date.now() - startTime,
+ size: new Blob([mockBody]).size,
+ };
+ } catch (error) {
+ response = {
+ status: 500,
+ statusText: "Error",
+ headers: [],
+ body: JSON.stringify({ error: "Request failed" }),
+ contentType: "application/json",
+ duration: Date.now() - startTime,
+ size: 0,
+ };
+ } finally {
+ loading = false;
+ }
}
async function handleUpdateRequest(request: Request) {
@@ -220,13 +282,21 @@
-
+
{#if selectedRequest}
-
+
{:else}
diff --git a/vite.config.js b/vite.config.js
index a114008..b124783 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -8,6 +8,15 @@ const host = process.env.TAURI_DEV_HOST;
// https://vitejs.dev/config/
export default defineConfig(async () => ({
plugins: [tailwindcss(), sveltekit()],
+ optimizeDeps: {
+ exclude: [
+ "svelte-codemirror-editor",
+ "codemirror",
+ "@codemirror/lang-json",
+ "@codemirror/lang-xml",
+ "@codemirror/lang-html",
+ ],
+ },
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
//
// 1. prevent vite from obscuring rust errors
@@ -18,7 +27,9 @@ export default defineConfig(async () => ({
strictPort: true,
host: host || false,
hmr: host ? { protocol: "ws", host, port: 1421 } : undefined,
- watch: { // 3. tell vite to ignore watching `src-tauri`
- ignored: ["**/src-tauri/**"] }
- }
+ watch: {
+ // 3. tell vite to ignore watching `src-tauri`
+ ignored: ["**/src-tauri/**"],
+ },
+ },
}));