Skip to content
Source
Sign inSign up

workspaceModel.ts

123 lines4.6 KB

1import {
2 EMPTY_SHELL_STATE,
3 type EnvironmentShellState,
4 type EnvironmentShellSummary,
5} from "@t3tools/client-runtime/state/shell";
6import type { EnvironmentId, ServerConfig } from "@t3tools/contracts";
7​
8import {
9 type EnvironmentConnectionPhase,
10 type NetworkStatus,
11} from "@t3tools/client-runtime/connection";
12​
13import type { EnvironmentConnectionSummary as WorkspaceEnvironment } from "@t3tools/client-runtime/state/presentation";
14​
15export type { EnvironmentConnectionSummary as WorkspaceEnvironment } from "@t3tools/client-runtime/state/presentation";
16​
17export interface WorkspaceConnectionState {
18 readonly isLoadingConnections: boolean;
19 readonly hasConnections: boolean;
20 readonly hasReadyEnvironment: boolean;
21 readonly hasConnectingEnvironment: boolean;
22 readonly connectingEnvironments: ReadonlyArray<WorkspaceEnvironment>;
23 readonly connectionState: EnvironmentConnectionPhase;
24 readonly connectionError: string | null;
25 readonly networkStatus: NetworkStatus;
27​
28export interface WorkspaceState extends WorkspaceConnectionState {
29 readonly hasLoadedShellSnapshot: boolean;
30 readonly hasPendingShellSnapshot: boolean;
31 readonly hasSyncingConnectedEnvironment: boolean;
32 readonly shellSnapshotError: string | null;
34​
35function overallConnectionState(
36 environments: ReadonlyArray<WorkspaceEnvironment>,
37 networkStatus: NetworkStatus,
38): EnvironmentConnectionPhase {
39 if (environments.length === 0) {
40 return "available";
41 }
42 if (networkStatus === "offline") {
43 return "offline";
44 }
45 if (environments.some((environment) => environment.connectionState === "connected")) {
46 return "connected";
47 }
48 if (environments.some((environment) => environment.connectionState === "reconnecting")) {
49 return "reconnecting";
50 }
51 if (environments.some((environment) => environment.connectionState === "connecting")) {
52 return "connecting";
53 }
54 if (environments.some((environment) => environment.connectionState === "unsupported")) {
55 return "unsupported";
56 }
57 if (environments.some((environment) => environment.connectionState === "error")) {
58 return "error";
59 }
60 if (environments.some((environment) => environment.connectionState === "offline")) {
61 return "offline";
62 }
63 return "available";
65​
66export function projectWorkspaceConnectionState(input: {
67 readonly isReady: boolean;
68 readonly networkStatus: NetworkStatus;
69 readonly environments: ReadonlyArray<WorkspaceEnvironment>;
70}): WorkspaceConnectionState {
71 const activeEnvironments = input.environments.filter((environment) => environment.isEnabled);
72 const connectingEnvironments = activeEnvironments.filter(
73 (environment) =>
74 environment.connectionState === "connecting" ||
75 environment.connectionState === "reconnecting",
76 );
77​
78 return {
79 isLoadingConnections: !input.isReady,
80 hasConnections: input.environments.length > 0,
81 hasReadyEnvironment:
82 input.networkStatus !== "offline" &&
83 activeEnvironments.some((environment) => environment.connectionState === "connected"),
84 hasConnectingEnvironment: connectingEnvironments.length > 0,
85 connectingEnvironments,
86 connectionState: overallConnectionState(activeEnvironments, input.networkStatus),
87 connectionError:
88 activeEnvironments.find((environment) => environment.connectionError !== null)
89 ?.connectionError ?? null,
90 networkStatus: input.networkStatus,
91 };
93​
94export function projectWorkspaceState(input: {
95 readonly isReady: boolean;
96 readonly networkStatus: NetworkStatus;
97 readonly environments: ReadonlyArray<WorkspaceEnvironment>;
98 readonly shellSummary: EnvironmentShellSummary;
99 readonly hasSyncingConnectedEnvironment: boolean;
100}): WorkspaceState {
101 return {
102 ...projectWorkspaceConnectionState(input),
103 hasLoadedShellSnapshot: input.shellSummary.hasSnapshot,
104 hasPendingShellSnapshot: input.shellSummary.hasSynchronizingShell,
105 hasSyncingConnectedEnvironment: input.hasSyncingConnectedEnvironment,
106 shellSnapshotError: input.shellSummary.firstError,
107 };
109​
110// The atom starts at EMPTY_SHELL_STATE itself; any state after the cache read is a new object.
111export function isEnvironmentShellPending(state: EnvironmentShellState): boolean {
112 return state === EMPTY_SHELL_STATE || state.status === "synchronizing";
114​
115// Reconnecting environments keep their last failure, so an error alone does not end the wait.
116export function isWorkspaceCatalogPending(state: WorkspaceState): boolean {
117 return (
118 state.networkStatus !== "offline" &&
119 (state.hasConnectingEnvironment || state.hasSyncingConnectedEnvironment)
120 );
122​
123export type ServerConfigByEnvironmentId = ReadonlyMap<EnvironmentId, ServerConfig>;