Преглед на файлове

add request method functionality

biblius преди 1 седмица
родител
ревизия
0b6fdd1eee

+ 16 - 4
src-tauri/src/cmd.rs

@@ -10,8 +10,8 @@ use crate::{
     state::{AppState, ResponseResult},
     var::{expand_vars, parse_vars},
     workspace::{
-        Workspace, WorkspaceEntry, WorkspaceEntryBase, WorkspaceEntryCreate, WorkspaceEnvVariable,
-        WorkspaceEnvironment,
+        Workspace, WorkspaceEntry, WorkspaceEntryBase, WorkspaceEntryCreate, WorkspaceEntryDisplay,
+        WorkspaceEnvVariable, WorkspaceEnvironment,
     },
 };
 use serde::Deserialize;
@@ -52,8 +52,8 @@ pub async fn get_workspace_entry(
 pub async fn list_workspace_entries(
     state: tauri::State<'_, AppState>,
     id: i64,
-) -> Result<Vec<WorkspaceEntryBase>, String> {
-    match db::list_workspace_entries(state.db.clone(), id).await {
+) -> Result<Vec<WorkspaceEntryDisplay>, String> {
+    match db::list_workspace_entries_display(state.db.clone(), id).await {
         Ok(ws) => Ok(ws),
         Err(e) => Err(e.to_string()),
     }
@@ -106,6 +106,18 @@ pub async fn update_request_body(
     Ok(())
 }
 
+#[tauri::command]
+pub async fn update_request_method(
+    state: tauri::State<'_, AppState>,
+    id: i64,
+    method: String,
+) -> Result<(), String> {
+    if let Err(e) = db::update_request_method(state.db.clone(), id, &method).await {
+        return Err(e.to_string());
+    }
+    Ok(())
+}
+
 #[tauri::command]
 pub async fn parse_url(url: String) -> Result<RequestUrlOwned, UrlError> {
     match RequestUrl::parse(&url) {

+ 22 - 2
src-tauri/src/db.rs

@@ -7,8 +7,8 @@ use crate::{
         RequestQueryUpdate, WorkspaceRequest,
     },
     workspace::{
-        Workspace, WorkspaceEntry, WorkspaceEntryBase, WorkspaceEntryCreate, WorkspaceEntryType,
-        WorkspaceEnvVariable, WorkspaceEnvironment,
+        Workspace, WorkspaceEntry, WorkspaceEntryBase, WorkspaceEntryCreate, WorkspaceEntryDisplay,
+        WorkspaceEntryType, WorkspaceEnvVariable, WorkspaceEnvironment,
     },
     AppResult,
 };
@@ -556,6 +556,26 @@ pub async fn get_workspace_entry(db: SqlitePool, id: i64) -> AppResult<Workspace
     }
 }
 
+pub async fn list_workspace_entries_display(
+    db: SqlitePool,
+    workspace_id: i64,
+) -> AppResult<Vec<WorkspaceEntryDisplay>> {
+    let mut sql = QueryBuilder::new(
+        r#"
+        SELECT e.id, e.workspace_id, e.parent_id, e.name, e.type, e.auth, e.auth_inherit, rp.method
+        FROM workspace_entries e
+        LEFT JOIN request_params rp ON e.id = rp.request_id
+        WHERE e.workspace_id = "#,
+    );
+
+    Ok(sql
+        .push_bind(workspace_id)
+        .push("ORDER BY type DESC")
+        .build_query_as()
+        .fetch_all(&db)
+        .await?)
+}
+
 pub async fn list_workspace_entries(
     db: SqlitePool,
     workspace_id: i64,

+ 1 - 0
src-tauri/src/lib.rs

@@ -54,6 +54,7 @@ pub fn run() {
             cmd::delete_header,
             cmd::insert_request_body,
             cmd::update_request_body,
+            cmd::update_request_method,
             cmd::insert_auth,
             cmd::set_workspace_entry_auth,
             cmd::list_auth,

+ 0 - 4
src-tauri/src/request/url.rs

@@ -477,8 +477,6 @@ pub struct QueryParam<'a> {
 
 impl<'a> QueryParam<'a> {
     fn parse(query: &'a str, mut offset: usize) -> (Vec<Self>, &'a str) {
-        dbg!(query);
-
         if query.is_empty() {
             return (vec![], "");
         }
@@ -517,8 +515,6 @@ impl<'a> QueryParam<'a> {
             })
             .collect();
 
-        dbg!(&params, rest);
-
         (params, rest)
     }
 

+ 12 - 2
src-tauri/src/workspace.rs

@@ -1,6 +1,6 @@
 use crate::request::WorkspaceRequest;
 use serde::{Deserialize, Serialize};
-use sqlx::prelude::Type;
+use sqlx::prelude::{FromRow, Type};
 
 #[derive(Debug, Serialize)]
 pub struct Workspace {
@@ -26,7 +26,7 @@ impl WorkspaceEntry {
 }
 
 /// Database model representation of either a collection or a request.
-#[derive(Debug, Serialize)]
+#[derive(Debug, Serialize, FromRow)]
 pub struct WorkspaceEntryBase {
     /// Entry ID.
     pub id: i64,
@@ -41,6 +41,7 @@ pub struct WorkspaceEntryBase {
     pub name: String,
 
     /// Whether this type is a collection or a request.
+    #[sqlx(try_from = "i64")]
     pub r#type: WorkspaceEntryType,
 
     /// If present, holds the [Authentication][crate::auth::Authentication] ID of the entry.
@@ -50,6 +51,15 @@ pub struct WorkspaceEntryBase {
     pub auth_inherit: bool,
 }
 
+/// Used when listing initial entries, contains additional data for display
+#[derive(Debug, Serialize, FromRow)]
+pub struct WorkspaceEntryDisplay {
+    #[sqlx(flatten)]
+    #[serde(flatten)]
+    base: WorkspaceEntryBase,
+    method: Option<String>,
+}
+
 #[derive(Debug, Serialize)]
 pub struct WorkspaceEnvironment {
     pub id: i64,

+ 58 - 25
src/lib/components/SidebarEntry.svelte

@@ -7,30 +7,70 @@
     traverseEntries,
     selectEntry,
   } from "$lib/state.svelte";
+  import { REQUEST_METHODS } from "$lib/types";
   import Self from "./SidebarEntry.svelte";
   import { setSetting } from "$lib/settings.svelte";
   import * as DropdownMenu from "./ui/dropdown-menu/index";
   import { Button } from "./ui/button";
   import DropdownMenuItem from "./ui/dropdown-menu/dropdown-menu-item.svelte";
+  import { Badge } from "$lib/components/ui/badge/index.js";
   import { ChevronDown, ChevronRight } from "@lucide/svelte";
 
   const isSelected = $derived(_state.entry?.id === id);
   const isParentSelected = $derived(_state.entry?.parent_id === id);
+
+  function onCtrlClick(e) {
+    if (!e.ctrlKey) {
+      return false;
+    }
+
+    if (_state.indexes[id].open) {
+      traverseEntries(id, (entry) => {
+        entry.open = false;
+      });
+    } else {
+      traverseEntries(id, (entry) => {
+        entry.open = true;
+      });
+    }
+
+    return true;
+  }
+
+  function onRequestClick(e) {
+    e.stopPropagation();
+
+    if (onCtrlClick(e)) {
+      return;
+    }
+
+    selectEntry(id);
+    onSelect();
+    setSetting("lastEntry", _state.indexes[id]);
+  }
 </script>
 
-<div class="p-0 m-0" style={`margin-left: ${level}rem`}>
+<div
+  class="pl-1 ml-1 border-l border-secondary"
+  style={`margin-left: ${level}px`}
+>
   <div
-    class="border-l m-0 flex items-center transition-colors"
+    class="w-full flex items-center transition-colors"
     class:bg-primary={isSelected}
     class:bg-secondary={isParentSelected}
   >
     {#if _state.indexes[id]!!.type === "Collection"}
+      <!-- COLLECTION TOGGLE OPEN -->
+
       {#if _state.indexes[id]!!.open}
         <Button
           class="rounded-none cursor-pointer w-fit px-1"
           variant="ghost"
           size="icon-sm"
-          onclick={() => {
+          onclick={(e) => {
+            if (onCtrlClick(e)) {
+              return;
+            }
             _state.indexes[id].open = !_state.indexes[id].open;
           }}
         >
@@ -41,38 +81,31 @@
           class="rounded-none cursor-pointer w-fit px-1"
           variant="ghost"
           size="icon-sm"
-          onclick={() => {
+          onclick={(e) => {
+            if (onCtrlClick(e)) {
+              return;
+            }
             _state.indexes[id].open = !_state.indexes[id].open;
           }}
         >
           <ChevronRight />
         </Button>
       {/if}
+    {:else if _state.indexes[id]!!.type === "Request"}
+      <p
+        onclick={(e) => onRequestClick(e)}
+        class={`cursor-pointer scale-75 text-xs text-center
+          ${REQUEST_METHODS.find((m) => m.method === _state.indexes[id].method)?.textColor}`}
+      >
+        {_state.indexes[id]!!.method}
+      </p>
     {/if}
 
     <p
-      class="w-full cursor-pointer py-1"
+      class="w-full cursor-pointer py-1 ml-1"
       onclick={(e) => {
-        e.stopPropagation();
-
-        if (e.ctrlKey) {
-          if (_state.indexes[id].open) {
-            traverseEntries(id, (entry) => {
-              entry.open = false;
-            });
-          } else {
-            traverseEntries(id, (entry) => {
-              entry.open = true;
-            });
-          }
-          return;
-        }
-
-        selectEntry(id);
-        onSelect();
-        setSetting("lastEntry", _state.indexes[id]);
+        onRequestClick(e);
       }}
-      class:ml-2={_state.indexes[id].type === "Request"}
     >
       {_state.indexes[id].name ||
         _state.indexes[id].type + "(" + _state.indexes[id].id + ")"}
@@ -103,7 +136,7 @@
 
   {#if _state.indexes[id].open && _state.children[id]?.length > 0}
     {#each _state.children[id] as child}
-      <Self id={child} level={level + 0.1} {onSelect} />
+      <Self id={child} level={level + 2} {onSelect} />
     {/each}
   {/if}
 </div>

+ 27 - 6
src/lib/components/WorkspaceEntry.svelte

@@ -14,13 +14,15 @@
     updateEntryName,
     updateHeader,
     updateQueryParamEnabled,
+    updateRequestMethod,
     updateUrl,
     type UrlUpdate,
   } from "$lib/state.svelte";
   import { Button } from "$lib/components/ui/button";
   import { Input } from "$lib/components/ui/input";
   import * as Tabs from "$lib/components/ui/tabs";
-  import type { HttpResponse, UrlError, WorkspaceEntry } from "$lib/types";
+  import type { UrlError, WorkspaceEntry } from "$lib/types";
+  import { REQUEST_METHODS } from "$lib/types";
   import Editable from "./Editable.svelte";
   import { Loader, PlusIcon, Trash } from "@lucide/svelte";
   import BodyEditor from "./BodyEditor.svelte";
@@ -29,8 +31,8 @@
   import Response from "./Response.svelte";
   import Checkbox from "./ui/checkbox/checkbox.svelte";
 
-  let requestPane: Resizable.Pane;
-  let responsePane: Resizable.Pane;
+  let requestPane: Resizable.Pane | undefined = $state();
+  let responsePane: Resizable.Pane | undefined = $state();
 
   let isSending = $derived(_state.pendingRequests.includes(_state.entry!!.id));
 
@@ -73,9 +75,9 @@
     } catch (e) {
       console.error("error sending request", e);
     } finally {
-      if (responsePane.getSize() === 0) {
-        requestPane.resize(50);
-        responsePane.resize(50);
+      if (responsePane!!.getSize() === 0) {
+        requestPane!!.resize(50);
+        responsePane!!.resize(50);
       }
     }
   }
@@ -229,6 +231,25 @@
     <!-- URL BAR -->
 
     <div class="flex flex-wrap w-full gap-3 mx-auto">
+      <Select.Root type="single" value={_state.entry.method}>
+        <Select.Trigger
+          class={REQUEST_METHODS.find((m) => m.method === _state.entry.method)
+            ?.textColor}
+        >
+          {_state.entry.method}
+        </Select.Trigger>
+        <Select.Content>
+          {#each REQUEST_METHODS as method}
+            <Select.Item
+              onclick={() => updateRequestMethod(method.method)}
+              class={method.textColor}
+              value={method.method}
+            >
+              {method.method}
+            </Select.Item>
+          {/each}
+        </Select.Content>
+      </Select.Root>
       <Input
         class="flex-1 font-mono"
         bind:value={_state.entry.url}

+ 11 - 0
src/lib/state.svelte.ts

@@ -650,6 +650,17 @@ export async function updateQueryParamEnabled(param: QueryParam) {
   expandUrl();
 }
 
+export async function updateRequestMethod(method: string) {
+  await invoke("update_request_method", { id: state.entry.id, method });
+
+  state.entry!!.method = method;
+  console.log(
+    "update request method:",
+    $state.snapshot(state.entry.id),
+    $state.snapshot(state.entry.method),
+  );
+}
+
 export async function expandUrl() {
   state.entry!!.expandedUrl = await invoke<string>("expand_url", {
     entryId: state.entry!!.id,

+ 72 - 0
src/lib/types.ts

@@ -182,3 +182,75 @@ export type ResponseResult =
       data: HttpResponse;
     }
   | { type: "Err"; data: string };
+
+/**
+ * As defined in
+ *
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods
+ *
+ */
+export type RequestMethod =
+  | "GET"
+  | "POST"
+  | "PUT"
+  | "DELETE"
+  | "PATCH"
+  | "HEAD"
+  | "OPTIONS"
+  | "TRACE";
+
+export const REQUEST_METHODS: {
+  method: RequestMethod;
+  textColor: string;
+  borderColor: string;
+  bgColor: string;
+}[] = [
+  {
+    method: "GET",
+    textColor: "text-green-500",
+    borderColor: "border-green-500",
+    bgColor: "bg-green-800",
+  },
+  {
+    method: "POST",
+    textColor: "text-yellow-500",
+    borderColor: "border-yellow-500",
+    bgColor: "bg-yellow-800",
+  },
+  {
+    method: "PUT",
+    textColor: "text-blue-500",
+    borderColor: "border-blue-500",
+    bgColor: "bg-blue-800",
+  },
+  {
+    method: "DELETE",
+    textColor: "text-red-500",
+    borderColor: "border-red-500",
+    bgColor: "bg-red-800",
+  },
+  {
+    method: "PATCH",
+    textColor: "text-violet-500",
+    borderColor: "border-violet-500",
+    bgColor: "bg-violet-800",
+  },
+  {
+    method: "HEAD",
+    textColor: "text-cyan-500",
+    borderColor: "border-cyan-500",
+    bgColor: "bg-cyan-800",
+  },
+  {
+    method: "OPTIONS",
+    textColor: "text-purple-500",
+    borderColor: "border-purple-500",
+    bgColor: "bg-purple-800",
+  },
+  {
+    method: "TRACE",
+    textColor: "text-pink-500",
+    borderColor: "border-pink-500",
+    bgColor: "bg-pink-800",
+  },
+];