Skip to content
Closed
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
16 changes: 15 additions & 1 deletion components/log-viewer-webui/client/src/AntdApp.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import {RouterProvider} from "react-router";

import {ConfigProvider} from "antd";

import router from "./router";
import THEME_CONFIG, {useThemeStore} from "./theme";

import "@ant-design/v5-patch-for-react-19";

Expand All @@ -11,7 +14,18 @@ import "@ant-design/v5-patch-for-react-19";
* @return
*/
const AntApp = () => {
return <RouterProvider router={router}/>;
const {algorithm} = useThemeStore();

return (
<ConfigProvider
theme={{
...THEME_CONFIG,
algorithm: algorithm,
}}
>
<RouterProvider router={router}/>
</ConfigProvider>
);
};

export default AntApp;
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.mainLayout {
min-height: 100vh;
height: 100vh;
overflow: hidden;
}

.siderLogoContainer {
Expand All @@ -9,8 +10,20 @@
height: 64px;
}

.siderContainer {
flex-direction: column;
display: flex;
justify-content: space-between;
height: 100%;
}

.siderLogo {
max-width: 100%;
max-height: 100%;
height: 32px;
padding-inline: 10%;
padding-block: 16px;
}

.siderTopMenu {
flex-grow: 1;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {useState} from "react";
import {
Link,
Outlet,
useLocation,
} from "react-router";

import {
Expand All @@ -15,45 +15,57 @@ import {
} from "antd";

import styles from "./MainLayout.module.css";
import ThemeToggleMenuItem from "./ThemeToggleMenuItem";


const {Sider} = Layout;

type MenuItem = Required<MenuProps>["items"][number];

const SIDEBAR_MENU_ITEMS: MenuItem[] = [
const sidebarTopMenuItems: MenuItem[] = [
{type: "divider"},
{label: <Link to={"/ingest"}>Ingest</Link>, key: "/ingest", icon: <UploadOutlined/>},
{label: <Link to={"/search"}>Search</Link>, key: "/search", icon: <SearchOutlined/>},
];

const sidebarBottomMenuItems = [
<ThemeToggleMenuItem key={"theme-toggle"}/>,
];

/**
* The main layout of web ui.
*
* @return
*/
const MainLayout = () => {
const [collapsed, setCollapsed] = useState(false);
const {pathname} = useLocation();

return (
<Layout className={styles["mainLayout"]}>
<Sider
collapsed={collapsed}
collapsedWidth={50}
collapsible={true}
theme={"light"}
width={"150"}
onCollapse={(value) => {
setCollapsed(value);
}}
width={"200"}
>
<div className={styles["siderLogoContainer"]}>
<img
alt={"CLP Logo"}
className={styles["siderLogo"]}
src={"/clp-logo.png"}/>
<div className={styles["siderContainer"]}>
<div className={styles["siderLogoContainer"]}>
<img
alt={"CLP Logo"}
className={styles["siderLogo"]}
src={"/clp-logo.png"}/>
</div>
<Menu
className={styles["siderTopMenu"]}
items={sidebarTopMenuItems}
selectedKeys={[pathname]}/>
<Menu
selectable={false}
selectedKeys={[]}
>
{sidebarBottomMenuItems}
</Menu>
</div>
<Menu
items={SIDEBAR_MENU_ITEMS}
mode={"inline"}/>
</Sider>
<Layout>
<Outlet/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {useCallback} from "react";

import {
FormatPainterOutlined,
MoonOutlined,
SunOutlined,
} from "@ant-design/icons";
import {Menu} from "antd";

import {
THEME_MODE,
useThemeStore,
} from "../../theme";


/**
* Maps theme mode to icon.
*/
const THEME_MODE_TO_ICON: Record<THEME_MODE, React.ReactNode> = Object.freeze({
[THEME_MODE.SYSTEM]: <FormatPainterOutlined/>,
[THEME_MODE.DARK]: <MoonOutlined/>,
[THEME_MODE.LIGHT]: <SunOutlined/>,
});

/**
* Maps theme mode to label.
*/
const THEME_MODE_TO_LABEL: Record<THEME_MODE, string> = Object.freeze({
[THEME_MODE.SYSTEM]: "System",
[THEME_MODE.DARK]: "Dark",
[THEME_MODE.LIGHT]: "Light",
});

const {Item} = Menu;

/**
* Displays a theme toggle menu item.
*
* @return
*/
const ThemeToggleMenuItem = () => {
const mode = useThemeStore((state) => state.mode);

const handleClick = useCallback(() => {
const {setMode} = useThemeStore.getState();
if (mode === THEME_MODE.SYSTEM) {
setMode(THEME_MODE.DARK);
} else if (mode === THEME_MODE.DARK) {
setMode(THEME_MODE.LIGHT);
} else {
setMode(THEME_MODE.SYSTEM);
}
}, [mode]);

return (
<Item
icon={THEME_MODE_TO_ICON[mode]}
onClick={handleClick}
>
{THEME_MODE_TO_LABEL[mode]}
</Item>
);
};


export default ThemeToggleMenuItem;
25 changes: 25 additions & 0 deletions components/log-viewer-webui/client/src/theme/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type {ThemeConfig} from "antd/es/config-provider/context";

import useThemeStore from "./themeStore";
import {THEME_MODE} from "./typings";


/**
* Ant Design theme configuration.
*/
const THEME_CONFIG: ThemeConfig = Object.freeze({
token: {
fontFamily: "'Inter', sans-serif",
colorPrimary: "#2a8efa",
borderRadius: 3,
},
cssVar: true,
hashed: false,
});


export {
THEME_MODE,
useThemeStore,
};
export default THEME_CONFIG;
56 changes: 56 additions & 0 deletions components/log-viewer-webui/client/src/theme/themeStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type {MappingAlgorithm} from "antd/es/theme/interface";
import {create} from "zustand";
import {persist} from "zustand/middleware";

import {THEME_MODE} from "./typings";
import {
getAlgorithm,
getSystemAlgorithm,
} from "./utils";


interface ThemeValues {
algorithm: MappingAlgorithm;
mode: THEME_MODE;
}

interface ThemeActions {
setMode: (mode: THEME_MODE) => void;
}

type ThemeState = ThemeValues & ThemeActions;

const THEME_DEFAULT: ThemeValues = {
algorithm: getSystemAlgorithm(),
mode: THEME_MODE.SYSTEM,
};

const useThemeStore = create(
persist<ThemeState>(
(set) => ({
...THEME_DEFAULT,
setMode: (newMode: THEME_MODE) => {
set({mode: newMode});
set({algorithm: getAlgorithm(newMode)});
},
}),
{
name: "theme-storage",
merge: (persistedState, currentState) => {
const safePersistedState = persistedState as Partial<ThemeState>;

return {
...currentState,
...safePersistedState,
...(
safePersistedState.mode &&
{algorithm: getAlgorithm(safePersistedState.mode)}
),
};
},
}
)
);


export default useThemeStore;
8 changes: 8 additions & 0 deletions components/log-viewer-webui/client/src/theme/typings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
enum THEME_MODE {
SYSTEM = "system",
DARK = "dark",
LIGHT = "light",
}


export {THEME_MODE};
37 changes: 37 additions & 0 deletions components/log-viewer-webui/client/src/theme/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {theme} from "antd";

import {THEME_MODE} from "./typings";


/**
* Detects Antd theme algorithm from system theme.
*
* @return The system theme algorithm.
*/
const getSystemAlgorithm = () => {
return window.matchMedia("(prefers-color-scheme: dark)").matches ?
theme.darkAlgorithm :
theme.defaultAlgorithm;
};

/**
* Detects Antd theme algorithm for the given theme mode.
*
* @param mode
* @return The theme algorithm.
*/
const getAlgorithm = (mode: THEME_MODE) => {
if (mode === THEME_MODE.SYSTEM) {
return getSystemAlgorithm();
} else if (mode === THEME_MODE.DARK) {
return theme.darkAlgorithm;
}

return theme.defaultAlgorithm;
};


export {
getAlgorithm,
getSystemAlgorithm,
};