diff --git a/libs/shared/lib/components/color-mode/index.tsx b/libs/shared/lib/components/color-mode/index.tsx index 7854d1f0582548051022f31a3757c51a46637f72..d15201f5634ba660514267b2718229575a949fca 100644 --- a/libs/shared/lib/components/color-mode/index.tsx +++ b/libs/shared/lib/components/color-mode/index.tsx @@ -1,44 +1,36 @@ import React, { useState, useEffect } from 'react'; import { Button } from '../buttons'; // Adjust the import path according to your project structure import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../components/tooltip'; +import { setTheme, Theme } from '@graphpolaris/shared/lib/data-access/store/configSlice'; +import { useAppDispatch, useConfig } from '@graphpolaris/shared/lib/data-access/store'; const ColorMode = () => { - // Initialize theme state without setting a default yet - const [theme, setTheme] = useState<string>(''); + const config = useConfig(); + const dispatch = useAppDispatch(); - // Function to update the body class and local storage + // Function to update the body class const applyTheme = (themeValue: string) => { document.body.className = themeValue; - localStorage.setItem('theme', themeValue); }; - // Load the theme preference from local storage on initial render - useEffect(() => { - const savedTheme = localStorage.getItem('theme'); - if (savedTheme) { - setTheme(savedTheme); - } else { - // Fallback to system preference if no saved theme - // const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; - const prefersDarkMode = false; // TODO: remove once dark theme is fixed - const defaultTheme = prefersDarkMode ? 'dark-mode' : 'light-mode'; - setTheme(defaultTheme); - applyTheme(defaultTheme); - } - }, []); - // Update the local storage and body class whenever the theme changes useEffect(() => { - if (theme) { - applyTheme(theme); + if (config.theme) { + applyTheme(config.theme); } - }, [theme]); + }, [config.theme]); // Function to toggle the theme const toggleTheme = () => { - setTheme((currentTheme) => (currentTheme === 'light-mode' ? 'dark-mode' : 'light-mode')); + const themes = [ + Theme.light, + Theme.dark, + ] + + const newTheme = themes[(themes.indexOf(config.theme) + 1) % themes.length]; + dispatch(setTheme(newTheme)); }; - const iconComponent = theme === 'dark-mode' ? 'icon-[ic--baseline-dark-mode]' : 'icon-[ic--baseline-light-mode]'; + const iconComponent = config.theme === Theme.dark ? 'icon-[ic--baseline-dark-mode]' : 'icon-[ic--baseline-light-mode]'; return ( <TooltipProvider delayDuration={0}> @@ -47,7 +39,7 @@ const ColorMode = () => { <Button variant="ghost" size="sm" iconComponent={iconComponent} onClick={toggleTheme} /> </TooltipTrigger> <TooltipContent> - <p>{`Switch to ${theme === 'dark-mode' ? 'light' : 'dark'}-mode`}</p> + <p>{`Switch to ${config.theme === Theme.dark ? 'light' : 'dark'}-mode`}</p> </TooltipContent> </Tooltip> </TooltipProvider> diff --git a/libs/shared/lib/data-access/store/configSlice.ts b/libs/shared/lib/data-access/store/configSlice.ts index ef8d75aa8666913cfd8873a2a051117e069f5b0c..f79c4d3f932773b554edadefebe0b4ee59981f2d 100644 --- a/libs/shared/lib/data-access/store/configSlice.ts +++ b/libs/shared/lib/data-access/store/configSlice.ts @@ -1,8 +1,14 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import type { RootState } from './store'; +export enum Theme { + light = 'light-mode', + dark = 'dark-mode', +} + // Define the initial state using that type export type ConfigStateI = { + theme: Theme; autoSendQueries: boolean; errors: string[]; warnings: string[]; @@ -10,6 +16,7 @@ export type ConfigStateI = { successes: string[]; }; export const initialState: ConfigStateI = { + theme: localStorage.getItem('theme') as Theme ?? Theme.light, autoSendQueries: true, errors: [], warnings: [], @@ -48,10 +55,14 @@ export const configSlice = createSlice({ removeLastWarning: (state) => { state.warnings.shift(); }, + setTheme: (state, action: PayloadAction<Theme>) => { + localStorage.setItem('theme', action.payload); + state.theme = action.payload; + }, }, }); -export const { addError, removeLastError, addWarning, removeLastWarning, addSuccess, removeLastSuccess, addInfo, removeLastInfo } = +export const { addError, removeLastError, addWarning, removeLastWarning, addSuccess, removeLastSuccess, addInfo, removeLastInfo, setTheme } = configSlice.actions; // Other code such as selectors can use the imported `RootState` type diff --git a/libs/shared/lib/vis/visualizations/nodelinkvis/components/NLPixi.tsx b/libs/shared/lib/vis/visualizations/nodelinkvis/components/NLPixi.tsx index 0fc07eb7d2cfbc1aabf71f193e52de4ef6edd50b..297e6062e9831548cd8dee2572aee3e97bc11baa 100644 --- a/libs/shared/lib/vis/visualizations/nodelinkvis/components/NLPixi.tsx +++ b/libs/shared/lib/vis/visualizations/nodelinkvis/components/NLPixi.tsx @@ -23,6 +23,8 @@ import { Viewport } from 'pixi-viewport'; import { NodelinkVisProps } from '../nodelinkvis'; import { Tooltip, TooltipContent, TooltipTrigger } from '@graphpolaris/shared/lib/components/tooltip'; import { MovedEvent } from 'pixi-viewport/dist/types'; +import { Theme } from '@graphpolaris/shared/lib/data-access/store/configSlice'; +import { useConfig } from '@graphpolaris/shared/lib/data-access/store'; type Props = { onClick: (event?: { node: NodeTypeD3; pos: IPointData }) => void; @@ -47,6 +49,12 @@ export const NLPixi = (props: Props) => { const [quickPopup, setQuickPopup] = useState<{ node: NodeType; pos: IPointData } | undefined>(); const [popups, setPopups] = useState<{ node: NodeTypeD3; pos: IPointData }[]>([]); + const globalConfig = useConfig(); + + useEffect(() => { + update(); + }, [globalConfig.currentTheme]); + const app = useMemo( () => new Application({ @@ -314,7 +322,12 @@ export const NLPixi = (props: Props) => { getLinkWidth() { return props.configuration.edges.width.width || config.LINE_WIDTH_DEFAULT; - } + }, + + getBackgroundColor() { + // Colors corresponding to .bg-light class + return globalConfig.currentTheme === Theme.dark ? 0x121621 : 0xffffff; + }, })); function resize() { @@ -458,7 +471,7 @@ export const NLPixi = (props: Props) => { const text = new Text(linkMeta.name, { fontSize: 60, fill: config.LINE_COLOR_DEFAULT, - stroke: 0xffffff, + stroke: imperative.current.getBackgroundColor(), strokeThickness: 30, }); text.cullable = true; @@ -588,7 +601,9 @@ export const NLPixi = (props: Props) => { } else { text.rotation = rads; } - } + + text.style.stroke = imperative.current.getBackgroundColor(); + }; const updateNodeLabel = (node: NodeTypeD3) => { if (graph.current.nodes.length > config.LABEL_MAX_NODES) return;