feat(app): recent projects section in command pallette (#15270)
This commit is contained in:
@@ -8,6 +8,7 @@ import fuzzysort from "fuzzysort"
|
|||||||
import { createMemo, createResource, createSignal } from "solid-js"
|
import { createMemo, createResource, createSignal } from "solid-js"
|
||||||
import { useGlobalSDK } from "@/context/global-sdk"
|
import { useGlobalSDK } from "@/context/global-sdk"
|
||||||
import { useGlobalSync } from "@/context/global-sync"
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
|
import { useLayout } from "@/context/layout"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
|
||||||
interface DialogSelectDirectoryProps {
|
interface DialogSelectDirectoryProps {
|
||||||
@@ -19,6 +20,7 @@ interface DialogSelectDirectoryProps {
|
|||||||
type Row = {
|
type Row = {
|
||||||
absolute: string
|
absolute: string
|
||||||
search: string
|
search: string
|
||||||
|
group: "recent" | "folders"
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanInput(value: string) {
|
function cleanInput(value: string) {
|
||||||
@@ -101,7 +103,7 @@ function displayPath(path: string, input: string, home: string) {
|
|||||||
return tildeOf(full, home) || full
|
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 full = trimTrailing(absolute)
|
||||||
const tilde = tildeOf(full, home)
|
const tilde = tildeOf(full, home)
|
||||||
const withSlash = (value: string) => {
|
const withSlash = (value: string) => {
|
||||||
@@ -113,7 +115,16 @@ function toRow(absolute: string, home: string): Row {
|
|||||||
const search = Array.from(
|
const search = Array.from(
|
||||||
new Set([full, withSlash(full), tilde, withSlash(tilde), getFilename(full)].filter(Boolean)),
|
new Set([full, withSlash(full), tilde, withSlash(tilde), getFilename(full)].filter(Boolean)),
|
||||||
).join("\n")
|
).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: {
|
function useDirectorySearch(args: {
|
||||||
@@ -237,6 +248,7 @@ function useDirectorySearch(args: {
|
|||||||
export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
|
export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
|
||||||
const sync = useGlobalSync()
|
const sync = useGlobalSync()
|
||||||
const sdk = useGlobalSDK()
|
const sdk = useGlobalSDK()
|
||||||
|
const layout = useLayout()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
|
||||||
@@ -266,9 +278,42 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
|
|||||||
start,
|
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 items = async (value: string) => {
|
||||||
const results = await directories(value)
|
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) {
|
function resolve(absolute: string) {
|
||||||
@@ -285,6 +330,14 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
|
|||||||
items={items}
|
items={items}
|
||||||
key={(x) => x.absolute}
|
key={(x) => x.absolute}
|
||||||
filterKeys={["search"]}
|
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)}
|
ref={(r) => (list = r)}
|
||||||
onFilter={(value) => setFilter(cleanInput(value))}
|
onFilter={(value) => setFilter(cleanInput(value))}
|
||||||
onKeyEvent={(e, item) => {
|
onKeyEvent={(e, item) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user