Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/shelve/app/components/CommandPalette.vue
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ function playAction(item: CommandItem, index: number) {
</span>
</div>

<span v-if="item.suffix" class="text-xs text-muted">
{{ item.suffix }}
</span>
<UIcon
v-if="item.hasSubmenu"
name="lucide:chevron-right"
Expand Down
68 changes: 67 additions & 1 deletion apps/shelve/app/composables/useAppCommands.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CommandGroup, CommandItem, SubMenuState } from '@types'
import type { CommandGroup, CommandItem, SubMenuState, Project } from '@types'

export function useAppCommands() {
const teams = useTeams()
Expand All @@ -11,6 +11,29 @@ export function useAppCommands() {
fetchTeams()
}

// Fetch projects for all teams
async function fetchAllProjects() {
if (!teams.value || teams.value.length === 0) return

await Promise.all(teams.value.map(async (team) => {
try {
const projects = useProjects(team.slug)
if (!projects.value || projects.value.length === 0) {
projects.value = await $fetch<Project[]>(`/api/teams/${team.slug}/projects`)
}
} catch (error) {
console.error(`Failed to fetch projects for ${team.slug}:`, error)
}
}))
}

// Eagerly fetch projects when teams are available
watch(teams, (newTeams) => {
if (newTeams && newTeams.length > 0) {
fetchAllProjects()
}
}, { immediate: true })

// Submenu state
const subMenuState = reactive<SubMenuState>({
active: false,
Expand Down Expand Up @@ -252,6 +275,40 @@ export function useAppCommands() {
}))
})

// Project commands - all projects from all teams
const projectCommands = computed<CommandItem[]>(() => {
if (!teams.value || teams.value.length === 0) return []

const currentTeamSlug = getTeamSlug()
const allProjects: CommandItem[] = []

// useProjects returns useState keyed by slug - safe to call in computed
for (const team of teams.value) {
const projects = useProjects(team.slug)
if (!projects.value || projects.value.length === 0) continue

const isCurrentTeam = team.slug === currentTeamSlug
for (const project of projects.value) {
allProjects.push({
id: `project-${project.id}`,
label: project.name,
icon: project.logo || 'lucide:folder',
isAvatar: Boolean(project.logo),
suffix: isCurrentTeam ? undefined : team.name,
description: isCurrentTeam ? undefined : `in ${team.name}`,
action: () => navigateTo(`/${team.slug}/projects/${project.id}`),
keywords: ['project', 'switch', project.name, team.name],
active: route.params.projectId === String(project.id),
})
}
}

// Hide if only 1 project total
if (allProjects.length <= 1) return []

return allProjects
})

// Help & Support commands
const helpCommands = computed<CommandItem[]>(() => [
{
Expand Down Expand Up @@ -346,6 +403,15 @@ export function useAppCommands() {
})
}

// Only show projects if available
if (projectCommands.value.length > 0) {
groups.push({
id: 'projects',
label: 'Projects',
items: projectCommands.value
})
}

// Only show navigation if we have teams
if (teams.value && teams.value.length > 0) {
groups.push({
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/Command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface CommandItem {
icon: string
isAvatar?: boolean
description?: string
suffix?: string
action?: () => void | Promise<void>
keywords?: string[]
active?: boolean
Expand Down