fix: rewrite titlebar drag to match VS Code's static pattern
Replace the stateful TitlebarDragRegion (hooks, ResizeObserver, IPC, fullscreen tracking) with a pure static component — matching VS Code's titlebar-drag-region exactly: position absolute, full size, no z-index, no pointer-events, no state, no event listeners. Remove TitlebarNoDragContent entirely — VS Code doesn't wrap content in no-drag; interactive elements get no-drag from the global CSS backstop in index.html. Add drag regions to all header surfaces: - ScreenHeader (sessions, workspace header) - Left sidebar (traffic light area + header) - Split container pane tabs - Explorer sidebar header (Changes/Files tabs) Fix workspace header empty space not draggable by changing headerTitleContainer from flex: 1 to flexShrink: 1.
This commit is contained in:
@@ -1,228 +1,57 @@
|
|||||||
import type { ReactNode } from "react";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import { getIsElectronRuntime } from "@/constants/layout";
|
import { getIsElectronRuntime } from "@/constants/layout";
|
||||||
import { getDesktopWindow, updateDesktopWindowControls } from "@/desktop/electron/window";
|
|
||||||
|
|
||||||
const DEFAULT_OVERLAY_HEIGHT = 29;
|
|
||||||
const MAX_EFFECTIVE_TITLEBAR_HEIGHT = 48;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VS Code-style titlebar drag region for Electron on Windows/Linux.
|
* VS Code-style titlebar drag region for Electron.
|
||||||
*
|
*
|
||||||
* Architecture copied from VS Code at commit daa0a70:
|
* Copied from VS Code at commit daa0a70:
|
||||||
* - titlebarPart.ts:463-464 → dedicated absolute drag overlay element
|
* - titlebarPart.ts:463-464 → prepend(container, $('div.titlebar-drag-region'))
|
||||||
* - titlebarpart.css:57-64 → drag region styles
|
* - titlebarpart.css:57-64 → position: absolute, full size, -webkit-app-region: drag
|
||||||
* - titlebarpart.css:249-260 → top-edge resizer with no-drag, hidden in fullscreen
|
* - titlebarpart.css:249-260 → top-edge resizer, no-drag, 4px
|
||||||
* - electron-browser/titlebarPart.ts:205-206 → resizer appended to root container
|
|
||||||
*
|
*
|
||||||
* This component renders two elements:
|
* VS Code's drag region is a static DOM element — no z-index, no pointer-events,
|
||||||
* 1. A full-size absolute drag overlay with -webkit-app-region: drag
|
* no state, no event listeners. Interactive elements get no-drag from their own
|
||||||
* 2. A 4px top-edge resizer with -webkit-app-region: no-drag (hidden in fullscreen)
|
* CSS (global backstop in index.html). The drag region never re-renders.
|
||||||
*
|
*
|
||||||
* Both are positioned absolute within the parent container. Interactive children
|
* The resizer is Windows/Linux only (titlebarpart.css:249 scopes to .windows/.linux).
|
||||||
* of the parent sit above the drag overlay via normal stacking order.
|
* On macOS, Electron handles edge resize natively.
|
||||||
* The global no-drag reset in index.html remains only as a defensive backstop.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function useIsFullscreen(): boolean {
|
|
||||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (Platform.OS !== "web" || !getIsElectronRuntime()) return;
|
|
||||||
|
|
||||||
let disposed = false;
|
|
||||||
let cleanup: (() => void) | undefined;
|
|
||||||
|
|
||||||
async function setup() {
|
|
||||||
const win = getDesktopWindow();
|
|
||||||
if (!win) return;
|
|
||||||
|
|
||||||
const fs = typeof win.isFullscreen === "function" ? await win.isFullscreen() : false;
|
|
||||||
if (disposed) return;
|
|
||||||
setIsFullscreen(fs);
|
|
||||||
|
|
||||||
if (typeof win.onResized !== "function") return;
|
|
||||||
|
|
||||||
const unlisten = await win.onResized(async () => {
|
|
||||||
if (disposed) return;
|
|
||||||
const fullscreen = typeof win.isFullscreen === "function" ? await win.isFullscreen() : false;
|
|
||||||
if (disposed) return;
|
|
||||||
setIsFullscreen(fullscreen);
|
|
||||||
});
|
|
||||||
|
|
||||||
cleanup = unlisten;
|
|
||||||
if (disposed) cleanup?.();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup();
|
|
||||||
return () => {
|
|
||||||
disposed = true;
|
|
||||||
cleanup?.();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return isFullscreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the VS Code-style drag overlay and top-edge resizer.
|
* Static drag overlay and top-edge resizer. Returns null on non-Electron.
|
||||||
* Only renders on Electron Windows/Linux (non-mac). Returns null otherwise.
|
* Place as FIRST child of any positioned container that should be draggable.
|
||||||
*
|
|
||||||
* Usage: Place as a child of any positioned container that should be draggable.
|
|
||||||
* Interactive elements in the same container will sit above the drag overlay
|
|
||||||
* via normal DOM stacking order (later siblings paint over earlier ones).
|
|
||||||
* Place TitlebarDragRegion BEFORE interactive children in JSX.
|
|
||||||
*/
|
*/
|
||||||
export function TitlebarDragRegion() {
|
export function TitlebarDragRegion() {
|
||||||
if (Platform.OS !== "web" || !getIsElectronRuntime()) {
|
if (Platform.OS !== "web" || !getIsElectronRuntime()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <TitlebarDragRegionInner />;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Explicit no-drag content layer above the drag overlay.
|
|
||||||
* Matches VS Code's pattern of z-index: 2500 + -webkit-app-region: no-drag
|
|
||||||
* on interactive containers within the titlebar (titlebarpart.css:141-143,
|
|
||||||
* 231-233, 264-268, 288-298, 382-401).
|
|
||||||
*
|
|
||||||
* Wrap interactive children of any TitlebarDragRegion parent in this component
|
|
||||||
* so the no-drag layering is structural, not dependent on the global CSS backstop.
|
|
||||||
* On non-Electron or mac, renders children directly with no wrapper.
|
|
||||||
*/
|
|
||||||
interface TitlebarNoDragContentProps {
|
|
||||||
children: ReactNode;
|
|
||||||
/** Layout direction for the no-drag wrapper. Default: "row" for header-style surfaces. */
|
|
||||||
direction?: "row" | "column";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TitlebarNoDragContent({ children, direction = "row" }: TitlebarNoDragContentProps) {
|
|
||||||
if (Platform.OS !== "web" || !getIsElectronRuntime()) {
|
|
||||||
return <>{children}</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "relative",
|
|
||||||
zIndex: 2500,
|
|
||||||
// @ts-expect-error — WebkitAppRegion is not in CSSProperties
|
|
||||||
WebkitAppRegion: "no-drag",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: direction,
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: direction === "column" ? "center" : undefined,
|
|
||||||
flex: 1,
|
|
||||||
height: "100%",
|
|
||||||
minWidth: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function TitlebarDragRegionInner() {
|
|
||||||
const isFullscreen = useIsFullscreen();
|
|
||||||
const dragRegionRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
const lastReportedHeightRef = useRef<number | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const dragRegion = dragRegionRef.current;
|
|
||||||
const parent = dragRegion?.parentElement;
|
|
||||||
if (!dragRegion || !parent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let disposed = false;
|
|
||||||
let frameId: number | null = null;
|
|
||||||
|
|
||||||
const reportHeight = () => {
|
|
||||||
if (disposed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextHeight = Math.min(
|
|
||||||
MAX_EFFECTIVE_TITLEBAR_HEIGHT,
|
|
||||||
Math.max(DEFAULT_OVERLAY_HEIGHT, Math.round(parent.getBoundingClientRect().height)),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (lastReportedHeightRef.current === nextHeight) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastReportedHeightRef.current = nextHeight;
|
|
||||||
void updateDesktopWindowControls({ height: nextHeight }).catch((error) => {
|
|
||||||
console.warn("[DesktopWindow] Failed to update window controls height", error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const scheduleReport = () => {
|
|
||||||
if (frameId !== null) {
|
|
||||||
cancelAnimationFrame(frameId);
|
|
||||||
}
|
|
||||||
frameId = requestAnimationFrame(() => {
|
|
||||||
frameId = null;
|
|
||||||
reportHeight();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
scheduleReport();
|
|
||||||
|
|
||||||
const resizeObserver =
|
|
||||||
typeof ResizeObserver === "function"
|
|
||||||
? new ResizeObserver(() => {
|
|
||||||
scheduleReport();
|
|
||||||
})
|
|
||||||
: null;
|
|
||||||
|
|
||||||
resizeObserver?.observe(parent);
|
|
||||||
window.addEventListener("resize", scheduleReport);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
disposed = true;
|
|
||||||
if (frameId !== null) {
|
|
||||||
cancelAnimationFrame(frameId);
|
|
||||||
}
|
|
||||||
resizeObserver?.disconnect();
|
|
||||||
window.removeEventListener("resize", scheduleReport);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Drag overlay — equivalent to VS Code .titlebar-drag-region */}
|
{/* Drag overlay — VS Code .titlebar-drag-region (titlebarpart.css:57-64) */}
|
||||||
<div
|
<div
|
||||||
ref={dragRegionRef}
|
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
|
display: "block",
|
||||||
|
position: "absolute",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
// @ts-expect-error — WebkitAppRegion is not in CSSProperties
|
// @ts-expect-error — WebkitAppRegion is not in CSSProperties
|
||||||
WebkitAppRegion: "drag",
|
WebkitAppRegion: "drag",
|
||||||
zIndex: 0,
|
|
||||||
pointerEvents: "auto",
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* Top-edge resizer — equivalent to VS Code .resizer */}
|
{/* Top-edge resizer — VS Code .resizer (titlebarpart.css:249-256) */}
|
||||||
{!isFullscreen && (
|
<div
|
||||||
<div
|
style={{
|
||||||
style={{
|
position: "absolute",
|
||||||
position: "absolute",
|
top: 0,
|
||||||
top: 0,
|
width: "100%",
|
||||||
left: 0,
|
height: 4,
|
||||||
width: "100%",
|
// @ts-expect-error — WebkitAppRegion is not in CSSProperties
|
||||||
height: 4,
|
WebkitAppRegion: "no-drag",
|
||||||
// @ts-expect-error — WebkitAppRegion is not in CSSProperties
|
}}
|
||||||
WebkitAppRegion: "no-drag",
|
/>
|
||||||
zIndex: 1,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { GitDiffPane } from "./git-diff-pane";
|
|||||||
import { FileExplorerPane } from "./file-explorer-pane";
|
import { FileExplorerPane } from "./file-explorer-pane";
|
||||||
import { useKeyboardShiftStyle } from "@/hooks/use-keyboard-shift-style";
|
import { useKeyboardShiftStyle } from "@/hooks/use-keyboard-shift-style";
|
||||||
import { useWindowControlsPadding } from "@/utils/desktop-window";
|
import { useWindowControlsPadding } from "@/utils/desktop-window";
|
||||||
|
import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region";
|
||||||
|
|
||||||
const MIN_CHAT_WIDTH = 400;
|
const MIN_CHAT_WIDTH = 400;
|
||||||
function logExplorerSidebar(_event: string, _details: Record<string, unknown>): void {}
|
function logExplorerSidebar(_event: string, _details: Record<string, unknown>): void {}
|
||||||
@@ -344,6 +345,7 @@ function SidebarContent({
|
|||||||
<View style={styles.sidebarContent} pointerEvents="auto">
|
<View style={styles.sidebarContent} pointerEvents="auto">
|
||||||
{/* Header with tabs and close button */}
|
{/* Header with tabs and close button */}
|
||||||
<View style={[styles.header, { paddingRight: padding.right }]} testID="explorer-header">
|
<View style={[styles.header, { paddingRight: padding.right }]} testID="explorer-header">
|
||||||
|
<TitlebarDragRegion />
|
||||||
<View style={styles.tabsContainer}>
|
<View style={styles.tabsContainer}>
|
||||||
{isGit && (
|
{isGit && (
|
||||||
<Pressable
|
<Pressable
|
||||||
@@ -434,6 +436,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
|
position: "relative",
|
||||||
height: HEADER_INNER_HEIGHT,
|
height: HEADER_INNER_HEIGHT,
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
isCompactFormFactor,
|
isCompactFormFactor,
|
||||||
} from "@/constants/layout";
|
} from "@/constants/layout";
|
||||||
import { useWindowControlsPadding } from "@/utils/desktop-window";
|
import { useWindowControlsPadding } from "@/utils/desktop-window";
|
||||||
import { TitlebarDragRegion, TitlebarNoDragContent } from "@/components/desktop/titlebar-drag-region";
|
import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region";
|
||||||
|
|
||||||
interface ScreenHeaderProps {
|
interface ScreenHeaderProps {
|
||||||
left?: ReactNode;
|
left?: ReactNode;
|
||||||
@@ -46,10 +46,8 @@ export function ScreenHeader({ left, right, leftStyle, rightStyle, borderless }:
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<TitlebarDragRegion />
|
<TitlebarDragRegion />
|
||||||
<TitlebarNoDragContent>
|
<View style={[styles.left, leftStyle]}>{left}</View>
|
||||||
<View style={[styles.left, leftStyle]}>{left}</View>
|
<View style={[styles.right, rightStyle]}>{right}</View>
|
||||||
<View style={[styles.right, rightStyle]}>{right}</View>
|
|
||||||
</TitlebarNoDragContent>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import {
|
|||||||
} from "@/hooks/use-sidebar-workspaces-list";
|
} from "@/hooks/use-sidebar-workspaces-list";
|
||||||
import { useSidebarAnimation } from "@/contexts/sidebar-animation-context";
|
import { useSidebarAnimation } from "@/contexts/sidebar-animation-context";
|
||||||
import { useWindowControlsPadding } from "@/utils/desktop-window";
|
import { useWindowControlsPadding } from "@/utils/desktop-window";
|
||||||
import { TitlebarDragRegion, TitlebarNoDragContent } from "@/components/desktop/titlebar-drag-region";
|
import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region";
|
||||||
import { Combobox } from "@/components/ui/combobox";
|
import { Combobox } from "@/components/ui/combobox";
|
||||||
import { getHostRuntimeStore, useHosts } from "@/runtime/host-runtime";
|
import { getHostRuntimeStore, useHosts } from "@/runtime/host-runtime";
|
||||||
import { formatConnectionStatus } from "@/utils/daemons";
|
import { formatConnectionStatus } from "@/utils/daemons";
|
||||||
@@ -689,14 +689,14 @@ function DesktopSidebar({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View style={[styles.desktopSidebar, resizeAnimatedStyle, { paddingTop: insetsTop }]}>
|
<Animated.View style={[styles.desktopSidebar, resizeAnimatedStyle, { paddingTop: insetsTop }]}>
|
||||||
{padding.top > 0 ? <View style={{ height: padding.top }} /> : null}
|
<View style={styles.sidebarDragArea}>
|
||||||
<View style={styles.sidebarHeader}>
|
|
||||||
<TitlebarDragRegion />
|
<TitlebarDragRegion />
|
||||||
<TitlebarNoDragContent>
|
{padding.top > 0 ? <View style={{ height: padding.top }} /> : null}
|
||||||
|
<View style={styles.sidebarHeader}>
|
||||||
<View style={styles.sidebarHeaderRow}>
|
<View style={styles.sidebarHeaderRow}>
|
||||||
<SessionsButton onPress={handleViewMore} />
|
<SessionsButton onPress={handleViewMore} />
|
||||||
</View>
|
</View>
|
||||||
</TitlebarNoDragContent>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{isInitialLoad ? (
|
{isInitialLoad ? (
|
||||||
@@ -834,8 +834,10 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
width: 10,
|
width: 10,
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
},
|
},
|
||||||
sidebarHeader: {
|
sidebarDragArea: {
|
||||||
position: "relative",
|
position: "relative",
|
||||||
|
},
|
||||||
|
sidebarHeader: {
|
||||||
height: {
|
height: {
|
||||||
xs: HEADER_INNER_HEIGHT_MOBILE,
|
xs: HEADER_INNER_HEIGHT_MOBILE,
|
||||||
md: HEADER_INNER_HEIGHT,
|
md: HEADER_INNER_HEIGHT,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import { ResizeHandle } from "@/components/resize-handle";
|
|||||||
import { shouldFocusPaneFromEventTarget } from "@/components/split-container-pane-focus";
|
import { shouldFocusPaneFromEventTarget } from "@/components/split-container-pane-focus";
|
||||||
import { usePanelStore } from "@/stores/panel-store";
|
import { usePanelStore } from "@/stores/panel-store";
|
||||||
import { useWindowControlsPadding } from "@/utils/desktop-window";
|
import { useWindowControlsPadding } from "@/utils/desktop-window";
|
||||||
|
import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region";
|
||||||
import {
|
import {
|
||||||
computeTabDropPreview,
|
computeTabDropPreview,
|
||||||
type TabDropPreview,
|
type TabDropPreview,
|
||||||
@@ -887,6 +888,7 @@ function SplitPaneView({
|
|||||||
{ paddingLeft: padding.left, paddingRight: padding.right },
|
{ paddingLeft: padding.left, paddingRight: padding.right },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
<TitlebarDragRegion />
|
||||||
<WorkspaceDesktopTabsRow
|
<WorkspaceDesktopTabsRow
|
||||||
paneId={pane.id}
|
paneId={pane.id}
|
||||||
isFocused={isFocused}
|
isFocused={isFocused}
|
||||||
@@ -997,6 +999,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
},
|
},
|
||||||
paneTabs: {
|
paneTabs: {
|
||||||
|
position: "relative",
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
},
|
},
|
||||||
paneContent: {
|
paneContent: {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ import type {
|
|||||||
} from "@server/server/agent/agent-sdk-types";
|
} from "@server/server/agent/agent-sdk-types";
|
||||||
import { AGENT_PROVIDER_DEFINITIONS } from "@server/server/agent/provider-manifest";
|
import { AGENT_PROVIDER_DEFINITIONS } from "@server/server/agent/provider-manifest";
|
||||||
import { prepareWorkspaceTab } from "@/utils/workspace-navigation";
|
import { prepareWorkspaceTab } from "@/utils/workspace-navigation";
|
||||||
import { TitlebarDragRegion, TitlebarNoDragContent } from "@/components/desktop/titlebar-drag-region";
|
import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region";
|
||||||
import { useKeyboardShiftStyle } from "@/hooks/use-keyboard-shift-style";
|
import { useKeyboardShiftStyle } from "@/hooks/use-keyboard-shift-style";
|
||||||
import { normalizeAgentSnapshot } from "@/utils/agent-snapshots";
|
import { normalizeAgentSnapshot } from "@/utils/agent-snapshots";
|
||||||
import { useAgentInputDraft } from "@/hooks/use-agent-input-draft";
|
import { useAgentInputDraft } from "@/hooks/use-agent-input-draft";
|
||||||
@@ -1000,8 +1000,7 @@ function DraftAgentScreenContent({
|
|||||||
const mainContent = (
|
const mainContent = (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<TitlebarDragRegion />
|
<TitlebarDragRegion />
|
||||||
<TitlebarNoDragContent direction="column">
|
<View style={styles.outerContainer}>
|
||||||
<View style={styles.outerContainer}>
|
|
||||||
<View style={styles.agentPanel}>
|
<View style={styles.agentPanel}>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
@@ -1264,7 +1263,6 @@ function DraftAgentScreenContent({
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
</TitlebarNoDragContent>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useOpenProjectPicker } from "@/hooks/use-open-project-picker";
|
|||||||
import { usePanelStore } from "@/stores/panel-store";
|
import { usePanelStore } from "@/stores/panel-store";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
import { isCompactFormFactor } from "@/constants/layout";
|
import { isCompactFormFactor } from "@/constants/layout";
|
||||||
import { TitlebarDragRegion, TitlebarNoDragContent } from "@/components/desktop/titlebar-drag-region";
|
import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region";
|
||||||
|
|
||||||
export function OpenProjectScreen({ serverId }: { serverId: string }) {
|
export function OpenProjectScreen({ serverId }: { serverId: string }) {
|
||||||
const openAgentList = usePanelStore((s) => s.openAgentList);
|
const openAgentList = usePanelStore((s) => s.openAgentList);
|
||||||
@@ -30,24 +30,22 @@ export function OpenProjectScreen({ serverId }: { serverId: string }) {
|
|||||||
<MenuHeader borderless />
|
<MenuHeader borderless />
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
<TitlebarDragRegion />
|
<TitlebarDragRegion />
|
||||||
<TitlebarNoDragContent direction="column">
|
<View style={styles.logo}>
|
||||||
<View style={styles.logo}>
|
<PaseoLogo size={56} />
|
||||||
<PaseoLogo size={56} />
|
</View>
|
||||||
</View>
|
<View style={styles.headingGroup}>
|
||||||
<View style={styles.headingGroup}>
|
<Text style={styles.heading}>What shall we build today?</Text>
|
||||||
<Text style={styles.heading}>What shall we build today?</Text>
|
{hasHydrated && !hasProjects ? (
|
||||||
{hasHydrated && !hasProjects ? (
|
<Text style={styles.subtitle}>
|
||||||
<Text style={styles.subtitle}>
|
Add a project folder to start running agents on your codebase
|
||||||
Add a project folder to start running agents on your codebase
|
</Text>
|
||||||
</Text>
|
) : null}
|
||||||
) : null}
|
</View>
|
||||||
</View>
|
<View style={styles.cta}>
|
||||||
<View style={styles.cta}>
|
<Button variant="default" leftIcon={FolderOpen} onPress={() => void openProjectPicker()} testID="open-project-submit">
|
||||||
<Button variant="default" leftIcon={FolderOpen} onPress={() => void openProjectPicker()} testID="open-project-submit">
|
Add a project
|
||||||
Add a project
|
</Button>
|
||||||
</Button>
|
</View>
|
||||||
</View>
|
|
||||||
</TitlebarNoDragContent>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
getDesktopDaemonLogs,
|
getDesktopDaemonLogs,
|
||||||
type DesktopDaemonLogs,
|
type DesktopDaemonLogs,
|
||||||
} from "@/desktop/daemon/desktop-daemon";
|
} from "@/desktop/daemon/desktop-daemon";
|
||||||
import { TitlebarDragRegion, TitlebarNoDragContent } from "@/components/desktop/titlebar-drag-region";
|
import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region";
|
||||||
|
|
||||||
type StartupSplashScreenProps = {
|
type StartupSplashScreenProps = {
|
||||||
bootstrapState?: {
|
bootstrapState?: {
|
||||||
@@ -224,10 +224,8 @@ export function StartupSplashScreen({ bootstrapState }: StartupSplashScreenProps
|
|||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<TitlebarDragRegion />
|
<TitlebarDragRegion />
|
||||||
<TitlebarNoDragContent direction="column">
|
<PaseoLogo size={96} />
|
||||||
<PaseoLogo size={96} />
|
<Text style={styles.subtitle}>Starting up…</Text>
|
||||||
<Text style={styles.subtitle}>Starting up…</Text>
|
|
||||||
</TitlebarNoDragContent>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -236,24 +234,22 @@ export function StartupSplashScreen({ bootstrapState }: StartupSplashScreenProps
|
|||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<TitlebarDragRegion />
|
<TitlebarDragRegion />
|
||||||
<TitlebarNoDragContent direction="column">
|
<View style={styles.centeredContent}>
|
||||||
<View style={styles.centeredContent}>
|
<PaseoLogo size={96} />
|
||||||
<PaseoLogo size={96} />
|
<Text style={styles.title}>Welcome to Paseo</Text>
|
||||||
<Text style={styles.title}>Welcome to Paseo</Text>
|
<View style={styles.progressSteps}>
|
||||||
<View style={styles.progressSteps}>
|
{progressSteps.map((step) => (
|
||||||
{progressSteps.map((step) => (
|
<View key={step.key} style={styles.progressStepRow}>
|
||||||
<View key={step.key} style={styles.progressStepRow}>
|
{step.status === "complete" ? (
|
||||||
{step.status === "complete" ? (
|
<Check size={18} color={theme.colors.success} />
|
||||||
<Check size={18} color={theme.colors.success} />
|
) : (
|
||||||
) : (
|
<ActivityIndicator color={theme.colors.accent} />
|
||||||
<ActivityIndicator color={theme.colors.accent} />
|
)}
|
||||||
)}
|
<Text style={styles.subtitleInline}>{step.label}</Text>
|
||||||
<Text style={styles.subtitleInline}>{step.label}</Text>
|
</View>
|
||||||
</View>
|
))}
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
</TitlebarNoDragContent>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -261,8 +257,7 @@ export function StartupSplashScreen({ bootstrapState }: StartupSplashScreenProps
|
|||||||
return (
|
return (
|
||||||
<View style={[styles.container, styles.containerError]}>
|
<View style={[styles.container, styles.containerError]}>
|
||||||
<TitlebarDragRegion />
|
<TitlebarDragRegion />
|
||||||
<TitlebarNoDragContent direction="column">
|
<View style={styles.errorContent}>
|
||||||
<View style={styles.errorContent}>
|
|
||||||
<View style={styles.errorHeader}>
|
<View style={styles.errorHeader}>
|
||||||
<PaseoLogo size={64} />
|
<PaseoLogo size={64} />
|
||||||
<Text style={[styles.title, styles.titleError]}>Something went wrong</Text>
|
<Text style={[styles.title, styles.titleError]}>Something went wrong</Text>
|
||||||
@@ -321,7 +316,6 @@ export function StartupSplashScreen({ bootstrapState }: StartupSplashScreenProps
|
|||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</TitlebarNoDragContent>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2327,7 +2327,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
flexShrink: 1,
|
flexShrink: 1,
|
||||||
},
|
},
|
||||||
headerTitleContainer: {
|
headerTitleContainer: {
|
||||||
flex: 1,
|
flexShrink: 1,
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
|||||||
Reference in New Issue
Block a user