|
4 | 4 | import { t } from '../stores/i18n.svelte.js'; |
5 | 5 | import { aiStore } from '../stores/aiStore.svelte.js'; |
6 | 6 | import { getShortcutDisplay } from '../utils/keyboardShortcuts.js'; |
7 | | - import { workspaceIconMap } from '../utils/icons.js'; |
| 7 | + import { workspaceMenuItems } from '../navigation/workspaceMenu.js'; |
8 | 8 | import { isTauri as getIsTauri } from '../utils/isTauri.js'; |
9 | 9 | import DropdownMenu from './DropdownMenu.svelte'; |
10 | 10 | import Tooltip from '../components/Tooltip.svelte'; |
|
13 | 13 | import NotificationTray from '../features/notifications/NotificationTray.svelte'; |
14 | 14 | import ScrollableSidebar from './ScrollableSidebar.svelte'; |
15 | 15 | import { |
16 | | - IconSearch, IconSettings, IconPlus, IconGridDots, IconUserScan, |
| 16 | + IconSearch, IconPlus, IconGridDots, IconUserScan, |
17 | 17 | IconFolders, IconLayoutSidebarLeftExpand, IconLayoutSidebarLeftCollapse, |
18 | 18 | IconMessage, IconTerminal2, |
19 | 19 | } from '@tabler/icons-svelte-runes'; |
|
32 | 32 |
|
33 | 33 | let workspaceSearchQuery = $state(''); |
34 | 34 |
|
35 | | - // Derived workspace dropdown items that automatically updates when store or search changes |
36 | | - const workspacesDropdownItems = $derived.by(() => { |
37 | | - const items = []; |
38 | | -
|
39 | | - // Add search input at the top |
40 | | - items.push({ |
41 | | - type: 'search', |
42 | | - id: 'search', |
43 | | - testid: 'workspaces-search', |
44 | | - placeholder: t('nav.searchWorkspaces'), |
45 | | - value: workspaceSearchQuery, |
46 | | - onInput: (value) => { |
47 | | - workspaceSearchQuery = value; |
48 | | - } |
49 | | - }); |
50 | | -
|
51 | | - // Filter workspaces based on search query (inactive workspaces are |
52 | | - // hidden here even for admins — Manage Workspaces is the only surface |
53 | | - // that shows them). |
54 | | - const activeRegularWorkspaces = $workspacesStore.regularWorkspaces.filter(ws => ws.active); |
55 | | - const search = workspaceSearchQuery?.trim().toLowerCase(); |
56 | | - const filteredWorkspaces = !search |
57 | | - ? activeRegularWorkspaces |
58 | | - : activeRegularWorkspaces.filter(workspace => { |
59 | | - const nameMatch = workspace.name?.toLowerCase().includes(search); |
60 | | - const keyMatch = workspace.key?.toLowerCase().includes(search); |
61 | | - const descriptionMatch = workspace.description?.toLowerCase().includes(search); |
62 | | - return nameMatch || keyMatch || descriptionMatch; |
63 | | - }); |
64 | | -
|
65 | | - // Add workspace items |
66 | | - if (filteredWorkspaces.length > 0) { |
67 | | - const maxVisible = 10; |
68 | | - const hasMore = filteredWorkspaces.length > maxVisible; |
69 | | - const visibleWorkspaces = filteredWorkspaces.slice(0, maxVisible); |
70 | | - const workspaceItems = visibleWorkspaces.map(workspace => { |
71 | | - const hasAvatar = workspace.avatar_url; |
72 | | - const workspaceIcon = workspaceIconMap[workspace.icon] || workspaceIconMap.Package; |
73 | | -
|
74 | | - return { |
75 | | - id: workspace.id, |
76 | | - type: 'regular', |
77 | | - testid: 'workspace-dropdown-item', |
78 | | - icon: hasAvatar ? null : workspaceIcon, |
79 | | - iconColor: hasAvatar ? null : workspace.color, |
80 | | - avatarUrl: hasAvatar ? workspace.avatar_url : null, |
81 | | - title: workspace.name, |
82 | | - subtitle: workspace.description, |
83 | | - href: `/workspaces/${workspace.id}` |
84 | | - }; |
85 | | - }); |
86 | | -
|
87 | | - items.push({ type: 'group', items: workspaceItems }); |
88 | | - if (hasMore) { |
89 | | - items.push({ type: 'text', text: t('nav.searchToFindMore') }); |
90 | | - } |
91 | | - items.push({ type: 'divider' }); |
92 | | - } else if (activeRegularWorkspaces.length > 0 && workspaceSearchQuery) { |
93 | | - // Show "no results" only if there are workspaces but search didn't match |
94 | | - items.push( |
95 | | - { type: 'text', text: t('nav.noWorkspacesMatch') }, |
96 | | - { type: 'divider' } |
97 | | - ); |
98 | | - } else if (activeRegularWorkspaces.length === 0) { |
99 | | - items.push( |
100 | | - { type: 'text', text: t('nav.noWorkspacesFound') }, |
101 | | - { type: 'divider' } |
102 | | - ); |
103 | | - } |
104 | | -
|
105 | | - // Add combined manage workspaces action |
106 | | - items.push({ |
107 | | - id: 'manage', |
108 | | - type: 'regular', |
109 | | - icon: IconSettings, |
110 | | - title: t('nav.manageWorkspaces'), |
111 | | - subtitle: t('nav.manageWorkspacesSubtitle'), |
112 | | - color: 'var(--ds-text-link)', |
113 | | - class: 'font-medium', |
114 | | - href: '/workspaces' |
115 | | - }); |
116 | | -
|
117 | | - return items; |
118 | | - }); |
| 35 | + const workspacesDropdownItems = $derived(workspaceMenuItems( |
| 36 | + $workspacesStore.regularWorkspaces, |
| 37 | + workspaceSearchQuery, |
| 38 | + (value) => workspaceSearchQuery = value, |
| 39 | + t |
| 40 | + )); |
119 | 41 |
|
120 | 42 | // Filter nav items based on permissions (registry: navigation/mainNavigation.js) |
121 | 43 | const filteredMainNav = $derived( |
|
174 | 96 | triggerGap="gap-3" |
175 | 97 | triggerText={$uiStore.navExpanded ? t('nav.workspaces') : ''} |
176 | 98 | triggerLabel={t('nav.workspaces')} |
177 | | - triggerClass="w-full px-3 h-10 rounded flex items-center justify-start cursor-pointer nav-button nav-button-emphasized {isWorkspaceRoute($currentRoute.view) || activeSurface === 'workspaces' ? 'nav-button-selected' : ''} {!$workspacesStore.loaded ? 'opacity-50 cursor-wait' : ''}" |
| 99 | + triggerClass="w-full px-3 h-10 rounded flex items-center justify-start cursor-pointer nav-button {isWorkspaceRoute($currentRoute.view) ? 'nav-button-selected' : ''} {!$workspacesStore.loaded ? 'opacity-50 cursor-wait' : ''}" |
178 | 100 | triggerTestid="workspaces-dropdown-trigger" |
179 | 101 | items={workspacesDropdownItems} |
180 | 102 | maxWidth="max-w-xs" |
|
0 commit comments