2
0
Эх сурвалжийг харах

feat(app): recent projects section in command pallette (#15270)

Filip 1 сар өмнө
parent
commit
1f108bc401

+ 56 - 3
packages/app/src/components/dialog-select-directory.tsx

@@ -8,6 +8,7 @@ import fuzzysort from "fuzzysort"
 import { createMemo, createResource, createSignal } from "solid-js"
 import { useGlobalSDK } from "@/context/global-sdk"
 import { useGlobalSync } from "@/context/global-sync"
+import { useLayout } from "@/context/layout"
 import { useLanguage } from "@/context/language"
 
 interface DialogSelectDirectoryProps {
@@ -19,6 +20,7 @@ interface DialogSelectDirectoryProps {
 type Row = {
   absolute: string
   search: string
+  group: "recent" | "folders"
 }
 
 function cleanInput(value: string) {
@@ -101,7 +103,7 @@ function displayPath(path: string, input: string, home: string) {
   return tildeOf(full, home) || full
 }
 
-function toRow(absolute: string, home: string): Row {
+function toRow(absolute: string, home: string, group: Row["group"]): Row {
   const full = trimTrailing(absolute)
   const tilde = tildeOf(full, home)
   const withSlash = (value: string) => {
@@ -113,7 +115,16 @@ function toRow(absolute: string, home: string): Row {
   const search = Array.from(
     new Set([full, withSlash(full), tilde, withSlash(tilde), getFilename(full)].filter(Boolean)),
   ).join("\n")
-  return { absolute: full, search }
+  return { absolute: full, search, group }
+}
+
+function uniqueRows(rows: Row[]) {
+  const seen = new Set<string>()
+  return rows.filter((row) => {
+    if (seen.has(row.absolute)) return false
+    seen.add(row.absolute)
+    return true
+  })
 }
 
 function useDirectorySearch(args: {
@@ -237,6 +248,7 @@ function useDirectorySearch(args: {
 export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
   const sync = useGlobalSync()
   const sdk = useGlobalSDK()
+  const layout = useLayout()
   const dialog = useDialog()
   const language = useLanguage()
 
@@ -266,9 +278,42 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
     start,
   })
 
+  const recentProjects = createMemo(() => {
+    const projects = layout.projects.list()
+    const byProject = new Map<string, number>()
+
+    for (const project of projects) {
+      let at = 0
+      const dirs = [project.worktree, ...(project.sandboxes ?? [])]
+      for (const directory of dirs) {
+        const sessions = sync.child(directory, { bootstrap: false })[0].session
+        for (const session of sessions) {
+          if (session.time.archived) continue
+          const updated = session.time.updated ?? session.time.created
+          if (updated > at) at = updated
+        }
+      }
+      byProject.set(project.worktree, at)
+    }
+
+    return projects
+      .map((project, index) => ({ project, at: byProject.get(project.worktree) ?? 0, index }))
+      .sort((a, b) => b.at - a.at || a.index - b.index)
+      .slice(0, 5)
+      .map(({ project }) => {
+        const row = toRow(project.worktree, home(), "recent")
+        const name = project.name || getFilename(project.worktree)
+        return {
+          ...row,
+          search: `${row.search}\n${name}`,
+        }
+      })
+  })
+
   const items = async (value: string) => {
     const results = await directories(value)
-    return results.map((absolute) => toRow(absolute, home()))
+    const directoryRows = results.map((absolute) => toRow(absolute, home(), "folders"))
+    return uniqueRows([...recentProjects(), ...directoryRows])
   }
 
   function resolve(absolute: string) {
@@ -285,6 +330,14 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
         items={items}
         key={(x) => x.absolute}
         filterKeys={["search"]}
+        groupBy={(item) => item.group}
+        sortGroupsBy={(a, b) => {
+          if (a.category === b.category) return 0
+          return a.category === "recent" ? -1 : 1
+        }}
+        groupHeader={(group) =>
+          group.category === "recent" ? language.t("home.recentProjects") : language.t("command.project.open")
+        }
         ref={(r) => (list = r)}
         onFilter={(value) => setFilter(cleanInput(value))}
         onKeyEvent={(e, item) => {