import type { ScopedProjectRef } from "@t3tools/contracts"; import { scopedProjectKey, scopeProjectRef } from "@t3tools/client-runtime/environment"; import { FolderPlusIcon } from "lucide-react"; import { useCallback, useMemo } from "react"; import { openCommandPalette } from "~/commandPaletteBus"; import { useNewThreadHandler } from "~/hooks/useHandleNewThread"; import { useClientSettings } from "~/hooks/useSettings"; import { selectProjectGroupingSettings } from "~/logicalProject"; import { buildSidebarProjectPickerEntries, buildSidebarProjectSnapshots, } from "~/sidebarProjectGrouping"; import { useProjects, useThreadShells } from "~/state/entities"; import { useEnvironments, usePrimaryEnvironmentId } from "~/state/environments"; import { sortLogicalProjectsForSidebar } from "../Sidebar.logic"; import { Menu, MenuItem, MenuPopup, MenuRadioGroup, MenuRadioItem, MenuSeparator, MenuTrigger, } from "../ui/menu"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; interface DraftHeroHeadlineProps { readonly activeProjectRef: ScopedProjectRef | null; readonly activeProjectTitle: string | null; } export function DraftHeroHeadline({ activeProjectRef, activeProjectTitle, }: DraftHeroHeadlineProps) { const projects = useProjects(); const threads = useThreadShells(); const { environments } = useEnvironments(); const primaryEnvironmentId = usePrimaryEnvironmentId(); const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings); const projectSortOrder = useClientSettings((settings) => settings.sidebarProjectSortOrder); const handleNewThread = useNewThreadHandler(); const openAddProject = useCallback(() => openCommandPalette({ open: "add-project" }), []); const environmentLabelById = useMemo( () => new Map( environments.map((environment) => [environment.environmentId, environment.label] as const), ), [environments], ); const projectGroups = useMemo( () => sortLogicalProjectsForSidebar( buildSidebarProjectSnapshots({ projects, settings: projectGroupingSettings, primaryEnvironmentId, resolveEnvironmentLabel: (environmentId) => environmentLabelById.get(environmentId) ?? null, }), threads, projectSortOrder, ), [ environmentLabelById, primaryEnvironmentId, projectGroupingSettings, projectSortOrder, projects, threads, ], ); const projectPickerEntries = useMemo( () => buildSidebarProjectPickerEntries({ groups: projectGroups, preferredProjectRef: activeProjectRef, }), [activeProjectRef, projectGroups], ); const projectEntryByKey = useMemo( () => new Map(projectPickerEntries.map((entry) => [entry.group.projectKey, entry] as const)), [projectPickerEntries], ); const activeProjectGroup = activeProjectRef === null ? null : (projectGroups.find((group) => group.memberProjectRefs.some( (projectRef) => scopedProjectKey(projectRef) === scopedProjectKey(activeProjectRef), ), ) ?? null); const activeProjectKey = activeProjectGroup?.projectKey ?? ""; const activeProjectDisplayName = activeProjectGroup?.displayName ?? activeProjectTitle; const hasResolvedProject = activeProjectTitle !== null; const canChooseProject = projectPickerEntries.length > 0; const shouldShowProjectMenu = canChooseProject; const projectSelector = shouldShowProjectMenu ? ( } > {activeProjectDisplayName ?? "Choose a project"} {activeProjectDisplayName ? ( {activeProjectDisplayName} ) : null} { const entry = projectEntryByKey.get(value as string); if (!entry || value === activeProjectKey) { return; } const project = entry.targetProject; // Changing the repo of a draft moves the typed content along: // the user started writing in the wrong project, not a new task. void handleNewThread(scopeProjectRef(project.environmentId, project.id), { replace: true, carryComposerContent: true, }); }} > {projectPickerEntries.map(({ group }) => { return ( }> {group.displayName} {group.displayName} ); })} New project ) : ( ); return (

{hasResolvedProject ? ( <>What should we build in {projectSelector}? ) : canChooseProject ? ( <>{projectSelector} to start ) : ( <>Add a project to start )}

); }