Polish sidebar drag snapback and workspace rows
This commit is contained in:
@@ -346,6 +346,7 @@ export function LeftSidebar({ selectedAgentId }: LeftSidebarProps) {
|
|||||||
projects={projects}
|
projects={projects}
|
||||||
isRefreshing={isManualRefresh && isRevalidating}
|
isRefreshing={isManualRefresh && isRevalidating}
|
||||||
onRefresh={handleRefresh}
|
onRefresh={handleRefresh}
|
||||||
|
onWorkspacePress={closeToAgent}
|
||||||
parentGestureRef={closeGestureRef}
|
parentGestureRef={closeGestureRef}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ interface SidebarAgentListProps {
|
|||||||
serverId: string | null
|
serverId: string | null
|
||||||
isRefreshing?: boolean
|
isRefreshing?: boolean
|
||||||
onRefresh?: () => void
|
onRefresh?: () => void
|
||||||
|
onWorkspacePress?: () => void
|
||||||
listFooterComponent?: ReactElement | null
|
listFooterComponent?: ReactElement | null
|
||||||
/** Gesture ref for coordinating with parent gestures (e.g., sidebar close) */
|
/** Gesture ref for coordinating with parent gestures (e.g., sidebar close) */
|
||||||
parentGestureRef?: MutableRefObject<GestureType | undefined>
|
parentGestureRef?: MutableRefObject<GestureType | undefined>
|
||||||
@@ -67,6 +68,7 @@ interface ProjectRowProps {
|
|||||||
|
|
||||||
interface WorkspaceRowProps {
|
interface WorkspaceRowProps {
|
||||||
workspace: SidebarWorkspaceEntry
|
workspace: SidebarWorkspaceEntry
|
||||||
|
compact?: boolean
|
||||||
onPress: () => void
|
onPress: () => void
|
||||||
onLongPress: () => void
|
onLongPress: () => void
|
||||||
}
|
}
|
||||||
@@ -107,9 +109,9 @@ function resolveWorkspaceBranchLabel(workspace: SidebarWorkspaceEntry): string {
|
|||||||
return 'Unknown branch'
|
return 'Unknown branch'
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveWorkspaceCreatedAtLabel(workspace: SidebarWorkspaceEntry): string {
|
function resolveWorkspaceCreatedAtLabel(workspace: SidebarWorkspaceEntry): string | null {
|
||||||
if (!workspace.createdAt) {
|
if (!workspace.createdAt) {
|
||||||
return 'No agents yet'
|
return null
|
||||||
}
|
}
|
||||||
return formatTimeAgo(workspace.createdAt)
|
return formatTimeAgo(workspace.createdAt)
|
||||||
}
|
}
|
||||||
@@ -195,8 +197,9 @@ function ProjectRow({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function WorkspaceRow({ workspace, onPress, onLongPress }: WorkspaceRowProps) {
|
function WorkspaceRow({ workspace, compact = false, onPress, onLongPress }: WorkspaceRowProps) {
|
||||||
const didLongPressRef = useRef(false)
|
const didLongPressRef = useRef(false)
|
||||||
|
const createdAtLabel = resolveWorkspaceCreatedAtLabel(workspace)
|
||||||
|
|
||||||
const handlePress = useCallback(() => {
|
const handlePress = useCallback(() => {
|
||||||
if (didLongPressRef.current) {
|
if (didLongPressRef.current) {
|
||||||
@@ -215,6 +218,7 @@ function WorkspaceRow({ workspace, onPress, onLongPress }: WorkspaceRowProps) {
|
|||||||
<Pressable
|
<Pressable
|
||||||
style={({ pressed, hovered = false }) => [
|
style={({ pressed, hovered = false }) => [
|
||||||
styles.workspaceRow,
|
styles.workspaceRow,
|
||||||
|
compact && styles.workspaceRowCompact,
|
||||||
hovered && styles.workspaceRowHovered,
|
hovered && styles.workspaceRowHovered,
|
||||||
pressed && styles.workspaceRowPressed,
|
pressed && styles.workspaceRowPressed,
|
||||||
]}
|
]}
|
||||||
@@ -226,9 +230,11 @@ function WorkspaceRow({ workspace, onPress, onLongPress }: WorkspaceRowProps) {
|
|||||||
<Text style={styles.workspaceBranchText} numberOfLines={1}>
|
<Text style={styles.workspaceBranchText} numberOfLines={1}>
|
||||||
{resolveWorkspaceBranchLabel(workspace)}
|
{resolveWorkspaceBranchLabel(workspace)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.workspaceCreatedAtText} numberOfLines={1}>
|
{createdAtLabel ? (
|
||||||
{resolveWorkspaceCreatedAtLabel(workspace)}
|
<Text style={styles.workspaceCreatedAtText} numberOfLines={1}>
|
||||||
</Text>
|
{createdAtLabel}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -261,6 +267,7 @@ export function SidebarAgentList({
|
|||||||
serverId,
|
serverId,
|
||||||
isRefreshing = false,
|
isRefreshing = false,
|
||||||
onRefresh,
|
onRefresh,
|
||||||
|
onWorkspacePress,
|
||||||
listFooterComponent,
|
listFooterComponent,
|
||||||
parentGestureRef,
|
parentGestureRef,
|
||||||
}: SidebarAgentListProps) {
|
}: SidebarAgentListProps) {
|
||||||
@@ -268,6 +275,7 @@ export function SidebarAgentList({
|
|||||||
const showDesktopWebScrollbar = Platform.OS === 'web' && !isMobile
|
const showDesktopWebScrollbar = Platform.OS === 'web' && !isMobile
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const [collapsedProjectKeys, setCollapsedProjectKeys] = useState<Set<string>>(new Set())
|
const [collapsedProjectKeys, setCollapsedProjectKeys] = useState<Set<string>>(new Set())
|
||||||
|
const [canonicalResyncNonce, setCanonicalResyncNonce] = useState(0)
|
||||||
|
|
||||||
const getProjectOrder = useSidebarOrderStore((state) => state.getProjectOrder)
|
const getProjectOrder = useSidebarOrderStore((state) => state.getProjectOrder)
|
||||||
const setProjectOrder = useSidebarOrderStore((state) => state.setProjectOrder)
|
const setProjectOrder = useSidebarOrderStore((state) => state.setProjectOrder)
|
||||||
@@ -381,7 +389,7 @@ export function SidebarAgentList({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return next
|
return next
|
||||||
}, [collapsedProjectKeys, projects])
|
}, [canonicalResyncNonce, collapsedProjectKeys, projects])
|
||||||
|
|
||||||
const toggleProjectCollapsed = useCallback((projectKey: string) => {
|
const toggleProjectCollapsed = useCallback((projectKey: string) => {
|
||||||
setCollapsedProjectKeys((prev) => {
|
setCollapsedProjectKeys((prev) => {
|
||||||
@@ -417,10 +425,12 @@ export function SidebarAgentList({
|
|||||||
return (
|
return (
|
||||||
<WorkspaceRow
|
<WorkspaceRow
|
||||||
workspace={item.workspace}
|
workspace={item.workspace}
|
||||||
|
compact={isMobile}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (!serverId) {
|
if (!serverId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
onWorkspacePress?.()
|
||||||
navigate(workspaceRoute as any)
|
navigate(workspaceRoute as any)
|
||||||
}}
|
}}
|
||||||
onLongPress={drag}
|
onLongPress={drag}
|
||||||
@@ -429,6 +439,8 @@ export function SidebarAgentList({
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
collapsedProjectKeys,
|
collapsedProjectKeys,
|
||||||
|
isMobile,
|
||||||
|
onWorkspacePress,
|
||||||
pathname,
|
pathname,
|
||||||
projectIconByProjectKey,
|
projectIconByProjectKey,
|
||||||
serverId,
|
serverId,
|
||||||
@@ -444,6 +456,7 @@ export function SidebarAgentList({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let didPersistOrderChange = false
|
||||||
const reorderedProjectKeys = reorderedRows
|
const reorderedProjectKeys = reorderedRows
|
||||||
.filter(
|
.filter(
|
||||||
(row): row is Extract<SidebarTreeRow, { kind: 'project' }> => row.kind === 'project'
|
(row): row is Extract<SidebarTreeRow, { kind: 'project' }> => row.kind === 'project'
|
||||||
@@ -457,6 +470,7 @@ export function SidebarAgentList({
|
|||||||
reorderedVisibleKeys: reorderedProjectKeys,
|
reorderedVisibleKeys: reorderedProjectKeys,
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
|
didPersistOrderChange = true
|
||||||
setProjectOrder(
|
setProjectOrder(
|
||||||
serverId,
|
serverId,
|
||||||
mergeWithRemainder({
|
mergeWithRemainder({
|
||||||
@@ -487,6 +501,7 @@ export function SidebarAgentList({
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
didPersistOrderChange = true
|
||||||
setWorkspaceOrder(
|
setWorkspaceOrder(
|
||||||
serverId,
|
serverId,
|
||||||
projectKey,
|
projectKey,
|
||||||
@@ -496,6 +511,12 @@ export function SidebarAgentList({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If persisted ordering did not change, force a local resync so draggable UI state
|
||||||
|
// snaps back to canonical Project -> Workspaces grouping.
|
||||||
|
if (!didPersistOrderChange) {
|
||||||
|
setCanonicalResyncNonce((prev) => prev + 1)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[getProjectOrder, getWorkspaceOrder, serverId, setProjectOrder, setWorkspaceOrder]
|
[getProjectOrder, getWorkspaceOrder, serverId, setProjectOrder, setWorkspaceOrder]
|
||||||
)
|
)
|
||||||
@@ -599,20 +620,21 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
},
|
},
|
||||||
workspaceRow: {
|
workspaceRow: {
|
||||||
minHeight: 32,
|
minHeight: 34,
|
||||||
marginBottom: theme.spacing[1],
|
marginBottom: theme.spacing[1],
|
||||||
marginLeft: theme.spacing[6],
|
marginLeft: theme.spacing[4],
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[1],
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[2],
|
||||||
borderRadius: theme.borderRadius.lg,
|
borderRadius: theme.borderRadius.lg,
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: theme.colors.border,
|
|
||||||
backgroundColor: theme.colors.surface0,
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
},
|
},
|
||||||
|
workspaceRowCompact: {
|
||||||
|
marginLeft: theme.spacing[3],
|
||||||
|
paddingHorizontal: theme.spacing[1],
|
||||||
|
},
|
||||||
workspaceRowHovered: {
|
workspaceRowHovered: {
|
||||||
backgroundColor: theme.colors.surface1,
|
backgroundColor: theme.colors.surface1,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user