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"} -
- {#each localRequest.params as param, i (i)} -
- { - const target = e.target as HTMLInputElement; - localRequest.params[i].key = target.value; - onUpdate(localRequest); - }} - /> - { - const target = e.target as HTMLInputElement; - localRequest.params[i].value = target.value; - onUpdate(localRequest); - }} - /> - -
- {/each} - -
- {:else if activeTab === "headers"} -
- {#each localRequest.headers as header, i (i)} -
- { - const target = e.target as HTMLInputElement; - localRequest.headers[i].key = target.value; - onUpdate(localRequest); - }} - /> - { - const target = e.target as HTMLInputElement; - localRequest.headers[i].value = target.value; - onUpdate(localRequest); - }} - /> - -
- {/each} - -
- {:else if activeTab === "body"} - - {/if} -
-
+ > + 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}) + + + + +
+ +
+
+ + +
+ + + + + + + + + {#each response.headers as header (header.key)} + + + + + {/each} + +
NameValue
{header.key}{header.value}
+
+
+
+ {: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/**"], + }, + }, }));