Przeglądaj źródła

fix codemirror and remove highlight

biblius 1 tydzień temu
rodzic
commit
e27fb00066

+ 0 - 2
package.json

@@ -27,9 +27,7 @@
     "@tauri-apps/plugin-store": "^2.4.1",
     "clsx": "^2.1.1",
     "codemirror": "^6.0.2",
-    "highlight.js": "^11.11.1",
     "mode-watcher": "^1.1.0",
-    "svelte-highlight": "^7.9.0",
     "tailwind-merge": "^3.4.0",
     "tailwindcss": "^4.1.17"
   },

+ 2 - 0
src-tauri/src/request.rs

@@ -246,6 +246,7 @@ pub struct HttpResponse {
     pub status: usize,
     pub headers: Vec<(String, String)>,
     pub body: Option<ResponseBody>,
+    pub duration_ms: Option<usize>,
 }
 
 impl HttpResponse {
@@ -267,6 +268,7 @@ impl HttpResponse {
                 })
                 .collect(),
             body,
+            duration_ms: None,
         }
     }
 }

+ 31 - 12
src-tauri/src/state.rs

@@ -5,7 +5,7 @@ use crate::{
 use futures::FutureExt;
 use serde::Serialize;
 use sqlx::SqlitePool;
-use std::{collections::HashMap, time::Instant};
+use std::{collections::HashMap, error::Error, time::Instant};
 use tauri::ipc::Channel;
 use tauri_plugin_log::log;
 use tokio::select;
@@ -56,16 +56,22 @@ impl AppState {
                             continue;
                         };
                     },
-                    Some((req_id, res)) = outbound.next() => {
-                        if let Some(start) = timers.remove(&req_id) {
-                            log::debug!("request complete {req_id}; took {}ms", Instant::now().duration_since(start).as_millis());
-                        }
+                    Some((req_id, mut res)) = outbound.next() => {
+                        let Some(start) = timers.remove(&req_id) else {
+                            log::warn!("missing timer for {req_id}");
+                            continue;
+                        };
+
+                        let duration = Instant::now().duration_since(start).as_millis();
+
+                        log::debug!("request complete {req_id}; took {}ms", duration);
+
                         let Some(channel) = return_channels.remove(&req_id) else {
                             log::warn!("missing return channel for {req_id}");
                             continue;
                         };
 
-                        channel.send(res.into()).unwrap();
+                        channel.send(ResponseResult::from_response(res, duration)).unwrap();
                     },
                 }
             }
@@ -113,15 +119,28 @@ pub struct OutboundRequest {
 #[derive(Debug, Serialize)]
 #[serde(tag = "type", content = "data")]
 pub enum ResponseResult {
-    Ok(HttpResponse),
-    Err(String),
+    Ok {
+        response: HttpResponse,
+        duration: u128,
+    },
+    Err {
+        message: String,
+        duration: u128,
+    },
 }
 
-impl From<AppResult<HttpResponse>> for ResponseResult {
-    fn from(value: AppResult<HttpResponse>) -> Self {
+impl ResponseResult {
+    fn from_response(value: AppResult<HttpResponse>, duration: u128) -> Self {
         match value {
-            Ok(res) => Self::Ok(res),
-            Err(e) => Self::Err(e.to_string()),
+            Ok(response) => Self::Ok { response, duration },
+            Err(e) => {
+                log::error!("request error: {e}");
+                log::error!(" - source: {:?}", e.source());
+                Self::Err {
+                    message: e.to_string(),
+                    duration,
+                }
+            }
         }
     }
 }

+ 5 - 5
src/lib/codemirror.svelte.ts

@@ -24,24 +24,24 @@ const lineWrapConfig = new Compartment();
 
 const editorPadding = EditorView.theme({
   ".cm-content": {
-    marginBottom: "4rem",
+    marginBottom: "2.5rem",
   },
   ".cm-scroller": {
-    marginBottom: "2rem",
+    marginBottom: "1.5rem",
   },
 
   "@media (min-height: 900px)": {
     ".cm-content": {
-      marginBottom: "3rem",
+      marginBottom: "1.5rem",
     },
     ".cm-scroller": {
-      marginBottom: "1rem",
+      marginBottom: "0.5rem",
     },
   },
 
   "@media (min-height: 1200px)": {
     ".cm-content": {
-      marginBottom: "2rem",
+      marginBottom: "1rem",
     },
     ".cm-scroller": {
       marginBottom: "0.5rem",

+ 0 - 23
src/lib/components/Highlight.svelte

@@ -1,23 +0,0 @@
-<script lang="ts">
-  import hljs from "$lib/highlight.svelte";
-  import "highlight.js/styles/sunburst.css";
-
-  let { code, lang, wrap }: { code: string; lang: string; wrap: boolean } =
-    $props();
-
-  let wrapped = $derived(wrap ? "whitespace-pre-wrap" : "whitespace-pre");
-
-  let highlighted = $derived(hljs.highlight(code, { language: lang }).code);
-
-  $effect(() => {
-    hljs.highlightAll();
-    console.log(highlighted);
-  });
-</script>
-
-{#if highlighted}
-  <pre
-    class={"wrap-anywhere text-sm overflow-auto max-w-fit min-w-0 " + wrapped}>
-      <code class={"lang-" + lang}>{highlighted}</code>
-  </pre>
-{/if}

+ 65 - 74
src/lib/components/Response.svelte

@@ -10,7 +10,7 @@
   import type { EditorView } from "codemirror";
   import { onMount } from "svelte";
 
-  let response = $derived(_state.responses[_state.entry!!.id]);
+  let result = $derived(_state.responses[_state.entry!!.id]);
   let wrap = $state(ls.WRAP_RESPONSE.get());
 
   let view: EditorView;
@@ -25,15 +25,26 @@
   });
 
   $effect(() => {
-    if (
-      response.body != null &&
-      response.body.content !== view.state.doc.toString()
-    ) {
-      setContent(view, response.body.content, response.body.type);
+    if (result.type === "Err") {
+      return;
+    }
+
+    if (result.type == "Ok" && result.data.response.body != null) {
+      setContent(
+        view,
+        result.data.response.body.content,
+        result.data.response.body.type,
+      );
     }
   });
 
   let borderColor = $derived.by(() => {
+    if (result.type === "Err") {
+      return "border-red-900";
+    }
+
+    const response = result.data.response;
+
     if (response.status >= 200 && response.status < 300)
       return "border-green-900";
     if (response.status >= 400 && response.status < 500)
@@ -42,6 +53,12 @@
   });
 
   let dotColor = $derived.by(() => {
+    if (result.type === "Err") {
+      return "red";
+    }
+
+    const response = result.data.response;
+
     if (response.status >= 200 && response.status < 300) return "green";
     if (response.status >= 400 && response.status < 500) return "orange";
     if (response.status >= 500) return "red";
@@ -49,21 +66,36 @@
 </script>
 
 <Tabs.Root value="body" class="min-h-0">
-  <header class="flex items-center w-full py-2 gap-4 border-b">
-    <Badge variant="outline" class={`h-4 rounded-sm ${borderColor}`}>
+  <header class="flex items-center w-full py-2 gap-2 border-b">
+    <!-- STATUS BADGE -->
+
+    <Badge variant="outline" class={`rounded-sm ${borderColor}`}>
       <Dot strokeWidth={5} color={dotColor} />
-      {response.status}
+      {#if result.type === "Ok"}
+        {result.data.response.status}
+      {:else}
+        ERROR
+      {/if}
+    </Badge>
+
+    <Badge variant="outline" class={`rounded-sm`}>
+      {result.data.duration}ms
     </Badge>
 
-    <Tabs.List class="h-6">
-      <Tabs.Trigger class="text-xs" value="body">Body</Tabs.Trigger>
-      <Tabs.Trigger class="text-xs" value="headers">Headers</Tabs.Trigger>
-    </Tabs.List>
+    {#if result.type === "Ok"}
+      <Tabs.List>
+        <Tabs.Trigger class="text-xs" value="body">Body</Tabs.Trigger>
+        <Tabs.Trigger class="text-xs" value="headers">Headers</Tabs.Trigger>
+      </Tabs.List>
+
+      <!-- RESPONSE UTILS -->
 
-    <Tabs.Content value="body" class="flex items-center">
-      <div class="flex items-center gap-1">
+      <Tabs.Content
+        value="body"
+        class="flex justify-end w-full items-center gap-1"
+      >
         <Button
-          class="h-6 p-1"
+          class="p-1"
           variant={wrap ? "default" : "outline"}
           onclick={handleToggleWrap}
           size="icon-sm"
@@ -71,73 +103,32 @@
           <TextWrap />
         </Button>
         <Button
-          class="h-6 p-1"
+          class="p-1"
           onclick={() => copyContent(view)}
           variant="outline"
           size="icon-sm"
         >
           <Clipboard />
         </Button>
-      </div>
-    </Tabs.Content>
+      </Tabs.Content>
+    {/if}
   </header>
 
-  <Tabs.Content value="body" class="flex-1 min-h-0 overflow-auto">
-    {#if response.body != null}
-      <!-- EDITOR -->
-
-      <div id="response-view"></div>
+  <Tabs.Content value="body" class="overflow-scroll h-[90%]">
+    {#if result.type === "Err"}
+      {result.data.message}
     {/if}
+    <div id="response-view" class:hidden={result.type === "Err"}></div>
   </Tabs.Content>
 
-  <Tabs.Content value="headers" class="flex-1 overflow-auto">
-    <div class="grid grid-cols-2" id="header-tab">
-      {#each response.headers as [name, value]}
-        <p>{name}</p>
-        <p class="wrap-anywhere">{value}</p>
-      {/each}
-    </div>
-  </Tabs.Content>
+  {#if result.type === "Ok"}
+    <Tabs.Content value="headers" class="flex-1">
+      <div class="grid grid-cols-2">
+        {#each result.data.response.headers as [name, value]}
+          <p>{name}</p>
+          <p class="wrap-anywhere">{value}</p>
+        {/each}
+      </div>
+    </Tabs.Content>
+  {/if}
 </Tabs.Root>
-
-<style>
-  #header-tab {
-    padding-bottom: 8rem;
-  }
-
-  @media (min-height: 400px) {
-    #header-tab {
-      padding-bottom: 7rem;
-    }
-  }
-
-  @media (min-height: 600px) {
-    #header-tab {
-      padding-bottom: 6rem;
-    }
-  }
-
-  @media (min-height: 800px) {
-    #header-tab {
-      padding-bottom: 5rem;
-    }
-  }
-
-  @media (min-height: 900px) {
-    #header-tab {
-      padding-bottom: 4rem;
-    }
-  }
-
-  @media (min-height: 1000px) {
-    #header-tab {
-      padding-bottom: 3rem;
-    }
-  }
-
-  @media (min-height: 1200px) {
-    #header-tab {
-      padding-bottom: 2rem;
-    }
-  }
-</style>

+ 1 - 1
src/lib/components/Sidebar.svelte

@@ -57,7 +57,7 @@
     <Sidebar.Group>
       <Sidebar.GroupContent>
         <Sidebar.Menu>
-          {#each _state.roots as root}
+          {#each _state.roots as root (root)}
             <SidebarEntry id={root} level={0} {onSelect} />
           {/each}
         </Sidebar.Menu>

+ 6 - 7
src/lib/components/SidebarEntry.svelte

@@ -13,7 +13,6 @@
   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);
@@ -92,24 +91,24 @@
         </Button>
       {/if}
     {:else if _state.indexes[id]!!.type === "Request"}
-      <p
+      <button
         onclick={(e) => onEntrySelect(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>
+      </button>
     {/if}
 
-    <p
-      class="w-full cursor-pointer py-1 ml-1"
+    <button
+      class="text-start w-full cursor-pointer py-1 ml-1"
       onclick={(e) => {
         onEntrySelect(e);
       }}
     >
       {_state.indexes[id].name ||
         _state.indexes[id].type + "(" + _state.indexes[id].id + ")"}
-    </p>
+    </button>
 
     <!-- ACTION MENU -->
 
@@ -135,7 +134,7 @@
   </div>
 
   {#if _state.indexes[id].open && _state.children[id]?.length > 0}
-    {#each _state.children[id] as child}
+    {#each _state.children[id] as child (child)}
       <Self id={child} level={level + 2} {onSelect} />
     {/each}
   {/if}

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

@@ -30,7 +30,7 @@
     RESPONSE_PANE_ID,
   } from "$lib/types";
   import Editable from "./Editable.svelte";
-  import { Loader, PlusIcon, Trash } from "@lucide/svelte";
+  import { Loader, Trash } from "@lucide/svelte";
   import BodyEditor from "./BodyEditor.svelte";
   import * as Resizable from "$lib/components/ui/resizable/index";
   import AuthParams from "./AuthParams.svelte";
@@ -71,12 +71,6 @@
   // Used for inputs in the URL bar
   let updateUrlTimeout: number | undefined = $state();
 
-  // Used for inputs in the query params
-  let addQueryTimeout: number | undefined = $state();
-
-  let addQueryParamKeyInput: string | undefined = $state();
-  let addQueryParamValInput: string | undefined = $state();
-
   async function handleRequest() {
     if (isRequestSending()) {
       try {
@@ -397,9 +391,12 @@
 
               {#if _state.entry?.path?.length > 0}
                 <WorkspaceEntrySection title="Path">
-                  <div class="grid grid-cols-2 gap-2 text-sm">
+                  <div
+                    class="grid grid-cols-[2%_1fr_1fr_2%] items-center justify-center gap-2 text-sm"
+                  >
                     {#each _state.entry.path as param}
                       <Input
+                        class="col-start-2"
                         bind:value={param.name}
                         placeholder="key"
                         oninput={() =>
@@ -410,6 +407,7 @@
                           })}
                       />
                       <Input
+                        class="col-start-3"
                         bind:value={param.value}
                         placeholder="value"
                         oninput={() =>

+ 0 - 17
src/lib/highlight.svelte.ts

@@ -1,17 +0,0 @@
-import hljs from "highlight.js/lib/core";
-import javascript from "highlight.js/lib/languages/javascript";
-import json from "highlight.js/lib/languages/json";
-import xml from "highlight.js/lib/languages/xml";
-import plaintext from "highlight.js/lib/languages/plaintext";
-
-export const JS = "javascript";
-export const JS_ON = "json";
-export const HTML = "html";
-export const PLAIN = "plaintext";
-
-hljs.registerLanguage(JS, javascript);
-hljs.registerLanguage(JS_ON, json);
-hljs.registerLanguage(HTML, xml);
-hljs.registerLanguage(PLAIN, plaintext);
-
-export default hljs;

+ 6 - 8
src/lib/state.svelte.ts

@@ -66,7 +66,7 @@ export type WorkspaceState = {
   /**
    * Maps request IDs to their latest response.
    */
-  responses: Record<number, HttpResponse>;
+  responses: Record<number, ResponseResult>;
 
   /**
    * Holds entry selection history.
@@ -95,6 +95,7 @@ export const state: WorkspaceState = $state({
 });
 
 export function isRequestSending() {
+  console.log(state.pendingRequests);
   return state.pendingRequests.includes(state.entry!!.id);
 }
 
@@ -434,18 +435,15 @@ export async function sendRequest(): Promise<void> {
 
     switch (response.type) {
       case "Ok": {
-        state.responses[state.entry!!.id] = response.data;
+        state.responses[state.entry!!.id] = response;
         console.log(state.responses);
         break;
       }
       case "Err": {
+        state.responses[state.entry!!.id] = response;
         console.error("received response error", response.data);
         break;
       }
-      default: {
-        console.error("unrecognized response type", response.type);
-        break;
-      }
     }
 
     console.timeEnd("request-" + reqId);
@@ -453,13 +451,13 @@ export async function sendRequest(): Promise<void> {
     state.pendingRequests = state.pendingRequests.filter((id) => id !== reqId);
   };
 
+  state.pendingRequests.push(reqId);
+
   await invoke<HttpResponse>("send_request", {
     reqId,
     envId: state.environment?.id,
     onComplete,
   });
-
-  state.pendingRequests.push(reqId);
 }
 
 export async function cancelRequest(): Promise<void> {

+ 11 - 2
src/lib/types.ts

@@ -183,9 +183,18 @@ export type HttpResponseBody =
 export type ResponseResult =
   | {
       type: "Ok";
-      data: HttpResponse;
+      data: {
+        response: HttpResponse;
+        duration: number;
+      };
     }
-  | { type: "Err"; data: string };
+  | {
+      type: "Err";
+      data: {
+        message: string;
+        duration: number;
+      };
+    };
 
 /**
  * As defined in