@peers-app/peers-ui 0.25.2 → 0.25.4
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/README.md +2 -1
- package/dist/components/markdown-editor/mention-node.d.ts +4 -0
- package/dist/components/markdown-editor/mention-node.js +4 -1
- package/dist/components/responsive-section-nav.d.ts +34 -0
- package/dist/components/responsive-section-nav.js +19 -0
- package/dist/components/responsive-section-nav.test.d.ts +1 -0
- package/dist/components/responsive-section-nav.test.js +45 -0
- package/dist/components/router.d.ts +2 -3
- package/dist/components/router.js +6 -9
- package/dist/globals-hash.test.d.ts +1 -0
- package/dist/globals-hash.test.js +16 -0
- package/dist/globals.d.ts +19 -0
- package/dist/globals.js +51 -12
- package/dist/hooks/use-host-contract.d.ts +18 -0
- package/dist/hooks/use-host-contract.js +53 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/peers-services-registration.d.ts +1 -5
- package/dist/peers-services-registration.js +6 -16
- package/dist/root-layout/root-layout-host.js +7 -4
- package/dist/root-layout/root-layout-host.test.js +3 -6
- package/dist/screens/account/account-screen.d.ts +1 -0
- package/dist/screens/account/account-screen.js +29 -53
- package/dist/screens/account/account-screen.test.d.ts +1 -0
- package/dist/screens/account/account-screen.test.js +47 -0
- package/dist/screens/contacts/contact-details.js +2 -23
- package/dist/screens/contacts/contact-list.d.ts +24 -1
- package/dist/screens/contacts/contact-list.js +128 -143
- package/dist/screens/contacts/contact-list.test.d.ts +1 -0
- package/dist/screens/contacts/contact-list.test.js +48 -0
- package/dist/screens/contacts/index.d.ts +0 -2
- package/dist/screens/contacts/index.js +0 -3
- package/dist/screens/device-pairing/device-pairing.js +17 -16
- package/dist/screens/device-pairing/device-pairing.test.js +4 -9
- package/dist/screens/device-pairing/identity-ipc.d.ts +11 -0
- package/dist/screens/device-pairing/identity-ipc.js +20 -0
- package/dist/screens/device-pairing/use-device-pairing-state.d.ts +2 -2
- package/dist/screens/device-pairing/use-device-pairing-state.js +7 -25
- package/dist/screens/devices/devices-list.d.ts +18 -0
- package/dist/screens/devices/devices-list.js +173 -0
- package/dist/screens/devices/devices-list.test.d.ts +1 -0
- package/dist/screens/devices/devices-list.test.js +53 -0
- package/dist/screens/devices/index.d.ts +1 -0
- package/dist/screens/devices/index.js +5 -0
- package/dist/screens/groups/group-details.d.ts +1 -0
- package/dist/screens/groups/group-details.js +4 -63
- package/dist/screens/groups/group-invite-panel.js +2 -2
- package/dist/screens/groups/group-list.d.ts +18 -1
- package/dist/screens/groups/group-list.js +107 -136
- package/dist/screens/groups/group-list.test.d.ts +1 -0
- package/dist/screens/groups/group-list.test.js +38 -0
- package/dist/screens/groups/group-members.d.ts +1 -0
- package/dist/screens/groups/group-members.js +2 -1
- package/dist/screens/groups/index.d.ts +0 -3
- package/dist/screens/groups/index.js +0 -4
- package/dist/screens/identity/identity-routes.d.ts +107 -0
- package/dist/screens/identity/identity-routes.js +245 -0
- package/dist/screens/identity/identity-routes.test.d.ts +1 -0
- package/dist/screens/identity/identity-routes.test.js +51 -0
- package/dist/screens/identity/identity-screen.d.ts +9 -0
- package/dist/screens/identity/identity-screen.js +143 -0
- package/dist/screens/identity/identity-screen.test.d.ts +1 -0
- package/dist/screens/identity/identity-screen.test.js +103 -0
- package/dist/screens/identity/index.d.ts +2 -0
- package/dist/screens/identity/index.js +6 -0
- package/dist/screens/identity/invite-contact-panel.d.ts +4 -0
- package/dist/screens/identity/invite-contact-panel.js +30 -0
- package/dist/screens/identity/profile-invite.d.ts +7 -0
- package/dist/screens/identity/profile-invite.js +62 -0
- package/dist/screens/invites/accept-invite.js +44 -2
- package/dist/screens/invites/accept-invite.test.d.ts +1 -0
- package/dist/screens/invites/accept-invite.test.js +103 -0
- package/dist/screens/invites/activity-view.d.ts +24 -0
- package/dist/screens/invites/activity-view.js +64 -0
- package/dist/screens/invites/activity-view.test.d.ts +1 -0
- package/dist/screens/invites/activity-view.test.js +108 -0
- package/dist/screens/invites/index.d.ts +1 -2
- package/dist/screens/invites/index.js +1 -3
- package/dist/screens/invites/invite-capture.d.ts +0 -2
- package/dist/screens/invites/invite-capture.js +1 -3
- package/dist/screens/invites/invite-classify.test.d.ts +1 -0
- package/dist/screens/invites/invite-classify.test.js +115 -0
- package/dist/screens/invites/invites-ipc.d.ts +1 -1
- package/dist/screens/invites/invites-ipc.js +5 -45
- package/dist/screens/invites/pending-invites-list.d.ts +35 -16
- package/dist/screens/invites/pending-invites-list.js +75 -30
- package/dist/screens/network-viewer/network-viewer-ipc.js +4 -59
- package/dist/screens/search/global-search.d.ts +1 -0
- package/dist/screens/search/global-search.js +2 -1
- package/dist/screens/settings/settings-page.js +25 -58
- package/dist/screens/setup-user.js +5 -5
- package/dist/screens/tools/tool-access-ipc.d.ts +2 -0
- package/dist/screens/tools/tool-access-ipc.js +8 -55
- package/dist/screens/welcome-modal.js +3 -2
- package/dist/system-apps/identity.app.d.ts +3 -0
- package/dist/system-apps/identity.app.js +10 -0
- package/dist/system-apps/index.d.ts +1 -4
- package/dist/system-apps/index.js +5 -17
- package/dist/tabs-layout/network-status-indicator.js +10 -3
- package/dist/tabs-layout/network-status-indicator.test.js +15 -0
- package/dist/tabs-layout/tabs-layout.js +3 -1
- package/dist/tabs-layout/tabs-state.d.ts +79 -1
- package/dist/tabs-layout/tabs-state.js +256 -79
- package/dist/tabs-layout/tabs-state.test.d.ts +1 -0
- package/dist/tabs-layout/tabs-state.test.js +110 -0
- package/package.json +3 -3
- package/src/components/markdown-editor/mention-node.ts +5 -1
- package/src/components/responsive-section-nav.test.tsx +88 -0
- package/src/components/responsive-section-nav.tsx +92 -0
- package/src/components/router.tsx +6 -9
- package/src/globals-hash.test.ts +19 -0
- package/src/globals.tsx +53 -13
- package/src/hooks/use-host-contract.ts +75 -0
- package/src/index.tsx +1 -0
- package/src/peers-services-registration.ts +6 -17
- package/src/root-layout/root-layout-host.test.tsx +4 -7
- package/src/root-layout/root-layout-host.tsx +9 -11
- package/src/screens/account/account-screen.test.tsx +61 -0
- package/src/screens/account/account-screen.tsx +116 -95
- package/src/screens/contacts/contact-details.tsx +4 -26
- package/src/screens/contacts/contact-list.test.tsx +59 -0
- package/src/screens/contacts/contact-list.tsx +246 -260
- package/src/screens/contacts/index.ts +0 -4
- package/src/screens/device-pairing/device-pairing.test.tsx +7 -9
- package/src/screens/device-pairing/device-pairing.tsx +19 -23
- package/src/screens/device-pairing/identity-ipc.ts +25 -0
- package/src/screens/device-pairing/use-device-pairing-state.ts +10 -33
- package/src/screens/devices/devices-list.test.tsx +68 -0
- package/src/screens/devices/devices-list.tsx +361 -0
- package/src/screens/devices/index.ts +1 -0
- package/src/screens/groups/group-details.tsx +8 -142
- package/src/screens/groups/group-invite-panel.tsx +2 -7
- package/src/screens/groups/group-list.test.tsx +44 -0
- package/src/screens/groups/group-list.tsx +227 -288
- package/src/screens/groups/group-members.tsx +3 -2
- package/src/screens/groups/index.ts +0 -5
- package/src/screens/identity/identity-routes.test.ts +70 -0
- package/src/screens/identity/identity-routes.ts +301 -0
- package/src/screens/identity/identity-screen.test.tsx +138 -0
- package/src/screens/identity/identity-screen.tsx +282 -0
- package/src/screens/identity/index.ts +2 -0
- package/src/screens/identity/invite-contact-panel.tsx +45 -0
- package/src/screens/identity/profile-invite.tsx +93 -0
- package/src/screens/invites/accept-invite.test.tsx +115 -0
- package/src/screens/invites/accept-invite.tsx +50 -3
- package/src/screens/invites/activity-view.test.tsx +127 -0
- package/src/screens/invites/activity-view.tsx +162 -0
- package/src/screens/invites/index.ts +1 -4
- package/src/screens/invites/invite-capture.ts +0 -3
- package/src/screens/invites/invite-classify.test.ts +129 -0
- package/src/screens/invites/invites-ipc.ts +6 -55
- package/src/screens/invites/pending-invites-list.tsx +94 -62
- package/src/screens/network-viewer/network-viewer-ipc.ts +8 -82
- package/src/screens/search/global-search.tsx +2 -1
- package/src/screens/settings/settings-page.tsx +53 -126
- package/src/screens/setup-user.tsx +5 -6
- package/src/screens/tools/tool-access-ipc.ts +13 -74
- package/src/screens/welcome-modal.tsx +3 -3
- package/src/system-apps/identity.app.ts +9 -0
- package/src/system-apps/index.ts +3 -12
- package/src/tabs-layout/network-status-indicator.test.tsx +22 -0
- package/src/tabs-layout/network-status-indicator.tsx +31 -0
- package/src/tabs-layout/tabs-layout.tsx +7 -1
- package/src/tabs-layout/tabs-state.test.ts +152 -0
- package/src/tabs-layout/tabs-state.ts +300 -83
- package/dist/screens/invites/invites-screen.d.ts +0 -9
- package/dist/screens/invites/invites-screen.js +0 -36
- package/dist/system-apps/account.app.d.ts +0 -2
- package/dist/system-apps/account.app.js +0 -8
- package/dist/system-apps/contacts.app.d.ts +0 -2
- package/dist/system-apps/contacts.app.js +0 -9
- package/dist/system-apps/groups.app.d.ts +0 -2
- package/dist/system-apps/groups.app.js +0 -9
- package/dist/system-apps/invites.app.d.ts +0 -2
- package/dist/system-apps/invites.app.js +0 -9
- package/src/screens/invites/invites-screen.tsx +0 -78
- package/src/system-apps/account.app.ts +0 -7
- package/src/system-apps/contacts.app.ts +0 -8
- package/src/system-apps/groups.app.ts +0 -8
- package/src/system-apps/invites.app.ts +0 -8
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
newid,
|
|
5
5
|
newKeys,
|
|
6
6
|
openMessageWithPublicKey,
|
|
7
|
-
rpcServerCalls,
|
|
8
7
|
signMessageWithSecretKey,
|
|
9
8
|
} from "@peers-app/peers-sdk";
|
|
10
9
|
import { useState } from "react";
|
|
11
10
|
import { useObservable } from "../hooks";
|
|
12
11
|
import { DestinationDevicePairing } from "./device-pairing";
|
|
12
|
+
import { identityTools } from "./device-pairing/identity-ipc";
|
|
13
13
|
import { colorMode } from "./settings/color-mode-dropdown";
|
|
14
14
|
|
|
15
15
|
type SetupStep = "select-user-type" | "complete";
|
|
@@ -37,9 +37,9 @@ export const SetupUser = () => {
|
|
|
37
37
|
const keys = newKeys();
|
|
38
38
|
const _userId = newid();
|
|
39
39
|
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
await
|
|
40
|
+
// signIn triggers device init, which installs peers-core into the personal
|
|
41
|
+
// context (S3-first, bundle fallback).
|
|
42
|
+
await (await identityTools()).signIn({ userId: _userId, secretKey: keys.secretKey });
|
|
43
43
|
|
|
44
44
|
// Success - reload the app (peers-services registration runs after Welcome modal)
|
|
45
45
|
window.location.reload();
|
|
@@ -76,8 +76,7 @@ export const SetupUser = () => {
|
|
|
76
76
|
throw new Error("Secret key validation failed");
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
await rpcServerCalls.setUserIdAndSecretKey(userId, keys.secretKey);
|
|
79
|
+
await (await identityTools()).signIn({ userId, secretKey: keys.secretKey });
|
|
81
80
|
|
|
82
81
|
// Success - reload the app
|
|
83
82
|
window.location.reload();
|
|
@@ -1,88 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createContractConsumer,
|
|
3
|
-
getHostContractTransport,
|
|
4
2
|
getToolAccessControlContractDefinition,
|
|
5
|
-
|
|
3
|
+
hostContractConsumer,
|
|
4
|
+
type IHostContractConsumer,
|
|
6
5
|
type IToolAccessControlConsumer,
|
|
7
|
-
subscribeHostContractTransport,
|
|
8
|
-
waitForHostContractTransport,
|
|
9
6
|
} from "@peers-app/peers-sdk";
|
|
10
7
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
type CachedConsumer = {
|
|
14
|
-
transport: IContractTransport;
|
|
15
|
-
consumer: IToolAccessControlConsumer;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const cachedByContext = new Map<string, CachedConsumer>();
|
|
19
|
-
const pendingByContext = new Map<string, Promise<IToolAccessControlConsumer>>();
|
|
20
|
-
|
|
21
|
-
function disposeCachedConsumers(): void {
|
|
22
|
-
for (const cached of cachedByContext.values()) {
|
|
23
|
-
cached.consumer.dispose();
|
|
24
|
-
}
|
|
25
|
-
cachedByContext.clear();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
subscribeHostContractTransport(
|
|
29
|
-
(transport) => {
|
|
30
|
-
if ([...cachedByContext.values()].some((cached) => cached.transport !== transport)) {
|
|
31
|
-
disposeCachedConsumers();
|
|
32
|
-
pendingByContext.clear();
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
{ emitCurrent: false },
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
async function loadConsumer(
|
|
39
|
-
transport: IContractTransport,
|
|
40
|
-
dataContextId: string,
|
|
41
|
-
): Promise<IToolAccessControlConsumer> {
|
|
42
|
-
cachedByContext.get(dataContextId)?.consumer.dispose();
|
|
43
|
-
cachedByContext.delete(dataContextId);
|
|
44
|
-
const consumer = createContractConsumer(getToolAccessControlContractDefinition(), transport, {
|
|
45
|
-
dataContextId,
|
|
46
|
-
}) as unknown as IToolAccessControlConsumer;
|
|
47
|
-
try {
|
|
48
|
-
await consumer.loadingPromise;
|
|
49
|
-
if (getHostContractTransport() !== transport) {
|
|
50
|
-
consumer.dispose();
|
|
51
|
-
pendingByContext.delete(dataContextId);
|
|
52
|
-
return getToolAccessControlConsumer(dataContextId);
|
|
53
|
-
}
|
|
54
|
-
cachedByContext.set(dataContextId, { transport, consumer });
|
|
55
|
-
return consumer;
|
|
56
|
-
} catch (error) {
|
|
57
|
-
consumer.dispose();
|
|
58
|
-
throw error;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
8
|
+
const consumers = new Map<string, IHostContractConsumer<IToolAccessControlConsumer>>();
|
|
61
9
|
|
|
62
10
|
/**
|
|
63
11
|
* Return a typed local-host consumer for Tool Access Control in `dataContextId`.
|
|
12
|
+
*
|
|
13
|
+
* One consumer is cached per data context and rebuilt when the host transport changes.
|
|
64
14
|
*/
|
|
65
15
|
export async function getToolAccessControlConsumer(
|
|
66
16
|
dataContextId: string,
|
|
67
17
|
): Promise<IToolAccessControlConsumer> {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const pending = pendingByContext.get(dataContextId);
|
|
76
|
-
if (pending) {
|
|
77
|
-
return pending;
|
|
78
|
-
}
|
|
79
|
-
const loadPromise = loadConsumer(transport, dataContextId);
|
|
80
|
-
pendingByContext.set(dataContextId, loadPromise);
|
|
81
|
-
try {
|
|
82
|
-
return await loadPromise;
|
|
83
|
-
} finally {
|
|
84
|
-
if (pendingByContext.get(dataContextId) === loadPromise) {
|
|
85
|
-
pendingByContext.delete(dataContextId);
|
|
86
|
-
}
|
|
18
|
+
let handle = consumers.get(dataContextId);
|
|
19
|
+
if (!handle) {
|
|
20
|
+
handle = hostContractConsumer<IToolAccessControlConsumer>(
|
|
21
|
+
getToolAccessControlContractDefinition(),
|
|
22
|
+
{ dataContextId: async () => dataContextId },
|
|
23
|
+
);
|
|
24
|
+
consumers.set(dataContextId, handle);
|
|
87
25
|
}
|
|
26
|
+
return handle.get();
|
|
88
27
|
}
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
getUserContext,
|
|
6
6
|
hasShownWelcomeModal,
|
|
7
7
|
type IDevice,
|
|
8
|
-
rpcServerCalls,
|
|
9
8
|
TrustLevel,
|
|
10
9
|
thisDeviceId,
|
|
11
10
|
Users,
|
|
@@ -14,6 +13,7 @@ import type React from "react";
|
|
|
14
13
|
import { useEffect, useState } from "react";
|
|
15
14
|
import { useObservable, usePromise } from "../hooks";
|
|
16
15
|
import { persistPeersServicesRegistration } from "../peers-services-registration";
|
|
16
|
+
import { identityTools } from "./device-pairing/identity-ipc";
|
|
17
17
|
|
|
18
18
|
interface WelcomeModalProps {
|
|
19
19
|
colorMode: string;
|
|
@@ -22,8 +22,8 @@ interface WelcomeModalProps {
|
|
|
22
22
|
|
|
23
23
|
async function tryRegisterPeersServicesAfterWelcome(): Promise<void> {
|
|
24
24
|
try {
|
|
25
|
-
const token = await
|
|
26
|
-
persistPeersServicesRegistration(token);
|
|
25
|
+
const { token } = await (await identityTools()).registerWithPeersServices();
|
|
26
|
+
await persistPeersServicesRegistration(token);
|
|
27
27
|
} catch (err: unknown) {
|
|
28
28
|
console.warn("Peers services registration after welcome modal:", err);
|
|
29
29
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IAppNav } from "@peers-app/peers-sdk";
|
|
2
|
+
|
|
3
|
+
/** Identity system app: activity, people, groups, devices, and account. */
|
|
4
|
+
export const identityApp: IAppNav = {
|
|
5
|
+
name: "Identity",
|
|
6
|
+
displayName: "Identity",
|
|
7
|
+
iconClassName: "bi-person-badge",
|
|
8
|
+
navigationPath: "identity",
|
|
9
|
+
};
|
package/src/system-apps/index.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import type { IAppNav } from "@peers-app/peers-sdk";
|
|
2
2
|
import type { IPackageWithNavs } from "../ui-router/routes-loader";
|
|
3
3
|
|
|
4
|
-
export { accountApp } from "./account.app";
|
|
5
4
|
export { assistantsApp } from "./assistants.app";
|
|
6
5
|
export { consoleLogsApp } from "./console-logs.app";
|
|
7
|
-
export { contactsApp } from "./contacts.app";
|
|
8
6
|
export { dataExplorerApp } from "./data-explorer.app";
|
|
9
|
-
export {
|
|
10
|
-
export { invitesApp } from "./invites.app";
|
|
7
|
+
export { identityApp } from "./identity.app";
|
|
11
8
|
export { mobileSettingsApp } from "./mobile-settings.app";
|
|
12
9
|
export { networkViewerApp } from "./network-viewer.app";
|
|
13
10
|
export { packagesApp } from "./packages.app";
|
|
@@ -20,13 +17,10 @@ export { typesApp } from "./types.app";
|
|
|
20
17
|
export { variablesApp } from "./variables.app";
|
|
21
18
|
export { workflowsApp } from "./workflows.app";
|
|
22
19
|
|
|
23
|
-
import { accountApp } from "./account.app";
|
|
24
20
|
import { assistantsApp } from "./assistants.app";
|
|
25
21
|
import { consoleLogsApp } from "./console-logs.app";
|
|
26
|
-
import { contactsApp } from "./contacts.app";
|
|
27
22
|
import { dataExplorerApp } from "./data-explorer.app";
|
|
28
|
-
import {
|
|
29
|
-
import { invitesApp } from "./invites.app";
|
|
23
|
+
import { identityApp } from "./identity.app";
|
|
30
24
|
import { mobileSettingsApp } from "./mobile-settings.app";
|
|
31
25
|
import { networkViewerApp } from "./network-viewer.app";
|
|
32
26
|
import { packagesApp } from "./packages.app";
|
|
@@ -62,12 +56,9 @@ export const systemApps: IAppNav[] = [
|
|
|
62
56
|
workflowsApp,
|
|
63
57
|
assistantsApp,
|
|
64
58
|
packagesApp,
|
|
65
|
-
|
|
66
|
-
groupsApp,
|
|
67
|
-
invitesApp,
|
|
59
|
+
identityApp,
|
|
68
60
|
|
|
69
61
|
// User & Settings Apps
|
|
70
|
-
accountApp,
|
|
71
62
|
settingsApp,
|
|
72
63
|
// Mobile Settings (only in React Native)
|
|
73
64
|
...(isReactNative() ? [mobileSettingsApp] : []),
|
|
@@ -2,6 +2,7 @@ import type { INetworkConnectionInfo, INetworkStatusSummary } from "@peers-app/p
|
|
|
2
2
|
import { TrustLevel } from "@peers-app/peers-sdk";
|
|
3
3
|
import { act, fireEvent, render, waitFor } from "@testing-library/react";
|
|
4
4
|
import moment from "moment";
|
|
5
|
+
import { IDENTITY_ACCEPT_PATH, IDENTITY_ACCOUNT_PATH } from "../screens/identity/identity-routes";
|
|
5
6
|
import {
|
|
6
7
|
connectionIdentityLabel,
|
|
7
8
|
formatRelativeSyncTime,
|
|
@@ -496,6 +497,27 @@ describe("NetworkStatusIndicator", () => {
|
|
|
496
497
|
});
|
|
497
498
|
});
|
|
498
499
|
|
|
500
|
+
it("offers shortcuts to use an invite and open the Identity account", () => {
|
|
501
|
+
mockViewState = { availability: "ready", status: status({ state: "synced" }) };
|
|
502
|
+
const view = render(<NetworkStatusIndicator />);
|
|
503
|
+
|
|
504
|
+
fireEvent.click(view.getByRole("button", { name: /Network synced/ }));
|
|
505
|
+
expect(
|
|
506
|
+
view.getByRole("button", { name: "Use Invite" }).querySelector(".bi-qr-code"),
|
|
507
|
+
).toBeTruthy();
|
|
508
|
+
expect(
|
|
509
|
+
view.getByRole("button", { name: "Identity" }).querySelector(".bi-person-badge"),
|
|
510
|
+
).toBeTruthy();
|
|
511
|
+
fireEvent.click(view.getByRole("button", { name: "Use Invite" }));
|
|
512
|
+
expect(goToTabPath).toHaveBeenCalledWith(IDENTITY_ACCEPT_PATH);
|
|
513
|
+
expect(view.queryByRole("dialog", { name: "Network status details" })).toBeNull();
|
|
514
|
+
|
|
515
|
+
fireEvent.click(view.getByRole("button", { name: /Network synced/ }));
|
|
516
|
+
fireEvent.click(view.getByRole("button", { name: "Identity" }));
|
|
517
|
+
expect(goToTabPath).toHaveBeenCalledWith(IDENTITY_ACCOUNT_PATH);
|
|
518
|
+
expect(view.queryByRole("dialog", { name: "Network status details" })).toBeNull();
|
|
519
|
+
});
|
|
520
|
+
|
|
499
521
|
it("updates on rerender and opens details before navigating to Network Viewer", async () => {
|
|
500
522
|
const view = render(<NetworkStatusIndicator />);
|
|
501
523
|
expect(view.getByRole("button", { name: /loading/ })).toBeTruthy();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { INetworkConnectionInfo, INetworkStatusSummary } from "@peers-app/peers-sdk";
|
|
2
2
|
import moment from "moment";
|
|
3
3
|
import { useEffect, useId, useRef, useState } from "react";
|
|
4
|
+
import { IDENTITY_ACCEPT_PATH, IDENTITY_ACCOUNT_PATH } from "../screens/identity/identity-routes";
|
|
4
5
|
import { getNetworkDiagnosticsConsumer } from "../screens/network-viewer/network-viewer-ipc";
|
|
5
6
|
import { goToTabPath } from "./tabs-state";
|
|
6
7
|
import { type INetworkStatusViewState, useNetworkStatus } from "./use-network-status";
|
|
@@ -311,12 +312,16 @@ function NetworkStatusDetails({
|
|
|
311
312
|
status,
|
|
312
313
|
unavailable,
|
|
313
314
|
connectionsLoadState,
|
|
315
|
+
onUseInvite,
|
|
316
|
+
onOpenIdentity,
|
|
314
317
|
onOpenViewer,
|
|
315
318
|
}: {
|
|
316
319
|
detailsId: string;
|
|
317
320
|
status?: INetworkStatusSummary;
|
|
318
321
|
unavailable: boolean;
|
|
319
322
|
connectionsLoadState: ConnectionsLoadState;
|
|
323
|
+
onUseInvite: () => void;
|
|
324
|
+
onOpenIdentity: () => void;
|
|
320
325
|
onOpenViewer: () => void;
|
|
321
326
|
}) {
|
|
322
327
|
return (
|
|
@@ -328,6 +333,24 @@ function NetworkStatusDetails({
|
|
|
328
333
|
style={{ top: "100%", width: "320px", zIndex: 1100 }}
|
|
329
334
|
>
|
|
330
335
|
<div className="card-body p-3">
|
|
336
|
+
<div className="d-flex gap-2 mb-3">
|
|
337
|
+
<button
|
|
338
|
+
type="button"
|
|
339
|
+
className="btn btn-sm btn-outline-primary flex-fill"
|
|
340
|
+
onClick={onUseInvite}
|
|
341
|
+
>
|
|
342
|
+
<i className="bi bi-qr-code me-1" aria-hidden="true" />
|
|
343
|
+
Use Invite
|
|
344
|
+
</button>
|
|
345
|
+
<button
|
|
346
|
+
type="button"
|
|
347
|
+
className="btn btn-sm btn-outline-primary flex-fill"
|
|
348
|
+
onClick={onOpenIdentity}
|
|
349
|
+
>
|
|
350
|
+
<i className="bi bi-person-badge me-1" aria-hidden="true" />
|
|
351
|
+
Identity
|
|
352
|
+
</button>
|
|
353
|
+
</div>
|
|
331
354
|
<h6 className="card-title mb-2">Network status</h6>
|
|
332
355
|
{unavailable || !status ? (
|
|
333
356
|
<p className="small text-muted mb-3">
|
|
@@ -559,6 +582,14 @@ export function NetworkStatusIndicator({ compact = false }: { compact?: boolean
|
|
|
559
582
|
status={viewState.status}
|
|
560
583
|
unavailable={viewState.availability !== "ready"}
|
|
561
584
|
connectionsLoadState={connectionsLoadState}
|
|
585
|
+
onUseInvite={() => {
|
|
586
|
+
setOpen(false);
|
|
587
|
+
goToTabPath(IDENTITY_ACCEPT_PATH);
|
|
588
|
+
}}
|
|
589
|
+
onOpenIdentity={() => {
|
|
590
|
+
setOpen(false);
|
|
591
|
+
goToTabPath(IDENTITY_ACCOUNT_PATH);
|
|
592
|
+
}}
|
|
562
593
|
onOpenViewer={() => {
|
|
563
594
|
setOpen(false);
|
|
564
595
|
goToTabPath("network-viewer");
|
|
@@ -10,6 +10,7 @@ import { GroupSwitcher } from "../components/group-switcher";
|
|
|
10
10
|
import { Router } from "../components/router";
|
|
11
11
|
import { isDesktop } from "../globals";
|
|
12
12
|
import { useObservable } from "../hooks";
|
|
13
|
+
import { isIdentityPath } from "../screens/identity/identity-routes";
|
|
13
14
|
import { colorMode } from "../screens/settings/color-mode-dropdown";
|
|
14
15
|
import { systemPackage } from "../system-apps";
|
|
15
16
|
import { allPackages } from "../ui-router/routes-loader";
|
|
@@ -546,9 +547,14 @@ const TabContentRenderer = React.memo(
|
|
|
546
547
|
// System apps use the main Router (which reads from mainContentPath)
|
|
547
548
|
// Package apps use UIRouter with explicit path
|
|
548
549
|
const isSystemApp = tab.packageId === "system-apps";
|
|
550
|
+
const identityApp = isSystemApp && isIdentityPath(tab.path);
|
|
549
551
|
|
|
550
552
|
return (
|
|
551
|
-
<div
|
|
553
|
+
<div
|
|
554
|
+
className={`h-100 ${identityApp ? "overflow-hidden" : "overflow-auto"} ${
|
|
555
|
+
identityApp ? "" : isMobile ? "p-2" : "p-3"
|
|
556
|
+
}`}
|
|
557
|
+
>
|
|
552
558
|
{isSystemApp ? (
|
|
553
559
|
<Router path={tab.path} />
|
|
554
560
|
) : (
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
jest.mock("../globals", () => {
|
|
2
|
+
const { observable } =
|
|
3
|
+
jest.requireActual<typeof import("@peers-app/peers-sdk")>("@peers-app/peers-sdk");
|
|
4
|
+
return { _mainContentPath: observable("") };
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
jest.mock("../ui-router/routes-loader", () => {
|
|
8
|
+
const { observable } =
|
|
9
|
+
jest.requireActual<typeof import("@peers-app/peers-sdk")>("@peers-app/peers-sdk");
|
|
10
|
+
return { allPackages: observable([]) };
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
jest.mock("@peers-app/peers-sdk", () => {
|
|
14
|
+
const actual = jest.requireActual<typeof import("@peers-app/peers-sdk")>("@peers-app/peers-sdk");
|
|
15
|
+
let nextId = 0;
|
|
16
|
+
const deviceVars = new Map<string, ReturnType<typeof actual.observable>>();
|
|
17
|
+
const userVars = new Map<string, ReturnType<typeof actual.observable>>();
|
|
18
|
+
|
|
19
|
+
function stored<T>(
|
|
20
|
+
map: Map<string, ReturnType<typeof actual.observable>>,
|
|
21
|
+
id: string,
|
|
22
|
+
initial: T,
|
|
23
|
+
) {
|
|
24
|
+
const existing = map.get(id);
|
|
25
|
+
if (existing) {
|
|
26
|
+
return existing;
|
|
27
|
+
}
|
|
28
|
+
const value = actual.observable(initial);
|
|
29
|
+
Object.assign(value, { loadingPromise: Promise.resolve() });
|
|
30
|
+
map.set(id, value);
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
...actual,
|
|
36
|
+
newid: () => `tab${++nextId}`,
|
|
37
|
+
groupDeviceVar: (id: string, options?: { defaultValue?: unknown }) =>
|
|
38
|
+
stored(deviceVars, id, options?.defaultValue),
|
|
39
|
+
groupUserVar: (id: string, options?: { defaultValue?: unknown }) =>
|
|
40
|
+
stored(userVars, id, options?.defaultValue),
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
import { _mainContentPath } from "../globals";
|
|
45
|
+
import {
|
|
46
|
+
activeTabId,
|
|
47
|
+
activeTabs,
|
|
48
|
+
goToTabPath,
|
|
49
|
+
launcherApp,
|
|
50
|
+
migrateRecentPath,
|
|
51
|
+
navigateTo,
|
|
52
|
+
recentlyUsedApps,
|
|
53
|
+
restoreNavigationState,
|
|
54
|
+
type TabState,
|
|
55
|
+
} from "./tabs-state";
|
|
56
|
+
|
|
57
|
+
const PERSON_ID = "abcdefghijklmnopqrstuvwxy";
|
|
58
|
+
|
|
59
|
+
function tab(partial: Partial<TabState> & Pick<TabState, "tabId" | "path">): TabState {
|
|
60
|
+
return {
|
|
61
|
+
packageId: "system-apps",
|
|
62
|
+
title: partial.path,
|
|
63
|
+
iconClassName: "bi-circle",
|
|
64
|
+
...partial,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
describe("restoreNavigationState", () => {
|
|
69
|
+
it("merges legacy identity tabs into the active Identity tab", () => {
|
|
70
|
+
const restored = restoreNavigationState(
|
|
71
|
+
[
|
|
72
|
+
launcherApp,
|
|
73
|
+
tab({ tabId: "people", path: "contacts", title: "Contacts" }),
|
|
74
|
+
tab({ tabId: "groups", path: `groups/${PERSON_ID}`, title: "Groups" }),
|
|
75
|
+
tab({ tabId: "devices", path: "devices?action=pair", title: "Devices" }),
|
|
76
|
+
tab({ tabId: "account", path: "account", title: "Account" }),
|
|
77
|
+
],
|
|
78
|
+
"groups",
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(restored.tabs.map((entry) => entry.path)).toEqual(["", `identity/groups/${PERSON_ID}`]);
|
|
82
|
+
expect(restored.activeId).toBe("groups");
|
|
83
|
+
expect(restored.tabs[1]?.title).toBe("Identity");
|
|
84
|
+
expect(migrateRecentPath("contacts")).toBe("identity");
|
|
85
|
+
expect(migrateRecentPath("invites/accept")).toBe("identity");
|
|
86
|
+
expect(migrateRecentPath("account")).toBe("identity");
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("navigateTo", () => {
|
|
91
|
+
beforeEach(async () => {
|
|
92
|
+
await activeTabs.loadingPromise;
|
|
93
|
+
activeTabs([launcherApp, tab({ tabId: "threads", path: "threads", title: "Threads" })]);
|
|
94
|
+
activeTabId("threads");
|
|
95
|
+
recentlyUsedApps([]);
|
|
96
|
+
_mainContentPath("threads");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("updates the same app in place and preserves the query", () => {
|
|
100
|
+
navigateTo("threads/thread-1?draft=1");
|
|
101
|
+
|
|
102
|
+
expect(activeTabs()).toHaveLength(2);
|
|
103
|
+
expect(activeTabs().find((entry) => entry.tabId === "threads")?.path).toBe(
|
|
104
|
+
"threads/thread-1?draft=1",
|
|
105
|
+
);
|
|
106
|
+
expect(activeTabId()).toBe("threads");
|
|
107
|
+
expect(_mainContentPath()).toBe("threads/thread-1?draft=1");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("opens another app without duplicating an existing tab", () => {
|
|
111
|
+
navigateTo(`contacts/${PERSON_ID}`);
|
|
112
|
+
const identityId = activeTabId();
|
|
113
|
+
activeTabId("threads");
|
|
114
|
+
navigateTo("identity/devices?foo=bar");
|
|
115
|
+
|
|
116
|
+
const identityTabs = activeTabs().filter((entry) => entry.path.startsWith("identity"));
|
|
117
|
+
expect(identityTabs).toHaveLength(1);
|
|
118
|
+
expect(identityTabs[0]?.path).toBe("identity/devices?foo=bar");
|
|
119
|
+
expect(activeTabId()).toBe(identityId);
|
|
120
|
+
expect(activeTabs().some((entry) => entry.tabId === "threads")).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("opens the account alias inside the Identity tab", () => {
|
|
124
|
+
navigateTo("account");
|
|
125
|
+
const identityId = activeTabId();
|
|
126
|
+
|
|
127
|
+
expect(_mainContentPath()).toBe("identity/account");
|
|
128
|
+
navigateTo("identity/people");
|
|
129
|
+
|
|
130
|
+
expect(activeTabId()).toBe(identityId);
|
|
131
|
+
expect(activeTabs().filter((entry) => entry.path.startsWith("identity"))).toHaveLength(1);
|
|
132
|
+
expect(_mainContentPath()).toBe("identity/people");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("rewrites invite acceptance onto the open Identity tab", () => {
|
|
136
|
+
navigateTo("identity/people");
|
|
137
|
+
const identityId = activeTabId();
|
|
138
|
+
navigateTo("invites/accept");
|
|
139
|
+
|
|
140
|
+
expect(activeTabId()).toBe(identityId);
|
|
141
|
+
expect(activeTabs().filter((entry) => entry.path.startsWith("identity"))).toHaveLength(1);
|
|
142
|
+
expect(_mainContentPath()).toBe("identity/activity/accept");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("keeps the current section when the app root is opened again", () => {
|
|
146
|
+
navigateTo("identity/devices/pair");
|
|
147
|
+
goToTabPath("identity");
|
|
148
|
+
|
|
149
|
+
expect(_mainContentPath()).toBe("identity/devices/pair");
|
|
150
|
+
expect(recentlyUsedApps()).toEqual(["identity"]);
|
|
151
|
+
});
|
|
152
|
+
});
|