@peers-app/peers-ui 0.7.39 → 0.8.0
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/globals.d.ts +1 -1
- package/dist/mention-configs.js +1 -1
- package/dist/screens/console-logs/console-logs-list.js +45 -47
- package/dist/screens/contacts/contact-list.js +4 -1
- package/dist/screens/contacts/index.d.ts +2 -0
- package/dist/screens/contacts/index.js +2 -0
- package/dist/screens/contacts/user-connect.d.ts +2 -0
- package/dist/screens/contacts/user-connect.js +312 -0
- 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/contacts/contact-list.tsx +4 -0
- package/src/screens/contacts/index.ts +3 -1
- package/src/screens/contacts/user-connect.tsx +452 -0
- package/src/screens/search/global-search.tsx +126 -7
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.UserConnect = UserConnect;
|
|
37
|
+
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
38
|
+
const react_1 = __importStar(require("react"));
|
|
39
|
+
const globals_1 = require("../../globals");
|
|
40
|
+
const hooks_1 = require("../../hooks");
|
|
41
|
+
const ui_loader_1 = require("../../ui-router/ui-loader");
|
|
42
|
+
function UserConnect() {
|
|
43
|
+
const [mode, setMode] = (0, react_1.useState)('select');
|
|
44
|
+
const [status, setStatus] = (0, react_1.useState)('idle');
|
|
45
|
+
const [connectionCode, setConnectionCode] = (0, react_1.useState)('');
|
|
46
|
+
const [inputCode, setInputCode] = (0, react_1.useState)('');
|
|
47
|
+
const [result, setResult] = (0, react_1.useState)(null);
|
|
48
|
+
const [error, setError] = (0, react_1.useState)('');
|
|
49
|
+
const [copied, setCopied] = (0, react_1.useState)(false);
|
|
50
|
+
// Subscribe to userConnectStatus from the device layer
|
|
51
|
+
const [connectStatus] = (0, hooks_1.useObservable)(peers_sdk_1.userConnectStatus);
|
|
52
|
+
// Also set up a direct subscription after loading is complete
|
|
53
|
+
(0, react_1.useEffect)(() => {
|
|
54
|
+
let disposed = false;
|
|
55
|
+
let subscription;
|
|
56
|
+
peers_sdk_1.userConnectStatus.loadingPromise.then(() => {
|
|
57
|
+
if (disposed)
|
|
58
|
+
return;
|
|
59
|
+
subscription = peers_sdk_1.userConnectStatus.subscribe(() => {
|
|
60
|
+
// Force a re-render by checking the current value
|
|
61
|
+
const currentStatus = (0, peers_sdk_1.userConnectStatus)();
|
|
62
|
+
if (currentStatus && typeof currentStatus === 'string') {
|
|
63
|
+
handleConnectStatusChange(currentStatus);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
return () => {
|
|
68
|
+
disposed = true;
|
|
69
|
+
subscription?.dispose();
|
|
70
|
+
};
|
|
71
|
+
}, []);
|
|
72
|
+
// Handle connect status changes
|
|
73
|
+
const handleConnectStatusChange = (0, react_1.useCallback)(async (connectStatusValue) => {
|
|
74
|
+
if (!connectStatusValue || status === 'success')
|
|
75
|
+
return;
|
|
76
|
+
if (connectStatusValue.startsWith('Error:')) {
|
|
77
|
+
// Error status
|
|
78
|
+
setError(connectStatusValue.replace('Error: ', ''));
|
|
79
|
+
setStatus('error');
|
|
80
|
+
}
|
|
81
|
+
else if (connectStatusValue.length > 0) {
|
|
82
|
+
// Success - connectStatus is the remote userId
|
|
83
|
+
const remoteUserId = connectStatusValue;
|
|
84
|
+
try {
|
|
85
|
+
const userContext = await (0, peers_sdk_1.getUserContext)();
|
|
86
|
+
const me = await (0, peers_sdk_1.getMe)();
|
|
87
|
+
const remoteUser = await (0, peers_sdk_1.Users)(userContext.userDataContext).get(remoteUserId);
|
|
88
|
+
if (!remoteUser) {
|
|
89
|
+
setError('Could not find connected user');
|
|
90
|
+
setStatus('error');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const remoteDevice = await (0, peers_sdk_1.Devices)(userContext.userDataContext).findOne({ userId: remoteUserId });
|
|
94
|
+
if (!remoteDevice) {
|
|
95
|
+
setError('Could not find connected device but connection was successful');
|
|
96
|
+
setStatus('error');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Build user connect info for confirmation hash
|
|
100
|
+
const myInfo = {
|
|
101
|
+
userId: me.userId,
|
|
102
|
+
publicKey: me.publicKey,
|
|
103
|
+
publicBoxKey: me.publicBoxKey,
|
|
104
|
+
deviceId: userContext.deviceId(),
|
|
105
|
+
};
|
|
106
|
+
const remoteInfo = {
|
|
107
|
+
userId: remoteUser.userId,
|
|
108
|
+
publicKey: remoteUser.publicKey,
|
|
109
|
+
publicBoxKey: remoteUser.publicBoxKey,
|
|
110
|
+
deviceId: remoteDevice.deviceId,
|
|
111
|
+
};
|
|
112
|
+
const confirmationHash = (0, peers_sdk_1.generateConfirmationHash)(myInfo, remoteInfo);
|
|
113
|
+
setResult({ remoteUser, confirmationHash });
|
|
114
|
+
setStatus('success');
|
|
115
|
+
// Clear the codes
|
|
116
|
+
(0, peers_sdk_1.userConnectCodeOffer)('');
|
|
117
|
+
(0, peers_sdk_1.userConnectCodeAnswer)('');
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
setError(err.message || 'Failed to complete connection');
|
|
121
|
+
setStatus('error');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}, [status]);
|
|
125
|
+
// React to userConnectStatus changes from useObservable
|
|
126
|
+
(0, react_1.useEffect)(() => {
|
|
127
|
+
if (connectStatus && typeof connectStatus === 'string') {
|
|
128
|
+
handleConnectStatusChange(connectStatus);
|
|
129
|
+
}
|
|
130
|
+
}, [connectStatus, handleConnectStatusChange]);
|
|
131
|
+
// Clean up on unmount
|
|
132
|
+
(0, react_1.useEffect)(() => {
|
|
133
|
+
return () => {
|
|
134
|
+
if (mode === 'initiate' && status === 'waiting') {
|
|
135
|
+
(0, peers_sdk_1.userConnectCodeOffer)('');
|
|
136
|
+
(0, peers_sdk_1.userConnectCodeAnswer)('');
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}, [mode, status]);
|
|
140
|
+
const handleInitiate = (0, react_1.useCallback)(async () => {
|
|
141
|
+
setMode('initiate');
|
|
142
|
+
setStatus('waiting');
|
|
143
|
+
setError('');
|
|
144
|
+
(0, peers_sdk_1.userConnectStatus)(''); // Clear any previous status
|
|
145
|
+
// Generate the connection code
|
|
146
|
+
const code = (0, peers_sdk_1.generateConnectionCode)();
|
|
147
|
+
(0, peers_sdk_1.userConnectCodeOffer)(code.code);
|
|
148
|
+
const formattedCode = (0, peers_sdk_1.formatConnectionCode)(code.code);
|
|
149
|
+
setConnectionCode(formattedCode);
|
|
150
|
+
}, []);
|
|
151
|
+
const handleRespond = (0, react_1.useCallback)(async () => {
|
|
152
|
+
if (inputCode.replace(/[^0-9A-Za-z]/g, '').length !== 12) {
|
|
153
|
+
setError('Please enter a valid 12-character connection code');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
setStatus('waiting');
|
|
157
|
+
setError('');
|
|
158
|
+
(0, peers_sdk_1.userConnectStatus)(''); // Clear any previous status
|
|
159
|
+
(0, peers_sdk_1.userConnectCodeAnswer)(inputCode);
|
|
160
|
+
}, [inputCode]);
|
|
161
|
+
const handleCancel = (0, react_1.useCallback)(async () => {
|
|
162
|
+
(0, peers_sdk_1.userConnectCodeOffer)('');
|
|
163
|
+
(0, peers_sdk_1.userConnectCodeAnswer)('');
|
|
164
|
+
(0, peers_sdk_1.userConnectStatus)('');
|
|
165
|
+
setMode('select');
|
|
166
|
+
setStatus('idle');
|
|
167
|
+
setConnectionCode('');
|
|
168
|
+
setError('');
|
|
169
|
+
}, []);
|
|
170
|
+
const handleReset = (0, react_1.useCallback)(() => {
|
|
171
|
+
(0, peers_sdk_1.userConnectCodeOffer)('');
|
|
172
|
+
(0, peers_sdk_1.userConnectCodeAnswer)('');
|
|
173
|
+
(0, peers_sdk_1.userConnectStatus)('');
|
|
174
|
+
setMode('select');
|
|
175
|
+
setStatus('idle');
|
|
176
|
+
setConnectionCode('');
|
|
177
|
+
setInputCode('');
|
|
178
|
+
setResult(null);
|
|
179
|
+
setError('');
|
|
180
|
+
setCopied(false);
|
|
181
|
+
}, []);
|
|
182
|
+
const handleSaveContact = (0, react_1.useCallback)(async () => {
|
|
183
|
+
if (!result)
|
|
184
|
+
return;
|
|
185
|
+
try {
|
|
186
|
+
const userContext = await (0, peers_sdk_1.getUserContext)();
|
|
187
|
+
// Contact is already saved by the device layer, just set trust level
|
|
188
|
+
await (0, peers_sdk_1.setUserTrustLevel)(result.remoteUser.userId, peers_sdk_1.TrustLevel.Trusted, userContext.userDataContext);
|
|
189
|
+
// Navigate to contact details
|
|
190
|
+
(0, globals_1.mainContentPath)(`contacts/${result.remoteUser.userId}`);
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
setError(err.message || 'Failed to save contact');
|
|
194
|
+
}
|
|
195
|
+
}, [result]);
|
|
196
|
+
// Copy connection code to clipboard
|
|
197
|
+
const handleCopyCode = (0, react_1.useCallback)(async () => {
|
|
198
|
+
if (!connectionCode)
|
|
199
|
+
return;
|
|
200
|
+
try {
|
|
201
|
+
await navigator.clipboard.writeText(connectionCode);
|
|
202
|
+
setCopied(true);
|
|
203
|
+
setTimeout(() => setCopied(false), 2000);
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
// Fallback for older browsers
|
|
207
|
+
const textArea = document.createElement('textarea');
|
|
208
|
+
textArea.value = connectionCode;
|
|
209
|
+
document.body.appendChild(textArea);
|
|
210
|
+
textArea.select();
|
|
211
|
+
document.execCommand('copy');
|
|
212
|
+
document.body.removeChild(textArea);
|
|
213
|
+
setCopied(true);
|
|
214
|
+
setTimeout(() => setCopied(false), 2000);
|
|
215
|
+
}
|
|
216
|
+
}, [connectionCode]);
|
|
217
|
+
// Format input code as user types
|
|
218
|
+
const handleCodeInput = (value) => {
|
|
219
|
+
// Remove non-alphanumeric characters
|
|
220
|
+
const cleaned = value.toUpperCase().replace(/[^0-9A-Z]/g, '');
|
|
221
|
+
// Format as XXXX-YYYY-ZZZZ
|
|
222
|
+
let formatted = '';
|
|
223
|
+
for (let i = 0; i < cleaned.length && i < 12; i++) {
|
|
224
|
+
if (i === 4 || i === 8)
|
|
225
|
+
formatted += '-';
|
|
226
|
+
formatted += cleaned[i];
|
|
227
|
+
}
|
|
228
|
+
setInputCode(formatted);
|
|
229
|
+
};
|
|
230
|
+
return (react_1.default.createElement("div", { className: "container-fluid p-3" },
|
|
231
|
+
react_1.default.createElement("div", { className: "d-flex justify-content-between align-items-center mb-4" },
|
|
232
|
+
react_1.default.createElement("h4", null,
|
|
233
|
+
react_1.default.createElement("i", { className: "bi-person-plus-fill me-2" }),
|
|
234
|
+
"Connect to New User"),
|
|
235
|
+
mode !== 'select' && (react_1.default.createElement("button", { className: "btn btn-outline-secondary btn-sm", onClick: handleReset },
|
|
236
|
+
react_1.default.createElement("i", { className: "bi-arrow-left me-1" }),
|
|
237
|
+
"Back"))),
|
|
238
|
+
mode === 'select' && (react_1.default.createElement("div", { className: "row g-3" },
|
|
239
|
+
react_1.default.createElement("div", { className: "col-md-6" },
|
|
240
|
+
react_1.default.createElement("div", { className: "card h-100 border-primary", style: { cursor: 'pointer' }, onClick: handleInitiate },
|
|
241
|
+
react_1.default.createElement("div", { className: "card-body text-center p-4" },
|
|
242
|
+
react_1.default.createElement("i", { className: "bi-qr-code display-4 text-primary mb-3" }),
|
|
243
|
+
react_1.default.createElement("h5", { className: "card-title" }, "Create Connection Code"),
|
|
244
|
+
react_1.default.createElement("p", { className: "card-text text-muted" }, "Generate a code to share with someone who wants to connect with you")))),
|
|
245
|
+
react_1.default.createElement("div", { className: "col-md-6" },
|
|
246
|
+
react_1.default.createElement("div", { className: "card h-100 border-success", style: { cursor: 'pointer' }, onClick: () => setMode('respond') },
|
|
247
|
+
react_1.default.createElement("div", { className: "card-body text-center p-4" },
|
|
248
|
+
react_1.default.createElement("i", { className: "bi-keyboard display-4 text-success mb-3" }),
|
|
249
|
+
react_1.default.createElement("h5", { className: "card-title" }, "Enter Connection Code"),
|
|
250
|
+
react_1.default.createElement("p", { className: "card-text text-muted" }, "Enter a code that someone shared with you to connect")))))),
|
|
251
|
+
mode === 'initiate' && status === 'waiting' && (react_1.default.createElement("div", { className: "text-center" },
|
|
252
|
+
react_1.default.createElement("div", { className: "mb-4" },
|
|
253
|
+
react_1.default.createElement("p", { className: "text-muted" }, "Share this code with the person you want to connect with:")),
|
|
254
|
+
react_1.default.createElement("div", { className: "mb-4", style: { maxWidth: '400px', margin: '0 auto' } },
|
|
255
|
+
react_1.default.createElement("div", { className: "input-group" },
|
|
256
|
+
react_1.default.createElement("input", { type: "text", className: "form-control form-control-lg text-center font-monospace", value: connectionCode || 'XXXX-YYYY-ZZZZ', readOnly: true, style: { letterSpacing: '0.15em', fontSize: '1.5rem' } }),
|
|
257
|
+
react_1.default.createElement("button", { className: "btn btn-outline-primary", onClick: handleCopyCode, title: "Copy to clipboard" },
|
|
258
|
+
react_1.default.createElement("i", { className: copied ? "bi-check-lg" : "bi-clipboard" }))),
|
|
259
|
+
copied && (react_1.default.createElement("small", { className: "text-success mt-1 d-block" }, "Copied!"))),
|
|
260
|
+
react_1.default.createElement("div", { className: "mb-4" },
|
|
261
|
+
react_1.default.createElement("div", { className: "spinner-border spinner-border-sm text-primary me-2", role: "status" }),
|
|
262
|
+
react_1.default.createElement("span", { className: "text-muted" }, "Waiting for connection...")),
|
|
263
|
+
react_1.default.createElement("p", { className: "text-muted small" }, "This code will expire in 10 minutes"),
|
|
264
|
+
react_1.default.createElement("button", { className: "btn btn-outline-secondary", onClick: handleCancel }, "Cancel"))),
|
|
265
|
+
mode === 'respond' && status !== 'success' && (react_1.default.createElement("div", { className: "text-center" },
|
|
266
|
+
react_1.default.createElement("div", { className: "mb-4" },
|
|
267
|
+
react_1.default.createElement("p", { className: "text-muted" }, "Enter the connection code shared with you:")),
|
|
268
|
+
react_1.default.createElement("div", { className: "mb-4", style: { maxWidth: '400px', margin: '0 auto' } },
|
|
269
|
+
react_1.default.createElement("input", { type: "text", className: "form-control form-control-lg text-center font-monospace", placeholder: "XXXX-YYYY-ZZZZ", value: inputCode, onChange: (e) => handleCodeInput(e.target.value), maxLength: 14, style: { letterSpacing: '0.15em', fontSize: '1.5rem' }, autoFocus: true })),
|
|
270
|
+
error && (react_1.default.createElement("div", { className: "alert alert-danger", style: { maxWidth: '400px', margin: '0 auto 1rem' } }, error)),
|
|
271
|
+
react_1.default.createElement("button", { className: "btn btn-primary btn-lg", onClick: handleRespond, disabled: status === 'waiting' || inputCode.replace(/[^0-9A-Z]/gi, '').length !== 12 }, status === 'waiting' ? (react_1.default.createElement(react_1.default.Fragment, null,
|
|
272
|
+
react_1.default.createElement("span", { className: "spinner-border spinner-border-sm me-2", role: "status" }),
|
|
273
|
+
"Connecting...")) : (react_1.default.createElement(react_1.default.Fragment, null,
|
|
274
|
+
react_1.default.createElement("i", { className: "bi-link-45deg me-2" }),
|
|
275
|
+
"Connect"))))),
|
|
276
|
+
status === 'success' && result && (react_1.default.createElement("div", { className: "text-center" },
|
|
277
|
+
react_1.default.createElement("div", { className: "mb-4" },
|
|
278
|
+
react_1.default.createElement("i", { className: "bi-check-circle-fill text-success display-3" })),
|
|
279
|
+
react_1.default.createElement("h5", { className: "mb-4" }, "Connection Successful!"),
|
|
280
|
+
react_1.default.createElement("div", { className: "card mb-4", style: { maxWidth: '400px', margin: '0 auto' } },
|
|
281
|
+
react_1.default.createElement("div", { className: "card-body" },
|
|
282
|
+
react_1.default.createElement("p", { className: "text-muted mb-2" }, "Verify with the other person that you both see:"),
|
|
283
|
+
react_1.default.createElement("h3", { className: "font-monospace text-primary mb-3", style: { letterSpacing: '0.2em' } }, result.confirmationHash),
|
|
284
|
+
react_1.default.createElement("hr", null),
|
|
285
|
+
react_1.default.createElement("div", { className: "text-start" },
|
|
286
|
+
react_1.default.createElement("small", { className: "text-muted" }, "Name:"),
|
|
287
|
+
react_1.default.createElement("p", { className: "mb-2" }, result.remoteUser.name),
|
|
288
|
+
react_1.default.createElement("small", { className: "text-muted" }, "User ID:"),
|
|
289
|
+
react_1.default.createElement("p", { className: "font-monospace small mb-0" }, result.remoteUser.userId)))),
|
|
290
|
+
react_1.default.createElement("div", { className: "d-flex gap-2 justify-content-center" },
|
|
291
|
+
react_1.default.createElement("button", { className: "btn btn-primary", onClick: handleSaveContact },
|
|
292
|
+
react_1.default.createElement("i", { className: "bi-check-lg me-2" }),
|
|
293
|
+
"Trust & View Contact"),
|
|
294
|
+
react_1.default.createElement("button", { className: "btn btn-outline-secondary", onClick: handleReset }, "Connect Another")))),
|
|
295
|
+
status === 'error' && (react_1.default.createElement("div", { className: "text-center" },
|
|
296
|
+
react_1.default.createElement("div", { className: "mb-4" },
|
|
297
|
+
react_1.default.createElement("i", { className: "bi-x-circle-fill text-danger display-3" })),
|
|
298
|
+
react_1.default.createElement("h5", { className: "mb-4" }, "Connection Failed"),
|
|
299
|
+
react_1.default.createElement("div", { className: "alert alert-danger", style: { maxWidth: '400px', margin: '0 auto 1rem' } }, error),
|
|
300
|
+
react_1.default.createElement("button", { className: "btn btn-primary", onClick: handleReset }, "Try Again")))));
|
|
301
|
+
}
|
|
302
|
+
(0, ui_loader_1.registerInternalPeersUI)({
|
|
303
|
+
peersUIId: '000user00connect0screen01',
|
|
304
|
+
component: UserConnect,
|
|
305
|
+
routes: [
|
|
306
|
+
{
|
|
307
|
+
isMatch: (props, context) => context.path === 'contacts/connect',
|
|
308
|
+
uiCategory: 'screen',
|
|
309
|
+
priority: 3
|
|
310
|
+
}
|
|
311
|
+
]
|
|
312
|
+
});
|
|
@@ -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.
|
|
3
|
+
"version": "0.8.0",
|
|
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.
|
|
30
|
+
"@peers-app/peers-sdk": "^0.8.0",
|
|
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.
|
|
60
|
+
"@peers-app/peers-sdk": "0.8.0",
|
|
61
61
|
"react": "^18.0.0",
|
|
62
62
|
"react-dom": "^18.0.0",
|
|
63
63
|
"string-width": "^7.1.0",
|