Browse Source

chore: cleanup

Adam 1 month ago
parent
commit
0596b02f19
2 changed files with 122 additions and 54 deletions
  1. 103 50
      packages/app/src/pages/layout.tsx
  2. 19 4
      packages/desktop/src/index.tsx

+ 103 - 50
packages/app/src/pages/layout.tsx

@@ -76,6 +76,7 @@ export default function Layout(props: ParentProps) {
       activeWorkspace: undefined as string | undefined,
       workspaceOrder: {} as Record<string, string[]>,
       workspaceName: {} as Record<string, string>,
+      workspaceBranchName: {} as Record<string, Record<string, string>>,
       workspaceExpanded: {} as Record<string, boolean>,
     }),
   )
@@ -198,7 +199,10 @@ export default function Layout(props: ParentProps) {
           value={editorValue()}
           class={props.class}
           onInput={(event) => setEditor("value", event.currentTarget.value)}
-          onKeyDown={(event) => editorKeyDown(event, props.onSave)}
+          onKeyDown={(event) => {
+            event.stopPropagation()
+            editorKeyDown(event, props.onSave)
+          }}
           onBlur={() => closeEditor()}
           onPointerDown={stopPropagation}
           onClick={stopPropagation}
@@ -458,9 +462,27 @@ export default function Layout(props: ParentProps) {
     ),
   )
 
-  const workspaceName = (directory: string) => store.workspaceName[directory]
-  const workspaceLabel = (directory: string, branch?: string) =>
-    workspaceName(directory) ?? branch ?? getFilename(directory)
+  const workspaceKey = (directory: string) => directory.replace(/[\\/]+$/, "")
+
+  const workspaceName = (directory: string, projectId?: string, branch?: string) => {
+    const key = workspaceKey(directory)
+    const direct = store.workspaceName[key] ?? store.workspaceName[directory]
+    if (direct) return direct
+    if (!projectId) return
+    if (!branch) return
+    return store.workspaceBranchName[projectId]?.[branch]
+  }
+
+  const setWorkspaceName = (directory: string, next: string, projectId?: string, branch?: string) => {
+    const key = workspaceKey(directory)
+    setStore("workspaceName", (prev) => ({ ...(prev ?? {}), [key]: next }))
+    if (!projectId) return
+    if (!branch) return
+    setStore("workspaceBranchName", projectId, (prev) => ({ ...(prev ?? {}), [branch]: next }))
+  }
+
+  const workspaceLabel = (directory: string, branch?: string, projectId?: string) =>
+    workspaceName(directory, projectId, branch) ?? branch ?? getFilename(directory)
 
   const isWorkspaceEditing = () => editor.active.startsWith("workspace:")
 
@@ -885,10 +907,10 @@ export default function Layout(props: ParentProps) {
     })
   }
 
-  const renameWorkspace = (directory: string, next: string) => {
-    const current = workspaceName(directory) ?? getFilename(directory)
+  const renameWorkspace = (directory: string, next: string, projectId?: string, branch?: string) => {
+    const current = workspaceName(directory, projectId, branch) ?? branch ?? getFilename(directory)
     if (current === next) return
-    setStore("workspaceName", directory, next)
+    setWorkspaceName(directory, next, projectId, branch)
   }
 
   function closeProject(directory: string) {
@@ -1491,7 +1513,7 @@ export default function Layout(props: ParentProps) {
 
       const [workspaceStore] = globalSync.child(directory)
       const kind = directory === project.worktree ? "local" : "sandbox"
-      const name = workspaceLabel(directory, workspaceStore.vcs?.branch)
+      const name = workspaceLabel(directory, workspaceStore.vcs?.branch, project.id)
       return `${kind} : ${name}`
     })
 
@@ -1508,6 +1530,7 @@ export default function Layout(props: ParentProps) {
     const sortable = createSortable(props.directory)
     const [workspaceStore, setWorkspaceStore] = globalSync.child(props.directory)
     const [menuOpen, setMenuOpen] = createSignal(false)
+    const [pendingRename, setPendingRename] = createSignal(false)
     const slug = createMemo(() => base64Encode(props.directory))
     const sessions = createMemo(() =>
       workspaceStore.session
@@ -1517,8 +1540,9 @@ export default function Layout(props: ParentProps) {
     )
     const local = createMemo(() => props.directory === props.project.worktree)
     const workspaceValue = createMemo(() => {
-      const name = workspaceStore.vcs?.branch ?? getFilename(props.directory)
-      return workspaceName(props.directory) ?? name
+      const branch = workspaceStore.vcs?.branch
+      const name = branch ?? getFilename(props.directory)
+      return workspaceName(props.directory, props.project.id, branch) ?? name
     })
     const open = createMemo(() => store.workspaceExpanded[props.directory] ?? true)
     const loading = createMemo(() => open() && workspaceStore.status !== "complete" && sessions().length === 0)
@@ -1537,6 +1561,44 @@ export default function Layout(props: ParentProps) {
       if (editorOpen(`workspace:${props.directory}`)) closeEditor()
     }
 
+    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" />
+        </div>
+        <span class="text-14-medium text-text-base shrink-0">{local() ? "local" : "sandbox"} :</span>
+        <Show
+          when={!local()}
+          fallback={
+            <span class="text-14-medium text-text-base min-w-0 truncate">
+              {workspaceStore.vcs?.branch ?? getFilename(props.directory)}
+            </span>
+          }
+        >
+          <InlineEditor
+            id={`workspace:${props.directory}`}
+            value={workspaceValue}
+            onSave={(next) => {
+              const trimmed = next.trim()
+              if (!trimmed) return
+              renameWorkspace(props.directory, trimmed, props.project.id, workspaceStore.vcs?.branch)
+              setEditor("value", workspaceValue())
+            }}
+            class="text-14-medium text-text-base min-w-0 truncate"
+            displayClass="text-14-medium text-text-base min-w-0 truncate"
+            editing={workspaceEditActive()}
+            stopPropagation={false}
+            openOnDblClick={false}
+          />
+        </Show>
+        <Icon
+          name={open() ? "chevron-down" : "chevron-right"}
+          size="small"
+          class="shrink-0 text-icon-base opacity-0 transition-opacity group-hover/workspace:opacity-100 group-focus-within/workspace:opacity-100"
+        />
+      </div>
+    )
+
     return (
       // @ts-ignore
       <div use:sortable classList={{ "opacity-30": sortable.isActiveDraggable }}>
@@ -1544,43 +1606,18 @@ export default function Layout(props: ParentProps) {
           <div class="px-2 py-1">
             <div class="group/workspace relative">
               <div class="flex items-center gap-1">
-                <Collapsible.Trigger class="flex items-center justify-between w-full pl-2 pr-16 py-1.5 rounded-md hover:bg-surface-raised-base-hover">
-                  <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" />
-                    </div>
-                    <span class="text-14-medium text-text-base shrink-0">{local() ? "local" : "sandbox"} :</span>
-                    <Show
-                      when={!local()}
-                      fallback={
-                        <span class="text-14-medium text-text-base min-w-0 truncate">
-                          {workspaceStore.vcs?.branch ?? getFilename(props.directory)}
-                        </span>
-                      }
-                    >
-                      <InlineEditor
-                        id={`workspace:${props.directory}`}
-                        value={workspaceValue}
-                        onSave={(next) => {
-                          const trimmed = next.trim()
-                          if (!trimmed) return
-                          renameWorkspace(props.directory, trimmed)
-                          setEditor("value", workspaceValue())
-                        }}
-                        class="text-14-medium text-text-base min-w-0 truncate"
-                        displayClass="text-14-medium text-text-base min-w-0 truncate"
-                        editing={workspaceEditActive()}
-                        stopPropagation={false}
-                        openOnDblClick={false}
-                      />
-                    </Show>
-                    <Icon
-                      name={open() ? "chevron-down" : "chevron-right"}
-                      size="small"
-                      class="shrink-0 text-icon-base opacity-0 transition-opacity group-hover/workspace:opacity-100 group-focus-within/workspace:opacity-100"
-                    />
+                <Show
+                  when={workspaceEditActive()}
+                  fallback={
+                    <Collapsible.Trigger class="flex items-center justify-between w-full pl-2 pr-16 py-1.5 rounded-md hover:bg-surface-raised-base-hover">
+                      {header()}
+                    </Collapsible.Trigger>
+                  }
+                >
+                  <div class="flex items-center justify-between w-full pl-2 pr-16 py-1.5 rounded-md">
+                    {header()}
                   </div>
-                </Collapsible.Trigger>
+                </Show>
                 <div
                   class="absolute right-1 top-1/2 -translate-y-1/2 flex items-center gap-0.5 transition-opacity"
                   classList={{
@@ -1595,21 +1632,37 @@ export default function Layout(props: ParentProps) {
                       <DropdownMenu.Trigger as={IconButton} icon="dot-grid" variant="ghost" class="size-6 rounded-md" />
                     </Tooltip>
                     <DropdownMenu.Portal>
-                      <DropdownMenu.Content>
+                      <DropdownMenu.Content
+                        onCloseAutoFocus={(event) => {
+                          if (!pendingRename()) return
+                          event.preventDefault()
+                          setPendingRename(false)
+                          openEditor(`workspace:${props.directory}`, workspaceValue())
+                        }}
+                      >
                         <DropdownMenu.Item onSelect={() => navigate(`/${slug()}/session`)}>
                           <DropdownMenu.ItemLabel>New session</DropdownMenu.ItemLabel>
                         </DropdownMenu.Item>
+                        <DropdownMenu.Item
+                          disabled={local()}
+                          onSelect={() => {
+                            setPendingRename(true)
+                            setMenuOpen(false)
+                          }}
+                        >
+                          <DropdownMenu.ItemLabel>Rename</DropdownMenu.ItemLabel>
+                        </DropdownMenu.Item>
                         <DropdownMenu.Item
                           disabled={local()}
                           onSelect={() => dialog.show(() => <DialogResetWorkspace directory={props.directory} />)}
                         >
-                          <DropdownMenu.ItemLabel>Reset workspace</DropdownMenu.ItemLabel>
+                          <DropdownMenu.ItemLabel>Reset</DropdownMenu.ItemLabel>
                         </DropdownMenu.Item>
                         <DropdownMenu.Item
                           disabled={local()}
                           onSelect={() => dialog.show(() => <DialogDeleteWorkspace directory={props.directory} />)}
                         >
-                          <DropdownMenu.ItemLabel>Delete workspace</DropdownMenu.ItemLabel>
+                          <DropdownMenu.ItemLabel>Delete</DropdownMenu.ItemLabel>
                         </DropdownMenu.Item>
                       </DropdownMenu.Content>
                     </DropdownMenu.Portal>
@@ -1681,7 +1734,7 @@ export default function Layout(props: ParentProps) {
     const label = (directory: string) => {
       const [data] = globalSync.child(directory)
       const kind = directory === props.project.worktree ? "local" : "sandbox"
-      const name = workspaceLabel(directory, data.vcs?.branch)
+      const name = workspaceLabel(directory, data.vcs?.branch, props.project.id)
       return `${kind} : ${name}`
     }
 

+ 19 - 4
packages/desktop/src/index.tsx

@@ -89,11 +89,26 @@ const createPlatform = (password: Accessor<string | null>): Platform => ({
       length(): Promise<number>
     }
 
-    const WRITE_DEBOUNCE_MS = 250
+      const WRITE_DEBOUNCE_MS = 250
 
-    const storeCache = new Map<string, Promise<StoreLike>>()
-    const apiCache = new Map<string, AsyncStorage & { flush: () => Promise<void> }>()
-    const memoryCache = new Map<string, StoreLike>()
+      const storeCache = new Map<string, Promise<StoreLike>>()
+      const apiCache = new Map<string, AsyncStorage & { flush: () => Promise<void> }>()
+      const memoryCache = new Map<string, StoreLike>()
+
+      const flushAll = async () => {
+        const apis = Array.from(apiCache.values())
+        await Promise.all(apis.map((api) => api.flush().catch(() => undefined)))
+      }
+
+      if ("addEventListener" in globalThis) {
+        const handleVisibility = () => {
+          if (document.visibilityState !== "hidden") return
+          void flushAll()
+        }
+
+        window.addEventListener("pagehide", () => void flushAll())
+        document.addEventListener("visibilitychange", handleVisibility)
+      }
 
     const createMemoryStore = () => {
       const data = new Map<string, string>()