Преглед изворни кода

fix(app): better pending states for workspace operations

Adam пре 1 месец
родитељ
комит
7f862533d8
1 измењених фајлова са 32 додато и 13 уклоњено
  1. 32 13
      packages/app/src/pages/layout.tsx

+ 32 - 13
packages/app/src/pages/layout.tsx

@@ -122,6 +122,17 @@ export default function Layout(props: ParentProps) {
     active: "" as string,
     value: "",
   })
+  const [busyWorkspaces, setBusyWorkspaces] = createSignal<Set<string>>(new Set())
+  const setBusy = (directory: string, value: boolean) => {
+    const key = workspaceKey(directory)
+    setBusyWorkspaces((prev) => {
+      const next = new Set(prev)
+      if (value) next.add(key)
+      else next.delete(key)
+      return next
+    })
+  }
+  const isBusy = (directory: string) => busyWorkspaces().has(workspaceKey(directory))
   const editorRef = { current: undefined as HTMLInputElement | undefined }
 
   const autoselecting = createMemo(() => {
@@ -995,6 +1006,8 @@ export default function Layout(props: ParentProps) {
     if (!current) return
     if (directory === current.worktree) return
 
+    setBusy(directory, true)
+
     const result = await globalSDK.client.worktree
       .remove({ directory: current.worktree, worktreeRemoveInput: { directory } })
       .then((x) => x.data)
@@ -1006,6 +1019,8 @@ export default function Layout(props: ParentProps) {
         return false
       })
 
+    setBusy(directory, false)
+
     if (!result) return
 
     layout.projects.close(directory)
@@ -1021,12 +1036,7 @@ export default function Layout(props: ParentProps) {
     if (!current) return
     if (directory === current.worktree) return
 
-    const progress = showToast({
-      persistent: true,
-      title: "Resetting workspace",
-      description: "This may take a minute.",
-    })
-    const dismiss = () => toaster.dismiss(progress)
+    setBusy(directory, true)
 
     const sessions = await globalSDK.client.session
       .list({ directory })
@@ -1037,7 +1047,6 @@ export default function Layout(props: ParentProps) {
       .reset({ directory: current.worktree, worktreeResetInput: { directory } })
       .then((x) => x.data)
       .catch((err) => {
-        dismiss()
         showToast({
           title: "Failed to reset workspace",
           description: errorMessage(err),
@@ -1046,7 +1055,7 @@ export default function Layout(props: ParentProps) {
       })
 
     if (!result) {
-      dismiss()
+      setBusy(directory, false)
       return
     }
 
@@ -1066,7 +1075,8 @@ export default function Layout(props: ParentProps) {
     )
 
     await globalSDK.client.instance.dispose({ directory }).catch(() => undefined)
-    dismiss()
+
+    setBusy(directory, false)
 
     const href = `/${base64Encode(directory)}/session`
     navigate(href)
@@ -1590,6 +1600,7 @@ export default function Layout(props: ParentProps) {
     const boot = createMemo(() => open() || active())
     const loading = createMemo(() => open() && workspaceStore.status !== "complete" && sessions().length === 0)
     const hasMore = createMemo(() => local() && workspaceStore.sessionTotal > workspaceStore.session.length)
+    const busy = createMemo(() => isBusy(props.directory))
     const loadMore = async () => {
       if (!local()) return
       setWorkspaceStore("limit", (limit) => limit + 5)
@@ -1612,7 +1623,9 @@ export default function Layout(props: ParentProps) {
     const header = () => (
       <div class="flex items-center gap-1 min-w-0 flex-1">
         <div class="flex items-center justify-center shrink-0 size-6">
-          <Icon name="branch" size="small" />
+          <Show when={busy()} fallback={<Icon name="branch" size="small" />}>
+            <Spinner class="size-[15px]" />
+          </Show>
         </div>
         <span class="text-14-medium text-text-base shrink-0">{local() ? "local" : "sandbox"} :</span>
         <Show
@@ -1649,7 +1662,13 @@ export default function Layout(props: ParentProps) {
 
     return (
       // @ts-ignore
-      <div use:sortable classList={{ "opacity-30": sortable.isActiveDraggable }}>
+      <div
+        use:sortable
+        classList={{
+          "opacity-30": sortable.isActiveDraggable,
+          "opacity-50 pointer-events-none": busy(),
+        }}
+      >
         <Collapsible variant="ghost" open={open()} class="shrink-0" onOpenChange={openWrapper}>
           <div class="px-2 py-1">
             <div class="group/workspace relative">
@@ -1699,13 +1718,13 @@ export default function Layout(props: ParentProps) {
                           <DropdownMenu.ItemLabel>Rename</DropdownMenu.ItemLabel>
                         </DropdownMenu.Item>
                         <DropdownMenu.Item
-                          disabled={local()}
+                          disabled={local() || busy()}
                           onSelect={() => dialog.show(() => <DialogResetWorkspace directory={props.directory} />)}
                         >
                           <DropdownMenu.ItemLabel>Reset</DropdownMenu.ItemLabel>
                         </DropdownMenu.Item>
                         <DropdownMenu.Item
-                          disabled={local()}
+                          disabled={local() || busy()}
                           onSelect={() => dialog.show(() => <DialogDeleteWorkspace directory={props.directory} />)}
                         >
                           <DropdownMenu.ItemLabel>Delete</DropdownMenu.ItemLabel>