@peers-app/peers-ui 0.7.39 → 0.7.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/command-palette/command-palette-ui.js +190 -35
- package/dist/command-palette/command-palette.js +0 -121
- package/dist/mention-configs.js +1 -1
- package/dist/screens/console-logs/console-logs-list.js +45 -47
- package/dist/screens/search/global-search.js +110 -36
- package/package.json +3 -3
- package/src/command-palette/command-palette-ui.tsx +245 -12
- package/src/command-palette/command-palette.ts +0 -121
- package/src/mention-configs.ts +1 -1
- package/src/screens/console-logs/console-logs-list.tsx +47 -56
- package/src/screens/search/global-search.tsx +126 -7
|
@@ -39,10 +39,14 @@ const hooks_1 = require("../../hooks");
|
|
|
39
39
|
const color_mode_dropdown_1 = require("../settings/color-mode-dropdown");
|
|
40
40
|
const tabs_state_1 = require("../../tabs-layout/tabs-state");
|
|
41
41
|
const mention_configs_1 = require("../../mention-configs");
|
|
42
|
+
const routes_loader_1 = require("../../ui-router/routes-loader");
|
|
43
|
+
const system_apps_1 = require("../../system-apps");
|
|
42
44
|
function GlobalSearch() {
|
|
43
45
|
const [_colorMode] = (0, hooks_1.useObservable)(color_mode_dropdown_1.colorMode);
|
|
46
|
+
const [packages] = (0, hooks_1.useObservable)(routes_loader_1.allPackages);
|
|
44
47
|
const [searchQuery, setSearchQuery] = (0, react_1.useState)('');
|
|
45
48
|
const [searchResults, setSearchResults] = (0, react_1.useState)([]);
|
|
49
|
+
const [appResults, setAppResults] = (0, react_1.useState)([]);
|
|
46
50
|
const [isSearching, setIsSearching] = (0, react_1.useState)(false);
|
|
47
51
|
const inputRef = (0, react_1.useRef)(null);
|
|
48
52
|
const isDark = _colorMode === 'dark';
|
|
@@ -62,10 +66,39 @@ function GlobalSearch() {
|
|
|
62
66
|
{ config: mention_configs_1.valueTypeMentionConfig, category: 'Types', navigationPath: 'peer-types' },
|
|
63
67
|
{ config: mention_configs_1.userMentionConfig, category: 'Users', navigationPath: 'profile' },
|
|
64
68
|
];
|
|
69
|
+
// Get all apps (system and user)
|
|
70
|
+
const getAllApps = () => {
|
|
71
|
+
const allPackages_ = [...packages, system_apps_1.systemPackage];
|
|
72
|
+
return allPackages_
|
|
73
|
+
.filter(p => !p.disabled && p.appNavs && p.appNavs.length > 0)
|
|
74
|
+
.flatMap(pkg => pkg.appNavs.map(navItem => {
|
|
75
|
+
// Construct path - use direct path for system apps, package-nav for others
|
|
76
|
+
let path;
|
|
77
|
+
if (pkg.packageId === 'system-apps') {
|
|
78
|
+
path = navItem.navigationPath ?? navItem.name.replace(/\s/g, '-').toLowerCase();
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
path = `package-nav/${pkg.packageId}/${(navItem.navigationPath ?? navItem.name).replace(/[^a-zA-Z0-9]/g, '-').toLowerCase()}`;
|
|
82
|
+
while (path.includes('//')) {
|
|
83
|
+
path = path.replace('//', '/');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
packageId: pkg.packageId,
|
|
88
|
+
packageName: pkg.name,
|
|
89
|
+
navItem,
|
|
90
|
+
path,
|
|
91
|
+
name: navItem.name,
|
|
92
|
+
displayName: navItem.displayName || navItem.name,
|
|
93
|
+
iconClassName: navItem.iconClassName || 'bi-box-seam'
|
|
94
|
+
};
|
|
95
|
+
}));
|
|
96
|
+
};
|
|
65
97
|
// Debounced search effect
|
|
66
98
|
(0, react_1.useEffect)(() => {
|
|
67
99
|
if (!searchQuery.trim()) {
|
|
68
100
|
setSearchResults([]);
|
|
101
|
+
setAppResults([]);
|
|
69
102
|
return;
|
|
70
103
|
}
|
|
71
104
|
const timeoutId = setTimeout(async () => {
|
|
@@ -88,13 +121,20 @@ function GlobalSearch() {
|
|
|
88
121
|
const searchResults = await Promise.all(searchPromises);
|
|
89
122
|
const filteredResults = searchResults.filter(Boolean);
|
|
90
123
|
setSearchResults(filteredResults);
|
|
124
|
+
// Search apps
|
|
125
|
+
const allApps = getAllApps();
|
|
126
|
+
const lowerQuery = searchQuery.toLowerCase();
|
|
127
|
+
const filteredApps = allApps.filter(app => app.name.toLowerCase().includes(lowerQuery) ||
|
|
128
|
+
app.displayName.toLowerCase().includes(lowerQuery) ||
|
|
129
|
+
app.packageName.toLowerCase().includes(lowerQuery));
|
|
130
|
+
setAppResults(filteredApps);
|
|
91
131
|
}
|
|
92
132
|
finally {
|
|
93
133
|
setIsSearching(false);
|
|
94
134
|
}
|
|
95
135
|
}, 300); // 300ms debounce
|
|
96
136
|
return () => clearTimeout(timeoutId);
|
|
97
|
-
}, [searchQuery]);
|
|
137
|
+
}, [searchQuery, packages]);
|
|
98
138
|
const handleItemClick = (result, item) => {
|
|
99
139
|
// Try using the config's onClick first
|
|
100
140
|
if (result.config.onClick) {
|
|
@@ -114,7 +154,10 @@ function GlobalSearch() {
|
|
|
114
154
|
}
|
|
115
155
|
}
|
|
116
156
|
};
|
|
117
|
-
const
|
|
157
|
+
const handleAppClick = (app) => {
|
|
158
|
+
(0, tabs_state_1.goToTabPath)(app.path);
|
|
159
|
+
};
|
|
160
|
+
const totalResults = searchResults.reduce((sum, result) => sum + result.items.length, 0) + appResults.length;
|
|
118
161
|
return (react_1.default.createElement("div", { className: "container-fluid h-100 p-4", style: { maxHeight: '100vh', overflowY: 'auto' } },
|
|
119
162
|
react_1.default.createElement("div", { className: "mb-4" },
|
|
120
163
|
react_1.default.createElement("h1", { className: "h3 mb-3 d-flex align-items-center" },
|
|
@@ -129,7 +172,7 @@ function GlobalSearch() {
|
|
|
129
172
|
zIndex: 1,
|
|
130
173
|
fontSize: '18px'
|
|
131
174
|
} }),
|
|
132
|
-
react_1.default.createElement("input", { ref: inputRef, type: "text", className: `form-control form-control-lg ${isDark ? 'bg-dark text-light border-secondary' : ''}`, placeholder: "Search across tools, assistants, workflows, events, and more...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), style: {
|
|
175
|
+
react_1.default.createElement("input", { ref: inputRef, type: "text", className: `form-control form-control-lg ${isDark ? 'bg-dark text-light border-secondary' : ''}`, placeholder: "Search across apps, tools, assistants, workflows, events, and more...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), style: {
|
|
133
176
|
paddingLeft: '50px',
|
|
134
177
|
fontSize: '18px',
|
|
135
178
|
borderRadius: '12px',
|
|
@@ -142,44 +185,75 @@ function GlobalSearch() {
|
|
|
142
185
|
transform: 'translateY(-50%)'
|
|
143
186
|
} },
|
|
144
187
|
react_1.default.createElement("div", { className: "spinner-border spinner-border-sm text-muted" })))),
|
|
145
|
-
searchQuery && (react_1.default.createElement("div", { className: "mt-3 text-muted small" }, isSearching ? ('Searching...') : totalResults > 0 ? (`Found ${totalResults} result${totalResults !== 1 ? 's' : ''}
|
|
146
|
-
searchQuery && !isSearching && (react_1.default.createElement("div", null, searchResults.length === 0 ? (react_1.default.createElement("div", { className: "text-center py-5" },
|
|
188
|
+
searchQuery && (react_1.default.createElement("div", { className: "mt-3 text-muted small" }, isSearching ? ('Searching...') : totalResults > 0 ? (`Found ${totalResults} result${totalResults !== 1 ? 's' : ''}`) : searchQuery.trim() ? ('No results found') : null))),
|
|
189
|
+
searchQuery && !isSearching && (react_1.default.createElement("div", null, searchResults.length === 0 && appResults.length === 0 ? (react_1.default.createElement("div", { className: "text-center py-5" },
|
|
147
190
|
react_1.default.createElement("i", { className: "bi-search mb-3 d-block text-muted", style: { fontSize: '48px' } }),
|
|
148
191
|
react_1.default.createElement("h4", { className: "text-muted" }, "No results found"),
|
|
149
|
-
react_1.default.createElement("p", { className: "text-muted" }, "Try different keywords or check your spelling"))) : (react_1.default.createElement("div", null,
|
|
150
|
-
react_1.default.createElement("div", { className: "
|
|
151
|
-
react_1.default.createElement("
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
react_1.default.createElement("div", { className:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
react_1.default.createElement("div", { className: "
|
|
170
|
-
react_1.default.createElement("
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
react_1.default.createElement("
|
|
177
|
-
|
|
178
|
-
|
|
192
|
+
react_1.default.createElement("p", { className: "text-muted" }, "Try different keywords or check your spelling"))) : (react_1.default.createElement("div", null,
|
|
193
|
+
appResults.length > 0 && (react_1.default.createElement("div", { className: "mb-5" },
|
|
194
|
+
react_1.default.createElement("div", { className: "d-flex align-items-center mb-3" },
|
|
195
|
+
react_1.default.createElement("i", { className: "bi-grid-3x3-gap me-3", style: { fontSize: '20px', color: '#6c757d' } }),
|
|
196
|
+
react_1.default.createElement("h4", { className: "mb-0 me-3" }, "Apps"),
|
|
197
|
+
react_1.default.createElement("span", { className: "badge bg-secondary" }, appResults.length)),
|
|
198
|
+
react_1.default.createElement("div", { className: "row g-3" }, appResults.map((app) => (react_1.default.createElement("div", { key: `${app.packageId}-${app.path}`, className: "col-12 col-md-6 col-lg-4" },
|
|
199
|
+
react_1.default.createElement("div", { className: `card h-100 ${isDark ? 'bg-dark border-secondary' : 'bg-light'}`, style: {
|
|
200
|
+
cursor: 'pointer',
|
|
201
|
+
transition: 'all 0.15s ease',
|
|
202
|
+
borderRadius: '8px'
|
|
203
|
+
}, onClick: () => handleAppClick(app), onMouseEnter: (e) => {
|
|
204
|
+
e.currentTarget.style.transform = 'translateY(-2px)';
|
|
205
|
+
e.currentTarget.style.boxShadow = isDark
|
|
206
|
+
? '0 4px 12px rgba(0,0,0,0.3)'
|
|
207
|
+
: '0 4px 12px rgba(0,0,0,0.1)';
|
|
208
|
+
}, onMouseLeave: (e) => {
|
|
209
|
+
e.currentTarget.style.transform = 'translateY(0)';
|
|
210
|
+
e.currentTarget.style.boxShadow = 'none';
|
|
211
|
+
} },
|
|
212
|
+
react_1.default.createElement("div", { className: "card-body p-3" },
|
|
213
|
+
react_1.default.createElement("div", { className: "d-flex align-items-start" },
|
|
214
|
+
react_1.default.createElement("i", { className: `${app.iconClassName} me-3 mt-1`, style: {
|
|
215
|
+
fontSize: '16px',
|
|
216
|
+
color: isDark ? '#0d6efd' : '#0d6efd',
|
|
217
|
+
minWidth: '16px'
|
|
218
|
+
} }),
|
|
219
|
+
react_1.default.createElement("div", { className: "flex-grow-1" },
|
|
220
|
+
react_1.default.createElement("h6", { className: "card-title mb-1 fw-medium" }, app.displayName),
|
|
221
|
+
react_1.default.createElement("small", { className: "text-muted text-uppercase", style: { fontSize: '11px', letterSpacing: '0.5px' } }, app.packageId === 'system-apps' ? 'System App' : app.packageName)),
|
|
222
|
+
react_1.default.createElement("i", { className: "bi-arrow-right text-muted", style: { fontSize: '12px' } })))))))))),
|
|
223
|
+
searchResults.map((result) => (react_1.default.createElement("div", { key: result.category, className: "mb-5" },
|
|
224
|
+
react_1.default.createElement("div", { className: "d-flex align-items-center mb-3" },
|
|
225
|
+
react_1.default.createElement("i", { className: `${result.config.iconClass} me-3`, style: { fontSize: '20px', color: '#6c757d' } }),
|
|
226
|
+
react_1.default.createElement("h4", { className: "mb-0 me-3" }, result.category),
|
|
227
|
+
react_1.default.createElement("span", { className: "badge bg-secondary" }, result.items.length)),
|
|
228
|
+
react_1.default.createElement("div", { className: "row g-3" }, result.items.map((item) => (react_1.default.createElement("div", { key: item.id, className: "col-12 col-md-6 col-lg-4" },
|
|
229
|
+
react_1.default.createElement("div", { className: `card h-100 ${isDark ? 'bg-dark border-secondary' : 'bg-light'}`, style: {
|
|
230
|
+
cursor: 'pointer',
|
|
231
|
+
transition: 'all 0.15s ease',
|
|
232
|
+
borderRadius: '8px'
|
|
233
|
+
}, onClick: () => handleItemClick(result, item), onMouseEnter: (e) => {
|
|
234
|
+
e.currentTarget.style.transform = 'translateY(-2px)';
|
|
235
|
+
e.currentTarget.style.boxShadow = isDark
|
|
236
|
+
? '0 4px 12px rgba(0,0,0,0.3)'
|
|
237
|
+
: '0 4px 12px rgba(0,0,0,0.1)';
|
|
238
|
+
}, onMouseLeave: (e) => {
|
|
239
|
+
e.currentTarget.style.transform = 'translateY(0)';
|
|
240
|
+
e.currentTarget.style.boxShadow = 'none';
|
|
241
|
+
} },
|
|
242
|
+
react_1.default.createElement("div", { className: "card-body p-3" },
|
|
243
|
+
react_1.default.createElement("div", { className: "d-flex align-items-start" },
|
|
244
|
+
react_1.default.createElement("i", { className: `${result.config.iconClass} me-3 mt-1`, style: {
|
|
245
|
+
fontSize: '16px',
|
|
246
|
+
color: isDark ? '#0d6efd' : '#0d6efd',
|
|
247
|
+
minWidth: '16px'
|
|
248
|
+
} }),
|
|
249
|
+
react_1.default.createElement("div", { className: "flex-grow-1" },
|
|
250
|
+
react_1.default.createElement("h6", { className: "card-title mb-1 fw-medium" }, item.name),
|
|
251
|
+
react_1.default.createElement("small", { className: "text-muted text-uppercase", style: { fontSize: '11px', letterSpacing: '0.5px' } }, result.category.slice(0, -1))),
|
|
252
|
+
react_1.default.createElement("i", { className: "bi-arrow-right text-muted", style: { fontSize: '12px' } }))))))))))))))),
|
|
179
253
|
!searchQuery && (react_1.default.createElement("div", { className: "text-center py-5" },
|
|
180
254
|
react_1.default.createElement("i", { className: "bi-search mb-3 d-block text-muted", style: { fontSize: '64px' } }),
|
|
181
255
|
react_1.default.createElement("h3", { className: "text-muted mb-3" }, "Search across everything"),
|
|
182
|
-
react_1.default.createElement("p", { className: "text-muted mb-4", style: { maxWidth: '400px', margin: '0 auto' } }, "Find tools, assistants, workflows, events, predicates, types, and users all in one place."),
|
|
256
|
+
react_1.default.createElement("p", { className: "text-muted mb-4", style: { maxWidth: '400px', margin: '0 auto' } }, "Find apps, tools, assistants, workflows, events, predicates, types, and users all in one place."),
|
|
183
257
|
react_1.default.createElement("div", { className: "d-flex flex-wrap justify-content-center gap-2" }, searchConfigs.map(({ config, category }) => (react_1.default.createElement("span", { key: category, className: `badge ${isDark ? 'bg-secondary' : 'bg-light text-dark'} px-3 py-2 d-flex align-items-center`, style: { fontSize: '12px' } },
|
|
184
258
|
react_1.default.createElement("i", { className: `${config.iconClass} me-2` }),
|
|
185
259
|
category))))))));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peers-app/peers-ui",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.40",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/peers-app/peers-ui.git"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"bootstrap": "^5.3.3",
|
|
30
|
-
"@peers-app/peers-sdk": "^0.7.
|
|
30
|
+
"@peers-app/peers-sdk": "^0.7.40",
|
|
31
31
|
"react": "^18.0.0",
|
|
32
32
|
"react-dom": "^18.0.0"
|
|
33
33
|
},
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"jest": "^29.7.0",
|
|
58
58
|
"jest-environment-jsdom": "^30.0.5",
|
|
59
59
|
"path-browserify": "^1.0.1",
|
|
60
|
-
"@peers-app/peers-sdk": "0.7.
|
|
60
|
+
"@peers-app/peers-sdk": "0.7.40",
|
|
61
61
|
"react": "^18.0.0",
|
|
62
62
|
"react-dom": "^18.0.0",
|
|
63
63
|
"string-width": "^7.1.0",
|
|
@@ -9,11 +9,27 @@ import {
|
|
|
9
9
|
isCommandPaletteOpen,
|
|
10
10
|
searchCommands
|
|
11
11
|
} from './command-palette';
|
|
12
|
+
import { allPackages } from '../ui-router/routes-loader';
|
|
13
|
+
import { systemPackage } from '../system-apps';
|
|
14
|
+
import { goToTabPath } from '../tabs-layout/tabs-state';
|
|
15
|
+
import { IAppNav, Messages, newid, getMe } from "@peers-app/peers-sdk";
|
|
16
|
+
import { openThreadInTab } from '../globals';
|
|
17
|
+
|
|
18
|
+
interface AppSearchItem {
|
|
19
|
+
packageId: string;
|
|
20
|
+
packageName: string;
|
|
21
|
+
navItem: IAppNav;
|
|
22
|
+
path: string;
|
|
23
|
+
name: string;
|
|
24
|
+
displayName: string;
|
|
25
|
+
iconClassName: string;
|
|
26
|
+
}
|
|
12
27
|
|
|
13
28
|
export function CommandPaletteOverlay() {
|
|
14
29
|
const [isOpen] = useObservable(isCommandPaletteOpen);
|
|
15
30
|
const [_persistedQuery] = useObservable(commandSearchQuery);
|
|
16
31
|
const [_colorMode] = useObservable(colorMode);
|
|
32
|
+
const [packages] = useObservable(allPackages);
|
|
17
33
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
18
34
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
19
35
|
|
|
@@ -53,7 +69,78 @@ export function CommandPaletteOverlay() {
|
|
|
53
69
|
const searchQuery = localQuery;
|
|
54
70
|
const filteredCommands = searchCommands(searchQuery);
|
|
55
71
|
|
|
72
|
+
// Get all apps (system and user)
|
|
73
|
+
const getAllApps = (): AppSearchItem[] => {
|
|
74
|
+
const allPackages_ = [...packages, systemPackage];
|
|
75
|
+
return allPackages_
|
|
76
|
+
.filter(p => !p.disabled && p.appNavs && p.appNavs.length > 0)
|
|
77
|
+
.flatMap(pkg =>
|
|
78
|
+
pkg.appNavs!.map(navItem => {
|
|
79
|
+
// Construct path - use direct path for system apps, package-nav for others
|
|
80
|
+
let path: string;
|
|
81
|
+
if (pkg.packageId === 'system-apps') {
|
|
82
|
+
path = navItem.navigationPath ?? navItem.name.replace(/\s/g, '-').toLowerCase();
|
|
83
|
+
} else {
|
|
84
|
+
path = `package-nav/${pkg.packageId}/${(navItem.navigationPath ?? navItem.name).replace(/[^a-zA-Z0-9]/g, '-').toLowerCase()}`;
|
|
85
|
+
while (path.includes('//')) {
|
|
86
|
+
path = path.replace('//', '/');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
packageId: pkg.packageId,
|
|
92
|
+
packageName: pkg.name,
|
|
93
|
+
navItem,
|
|
94
|
+
path,
|
|
95
|
+
name: navItem.name,
|
|
96
|
+
displayName: navItem.displayName || navItem.name,
|
|
97
|
+
iconClassName: navItem.iconClassName || 'bi-box-seam'
|
|
98
|
+
};
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Search apps
|
|
104
|
+
const searchApps = (query: string): AppSearchItem[] => {
|
|
105
|
+
if (!query.trim()) return [];
|
|
106
|
+
const allApps = getAllApps();
|
|
107
|
+
const lowerQuery = query.toLowerCase();
|
|
108
|
+
return allApps.filter(app =>
|
|
109
|
+
app.name.toLowerCase().includes(lowerQuery) ||
|
|
110
|
+
app.displayName.toLowerCase().includes(lowerQuery) ||
|
|
111
|
+
app.packageName.toLowerCase().includes(lowerQuery)
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const filteredApps = searchApps(searchQuery);
|
|
116
|
+
|
|
117
|
+
// Function to create a new thread from search query
|
|
118
|
+
const createNewThreadFromQuery = async (query: string) => {
|
|
119
|
+
try {
|
|
120
|
+
const currentUser = await getMe();
|
|
121
|
+
const threadMessage = await Messages().insert({
|
|
122
|
+
messageId: newid(),
|
|
123
|
+
userId: currentUser.userId,
|
|
124
|
+
channelId: currentUser.userId,
|
|
125
|
+
message: query.trim(),
|
|
126
|
+
createdAt: new Date(),
|
|
127
|
+
});
|
|
128
|
+
await openThreadInTab(threadMessage);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error('Failed to create new thread:', error);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
56
134
|
// Create a flattened list that matches the visual rendering order
|
|
135
|
+
// Apps first, then commands
|
|
136
|
+
const allItems: Array<{ type: 'app' | 'command'; app?: AppSearchItem; command?: Command; id: string }> = [];
|
|
137
|
+
|
|
138
|
+
// Add apps first
|
|
139
|
+
filteredApps.forEach((app) => {
|
|
140
|
+
allItems.push({ type: 'app', app, id: `app-${app.packageId}-${app.path}` });
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Then add commands
|
|
57
144
|
const visualOrderCommands = Object.entries(
|
|
58
145
|
filteredCommands.reduce((acc, cmd) => {
|
|
59
146
|
const category = cmd.category || 'Other';
|
|
@@ -63,6 +150,10 @@ export function CommandPaletteOverlay() {
|
|
|
63
150
|
}, {} as Record<string, Command[]>)
|
|
64
151
|
).flatMap(([, commands]) => commands);
|
|
65
152
|
|
|
153
|
+
visualOrderCommands.forEach((cmd) => {
|
|
154
|
+
allItems.push({ type: 'command', command: cmd, id: `command-${cmd.id}` });
|
|
155
|
+
});
|
|
156
|
+
|
|
66
157
|
// Focus input when opened
|
|
67
158
|
useEffect(() => {
|
|
68
159
|
if (isOpen && inputRef.current) {
|
|
@@ -82,21 +173,40 @@ export function CommandPaletteOverlay() {
|
|
|
82
173
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
83
174
|
if (e.key === 'ArrowDown') {
|
|
84
175
|
e.preventDefault();
|
|
85
|
-
|
|
176
|
+
// Allow selection to go one past the end if there's a search query (for new thread option)
|
|
177
|
+
const maxIndex = searchQuery.trim() ? allItems.length : allItems.length - 1;
|
|
178
|
+
setSelectedIndex(prev => Math.min(prev + 1, maxIndex));
|
|
86
179
|
} else if (e.key === 'ArrowUp') {
|
|
87
180
|
e.preventDefault();
|
|
88
181
|
setSelectedIndex(prev => Math.max(prev - 1, 0));
|
|
89
182
|
} else if (e.key === 'Enter') {
|
|
90
183
|
e.preventDefault();
|
|
91
|
-
if (
|
|
92
|
-
|
|
184
|
+
// Check if the new thread option is selected (selectedIndex >= allItems.length)
|
|
185
|
+
if (selectedIndex >= allItems.length && searchQuery.trim()) {
|
|
186
|
+
closeCommandPalette();
|
|
187
|
+
createNewThreadFromQuery(searchQuery);
|
|
188
|
+
} else {
|
|
189
|
+
const selectedItem = allItems[selectedIndex];
|
|
190
|
+
if (selectedItem) {
|
|
191
|
+
if (selectedItem.type === 'app' && selectedItem.app) {
|
|
192
|
+
closeCommandPalette();
|
|
193
|
+
goToTabPath(selectedItem.app.path);
|
|
194
|
+
} else if (selectedItem.type === 'command' && selectedItem.command) {
|
|
195
|
+
executeCommand(selectedItem.command.id);
|
|
196
|
+
}
|
|
197
|
+
} else if (searchQuery.trim()) {
|
|
198
|
+
// Fallback: Create a new thread with the search query as the message
|
|
199
|
+
// This happens when no item is selected (selectedIndex is out of bounds or -1)
|
|
200
|
+
closeCommandPalette();
|
|
201
|
+
createNewThreadFromQuery(searchQuery);
|
|
202
|
+
}
|
|
93
203
|
}
|
|
94
204
|
}
|
|
95
205
|
};
|
|
96
206
|
|
|
97
207
|
document.addEventListener('keydown', handleKeyDown);
|
|
98
208
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
99
|
-
}, [isOpen,
|
|
209
|
+
}, [isOpen, allItems, selectedIndex]);
|
|
100
210
|
|
|
101
211
|
if (!isOpen) return null;
|
|
102
212
|
|
|
@@ -151,7 +261,7 @@ export function CommandPaletteOverlay() {
|
|
|
151
261
|
ref={inputRef}
|
|
152
262
|
type="text"
|
|
153
263
|
className={`form-control ${isDark ? 'bg-dark text-light border-secondary' : ''}`}
|
|
154
|
-
placeholder="
|
|
264
|
+
placeholder="Search apps, commands, or navigate..."
|
|
155
265
|
value={searchQuery}
|
|
156
266
|
onChange={(e) => {
|
|
157
267
|
const newQuery = e.target.value;
|
|
@@ -178,22 +288,85 @@ export function CommandPaletteOverlay() {
|
|
|
178
288
|
</div>
|
|
179
289
|
</div>
|
|
180
290
|
|
|
181
|
-
{/* Commands List */}
|
|
291
|
+
{/* Commands and Apps List */}
|
|
182
292
|
<div
|
|
183
293
|
style={{
|
|
184
294
|
maxHeight: '400px',
|
|
185
295
|
overflowY: 'auto'
|
|
186
296
|
}}
|
|
187
297
|
>
|
|
188
|
-
{
|
|
298
|
+
{allItems.length === 0 && !searchQuery.trim() ? (
|
|
189
299
|
<div className="p-4 text-center text-muted">
|
|
190
300
|
<i className="bi-search mb-2 d-block" style={{ fontSize: '24px' }} />
|
|
191
|
-
No
|
|
301
|
+
No results found
|
|
192
302
|
</div>
|
|
193
303
|
) : (
|
|
194
304
|
<div className="py-2">
|
|
195
|
-
{/*
|
|
196
|
-
{
|
|
305
|
+
{/* Show "No results" message if there's a query but no results yet */}
|
|
306
|
+
{allItems.length === 0 && searchQuery.trim() && (
|
|
307
|
+
<div className="px-3 py-2 text-muted small text-center">
|
|
308
|
+
No matching results
|
|
309
|
+
</div>
|
|
310
|
+
)}
|
|
311
|
+
{/* Apps Section */}
|
|
312
|
+
{filteredApps.length > 0 && (
|
|
313
|
+
<div>
|
|
314
|
+
<div
|
|
315
|
+
className="px-3 py-1 small text-muted fw-bold text-uppercase"
|
|
316
|
+
style={{ fontSize: '11px', letterSpacing: '0.5px' }}
|
|
317
|
+
>
|
|
318
|
+
Apps
|
|
319
|
+
</div>
|
|
320
|
+
{filteredApps.map((app) => {
|
|
321
|
+
const appId = `app-${app.packageId}-${app.path}`;
|
|
322
|
+
const globalIndex = allItems.findIndex(item => item.id === appId);
|
|
323
|
+
const isSelected = globalIndex === selectedIndex;
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<div
|
|
327
|
+
key={`${app.packageId}-${app.path}`}
|
|
328
|
+
className={`px-3 py-2 d-flex align-items-center justify-content-between ${
|
|
329
|
+
isSelected
|
|
330
|
+
? (isDark ? 'bg-primary bg-opacity-25' : 'bg-primary bg-opacity-10')
|
|
331
|
+
: ''
|
|
332
|
+
}`}
|
|
333
|
+
style={{
|
|
334
|
+
cursor: 'pointer',
|
|
335
|
+
transition: 'background-color 0.1s ease'
|
|
336
|
+
}}
|
|
337
|
+
onClick={() => {
|
|
338
|
+
closeCommandPalette();
|
|
339
|
+
goToTabPath(app.path);
|
|
340
|
+
}}
|
|
341
|
+
onMouseEnter={() => setSelectedIndex(globalIndex)}
|
|
342
|
+
>
|
|
343
|
+
<div className="d-flex align-items-center">
|
|
344
|
+
<i
|
|
345
|
+
className={`${app.iconClassName} me-3`}
|
|
346
|
+
style={{
|
|
347
|
+
fontSize: '16px',
|
|
348
|
+
color: isSelected ? (isDark ? '#ffffff' : '#0d6efd') : '#6c757d',
|
|
349
|
+
minWidth: '16px'
|
|
350
|
+
}}
|
|
351
|
+
/>
|
|
352
|
+
<div>
|
|
353
|
+
<div className="fw-medium">{app.displayName}</div>
|
|
354
|
+
<div
|
|
355
|
+
className="small text-muted"
|
|
356
|
+
style={{ fontSize: '12px' }}
|
|
357
|
+
>
|
|
358
|
+
{app.packageId === 'system-apps' ? 'System App' : app.packageName}
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
);
|
|
364
|
+
})}
|
|
365
|
+
</div>
|
|
366
|
+
)}
|
|
367
|
+
|
|
368
|
+
{/* Commands Section - Group commands by category */}
|
|
369
|
+
{filteredCommands.length > 0 && Object.entries(
|
|
197
370
|
filteredCommands.reduce((acc, cmd) => {
|
|
198
371
|
const category = cmd.category || 'Other';
|
|
199
372
|
if (!acc[category]) acc[category] = [];
|
|
@@ -212,7 +385,8 @@ export function CommandPaletteOverlay() {
|
|
|
212
385
|
|
|
213
386
|
{/* Commands in Category */}
|
|
214
387
|
{commands.map((command) => {
|
|
215
|
-
const
|
|
388
|
+
const commandId = `command-${command.id}`;
|
|
389
|
+
const globalIndex = allItems.findIndex(item => item.id === commandId);
|
|
216
390
|
const isSelected = globalIndex === selectedIndex;
|
|
217
391
|
|
|
218
392
|
return (
|
|
@@ -274,6 +448,64 @@ export function CommandPaletteOverlay() {
|
|
|
274
448
|
})}
|
|
275
449
|
</div>
|
|
276
450
|
))}
|
|
451
|
+
|
|
452
|
+
{/* New Thread Indicator - Show when there's a search query */}
|
|
453
|
+
{searchQuery.trim() && (
|
|
454
|
+
<div>
|
|
455
|
+
<div
|
|
456
|
+
className="px-3 py-1 small text-muted fw-bold text-uppercase"
|
|
457
|
+
style={{ fontSize: '11px', letterSpacing: '0.5px' }}
|
|
458
|
+
>
|
|
459
|
+
Actions
|
|
460
|
+
</div>
|
|
461
|
+
<div
|
|
462
|
+
className={`px-3 py-2 d-flex align-items-center ${
|
|
463
|
+
(allItems.length === 0 || selectedIndex >= allItems.length) && searchQuery.trim()
|
|
464
|
+
? (isDark ? 'bg-primary bg-opacity-25' : 'bg-primary bg-opacity-10')
|
|
465
|
+
: ''
|
|
466
|
+
}`}
|
|
467
|
+
style={{
|
|
468
|
+
cursor: 'pointer',
|
|
469
|
+
transition: 'background-color 0.1s ease',
|
|
470
|
+
borderTop: allItems.length > 0 ? `1px solid ${isDark ? '#495057' : '#dee2e6'}` : 'none',
|
|
471
|
+
marginTop: allItems.length > 0 ? '8px' : '0',
|
|
472
|
+
paddingTop: allItems.length > 0 ? '12px' : '8px'
|
|
473
|
+
}}
|
|
474
|
+
onClick={() => {
|
|
475
|
+
closeCommandPalette();
|
|
476
|
+
createNewThreadFromQuery(searchQuery);
|
|
477
|
+
}}
|
|
478
|
+
onMouseEnter={() => {
|
|
479
|
+
// Set selected index beyond the list to indicate this item is selected
|
|
480
|
+
setSelectedIndex(allItems.length);
|
|
481
|
+
}}
|
|
482
|
+
>
|
|
483
|
+
<div className="d-flex align-items-center">
|
|
484
|
+
<i
|
|
485
|
+
className="bi-chat-dots me-3"
|
|
486
|
+
style={{
|
|
487
|
+
fontSize: '16px',
|
|
488
|
+
color: (allItems.length === 0 || selectedIndex >= allItems.length) && searchQuery.trim()
|
|
489
|
+
? (isDark ? '#ffffff' : '#0d6efd')
|
|
490
|
+
: '#6c757d',
|
|
491
|
+
minWidth: '16px'
|
|
492
|
+
}}
|
|
493
|
+
/>
|
|
494
|
+
<div>
|
|
495
|
+
<div className="fw-medium">
|
|
496
|
+
Start new thread: <span className="text-muted">{searchQuery.trim().slice(0, 50)}{searchQuery.trim().length > 50 ? '...' : ''}</span>
|
|
497
|
+
</div>
|
|
498
|
+
<div
|
|
499
|
+
className="small text-muted"
|
|
500
|
+
style={{ fontSize: '12px' }}
|
|
501
|
+
>
|
|
502
|
+
Press Enter to create
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
|
+
</div>
|
|
506
|
+
</div>
|
|
507
|
+
</div>
|
|
508
|
+
)}
|
|
277
509
|
</div>
|
|
278
510
|
)}
|
|
279
511
|
</div>
|
|
@@ -298,7 +530,8 @@ export function CommandPaletteOverlay() {
|
|
|
298
530
|
</span>
|
|
299
531
|
</div>
|
|
300
532
|
<div>
|
|
301
|
-
{
|
|
533
|
+
{allItems.length} result{allItems.length !== 1 ? 's' : ''}
|
|
534
|
+
{searchQuery.trim() && allItems.length === 0 && ' • Start thread'}
|
|
302
535
|
</div>
|
|
303
536
|
</div>
|
|
304
537
|
</div>
|