| import { Category, defaultCategories } from "./categories"; |
| import { CreateValidators, Validator } from "./config"; |
| import { GitHubService, GitHubServiceImpl, GitHubRepository } from "./github"; |
| import type { Edge, OnConnect, OnEdgesChange, OnNodesChange, Viewport as ReactFlowViewport } from "@xyflow/react"; |
| import { |
| addEdge, |
| applyEdgeChanges, |
| applyNodeChanges, |
| Connection, |
| EdgeChange, |
| useNodes, |
| XYPosition, |
| } from "@xyflow/react"; |
| import type { DeepPartial } from "react-hook-form"; |
| import { v4 as uuidv4 } from "uuid"; |
| import { create } from "zustand"; |
| import { AppNode, Env, NodeType, VolumeNode, GatewayTCPData, envSchema, AgentAccess } from "config"; |
| |
| export function nodeLabel(n: AppNode): string { |
| try { |
| switch (n.type) { |
| case "network": |
| return n.data.domain; |
| case "app": |
| return n.data.label || "Service"; |
| case "github": |
| return n.data.repository?.fullName || "Github"; |
| case "gateway-https": { |
| if (n.data && n.data.subdomain) { |
| return `${n.data.subdomain}`; |
| } else { |
| return "HTTPS Gateway"; |
| } |
| } |
| case "gateway-tcp": { |
| if (n.data && n.data.subdomain) { |
| return `${n.data.subdomain}`; |
| } else { |
| return "TCP Gateway"; |
| } |
| } |
| case "mongodb": |
| return n.data.label || "MongoDB"; |
| case "postgresql": |
| return n.data.label || "PostgreSQL"; |
| case "volume": |
| return n.data.label || "Volume"; |
| case undefined: |
| throw new Error(`nodeLabel: Node type is undefined. Node ID: ${n.id}, Data: ${JSON.stringify(n.data)}`); |
| } |
| } catch (e) { |
| console.error("opaa", e); |
| } finally { |
| console.log("done"); |
| } |
| return "Unknown Node"; |
| } |
| |
| export function nodeLabelFull(n: AppNode): string { |
| if (n.type === "gateway-https") { |
| return `https://${n.data.subdomain}.${n.data.network}`; |
| } else { |
| return nodeLabel(n); |
| } |
| } |
| |
| export function nodeIsConnectable(n: AppNode, handle: string): boolean { |
| switch (n.type) { |
| case "network": |
| return true; |
| case "app": |
| if (handle === "ports") { |
| return n.data !== undefined && n.data.ports !== undefined && n.data.ports.length > 0; |
| } else if (handle === "repository") { |
| if (!n.data || !n.data.repository || !n.data.repository.id) { |
| return true; |
| } |
| return false; |
| } |
| return false; |
| case "github": |
| if (n.data.repository?.id !== undefined) { |
| return true; |
| } |
| return false; |
| case "gateway-https": |
| if (handle === "subdomain") { |
| return n.data.network === undefined; |
| } |
| return n.data === undefined || n.data.https === undefined; |
| case "gateway-tcp": |
| if (handle === "subdomain") { |
| return n.data.network === undefined; |
| } |
| return true; |
| case "mongodb": |
| return true; |
| case "postgresql": |
| return true; |
| case "volume": |
| if (n.data === undefined || n.data.type === undefined) { |
| return false; |
| } |
| if (n.data.type === "ReadWriteOnce" || n.data.type === "ReadWriteOncePod") { |
| return n.data.attachedTo === undefined || n.data.attachedTo.length === 0; |
| } |
| return true; |
| case undefined: |
| throw new Error( |
| `nodeIsConnectable: Node type is undefined. Node ID: ${n.id}, Handle: ${handle}, Data: ${JSON.stringify(n.data)}`, |
| ); |
| } |
| } |
| |
| export function nodeEnvVarNamePort(n: AppNode, portName: string): string { |
| return `DODO_SERVICE_${n.data.label.toUpperCase()}_ADDRESS_${portName.toUpperCase()}`; |
| } |
| |
| export function nodeEnvVarNames(n: AppNode): string[] { |
| switch (n.type) { |
| case "app": |
| return [ |
| `DODO_SERVICE_${n.data.label.toUpperCase()}_ADDRESS`, |
| ...(n.data.ports || []).map((p) => nodeEnvVarNamePort(n, p.name)), |
| ]; |
| case "github": |
| return []; |
| case "gateway-https": |
| return []; |
| case "gateway-tcp": |
| return []; |
| case "mongodb": |
| return [`DODO_MONGODB_${n.data.label.toUpperCase()}_URL`]; |
| case "postgresql": |
| return [`DODO_POSTGRESQL_${n.data.label.toUpperCase()}_URL`]; |
| case "volume": |
| return [`DODO_VOLUME_${n.data.label.toUpperCase()}_PATH`]; |
| case undefined: |
| throw new Error("MUST NOT REACH"); |
| default: |
| throw new Error("MUST NOT REACH"); |
| } |
| } |
| |
| export type MessageType = "INFO" | "WARNING" | "FATAL"; |
| |
| export type Message = { |
| id: string; |
| type: MessageType; |
| nodeId?: string; |
| message: string; |
| onHighlight?: (state: AppState) => void; |
| onLooseHighlight?: (state: AppState) => void; |
| onClick?: (state: AppState) => void; |
| }; |
| |
| const defaultEnv: Env = { |
| deployKeyPublic: undefined, |
| instanceId: undefined, |
| networks: [], |
| integrations: { |
| github: false, |
| gemini: false, |
| anthropic: false, |
| }, |
| services: [], |
| user: { |
| id: "", |
| username: "", |
| }, |
| access: [], |
| }; |
| |
| export type Project = { |
| id: string; |
| name: string; |
| }; |
| |
| export type IntegrationsConfig = { |
| github: boolean; |
| }; |
| |
| type NodeUpdate<T extends NodeType> = DeepPartial<Extract<AppNode, { type: T }>>; |
| type NodeDataUpdate<T extends NodeType> = DeepPartial<Extract<AppNode, { type: T }>["data"]>; |
| |
| type Viewport = { |
| transformX: number; |
| transformY: number; |
| transformZoom: number; |
| width: number; |
| height: number; |
| }; |
| |
| let refreshEnvIntervalId: number | null = null; |
| |
| export type AppState = { |
| projectId: string | undefined; |
| mode: "edit" | "deploy"; |
| projects: Project[]; |
| nodes: AppNode[]; |
| edges: Edge[]; |
| zoom: ReactFlowViewport; |
| categories: Category[]; |
| messages: Message[]; |
| env: Env; |
| viewport: Viewport; |
| setViewport: (viewport: Viewport) => void; |
| githubService: GitHubService | null; |
| githubRepositories: GitHubRepository[]; |
| githubRepositoriesLoading: boolean; |
| githubRepositoriesError: string | null; |
| stateEventSource: EventSource | null; |
| setHighlightCategory: (name: string, active: boolean) => void; |
| onNodesChange: OnNodesChange<AppNode>; |
| onEdgesChange: OnEdgesChange; |
| onConnect: OnConnect; |
| addNode: (node: Omit<AppNode, "position">) => void; |
| setNodes: (nodes: AppNode[]) => void; |
| setEdges: (edges: Edge[]) => void; |
| setProject: (projectId: string | undefined) => Promise<void>; |
| setMode: (mode: "edit" | "deploy") => void; |
| updateNode: <T extends NodeType>(id: string, node: NodeUpdate<T>) => void; |
| updateNodeData: <T extends NodeType>(id: string, data: NodeDataUpdate<T>) => void; |
| replaceEdge: (c: Connection, id?: string) => void; |
| refreshEnv: () => Promise<void>; |
| fetchGithubRepositories: () => Promise<void>; |
| }; |
| |
| const projectIdSelector = (state: AppState) => state.projectId; |
| const categoriesSelector = (state: AppState) => state.categories; |
| const messagesSelector = (state: AppState) => state.messages; |
| const envSelector = (state: AppState) => state.env; |
| const zoomSelector = (state: AppState) => state.zoom; |
| const githubRepositoriesSelector = (state: AppState) => state.githubRepositories; |
| const githubRepositoriesLoadingSelector = (state: AppState) => state.githubRepositoriesLoading; |
| const githubRepositoriesErrorSelector = (state: AppState) => state.githubRepositoriesError; |
| |
| export function useZoom(): ReactFlowViewport { |
| return useStateStore(zoomSelector); |
| } |
| |
| export function useProjectId(): string | undefined { |
| return useStateStore(projectIdSelector); |
| } |
| |
| export function useSetProject(): (projectId: string | undefined) => void { |
| return useStateStore((state) => state.setProject); |
| } |
| |
| export function useCategories(): Category[] { |
| return useStateStore(categoriesSelector); |
| } |
| |
| export function useMessages(): Message[] { |
| return useStateStore(messagesSelector); |
| } |
| |
| export function useNodeMessages(id: string): Message[] { |
| return useMessages().filter((m) => m.nodeId === id); |
| } |
| |
| export function useNodeLabel(id: string): string { |
| return nodeLabel(useNodes<AppNode>().find((n) => n.id === id)!); |
| } |
| |
| export function useNodePortName(id: string, portId: string): string { |
| return (useNodes<AppNode>().find((n) => n.id === id)!.data.ports || []).find((p) => p.id === portId)!.name; |
| } |
| |
| export function useEnv(): Env { |
| return useStateStore(envSelector); |
| } |
| |
| export function useAgents(): AgentAccess[] { |
| return useStateStore(envSelector).access.filter( |
| (acc): acc is AgentAccess => acc.type === "https" && acc.agentName != null, |
| ); |
| } |
| |
| export function useLeadAgent(): AgentAccess | undefined { |
| const agents = useAgents(); |
| return agents.find((a) => a.agentName === "dodo") || agents[0]; |
| } |
| |
| export function useGithubService(): boolean { |
| return useStateStore(envSelector).integrations.github; |
| } |
| |
| export function useGeminiService(): boolean { |
| return useStateStore(envSelector).integrations.gemini; |
| } |
| |
| export function useAnthropicService(): boolean { |
| return useStateStore(envSelector).integrations.anthropic; |
| } |
| |
| export function useGithubRepositories(): GitHubRepository[] { |
| return useStateStore(githubRepositoriesSelector); |
| } |
| |
| export function useGithubRepositoriesLoading(): boolean { |
| return useStateStore(githubRepositoriesLoadingSelector); |
| } |
| |
| export function useGithubRepositoriesError(): string | null { |
| return useStateStore(githubRepositoriesErrorSelector); |
| } |
| |
| export function useFetchGithubRepositories(): () => Promise<void> { |
| return useStateStore((state) => state.fetchGithubRepositories); |
| } |
| |
| export function useMode(): "edit" | "deploy" { |
| return useStateStore((state) => state.mode); |
| } |
| |
| const v: Validator = CreateValidators(); |
| |
| function getRandomPosition({ width, height, transformX, transformY, transformZoom }: Viewport): XYPosition { |
| const zoomMultiplier = 1 / transformZoom; |
| const realWidth = width * zoomMultiplier; |
| const realHeight = height * zoomMultiplier; |
| const paddingMultiplier = 0.8; |
| const ret = { |
| x: -transformX * zoomMultiplier + Math.random() * realWidth * paddingMultiplier, |
| y: -transformY * zoomMultiplier + Math.random() * realHeight * paddingMultiplier, |
| }; |
| return ret; |
| } |
| |
| export const useStateStore = create<AppState>((setOg, get): AppState => { |
| const set = (state: Partial<AppState>) => { |
| setOg(state); |
| }; |
| const setN = (nodes: AppNode[]) => { |
| const env = get().env; |
| console.log("---env", env); |
| set({ |
| nodes, |
| messages: v(nodes, env), |
| }); |
| }; |
| |
| const startRefreshEnvInterval = () => { |
| if (refreshEnvIntervalId) { |
| clearInterval(refreshEnvIntervalId); |
| } |
| if (get().projectId && typeof document !== "undefined" && document.visibilityState === "visible") { |
| console.log("Starting refreshEnv interval for project:", get().projectId); |
| refreshEnvIntervalId = setInterval(async () => { |
| if (get().projectId && typeof document !== "undefined" && document.visibilityState === "visible") { |
| console.log("Interval: Calling refreshEnv for project:", get().projectId); |
| await get().refreshEnv(); |
| } else if (refreshEnvIntervalId) { |
| console.log( |
| "Interval: Conditions not met (project removed or tab hidden), stopping interval from inside.", |
| ); |
| clearInterval(refreshEnvIntervalId); |
| refreshEnvIntervalId = null; |
| } |
| }, 5000) as unknown as number; |
| } else { |
| console.log( |
| "Not starting refreshEnv interval. Project ID:", |
| get().projectId, |
| "Visibility:", |
| typeof document !== "undefined" ? document.visibilityState : "SSR", |
| ); |
| } |
| }; |
| |
| const stopRefreshEnvInterval = () => { |
| if (refreshEnvIntervalId) { |
| console.log("Stopping refreshEnv interval for project:", get().projectId); |
| clearInterval(refreshEnvIntervalId); |
| refreshEnvIntervalId = null; |
| } |
| }; |
| |
| if (typeof document !== "undefined") { |
| document.addEventListener("visibilitychange", () => { |
| if (document.visibilityState === "visible") { |
| console.log("Tab became visible, attempting to start refreshEnv interval."); |
| startRefreshEnvInterval(); |
| } else { |
| console.log("Tab became hidden, stopping refreshEnv interval."); |
| stopRefreshEnvInterval(); |
| } |
| }); |
| } |
| |
| const injectNetworkNodes = () => { |
| const newNetworks = get().env.networks.filter( |
| (x) => !get().nodes.some((n) => n.type === "network" && n.data.domain === x.domain), |
| ); |
| newNetworks.forEach((n) => { |
| get().addNode({ |
| id: n.domain, |
| type: "network", |
| connectable: true, |
| data: { |
| domain: n.domain, |
| label: n.domain, |
| envVars: [], |
| ports: [], |
| state: "success", // TODO(gio): monitor network health |
| }, |
| }); |
| console.log("added network", n.domain); |
| }); |
| }; |
| |
| function updateNodeData<T extends NodeType>(id: string, data: NodeDataUpdate<T>): void { |
| setN( |
| get().nodes.map((n) => { |
| if (n.id === id) { |
| return { |
| ...n, |
| data: { |
| ...n.data, |
| ...data, |
| }, |
| } as Extract<AppNode, { type: T }>; |
| } |
| return n; |
| }), |
| ); |
| } |
| |
| function updateNode<T extends NodeType>(id: string, node: NodeUpdate<T>): void { |
| setN( |
| get().nodes.map((n) => { |
| if (n.id === id) { |
| return { |
| ...n, |
| ...node, |
| } as Extract<AppNode, { type: T }>; |
| } |
| return n; |
| }), |
| ); |
| } |
| |
| function onConnect(c: Connection) { |
| const { nodes, edges } = get(); |
| set({ |
| edges: addEdge(c, edges), |
| }); |
| const sn = nodes.filter((n) => n.id === c.source)[0]!; |
| const tn = nodes.filter((n) => n.id === c.target)[0]!; |
| if (tn.type === "network") { |
| if (sn.type === "gateway-https") { |
| updateNodeData<"gateway-https">(sn.id, { |
| network: tn.data.domain, |
| }); |
| } else if (sn.type === "gateway-tcp") { |
| updateNodeData<"gateway-tcp">(sn.id, { |
| network: tn.data.domain, |
| }); |
| } |
| } |
| if (tn.type === "app") { |
| if (c.sourceHandle === "env_var" && c.targetHandle === "env_var") { |
| const sourceEnvVars = nodeEnvVarNames(sn); |
| if (sourceEnvVars.length === 0) { |
| throw new Error( |
| `onConnect (env_var): Source node ${sn.id} (type: ${sn.type}) has no env vars to connect from.`, |
| ); |
| } |
| const id = uuidv4(); |
| if (sourceEnvVars.length === 1) { |
| updateNode<"app">(c.target, { |
| ...tn, |
| data: { |
| ...tn.data, |
| envVars: [ |
| ...(tn.data.envVars || []), |
| { |
| id: id, |
| source: c.source, |
| name: sourceEnvVars[0], |
| isEditting: false, |
| }, |
| ], |
| }, |
| }); |
| } else { |
| updateNode<"app">(c.target, { |
| ...tn, |
| data: { |
| ...tn.data, |
| envVars: [ |
| ...(tn.data.envVars || []), |
| { |
| id: id, |
| source: c.source, |
| }, |
| ], |
| }, |
| }); |
| } |
| } |
| if (c.sourceHandle === "ports" && c.targetHandle === "env_var") { |
| const sourcePorts = sn.data.ports || []; |
| const id = uuidv4(); |
| if (sourcePorts.length === 1) { |
| updateNode<"app">(c.target, { |
| ...tn, |
| data: { |
| ...tn.data, |
| envVars: [ |
| ...(tn.data.envVars || []), |
| { |
| id: id, |
| source: c.source, |
| name: nodeEnvVarNamePort(sn, sourcePorts[0].name), |
| portId: sourcePorts[0].id, |
| isEditting: false, |
| }, |
| ], |
| }, |
| }); |
| } |
| } |
| if (c.targetHandle === "repository") { |
| const sourceNode = nodes.find((n) => n.id === c.source); |
| if (sourceNode && sourceNode.type === "github" && sourceNode.data.repository) { |
| updateNodeData<"app">(tn.id, { |
| repository: { |
| id: sourceNode.data.repository.id, |
| repoNodeId: c.source, |
| }, |
| }); |
| } |
| } |
| } |
| if (c.sourceHandle === "volume") { |
| updateNodeData<"volume">(c.source, { |
| attachedTo: ((sn as VolumeNode).data.attachedTo || []).concat(c.source), |
| }); |
| } |
| if (c.targetHandle === "https") { |
| if ((sn.data.ports || []).length === 1) { |
| updateNodeData<"gateway-https">(c.target, { |
| https: { |
| serviceId: c.source, |
| portId: sn.data.ports![0].id, |
| }, |
| }); |
| } else { |
| updateNodeData<"gateway-https">(c.target, { |
| https: { |
| serviceId: c.source, |
| portId: "", // TODO(gio) |
| }, |
| }); |
| } |
| } |
| if (c.targetHandle === "tcp") { |
| const td = tn.data as GatewayTCPData; |
| if ((sn.data.ports || []).length === 1) { |
| updateNodeData<"gateway-tcp">(c.target, { |
| exposed: (td.exposed || []).concat({ |
| serviceId: c.source, |
| portId: sn.data.ports![0].id, |
| }), |
| }); |
| } else { |
| updateNodeData<"gateway-tcp">(c.target, { |
| selected: { |
| serviceId: c.source, |
| portId: undefined, |
| }, |
| }); |
| } |
| } |
| if (sn.type === "app") { |
| if (c.sourceHandle === "ports") { |
| updateNodeData<"app">(sn.id, { |
| isChoosingPortToConnect: true, |
| }); |
| } |
| } |
| } |
| |
| const fetchGithubRepositories = async () => { |
| const { githubService, projectId } = get(); |
| if (!githubService || !projectId) { |
| set({ |
| githubRepositories: [], |
| githubRepositoriesError: "GitHub service or Project ID not available.", |
| githubRepositoriesLoading: false, |
| }); |
| return; |
| } |
| |
| set({ githubRepositoriesLoading: true, githubRepositoriesError: null }); |
| try { |
| const repos = await githubService.getRepositories(); |
| set({ githubRepositories: repos, githubRepositoriesLoading: false }); |
| } catch (error) { |
| console.error("Failed to fetch GitHub repositories in store:", error); |
| const errorMessage = error instanceof Error ? error.message : "Unknown error fetching repositories"; |
| set({ githubRepositories: [], githubRepositoriesError: errorMessage, githubRepositoriesLoading: false }); |
| } |
| }; |
| |
| const disconnectFromStateStream = () => { |
| const { stateEventSource } = get(); |
| if (stateEventSource) { |
| stateEventSource.close(); |
| set({ stateEventSource: null }); |
| } |
| }; |
| |
| const connectToStateStream = (projectId: string, mode: "deploy" | "edit") => { |
| disconnectFromStateStream(); |
| |
| const eventSource = new EventSource( |
| `/api/project/${projectId}/state/stream/${mode === "edit" ? "draft" : "deploy"}`, |
| ); |
| set({ stateEventSource: eventSource }); |
| |
| eventSource.onmessage = (event) => { |
| const inst = JSON.parse(event.data); |
| setN(inst.nodes); |
| set({ edges: inst.edges }); |
| injectNetworkNodes(); |
| if ( |
| get().zoom.x !== inst.viewport.x || |
| get().zoom.y !== inst.viewport.y || |
| get().zoom.zoom !== inst.viewport.zoom |
| ) { |
| set({ zoom: inst.viewport }); |
| } |
| }; |
| |
| eventSource.onerror = (err) => { |
| console.error("EventSource failed:", err); |
| eventSource.close(); |
| set({ stateEventSource: null }); |
| }; |
| }; |
| |
| return { |
| projectId: undefined, |
| mode: "edit", |
| projects: [], |
| nodes: [], |
| edges: [], |
| categories: defaultCategories, |
| messages: [], |
| env: defaultEnv, |
| viewport: { |
| transformX: 0, |
| transformY: 0, |
| transformZoom: 1, |
| width: 800, |
| height: 600, |
| }, |
| zoom: { |
| x: 0, |
| y: 0, |
| zoom: 1, |
| }, |
| githubService: null, |
| githubRepositories: [], |
| githubRepositoriesLoading: false, |
| githubRepositoriesError: null, |
| stateEventSource: null, |
| setViewport: (viewport) => { |
| const { viewport: vp } = get(); |
| if ( |
| viewport.transformX !== vp.transformX || |
| viewport.transformY !== vp.transformY || |
| viewport.transformZoom !== vp.transformZoom || |
| viewport.width !== vp.width || |
| viewport.height !== vp.height |
| ) { |
| set({ viewport }); |
| } |
| }, |
| setHighlightCategory: (name, active) => { |
| set({ |
| categories: get().categories.map((c) => { |
| if (c.title.toLowerCase() !== name.toLowerCase()) { |
| return c; |
| } else { |
| return { |
| ...c, |
| active, |
| }; |
| } |
| }), |
| }); |
| }, |
| onNodesChange: (changes) => { |
| const nodes = applyNodeChanges(changes, get().nodes); |
| setN(nodes); |
| }, |
| onEdgesChange: (changes) => { |
| set({ |
| edges: applyEdgeChanges(changes, get().edges), |
| }); |
| }, |
| addNode: (node) => { |
| const { viewport, nodes } = get(); |
| setN( |
| nodes.concat({ |
| ...node, |
| position: getRandomPosition(viewport), |
| } as AppNode), |
| ); |
| }, |
| setNodes: (nodes) => { |
| setN(nodes); |
| }, |
| setEdges: (edges) => { |
| set({ edges }); |
| }, |
| replaceEdge: (c, id) => { |
| let change: EdgeChange; |
| if (id === undefined) { |
| change = { |
| type: "add", |
| item: { |
| id: uuidv4(), |
| ...c, |
| }, |
| }; |
| onConnect(c); |
| } else { |
| change = { |
| type: "replace", |
| id, |
| item: { |
| id, |
| ...c, |
| }, |
| }; |
| } |
| set({ |
| edges: applyEdgeChanges([change], get().edges), |
| }); |
| }, |
| updateNode, |
| updateNodeData, |
| onConnect, |
| refreshEnv: async () => { |
| const projectId = get().projectId; |
| let env: Env = defaultEnv; |
| try { |
| if (projectId) { |
| const response = await fetch(`/api/project/${projectId}/env`); |
| if (response.ok) { |
| const data = await response.json(); |
| const result = envSchema.safeParse(data); |
| if (result.success) { |
| env = result.data; |
| } else { |
| console.error("Invalid env data:", result.error); |
| } |
| } |
| } |
| } catch (error) { |
| console.error("Failed to fetch integrations:", error); |
| } finally { |
| const oldEnv = get().env; |
| const oldGithubIntegrationStatus = oldEnv.integrations.github; |
| if (JSON.stringify(oldEnv) !== JSON.stringify(env)) { |
| set({ env }); |
| injectNetworkNodes(); |
| let ghService = null; |
| if (env.integrations.github) { |
| ghService = new GitHubServiceImpl(projectId!); |
| } |
| if (get().githubService !== ghService || (ghService && !get().githubService)) { |
| set({ githubService: ghService }); |
| } |
| if ( |
| ghService && |
| (oldGithubIntegrationStatus !== env.integrations.github || !oldEnv.integrations.github) |
| ) { |
| get().fetchGithubRepositories(); |
| } |
| if (!env.integrations.github) { |
| set({ |
| githubRepositories: [], |
| githubRepositoriesError: null, |
| githubRepositoriesLoading: false, |
| }); |
| } |
| } |
| } |
| }, |
| setMode: (mode) => { |
| disconnectFromStateStream(); |
| set({ mode }); |
| const projectId = get().projectId; |
| if (projectId) { |
| connectToStateStream(projectId, mode); |
| } |
| }, |
| setProject: async (projectId) => { |
| const currentProjectId = get().projectId; |
| if (projectId === currentProjectId) { |
| return; |
| } |
| stopRefreshEnvInterval(); |
| disconnectFromStateStream(); |
| set({ |
| projectId, |
| githubRepositories: [], |
| githubRepositoriesLoading: false, |
| githubRepositoriesError: null, |
| }); |
| if (projectId) { |
| await get().refreshEnv(); |
| if (get().env.instanceId) { |
| set({ mode: "deploy" }); |
| } else { |
| set({ mode: "edit" }); |
| } |
| const mode = get().mode; |
| connectToStateStream(projectId, mode); |
| startRefreshEnvInterval(); |
| } else { |
| set({ |
| nodes: [], |
| edges: [], |
| env: defaultEnv, |
| githubService: null, |
| githubRepositories: [], |
| githubRepositoriesLoading: false, |
| githubRepositoriesError: null, |
| }); |
| } |
| }, |
| fetchGithubRepositories: fetchGithubRepositories, |
| }; |
| }); |