From cafc195c85c40c5a895293711f7aad2417021123 Mon Sep 17 00:00:00 2001
From: Dennis Collaris <d.a.c.collaris@uu.nl>
Date: Thu, 1 Aug 2024 17:59:58 +0000
Subject: [PATCH] feat: implement dark mode edge labels

---
 .../lib/components/color-mode/index.tsx       | 42 ++++++++-----------
 .../lib/data-access/store/configSlice.ts      | 13 +++++-
 .../nodelinkvis/components/NLPixi.tsx         | 21 ++++++++--
 3 files changed, 47 insertions(+), 29 deletions(-)

diff --git a/libs/shared/lib/components/color-mode/index.tsx b/libs/shared/lib/components/color-mode/index.tsx
index 7854d1f05..d15201f56 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 ef8d75aa8..f79c4d3f9 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 0fc07eb7d..297e6062e 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;
-- 
GitLab