From 9cb60f74739e66b32fa2a6e781c768b63a71e7ff Mon Sep 17 00:00:00 2001 From: xyroscar Date: Mon, 24 Nov 2025 16:16:45 -0800 Subject: [PATCH] added request panel and collections sidebar --- src/lib/components/request-panel.svelte | 261 +++++++++++++++ src/lib/components/workspace-sidebar.svelte | 247 ++++++++++++++ src/lib/services/collections.ts | 180 ++++++++++ src/lib/services/workspaces.ts | 4 + src/lib/types/collection.ts | 9 + src/lib/types/request.ts | 32 ++ src/routes/workspaces/[id]/+page.svelte | 345 +++++++++++++++++++- 7 files changed, 1066 insertions(+), 12 deletions(-) create mode 100644 src/lib/components/request-panel.svelte create mode 100644 src/lib/components/workspace-sidebar.svelte create mode 100644 src/lib/services/collections.ts create mode 100644 src/lib/types/collection.ts create mode 100644 src/lib/types/request.ts diff --git a/src/lib/components/request-panel.svelte b/src/lib/components/request-panel.svelte new file mode 100644 index 0000000..5cd2954 --- /dev/null +++ b/src/lib/components/request-panel.svelte @@ -0,0 +1,261 @@ + + +
+
+
+ + + {#snippet child({ props })} + + {/snippet} + + + {#each methods as method (method)} + handleMethodChange(method)}> + + {method} + + + {/each} + + + + + + +
+
+ +
+
+
+ + + +
+
+ +
+ {#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} +
+
+
diff --git a/src/lib/components/workspace-sidebar.svelte b/src/lib/components/workspace-sidebar.svelte new file mode 100644 index 0000000..4b236bd --- /dev/null +++ b/src/lib/components/workspace-sidebar.svelte @@ -0,0 +1,247 @@ + + + + + + + +
+ +
+
+ {workspaceName} + Back to workspaces +
+
+
+
+
+ + + + + Collections + + + + + {#each collections as collection (collection.id)} + + + + {#snippet child({ props })} + + + {/snippet} + + + + {#snippet child({ props })} + + + + {/snippet} + + + onCreateRequest(collection.id)} + > + Add Request + + onEditCollection(collection)} + > + Edit Collection + + + onDeleteCollection(collection)} + > + Delete Collection + + + + + + + {#each collection.requests as request (request.id)} + + onRequestSelect(request)} + > + + {request.method.substring(0, 3)} + + {request.name} + + + + {#snippet child({ props })} + + + + {/snippet} + + + onEditRequest(request)} + > + Edit Request + + + onDeleteRequest(request)} + > + Delete Request + + + + + {/each} + + + + {/each} + + + + + + + + + Requests + + + + + {#each standaloneRequests as request (request.id)} + + onRequestSelect(request)} + > + + {request.method.substring(0, 3)} + + {request.name} + + + + {#snippet child({ props })} + + + + {/snippet} + + + onEditRequest(request)}> + Edit Request + + + onDeleteRequest(request)} + > + Delete Request + + + + + {/each} + + + + + + +
diff --git a/src/lib/services/collections.ts b/src/lib/services/collections.ts new file mode 100644 index 0000000..9d3703d --- /dev/null +++ b/src/lib/services/collections.ts @@ -0,0 +1,180 @@ +import type { Collection } from "$lib/types/collection"; +import type { Request, HttpMethod } from "$lib/types/request"; + +const collections: Map = new Map([ + [ + "col-1", + { + id: "col-1", + name: "User API", + description: "User management endpoints", + workspaceId: "1", + requests: [ + { + id: "req-1", + name: "Get Users", + method: "GET", + url: "https://api.example.com/users", + headers: [], + params: [], + body: "", + collectionId: "col-1", + workspaceId: "1", + }, + { + id: "req-2", + name: "Create User", + method: "POST", + url: "https://api.example.com/users", + headers: [ + { key: "Content-Type", value: "application/json", enabled: true }, + ], + params: [], + body: '{"name": "John", "email": "john@example.com"}', + collectionId: "col-1", + workspaceId: "1", + }, + ], + }, + ], + [ + "col-2", + { + id: "col-2", + name: "Products API", + description: "Product catalog endpoints", + workspaceId: "1", + requests: [ + { + id: "req-3", + name: "List Products", + method: "GET", + url: "https://api.example.com/products", + headers: [], + params: [{ key: "limit", value: "10", enabled: true }], + body: "", + collectionId: "col-2", + workspaceId: "1", + }, + ], + }, + ], +]); + +const standaloneRequests: Map = new Map([ + [ + "req-standalone-1", + { + id: "req-standalone-1", + name: "Health Check", + method: "GET", + url: "https://api.example.com/health", + headers: [], + params: [], + body: "", + collectionId: null, + workspaceId: "1", + }, + ], +]); + +export async function get_collections_by_workspace( + workspaceId: string +): Promise { + return [...collections.values()].filter((c) => c.workspaceId === workspaceId); +} + +export async function get_collection( + id: string +): Promise { + return collections.get(id); +} + +export async function create_collection( + collection: Omit +): Promise { + const newCollection: Collection = { + ...collection, + id: crypto.randomUUID(), + requests: [], + }; + collections.set(newCollection.id, newCollection); + return newCollection; +} + +export async function update_collection( + id: string, + updates: Partial> +): Promise { + const collection = collections.get(id); + if (!collection) return false; + + if (updates.name !== undefined) collection.name = updates.name; + if (updates.description !== undefined) + collection.description = updates.description; + return true; +} + +export async function delete_collection(id: string): Promise { + return collections.delete(id); +} + +export async function get_standalone_requests_by_workspace( + workspaceId: string +): Promise { + return [...standaloneRequests.values()].filter( + (r) => r.workspaceId === workspaceId + ); +} + +export async function get_request(id: string): Promise { + for (const collection of collections.values()) { + const request = collection.requests.find((r) => r.id === id); + if (request) return request; + } + return standaloneRequests.get(id); +} + +export async function create_request( + request: Omit +): Promise { + const newRequest: Request = { + ...request, + id: crypto.randomUUID(), + }; + + if (request.collectionId) { + const collection = collections.get(request.collectionId); + if (collection) { + collection.requests.push(newRequest); + } + } else { + standaloneRequests.set(newRequest.id, newRequest); + } + + return newRequest; +} + +export async function update_request( + id: string, + updates: Partial> +): Promise { + const request = await get_request(id); + if (!request) return false; + + Object.assign(request, updates); + return true; +} + +export async function delete_request(id: string): Promise { + if (standaloneRequests.delete(id)) return true; + + for (const collection of collections.values()) { + const index = collection.requests.findIndex((r) => r.id === id); + if (index !== -1) { + collection.requests.splice(index, 1); + return true; + } + } + return false; +} diff --git a/src/lib/services/workspaces.ts b/src/lib/services/workspaces.ts index 2b5ef30..6aa0b3e 100644 --- a/src/lib/services/workspaces.ts +++ b/src/lib/services/workspaces.ts @@ -52,6 +52,10 @@ export async function get_workspaces(): Promise { return convert_to_list(ws); } +export async function get_workspace(id: string): Promise { + return ws.get(id) ?? null; +} + export async function update_workspace(workspace: Workspace): Promise { let w = ws.get(workspace.Id); if (w != undefined) { diff --git a/src/lib/types/collection.ts b/src/lib/types/collection.ts new file mode 100644 index 0000000..deab6d8 --- /dev/null +++ b/src/lib/types/collection.ts @@ -0,0 +1,9 @@ +import type { Request } from "./request"; + +export type Collection = { + id: string; + name: string; + description: string; + workspaceId: string; + requests: Request[]; +}; diff --git a/src/lib/types/request.ts b/src/lib/types/request.ts new file mode 100644 index 0000000..385fb37 --- /dev/null +++ b/src/lib/types/request.ts @@ -0,0 +1,32 @@ +export type HttpMethod = + | "GET" + | "POST" + | "PUT" + | "PATCH" + | "DELETE" + | "HEAD" + | "OPTIONS"; + +export type RequestHeader = { + key: string; + value: string; + enabled: boolean; +}; + +export type RequestParam = { + key: string; + value: string; + enabled: boolean; +}; + +export type Request = { + id: string; + name: string; + method: HttpMethod; + url: string; + headers: RequestHeader[]; + params: RequestParam[]; + body: string; + collectionId: string | null; + workspaceId: string; +}; diff --git a/src/routes/workspaces/[id]/+page.svelte b/src/routes/workspaces/[id]/+page.svelte index 7830391..8796d04 100644 --- a/src/routes/workspaces/[id]/+page.svelte +++ b/src/routes/workspaces/[id]/+page.svelte @@ -1,23 +1,344 @@ -
-

Workspace ID

-

- Current workspace id: {params.id} -

+ + - -
+ +
+
+ +
+ {#if selectedRequest} +

{selectedRequest.name}

+ {:else} +

+ Select a request +

+ {/if} +
+
+ +
+ {#if selectedRequest} + + {:else} + + + + + + No Request Selected + + Select a request from the sidebar or create a new one to get + started. + + + + {/if} +
+
+
+ + + + + + + {collectionDialogMode === "create" + ? "Create Collection" + : "Edit Collection"} + + + {collectionDialogMode === "create" + ? "Create a new collection to organize your requests." + : "Update your collection details."} + + +
+
+ + +
+
+ + +
+ + + +
+
+
+ + + + + + {requestDialogMode === "create" ? "Create Request" : "Edit Request"} + + + {requestDialogMode === "create" + ? "Create a new API request." + : "Update your request details."} + + +
+
+ + +
+
+ + +
+ + + +
+
+
+ + + + + Confirm Delete + + Are you sure you want to delete this {deleteTarget?.type}? This action + cannot be undone. + + + + + + + +