@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.
@@ -38,10 +38,16 @@ const react_1 = __importStar(require("react"));
38
38
  const hooks_1 = require("../hooks");
39
39
  const color_mode_dropdown_1 = require("../screens/settings/color-mode-dropdown");
40
40
  const command_palette_1 = require("./command-palette");
41
+ const routes_loader_1 = require("../ui-router/routes-loader");
42
+ const system_apps_1 = require("../system-apps");
43
+ const tabs_state_1 = require("../tabs-layout/tabs-state");
44
+ const peers_sdk_1 = require("@peers-app/peers-sdk");
45
+ const globals_1 = require("../globals");
41
46
  function CommandPaletteOverlay() {
42
47
  const [isOpen] = (0, hooks_1.useObservable)(command_palette_1.isCommandPaletteOpen);
43
48
  const [_persistedQuery] = (0, hooks_1.useObservable)(command_palette_1.commandSearchQuery);
44
49
  const [_colorMode] = (0, hooks_1.useObservable)(color_mode_dropdown_1.colorMode);
50
+ const [packages] = (0, hooks_1.useObservable)(routes_loader_1.allPackages);
45
51
  const [selectedIndex, setSelectedIndex] = (0, react_1.useState)(0);
46
52
  const inputRef = (0, react_1.useRef)(null);
47
53
  // Local state for query input to avoid locking up on fast typing
@@ -75,7 +81,70 @@ function CommandPaletteOverlay() {
75
81
  }, []);
76
82
  const searchQuery = localQuery;
77
83
  const filteredCommands = (0, command_palette_1.searchCommands)(searchQuery);
84
+ // Get all apps (system and user)
85
+ const getAllApps = () => {
86
+ const allPackages_ = [...packages, system_apps_1.systemPackage];
87
+ return allPackages_
88
+ .filter(p => !p.disabled && p.appNavs && p.appNavs.length > 0)
89
+ .flatMap(pkg => pkg.appNavs.map(navItem => {
90
+ // Construct path - use direct path for system apps, package-nav for others
91
+ let path;
92
+ if (pkg.packageId === 'system-apps') {
93
+ path = navItem.navigationPath ?? navItem.name.replace(/\s/g, '-').toLowerCase();
94
+ }
95
+ else {
96
+ path = `package-nav/${pkg.packageId}/${(navItem.navigationPath ?? navItem.name).replace(/[^a-zA-Z0-9]/g, '-').toLowerCase()}`;
97
+ while (path.includes('//')) {
98
+ path = path.replace('//', '/');
99
+ }
100
+ }
101
+ return {
102
+ packageId: pkg.packageId,
103
+ packageName: pkg.name,
104
+ navItem,
105
+ path,
106
+ name: navItem.name,
107
+ displayName: navItem.displayName || navItem.name,
108
+ iconClassName: navItem.iconClassName || 'bi-box-seam'
109
+ };
110
+ }));
111
+ };
112
+ // Search apps
113
+ const searchApps = (query) => {
114
+ if (!query.trim())
115
+ return [];
116
+ const allApps = getAllApps();
117
+ const lowerQuery = query.toLowerCase();
118
+ return allApps.filter(app => app.name.toLowerCase().includes(lowerQuery) ||
119
+ app.displayName.toLowerCase().includes(lowerQuery) ||
120
+ app.packageName.toLowerCase().includes(lowerQuery));
121
+ };
122
+ const filteredApps = searchApps(searchQuery);
123
+ // Function to create a new thread from search query
124
+ const createNewThreadFromQuery = async (query) => {
125
+ try {
126
+ const currentUser = await (0, peers_sdk_1.getMe)();
127
+ const threadMessage = await (0, peers_sdk_1.Messages)().insert({
128
+ messageId: (0, peers_sdk_1.newid)(),
129
+ userId: currentUser.userId,
130
+ channelId: currentUser.userId,
131
+ message: query.trim(),
132
+ createdAt: new Date(),
133
+ });
134
+ await (0, globals_1.openThreadInTab)(threadMessage);
135
+ }
136
+ catch (error) {
137
+ console.error('Failed to create new thread:', error);
138
+ }
139
+ };
78
140
  // Create a flattened list that matches the visual rendering order
141
+ // Apps first, then commands
142
+ const allItems = [];
143
+ // Add apps first
144
+ filteredApps.forEach((app) => {
145
+ allItems.push({ type: 'app', app, id: `app-${app.packageId}-${app.path}` });
146
+ });
147
+ // Then add commands
79
148
  const visualOrderCommands = Object.entries(filteredCommands.reduce((acc, cmd) => {
80
149
  const category = cmd.category || 'Other';
81
150
  if (!acc[category])
@@ -83,6 +152,9 @@ function CommandPaletteOverlay() {
83
152
  acc[category].push(cmd);
84
153
  return acc;
85
154
  }, {})).flatMap(([, commands]) => commands);
155
+ visualOrderCommands.forEach((cmd) => {
156
+ allItems.push({ type: 'command', command: cmd, id: `command-${cmd.id}` });
157
+ });
86
158
  // Focus input when opened
87
159
  (0, react_1.useEffect)(() => {
88
160
  if (isOpen && inputRef.current) {
@@ -100,7 +172,9 @@ function CommandPaletteOverlay() {
100
172
  const handleKeyDown = (e) => {
101
173
  if (e.key === 'ArrowDown') {
102
174
  e.preventDefault();
103
- setSelectedIndex(prev => Math.min(prev + 1, visualOrderCommands.length - 1));
175
+ // Allow selection to go one past the end if there's a search query (for new thread option)
176
+ const maxIndex = searchQuery.trim() ? allItems.length : allItems.length - 1;
177
+ setSelectedIndex(prev => Math.min(prev + 1, maxIndex));
104
178
  }
105
179
  else if (e.key === 'ArrowUp') {
106
180
  e.preventDefault();
@@ -108,14 +182,34 @@ function CommandPaletteOverlay() {
108
182
  }
109
183
  else if (e.key === 'Enter') {
110
184
  e.preventDefault();
111
- if (visualOrderCommands[selectedIndex]) {
112
- (0, command_palette_1.executeCommand)(visualOrderCommands[selectedIndex].id);
185
+ // Check if the new thread option is selected (selectedIndex >= allItems.length)
186
+ if (selectedIndex >= allItems.length && searchQuery.trim()) {
187
+ (0, command_palette_1.closeCommandPalette)();
188
+ createNewThreadFromQuery(searchQuery);
189
+ }
190
+ else {
191
+ const selectedItem = allItems[selectedIndex];
192
+ if (selectedItem) {
193
+ if (selectedItem.type === 'app' && selectedItem.app) {
194
+ (0, command_palette_1.closeCommandPalette)();
195
+ (0, tabs_state_1.goToTabPath)(selectedItem.app.path);
196
+ }
197
+ else if (selectedItem.type === 'command' && selectedItem.command) {
198
+ (0, command_palette_1.executeCommand)(selectedItem.command.id);
199
+ }
200
+ }
201
+ else if (searchQuery.trim()) {
202
+ // Fallback: Create a new thread with the search query as the message
203
+ // This happens when no item is selected (selectedIndex is out of bounds or -1)
204
+ (0, command_palette_1.closeCommandPalette)();
205
+ createNewThreadFromQuery(searchQuery);
206
+ }
113
207
  }
114
208
  }
115
209
  };
116
210
  document.addEventListener('keydown', handleKeyDown);
117
211
  return () => document.removeEventListener('keydown', handleKeyDown);
118
- }, [isOpen, visualOrderCommands, selectedIndex]);
212
+ }, [isOpen, allItems, selectedIndex]);
119
213
  if (!isOpen)
120
214
  return null;
121
215
  const isDark = _colorMode === 'dark';
@@ -152,7 +246,7 @@ function CommandPaletteOverlay() {
152
246
  color: '#6c757d',
153
247
  zIndex: 1
154
248
  } }),
155
- react_1.default.createElement("input", { ref: inputRef, type: "text", className: `form-control ${isDark ? 'bg-dark text-light border-secondary' : ''}`, placeholder: "Type a command or search...", value: searchQuery, onChange: (e) => {
249
+ react_1.default.createElement("input", { ref: inputRef, type: "text", className: `form-control ${isDark ? 'bg-dark text-light border-secondary' : ''}`, placeholder: "Search apps, commands, or navigate...", value: searchQuery, onChange: (e) => {
156
250
  const newQuery = e.target.value;
157
251
  setLocalQuery(newQuery);
158
252
  updatePersistedQuery(newQuery);
@@ -174,41 +268,101 @@ function CommandPaletteOverlay() {
174
268
  react_1.default.createElement("div", { style: {
175
269
  maxHeight: '400px',
176
270
  overflowY: 'auto'
177
- } }, visualOrderCommands.length === 0 ? (react_1.default.createElement("div", { className: "p-4 text-center text-muted" },
271
+ } }, allItems.length === 0 && !searchQuery.trim() ? (react_1.default.createElement("div", { className: "p-4 text-center text-muted" },
178
272
  react_1.default.createElement("i", { className: "bi-search mb-2 d-block", style: { fontSize: '24px' } }),
179
- "No commands found")) : (react_1.default.createElement("div", { className: "py-2" }, Object.entries(filteredCommands.reduce((acc, cmd) => {
180
- const category = cmd.category || 'Other';
181
- if (!acc[category])
182
- acc[category] = [];
183
- acc[category].push(cmd);
184
- return acc;
185
- }, {})).map(([category, commands]) => (react_1.default.createElement("div", { key: category },
186
- react_1.default.createElement("div", { className: "px-3 py-1 small text-muted fw-bold text-uppercase", style: { fontSize: '11px', letterSpacing: '0.5px' } }, category),
187
- commands.map((command) => {
188
- const globalIndex = visualOrderCommands.indexOf(command);
189
- const isSelected = globalIndex === selectedIndex;
190
- return (react_1.default.createElement("div", { key: command.id, className: `px-3 py-2 d-flex align-items-center justify-content-between ${isSelected
273
+ "No results found")) : (react_1.default.createElement("div", { className: "py-2" },
274
+ allItems.length === 0 && searchQuery.trim() && (react_1.default.createElement("div", { className: "px-3 py-2 text-muted small text-center" }, "No matching results")),
275
+ filteredApps.length > 0 && (react_1.default.createElement("div", null,
276
+ react_1.default.createElement("div", { className: "px-3 py-1 small text-muted fw-bold text-uppercase", style: { fontSize: '11px', letterSpacing: '0.5px' } }, "Apps"),
277
+ filteredApps.map((app) => {
278
+ const appId = `app-${app.packageId}-${app.path}`;
279
+ const globalIndex = allItems.findIndex(item => item.id === appId);
280
+ const isSelected = globalIndex === selectedIndex;
281
+ return (react_1.default.createElement("div", { key: `${app.packageId}-${app.path}`, className: `px-3 py-2 d-flex align-items-center justify-content-between ${isSelected
282
+ ? (isDark ? 'bg-primary bg-opacity-25' : 'bg-primary bg-opacity-10')
283
+ : ''}`, style: {
284
+ cursor: 'pointer',
285
+ transition: 'background-color 0.1s ease'
286
+ }, onClick: () => {
287
+ (0, command_palette_1.closeCommandPalette)();
288
+ (0, tabs_state_1.goToTabPath)(app.path);
289
+ }, onMouseEnter: () => setSelectedIndex(globalIndex) },
290
+ react_1.default.createElement("div", { className: "d-flex align-items-center" },
291
+ react_1.default.createElement("i", { className: `${app.iconClassName} me-3`, style: {
292
+ fontSize: '16px',
293
+ color: isSelected ? (isDark ? '#ffffff' : '#0d6efd') : '#6c757d',
294
+ minWidth: '16px'
295
+ } }),
296
+ react_1.default.createElement("div", null,
297
+ react_1.default.createElement("div", { className: "fw-medium" }, app.displayName),
298
+ react_1.default.createElement("div", { className: "small text-muted", style: { fontSize: '12px' } }, app.packageId === 'system-apps' ? 'System App' : app.packageName)))));
299
+ }))),
300
+ filteredCommands.length > 0 && Object.entries(filteredCommands.reduce((acc, cmd) => {
301
+ const category = cmd.category || 'Other';
302
+ if (!acc[category])
303
+ acc[category] = [];
304
+ acc[category].push(cmd);
305
+ return acc;
306
+ }, {})).map(([category, commands]) => (react_1.default.createElement("div", { key: category },
307
+ react_1.default.createElement("div", { className: "px-3 py-1 small text-muted fw-bold text-uppercase", style: { fontSize: '11px', letterSpacing: '0.5px' } }, category),
308
+ commands.map((command) => {
309
+ const commandId = `command-${command.id}`;
310
+ const globalIndex = allItems.findIndex(item => item.id === commandId);
311
+ const isSelected = globalIndex === selectedIndex;
312
+ return (react_1.default.createElement("div", { key: command.id, className: `px-3 py-2 d-flex align-items-center justify-content-between ${isSelected
313
+ ? (isDark ? 'bg-primary bg-opacity-25' : 'bg-primary bg-opacity-10')
314
+ : ''}`, style: {
315
+ cursor: 'pointer',
316
+ transition: 'background-color 0.1s ease'
317
+ }, onClick: () => (0, command_palette_1.executeCommand)(command.id), onMouseEnter: () => setSelectedIndex(globalIndex) },
318
+ react_1.default.createElement("div", { className: "d-flex align-items-center" },
319
+ command.iconClassName && (react_1.default.createElement("i", { className: `${command.iconClassName} me-3`, style: {
320
+ fontSize: '16px',
321
+ color: isSelected ? (isDark ? '#ffffff' : '#0d6efd') : '#6c757d',
322
+ minWidth: '16px'
323
+ } })),
324
+ react_1.default.createElement("div", null,
325
+ react_1.default.createElement("div", { className: "fw-medium" }, command.label),
326
+ command.description && (react_1.default.createElement("div", { className: "small text-muted", style: { fontSize: '12px' } }, command.description)))),
327
+ command.shortcut && (react_1.default.createElement("div", { className: `small px-2 py-1 rounded ${isDark ? 'bg-secondary bg-opacity-50' : 'bg-light'}`, style: {
328
+ fontSize: '11px',
329
+ fontFamily: 'monospace',
330
+ color: isDark ? '#adb5bd' : '#6c757d',
331
+ border: isDark ? '1px solid #495057' : '1px solid #dee2e6'
332
+ } }, command.shortcut))));
333
+ })))),
334
+ searchQuery.trim() && (react_1.default.createElement("div", null,
335
+ react_1.default.createElement("div", { className: "px-3 py-1 small text-muted fw-bold text-uppercase", style: { fontSize: '11px', letterSpacing: '0.5px' } }, "Actions"),
336
+ react_1.default.createElement("div", { className: `px-3 py-2 d-flex align-items-center ${(allItems.length === 0 || selectedIndex >= allItems.length) && searchQuery.trim()
191
337
  ? (isDark ? 'bg-primary bg-opacity-25' : 'bg-primary bg-opacity-10')
192
338
  : ''}`, style: {
193
339
  cursor: 'pointer',
194
- transition: 'background-color 0.1s ease'
195
- }, onClick: () => (0, command_palette_1.executeCommand)(command.id), onMouseEnter: () => setSelectedIndex(globalIndex) },
340
+ transition: 'background-color 0.1s ease',
341
+ borderTop: allItems.length > 0 ? `1px solid ${isDark ? '#495057' : '#dee2e6'}` : 'none',
342
+ marginTop: allItems.length > 0 ? '8px' : '0',
343
+ paddingTop: allItems.length > 0 ? '12px' : '8px'
344
+ }, onClick: () => {
345
+ (0, command_palette_1.closeCommandPalette)();
346
+ createNewThreadFromQuery(searchQuery);
347
+ }, onMouseEnter: () => {
348
+ // Set selected index beyond the list to indicate this item is selected
349
+ setSelectedIndex(allItems.length);
350
+ } },
196
351
  react_1.default.createElement("div", { className: "d-flex align-items-center" },
197
- command.iconClassName && (react_1.default.createElement("i", { className: `${command.iconClassName} me-3`, style: {
352
+ react_1.default.createElement("i", { className: "bi-chat-dots me-3", style: {
198
353
  fontSize: '16px',
199
- color: isSelected ? (isDark ? '#ffffff' : '#0d6efd') : '#6c757d',
354
+ color: (allItems.length === 0 || selectedIndex >= allItems.length) && searchQuery.trim()
355
+ ? (isDark ? '#ffffff' : '#0d6efd')
356
+ : '#6c757d',
200
357
  minWidth: '16px'
201
- } })),
358
+ } }),
202
359
  react_1.default.createElement("div", null,
203
- react_1.default.createElement("div", { className: "fw-medium" }, command.label),
204
- command.description && (react_1.default.createElement("div", { className: "small text-muted", style: { fontSize: '12px' } }, command.description)))),
205
- command.shortcut && (react_1.default.createElement("div", { className: `small px-2 py-1 rounded ${isDark ? 'bg-secondary bg-opacity-50' : 'bg-light'}`, style: {
206
- fontSize: '11px',
207
- fontFamily: 'monospace',
208
- color: isDark ? '#adb5bd' : '#6c757d',
209
- border: isDark ? '1px solid #495057' : '1px solid #dee2e6'
210
- } }, command.shortcut))));
211
- }))))))),
360
+ react_1.default.createElement("div", { className: "fw-medium" },
361
+ "Start new thread: ",
362
+ react_1.default.createElement("span", { className: "text-muted" },
363
+ searchQuery.trim().slice(0, 50),
364
+ searchQuery.trim().length > 50 ? '...' : '')),
365
+ react_1.default.createElement("div", { className: "small text-muted", style: { fontSize: '12px' } }, "Press Enter to create"))))))))),
212
366
  react_1.default.createElement("div", { className: `px-3 py-2 small text-muted border-top d-flex align-items-center justify-content-between`, style: {
213
367
  borderTopColor: isDark ? '#495057' : '#dee2e6',
214
368
  fontSize: '11px'
@@ -224,7 +378,8 @@ function CommandPaletteOverlay() {
224
378
  react_1.default.createElement("kbd", { className: "small" }, "esc"),
225
379
  " to close")),
226
380
  react_1.default.createElement("div", null,
227
- visualOrderCommands.length,
228
- " command",
229
- visualOrderCommands.length !== 1 ? 's' : '')))));
381
+ allItems.length,
382
+ " result",
383
+ allItems.length !== 1 ? 's' : '',
384
+ searchQuery.trim() && allItems.length === 0 && ' • Start thread')))));
230
385
  }
@@ -129,127 +129,6 @@ const coreCommands = [
129
129
  closeCommandPalette();
130
130
  (0, tabs_state_1.goToTabPath)('threads');
131
131
  }
132
- },
133
- {
134
- id: 'go-settings',
135
- label: 'Open Settings',
136
- description: 'Open application settings',
137
- iconClassName: 'bi-gear-fill',
138
- category: 'Navigation',
139
- action: () => {
140
- closeCommandPalette();
141
- (0, tabs_state_1.goToTabPath)('settings');
142
- }
143
- },
144
- {
145
- id: 'go-assistants',
146
- label: 'Go to Assistants',
147
- description: 'View and manage assistants',
148
- iconClassName: 'bi-person-fill-gear',
149
- category: 'Navigation',
150
- action: () => {
151
- closeCommandPalette();
152
- (0, tabs_state_1.goToTabPath)('assistants');
153
- }
154
- },
155
- {
156
- id: 'go-workflows',
157
- label: 'Go to Workflows',
158
- description: 'View and manage workflows',
159
- iconClassName: 'bi-database-fill-gear',
160
- category: 'Navigation',
161
- action: () => {
162
- closeCommandPalette();
163
- (0, tabs_state_1.goToTabPath)('workflows');
164
- }
165
- },
166
- {
167
- id: 'go-tools',
168
- label: 'Go to Tools',
169
- description: 'View and manage tools',
170
- iconClassName: 'bi-tools',
171
- category: 'Navigation',
172
- action: () => {
173
- closeCommandPalette();
174
- (0, tabs_state_1.goToTabPath)('tools');
175
- }
176
- },
177
- {
178
- id: 'go-events',
179
- label: 'Go to Events',
180
- description: 'View and manage events',
181
- iconClassName: 'bi-lightning-charge-fill',
182
- category: 'Navigation',
183
- action: () => {
184
- closeCommandPalette();
185
- (0, tabs_state_1.goToTabPath)('events');
186
- }
187
- },
188
- {
189
- id: 'go-predicates',
190
- label: 'Go to Predicates',
191
- description: 'View and manage predicates',
192
- iconClassName: 'bi-node-plus-fill',
193
- category: 'Navigation',
194
- action: () => {
195
- closeCommandPalette();
196
- (0, tabs_state_1.goToTabPath)('predicates');
197
- }
198
- },
199
- {
200
- id: 'go-peer-types',
201
- label: 'Go to Peer Types',
202
- description: 'View and manage peer types',
203
- iconClassName: 'bi-code-square',
204
- category: 'Navigation',
205
- action: () => {
206
- closeCommandPalette();
207
- (0, tabs_state_1.goToTabPath)('peer-types');
208
- }
209
- },
210
- {
211
- id: 'go-packages',
212
- label: 'Go to Packages',
213
- description: 'View and manage packages',
214
- iconClassName: 'bi-box-fill',
215
- category: 'Navigation',
216
- action: () => {
217
- closeCommandPalette();
218
- (0, tabs_state_1.goToTabPath)('packages');
219
- }
220
- },
221
- {
222
- id: 'go-variables',
223
- label: 'Go to Variables',
224
- description: 'View and manage variables',
225
- iconClassName: 'bi-braces',
226
- category: 'Navigation',
227
- action: () => {
228
- closeCommandPalette();
229
- (0, tabs_state_1.goToTabPath)('variables');
230
- }
231
- },
232
- {
233
- id: 'go-knowledge-values',
234
- label: 'Go to Knowledge Values',
235
- description: 'View and manage knowledge values',
236
- iconClassName: 'bi-journal-bookmark-fill',
237
- category: 'Navigation',
238
- action: () => {
239
- closeCommandPalette();
240
- (0, tabs_state_1.goToTabPath)('knowledge-values');
241
- }
242
- },
243
- {
244
- id: 'go-knowledge-frames',
245
- label: 'Go to Knowledge Frames',
246
- description: 'View and manage knowledge frames',
247
- iconClassName: 'bi-window-dock',
248
- category: 'Navigation',
249
- action: () => {
250
- closeCommandPalette();
251
- (0, tabs_state_1.goToTabPath)('knowledge-frames');
252
- }
253
132
  }
254
133
  ];
255
134
  // Register core commands
@@ -128,7 +128,7 @@ exports.valueTypeMentionConfig = {
128
128
  name: item.name
129
129
  }),
130
130
  onClick(data) {
131
- peers_sdk_1.rpcClientCalls.setClientPath(`value-types/${data.id}`);
131
+ peers_sdk_1.rpcClientCalls.setClientPath(`peer-types/${data.id}`);
132
132
  },
133
133
  };
134
134
  // Tasks().list({ title: { $matchWords: mentionString }, completeDT: { $exists: searchCompletedTasks } }, { pageSize: MAX_RESULTS })
@@ -59,9 +59,10 @@ const DEFAULT_COLUMNS = [
59
59
  const ConsoleLogsList = () => {
60
60
  const logs = (0, hooks_1.useObservableState)([]);
61
61
  const [allLogsLoaded, setAllLogsLoaded] = (0, react_1.useState)(false);
62
+ const loadMoreId = (0, hooks_1.useObservableState)((0, peers_sdk_1.newid)(), true);
62
63
  const [levelFilter, setLevelFilter] = (0, react_1.useState)('all');
63
64
  const [processFilter, setProcessFilter] = (0, react_1.useState)('all');
64
- const [searchText, setSearchText] = (0, react_1.useState)('');
65
+ const searchText = (0, hooks_1.useObservableState)('');
65
66
  const [columns, setColumns] = (0, react_1.useState)(DEFAULT_COLUMNS);
66
67
  const [totalLogCount, setTotalLogCount] = (0, react_1.useState)(0);
67
68
  const [_colorMode] = (0, hooks_1.useObservable)(color_mode_dropdown_1.colorMode);
@@ -102,6 +103,9 @@ const ConsoleLogsList = () => {
102
103
  if (processFilter !== 'all') {
103
104
  filter.process = processFilter;
104
105
  }
106
+ if (searchText()) {
107
+ filter.message = { $matchWords: searchText() };
108
+ }
105
109
  return filter;
106
110
  };
107
111
  // Update total log count
@@ -109,11 +113,6 @@ const ConsoleLogsList = () => {
109
113
  const table = await (0, peers_sdk_1.ConsoleLogs)();
110
114
  const filter = buildFilter();
111
115
  let count = await table.count(filter);
112
- // If search text is applied, we need to count manually since it's client-side filtering
113
- if (searchText) {
114
- const allLogs = await table.list(filter);
115
- count = allLogs.filter(log => log.message.toLowerCase().includes(searchText.toLowerCase())).length;
116
- }
117
116
  setTotalLogCount(count);
118
117
  }
119
118
  // Fetch logs using cursor-based pagination
@@ -123,54 +122,50 @@ const ConsoleLogsList = () => {
123
122
  if (lastLog) {
124
123
  filter.logId = { $lt: lastLog.logId };
125
124
  }
126
- const cursor = table.cursor(filter, {
127
- sortBy: ['-timestamp', '-logId'],
128
- });
129
- const fetchedLogs = [];
130
- for await (const log of cursor) {
131
- // Apply text search filter (if search is implemented in cursor, this can be removed)
132
- if (searchText && !log.message.toLowerCase().includes(searchText.toLowerCase())) {
133
- continue;
134
- }
135
- fetchedLogs.push(log);
136
- if (fetchedLogs.length >= batchSize) {
137
- break;
138
- }
139
- }
140
- return fetchedLogs;
125
+ const results = await table.list(filter, { pageSize: batchSize, sortBy: ['-timestamp', '-logId'] });
126
+ return results;
141
127
  }
142
128
  // Load older logs (prepend to list)
143
- function prependLogs() {
129
+ async function loadMoreLogs(startLoadId) {
130
+ if (startLoadId && startLoadId !== loadMoreId()) {
131
+ return;
132
+ }
133
+ startLoadId ??= loadMoreId();
144
134
  const oldestLog = logs()[0];
145
- fetchLogs(oldestLog).then(fetchedLogs => {
146
- if (fetchedLogs.length === 0) {
147
- setAllLogsLoaded(true);
148
- }
149
- else {
150
- let _logs = (0, lodash_1.sortBy)([...logs(), ...fetchedLogs], 'timestamp');
151
- _logs = (0, lodash_1.uniqBy)(_logs, l => l.logId);
152
- logs(_logs);
153
- }
154
- });
155
- return false;
135
+ const fetchedLogs = await fetchLogs(oldestLog);
136
+ if (loadMoreId() !== startLoadId) {
137
+ loadMoreLogs(loadMoreId());
138
+ return;
139
+ }
140
+ if (fetchedLogs.length === 0) {
141
+ setAllLogsLoaded(true);
142
+ }
143
+ let _logs = (0, lodash_1.sortBy)([...logs(), ...fetchedLogs], 'timestamp');
144
+ _logs = (0, lodash_1.uniqBy)(_logs, l => l.logId);
145
+ logs(_logs);
156
146
  }
157
147
  // Initial load and ensure screen is filled
158
148
  const minHeightOfLog = 30;
159
149
  (0, react_1.useEffect)(() => {
160
150
  if (!allLogsLoaded && (!logs.length || logs.length * minHeightOfLog < windowHeight())) {
161
- prependLogs();
151
+ loadMoreLogs();
162
152
  }
163
- }, [logs, levelFilter, processFilter, searchText]);
153
+ }, [logs, levelFilter, processFilter, searchText()]);
164
154
  // Reset when filters change
165
155
  (0, react_1.useEffect)(() => {
156
+ loadMoreId((0, peers_sdk_1.newid)());
166
157
  logs([]);
167
- setAllLogsLoaded(false);
168
158
  updateLogCount();
169
- }, [levelFilter, processFilter, searchText]);
159
+ if (allLogsLoaded) {
160
+ setAllLogsLoaded(false);
161
+ loadMoreLogs();
162
+ }
163
+ }, [levelFilter, processFilter, searchText()]);
170
164
  // Subscribe to new logs
171
165
  (0, react_1.useEffect)(() => {
166
+ let sub = undefined;
172
167
  (0, peers_sdk_1.ConsoleLogs)().then(table => {
173
- const sub = table.dataChanged.subscribe(evt => {
168
+ sub = table.dataChanged.subscribe(evt => {
174
169
  // Update count whenever data changes
175
170
  updateLogCount();
176
171
  const log = evt.dataObject;
@@ -179,8 +174,12 @@ const ConsoleLogsList = () => {
179
174
  return;
180
175
  if (processFilter !== 'all' && log.process !== processFilter)
181
176
  return;
182
- if (searchText && !log.message.toLowerCase().includes(searchText.toLowerCase()))
183
- return;
177
+ if (searchText()) {
178
+ const logMessage = log.message.toLowerCase();
179
+ const filterOut = searchText().toLowerCase().split(' ').some(word => !logMessage.includes(word));
180
+ if (filterOut)
181
+ return;
182
+ }
184
183
  // Don't add we're only showing a limited batch and this is older
185
184
  if (logs().length > batchSize && (0, lodash_1.min)(logs().map(l => l.timestamp)) > log.timestamp)
186
185
  return;
@@ -191,11 +190,11 @@ const ConsoleLogsList = () => {
191
190
  scrollToBottom('smooth');
192
191
  }
193
192
  });
194
- return () => {
195
- sub.unsubscribe();
196
- };
197
193
  });
198
- }, [levelFilter, processFilter, searchText]);
194
+ return () => {
195
+ sub?.unsubscribe();
196
+ };
197
+ }, [levelFilter, processFilter]);
199
198
  function scrollToBottom(behavior, delay = 100) {
200
199
  setTimeout(() => {
201
200
  logsEndRef.current?.scrollIntoView({ behavior });
@@ -207,7 +206,6 @@ const ConsoleLogsList = () => {
207
206
  const table = await (0, peers_sdk_1.ConsoleLogs)();
208
207
  await table.deleteOldLogs(Date.now());
209
208
  logs([]);
210
- setAllLogsLoaded(true);
211
209
  setTotalLogCount(0);
212
210
  }
213
211
  catch (err) {
@@ -217,7 +215,7 @@ const ConsoleLogsList = () => {
217
215
  }
218
216
  const _logs = (0, lodash_1.uniqBy)(logs(), l => l.logId);
219
217
  return (react_1.default.createElement("div", { className: "container-fluid", style: { height: 'calc(100vh - 100px)', display: 'flex', flexDirection: 'column' } },
220
- react_1.default.createElement(log_filters_1.LogFilters, { levelFilter: levelFilter, setLevelFilter: setLevelFilter, processFilter: processFilter, setProcessFilter: setProcessFilter, searchText: searchText, setSearchText: setSearchText }),
218
+ react_1.default.createElement(log_filters_1.LogFilters, { levelFilter: levelFilter, setLevelFilter: setLevelFilter, processFilter: processFilter, setProcessFilter: setProcessFilter, searchText: searchText(), setSearchText: searchText }),
221
219
  react_1.default.createElement("div", { ref: containerRef, style: {
222
220
  flex: 1,
223
221
  display: 'flex',
@@ -234,7 +232,7 @@ const ConsoleLogsList = () => {
234
232
  display: 'flex',
235
233
  flexDirection: 'column-reverse',
236
234
  } },
237
- react_1.default.createElement(react_infinite_scroll_component_1.default, { dataLength: _logs.length, next: prependLogs, style: { display: 'flex', flexDirection: 'column-reverse', overflow: 'hidden' }, inverse: true, hasMore: !allLogsLoaded, loader: react_1.default.createElement(loading_indicator_1.LoadingIndicator, null), scrollableTarget: "scrollableLogsDiv", endMessage: react_1.default.createElement(react_1.Fragment, null,
235
+ react_1.default.createElement(react_infinite_scroll_component_1.default, { dataLength: _logs.length, next: loadMoreLogs, style: { display: 'flex', flexDirection: 'column-reverse', overflow: 'hidden' }, inverse: true, hasMore: !allLogsLoaded, loader: react_1.default.createElement(loading_indicator_1.LoadingIndicator, null), scrollableTarget: "scrollableLogsDiv", endMessage: react_1.default.createElement(react_1.Fragment, null,
238
236
  react_1.default.createElement("div", { className: "d-flex justify-content-center p-3" },
239
237
  react_1.default.createElement("div", { className: "text-muted" },
240
238
  react_1.default.createElement("i", null, "beginning of logs")))) },