Selaa lähdekoodia

improve auth params display

biblius 1 viikko sitten
vanhempi
commit
57f3fc3e1e

+ 23 - 2
src/lib/components/Auth.svelte

@@ -45,8 +45,29 @@
   </div>
 
   {#each _state.auth as auth}
-    <div class="border p-2">
-      <AuthParams {auth} readonly={false} />
+    <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>
+        <Trash
+          class="ml-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} />
+      </div>
     </div>
   {/each}
 </div>

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

@@ -1,49 +1,67 @@
 <script lang="ts">
+  import * as Select from "$lib/components/ui/select";
   import {
     state as _state,
-    createAuth,
     deleteAuth,
     renameAuth,
+    setEntryAuth,
     updateAuthParams,
   } from "$lib/state.svelte";
-
-  let { auth, readonly }: { auth: Authentication; readonly: bool } = $props();
-
-  import { Button } from "$lib/components/ui/button";
-  import { Trash, Plus } from "@lucide/svelte";
   import { Input } from "./ui/input";
-  import Editable from "./Editable.svelte";
   import type { Authentication } from "$lib/types";
+
+  let { auth, readonly }: { auth: Authentication; readonly: boolean } =
+    $props();
+
+  console.log(auth);
 </script>
 
-<div class="flex">
-  <Editable
-    bind:value={auth.name}
-    onSave={(value) => {
-      renameAuth(auth.id, value);
-    }}
-  >
-    {#snippet display({ value, startEdit })}
-      <h2 ondblclick={startEdit}>
-        {auth.name}
-      </h2>
-    {/snippet}
-  </Editable>
-  {#if !readonly}
-    <Trash onclick={() => deleteAuth(auth.id)} />
-  {/if}
-</div>
 {#if auth.params.type === "Token"}
-  <div>
+  <div class="grid grid-cols-[auto_1fr] gap-2 items-center">
+    <p>Name</p>
     <Input
+      class="w-80"
       bind:value={auth.params.value.name}
       oninput={() => updateAuthParams(auth.id)}
       {readonly}
     ></Input>
+    <p>Value</p>
     <Input
+      class="w-80"
       bind:value={auth.params.value.value}
       oninput={() => updateAuthParams(auth.id)}
       {readonly}
     ></Input>
+    <p class="pr-2">Placement</p>
+    <Select.Root
+      type="single"
+      disabled={readonly}
+      value={auth.params.value.placement}
+    >
+      <Select.Trigger disabled={readonly} class="w-80">
+        {auth.params.value.placement}
+      </Select.Trigger>
+
+      <Select.Content>
+        <Select.Item
+          onclick={() => {
+            auth.params.value.placement = "Query";
+            setEntryAuth(auth.id, null);
+          }}
+          value={"Query"}
+        >
+          Query
+        </Select.Item>
+        <Select.Item
+          onclick={() => {
+            auth.params.value.placement = "Header";
+            setEntryAuth(auth.id, null);
+          }}
+          value={"Header"}
+        >
+          Header
+        </Select.Item>
+      </Select.Content>
+    </Select.Root>
   </div>
 {/if}

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

@@ -27,7 +27,7 @@
   });
 
   $effect(() => {
-    if (input !== null && input !== view.state.doc.toString()) {
+    if (input != null && input !== view.state.doc.toString()) {
       setContent(view, input);
     }
   });

+ 26 - 3
src/lib/components/Editable.svelte

@@ -1,5 +1,4 @@
 <script lang="ts">
-  import { Input } from "$lib/components/ui/input";
   import { Button } from "$lib/components/ui/button";
   import { Check, X } from "@lucide/svelte";
   import { state as _state } from "$lib/state.svelte";
@@ -54,18 +53,42 @@
     value = lastValue;
     editing = false;
   }
+
+  function clickOutsideCancel(node: HTMLElement) {
+    const onPointerDown = (event: PointerEvent) => {
+      if (!node.contains(event.target as Node)) {
+        if (
+          node === document.activeElement ||
+          node.contains(document.activeElement)
+        ) {
+          if (editing) {
+            cancel();
+          }
+        }
+      }
+    };
+
+    document.addEventListener("pointerdown", onPointerDown, true);
+
+    return {
+      destroy() {
+        document.removeEventListener("pointerdown", onPointerDown, true);
+      },
+    };
+  }
 </script>
 
-<div class="w-fit flex items-center">
+<div class="flex items-center">
   {#if editing}
     <input
+      use:clickOutsideCancel
       bind:this={inputRef}
       bind:value
+      size={Math.max(value.length, 1)}
       onkeydown={(e) => {
         if (e.key === "Enter") save();
         if (e.key === "Escape") cancel();
       }}
-      class="w-fit"
     />
     <Button variant="ghost" size="icon" onclick={save}>
       <Check class="w-4 h-4" />

+ 97 - 74
src/lib/components/WorkspaceEntry.svelte

@@ -15,17 +15,15 @@
   } from "$lib/state.svelte";
   import { Button } from "$lib/components/ui/button";
   import { Input } from "$lib/components/ui/input";
-  import * as Accordion from "$lib/components/ui/accordion";
   import * as Tabs from "$lib/components/ui/tabs";
-  import type { UrlError } from "$lib/types";
+  import type { UrlError, WorkspaceEntry } from "$lib/types";
   import Editable from "./Editable.svelte";
   import Highlight, { LineNumbers } from "svelte-highlight";
   import json from "svelte-highlight/languages/json";
   import { atelierForest } from "svelte-highlight/styles";
-  import { Loader, PlusIcon, TrashIcon } from "@lucide/svelte";
+  import { Loader, PlusIcon, Trash } from "@lucide/svelte";
   import CodeMirror from "./CodeMirror.svelte";
   import * as Resizable from "$lib/components/ui/resizable/index";
-  import Auth from "./Auth.svelte";
   import AuthParams from "./AuthParams.svelte";
   import Checkbox from "./ui/checkbox/checkbox.svelte";
 
@@ -34,12 +32,36 @@
   let isSending = $state(false);
   let response: any = $state();
 
+  const parentAuth = $derived.by(() => {
+    let parentId = _state.entry!!.parent_id;
+
+    while (parentId != null) {
+      const parent = _state.indexes[parentId];
+
+      if (!parent) {
+        console.warn("Parent index is null", parentId);
+        return;
+      }
+
+      if (parent.auth_inherit) {
+        parentId = parent.id;
+        continue;
+      }
+
+      if (parent.auth == null) {
+        return null;
+      }
+
+      return _state.auth.find((a) => a.id === parent.auth) ?? null;
+    }
+  });
+
   const referenceChain = $derived.by(() => {
     const parents = [];
 
     let parent = _state.entry!!.parent_id;
 
-    while (parent !== null) {
+    while (parent != null) {
       parents.push(_state.indexes[parent]);
       parent = _state.indexes[parent].parent_id;
     }
@@ -126,14 +148,6 @@
 
     return url;
   }
-
-  function resolveAuthName() {
-    if (_state.entry.auth !== null) {
-      return _state.auth.find((a) => a.id === _state.entry.auth).name;
-    } else {
-      return "None";
-    }
-  }
 </script>
 
 <svelte:head>
@@ -144,10 +158,14 @@
   <!-- ENTRY PATH -->
   <div class="h-8 flex items-center">
     {#each referenceChain as ref}
-      <Button onclick={() => selectEntry(ref.id)} variant="ghost">
+      <Button
+        class="p-0 h-fit cursor-pointer"
+        onclick={() => selectEntry(ref.id)}
+        variant="ghost"
+      >
         {ref.name || ref.type + "(" + ref.id + ")"}
       </Button>
-      <p>/</p>
+      <p class="pl-1 pr-1">/</p>
     {/each}
     <Editable
       bind:value={_state.entry!!.name}
@@ -164,28 +182,72 @@
   </div>
 {/snippet}
 
-{#if _state.entry?.type === "Collection"}
-  <!-- COLLECTION VIEW -->
+{#snippet authParams(
+  entry: WorkspaceEntry & { auth: number | null; auth_inherit: boolean },
+)}
+  <div class="w-full p-4 pl-2">
+    <Select.Root
+      type="single"
+      value={entry.auth_inherit ? "inherit" : (entry.auth?.toString() ?? "-")}
+    >
+      <Select.Trigger class="w-64">
+        {#if entry.auth != null && !entry.auth_inherit}
+          {_state.auth.find((a) => a.id === entry.auth)?.name}
+        {:else if entry.auth_inherit}
+          Inherit ({parentAuth?.name})
+        {:else}
+          -
+        {/if}
+      </Select.Trigger>
 
-  {@render entryPath()}
-  <section class="space-y-4">
-    <h1 class="text-xl font-semibold">{_state.entry.name}</h1>
+      <Select.Content>
+        <Select.Item onclick={() => setEntryAuth(null, false)} value="-">
+          -
+        </Select.Item>
 
-    <div class="rounded-md p-4 space-y-2">
-      <h2 class="font-medium">Variables</h2>
+        {#if entry.parent_id != null}
+          <Select.Item onclick={() => setEntryAuth(null, true)} value="inherit">
+            Inherit ({parentAuth?.name})
+          </Select.Item>
+        {/if}
 
-      <div class="grid grid-cols-3 gap-2 text-sm">
-        <div class="font-medium text-muted-foreground">Key</div>
-        <div class="font-medium text-muted-foreground col-span-2">Value</div>
+        <Select.Separator />
 
-        <div>baseUrl</div>
-        <div class="col-span-2">https://api.example.com</div>
+        {#each _state.auth as auth}
+          <Select.Item
+            onclick={() => setEntryAuth(auth.id, false)}
+            value={auth.id.toString()}
+          >
+            {auth.name}
+          </Select.Item>
+        {/each}
+      </Select.Content>
+    </Select.Root>
+  </div>
 
-        <div>token</div>
-        <div class="col-span-2">••••••••</div>
-      </div>
+  {#if entry.auth_inherit && parentAuth}
+    <div class="opacity-75 p-4">
+      <AuthParams auth={parentAuth} readonly={true} />
     </div>
-  </section>
+  {:else if entry.auth}
+    <div class="opacity-75 p-4">
+      <AuthParams
+        auth={_state.auth.find((a) => a.id === entry.auth)!!}
+        readonly={true}
+      />
+    </div>
+  {/if}
+{/snippet}
+
+{#if _state.entry?.type === "Collection"}
+  <!-- COLLECTION VIEW -->
+
+  {@render entryPath()}
+
+  <div class="flex flex-wrap p-2 border">
+    <h2 class="pb-2 w-full border-b">Auth</h2>
+    {@render authParams(_state.entry!!)}
+  </div>
 {:else if _state.entry?.type === "Request"}
   <!-- REQUEST WORK AREA -->
 
@@ -301,7 +363,7 @@
                           updateHeader(header.id, header.name, header.value)}
                       />
 
-                      <TrashIcon
+                      <Trash
                         class="h-4 w-4 cursor-pointer text-muted-foreground hover:text-destructive"
                         onclick={() => deleteHeader(header.id)}
                       />
@@ -358,49 +420,10 @@
               </Tabs.Root>
             </Tabs.Content>
 
-            <Tabs.Content value="auth" class="space-y-4">
-              <div class="flex items-center">
-                <p class="mr-2">Inherit</p>
-                <Checkbox
-                  checked={_state.entry.auth_inherit}
-                  onCheckedChange={(v) => setEntryAuth(_state.entry.auth, v)}
-                />
-              </div>
-              <div
-                class="transition-opacity"
-                class:opacity-50={_state.entry.auth_inherit}
-                class:pointer-events-none={_state.entry.auth_inherit}
-              >
-                <Select.Root type="single" value={resolveAuthName()}>
-                  <Select.Trigger class="w-64">
-                    {resolveAuthName()}
-                  </Select.Trigger>
-
-                  <Select.Content>
-                    <Select.Item
-                      onclick={() => setEntryAuth(null, null)}
-                      value={"None"}
-                    >
-                      None
-                    </Select.Item>
-                    {#each _state.auth as auth}
-                      <Select.Item
-                        onclick={() => setEntryAuth(auth.id, null)}
-                        value={auth.name}
-                      >
-                        {auth.name}
-                      </Select.Item>
-                    {/each}
-                  </Select.Content>
-                </Select.Root>
+            <!-- ================= AUTH ================= -->
 
-                {#if _state.entry.auth}
-                  <AuthParams
-                    auth={_state.auth.find((a) => a.id === _state.entry.auth)!!}
-                    readonly={true}
-                  />
-                {/if}
-              </div>
+            <Tabs.Content value="auth" class="space-y-4">
+              {@render authParams(_state.entry!!)}
             </Tabs.Content>
           </div>
         </Tabs.Root>

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

@@ -50,6 +50,9 @@ export type WorkspaceState = {
    */
   environment: WorkspaceEnvironment | null;
 
+  /**
+   * All workspace authentication schemes.
+   */
   auth: Authentication[];
 };
 
@@ -61,14 +64,14 @@ export const state: WorkspaceState = $state({
   indexes: {},
   environments: [],
   environment: null,
-  auths: [],
+  auth: [],
 });
 
 const index = (entry: WorkspaceEntry) => {
   console.log("indexing", entry);
   state.indexes[entry.id] = entry;
 
-  if (entry.parent_id !== null) {
+  if (entry.parent_id != null) {
     if (state.children[entry.parent_id]) {
       state.children[entry.parent_id].push(entry.id);
     } else {
@@ -132,7 +135,7 @@ export async function selectEntry(id: number) {
 
   console.log("selected entry:", $state.snapshot(state.entry));
 
-  if (state.entry.parent_id !== null) {
+  if (state.entry.parent_id != null) {
     let parent = state.indexes[state.entry.parent_id];
     while (parent) {
       parent.open = true;
@@ -195,6 +198,8 @@ 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);
@@ -459,7 +464,7 @@ export async function deleteBody() {
 }
 
 export async function updateBodyContent(body: string, ct: string) {
-  if (state.entry!!.body !== null) {
+  if (state.entry!!.body != null) {
     await invoke("update_request_body", {
       id: state.entry!!.body.id,
       body: {
@@ -528,7 +533,7 @@ export async function deleteAuth(id: number) {
 }
 
 export async function setEntryAuth(id: number | null, inherit: boolean | null) {
-  console.debug("setting entry auth to", id);
+  console.debug("setting entry auth to", id, "inheriting:", inherit);
 
   await invoke("set_workspace_entry_auth", {
     entryId: state.entry!!.id,
@@ -536,9 +541,9 @@ export async function setEntryAuth(id: number | null, inherit: boolean | null) {
     inherit,
   });
 
-  state.entry.auth = id;
-  if (inherit !== null) {
-    state.entry.auth_inherit = inherit;
+  state.entry!!.auth = id;
+  if (inherit != null) {
+    state.entry!!.auth_inherit = inherit;
   }
 }
 

+ 6 - 0
src/lib/types.ts

@@ -15,6 +15,9 @@ export type WorkspaceEntryBase = {
   name: string;
   type: WorkspaceEntryType;
 
+  auth: number | null;
+  auth_inherit: boolean;
+
   // UI values,
   open?: boolean;
 };
@@ -26,6 +29,9 @@ export type WorkspaceRequest = WorkspaceEntryBase & {
   headers: RequestHeader[];
   path: RequestPathParam[];
 
+  auth: number | null;
+  auth_inherit: boolean;
+
   // Display fields
 
   workingUrl?: RequestUrl;