History

Open folder picker immediately on desktop Electron

- add `shouldOpenProjectFolderPickerImmediately` to centralize Sidebar behavior - trigger folder browse directly when adding a project in desktop Electron - keep manual path entry for mobile layouts and non-Electron, with tests for all cases

Julius Marminge committed 8 months ago

Commit 75ca0d41 parent 8282b113 files · +64 −8Browse files

Unified · Split

3 files
  1. apps/web/src/components/Sidebar.logic.test.ts +31 −0
  2. apps/web/src/components/Sidebar.logic.ts +6 −0
  3. apps/web/src/components/Sidebar.tsx +27 −8
apps/web/src/components/Sidebar.logic.test.ts+31−0
@@ -0,0 +1,31 @@
1+import { describe, expect, it } from "vitest";
2+import { shouldOpenProjectFolderPickerImmediately } from "./Sidebar.logic";
3+
4+describe("shouldOpenProjectFolderPickerImmediately", () => {
5+ it("opens the folder picker immediately in Electron on desktop", () => {
6+ expect(
7+ shouldOpenProjectFolderPickerImmediately({
8+ isElectron: true,
9+ isMobile: false,
10+ }),
11+ ).toBe(true);
12+ });
13+
14+ it("keeps manual project entry on mobile Electron layouts", () => {
15+ expect(
16+ shouldOpenProjectFolderPickerImmediately({
17+ isElectron: true,
18+ isMobile: true,
19+ }),
20+ ).toBe(false);
21+ });
22+
23+ it("keeps manual project entry outside Electron", () => {
24+ expect(
25+ shouldOpenProjectFolderPickerImmediately({
26+ isElectron: false,
27+ isMobile: false,
28+ }),
29+ ).toBe(false);
30+ });
31+});
apps/web/src/components/Sidebar.logic.ts+6−0
@@ -0,0 +1,6 @@
1+export function shouldOpenProjectFolderPickerImmediately(input: {
2+ isElectron: boolean;
3+ isMobile: boolean;
4+}): boolean {
5+ return input.isElectron && !input.isMobile;
6+}
apps/web/src/components/Sidebar.tsx+27−8
@@ -32,6 +32,7 @@ import { type Thread } from "../types"; · 31 unmodified lines
3232 import { derivePendingApprovals } from "../session-logic";
3333 import { gitRemoveWorktreeMutationOptions, gitStatusQueryOptions } from "../lib/gitReactQuery";
3434 import { serverConfigQueryOptions } from "../lib/serverReactQuery";
35+import { useMediaQuery } from "../hooks/useMediaQuery";
3536 import { readNativeApi } from "../nativeApi";
3637 import { type DraftThreadEnvMode, useComposerDraftStore } from "../composerDraftStore";
3738 import { selectThreadTerminalState, useTerminalStateStore } from "../terminalStateStore";
@@ -49,6 +50,7 @@ import { · 11 unmodified lines
4950 } from "./desktopUpdate.logic";
5051 import { Alert, AlertAction, AlertDescription, AlertTitle } from "./ui/alert";
5152 import { Button } from "./ui/button";
53+import { shouldOpenProjectFolderPickerImmediately } from "./Sidebar.logic";
5254 import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./ui/collapsible";
5355 import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";
5456 import {
@@ -266,6 +268,7 @@ function ProjectFavicon({ cwd }: { cwd: string }) { · 211 unmodified lines
266268 }
267269
268270 export default function Sidebar() {
271+ const isMobile = useMediaQuery("(max-width: 767px)");
269272 const projects = useStore((store) => store.projects);
270273 const threads = useStore((store) => store.threads);
271274 const markThreadUnread = useStore((store) => store.markThreadUnread);
@@ -312,6 +315,11 @@ export default function Sidebar() { · 40 unmodified lines
312315 const renamingCommittedRef = useRef(false);
313316 const renamingInputRef = useRef<HTMLInputElement | null>(null);
314317 const [desktopUpdateState, setDesktopUpdateState] = useState<DesktopUpdateState | null>(null);
318+ const shouldBrowseForProjectImmediately = shouldOpenProjectFolderPickerImmediately({
319+ isElectron,
320+ isMobile,
321+ });
322+ const shouldShowProjectPathEntry = addingProject && !shouldBrowseForProjectImmediately;
315323 const pendingApprovalByThreadId = useMemo(() => {
316324 const map = new Map<ThreadId, boolean>();
317325 for (const thread of threads) {
@@ -550,12 +558,21 @@ export default function Sidebar() { · 232 unmodified lines
550558 }
551559 if (pickedPath) {
552560 await addProjectFromPath(pickedPath);
553− } else {
561+ } else if (!shouldBrowseForProjectImmediately) {
554562 addProjectInputRef.current?.focus();
555563 }
556564 setIsPickingFolder(false);
557565 };
558566
567+ const handleStartAddProject = () => {
568+ setAddProjectError(null);
569+ if (shouldBrowseForProjectImmediately) {
570+ void handlePickFolder();
571+ return;
572+ }
573+ setAddingProject((prev) => !prev);
574+ };
575+
559576 const cancelRename = useCallback(() => {
560577 setRenamingThreadId(null);
561578 renamingInputRef.current = null;
@@ -1075,21 +1092,23 @@ export default function Sidebar() { · 513 unmodified lines
10751092 <button
10761093 type="button"
10771094 aria-label="Add project"
1095+ aria-pressed={shouldShowProjectPathEntry}
10781096 className="inline-flex size-5 items-center justify-center rounded-md text-muted-foreground/60 transition-colors hover:bg-accent hover:text-foreground"
1079− onClick={() => {
1080− setAddingProject((prev) => !prev);
1081− setAddProjectError(null);
1082− }}
1097+ onClick={handleStartAddProject}
10831098 />
10841099 }
10851100 >
1086− <PlusIcon className="size-3.5" />
1101+ <PlusIcon
1102+ className={`size-3.5 transition-transform duration-150 ${
1103+ shouldShowProjectPathEntry ? "rotate-45" : "rotate-0"
1104+ }`}
1105+ />
10871106 </TooltipTrigger>
10881107 <TooltipPopup side="right">Add project</TooltipPopup>
10891108 </Tooltip>
10901109 </div>
10911110
1092− {addingProject && (
1111+ {shouldShowProjectPathEntry && (
10931112 <div className="mb-2 px-1">
10941113 {isElectron && (
10951114 <button
@@ -1415,7 +1434,7 @@ export default function Sidebar() { · 319 unmodified lines
14151434 })}
14161435 </SidebarMenu>
14171436
1418− {projects.length === 0 && !addingProject && (
1437+ {projects.length === 0 && !shouldShowProjectPathEntry && (
14191438 <div className="px-2 pt-4 text-center text-xs text-muted-foreground/60">
14201439 No projects yet
14211440 </div>