@peers-app/peers-ui 0.8.0 → 0.8.1
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.d.ts +2 -2
- package/dist/command-palette/command-palette.js +3 -7
- package/dist/components/group-switcher.d.ts +2 -1
- package/dist/components/group-switcher.js +7 -6
- package/dist/screens/network-viewer/device-details-modal.js +44 -0
- package/dist/screens/network-viewer/group-details-modal.js +80 -2
- package/dist/screens/network-viewer/network-viewer.js +36 -16
- package/dist/screens/settings/settings-page.js +13 -7
- package/dist/screens/setup-user.js +8 -6
- package/dist/system-apps/index.d.ts +1 -0
- package/dist/system-apps/index.js +10 -1
- package/dist/system-apps/mobile-settings.app.d.ts +2 -0
- package/dist/system-apps/mobile-settings.app.js +8 -0
- package/dist/tabs-layout/tabs-layout.js +60 -38
- package/dist/tabs-layout/tabs-state.d.ts +10 -4
- package/dist/tabs-layout/tabs-state.js +41 -4
- package/dist/ui-router/ui-loader.js +45 -12
- package/package.json +3 -3
- package/src/command-palette/command-palette.ts +4 -8
- package/src/components/group-switcher.tsx +12 -8
- package/src/screens/network-viewer/device-details-modal.tsx +55 -0
- package/src/screens/network-viewer/group-details-modal.tsx +144 -1
- package/src/screens/network-viewer/network-viewer.tsx +36 -29
- package/src/screens/settings/settings-page.tsx +17 -9
- package/src/screens/setup-user.tsx +9 -6
- package/src/system-apps/index.ts +9 -0
- package/src/system-apps/mobile-settings.app.ts +8 -0
- package/src/tabs-layout/tabs-layout.tsx +108 -82
- package/src/tabs-layout/tabs-state.ts +54 -5
- package/src/ui-router/ui-loader.tsx +50 -11
|
@@ -85,7 +85,7 @@ export const SetupUser = () => {
|
|
|
85
85
|
// Step 1: Select User Type
|
|
86
86
|
if (step === 'select-user-type' && isExistingUser === null) {
|
|
87
87
|
return (
|
|
88
|
-
<div className="container-fluid d-flex align-items-start justify-content-center" style={{
|
|
88
|
+
<div className="container-fluid d-flex align-items-start justify-content-center" style={{ paddingTop: '20px', backgroundColor: isDark ? '#212529' : '#f8f9fa' }}>
|
|
89
89
|
<div className="card shadow" style={{ maxWidth: '500px', width: '100%', backgroundColor: isDark ? '#2d3238' : '#ffffff' }}>
|
|
90
90
|
<div className="card-body p-4 p-md-5">
|
|
91
91
|
<div className="text-center mb-4">
|
|
@@ -129,7 +129,7 @@ export const SetupUser = () => {
|
|
|
129
129
|
// Step 2: New User Confirmation
|
|
130
130
|
if (isExistingUser === false) {
|
|
131
131
|
return (
|
|
132
|
-
<div className="container-fluid d-flex align-items-start justify-content-center" style={{
|
|
132
|
+
<div className="container-fluid d-flex align-items-start justify-content-center" style={{ paddingTop: '20px', backgroundColor: isDark ? '#212529' : '#f8f9fa' }}>
|
|
133
133
|
<div className="card shadow" style={{ maxWidth: '500px', width: '100%', backgroundColor: isDark ? '#2d3238' : '#ffffff' }}>
|
|
134
134
|
<div className="card-body p-4 p-md-5">
|
|
135
135
|
<div className="text-center mb-4">
|
|
@@ -182,12 +182,15 @@ export const SetupUser = () => {
|
|
|
182
182
|
// Step 3: Existing User Sign In
|
|
183
183
|
if (isExistingUser === true) {
|
|
184
184
|
return (
|
|
185
|
-
<div className="container-fluid d-flex align-items-start justify-content-center" style={{
|
|
185
|
+
<div className="container-fluid d-flex align-items-start justify-content-center" style={{ paddingTop: '20px', backgroundColor: isDark ? '#212529' : '#f8f9fa' }}>
|
|
186
186
|
<div className="card shadow" style={{ maxWidth: '500px', width: '100%', backgroundColor: isDark ? '#2d3238' : '#ffffff' }}>
|
|
187
187
|
<div className="card-body p-4 p-md-5">
|
|
188
|
-
<div className="text-center mb-
|
|
189
|
-
<
|
|
190
|
-
|
|
188
|
+
<div className="text-center mb-2">
|
|
189
|
+
<h3 className={`fw-bold mb-2 ${isDark ? 'text-light' : ''}`}>
|
|
190
|
+
<span>Sign In</span>
|
|
191
|
+
|
|
192
|
+
<i className="bi bi-box-arrow-in-right text-primary" />
|
|
193
|
+
</h3>
|
|
191
194
|
<p className={isDark ? 'text-light opacity-75' : 'text-muted'}>Enter your existing credentials</p>
|
|
192
195
|
</div>
|
|
193
196
|
|
package/src/system-apps/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ export { contactsApp } from './contacts.app';
|
|
|
20
20
|
export { consoleLogsApp } from './console-logs.app';
|
|
21
21
|
export { networkViewerApp } from './network-viewer.app';
|
|
22
22
|
export { dataExplorerApp } from './data-explorer.app';
|
|
23
|
+
export { mobileSettingsApp } from './mobile-settings.app';
|
|
23
24
|
|
|
24
25
|
// Import individual apps
|
|
25
26
|
import { searchApp } from './search.app';
|
|
@@ -41,6 +42,12 @@ import { contactsApp } from './contacts.app';
|
|
|
41
42
|
import { consoleLogsApp } from './console-logs.app';
|
|
42
43
|
import { networkViewerApp } from './network-viewer.app';
|
|
43
44
|
import { dataExplorerApp } from './data-explorer.app';
|
|
45
|
+
import { mobileSettingsApp } from './mobile-settings.app';
|
|
46
|
+
|
|
47
|
+
// Helper to check if running in React Native
|
|
48
|
+
function isReactNative(): boolean {
|
|
49
|
+
return typeof (window as any).__NATIVE_THEME !== 'undefined';
|
|
50
|
+
}
|
|
44
51
|
|
|
45
52
|
// Collection of all system apps
|
|
46
53
|
export const systemApps: IAppNav[] = [
|
|
@@ -67,6 +74,8 @@ export const systemApps: IAppNav[] = [
|
|
|
67
74
|
// User & Settings Apps
|
|
68
75
|
profileApp,
|
|
69
76
|
settingsApp,
|
|
77
|
+
// Mobile Settings (only in React Native)
|
|
78
|
+
...(isReactNative() ? [mobileSettingsApp] : []),
|
|
70
79
|
|
|
71
80
|
// System Tools & Debugging
|
|
72
81
|
consoleLogsApp,
|
|
@@ -133,7 +133,7 @@ function TabsLayoutInternal() {
|
|
|
133
133
|
) : (
|
|
134
134
|
<div className="d-flex align-items-center px-1" style={{ height: '36px' }}>
|
|
135
135
|
{/* Group Switcher */}
|
|
136
|
-
<GroupSwitcher colorMode={_colorMode} />
|
|
136
|
+
<GroupSwitcher colorMode={_colorMode} isMobile={false} />
|
|
137
137
|
|
|
138
138
|
{/* Search Button */}
|
|
139
139
|
<button
|
|
@@ -207,112 +207,122 @@ interface MobileTabsHeaderProps {
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
function MobileTabsHeader({ tabs, activeTab, onSwitch, onClose, colorMode }: MobileTabsHeaderProps) {
|
|
210
|
-
const [
|
|
210
|
+
const [showMenuDropdown, setShowMenuDropdown] = React.useState(false);
|
|
211
211
|
const currentTab = tabs.find(t => t.tabId === activeTab);
|
|
212
212
|
const nonLauncherTabs = tabs.filter(t => t.packageId !== 'launcher');
|
|
213
213
|
|
|
214
214
|
return (
|
|
215
|
-
<div className="d-flex align-items-center justify-content-between px-2" style={{ height: '36px' }}>
|
|
216
|
-
{/* Left Side - Group Switcher
|
|
215
|
+
<div className="d-flex align-items-center justify-content-between px-2 position-relative" style={{ height: '36px' }}>
|
|
216
|
+
{/* Left Side - Group Switcher */}
|
|
217
217
|
<div className="d-flex align-items-center gap-1">
|
|
218
|
-
<GroupSwitcher colorMode={colorMode} />
|
|
219
|
-
<button
|
|
220
|
-
className="btn btn-sm"
|
|
221
|
-
onClick={openCommandPalette}
|
|
222
|
-
title="Search everything"
|
|
223
|
-
style={{
|
|
224
|
-
minWidth: '36px',
|
|
225
|
-
border: 'none',
|
|
226
|
-
background: 'transparent',
|
|
227
|
-
color: colorMode === 'light' ? '#6c757d' : '#adb5bd'
|
|
228
|
-
}}
|
|
229
|
-
onMouseEnter={(e) => {
|
|
230
|
-
e.currentTarget.style.backgroundColor = colorMode === 'light' ? '#f8f9fa' : '#495057';
|
|
231
|
-
e.currentTarget.style.color = colorMode === 'light' ? '#0d6efd' : '#ffffff';
|
|
232
|
-
}}
|
|
233
|
-
onMouseLeave={(e) => {
|
|
234
|
-
e.currentTarget.style.backgroundColor = 'transparent';
|
|
235
|
-
e.currentTarget.style.color = colorMode === 'light' ? '#6c757d' : '#adb5bd';
|
|
236
|
-
}}
|
|
237
|
-
>
|
|
238
|
-
<i className="bi-search" />
|
|
239
|
-
</button>
|
|
240
|
-
<button
|
|
241
|
-
className={`btn btn-sm ${colorMode === 'light' ? 'btn-outline-primary' : 'btn-outline-light'}`}
|
|
242
|
-
onClick={() => onSwitch('launcher')}
|
|
243
|
-
style={{ minWidth: '36px' }}
|
|
244
|
-
>
|
|
245
|
-
<i className="bi-grid-3x3-gap" />
|
|
246
|
-
</button>
|
|
218
|
+
<GroupSwitcher colorMode={colorMode} isMobile={true} />
|
|
247
219
|
</div>
|
|
248
220
|
|
|
249
|
-
{/* Center - Current Tab Display */}
|
|
250
|
-
<div
|
|
221
|
+
{/* Center - Current Tab Display (absolutely positioned to center on screen) */}
|
|
222
|
+
<div
|
|
223
|
+
className="d-flex align-items-center justify-content-center position-absolute"
|
|
224
|
+
style={{
|
|
225
|
+
left: '50%',
|
|
226
|
+
transform: 'translateX(-50%)',
|
|
227
|
+
pointerEvents: 'none',
|
|
228
|
+
maxWidth: 'calc(100% - 140px)'
|
|
229
|
+
}}
|
|
230
|
+
>
|
|
251
231
|
{currentTab?.iconClassName && currentTab.packageId !== 'launcher' && (
|
|
252
232
|
<i className={`${currentTab.iconClassName} me-2`} />
|
|
253
233
|
)}
|
|
254
|
-
<span className="fw-medium text-truncate
|
|
234
|
+
<span className="fw-medium text-truncate small">
|
|
255
235
|
{currentTab?.title || 'Apps'}
|
|
256
236
|
</span>
|
|
257
237
|
</div>
|
|
258
238
|
|
|
259
|
-
{/* Right Side -
|
|
260
|
-
<div className="d-flex align-items-center
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
239
|
+
{/* Right Side - Hamburger Menu */}
|
|
240
|
+
<div className="d-flex align-items-center">
|
|
241
|
+
<div className="dropdown">
|
|
242
|
+
<button
|
|
243
|
+
className={`btn btn-sm ${colorMode === 'light' ? 'btn-outline-dark' : 'btn-outline-light'}`}
|
|
244
|
+
onClick={() => setShowMenuDropdown(!showMenuDropdown)}
|
|
245
|
+
style={{ minWidth: '36px' }}
|
|
246
|
+
>
|
|
247
|
+
<i className="bi-list" />
|
|
248
|
+
{nonLauncherTabs.length > 0 && (
|
|
269
249
|
<span className="ms-1">{nonLauncherTabs.length}</span>
|
|
270
|
-
|
|
271
|
-
|
|
250
|
+
)}
|
|
251
|
+
</button>
|
|
252
|
+
{showMenuDropdown && (
|
|
253
|
+
<div
|
|
254
|
+
className={`dropdown-menu show position-absolute ${colorMode === 'light' ? '' : 'dropdown-menu-dark'}`}
|
|
255
|
+
style={{ right: 0, top: '100%', zIndex: 1000, minWidth: '250px' }}
|
|
256
|
+
>
|
|
257
|
+
{/* Apps Option */}
|
|
272
258
|
<div
|
|
273
|
-
className={`dropdown-
|
|
274
|
-
style={{
|
|
259
|
+
className={`dropdown-item d-flex align-items-center ${activeTab === 'launcher' ? 'active' : ''}`}
|
|
260
|
+
style={{ cursor: 'pointer' }}
|
|
261
|
+
onClick={() => {
|
|
262
|
+
onSwitch('launcher');
|
|
263
|
+
setShowMenuDropdown(false);
|
|
264
|
+
}}
|
|
275
265
|
>
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
266
|
+
<i className="bi-grid-3x3-gap me-2" />
|
|
267
|
+
<span>Apps</span>
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
{/* Search Option */}
|
|
271
|
+
<div
|
|
272
|
+
className="dropdown-item d-flex align-items-center"
|
|
273
|
+
style={{ cursor: 'pointer' }}
|
|
274
|
+
onClick={() => {
|
|
275
|
+
openCommandPalette();
|
|
276
|
+
setShowMenuDropdown(false);
|
|
277
|
+
}}
|
|
278
|
+
>
|
|
279
|
+
<i className="bi-search me-2" />
|
|
280
|
+
<span>Search</span>
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
{/* Divider if there are open tabs */}
|
|
284
|
+
{nonLauncherTabs.length > 0 && (
|
|
285
|
+
<div className="dropdown-divider" />
|
|
286
|
+
)}
|
|
287
|
+
|
|
288
|
+
{/* Open Tabs */}
|
|
289
|
+
{nonLauncherTabs.slice().reverse().map(tab => (
|
|
290
|
+
<div
|
|
291
|
+
key={tab.tabId}
|
|
292
|
+
className={`dropdown-item d-flex align-items-center justify-content-between ${activeTab === tab.tabId ? 'active' : ''}`}
|
|
293
|
+
style={{ cursor: 'pointer' }}
|
|
294
|
+
onClick={() => {
|
|
295
|
+
onSwitch(tab.tabId);
|
|
296
|
+
setShowMenuDropdown(false);
|
|
297
|
+
}}
|
|
298
|
+
>
|
|
299
|
+
<div className="d-flex align-items-center">
|
|
300
|
+
{tab.iconClassName && <i className={`${tab.iconClassName} me-2`} />}
|
|
301
|
+
<span>{tab.title}</span>
|
|
302
|
+
</div>
|
|
303
|
+
<button
|
|
304
|
+
className="btn btn-sm p-0 ms-2"
|
|
305
|
+
style={{ width: '20px', height: '20px' }}
|
|
306
|
+
onClick={(e) => {
|
|
307
|
+
e.stopPropagation();
|
|
308
|
+
onClose(tab.tabId);
|
|
284
309
|
}}
|
|
285
310
|
>
|
|
286
|
-
<
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
style={{ width: '20px', height: '20px' }}
|
|
294
|
-
onClick={(e) => {
|
|
295
|
-
e.stopPropagation();
|
|
296
|
-
onClose(tab.tabId);
|
|
297
|
-
}}
|
|
298
|
-
>
|
|
299
|
-
<i className="bi-x" />
|
|
300
|
-
</button>
|
|
301
|
-
)}
|
|
302
|
-
</div>
|
|
303
|
-
))}
|
|
304
|
-
</div>
|
|
305
|
-
)}
|
|
306
|
-
</div>
|
|
307
|
-
)}
|
|
311
|
+
<i className="bi-x" />
|
|
312
|
+
</button>
|
|
313
|
+
</div>
|
|
314
|
+
))}
|
|
315
|
+
</div>
|
|
316
|
+
)}
|
|
317
|
+
</div>
|
|
308
318
|
</div>
|
|
309
319
|
|
|
310
320
|
{/* Backdrop to close dropdown */}
|
|
311
|
-
{
|
|
321
|
+
{showMenuDropdown && (
|
|
312
322
|
<div
|
|
313
323
|
className="position-fixed w-100 h-100"
|
|
314
324
|
style={{ top: 0, left: 0, zIndex: 999 }}
|
|
315
|
-
onClick={() =>
|
|
325
|
+
onClick={() => setShowMenuDropdown(false)}
|
|
316
326
|
/>
|
|
317
327
|
)}
|
|
318
328
|
</div>
|
|
@@ -515,6 +525,22 @@ function AppLauncherTab({ isMobile }: AppLauncherTabProps) {
|
|
|
515
525
|
.filter(Boolean) as AppItem[];
|
|
516
526
|
|
|
517
527
|
const openApp = (appItem: AppItem) => {
|
|
528
|
+
// Check if this is the mobile-settings app and we're in React Native
|
|
529
|
+
if (appItem.path === 'mobile-settings' && typeof (window as any).__NATIVE_THEME !== 'undefined') {
|
|
530
|
+
// Use expo-linking to navigate to native screen
|
|
531
|
+
// @ts-ignore
|
|
532
|
+
if (window.ReactNativeWebView) {
|
|
533
|
+
// @ts-ignore
|
|
534
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
535
|
+
type: 'navigate',
|
|
536
|
+
path: 'mobile-settings'
|
|
537
|
+
}));
|
|
538
|
+
} else {
|
|
539
|
+
// Fallback to deep link
|
|
540
|
+
window.location.href = 'peers://mobile-settings';
|
|
541
|
+
}
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
518
544
|
goToTabPath(appItem.path);
|
|
519
545
|
};
|
|
520
546
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { groupDeviceVar, groupUserVar, IAppNav, IPackage, newid } from "@peers-app/peers-sdk";
|
|
1
|
+
import { groupDeviceVar, groupUserVar, IAppNav, IPackage, newid, observable, Observable } from "@peers-app/peers-sdk";
|
|
2
2
|
import { _mainContentPath } from "../globals";
|
|
3
3
|
import { systemPackage } from "../system-apps";
|
|
4
4
|
import { allPackages } from "../ui-router/routes-loader";
|
|
@@ -19,19 +19,68 @@ export const launcherApp: TabState = {
|
|
|
19
19
|
iconClassName: 'bi-grid-3x3-gap',
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
//
|
|
23
|
-
|
|
22
|
+
// Persistent vars for storage (write-only, no subscription to avoid oscillation)
|
|
23
|
+
const _persistentActiveTabs = groupDeviceVar<TabState[]>('activeTabs', {
|
|
24
24
|
defaultValue: [launcherApp],
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
const _persistentActiveTabId = groupDeviceVar<string>('activeTabId', {
|
|
28
28
|
defaultValue: 'launcher',
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
const _persistentRecentlyUsedApps = groupUserVar<string[]>('recentlyUsedApps', {
|
|
32
32
|
defaultValue: [],
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
+
// In-memory observables for UI state (loaded once, then write-through to persistent vars)
|
|
36
|
+
export const activeTabs: Observable<TabState[]> & { loadingPromise: Promise<void> } = (() => {
|
|
37
|
+
const obs = observable<TabState[]>([launcherApp]);
|
|
38
|
+
|
|
39
|
+
// Write-through to persistent var on change
|
|
40
|
+
obs.subscribe(value => {
|
|
41
|
+
_persistentActiveTabs(value);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Load initial value once
|
|
45
|
+
const loadingPromise = _persistentActiveTabs.loadingPromise.then(() => {
|
|
46
|
+
obs(_persistentActiveTabs());
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return Object.assign(obs, { loadingPromise });
|
|
50
|
+
})();
|
|
51
|
+
|
|
52
|
+
export const activeTabId: Observable<string> & { loadingPromise: Promise<void> } = (() => {
|
|
53
|
+
const obs = observable<string>('launcher');
|
|
54
|
+
|
|
55
|
+
// Write-through to persistent var on change
|
|
56
|
+
obs.subscribe(value => {
|
|
57
|
+
_persistentActiveTabId(value);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Load initial value once
|
|
61
|
+
const loadingPromise = _persistentActiveTabId.loadingPromise.then(() => {
|
|
62
|
+
obs(_persistentActiveTabId());
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return Object.assign(obs, { loadingPromise });
|
|
66
|
+
})();
|
|
67
|
+
|
|
68
|
+
export const recentlyUsedApps: Observable<string[]> & { loadingPromise: Promise<void> } = (() => {
|
|
69
|
+
const obs = observable<string[]>([]);
|
|
70
|
+
|
|
71
|
+
// Write-through to persistent var on change
|
|
72
|
+
obs.subscribe(value => {
|
|
73
|
+
_persistentRecentlyUsedApps(value);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Load initial value once
|
|
77
|
+
const loadingPromise = _persistentRecentlyUsedApps.loadingPromise.then(() => {
|
|
78
|
+
obs(_persistentRecentlyUsedApps());
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return Object.assign(obs, { loadingPromise });
|
|
82
|
+
})();
|
|
83
|
+
|
|
35
84
|
export const initializedTabs = new Set<string>();
|
|
36
85
|
|
|
37
86
|
export function goToTabPath(path: string) {
|
|
@@ -221,26 +221,65 @@ const UILoader = (args: { peersUIId: string, props: Record<string, any> }) => {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
const uiLoadingPromises: Record<string, Promise<any>> = {};
|
|
224
|
+
|
|
225
|
+
// Check if we're running in a React Native WebView (has injectUIBundle available)
|
|
226
|
+
const isReactNativeWebView = typeof (window as any).ReactNativeWebView !== 'undefined';
|
|
227
|
+
|
|
224
228
|
function loadUIBundle(pkg: IPackage, forceRefresh?: boolean) {
|
|
225
229
|
// Dynamically import the bundle
|
|
226
230
|
let importPromise: Promise<any> = uiLoadingPromises[pkg.packageId];
|
|
227
231
|
if (!importPromise || forceRefresh) {
|
|
232
|
+
const sTime = Date.now();
|
|
228
233
|
console.log(`loading ui bundle for ${pkg.name}`);
|
|
229
234
|
importPromise = new Promise<void>(async (resolve, reject) => {
|
|
230
235
|
try {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
236
|
+
if (!pkg.uiBundleFileId) {
|
|
237
|
+
resolve();
|
|
238
|
+
return;
|
|
234
239
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
240
|
+
|
|
241
|
+
// Use fast injection path for React Native WebView
|
|
242
|
+
if (isReactNativeWebView && rpcServerCalls.injectUIBundle) {
|
|
243
|
+
// Set up listeners for bundle load completion
|
|
244
|
+
const _window = window as any;
|
|
245
|
+
_window.__peersUIs = _window.__peersUIs || {};
|
|
246
|
+
|
|
247
|
+
const loadPromise = new Promise<void>((resolveLoad, rejectLoad) => {
|
|
248
|
+
const fileId = pkg.uiBundleFileId!;
|
|
249
|
+
|
|
250
|
+
_window.__peersUIBundleLoaded = (loadedFileId: string) => {
|
|
251
|
+
if (loadedFileId === fileId) {
|
|
252
|
+
// Copy loaded UIs to our local registry
|
|
253
|
+
Object.keys(_window.__peersUIs || {}).forEach(peersUIId => {
|
|
254
|
+
peersUIs[peersUIId] = _window.__peersUIs[peersUIId];
|
|
255
|
+
});
|
|
256
|
+
resolveLoad();
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
_window.__peersUIBundleError = (errorFileId: string, errorMsg: string) => {
|
|
261
|
+
if (errorFileId === fileId) {
|
|
262
|
+
rejectLoad(new Error(errorMsg));
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
await rpcServerCalls.injectUIBundle(pkg.uiBundleFileId);
|
|
268
|
+
await loadPromise;
|
|
269
|
+
console.log(`finished loading ui bundle for ${pkg.name}: ${(Date.now() - sTime).toFixed(0)}ms`);
|
|
270
|
+
} else {
|
|
271
|
+
// Fallback: use postMessage-based getFileContents (slower for large bundles)
|
|
272
|
+
let bundleCode = await rpcServerCalls.getFileContents(pkg.uiBundleFileId);
|
|
273
|
+
if (bundleCode) {
|
|
274
|
+
const exportUIs = (peerUIs: IPeersPackageUIs) => {
|
|
275
|
+
peerUIs?.uis?.forEach(ui => {
|
|
276
|
+
peersUIs[ui.peersUIId] = ui;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
const bundleFunction = new Function('exportUIs', bundleCode);
|
|
280
|
+
await bundleFunction(exportUIs);
|
|
241
281
|
}
|
|
242
|
-
|
|
243
|
-
await bundleFunction(exportUIs);
|
|
282
|
+
console.log(`finished loading ui bundle for ${pkg.name}: ${(Date.now() - sTime).toFixed(0)}ms, ${(bundleCode.length/1000).toFixed(0)} KB`);
|
|
244
283
|
}
|
|
245
284
|
resolve();
|
|
246
285
|
} catch (err) {
|