@peers-app/peers-ui 0.19.12 → 0.19.13

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.
Files changed (53) hide show
  1. package/README.md +7 -2
  2. package/dist/components/voice-playback.d.ts +15 -0
  3. package/dist/components/voice-playback.js +86 -0
  4. package/dist/components/voice-subscribe-events.d.ts +0 -4
  5. package/dist/index.d.ts +10 -2
  6. package/dist/index.js +23 -2
  7. package/dist/operator-console/operator-console-state.d.ts +49 -0
  8. package/dist/operator-console/operator-console-state.js +97 -0
  9. package/dist/operator-console/operator-console.d.ts +32 -0
  10. package/dist/operator-console/operator-console.js +88 -0
  11. package/dist/root-layout/global-overlays.d.ts +37 -0
  12. package/dist/root-layout/global-overlays.js +37 -0
  13. package/dist/root-layout/layout-picker.d.ts +9 -0
  14. package/dist/root-layout/layout-picker.js +22 -0
  15. package/dist/root-layout/layouts/full-screen-layout.d.ts +12 -0
  16. package/dist/root-layout/layouts/full-screen-layout.js +60 -0
  17. package/dist/root-layout/layouts/tabs-with-console-layout.d.ts +20 -0
  18. package/dist/root-layout/layouts/tabs-with-console-layout.js +158 -0
  19. package/dist/root-layout/root-layout-host.d.ts +19 -0
  20. package/dist/root-layout/root-layout-host.js +88 -0
  21. package/dist/root-layout/root-layout-registry.d.ts +15 -0
  22. package/dist/root-layout/root-layout-registry.js +50 -0
  23. package/dist/root-layout/root-layout-state.d.ts +23 -0
  24. package/dist/root-layout/root-layout-state.js +54 -0
  25. package/dist/root-layout/root-layout.d.ts +30 -0
  26. package/dist/root-layout/root-layout.js +12 -0
  27. package/dist/screens/network-viewer/connection-troubleshooter.js +7 -11
  28. package/dist/screens/settings/settings-page.js +2 -1
  29. package/dist/tabs-layout/tabs-layout.d.ts +48 -4
  30. package/dist/tabs-layout/tabs-layout.js +27 -53
  31. package/dist/tabs-layout/tabs-state.d.ts +10 -0
  32. package/dist/tabs-layout/tabs-state.js +33 -1
  33. package/package.json +3 -3
  34. package/src/components/voice-playback.tsx +97 -0
  35. package/src/components/voice-subscribe-events.ts +0 -4
  36. package/src/index.tsx +24 -2
  37. package/src/operator-console/operator-console-state.ts +119 -0
  38. package/src/operator-console/operator-console.tsx +233 -0
  39. package/src/root-layout/global-overlays.ts +50 -0
  40. package/src/root-layout/layout-picker.tsx +47 -0
  41. package/src/root-layout/layouts/full-screen-layout.tsx +144 -0
  42. package/src/root-layout/layouts/tabs-with-console-layout.tsx +260 -0
  43. package/src/root-layout/root-layout-host.tsx +113 -0
  44. package/src/root-layout/root-layout-registry.ts +51 -0
  45. package/src/root-layout/root-layout-state.ts +55 -0
  46. package/src/root-layout/root-layout.ts +32 -0
  47. package/src/screens/network-viewer/connection-troubleshooter.tsx +10 -14
  48. package/src/screens/settings/settings-page.tsx +7 -1
  49. package/src/tabs-layout/tabs-layout.tsx +28 -74
  50. package/src/tabs-layout/tabs-state.ts +31 -0
  51. package/dist/components/chat-overlay.d.ts +0 -14
  52. package/dist/components/chat-overlay.js +0 -477
  53. package/src/components/chat-overlay.tsx +0 -854
@@ -1,23 +1,18 @@
1
1
  import {
2
2
  getUserContext,
3
- hasShownWelcomeModal,
4
3
  type IAppNav,
5
4
  rpcServerCalls,
6
5
  type Subscription,
7
- setDefaultClientUserContext,
8
6
  } from "@peers-app/peers-sdk";
9
7
  import React, { Component, useEffect, useState } from "react";
10
8
  import { openCommandPalette } from "../command-palette/command-palette";
11
- import { CommandPaletteOverlay } from "../command-palette/command-palette-ui";
12
9
  import { GroupSwitcher } from "../components/group-switcher";
13
10
  import { Router } from "../components/router";
14
- import { isDesktop, loadGlobals } from "../globals";
15
- import { useObservable, usePromise } from "../hooks";
11
+ import { isDesktop } from "../globals";
12
+ import { useObservable } from "../hooks";
16
13
  import { colorMode } from "../screens/settings/color-mode-dropdown";
17
- import { SetupUser } from "../screens/setup-user";
18
- import { WelcomeModal } from "../screens/welcome-modal";
19
14
  import { systemPackage } from "../system-apps";
20
- import { allPackages, loadAllRoutes } from "../ui-router/routes-loader";
15
+ import { allPackages } from "../ui-router/routes-loader";
21
16
  import { UIRouter } from "../ui-router/ui-loader";
22
17
  import {
23
18
  activeTabId,
@@ -41,7 +36,12 @@ interface TabErrorBoundaryState {
41
36
  error: Error | null;
42
37
  }
43
38
 
44
- class TabErrorBoundary extends Component<TabErrorBoundaryProps, TabErrorBoundaryState> {
39
+ /**
40
+ * Wraps a single tab's content so a crash in one tab doesn't take down the whole app.
41
+ * Exported so other root layouts (e.g. the full-screen layout) can render a tab body with
42
+ * the same crash isolation. Reset it across screens by giving it a `key`.
43
+ */
44
+ export class TabErrorBoundary extends Component<TabErrorBoundaryProps, TabErrorBoundaryState> {
45
45
  constructor(props: TabErrorBoundaryProps) {
46
46
  super(props);
47
47
  this.state = { hasError: false, error: null };
@@ -88,69 +88,18 @@ class TabErrorBoundary extends Component<TabErrorBoundaryProps, TabErrorBoundary
88
88
  }
89
89
  }
90
90
 
91
- export function TabsLayoutApp() {
92
- const userId = usePromise(async () => {
93
- const _userId = await rpcServerCalls.getUserId();
94
- return _userId || (null as string | null);
95
- });
96
-
97
- if (userId === undefined) {
98
- return;
99
- }
100
-
101
- if (!userId) {
102
- document.getElementById("appLoadingDiv")?.remove();
103
- return <SetupUser />;
104
- } else {
105
- return <TabsLayout userId={userId} />;
106
- }
107
- }
108
-
109
- let userContextInitialized = false;
110
- export function TabsLayout(props: { userId: string }) {
111
- if (!userContextInitialized) {
112
- setDefaultClientUserContext(props.userId);
113
- userContextInitialized = true;
114
- }
115
-
116
- const loaded = usePromise(async () => {
117
- await loadGlobals();
118
- await loadAllRoutes();
119
- await Promise.all([
120
- activeTabs.loadingPromise,
121
- activeTabId.loadingPromise,
122
- recentlyUsedApps.loadingPromise,
123
- hasShownWelcomeModal.loadingPromise,
124
- ]);
125
- return true;
126
- });
127
-
128
- const [showWelcomeModal, setShowWelcomeModal] = useState(false);
129
- const [_colorMode] = useObservable(colorMode);
130
-
131
- // Check if we should show the welcome modal after loading
132
- useEffect(() => {
133
- if (loaded && !hasShownWelcomeModal()) {
134
- setShowWelcomeModal(true);
135
- }
136
- }, [loaded]);
137
-
138
- if (!loaded) return false;
139
-
140
- document.getElementById("appLoadingDiv")?.remove();
141
-
142
- return (
143
- <>
144
- <TabsLayoutInternal />
145
- <CommandPaletteOverlay />
146
- {showWelcomeModal && (
147
- <WelcomeModal colorMode={_colorMode} onClose={() => setShowWelcomeModal(false)} />
148
- )}
149
- </>
150
- );
151
- }
152
-
153
- function TabsLayoutInternal() {
91
+ /**
92
+ * The tab UI body: the tab strip plus the active tab's content. Rendered as the body of
93
+ * the default `tabs` root layout inside `RootLayoutHost` (which owns bootstrapping, the
94
+ * command palette, and the welcome modal). Branches on `isDesktop` for mobile vs. desktop
95
+ * chrome.
96
+ *
97
+ * @param fillContainer When `true`, the body fills its parent (`height: 100%`) instead of
98
+ * sizing itself to the viewport. Set this when embedding the tabs UI inside another
99
+ * layout that owns the vertical space (e.g. the `tabs-with-console` split layout);
100
+ * leave it `false`/unset for the standalone `tabs` layout, which sizes to the viewport.
101
+ */
102
+ export function TabsLayoutInternal({ fillContainer = false }: { fillContainer?: boolean } = {}) {
154
103
  const [_colorMode] = useObservable(colorMode);
155
104
  const [tabs] = useObservable(activeTabs);
156
105
  const [activeTab] = useObservable(activeTabId);
@@ -209,7 +158,7 @@ function TabsLayoutInternal() {
209
158
  <div
210
159
  key={currentlyActiveGroupId}
211
160
  className="d-flex flex-column"
212
- style={{ height: "calc(100vh - 25px)", overflow: "hidden" }}
161
+ style={{ height: fillContainer ? "100%" : "calc(100vh - 25px)", overflow: "hidden" }}
213
162
  >
214
163
  {switchingGroup && (
215
164
  <div
@@ -561,7 +510,12 @@ interface TabContentProps {
561
510
  isActive: boolean;
562
511
  }
563
512
 
564
- function TabContent({ tab, isMobile, isActive }: TabContentProps) {
513
+ /**
514
+ * Renders a single tab's body, lazily initializing it on first activation and keeping it
515
+ * mounted thereafter (`initializedTabs`). Exported so other root layouts (e.g. full-screen)
516
+ * can render the active screen through the same routing/launcher path.
517
+ */
518
+ export function TabContent({ tab, isMobile, isActive }: TabContentProps) {
565
519
  // Only render content if this tab is active OR has been previously initialized
566
520
  const shouldRender = isActive || initializedTabs.has(tab.tabId);
567
521
 
@@ -244,6 +244,23 @@ export const updateActiveTabTitle = (newTitle: string) => {
244
244
  }, 100);
245
245
  };
246
246
 
247
+ // Single-tab mode: when enabled, opening an app replaces the current screen instead of
248
+ // appending a tab. Used by the full-screen root layout so it shows one screen at a time.
249
+ let singleTabModeEnabled = false;
250
+
251
+ /**
252
+ * Enable or disable single-tab mode.
253
+ *
254
+ * When enabled, {@link openNewTab} replaces the active (non-launcher) screen in place
255
+ * rather than appending a new tab, so single-screen layouts (e.g. full-screen) never
256
+ * accumulate tabs. This is non-destructive: any pre-existing tabs are left untouched and
257
+ * reappear when a multi-tab layout becomes active again. Layouts should enable it on mount
258
+ * and disable it on unmount.
259
+ */
260
+ export const setSingleTabMode = (enabled: boolean) => {
261
+ singleTabModeEnabled = enabled;
262
+ };
263
+
247
264
  // Global function to open content in new tab
248
265
  export const openNewTab = (tab: Omit<TabState, "tabId">, forceNew = false) => {
249
266
  const newTab: TabState = {
@@ -259,6 +276,20 @@ export const openNewTab = (tab: Omit<TabState, "tabId">, forceNew = false) => {
259
276
  if (existingTabIndex >= 0 && !forceNew) {
260
277
  // Switch to existing tab
261
278
  activeTabId(currentTabs[existingTabIndex].tabId);
279
+ } else if (singleTabModeEnabled) {
280
+ // Single-tab mode: replace the active non-launcher screen in place. From the launcher
281
+ // (home), append once so the launcher stays put and the new app becomes the single
282
+ // app slot.
283
+ const activeIndex = currentTabs.findIndex((t) => t.tabId === activeTabId());
284
+ const canReplace = activeIndex >= 0 && currentTabs[activeIndex].tabId !== "launcher";
285
+ if (canReplace) {
286
+ const newTabs = [...currentTabs];
287
+ newTabs[activeIndex] = newTab;
288
+ activeTabs(newTabs);
289
+ } else {
290
+ activeTabs([...currentTabs, newTab]);
291
+ }
292
+ activeTabId(newTab.tabId);
262
293
  } else {
263
294
  // Always add new tab (for app launcher or when forceNew is true)
264
295
  activeTabs([...currentTabs, newTab]);
@@ -1,14 +0,0 @@
1
- /**
2
- * Chat Overlay Component
3
- *
4
- * Floating UI element combining voice input with chat functionality.
5
- * Shows minimized voice button that expands to full chat overlay.
6
- * Supports both voice and text input in the same thread.
7
- */
8
- import type React from "react";
9
- interface ChatOverlayProps {
10
- /** Position of the overlay */
11
- position?: "bottom-right" | "bottom-left";
12
- }
13
- export declare const ChatOverlay: React.FC<ChatOverlayProps>;
14
- export default ChatOverlay;