Keep selected projects when switching hosts (#2700)
* fix(app): preserve project selection across hosts Rendered project identity and persisted equivalence were conflated after duplicate-project handling. Keep them separate so host changes resolve selectable equivalents deterministically without resetting legacy sidebar identity. * fix(app): restore selected clone after host round trip Keep manual origin and current destination placements separately so switching back restores the chosen checkout while archive gaps preserve the active host selection. * fix(app): key status icons by project view * fix(app): preserve origin during archive gaps
This commit is contained in:
@@ -77,14 +77,14 @@ interface SidebarSharedProps {
|
||||
pinnedGroups: PinnedSidebarGroups;
|
||||
projects: SidebarProjectEntry[];
|
||||
workspaceEntriesByKey: ReadonlyMap<string, SidebarWorkspaceEntry>;
|
||||
projectNamesByKey: Map<string, string>;
|
||||
projectNamesByViewKey: Map<string, string>;
|
||||
isInitialLoad: boolean;
|
||||
isRevalidating: boolean;
|
||||
isManualRefresh: boolean;
|
||||
groupMode: SidebarGroupMode;
|
||||
collapsedProjectKeys: ReadonlySet<string>;
|
||||
shortcutIndexByWorkspaceKey: Map<string, number>;
|
||||
toggleProjectCollapsed: (projectKey: string) => void;
|
||||
toggleProjectCollapsed: (projectViewKey: string) => void;
|
||||
handleRefresh: () => void;
|
||||
handleOpenProject: () => void;
|
||||
handleHome: () => void;
|
||||
@@ -132,7 +132,7 @@ export const LeftSidebar = memo(function LeftSidebar({ active }: { active: boole
|
||||
const {
|
||||
projects,
|
||||
workspaceEntriesByKey,
|
||||
projectNamesByKey,
|
||||
projectNamesByViewKey,
|
||||
isInitialLoad,
|
||||
isRevalidating,
|
||||
refreshAll,
|
||||
@@ -238,7 +238,7 @@ export const LeftSidebar = memo(function LeftSidebar({ active }: { active: boole
|
||||
pinnedGroups,
|
||||
projects,
|
||||
workspaceEntriesByKey,
|
||||
projectNamesByKey,
|
||||
projectNamesByViewKey,
|
||||
isInitialLoad,
|
||||
isRevalidating,
|
||||
isManualRefresh,
|
||||
@@ -592,7 +592,7 @@ function MobileSidebar({
|
||||
pinnedGroups,
|
||||
projects,
|
||||
workspaceEntriesByKey,
|
||||
projectNamesByKey,
|
||||
projectNamesByViewKey,
|
||||
isInitialLoad,
|
||||
isRevalidating,
|
||||
isManualRefresh,
|
||||
@@ -708,7 +708,7 @@ function MobileSidebar({
|
||||
pinnedGroups={pinnedGroups}
|
||||
projects={projects}
|
||||
workspaceEntriesByKey={workspaceEntriesByKey}
|
||||
projectNamesByKey={projectNamesByKey}
|
||||
projectNamesByViewKey={projectNamesByViewKey}
|
||||
isRefreshing={isManualRefresh && isRevalidating}
|
||||
onRefresh={handleRefresh}
|
||||
onWorkspacePress={handleWorkspacePress}
|
||||
@@ -738,7 +738,7 @@ function DesktopSidebar({
|
||||
pinnedGroups,
|
||||
projects,
|
||||
workspaceEntriesByKey,
|
||||
projectNamesByKey,
|
||||
projectNamesByViewKey,
|
||||
isInitialLoad,
|
||||
isRevalidating,
|
||||
isManualRefresh,
|
||||
@@ -875,7 +875,7 @@ function DesktopSidebar({
|
||||
pinnedGroups={pinnedGroups}
|
||||
projects={projects}
|
||||
workspaceEntriesByKey={workspaceEntriesByKey}
|
||||
projectNamesByKey={projectNamesByKey}
|
||||
projectNamesByViewKey={projectNamesByViewKey}
|
||||
isRefreshing={isManualRefresh && isRevalidating}
|
||||
onRefresh={handleRefresh}
|
||||
onAddProject={handleOpenProject}
|
||||
|
||||
@@ -15,22 +15,22 @@ const WHITE_TEXT = { color: "#ffffff" } as const;
|
||||
export function ProjectIconView({
|
||||
iconDataUri,
|
||||
initial,
|
||||
projectKey,
|
||||
projectViewKey,
|
||||
imageStyle,
|
||||
fallbackStyle,
|
||||
textStyle,
|
||||
}: {
|
||||
iconDataUri: string | null;
|
||||
initial: string;
|
||||
projectKey: string;
|
||||
projectViewKey: string;
|
||||
imageStyle: StyleProp<ImageStyle>;
|
||||
fallbackStyle: StyleProp<ViewStyle>;
|
||||
textStyle: StyleProp<TextStyle>;
|
||||
}) {
|
||||
const imageSource = useMemo(() => ({ uri: iconDataUri ?? "" }), [iconDataUri]);
|
||||
const fallbackStyles = useMemo(
|
||||
() => [fallbackStyle, { backgroundColor: deriveProjectIconColor(projectKey) }],
|
||||
[fallbackStyle, projectKey],
|
||||
() => [fallbackStyle, { backgroundColor: deriveProjectIconColor(projectViewKey) }],
|
||||
[fallbackStyle, projectViewKey],
|
||||
);
|
||||
const textStyles = useMemo(() => [textStyle, WHITE_TEXT], [textStyle]);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import React from "react";
|
||||
import type { ReactElement } from "react";
|
||||
import { createProjectViewKey } from "@/projects/workspace-structure";
|
||||
|
||||
vi.hoisted(() => {
|
||||
(globalThis as unknown as { __DEV__: boolean }).__DEV__ = false;
|
||||
@@ -196,7 +197,7 @@ function ProjectHeaderProbe({
|
||||
project: SidebarProjectEntry;
|
||||
counts: RenderCounts;
|
||||
}): null {
|
||||
incrementRecord(counts.headers, project.projectKey);
|
||||
incrementRecord(counts.headers, project.viewKey);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -232,7 +233,7 @@ function ProjectActiveProbe({
|
||||
activeSelection?.serverId === serverId &&
|
||||
project.workspaces.some((entry) => entry.workspaceId === activeSelection.workspaceId);
|
||||
void isActive;
|
||||
incrementRecord(counts.projectSelection, project.projectKey);
|
||||
incrementRecord(counts.projectSelection, project.viewKey);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -260,7 +261,7 @@ function SidebarFrameProbe({ counts }: { counts: RenderCounts }): ReactElement {
|
||||
return (
|
||||
<>
|
||||
{projects.map((project) => (
|
||||
<div key={project.projectKey}>
|
||||
<div key={project.viewKey}>
|
||||
<ProjectHeaderProbe project={project} counts={counts} />
|
||||
<ProjectActiveProbe serverId={SERVER_ID} project={project} counts={counts} />
|
||||
{project.workspaces.map((entry) => (
|
||||
@@ -453,8 +454,8 @@ describe("sidebar workspace render isolation", () => {
|
||||
|
||||
expect(counts.frame).toBe(1);
|
||||
expect(counts.projectSelection).toEqual({
|
||||
"project-a": 1,
|
||||
"project-b": 1,
|
||||
[createProjectViewKey({ kind: "equivalence", projectKey: "project-a" })]: 1,
|
||||
[createProjectViewKey({ kind: "equivalence", projectKey: "project-b" })]: 1,
|
||||
});
|
||||
expect(counts.rowSelection).toEqual({
|
||||
"a-main": 1,
|
||||
|
||||
@@ -57,7 +57,7 @@ import {
|
||||
import { useSidebarCollapsedSectionsStore } from "@/stores/sidebar-collapsed-sections-store";
|
||||
import { useHostFeatureMap } from "@/runtime/host-features";
|
||||
import { useIsCompactFormFactor } from "@/constants/layout";
|
||||
import { useProjectIconDataByProjectKey } from "@/projects/project-icons";
|
||||
import { useProjectIconDataByProjectViewKey } from "@/projects/project-icons";
|
||||
import {
|
||||
buildNewWorkspaceRoute,
|
||||
buildProjectSettingsRoute,
|
||||
@@ -137,7 +137,7 @@ import { useLocalDaemonServerId } from "@/hooks/use-is-local-daemon";
|
||||
|
||||
const workspaceKeyExtractor = (workspace: SidebarWorkspacePlacement) => workspace.workspaceKey;
|
||||
|
||||
const projectKeyExtractor = (project: SidebarProjectEntry) => project.projectKey;
|
||||
const projectViewKeyExtractor = (project: SidebarProjectEntry) => project.viewKey;
|
||||
|
||||
const WORKSPACE_STATUS_DOT_WIDTH = 14;
|
||||
const DEFAULT_STATUS_DOT_SIZE = 7;
|
||||
@@ -234,9 +234,9 @@ interface SidebarWorkspaceListProps {
|
||||
pinnedGroups: PinnedSidebarGroups;
|
||||
projects: SidebarProjectEntry[];
|
||||
workspaceEntriesByKey: ReadonlyMap<string, SidebarWorkspaceEntry>;
|
||||
projectNamesByKey: Map<string, string>;
|
||||
projectNamesByViewKey: Map<string, string>;
|
||||
collapsedProjectKeys: ReadonlySet<string>;
|
||||
onToggleProjectCollapsed: (projectKey: string) => void;
|
||||
onToggleProjectCollapsed: (projectViewKey: string) => void;
|
||||
shortcutIndexByWorkspaceKey: Map<string, number>;
|
||||
groupMode: "project" | "status";
|
||||
isRefreshing?: boolean;
|
||||
@@ -431,7 +431,7 @@ function ProjectLeadingVisual({
|
||||
displayName,
|
||||
iconDataUri,
|
||||
workspace,
|
||||
projectKey,
|
||||
projectViewKey,
|
||||
chevron = null,
|
||||
showChevron = false,
|
||||
isArchiving = false,
|
||||
@@ -439,7 +439,7 @@ function ProjectLeadingVisual({
|
||||
displayName: string;
|
||||
iconDataUri: string | null;
|
||||
workspace: SidebarWorkspaceEntry | null;
|
||||
projectKey: string;
|
||||
projectViewKey: string;
|
||||
chevron?: "expand" | "collapse" | null;
|
||||
showChevron?: boolean;
|
||||
isArchiving?: boolean;
|
||||
@@ -467,7 +467,7 @@ function ProjectLeadingVisual({
|
||||
<ProjectIcon
|
||||
iconDataUri={iconDataUri}
|
||||
placeholderInitial={placeholderInitial}
|
||||
projectKey={projectKey}
|
||||
projectViewKey={projectViewKey}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
@@ -477,7 +477,7 @@ function ProjectLeadingVisual({
|
||||
<ProjectLeadingVisualStatus
|
||||
iconDataUri={iconDataUri}
|
||||
placeholderInitial={placeholderInitial}
|
||||
projectKey={projectKey}
|
||||
projectViewKey={projectViewKey}
|
||||
isArchiving={isArchiving}
|
||||
shouldShowSyncedLoader={shouldShowSyncedLoader}
|
||||
activeWorkspace={activeWorkspace}
|
||||
@@ -518,7 +518,7 @@ function ProjectRowTrailingActions({
|
||||
onPress={onBeginWorkspaceSetup}
|
||||
visible={actionsVisible}
|
||||
showShortcutHint={isProjectActive}
|
||||
testID={`sidebar-project-new-worktree-${project.projectKey}`}
|
||||
testID={`sidebar-project-new-worktree-${project.viewKey}`}
|
||||
/>
|
||||
) : null}
|
||||
{onRemoveProject ? (
|
||||
@@ -527,7 +527,7 @@ function ProjectRowTrailingActions({
|
||||
pointerEvents={actionsVisible ? "auto" : "none"}
|
||||
>
|
||||
<ProjectKebabMenu
|
||||
projectKey={project.projectKey}
|
||||
projectViewKey={project.viewKey}
|
||||
settingsTarget={settingsTarget}
|
||||
projectPath={localProjectPath}
|
||||
onRemoveProject={onRemoveProject}
|
||||
@@ -555,13 +555,13 @@ function renderKebabTriggerIcon({ hovered }: { hovered?: boolean }) {
|
||||
}
|
||||
|
||||
function ProjectKebabMenu({
|
||||
projectKey,
|
||||
projectViewKey,
|
||||
settingsTarget,
|
||||
projectPath,
|
||||
onRemoveProject,
|
||||
removeProjectStatus,
|
||||
}: {
|
||||
projectKey: string;
|
||||
projectViewKey: string;
|
||||
settingsTarget: { serverId: string; projectId: string } | null;
|
||||
projectPath: string;
|
||||
onRemoveProject: () => void;
|
||||
@@ -595,14 +595,14 @@ function ProjectKebabMenu({
|
||||
style={projectKebabStyle}
|
||||
accessibilityRole={platformIsWeb ? undefined : "button"}
|
||||
accessibilityLabel={t("sidebar.project.actions.menu")}
|
||||
testID={`sidebar-project-kebab-${projectKey}`}
|
||||
testID={`sidebar-project-kebab-${projectViewKey}`}
|
||||
>
|
||||
{renderKebabTriggerIcon}
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" width={220}>
|
||||
{canOpenProjectSettings ? (
|
||||
<DropdownMenuItem
|
||||
testID={`sidebar-project-menu-open-settings-${projectKey}`}
|
||||
testID={`sidebar-project-menu-open-settings-${projectViewKey}`}
|
||||
leading={settingsLeadingIcon}
|
||||
onSelect={handleOpenProjectSettings}
|
||||
>
|
||||
@@ -611,7 +611,7 @@ function ProjectKebabMenu({
|
||||
) : null}
|
||||
{canOpenInNewWindow ? (
|
||||
<DropdownMenuItem
|
||||
testID={`sidebar-project-menu-open-new-window-${projectKey}`}
|
||||
testID={`sidebar-project-menu-open-new-window-${projectViewKey}`}
|
||||
leading={openInNewWindowLeadingIcon}
|
||||
onSelect={handleOpenInNewWindow}
|
||||
>
|
||||
@@ -620,10 +620,10 @@ function ProjectKebabMenu({
|
||||
) : null}
|
||||
<OpenInFileManagerMenuItem
|
||||
path={projectPath}
|
||||
testID={`sidebar-project-menu-open-folder-${projectKey}`}
|
||||
testID={`sidebar-project-menu-open-folder-${projectViewKey}`}
|
||||
/>
|
||||
<DropdownMenuItem
|
||||
testID={`sidebar-project-menu-remove-${projectKey}`}
|
||||
testID={`sidebar-project-menu-remove-${projectViewKey}`}
|
||||
leading={trash2LeadingIcon}
|
||||
status={removeProjectStatus}
|
||||
pendingLabel={t("sidebar.project.actions.removing")}
|
||||
@@ -725,17 +725,17 @@ function WorkspaceRowRightGroup({
|
||||
function ProjectIcon({
|
||||
iconDataUri,
|
||||
placeholderInitial,
|
||||
projectKey,
|
||||
projectViewKey,
|
||||
}: {
|
||||
iconDataUri: string | null;
|
||||
placeholderInitial: string;
|
||||
projectKey: string;
|
||||
projectViewKey: string;
|
||||
}) {
|
||||
return (
|
||||
<ProjectIconView
|
||||
iconDataUri={iconDataUri}
|
||||
initial={placeholderInitial}
|
||||
projectKey={projectKey}
|
||||
projectViewKey={projectViewKey}
|
||||
imageStyle={styles.projectIcon}
|
||||
fallbackStyle={styles.projectIconFallback}
|
||||
textStyle={styles.projectIconFallbackText}
|
||||
@@ -746,14 +746,14 @@ function ProjectIcon({
|
||||
function ProjectLeadingVisualStatus({
|
||||
iconDataUri,
|
||||
placeholderInitial,
|
||||
projectKey,
|
||||
projectViewKey,
|
||||
isArchiving,
|
||||
shouldShowSyncedLoader,
|
||||
activeWorkspace,
|
||||
}: {
|
||||
iconDataUri: string | null;
|
||||
placeholderInitial: string;
|
||||
projectKey: string;
|
||||
projectViewKey: string;
|
||||
isArchiving: boolean;
|
||||
shouldShowSyncedLoader: boolean;
|
||||
activeWorkspace: SidebarWorkspaceEntry;
|
||||
@@ -796,7 +796,7 @@ function ProjectLeadingVisualStatus({
|
||||
<ProjectIcon
|
||||
iconDataUri={iconDataUri}
|
||||
placeholderInitial={placeholderInitial}
|
||||
projectKey={projectKey}
|
||||
projectViewKey={projectViewKey}
|
||||
/>
|
||||
{dotColorStyle ? (
|
||||
<StatusDotOverlay
|
||||
@@ -936,7 +936,7 @@ function NewWorkspaceGhostRow({
|
||||
})}
|
||||
onPress={handlePress}
|
||||
style={rowStyle}
|
||||
testID={`sidebar-project-new-workspace-row-${project.projectKey}`}
|
||||
testID={`sidebar-project-new-workspace-row-${project.viewKey}`}
|
||||
>
|
||||
{({ hovered, pressed }) => (
|
||||
<>
|
||||
@@ -1040,7 +1040,7 @@ function ProjectHeaderRow({
|
||||
displayName={displayName}
|
||||
iconDataUri={iconDataUri}
|
||||
workspace={workspace}
|
||||
projectKey={project.projectKey}
|
||||
projectViewKey={project.viewKey}
|
||||
chevron={chevron}
|
||||
showChevron={isHovered && chevron !== null}
|
||||
isArchiving={isArchiving}
|
||||
@@ -1088,7 +1088,7 @@ function ProjectHeaderRow({
|
||||
onTouchMove={interaction.handleTouchMove}
|
||||
onPressOut={interaction.handlePressOut}
|
||||
onPress={handlePress}
|
||||
testID={`sidebar-project-row-${project.projectKey}`}
|
||||
testID={`sidebar-project-row-${project.viewKey}`}
|
||||
>
|
||||
{rowChildren}
|
||||
</ContextMenuTrigger>
|
||||
@@ -1111,7 +1111,7 @@ function ProjectHeaderRow({
|
||||
onTouchMove={interaction.handleTouchMove}
|
||||
onPressOut={interaction.handlePressOut}
|
||||
onPress={handlePress}
|
||||
testID={`sidebar-project-row-${project.projectKey}`}
|
||||
testID={`sidebar-project-row-${project.viewKey}`}
|
||||
>
|
||||
{rowChildren}
|
||||
</Pressable>
|
||||
@@ -1622,9 +1622,9 @@ function ProjectBlock({
|
||||
showShortcutBadges: boolean;
|
||||
shortcutIndexByWorkspaceKey: Map<string, number>;
|
||||
parentGestureRef?: MutableRefObject<GestureType | undefined>;
|
||||
onToggleCollapsed: (projectKey: string) => void;
|
||||
onToggleCollapsed: (projectViewKey: string) => void;
|
||||
onWorkspacePress?: () => void;
|
||||
onWorkspaceReorder: (projectKey: string, workspaces: SidebarWorkspacePlacement[]) => void;
|
||||
onWorkspaceReorder: (projectViewKey: string, workspaces: SidebarWorkspacePlacement[]) => void;
|
||||
onWorktreeCreated?: (workspaceId: string) => void;
|
||||
drag: () => void;
|
||||
isDragging: boolean;
|
||||
@@ -1725,9 +1725,9 @@ function ProjectBlock({
|
||||
|
||||
const handleWorkspaceDragEnd = useCallback(
|
||||
(workspaces: SidebarWorkspacePlacement[]) => {
|
||||
onWorkspaceReorder(project.projectKey, workspaces);
|
||||
onWorkspaceReorder(project.viewKey, workspaces);
|
||||
},
|
||||
[onWorkspaceReorder, project.projectKey],
|
||||
[onWorkspaceReorder, project.viewKey],
|
||||
);
|
||||
|
||||
const toast = useToast();
|
||||
@@ -1753,7 +1753,6 @@ function ProjectBlock({
|
||||
|
||||
setIsRemovingProject(true);
|
||||
const readiness = getCurrentProjectRemoveReadiness({
|
||||
projectKey: project.projectKey,
|
||||
hosts: project.hosts,
|
||||
});
|
||||
if (readiness.kind === "needs_host_update") {
|
||||
@@ -1785,11 +1784,11 @@ function ProjectBlock({
|
||||
setIsRemovingProject(false);
|
||||
});
|
||||
})();
|
||||
}, [isRemovingProject, displayName, t, toast, project.projectKey, project.hosts]);
|
||||
}, [isRemovingProject, displayName, t, toast, project.hosts]);
|
||||
|
||||
const handleToggleCollapsed = useCallback(() => {
|
||||
onToggleCollapsed(project.projectKey);
|
||||
}, [onToggleCollapsed, project.projectKey]);
|
||||
onToggleCollapsed(project.viewKey);
|
||||
}, [onToggleCollapsed, project.viewKey]);
|
||||
|
||||
let projectChildren = null;
|
||||
if (!collapsed) {
|
||||
@@ -1797,7 +1796,7 @@ function ProjectBlock({
|
||||
projectChildren = (
|
||||
<>
|
||||
<DraggableList
|
||||
testID={`sidebar-workspace-list-${project.projectKey}`}
|
||||
testID={`sidebar-workspace-list-${project.viewKey}`}
|
||||
data={visibleWorkspaces}
|
||||
keyExtractor={workspaceKeyExtractor}
|
||||
renderItem={renderWorkspace}
|
||||
@@ -1813,7 +1812,7 @@ function ProjectBlock({
|
||||
<SidebarGroupToggleRow
|
||||
expanded={workspacesExpanded}
|
||||
onPress={toggleWorkspacesExpanded}
|
||||
testID={`sidebar-project-show-more-${project.projectKey}`}
|
||||
testID={`sidebar-project-show-more-${project.viewKey}`}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
@@ -1925,7 +1924,7 @@ export function SidebarWorkspaceList({
|
||||
pinnedGroups,
|
||||
projects,
|
||||
workspaceEntriesByKey,
|
||||
projectNamesByKey,
|
||||
projectNamesByViewKey,
|
||||
collapsedProjectKeys,
|
||||
onToggleProjectCollapsed,
|
||||
shortcutIndexByWorkspaceKey,
|
||||
@@ -1959,7 +1958,7 @@ export function SidebarWorkspaceList({
|
||||
() => (groupMode === "status" ? resolveSidebarProjectIconTargets(projects) : []),
|
||||
[groupMode, projects],
|
||||
);
|
||||
const statusProjectIconByProjectKey = useProjectIconDataByProjectKey({
|
||||
const statusProjectIconByProjectViewKey = useProjectIconDataByProjectViewKey({
|
||||
projects: statusProjectIconTargets,
|
||||
});
|
||||
|
||||
@@ -1969,8 +1968,8 @@ export function SidebarWorkspaceList({
|
||||
statusGroups={statusGroups}
|
||||
pinnedGroups={pinnedGroups}
|
||||
workspaceEntriesByKey={workspaceEntriesByKey}
|
||||
projectNamesByKey={projectNamesByKey}
|
||||
projectIconByProjectKey={statusProjectIconByProjectKey}
|
||||
projectNamesByViewKey={projectNamesByViewKey}
|
||||
projectIconByProjectViewKey={statusProjectIconByProjectViewKey}
|
||||
shortcutIndexByWorkspaceKey={shortcutIndexByWorkspaceKey}
|
||||
onWorkspacePress={onWorkspacePress}
|
||||
hostLabelByServerId={hostLabelByServerId}
|
||||
@@ -2008,8 +2007,8 @@ function SidebarStatusModeWrapper({
|
||||
statusGroups,
|
||||
pinnedGroups,
|
||||
workspaceEntriesByKey,
|
||||
projectNamesByKey,
|
||||
projectIconByProjectKey,
|
||||
projectNamesByViewKey,
|
||||
projectIconByProjectViewKey,
|
||||
shortcutIndexByWorkspaceKey: _projectShortcutIndex,
|
||||
onWorkspacePress,
|
||||
hostLabelByServerId,
|
||||
@@ -2021,8 +2020,8 @@ function SidebarStatusModeWrapper({
|
||||
statusGroups: StatusGroup[];
|
||||
pinnedGroups: PinnedSidebarGroups;
|
||||
workspaceEntriesByKey: ReadonlyMap<string, SidebarWorkspaceEntry>;
|
||||
projectNamesByKey: Map<string, string>;
|
||||
projectIconByProjectKey: ReadonlyMap<string, string | null>;
|
||||
projectNamesByViewKey: Map<string, string>;
|
||||
projectIconByProjectViewKey: ReadonlyMap<string, string | null>;
|
||||
shortcutIndexByWorkspaceKey: Map<string, number>;
|
||||
onWorkspacePress?: () => void;
|
||||
hostLabelByServerId: ReadonlyMap<string, string>;
|
||||
@@ -2040,8 +2039,8 @@ function SidebarStatusModeWrapper({
|
||||
const entry = workspaceEntriesByKey.get(workspace.workspaceKey);
|
||||
return entry ? [entry] : [];
|
||||
})}
|
||||
projectNamesByKey={projectNamesByKey}
|
||||
projectIconByProjectKey={projectIconByProjectKey}
|
||||
projectNamesByViewKey={projectNamesByViewKey}
|
||||
projectIconByProjectViewKey={projectIconByProjectViewKey}
|
||||
shortcutIndexByWorkspaceKey={_projectShortcutIndex}
|
||||
showShortcutBadges={showShortcutBadges}
|
||||
onWorkspacePress={onWorkspacePress}
|
||||
@@ -2074,7 +2073,7 @@ function ProjectModeList({
|
||||
onToggleWorkspacePin,
|
||||
}: Omit<
|
||||
SidebarWorkspaceListProps,
|
||||
"statusGroups" | "projectNamesByKey" | "groupMode" | "isRefreshing" | "onRefresh"
|
||||
"statusGroups" | "projectNamesByViewKey" | "groupMode" | "isRefreshing" | "onRefresh"
|
||||
> & {
|
||||
pathname: string;
|
||||
hostLabelByServerId: ReadonlyMap<string, string>;
|
||||
@@ -2128,7 +2127,7 @@ function ProjectModeList({
|
||||
[parentGestureRef],
|
||||
);
|
||||
|
||||
const projectIconByProjectKey = useProjectIconDataByProjectKey({
|
||||
const projectIconByProjectViewKey = useProjectIconDataByProjectViewKey({
|
||||
projects: projectIconTargets,
|
||||
});
|
||||
|
||||
@@ -2180,7 +2179,7 @@ function ProjectModeList({
|
||||
|
||||
const handleProjectDragEnd = useCallback(
|
||||
(reorderedProjects: SidebarProjectEntry[]) => {
|
||||
const reorderedProjectKeys = reorderedProjects.map((project) => project.projectKey);
|
||||
const reorderedProjectKeys = reorderedProjects.map((project) => project.viewKey);
|
||||
const currentProjectOrder = getProjectOrder();
|
||||
if (
|
||||
!hasVisibleOrderChanged({
|
||||
@@ -2202,9 +2201,9 @@ function ProjectModeList({
|
||||
);
|
||||
|
||||
const handleWorkspaceReorder = useCallback(
|
||||
(projectKey: string, reorderedWorkspaces: SidebarWorkspacePlacement[]) => {
|
||||
(projectViewKey: string, reorderedWorkspaces: SidebarWorkspacePlacement[]) => {
|
||||
const reorderedWorkspaceKeys = reorderedWorkspaces.map((workspace) => workspace.workspaceKey);
|
||||
const currentWorkspaceOrder = getWorkspaceOrder(projectKey);
|
||||
const currentWorkspaceOrder = getWorkspaceOrder(projectViewKey);
|
||||
if (
|
||||
!hasVisibleOrderChanged({
|
||||
currentOrder: currentWorkspaceOrder,
|
||||
@@ -2215,7 +2214,7 @@ function ProjectModeList({
|
||||
}
|
||||
|
||||
setWorkspaceOrder(
|
||||
projectKey,
|
||||
projectViewKey,
|
||||
mergeWithRemainder({
|
||||
currentOrder: currentWorkspaceOrder,
|
||||
reorderedVisibleKeys: reorderedWorkspaceKeys,
|
||||
@@ -2262,12 +2261,12 @@ function ProjectModeList({
|
||||
) => {
|
||||
return (
|
||||
<MemoProjectBlock
|
||||
key={item.projectKey}
|
||||
key={item.viewKey}
|
||||
project={item}
|
||||
workspaceEntriesByKey={workspaceEntriesByKey}
|
||||
collapsed={collapsedProjectKeys.has(item.projectKey)}
|
||||
collapsed={collapsedProjectKeys.has(item.viewKey)}
|
||||
displayName={item.projectName}
|
||||
iconDataUri={projectIconByProjectKey.get(item.projectKey) ?? null}
|
||||
iconDataUri={projectIconByProjectViewKey.get(item.viewKey) ?? null}
|
||||
selectionEnabled={selectionEnabled}
|
||||
showShortcutBadges={showShortcutBadges}
|
||||
shortcutIndexByWorkspaceKey={shortcutIndexByWorkspaceKey}
|
||||
@@ -2303,7 +2302,7 @@ function ProjectModeList({
|
||||
onWorkspacePress,
|
||||
onToggleProjectCollapsed,
|
||||
parentGestureRef,
|
||||
projectIconByProjectKey,
|
||||
projectIconByProjectViewKey,
|
||||
selectionEnabled,
|
||||
shortcutIndexByWorkspaceKey,
|
||||
showShortcutBadges,
|
||||
@@ -2392,7 +2391,7 @@ function ProjectModeList({
|
||||
<DraggableList
|
||||
testID="sidebar-project-list"
|
||||
data={unpinnedProjects}
|
||||
keyExtractor={projectKeyExtractor}
|
||||
keyExtractor={projectViewKeyExtractor}
|
||||
renderItem={renderProject}
|
||||
onDragEnd={handleProjectDragEnd}
|
||||
extraData={activeWorkspaceSelectionKey(activeWorkspaceSelection)}
|
||||
|
||||
@@ -18,7 +18,7 @@ interface SidebarModel extends SidebarWorkspacesListResult {
|
||||
statusGroups: StatusGroup[];
|
||||
pinnedGroups: PinnedSidebarGroups;
|
||||
collapsedProjectKeys: ReadonlySet<string>;
|
||||
toggleProjectCollapsed: (projectKey: string) => void;
|
||||
toggleProjectCollapsed: (projectViewKey: string) => void;
|
||||
shortcutModel: SidebarShortcutModel;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ export function SidebarModelProvider({
|
||||
projects: list.projects,
|
||||
pinnedKeys,
|
||||
workspaceEntriesByKey: projectionWorkspaceEntriesByKey,
|
||||
projectNamesByKey: list.projectNamesByKey,
|
||||
projectNamesByViewKey: list.projectNamesByViewKey,
|
||||
groupMode,
|
||||
pinnedCollapsed,
|
||||
collapsedProjectKeys,
|
||||
@@ -69,7 +69,7 @@ export function SidebarModelProvider({
|
||||
collapsedProjectKeys,
|
||||
collapsedStatusGroupKeys,
|
||||
groupMode,
|
||||
list.projectNamesByKey,
|
||||
list.projectNamesByViewKey,
|
||||
list.projects,
|
||||
pinnedCollapsed,
|
||||
pinnedKeys,
|
||||
|
||||
@@ -11,7 +11,7 @@ function makeWorkspace(id: string, statusBucket: SidebarWorkspaceEntry["statusBu
|
||||
workspaceKey: `srv:${id}`,
|
||||
serverId: "srv",
|
||||
workspaceId: id,
|
||||
projectKey: "project",
|
||||
projectViewKey: "project",
|
||||
projectName: "Project",
|
||||
projectKind: "git",
|
||||
workspaceKind: "worktree",
|
||||
@@ -36,7 +36,7 @@ function makeWorkspace(id: string, statusBucket: SidebarWorkspaceEntry["statusBu
|
||||
|
||||
function makeProject(workspaces: SidebarWorkspacePlacement[]): SidebarProjectEntry {
|
||||
return {
|
||||
projectKey: "project",
|
||||
viewKey: "project",
|
||||
projectName: "Project",
|
||||
projectKind: "git",
|
||||
iconWorkingDir: "/repo",
|
||||
@@ -68,7 +68,7 @@ function projectionInput(options?: {
|
||||
[pinned.entry.workspaceKey, pinned.entry],
|
||||
[unpinned.entry.workspaceKey, unpinned.entry],
|
||||
]),
|
||||
projectNamesByKey: new Map([["project", "Project"]]),
|
||||
projectNamesByViewKey: new Map([["project", "Project"]]),
|
||||
groupMode: options?.groupMode ?? ("project" as const),
|
||||
pinnedCollapsed: options?.pinnedCollapsed ?? false,
|
||||
collapsedProjectKeys: new Set<string>(),
|
||||
|
||||
@@ -25,7 +25,7 @@ export function buildSidebarProjection(input: {
|
||||
projects: SidebarProjectEntry[];
|
||||
pinnedKeys: PinnedSidebarKeys;
|
||||
workspaceEntriesByKey: ReadonlyMap<string, SidebarWorkspaceEntry>;
|
||||
projectNamesByKey: Map<string, string>;
|
||||
projectNamesByViewKey: Map<string, string>;
|
||||
groupMode: SidebarGroupMode;
|
||||
pinnedCollapsed: boolean;
|
||||
collapsedProjectKeys: ReadonlySet<string>;
|
||||
@@ -42,7 +42,7 @@ export function buildSidebarProjection(input: {
|
||||
Array.from(input.workspaceEntriesByKey.values()).filter(
|
||||
(workspace) => !pinnedWorkspaceKeys.has(workspace.workspaceKey),
|
||||
),
|
||||
input.projectNamesByKey,
|
||||
input.projectNamesByViewKey,
|
||||
)
|
||||
: [];
|
||||
|
||||
@@ -61,7 +61,7 @@ export function buildSidebarProjection(input: {
|
||||
sections.push(
|
||||
...pinnedGroups.unpinnedProjects.map((project) => ({
|
||||
workspaces: project.workspaces,
|
||||
collapsed: input.collapsedProjectKeys.has(project.projectKey),
|
||||
collapsed: input.collapsedProjectKeys.has(project.viewKey),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,8 +64,8 @@ const ThemedCircleX = withUnistyles(CircleX);
|
||||
interface StatusWorkspaceListProps {
|
||||
groups: StatusGroup[];
|
||||
pinnedWorkspaces: SidebarWorkspaceEntry[];
|
||||
projectNamesByKey: Map<string, string>;
|
||||
projectIconByProjectKey: ReadonlyMap<string, string | null>;
|
||||
projectNamesByViewKey: Map<string, string>;
|
||||
projectIconByProjectViewKey: ReadonlyMap<string, string | null>;
|
||||
shortcutIndexByWorkspaceKey: Map<string, number>;
|
||||
showShortcutBadges: boolean;
|
||||
onWorkspacePress?: () => void;
|
||||
@@ -79,8 +79,8 @@ interface StatusWorkspaceListProps {
|
||||
export function SidebarStatusWorkspaceList({
|
||||
groups,
|
||||
pinnedWorkspaces,
|
||||
projectNamesByKey,
|
||||
projectIconByProjectKey,
|
||||
projectNamesByViewKey,
|
||||
projectIconByProjectViewKey,
|
||||
shortcutIndexByWorkspaceKey,
|
||||
showShortcutBadges,
|
||||
onWorkspacePress,
|
||||
@@ -118,8 +118,8 @@ export function SidebarStatusWorkspaceList({
|
||||
workspace={workspace}
|
||||
{...buildStatusRowProjectPresentation({
|
||||
workspace,
|
||||
projectNamesByKey,
|
||||
projectIconByProjectKey,
|
||||
projectNamesByViewKey,
|
||||
projectIconByProjectViewKey,
|
||||
hostLabelByServerId,
|
||||
showHostLabels,
|
||||
})}
|
||||
@@ -145,8 +145,8 @@ export function SidebarStatusWorkspaceList({
|
||||
<StatusGroupList
|
||||
groups={groups}
|
||||
collapsedStatusGroupKeys={collapsedStatusGroupKeys}
|
||||
projectNamesByKey={projectNamesByKey}
|
||||
projectIconByProjectKey={projectIconByProjectKey}
|
||||
projectNamesByViewKey={projectNamesByViewKey}
|
||||
projectIconByProjectViewKey={projectIconByProjectViewKey}
|
||||
shortcutIndex={statusShortcutIndex}
|
||||
showShortcutBadges={showShortcutBadges}
|
||||
onWorkspacePress={onWorkspacePress}
|
||||
@@ -186,8 +186,8 @@ export function SidebarStatusWorkspaceList({
|
||||
function StatusGroupList({
|
||||
groups,
|
||||
collapsedStatusGroupKeys,
|
||||
projectNamesByKey,
|
||||
projectIconByProjectKey,
|
||||
projectNamesByViewKey,
|
||||
projectIconByProjectViewKey,
|
||||
shortcutIndex,
|
||||
showShortcutBadges,
|
||||
onWorkspacePress,
|
||||
@@ -198,8 +198,8 @@ function StatusGroupList({
|
||||
}: {
|
||||
groups: StatusGroup[];
|
||||
collapsedStatusGroupKeys: ReadonlySet<string>;
|
||||
projectNamesByKey: Map<string, string>;
|
||||
projectIconByProjectKey: ReadonlyMap<string, string | null>;
|
||||
projectNamesByViewKey: Map<string, string>;
|
||||
projectIconByProjectViewKey: ReadonlyMap<string, string | null>;
|
||||
shortcutIndex: Map<string, number>;
|
||||
showShortcutBadges: boolean;
|
||||
onWorkspacePress?: () => void;
|
||||
@@ -215,8 +215,8 @@ function StatusGroupList({
|
||||
key={group.bucket}
|
||||
group={group}
|
||||
collapsed={collapsedStatusGroupKeys.has(group.bucket)}
|
||||
projectNamesByKey={projectNamesByKey}
|
||||
projectIconByProjectKey={projectIconByProjectKey}
|
||||
projectNamesByViewKey={projectNamesByViewKey}
|
||||
projectIconByProjectViewKey={projectIconByProjectViewKey}
|
||||
shortcutIndex={shortcutIndex}
|
||||
showShortcutBadges={showShortcutBadges}
|
||||
onWorkspacePress={onWorkspacePress}
|
||||
@@ -233,8 +233,8 @@ function StatusGroupList({
|
||||
function StatusGroupRows({
|
||||
group,
|
||||
collapsed,
|
||||
projectNamesByKey,
|
||||
projectIconByProjectKey,
|
||||
projectNamesByViewKey,
|
||||
projectIconByProjectViewKey,
|
||||
shortcutIndex,
|
||||
showShortcutBadges,
|
||||
onWorkspacePress,
|
||||
@@ -245,8 +245,8 @@ function StatusGroupRows({
|
||||
}: {
|
||||
group: StatusGroup;
|
||||
collapsed: boolean;
|
||||
projectNamesByKey: Map<string, string>;
|
||||
projectIconByProjectKey: ReadonlyMap<string, string | null>;
|
||||
projectNamesByViewKey: Map<string, string>;
|
||||
projectIconByProjectViewKey: ReadonlyMap<string, string | null>;
|
||||
shortcutIndex: Map<string, number>;
|
||||
showShortcutBadges: boolean;
|
||||
onWorkspacePress?: () => void;
|
||||
@@ -276,8 +276,8 @@ function StatusGroupRows({
|
||||
workspace={workspace}
|
||||
{...buildStatusRowProjectPresentation({
|
||||
workspace,
|
||||
projectNamesByKey,
|
||||
projectIconByProjectKey,
|
||||
projectNamesByViewKey,
|
||||
projectIconByProjectViewKey,
|
||||
hostLabelByServerId,
|
||||
showHostLabels,
|
||||
})}
|
||||
@@ -324,25 +324,25 @@ interface StatusRowProjectPresentation {
|
||||
|
||||
function buildStatusRowProjectPresentation({
|
||||
workspace,
|
||||
projectNamesByKey,
|
||||
projectIconByProjectKey,
|
||||
projectNamesByViewKey,
|
||||
projectIconByProjectViewKey,
|
||||
hostLabelByServerId,
|
||||
showHostLabels,
|
||||
}: {
|
||||
workspace: SidebarWorkspaceEntry;
|
||||
projectNamesByKey: Map<string, string>;
|
||||
projectIconByProjectKey: ReadonlyMap<string, string | null>;
|
||||
projectNamesByViewKey: Map<string, string>;
|
||||
projectIconByProjectViewKey: ReadonlyMap<string, string | null>;
|
||||
hostLabelByServerId: ReadonlyMap<string, string>;
|
||||
showHostLabels: boolean;
|
||||
}): StatusRowProjectPresentation {
|
||||
const projectName = projectNamesByKey.get(workspace.projectKey) ?? "";
|
||||
const projectName = projectNamesByViewKey.get(workspace.projectViewKey) ?? "";
|
||||
const hostLabel = showHostLabels
|
||||
? (hostLabelByServerId.get(workspace.serverId) ?? workspace.serverId)
|
||||
: null;
|
||||
return {
|
||||
subtitle: buildStatusRowSubtitle({ projectName, hostLabel }),
|
||||
projectName,
|
||||
projectIconDataUri: projectIconByProjectKey.get(workspace.projectKey) ?? null,
|
||||
projectIconDataUri: projectIconByProjectViewKey.get(workspace.projectViewKey) ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@ import { projectIconPlaceholderLabelFromDisplayName } from "@/utils/project-disp
|
||||
* the meaning, so the icon itself stays out of the accessibility tree.
|
||||
*/
|
||||
export function SidebarSubtitleProjectIcon({
|
||||
projectKey,
|
||||
projectViewKey,
|
||||
projectName,
|
||||
iconDataUri,
|
||||
testID,
|
||||
}: {
|
||||
projectKey: string;
|
||||
projectViewKey: string;
|
||||
projectName: string;
|
||||
iconDataUri: string | null;
|
||||
testID?: string;
|
||||
@@ -37,7 +37,7 @@ export function SidebarSubtitleProjectIcon({
|
||||
<ProjectIconView
|
||||
iconDataUri={iconDataUri}
|
||||
initial={placeholderInitial}
|
||||
projectKey={projectKey}
|
||||
projectViewKey={projectViewKey}
|
||||
imageStyle={styles.icon}
|
||||
fallbackStyle={styles.fallback}
|
||||
textStyle={styles.fallbackText}
|
||||
|
||||
@@ -157,7 +157,7 @@ export const SidebarWorkspaceRowContent = memo(function SidebarWorkspaceRowConte
|
||||
<View style={styles.workspaceSubtitleRow}>
|
||||
{subtitleProjectName ? (
|
||||
<SidebarSubtitleProjectIcon
|
||||
projectKey={workspace.projectKey}
|
||||
projectViewKey={workspace.projectViewKey}
|
||||
projectName={subtitleProjectName}
|
||||
iconDataUri={subtitleProjectIconDataUri}
|
||||
testID={`sidebar-row-project-icon-${workspace.workspaceKey}`}
|
||||
|
||||
Reference in New Issue
Block a user