import { resolveEnvironmentMachineKind } from "@t3tools/contracts"; import { fileBasename } from "@t3tools/shared/path"; import { useLocation } from "@tanstack/react-router"; import { CheckIcon, ChevronDownIcon, FolderIcon, LayersIcon } from "lucide-react"; import { Fragment, useState, type ReactNode } from "react"; import type { SidebarProjectSnapshot } from "../../sidebarProjectGrouping"; import { useEnvironments, type EnvironmentPresentation } from "../../state/environments"; import { EnvironmentMachineIcon } from "../EnvironmentMachineIcon"; import { ProjectFavicon } from "../ProjectFavicon"; import { Button } from "../ui/button"; import { Combobox, ComboboxEmpty, ComboboxItem, ComboboxList, ComboboxPopup, ComboboxSearchInput, ComboboxTrigger, } from "../ui/combobox"; import { Menu, MenuPopup, MenuRadioGroup, MenuRadioItem, MenuRadioItemIndicator, MenuSeparator, MenuTrigger, } from "../ui/menu"; import { useOptionalSettingsScope } from "./SettingsScopeContext"; import { resolveSettingsScope, type SettingsScopeSearch } from "./settingsScope"; import { ALL_CHECKOUTS_VALUE, ALL_ENVIRONMENTS_VALUE, ALL_PROJECTS_VALUE, environmentAxisValue, projectAxisValue, projectScopeMenuGroups, selectCheckoutAxis, selectEnvironmentAxis, selectProjectAxis, settingsScopeCheckoutChoices, settingsScopeEnvironmentAddress, settingsScopeEnvironmentLabel, settingsScopeEnvironmentLabels, settingsScopeProjectMachines, } from "./settingsScopeAxis"; export const SETTINGS_DEVICE_ONLY_PATHS: ReadonlySet = new Set([ "/settings/general", "/settings/appearance", "/settings/chat", "/settings/diffs", "/settings/snap-shot", "/settings/connections", // Network access and authorized clients belong to the environment serving this app. "/settings/access", "/settings/about", ]); interface SettingsScopeMenuProps { readonly value: SettingsScopeSearch; readonly groups: readonly SidebarProjectSnapshot[]; readonly environments: readonly EnvironmentPresentation[]; readonly singleEnvironment: boolean; readonly onChange: (next: SettingsScopeSearch) => void; } export function SettingsScopeSentence() { const scope = useOptionalSettingsScope(); const pathname = useLocation({ select: (location) => location.pathname }); const { environments } = useEnvironments(); if (scope === null || SETTINGS_DEVICE_ONLY_PATHS.has(pathname)) return null; const props: SettingsScopeMenuProps = { value: scope.search, singleEnvironment: scope.singleEnvironment, groups: scope.groups, environments, onChange: scope.selectScope, }; return (

Applying settings for {scope.search.machine || scope.scope.kind === "checkout" ? "on" : "across"}

); } interface ScopeOption { value: string; label: string; secondary?: string; icon?: ReactNode; offline?: boolean; } function ScopeOptionLabel({ option }: { option: ScopeOption }) { return ( <> {option.icon} {option.label} {option.secondary ? ( {option.secondary} ) : null} {option.offline ? ( Offline ) : null} ); } function ScopeMenu({ ariaLabel, icon, label, value, options, allOption, searchLabel, onChange, }: { ariaLabel: string; icon: ReactNode; label: string; value: string; options: ScopeOption[]; allOption: ScopeOption | null; searchLabel: string; onChange: (next: string) => void; }) { const [query, setQuery] = useState(""); const Trigger = options.length > 6 ? ComboboxTrigger : MenuTrigger; const trigger = ( } className="min-w-0 max-w-72" > {icon} {label} ); const choices = allOption ? [allOption, ...options] : options; if (options.length > 6) { const needle = query.trim().toLowerCase(); const filteredItems = choices.filter((option) => `${option.label} ${option.secondary ?? ""} ${option.value}`.toLowerCase().includes(needle), ); return ( option.label} isItemEqualToValue={(a, b) => a.value === b.value} value={choices.find((option) => option.value === value) ?? null} onOpenChange={(open) => { if (!open) setQuery(""); }} onValueChange={(option) => { if (option) onChange(option.value); }} > {trigger} setQuery(event.target.value)} /> No matches found. {(option: ScopeOption) => ( {option.value === value ? ( ) : null} )} ); } return ( {trigger}
{ if (typeof next === "string") onChange(next); }} > {choices.map((option, index) => ( {index === 0 && allOption ? : null} ))}
); } function EnvironmentScopeMenu({ value, groups, environments, onChange, singleEnvironment, }: SettingsScopeMenuProps) { const resolved = resolveSettingsScope(value, groups, environments); const environmentValue = environmentAxisValue( value, resolved.kind === "checkout" ? resolved.environmentId : null, ); const selected = environments.find( (environment) => environment.environmentId === environmentValue, ); const project = groups.find((group) => group.projectKey === value.project); const memberEnvironmentIds = project ? new Set(project.memberProjects.map((member) => member.environmentId)) : null; const options = memberEnvironmentIds ? environments.filter((environment) => memberEnvironmentIds.has(environment.environmentId)) : environments; return ( ) : null } label={ selected ? settingsScopeEnvironmentLabel(selected, environments) : environmentValue !== ALL_ENVIRONMENTS_VALUE ? "Unavailable environment" : singleEnvironment ? "No environments" : "All environments" } value={environmentValue} onChange={(next) => onChange(selectEnvironmentAxis(value, next))} searchLabel="Search environments" allOption={ singleEnvironment ? null : { value: ALL_ENVIRONMENTS_VALUE, label: "All environments", icon: , } } options={options.map((environment) => { const { primary, secondary } = settingsScopeEnvironmentLabels(environment, environments); return { value: environment.environmentId, label: primary, secondary: environments.length <= 1 ? "" : secondary || settingsScopeEnvironmentAddress(environment), icon: ( ), offline: environment.connection.phase !== "connected", }; })} /> ); } function CheckoutScopeMenu({ value, groups, onChange }: SettingsScopeMenuProps) { const group = groups.find((candidate) => candidate.projectKey === value.project); const checkouts = group ? settingsScopeCheckoutChoices(group, value) : []; if (checkouts.length === 0) return null; const selected = checkouts.find((member) => member.physicalProjectKey === value.checkout); return ( in } label={ selected ? fileBasename(selected.workspaceRoot) : value.checkout ? "Unavailable checkout" : "All checkouts" } value={value.checkout ?? ALL_CHECKOUTS_VALUE} onChange={(next) => onChange( selectCheckoutAxis( value, checkouts.find((member) => member.physicalProjectKey === next) ?? null, ), ) } searchLabel="Search checkouts" allOption={{ value: ALL_CHECKOUTS_VALUE, label: "All checkouts" }} options={checkouts.map((member) => ({ value: member.physicalProjectKey, label: fileBasename(member.workspaceRoot), secondary: member.workspaceRoot, }))} /> ); } function ProjectScopeMenu({ value, groups, environments, onChange }: SettingsScopeMenuProps) { const selected = groups.find((group) => group.projectKey === value.project); const projects = projectScopeMenuGroups(groups, value).map((group) => ({ group, machines: settingsScopeProjectMachines(group, environments, true).join(", "), })); return ( : null} label={selected?.displayName ?? (value.project ? "Unavailable project" : "All projects")} value={projectAxisValue(value)} onChange={(next) => onChange(selectProjectAxis(value, next))} searchLabel="Search projects" allOption={{ value: ALL_PROJECTS_VALUE, label: "All projects" }} options={projects.map(({ group, machines }) => { const sameName = projects.filter( (other) => other.group.projectKey !== group.projectKey && other.group.displayName === group.displayName, ); const location = sameName.length === 0 ? environments.length > 1 ? group.workspaceRoot : "" : sameName.some((other) => other.machines === machines) ? `${machines} · ${group.workspaceRoot}` : machines; return { value: group.projectKey, label: group.displayName, secondary: group.archived ? ["Archived", location].filter(Boolean).join(" · ") : location, icon: , }; })} /> ); }