History

fix grouped project menu actions

Julius Marminge committed 3 months ago

Commit ebb4fa01 parent 2e484a21 file · +68 −48Browse files

Unified · Split

apps/web/src/components/SidebarV2.tsx+68−48
@@ -1,10 +1,7 @@
1import { autoAnimate } from "@formkit/auto-animate";1import { autoAnimate } from "@formkit/auto-animate";
2import { useAtomValue } from "@effect/atom-react";2import { useAtomValue } from "@effect/atom-react";
3import { effectiveSettled } from "@t3tools/client-runtime/state/thread-settled";3import { effectiveSettled } from "@t3tools/client-runtime/state/thread-settled";
4import type {4import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/models";
5 EnvironmentProject,
6 EnvironmentThreadShell,
7} from "@t3tools/client-runtime/state/models";
8import {5import {
9 scopeProjectRef,6 scopeProjectRef,
10 scopeThreadRef,7 scopeThreadRef,
@@ -62,7 +59,10 @@ import { isMacPlatform } from "~/lib/utils"; · 51 unmodified lines
62import { useOpenPrLink } from "../lib/openPullRequestLink";59import { useOpenPrLink } from "../lib/openPullRequestLink";
63import { readLocalApi } from "../localApi";60import { readLocalApi } from "../localApi";
64import { getProjectOrderKey, selectProjectGroupingSettings } from "../logicalProject";61import { getProjectOrderKey, selectProjectGroupingSettings } from "../logicalProject";
65import { buildSidebarProjectSnapshots } from "../sidebarProjectGrouping";62import {
63 buildSidebarProjectSnapshots,
64 type SidebarProjectSnapshot,
65} from "../sidebarProjectGrouping";
66import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore";66import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore";
67import { useThreadSelectionStore } from "../threadSelectionStore";67import { useThreadSelectionStore } from "../threadSelectionStore";
68import { useThreadActions } from "../hooks/useThreadActions";68import { useThreadActions } from "../hooks/useThreadActions";
@@ -894,66 +894,86 @@ export default function SidebarV2() { · 825 unmodified lines
894 }, [clearSelection, projectScopeKey]);894 }, [clearSelection, projectScopeKey]);
895 895
896 const handleRemoveProject = useCallback(896 const handleRemoveProject = useCallback(
897 async (project: EnvironmentProject) => {897 async (projectGroup: SidebarProjectSnapshot) => {
898 const api = readLocalApi();898 const api = readLocalApi();
899 if (!api) return;899 if (!api) return;
900 900
901 const projectThreads = threads.filter(901 const memberKeys = new Set(
902 (thread) =>902 projectGroup.memberProjectRefs.map(
903 thread.environmentId === project.environmentId && thread.projectId === project.id,903 (projectRef) => `${projectRef.environmentId}:${projectRef.projectId}`,
904 ),
905 );
906 const projectThreads = threads.filter((thread) =>
907 memberKeys.has(`${thread.environmentId}:${thread.projectId}`),
904 );908 );
905 const confirmed = await settlePromise(() =>909 const confirmed = await settlePromise(() =>
906 api.dialogs.confirm(910 api.dialogs.confirm(
907 projectThreads.length > 0911 projectThreads.length > 0
908 ? [912 ? [
909 `Remove project "${project.title}" and delete its ${projectThreads.length} thread${projectThreads.length === 1 ? "" : "s"}?`,913 `Remove project "${projectGroup.displayName}" and delete its ${projectThreads.length} thread${projectThreads.length === 1 ? "" : "s"}?`,
910 `Path: ${project.workspaceRoot}`,914 ...(projectGroup.memberProjects.length === 1
915 ? [`Path: ${projectGroup.workspaceRoot}`]
916 : [
917 `This removes ${projectGroup.memberProjects.length} grouped project entries.`,
918 ]),
911 "This permanently clears conversation history for those threads.",919 "This permanently clears conversation history for those threads.",
912 "This removes only the project entry, not the files on disk.",920 "This removes only the project entries, not the files on disk.",
913 "This action cannot be undone.",921 "This action cannot be undone.",
914 ].join("\n")922 ].join("\n")
915 : [923 : [
916 `Remove project "${project.title}"?`,924 `Remove project "${projectGroup.displayName}"?`,
917 `Path: ${project.workspaceRoot}`,925 ...(projectGroup.memberProjects.length === 1
918 "This removes only the project entry, not the files on disk.",926 ? [`Path: ${projectGroup.workspaceRoot}`]
927 : [
928 `This removes ${projectGroup.memberProjects.length} grouped project entries.`,
929 ]),
930 "This removes only the project entries, not the files on disk.",
919 ].join("\n"),931 ].join("\n"),
920 ),932 ),
921 );933 );
922 if (confirmed._tag === "Failure" || !confirmed.value) return;934 if (confirmed._tag === "Failure" || !confirmed.value) return;
923 935
924 const projectRef = scopeProjectRef(project.environmentId, project.id);936 const draftStore = useComposerDraftStore.getState();
925 const result = await deleteProject({937 let shouldNavigate = false;
926 environmentId: project.environmentId,938 for (const project of projectGroup.memberProjects) {
927 input: {939 const memberThreads = projectThreads.filter(
928 projectId: project.id,940 (thread) =>
929 ...(projectThreads.length > 0 ? { force: true } : {}),941 thread.environmentId === project.environmentId && thread.projectId === project.id,
930 },942 );
931 });943 const projectRef = scopeProjectRef(project.environmentId, project.id);
932 if (result._tag === "Failure") {944 const projectDraftThread = draftStore.getDraftThreadByProjectRef(projectRef);
933 if (!isAtomCommandInterrupted(result)) {945 shouldNavigate ||= shouldNavigateAfterProjectRemoval({
934 const error = squashAtomCommandFailure(result);946 routeTarget: routeTargetRef.current,
935 toastManager.add(947 projectThreads: memberThreads,
936 stackedThreadToast({948 projectDraftId: projectDraftThread?.draftId ?? null,
937 type: "error",949 });
938 title: `Failed to remove "${project.title}"`,950
939 description: error instanceof Error ? error.message : "An error occurred.",951 const result = await deleteProject({
940 }),952 environmentId: project.environmentId,
941 );953 input: {
954 projectId: project.id,
955 ...(memberThreads.length > 0 ? { force: true } : {}),
956 },
957 });
958 if (result._tag === "Failure") {
959 if (!isAtomCommandInterrupted(result)) {
960 const error = squashAtomCommandFailure(result);
961 toastManager.add(
962 stackedThreadToast({
963 type: "error",
964 title: `Failed to remove "${project.title}"`,
965 description: error instanceof Error ? error.message : "An error occurred.",
966 }),
967 );
968 }
969 return;
942 }970 }
943 return;
944 }
945 971
946 const draftStore = useComposerDraftStore.getState();972 if (projectDraftThread) {
947 const projectDraftThread = draftStore.getDraftThreadByProjectRef(projectRef);973 draftStore.clearDraftThread(projectDraftThread.draftId);
948 const shouldNavigate = shouldNavigateAfterProjectRemoval({974 }
949 routeTarget: routeTargetRef.current,975 draftStore.clearProjectDraftThreadId(projectRef);
950 projectThreads,
951 projectDraftId: projectDraftThread?.draftId ?? null,
952 });
953 if (projectDraftThread) {
954 draftStore.clearDraftThread(projectDraftThread.draftId);
955 }976 }
956 draftStore.clearProjectDraftThreadId(projectRef);
957 977
958 if (shouldNavigate) {978 if (shouldNavigate) {
959 void router.navigate({ to: "/" });979 void router.navigate({ to: "/" });
@@ -1650,11 +1670,11 @@ export default function SidebarV2() { · 690 unmodified lines
1650 cwd={project.workspaceRoot}1670 cwd={project.workspaceRoot}
1651 className="size-4 shrink-0"1671 className="size-4 shrink-0"
1652 />1672 />
1653 <span className="min-w-0 truncate text-sm">{project.title}</span>1673 <span className="min-w-0 truncate text-sm">{project.displayName}</span>
1654 <button1674 <button
1655 type="button"1675 type="button"
1656 aria-label={`Remove project ${project.title}`}1676 aria-label={`Remove project ${project.displayName}`}
1657 title={`Remove ${project.title}`}1677 title={`Remove ${project.displayName}`}
1658 className="ml-auto inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground/55 outline-none transition-colors hover:bg-destructive/12 hover:text-destructive focus-visible:bg-destructive/12 focus-visible:text-destructive focus-visible:ring-2 focus-visible:ring-destructive/40"1678 className="ml-auto inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground/55 outline-none transition-colors hover:bg-destructive/12 hover:text-destructive focus-visible:bg-destructive/12 focus-visible:text-destructive focus-visible:ring-2 focus-visible:ring-destructive/40"
1659 onPointerDown={(event) => event.stopPropagation()}1679 onPointerDown={(event) => event.stopPropagation()}
1660 onClick={(event) => {1680 onClick={(event) => {