Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added desktop/public/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion desktop/src/components/PageWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export function PageWrapper({ children }: { children: React.ReactNode }) {
return (
<ScrollArea
h="calc(100vh - var(--app-shell-padding-total))"
w="calc(100vw - var(--app-navbar-width) - var(--app-shell-padding-total))"
w="100%"
type="auto"
scrollbarSize={6}
scrollHideDelay={100}
Expand Down
3 changes: 3 additions & 0 deletions desktop/src/layout/AppShell/AppShell.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@

.main {
background-color: var(--mantine-color-other-backgroundColors-main);
display: flex;
flex-direction: column;
min-height: 0; /* allow nested ScrollArea to compute height */
}
9 changes: 5 additions & 4 deletions desktop/src/layout/AppShell/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
Notification as MantineNotification,
Text,
} from "@mantine/core";
import { ReactNode, useEffect } from "react";
import { ReactNode, useEffect, useState } from "react";
import { useAppContext } from "../../hooks/useAppContext.tsx";
import { useNotifier } from "../../hooks/useNotifier.tsx";
import { colorFromType, NotificationType } from "../../types/notification";
Expand All @@ -18,6 +18,7 @@ interface AppShellProps {
export function AppShell({ children }: AppShellProps) {
const { isLoading, hasError } = useAppContext();
const { notification, setNotification } = useNotifier();
const [collapsed, setCollapsed] = useState(false);

useEffect(() => {
if (hasError) {
Expand All @@ -33,7 +34,7 @@ export function AppShell({ children }: AppShellProps) {
return (
<MantineAppShell
header={{ height: "var(--app-header-height)" }}
navbar={{ width: "var(--app-navbar-width)", breakpoint: "sm" }}
navbar={{ width: collapsed ? 56 : 250, breakpoint: "sm" }}
padding="md"
classNames={{
root: styles.appShell,
Expand All @@ -43,7 +44,7 @@ export function AppShell({ children }: AppShellProps) {
}}
>
<MantineAppShell.Navbar>
<Sidebar />
<Sidebar collapsed={collapsed} onToggle={() => setCollapsed((c) => !c)} />
</MantineAppShell.Navbar>

<MantineAppShell.Main>
Expand All @@ -62,7 +63,7 @@ export function AppShell({ children }: AppShellProps) {
<Text c="red">{hasError.message}</Text>
</div>
) : (
<div style={{ position: "relative", height: "100%" }}>
<div style={{ position: "relative", height: "100%", flex: 1, minHeight: 0 }}>
{children}
{isLoading && (
<div
Expand Down
55 changes: 44 additions & 11 deletions desktop/src/layout/Sidebar/Sidebar.module.css
Original file line number Diff line number Diff line change
@@ -1,27 +1,65 @@
.sidebar {
display: flex;
flex-direction: column;
padding: 0 1rem 1rem 1rem;
padding: var(--mantine-spacing-sm);
height: 100%;
overflow-y: auto;
overflow: hidden;
}

/* Collapsed state */
.sidebarCollapsed {
width: 56px;
}

/* Hide labels when collapsed */
.sidebarCollapsed :global(.mantine-NavLink-label) {
display: none;
}

/* Center icons in collapsed state */
.sidebarCollapsed :global(.mantine-NavLink-root) {
justify-content: center;
padding-left: var(--mantine-spacing-sm);
padding-right: var(--mantine-spacing-sm);
}


.sidebarCollapsed :global(.mantine-NavLink-section) {
margin-inline-end: 0;
}
/* Hide caret/chevron if any in collapsed state */
.sidebarCollapsed :global(.mantine-NavLink-rightSection) {
display: none;
}

/* Reduce logo area when collapsed */
.sidebarCollapsed .sidebar__logo {
height: 56px;
padding: 0.5rem;
}

.sidebar__context {
background-color: var(--mantine-color-gray-3);
color: var(--mantine-color-body-0);
padding: var(--mantine-spacing-xs);
margin: var(--mantine-spacing-xs);
border-radius: var(--mantine-radius-sm);
}

.sidebar__logo {
margin: -1rem -1rem 1rem -1rem;
padding: 1rem;
background-color: var(--mantine-color-other-backgroundColors-header);
border-bottom: 1px solid var(--mantine-color-other-colors-border);
display: flex;
align-items: center;
justify-content: center;
height: 64px;
height: 100px;
}

.sidebar__logo img {
display: block;
width: 100%;
height: 100%;
object-fit: contain;
filter: drop-shadow(0 0 20px color-mix(in srgb, var(--mantine-color-pink-0) 10%, transparent));
}

.sidebar__content {
Expand All @@ -37,11 +75,6 @@
gap: 0.5rem;
}

.sidebar__tree {
flex: 1;
overflow-y: auto;
}

.sidebar__section {
padding: 1rem;
border-bottom: 1px solid var(--mantine-color-other-colors-border);
Expand Down
183 changes: 137 additions & 46 deletions desktop/src/layout/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,53 @@
import { Group, Image, NavLink, Stack } from "@mantine/core";
import {
ActionIcon,
Box,
Button, ButtonGroup,
Flex,
Group,
Image,
Menu,
NavLink,
Stack,
Text,
Tooltip,
} from "@mantine/core";
import {
IconDatabase,
IconFolders,
IconLogs,
IconSettings,
IconTerminal2,
IconInfoCircle,
IconReload,
IconLayoutSidebarLeftExpand,
IconLayoutSidebarLeftCollapse,
IconStarFilled,
IconStar,
IconTools,
IconArrowRight,
IconChevronCompactRight,
IconChevronRight,
IconShield,
IconShieldLockFilled,
IconTemplate,
IconShieldLock,
} from "@tabler/icons-react";
import { useCallback } from "react";
import { Link, useLocation } from "wouter";
import { useAppContext } from "../../hooks/useAppContext.tsx";
import styles from "./Sidebar.module.css";
import iconImage from "/logo-dark.png";
import iconImage from "/icon.png";

interface SidebarProps {
collapsed: boolean;
onToggle: () => void;
}

export function Sidebar() {
export function Sidebar({ collapsed, onToggle }: SidebarProps) {
const [location, setLocation] = useLocation();
const { config, selectedWorkspace } = useAppContext();
const currentWorkspaceName = selectedWorkspace || config?.currentWorkspace || "—";
const currentNamespace = config?.currentNamespace || "—";

const navigateToWorkspaces = useCallback(() => {
setLocation(`/workspaces`);
Expand All @@ -39,12 +74,30 @@ export function Sidebar() {
}, [setLocation]);

return (
<div className={styles.sidebar}>
<Link to="/" className={styles.sidebar__logo}>
<Image src={iconImage} alt="flow" fit="contain" />
</Link>
<Stack gap="xs">
<Group gap="xs" mt="md">
<Stack justify="space-between" className={`${styles.sidebar} ${collapsed ? styles.sidebarCollapsed : ""}`}>
<Box>
<Flex justify="space-between" align={ collapsed ? "center" : "flex-end" } direction={ collapsed ? "column-reverse" : "column" }>
<ActionIcon
variant="transparent"
aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}
onClick={onToggle}
color="bodyLight"
>
{collapsed ? <IconLayoutSidebarLeftExpand size={16} /> : <IconLayoutSidebarLeftCollapse size={16} />}
</ActionIcon>
<Link to="/" className={styles.sidebar__logo}>
<Image src={iconImage} alt="flow" fit="contain" />
</Link>
</Flex>

<Stack gap="xs" mt="xl" justify="stretch" align={ collapsed ? "center" : "flex-start" }>
<NavLink
label="Favorites"
leftSection={<IconStar size={16} />}
active={location.startsWith("/favorites")}
variant="filled"
onClick={navigateToWorkspaces}
/>
<NavLink
label="Workspaces"
leftSection={<IconFolders size={16} />}
Expand All @@ -60,45 +113,83 @@ export function Sidebar() {
variant="filled"
onClick={navigateToExecutables}
/>
</Stack>
</Box>
<Box>
{collapsed ? (
<Group justify="center" gap={8}>
<Tooltip label={`Workspace: ${currentWorkspaceName} | Namespace: ${currentNamespace}`} position="right" openDelay={300}>
<ActionIcon variant="transparent" size="sm" aria-label="Context">
<IconInfoCircle size={14} />
</ActionIcon>
</Tooltip>
<Tooltip label="Sync" position="right" openDelay={300}>
<ActionIcon variant="light" size="sm" aria-label="Sync workspaces and executables">
<IconReload size={14} />
</ActionIcon>
</Tooltip>
</Group>
) : (

<NavLink
label="Logs"
leftSection={<IconLogs size={16} />}
active={location.startsWith("/logs")}
variant="filled"
onClick={navigateToLogs}
/>
<Stack gap="xs" align="stretch" justify="center">

<NavLink
label="Data"
leftSection={<IconDatabase size={16} />}
variant="filled"
childrenOffset={28}
>
<NavLink
label="Cache"
variant="filled"
active={location.startsWith("/cache")}
onClick={navigateToCache}
/>
<NavLink
label="Vault"
variant="filled"
active={location.startsWith("/vault")}
onClick={navigateToVault}
/>
</NavLink>

<NavLink
label="Settings"
leftSection={<IconSettings size={16} />}
active={location.startsWith("/settings")}
variant="filled"
onClick={navigateToSettings}
/>
</Group>
<Box className={styles.sidebar__context}>
<Group gap="xs" justify="space-between" mb="xs">
<Text size="xs" fw={700} c="dimmed">Context</Text>
<IconInfoCircle size={16} />
</Group>
<Group>
<Text size="xs" c="dimmed">Workspace</Text>
<Text size="xs" truncate>{currentWorkspaceName}</Text>
</Group>
<Group>
<Text size="xs" c="dimmed" mt={6}>Namespace</Text>
<Text size="xs" truncate>{currentNamespace}</Text>
</Group>
</Box>
<ButtonGroup style={{ alignSelf: 'center' }}>
<Button
leftSection={<IconReload size={12} />}
size="compact-xs" variant="transparent" justify="start">Sync</Button>
<Menu shadow="md" position="top-start" offset={15} width={200} withArrow>
<Menu.Target>
<Button
leftSection={<IconTools size={12} />}
size="compact-xs"
variant="transparent"
onClick={navigateToSettings}
justify="start"
>Tools</Button>
</Menu.Target>

</Stack>
</div>
<Menu.Dropdown>
<Menu.Item leftSection={<IconShieldLock size={14} />}>
Vault
</Menu.Item>
<Menu.Item leftSection={<IconDatabase size={14} />}>
Cache Store
</Menu.Item>
<Menu.Item leftSection={<IconTemplate size={14} />}>
Templates
</Menu.Item>
<Menu.Item
leftSection={<IconLogs size={14} />}
>
Logs
</Menu.Item>
</Menu.Dropdown>
</Menu>
<Button
leftSection={<IconSettings size={12} />}
size="compact-xs"
variant="transparent"
onClick={navigateToSettings}
justify="start"
>Settings</Button>
</ButtonGroup>
</Stack>
)}
</Box>
</Stack>
);
}