biblius 1 hete
szülő
commit
be03fbd4ed

+ 20 - 24
src/lib/components/Auth.svelte

@@ -15,14 +15,8 @@
 </script>
 
 <div class="space-y-6">
-  <!-- Inherit checkbox -->
-  <!-- <div class="flex items-center gap-3"> -->
-  <!-- <Checkbox checked={inherit} onCheckedChange={(v) => toggleInherit(!!v)} /> -->
-  <!-- <span class="text-sm">Inherit auth from parent collection</span> -->
-  <!-- </div> -->
-
-  <div class="border">
-    <h1 class="p-2">Workspace authentication</h1>
+  <div>
+    <h1 class="p-2 text-lg">Authentication</h1>
     <p class="p-2 text-sm">
       Create workspace-wide authentication schemes. Use the schemes created here
       in request/collection tabs.
@@ -45,28 +39,30 @@
   </div>
 
   {#each _state.auth as auth}
-    <div class="border">
-      <div class="flex items-center">
-        <Editable
-          bind:value={auth.name}
-          onSave={(value) => {
-            renameAuth(auth.id, value);
-          }}
-        >
-          {#snippet display({ value, startEdit })}
-            <h2 ondblclick={startEdit}>
-              {auth.name}
-            </h2>
-          {/snippet}
-        </Editable>
+    <div class="p-2 border rounded-xl">
+      <div class="mx-2 flex items-center border-b space-y-2">
+        <div class="w-full">
+          <Editable
+            bind:value={auth.name}
+            onSave={(value) => {
+              renameAuth(auth.id, value);
+            }}
+          >
+            {#snippet display({ value, startEdit })}
+              <h2 ondblclick={startEdit}>
+                {auth.name}
+              </h2>
+            {/snippet}
+          </Editable>
+        </div>
         <Trash
-          class="ml-2 h-4 w-4 cursor-pointer text-muted-foreground hover:text-destructive"
+          class="mb-2 h-4 w-4 cursor-pointer text-muted-foreground hover:text-destructive"
           size={16}
           onclick={() => deleteAuth(auth.id)}
         />
       </div>
       <div class="p-2">
-        <AuthParams {auth} readonly={false} />
+        <AuthParams bind:auth readonly={false} />
       </div>
     </div>
   {/each}

+ 25 - 13
src/lib/components/AuthParams.svelte

@@ -1,19 +1,13 @@
 <script lang="ts">
   import * as Select from "$lib/components/ui/select";
-  import {
-    state as _state,
-    deleteAuth,
-    renameAuth,
-    setEntryAuth,
-    updateAuthParams,
-  } from "$lib/state.svelte";
+  import { state as _state, updateAuthParams } from "$lib/state.svelte";
   import { Input } from "./ui/input";
   import type { Authentication } from "$lib/types";
 
-  let { auth, readonly }: { auth: Authentication; readonly: boolean } =
-    $props();
-
-  console.log(auth);
+  let {
+    auth = $bindable(),
+    readonly,
+  }: { auth: Authentication; readonly: boolean } = $props();
 </script>
 
 {#if auth.params.type === "Token"}
@@ -46,7 +40,7 @@
         <Select.Item
           onclick={() => {
             auth.params.value.placement = "Query";
-            setEntryAuth(auth.id, null);
+            updateAuthParams(auth.id);
           }}
           value={"Query"}
         >
@@ -55,7 +49,7 @@
         <Select.Item
           onclick={() => {
             auth.params.value.placement = "Header";
-            setEntryAuth(auth.id, null);
+            updateAuthParams(auth.id);
           }}
           value={"Header"}
         >
@@ -64,4 +58,22 @@
       </Select.Content>
     </Select.Root>
   </div>
+{:else if auth.params.type === "Basic"}
+  <div class="grid grid-cols-[auto_1fr] gap-2 items-center">
+    <p>Username</p>
+    <Input
+      class="w-80"
+      bind:value={auth.params.value.user}
+      oninput={() => updateAuthParams(auth.id)}
+      {readonly}
+    ></Input>
+    <p>Password</p>
+    <Input
+      type="password"
+      class="w-80"
+      bind:value={auth.params.value.password}
+      oninput={() => updateAuthParams(auth.id)}
+      {readonly}
+    ></Input>
+  </div>
 {/if}

+ 58 - 55
src/lib/components/Environment.svelte

@@ -2,6 +2,7 @@
   import {
     state as _state,
     createEnvironment,
+    deleteEnvVariable,
     insertEnvVariable,
     selectEnvironment,
     updateEnvironment,
@@ -9,13 +10,16 @@
   } from "$lib/state.svelte";
   import * as Select from "$lib/components/ui/select";
   import { Input } from "$lib/components/ui/input";
-  import { Label } from "$lib/components/ui/label";
   import Button from "./ui/button/button.svelte";
   import Editable from "./Editable.svelte";
   import type { EnvVariable } from "$lib/types";
+  import { Plus, Trash } from "@lucide/svelte";
 
   let placeholderKey = $state("");
   let placeholderVal = $state("");
+  let error = $state(false);
+  let errorMessage = $state("");
+  let showTooltip = $state(false);
 
   async function handlePlaceholder() {
     if (placeholderKey && placeholderVal) {
@@ -29,8 +33,14 @@
         );
         placeholderKey = "";
         placeholderVal = "";
-      } catch (e) {
+        error = false;
+        showTooltip = false;
+      } catch (e: any) {
         console.error("cannot insert env var", e);
+        errorMessage = e;
+
+        error = true;
+        showTooltip = true;
       }
     }
   }
@@ -41,38 +51,6 @@
 </script>
 
 <main class="w-full space-y-6 p-4">
-  <!-- Environment selector -->
-  <div class="flex items-center gap-4">
-    <Label>Environment</Label>
-
-    <Select.Root type="single" value={_state.environment?.id.toString() || "-"}>
-      <Select.Trigger class="w-64">
-        {_state.environment?.name || "-"}
-      </Select.Trigger>
-
-      <Select.Content>
-        <Select.Item value={"-"} onclick={() => selectEnvironment(null)}>
-          -
-        </Select.Item>
-        {#each _state.environments as env}
-          <Select.Item
-            value={env.id.toString()}
-            onclick={() => selectEnvironment(env.id)}
-          >
-            {env.name}
-          </Select.Item>
-        {/each}
-      </Select.Content>
-    </Select.Root>
-
-    <Button
-      class="w-8 h-8"
-      onclick={() => {
-        createEnvironment(_state.workspace!!.id, "New environment");
-      }}>+</Button
-    >
-  </div>
-
   {#if _state.environment}
     <!-- Environment name -->
     <Editable
@@ -89,43 +67,68 @@
     </Editable>
 
     <!-- Variables -->
-    <div class="space-y-3">
+    <div class="grid grid-cols-[1fr_1fr_auto] items-center gap-3">
       {#each _state.environment.variables as v}
-        <div class="grid grid-cols-2 gap-3">
-          <Input
-            class="font-mono"
-            bind:value={v.name}
-            oninput={() => handleUpdate(v)}
-          />
-          <Input
-            class="font-mono"
-            bind:value={v.value}
-            oninput={() => handleUpdate(v)}
-          />
-        </div>
+        <Input
+          class="font-mono"
+          bind:value={v.name}
+          oninput={() => handleUpdate(v)}
+        />
+        <Input
+          class="font-mono"
+          bind:value={v.value}
+          oninput={() => handleUpdate(v)}
+        />
+        <Trash
+          class="h-4 w-4 cursor-pointer text-muted-foreground hover:text-destructive"
+          onclick={() => deleteEnvVariable(v.id)}
+        />
       {/each}
     </div>
 
-    <p class="w-full border-b">Add</p>
-    <div class="grid grid-cols-2 gap-3">
+    <p class="opacity-75 border-b pointer-events-none">Add variable</p>
+
+    <div
+      class={`p-2 relative grid grid-cols-2 gap-3 ${
+        error ? "border rounded-2xl border-red-500 ring-red-400" : ""
+      }`}
+    >
+      <!-- ADD KEY -->
+
       <Input
         class="font-mono"
         bind:value={placeholderKey}
         onkeypress={(e) => {
-          if (e.key === "Enter") {
-            handlePlaceholder();
-          }
+          error = false;
+          showTooltip = false;
+          if (e.key === "Enter") handlePlaceholder();
         }}
       />
+
+      <!-- ADD VALUE -->
+
       <Input
         class="font-mono"
         bind:value={placeholderVal}
         onkeypress={(e) => {
-          if (e.key === "Enter") {
-            handlePlaceholder();
-          }
+          error = false;
+          showTooltip = false;
+          if (e.key === "Enter") handlePlaceholder();
         }}
       />
+
+      <Plus
+        class="border p-1 rounded-2xl mx-auto col-span-2 cursor-pointer"
+        onclick={() => handlePlaceholder()}
+      />
+
+      <!-- ERROR TOOLTIP -->
+
+      {#if error && showTooltip}
+        <div class="w-max bg-red-600 text-white text-sm px-2 rounded shadow-md">
+          {errorMessage}
+        </div>
+      {/if}
     </div>
   {/if}
 </main>

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

@@ -19,6 +19,8 @@
   import SidebarEntry from "./SidebarEntry.svelte";
   import { getSetting } from "$lib/settings.svelte";
 
+  let { onSelect } = $props();
+
   let workspaces: Workspace[] = $state([]);
 
   let addWorkspaceInput = $state("");
@@ -147,7 +149,7 @@
         <Sidebar.GroupContent>
           <Sidebar.Menu>
             {#each _state.roots as root}
-              <SidebarEntry id={root} level={0} />
+              <SidebarEntry id={root} level={0} {onSelect} />
             {/each}
           </Sidebar.Menu>
         </Sidebar.GroupContent>

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

@@ -1,5 +1,5 @@
 <script lang="ts">
-  const { id, level } = $props();
+  const { id, level, onSelect } = $props();
   import {
     state as _state,
     createCollection,
@@ -13,8 +13,6 @@
   import { Button } from "./ui/button";
   import DropdownMenuItem from "./ui/dropdown-menu/dropdown-menu-item.svelte";
 
-  let open = $derived(!!_state.indexes[id].open);
-
   const isSelected = $derived(_state.entry?.id === id);
 </script>
 
@@ -23,14 +21,15 @@
     onclick={(e) => {
       e.stopPropagation();
       selectEntry(id);
-      open = !open;
+      onSelect();
+      _state.indexes[id].open = !_state.indexes[id].open;
       setSetting("lastEntry", _state.indexes[id]);
     }}
   >
     <Sidebar.MenuButton>
       {#snippet child()}
         <div
-          class="border flex items-center transition-colors"
+          class="flex items-center transition-colors"
           class:border={isSelected}
         >
           <p class="p-2 w-full">
@@ -63,9 +62,9 @@
       {/snippet}
     </Sidebar.MenuButton>
 
-    {#if open && _state.children[id]?.length > 0}
+    {#if _state.indexes[id].open && _state.children[id]?.length > 0}
       {#each _state.children[id] as child}
-        <Self id={child} level={level + 0.1} />
+        <Self id={child} level={level + 0.25} {onSelect} />
       {/each}
     {/if}
   </Sidebar.MenuItem>

+ 15 - 16
src/lib/components/WorkspaceEntry.svelte

@@ -190,7 +190,7 @@
       type="single"
       value={entry.auth_inherit ? "inherit" : (entry.auth?.toString() ?? "-")}
     >
-      <Select.Trigger class="w-64">
+      <Select.Trigger>
         {#if entry.auth != null && !entry.auth_inherit}
           {_state.auth.find((a) => a.id === entry.auth)?.name}
         {:else if entry.auth_inherit}
@@ -344,7 +344,9 @@
             <!-- ================= HEADERS ================= -->
 
             <Tabs.Content value="headers">
-              <div class="grid grid-cols-3 gap-2 text-sm">
+              <div
+                class="w-10/12 mx-auto grid grid-cols-[auto_1fr] gap-2 items-center"
+              >
                 {#each _state.entry.headers as header}
                   <div class="contents">
                     <Input
@@ -354,25 +356,22 @@
                         updateHeader(header.id, header.name, header.value)}
                     />
 
-                    <div class="flex gap-2 col-span-2 items-center">
-                      <Input
-                        bind:value={header.value}
-                        placeholder="Value"
-                        class="flex-1"
-                        oninput={() =>
-                          updateHeader(header.id, header.name, header.value)}
-                      />
+                    <Input
+                      bind:value={header.value}
+                      placeholder="Value"
+                      oninput={() =>
+                        updateHeader(header.id, header.name, header.value)}
+                    />
 
-                      <Trash
-                        class="h-4 w-4 cursor-pointer text-muted-foreground hover:text-destructive"
-                        onclick={() => deleteHeader(header.id)}
-                      />
-                    </div>
+                    <Trash
+                      class="h-4 w-4 cursor-pointer text-muted-foreground hover:text-destructive"
+                      onclick={() => deleteHeader(header.id)}
+                    />
                   </div>
                 {/each}
 
                 <PlusIcon
-                  class="col-span-3 mt-2 cursor-pointer text-muted-foreground hover:text-primary"
+                  class="border p-1 rounded-2xl mx-auto col-span-3 cursor-pointer"
                   onclick={() => insertHeader()}
                 />
               </div>

+ 4 - 2
src/lib/state.svelte.ts

@@ -89,6 +89,7 @@ function reset() {
   state.entry = null;
   state.environment = null;
   state.environments = [];
+  state.auth = [];
 }
 
 export async function selectEnvironment(
@@ -198,8 +199,6 @@ export async function loadWorkspace(ws: Workspace) {
         headers: entry.data.headers,
         body: entry.data.body,
         path: entry.data.path_params,
-        auth: entry.data.auth,
-        auth_inherit: entry.data.auth_inherit,
       });
     } else {
       index(entry.data);
@@ -518,6 +517,8 @@ export async function updateAuthParams(id: number) {
     return;
   }
 
+  console.log($state.snapshot(auth.params));
+
   await invoke<Authentication>("update_auth", {
     id,
     params: auth.params,
@@ -542,6 +543,7 @@ export async function setEntryAuth(id: number | null, inherit: boolean | null) {
   });
 
   state.entry!!.auth = id;
+
   if (inherit != null) {
     state.entry!!.auth_inherit = inherit;
   }

+ 1 - 1
src/lib/types.ts

@@ -120,7 +120,7 @@ export type AuthParams =
         value: string;
       };
     }
-  | { type: "Basic"; value: { username: string; password: string } }
+  | { type: "Basic"; value: { user: string; password: string } }
   | {
       type: "OAuth";
       value: {

+ 31 - 4
src/routes/+page.svelte

@@ -5,17 +5,23 @@
   import { Button } from "$lib/components/ui/button";
   import { SunIcon, MoonIcon, Lock } from "@lucide/svelte";
   import WorkspaceEntry from "$lib/components/WorkspaceEntry.svelte";
-  import { state as _state, selectEnvironment } from "$lib/state.svelte";
+  import {
+    state as _state,
+    createEnvironment,
+    selectEnvironment,
+  } from "$lib/state.svelte";
   import { SlidersHorizontal } from "@lucide/svelte";
   import Environment from "$lib/components/Environment.svelte";
   import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index";
   import Auth from "$lib/components/Auth.svelte";
+  import { Input } from "$lib/components/ui/input";
 
   let displayModal: "env" | "auth" | null = $state(null);
+  let addEnvInput = $state("");
 </script>
 
 <Sidebar.Provider>
-  <AppSidebar />
+  <AppSidebar onSelect={() => (displayModal = null)} />
 
   <main class="w-full p-4 space-y-4">
     <header class="flex items-center w-full border-b pb-2">
@@ -38,6 +44,27 @@
         </DropdownMenu.Trigger>
 
         <DropdownMenu.Content align="center">
+          <DropdownMenu.Label>New environment</DropdownMenu.Label>
+          <form
+            class="flex w-full max-w-sm items-center gap-2"
+            onsubmit={(e) => {
+              e.preventDefault();
+              if (addEnvInput.length > 0 && _state.workspace) {
+                createEnvironment(_state.workspace.id, addEnvInput);
+                addEnvInput = "";
+              }
+            }}
+          >
+            <Input
+              type="text"
+              placeholder="My environment"
+              bind:value={addEnvInput}
+            />
+            <Button type="submit" variant="outline">+</Button>
+          </form>
+
+          <DropdownMenu.Separator />
+
           <DropdownMenu.Item onSelect={() => selectEnvironment(null)}
             >- {_state.environment === null ? " ✓" : ""}</DropdownMenu.Item
           >
@@ -80,7 +107,7 @@
           <Button
             onclick={() =>
               (displayModal = displayModal === "env" ? null : "env")}
-            variant="ghost"
+            variant={displayModal === "env" ? "default" : "ghost"}
             size="icon-sm"
           >
             <SlidersHorizontal />
@@ -92,7 +119,7 @@
           <Button
             onclick={() =>
               (displayModal = displayModal === "auth" ? null : "auth")}
-            variant="ghost"
+            variant={displayModal === "auth" ? "default" : "ghost"}
             size="icon-sm"
           >
             <Lock />