import { ENVIRONMENT_MACHINE_KINDS, isEnvironmentMachineKind, resolveEnvironmentMachineKind, type EnvironmentId, type ServerConfig, } from "@t3tools/contracts"; import { useCallback } from "react"; import { isElectron } from "../../env"; import { usePrimarySessionState } from "../../environments/primary"; import { useUpdateEnvironmentSettings } from "../../hooks/useSettings"; import { usePrimaryEnvironmentId } from "../../state/environments"; import { useEnvironmentSessionState } from "../../state/session"; import { ENVIRONMENT_MACHINE_KIND_LABELS, EnvironmentMachineIcon } from "../EnvironmentMachineIcon"; import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { resolvePrimaryOperateAccess, resolveRemoteOperateAccess, } from "./ProviderSettingsPanel.logic"; const AUTOMATIC_VALUE = "automatic"; /** * Why the picker is inert, in the order the user can do something about it. * Null means it can be changed. */ export function resolveEnvironmentIconPickerLock(input: { readonly serverConfig: ServerConfig | null; readonly operateAccess: "granted" | "denied" | "pending"; }): string | null { if (input.serverConfig === null) { return "Connect to this environment to change its icon."; } if (input.serverConfig.environment.capabilities.environmentIcon !== true) { return "This environment's server is too old to keep an icon. Update it to choose one."; } if (input.operateAccess === "denied") { return "Your session on this environment cannot change its settings."; } return null; } // Same split the provider settings use: the desktop app owns its primary // server outright, a browser session on the primary checks its cookie // session's scopes, and a remote checks the scopes its own server reports. function useEnvironmentOperateAccess(environmentId: EnvironmentId) { const isPrimary = usePrimaryEnvironmentId() === environmentId; const primarySession = usePrimarySessionState(); const remoteSession = useEnvironmentSessionState(environmentId); if (isPrimary) { return isElectron ? "granted" : resolvePrimaryOperateAccess({ isPrimary: true, hasDesktopBridge: false, session: primarySession.data, isPending: primarySession.isPending, hasError: primarySession.error !== null, }); } return resolveRemoteOperateAccess({ session: remoteSession.data, isPending: remoteSession.isPending, hasError: remoteSession.hasError, }); } /** * Picks the machine glyph an environment wears everywhere it is listed. * "Automatic" clears the override so the server's own detection shows * through; the label says what that currently resolves to so the user can * tell whether detection got it right before overriding. The control stays * visible while locked so the current icon still reads, the same way * server-scoped rows go inert instead of disappearing. */ export function EnvironmentIconPicker({ environmentId, serverConfig, size = "sm", }: { readonly environmentId: EnvironmentId; readonly serverConfig: ServerConfig | null; readonly size?: "xs" | "sm"; }) { const updateSettings = useUpdateEnvironmentSettings(environmentId); const operateAccess = useEnvironmentOperateAccess(environmentId); const lock = resolveEnvironmentIconPickerLock({ serverConfig, operateAccess }); const override = serverConfig?.settings.environmentIcon ?? null; const detected = serverConfig?.environment.platform.machine ?? null; const resolved = resolveEnvironmentMachineKind(serverConfig); const value = override ?? AUTOMATIC_VALUE; const automaticLabel = detected === null ? "Automatic" : `Automatic (${ENVIRONMENT_MACHINE_KIND_LABELS[detected]})`; const handleValueChange = useCallback( (next: string | null) => { if (next === null) return; if (next === AUTOMATIC_VALUE) { updateSettings({ environmentIcon: null }); } else if (isEnvironmentMachineKind(next)) { updateSettings({ environmentIcon: next }); } }, [updateSettings], ); const select = ( ); if (lock === null) { return select; } return ( } > {select} {lock} ); }