feat(session-sidebar): add project rename action (#326)

This commit is contained in:
shekohex
2026-02-06 02:07:50 +02:00
committed by GitHub
parent 234a91b444
commit c63651d934
@@ -149,6 +149,12 @@ interface SortableProjectItemProps {
onNewSessionFromGitHubIssue?: () => void; onNewSessionFromGitHubIssue?: () => void;
onNewSessionFromGitHubPR?: () => void; onNewSessionFromGitHubPR?: () => void;
onOpenMultiRunLauncher: () => void; onOpenMultiRunLauncher: () => void;
onRenameStart: () => void;
onRenameSave: () => void;
onRenameCancel: () => void;
onRenameValueChange: (value: string) => void;
renameValue: string;
isRenaming: boolean;
onClose: () => void; onClose: () => void;
sentinelRef: (el: HTMLDivElement | null) => void; sentinelRef: (el: HTMLDivElement | null) => void;
children?: React.ReactNode; children?: React.ReactNode;
@@ -175,6 +181,12 @@ const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
onNewSessionFromGitHubIssue, onNewSessionFromGitHubIssue,
onNewSessionFromGitHubPR, onNewSessionFromGitHubPR,
onOpenMultiRunLauncher, onOpenMultiRunLauncher,
onRenameStart,
onRenameSave,
onRenameCancel,
onRenameValueChange,
renameValue,
isRenaming,
onClose, onClose,
sentinelRef, sentinelRef,
children, children,
@@ -221,11 +233,48 @@ const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
onMouseLeave={() => onHoverChange(false)} onMouseLeave={() => onHoverChange(false)}
onContextMenu={(event) => { onContextMenu={(event) => {
event.preventDefault(); event.preventDefault();
if (!isRenaming) {
setIsMenuOpen(true); setIsMenuOpen(true);
}
}} }}
> >
<div className="relative flex items-center gap-1 px-1" {...attributes}> <div className="relative flex items-center gap-1 px-1" {...attributes}>
{/* Project name with tooltip for path - draggable */} {isRenaming ? (
<form
className="flex min-w-0 flex-1 items-center gap-2"
data-keyboard-avoid="true"
onSubmit={(event) => {
event.preventDefault();
onRenameSave();
}}
>
<input
value={renameValue}
onChange={(event) => onRenameValueChange(event.target.value)}
className="flex-1 min-w-0 bg-transparent typography-ui-label outline-none placeholder:text-muted-foreground"
autoFocus
placeholder="Rename project"
onKeyDown={(event) => {
if (event.key === 'Escape') {
onRenameCancel();
}
}}
/>
<button
type="submit"
className="shrink-0 text-muted-foreground hover:text-foreground"
>
<RiCheckLine className="size-4" />
</button>
<button
type="button"
onClick={onRenameCancel}
className="shrink-0 text-muted-foreground hover:text-foreground"
>
<RiCloseLine className="size-4" />
</button>
</form>
) : (
<Tooltip delayDuration={1500}> <Tooltip delayDuration={1500}>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
@@ -246,8 +295,9 @@ const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
{projectDescription} {projectDescription}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
)}
{/* Project menu */} {!isRenaming ? (
<DropdownMenu <DropdownMenu
open={isMenuOpen} open={isMenuOpen}
onOpenChange={setIsMenuOpen} onOpenChange={setIsMenuOpen}
@@ -302,6 +352,10 @@ const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
Manage Branches Manage Branches
</DropdownMenuItem> </DropdownMenuItem>
)} )}
<DropdownMenuItem onClick={onRenameStart}>
<RiPencilAiLine className="mr-1.5 h-4 w-4" />
Rename
</DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
onClick={onClose} onClick={onClose}
className="text-destructive focus:text-destructive" className="text-destructive focus:text-destructive"
@@ -311,8 +365,9 @@ const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
) : null}
{isRepo && !hideDirectoryControls && onNewWorktreeSession && settingsAutoCreateWorktree && ( {isRepo && !hideDirectoryControls && onNewWorktreeSession && settingsAutoCreateWorktree && !isRenaming && (
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
@@ -335,7 +390,7 @@ const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
)} )}
{(!settingsAutoCreateWorktree || !isRepo) && ( {(!settingsAutoCreateWorktree || !isRepo) && !isRenaming && (
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
@@ -408,6 +463,8 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
}) => { }) => {
const [editingId, setEditingId] = React.useState<string | null>(null); const [editingId, setEditingId] = React.useState<string | null>(null);
const [editTitle, setEditTitle] = React.useState(''); const [editTitle, setEditTitle] = React.useState('');
const [editingProjectId, setEditingProjectId] = React.useState<string | null>(null);
const [editProjectTitle, setEditProjectTitle] = React.useState('');
const [copiedSessionId, setCopiedSessionId] = React.useState<string | null>(null); const [copiedSessionId, setCopiedSessionId] = React.useState<string | null>(null);
const copyTimeout = React.useRef<number | null>(null); const copyTimeout = React.useRef<number | null>(null);
const [expandedParents, setExpandedParents] = React.useState<Set<string>>(new Set()); const [expandedParents, setExpandedParents] = React.useState<Set<string>>(new Set());
@@ -443,6 +500,7 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
const removeProject = useProjectsStore((state) => state.removeProject); const removeProject = useProjectsStore((state) => state.removeProject);
const setActiveProject = useProjectsStore((state) => state.setActiveProject); const setActiveProject = useProjectsStore((state) => state.setActiveProject);
const setActiveProjectIdOnly = useProjectsStore((state) => state.setActiveProjectIdOnly); const setActiveProjectIdOnly = useProjectsStore((state) => state.setActiveProjectIdOnly);
const renameProject = useProjectsStore((state) => state.renameProject);
const reorderProjects = useProjectsStore((state) => state.reorderProjects); const reorderProjects = useProjectsStore((state) => state.reorderProjects);
const setActiveMainTab = useUIStore((state) => state.setActiveMainTab); const setActiveMainTab = useUIStore((state) => state.setActiveMainTab);
@@ -768,6 +826,19 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
setEditTitle(''); setEditTitle('');
}, []); }, []);
const handleSaveProjectEdit = React.useCallback(() => {
if (editingProjectId && editProjectTitle.trim()) {
renameProject(editingProjectId, editProjectTitle.trim());
setEditingProjectId(null);
setEditProjectTitle('');
}
}, [editingProjectId, editProjectTitle, renameProject]);
const handleCancelProjectEdit = React.useCallback(() => {
setEditingProjectId(null);
setEditProjectTitle('');
}, []);
const handleShareSession = React.useCallback( const handleShareSession = React.useCallback(
async (session: Session) => { async (session: Session) => {
const result = await shareSession(session.id); const result = await shareSession(session.id);
@@ -1751,6 +1822,15 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
} }
openMultiRunLauncher(); openMultiRunLauncher();
}} }}
onRenameStart={() => {
setEditingProjectId(projectKey);
setEditProjectTitle(project.label?.trim() || formatDirectoryName(project.normalizedPath, homeDirectory) || project.normalizedPath);
}}
onRenameSave={handleSaveProjectEdit}
onRenameCancel={handleCancelProjectEdit}
onRenameValueChange={setEditProjectTitle}
renameValue={editingProjectId === projectKey ? editProjectTitle : ''}
isRenaming={editingProjectId === projectKey}
onClose={() => removeProject(projectKey)} onClose={() => removeProject(projectKey)}
sentinelRef={(el) => { projectHeaderSentinelRefs.current.set(projectKey, el); }} sentinelRef={(el) => { projectHeaderSentinelRefs.current.set(projectKey, el); }}
settingsAutoCreateWorktree={settingsAutoCreateWorktree} settingsAutoCreateWorktree={settingsAutoCreateWorktree}