1import { type EnvironmentShellSummary } from "@t3tools/client-runtime/state/shell";
2import type { EnvironmentId, ServerConfig } from "@t3tools/contracts";
3
4import {
5 type EnvironmentConnectionPhase,
6 type NetworkStatus,
7} from "@t3tools/client-runtime/connection";
8
9import type { EnvironmentConnectionSummary as WorkspaceEnvironment } from "@t3tools/client-runtime/state/presentation";
10
11export { projectEnvironmentConnectionSummary as projectWorkspaceEnvironment } from "@t3tools/client-runtime/state/presentation";
12export type { EnvironmentConnectionSummary as WorkspaceEnvironment } from "@t3tools/client-runtime/state/presentation";
13
14export interface WorkspaceConnectionState {
15 readonly isLoadingConnections: boolean;
16 readonly hasConnections: boolean;
17 readonly hasReadyEnvironment: boolean;
18 readonly hasConnectingEnvironment: boolean;
19 readonly connectingEnvironments: ReadonlyArray<WorkspaceEnvironment>;
20 readonly connectionState: EnvironmentConnectionPhase;
21 readonly connectionError: string | null;
22 readonly networkStatus: NetworkStatus;
23}
24
25export interface WorkspaceState extends WorkspaceConnectionState {
26 readonly hasLoadedShellSnapshot: boolean;
27 readonly hasPendingShellSnapshot: boolean;
28 readonly shellSnapshotError: string | null;
29}
30
31function overallConnectionState(
32 environments: ReadonlyArray<WorkspaceEnvironment>,
33 networkStatus: NetworkStatus,
34): EnvironmentConnectionPhase {
35 if (environments.length === 0) {
36 return "available";
37 }
38 if (networkStatus === "offline") {
39 return "offline";
40 }
41 if (environments.some((environment) => environment.connectionState === "connected")) {
42 return "connected";
43 }
44 if (environments.some((environment) => environment.connectionState === "reconnecting")) {
45 return "reconnecting";
46 }
47 if (environments.some((environment) => environment.connectionState === "connecting")) {
48 return "connecting";
49 }
50 if (environments.some((environment) => environment.connectionState === "unsupported")) {
51 return "unsupported";
52 }
53 if (environments.some((environment) => environment.connectionState === "error")) {
54 return "error";
55 }
56 if (environments.some((environment) => environment.connectionState === "offline")) {
57 return "offline";
58 }
59 return "available";
60}
61
62export function projectWorkspaceConnectionState(input: {
63 readonly isReady: boolean;
64 readonly networkStatus: NetworkStatus;
65 readonly environments: ReadonlyArray<WorkspaceEnvironment>;
66}): WorkspaceConnectionState {
67 // Switched-off environments still count as saved connections, but they do
68 // not drive the overall connection state or surface their last error.
69 const activeEnvironments = input.environments.filter((environment) => environment.isEnabled);
70 const connectingEnvironments = activeEnvironments.filter(
71 (environment) =>
72 environment.connectionState === "connecting" ||
73 environment.connectionState === "reconnecting",
74 );
75
76 return {
77 isLoadingConnections: !input.isReady,
78 hasConnections: input.environments.length > 0,
79 hasReadyEnvironment:
80 input.networkStatus !== "offline" &&
81 activeEnvironments.some((environment) => environment.connectionState === "connected"),
82 hasConnectingEnvironment: connectingEnvironments.length > 0,
83 connectingEnvironments,
84 connectionState: overallConnectionState(activeEnvironments, input.networkStatus),
85 connectionError:
86 activeEnvironments.find((environment) => environment.connectionError !== null)
87 ?.connectionError ?? null,
88 networkStatus: input.networkStatus,
89 };
90}
91
92export function projectWorkspaceState(input: {
93 readonly isReady: boolean;
94 readonly networkStatus: NetworkStatus;
95 readonly environments: ReadonlyArray<WorkspaceEnvironment>;
96 readonly shellSummary: EnvironmentShellSummary;
97}): WorkspaceState {
98 return {
99 ...projectWorkspaceConnectionState(input),
100 hasLoadedShellSnapshot: input.shellSummary.hasSnapshot,
101 hasPendingShellSnapshot: input.shellSummary.hasSynchronizingShell,
102 shellSnapshotError: input.shellSummary.firstError,
103 };
104}
105
106export type ServerConfigByEnvironmentId = ReadonlyMap<EnvironmentId, ServerConfig>;