import { SettingsGroup } from "./SettingsGroup"; import { AuthSettingsWriteScope } from "@t3tools/contracts"; import { usePrimaryEnvironmentId, usePrimaryEnvironment } from "../../state/environments"; import { useEnvironmentScope, useEnvironmentsWithScope } from "../../state/session"; import { useServerConfigs } from "../../state/entities"; import { InfoIcon, Undo2Icon } from "lucide-react"; import { DEFAULT_SERVER_SETTINGS, isManagedSettingPath, type ServerSettings, } from "@t3tools/contracts"; import * as Equal from "effect/Equal"; import { useLocation, useNavigate } from "@tanstack/react-router"; import { createContext, type ComponentPropsWithoutRef, type ReactNode, useContext } from "react"; import { PRIMARY_SETTINGS_UNAVAILABLE_MESSAGE, usePrimarySettingsAvailable, } from "../../hooks/useSettings"; import { cn } from "../../lib/utils"; import { WorkspacePageContainer, type WorkspacePageWidth } from "../WorkspacePageContainer"; import { Button } from "../ui/button"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { useOptionalSettingsScope } from "./SettingsScopeContext"; import { SettingsScopeSentence } from "./SettingsScopeSentence"; import { isProjectScopedSettingKey, listProjectOverrides, type ProjectOverrideEntry, scopedSettingsAreMixed, scopedSettingsSource, } from "./scopedSettings"; import { useClearProjectOverrides, useClearScopedSettings } from "./useScopedSettings"; import { SettingInheritance, type SettingInheritanceState, type SettingOverridingProject, } from "./SettingInheritance"; const EMPTY_SETTING_KEYS: readonly (keyof ServerSettings)[] = []; const EMPTY_MANAGED_PATHS: readonly string[] = []; declare module "@tanstack/react-router" { interface HistoryState { settingsTargetHighlight?: boolean; } } interface SettingsSearchTargetContextValue { readonly targetId: string | null; readonly highlightTarget: boolean; readonly onTargetHandled: () => void; } const noop = () => undefined; const SettingsSearchTargetContext = createContext({ targetId: null, highlightTarget: true, onTargetHandled: noop, }); function SettingsSearchTargetProvider({ targetId, highlightTarget = true, onTargetHandled = noop, children, }: { targetId: string | null; highlightTarget?: boolean; onTargetHandled?: () => void; children: ReactNode; }) { const value = { targetId, highlightTarget, onTargetHandled }; return {children}; } let pendingTargetScroll: AbortController | undefined; function scrollAndFocusSettingsTarget(target: HTMLElement, highlight = true): void { pendingTargetScroll?.abort(); const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; const markedScrollTarget = typeof target.querySelector === "function" ? target.querySelector(":scope > [data-settings-scroll-target]") : null; const scrollTarget = markedScrollTarget ?? (target.tagName === "SECTION" && target.firstElementChild ? (target.firstElementChild as HTMLElement) : target); const scroll = () => scrollTarget.scrollIntoView({ behavior: prefersReducedMotion ? "auto" : "smooth", block: "center", }); scroll(); const panel = target.parentElement?.closest( '[data-slot="collapsible-panel"][data-open]', ); const heightTransition = () => panel ?.getAnimations() .find( (animation) => animation instanceof CSSTransition && animation.transitionProperty === "height", ); if (panel && (panel.hasAttribute("data-starting-style") || heightTransition())) { const controller = new AbortController(); pendingTargetScroll = controller; const { signal } = controller; const finishScroll = () => { if (signal.aborted) return; controller.abort(); if ( panel.hasAttribute("data-open") && target.isConnected && document.activeElement === target ) { scroll(); } }; const waitForHeight = () => { const transition = heightTransition(); if (transition) { void transition.finished.then(finishScroll, () => controller.abort()); } else { finishScroll(); } }; // Base UI clears the starting style before the height transition finishes. if (panel.hasAttribute("data-starting-style")) { const observer = new MutationObserver(() => { if (!panel.hasAttribute("data-starting-style")) { observer.disconnect(); waitForHeight(); } }); observer.observe(panel, { attributes: true, attributeFilter: ["data-starting-style"] }); signal.addEventListener("abort", () => observer.disconnect(), { once: true }); } else { waitForHeight(); } target.addEventListener("blur", () => controller.abort(), { once: true, signal }); } target.focus({ preventScroll: true }); target.classList.remove("settings-search-target-pulse"); if (!highlight || prefersReducedMotion) return; void target.offsetWidth; target.classList.add("settings-search-target-pulse"); target.addEventListener("blur", () => target.classList.remove("settings-search-target-pulse"), { once: true, }); } export function useSettingsSearchTargetId(): string | null { return useContext(SettingsSearchTargetContext).targetId; } export function useSettingsSearchTarget(id: string | undefined) { const { targetId, highlightTarget, onTargetHandled } = useContext(SettingsSearchTargetContext); const isSearchTarget = id !== undefined && id === targetId; const targetRef = (target: T | null) => { if (target && isSearchTarget) { scrollAndFocusSettingsTarget(target, highlightTarget); onTargetHandled(); } }; return targetRef; } export function SettingsSearchTarget({ children, // `searchableSetting` names the target for search; as an HTML title it shows a native tooltip. title: _searchTitle, ...targetProps }: ComponentPropsWithoutRef<"div">) { const targetRef = useSettingsSearchTarget(targetProps.id); return (
{children}
); } // Settings pickers reuse composer controls but match the bordered selects beside them. export const SETTINGS_PICKER_TRIGGER_CLASSNAME = "min-w-0 max-w-none shrink-0 border-input bg-background font-normal text-foreground shadow-xs/5 hover:bg-accent/50 dark:bg-input/32 [&_svg[data-composer-control-chevron]]:stroke-[1.75] [&_svg[data-composer-control-chevron]]:opacity-70"; export function PolicyTooltip({ children }: { readonly children: string }) { return ( } /> {children} ); } export function SettingsSection({ title, hideTitle = false, icon, titleAction, headerAction, variant = "grouped", children, className, ...sectionProps }: ComponentPropsWithoutRef<"section"> & { title: string; hideTitle?: boolean; icon?: ReactNode; titleAction?: ReactNode; headerAction?: ReactNode; variant?: "grouped" | "plain"; children: ReactNode; }) { const targetRef = useSettingsSearchTarget(sectionProps.id); return (
{hideTitle ? (

{title}

) : (

{icon} {title}

{titleAction}
{headerAction}
)} {children}
); } export function SettingsUnavailableGroup({ children, message, }: { children: ReactNode; message?: ReactNode; }) { if (message === undefined) return children; return (

{message}

{children}
); } function projectOverrides( context: ReturnType, scopedKeys: ReadonlyArray, ): SettingOverridingProject[] { if (context === null || scopedKeys.length === 0) return []; return listProjectOverrides(context.connectedEnvironments, scopedKeys).flatMap((entry) => { const group = context.groups.find((candidate) => candidate.memberProjects.some( (member) => member.environmentId === entry.environmentId && member.id === entry.projectId, ), ); if (!group) return []; return [ { ...entry, label: group.displayName, open: () => context.selectScope({ project: group.projectKey, ...(context.search.machine ? { machine: context.search.machine } : {}), }), }, ]; }); } export function SettingsRow({ title, description, status, resetAction, onResetOverride, control, serverScoped = false, settingKeys = EMPTY_SETTING_KEYS, managedPaths = EMPTY_MANAGED_PATHS, mixed: mixedOverride, managed, children, className, ...rowProps }: Omit, "title"> & { title: ReactNode; description?: ReactNode; status?: ReactNode; resetAction?: ReactNode; onResetOverride?: () => void; control?: ReactNode; serverScoped?: boolean; settingKeys?: readonly (keyof ServerSettings)[]; /** Dot paths inside a setting, such as `storageCleanup.logsAfterDays`, an administrator can lock. */ managedPaths?: readonly string[]; mixed?: boolean; /** Why an administrator's policy turns this row's control off, from the contract's denial helpers. */ managed?: string | undefined; children?: ReactNode; }) { const targetRef = useSettingsSearchTarget(rowProps.id); const primarySettingsAvailable = usePrimarySettingsAvailable(); const context = useOptionalSettingsScope(); const primaryEnvironmentId = usePrimaryEnvironmentId(); const primaryEnvironment = usePrimaryEnvironment(); const primaryCanWrite = useEnvironmentScope(primaryEnvironmentId, AuthSettingsWriteScope); const writableIds = useEnvironmentsWithScope( context?.connectedEnvironments ?? [], AuthSettingsWriteScope, ); const canWriteSettings = context ? context.connectedEnvironments.length > 0 && context.connectedEnvironments.every((target) => writableIds.has(target.environmentId)) : primaryCanWrite; const serverConfigs = useServerConfigs(); // An administrator's lock on any target wins over every other reason a control is inert. const lockablePaths = [...settingKeys, ...managedPaths]; const managedBy = lockablePaths.length === 0 ? undefined : (context ? context.connectedEnvironments.map((environment) => environment.serverConfig) : [primaryEnvironmentId === null ? undefined : serverConfigs.get(primaryEnvironmentId)] ) .map((config) => config?.managedPolicy) .find( (policy) => policy !== undefined && lockablePaths.some((path) => isManagedSettingPath(policy, path)), )?.organization; const clearOverrides = useClearScopedSettings(); const clearProjectOverrides = useClearProjectOverrides(); const isProjectScope = context !== null && (context.scope.kind === "project" || context.scope.kind === "checkout"); const scopedKeys = settingKeys.filter(isProjectScopedSettingKey); const environmentWide = isProjectScope && serverScoped && scopedKeys.length === 0; const mixed = mixedOverride ?? (context !== null && scopedSettingsAreMixed(context.targets, settingKeys)); const source = context && isProjectScope ? scopedSettingsSource(context.targets, scopedKeys) : null; const unavailable = serverScoped && (!canWriteSettings || !(context ? context.connectedEnvironments.length > 0 : primarySettingsAvailable)); const inheritedFrom = source === "environment" && context?.scope.environmentIds.length === 1 ? (context.environments.find( (environment) => environment.environmentId === context.scope.environmentIds[0], )?.label ?? "environment") : "environment"; const environmentSettingsById = new Map( (context?.connectedEnvironments ?? []).flatMap((environment) => environment.serverConfig ? [[environment.environmentId, environment.serverConfig.settings] as const] : [], ), ); const overridingProjects = isProjectScope ? [] : projectOverrides(context, scopedKeys); const renderedReset = unavailable || managedBy !== undefined || managed !== undefined ? null : isProjectScope && scopedKeys.length > 0 ? ( source === "project" || source === "mixed" ? ( (onResetOverride ? onResetOverride() : clearOverrides(scopedKeys))} /> ) : null ) : ( resetAction ); const inertControl = (message: string) => ( } >
{control}
{message}
); const renderedControl = managed !== undefined && control ? inertControl(managed) : managedBy !== undefined && control ? inertControl(`Managed by ${managedBy}.`) : unavailable && control ? inertControl( !canWriteSettings ? `This connection lacks permission to change settings on ${ context ? context.connectedEnvironments .filter((target) => !writableIds.has(target.environmentId)) .map((target) => target.label) .join(", ") || "the selected environment" : (primaryEnvironment?.label ?? "the primary environment") }.` : context ? "Reconnect the selected environment to change this setting." : PRIMARY_SETTINGS_UNAVAILABLE_MESSAGE, ) : environmentWide && control ? inertControl("Environment-wide setting. Select an environment to change it.") : control; const customized = context !== null && settingKeys.some((key) => context.targets.some((candidate) => { const environmentSettings = environmentSettingsById.get(candidate.environmentId); return ( environmentSettings !== undefined && !Equal.equals(environmentSettings[key], DEFAULT_SERVER_SETTINGS[key]) ); }), ); const inheritance: { state: SettingInheritanceState; summary: string } = mixed ? { state: "mixed", summary: "Mixed across selected environments" } : source === "project" ? { state: "overridden", summary: "Overridden for this project" } : source === "t3.json" ? { state: "inherited", summary: "Inherited from the repository's t3.json" } : source === "environment" && scopedKeys.length > 0 ? { state: "inherited", summary: `Inherited from ${inheritedFrom}` } : customized ? { state: "environment", summary: "Set on the environment" } : { state: "default", summary: "Built-in default" }; const renderedInheritance = context && serverScoped && settingKeys.length > 0 ? ( clearProjectOverrides(entries, scopedKeys), } : {})} /> ) : null; const renderedStatus = status; return (

{title}

{renderedInheritance ? ( {renderedInheritance} ) : null} {renderedReset}
{description ? (

{description}

) : null} {renderedStatus ? (
{renderedStatus}
) : null}
{renderedControl ? (
{renderedControl}
) : null}
{unavailable && children ? (
{children}
) : ( children )}
); } export function SettingResetButton({ label, tooltip = "Reset to default", disabled = false, onClick, }: { label: string; tooltip?: string; disabled?: boolean; onClick: () => void; }) { return ( { event.stopPropagation(); onClick(); }} > } /> {tooltip} ); } export function SettingsPageContainer({ children, className, width = "readable", searchTargetFallbacks, }: { children: ReactNode; className?: string; width?: WorkspacePageWidth; searchTargetFallbacks?: Readonly>; }) { const navigate = useNavigate(); const hash = useLocation({ select: (location) => location.hash }); const highlightTarget = useLocation({ select: (location) => location.state.settingsTargetHighlight !== false, }); const targetId = hash.replace(/^#/, "") || null; const clearTargetHash = () => { void navigate({ hash: "", replace: true, resetScroll: false, hashScrollIntoView: false, state: { settingsTargetHighlight: true }, }); }; return (
{children}
); } export function scrollToSettingsTarget( targetId: string, { highlight = true }: { readonly highlight?: boolean } = {}, ): boolean { const target = document.getElementById(targetId); if (!target) return false; scrollAndFocusSettingsTarget(target, highlight); return true; }