Fix dark mode graph labels for system theme and improve colors

- Fix dark mode detection to work when theme is set to 'system'
- Add real-time system theme change detection
- Update label colors from cyan to white for better readability
- Update edge colors to medium gray (#888888) for better contrast
- Add development feature: random graph generator button in settings
- Enhance random graph with edge attributes and labels
- Install missing graphology layout dependencies
This commit is contained in:
Roman Marchuk 2025-10-01 17:36:25 -04:00
parent 20402f515f
commit 7297ca1d5c
5 changed files with 67 additions and 7 deletions

View file

@ -43,6 +43,10 @@
"cmdk": "^1.0.4", "cmdk": "^1.0.4",
"graphology": "^0.26.0", "graphology": "^0.26.0",
"graphology-generators": "^0.11.2", "graphology-generators": "^0.11.2",
"graphology-layout": "^0.6.1",
"graphology-layout-force": "^0.2.4",
"graphology-layout-forceatlas2": "^0.10.1",
"graphology-layout-noverlap": "^0.4.2",
"i18next": "^24.2.2", "i18next": "^24.2.2",
"katex": "^0.16.22", "katex": "^0.16.22",
"lucide-react": "^0.475.0", "lucide-react": "^0.475.0",
@ -56,6 +60,7 @@
"react-markdown": "^9.1.0", "react-markdown": "^9.1.0",
"react-number-format": "^5.4.3", "react-number-format": "^5.4.3",
"react-router-dom": "^7.3.0", "react-router-dom": "^7.3.0",
"react-select": "^5.10.2",
"react-syntax-highlighter": "^15.6.1", "react-syntax-highlighter": "^15.6.1",
"rehype-katex": "^7.0.1", "rehype-katex": "^7.0.1",
"rehype-react": "^8.0.0", "rehype-react": "^8.0.0",

View file

@ -2,7 +2,7 @@ import { useRegisterEvents, useSetSettings, useSigma } from '@react-sigma/core'
import { AbstractGraph } from 'graphology-types' import { AbstractGraph } from 'graphology-types'
// import { useLayoutCircular } from '@react-sigma/layout-circular' // import { useLayoutCircular } from '@react-sigma/layout-circular'
import { useLayoutForceAtlas2 } from '@react-sigma/layout-forceatlas2' import { useLayoutForceAtlas2 } from '@react-sigma/layout-forceatlas2'
import { useEffect } from 'react' import { useEffect, useState } from 'react'
// import useRandomGraph, { EdgeType, NodeType } from '@/hooks/useRandomGraph' // import useRandomGraph, { EdgeType, NodeType } from '@/hooks/useRandomGraph'
import { EdgeType, NodeType } from '@/hooks/useLightragGraph' import { EdgeType, NodeType } from '@/hooks/useLightragGraph'
@ -43,6 +43,20 @@ const GraphControl = ({ disableHoverEffect }: { disableHoverEffect?: boolean })
const selectedEdge = useGraphStore.use.selectedEdge() const selectedEdge = useGraphStore.use.selectedEdge()
const focusedEdge = useGraphStore.use.focusedEdge() const focusedEdge = useGraphStore.use.focusedEdge()
const sigmaGraph = useGraphStore.use.sigmaGraph() const sigmaGraph = useGraphStore.use.sigmaGraph()
// Track system theme changes when theme is set to 'system'
const [systemThemeIsDark, setSystemThemeIsDark] = useState(() =>
window.matchMedia('(prefers-color-scheme: dark)').matches
)
useEffect(() => {
if (theme === 'system') {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
const handler = (e: MediaQueryListEvent) => setSystemThemeIsDark(e.matches)
mediaQuery.addEventListener('change', handler)
return () => mediaQuery.removeEventListener('change', handler)
}
}, [theme])
/** /**
* When component mount or maxIterations changes * When component mount or maxIterations changes
@ -194,7 +208,9 @@ const GraphControl = ({ disableHoverEffect }: { disableHoverEffect?: boolean })
* => Setting the sigma reducers * => Setting the sigma reducers
*/ */
useEffect(() => { useEffect(() => {
const isDarkTheme = theme === 'dark' // Check if dark mode is actually applied (handles both 'dark' theme and 'system' theme when OS is dark)
const isDarkTheme = theme === 'dark' ||
(theme === 'system' && window.document.documentElement.classList.contains('dark'))
const labelColor = isDarkTheme ? Constants.labelColorDarkTheme : undefined const labelColor = isDarkTheme ? Constants.labelColorDarkTheme : undefined
const edgeColor = isDarkTheme ? Constants.edgeColorDarkTheme : undefined const edgeColor = isDarkTheme ? Constants.edgeColorDarkTheme : undefined
@ -298,6 +314,7 @@ const GraphControl = ({ disableHoverEffect }: { disableHoverEffect?: boolean })
sigma, sigma,
disableHoverEffect, disableHoverEffect,
theme, theme,
systemThemeIsDark,
hideUnselectedEdges, hideUnselectedEdges,
enableEdgeEvents, enableEdgeEvents,
renderEdgeLabels, renderEdgeLabels,

View file

@ -7,8 +7,10 @@ import Input from '@/components/ui/Input'
import { controlButtonVariant } from '@/lib/constants' import { controlButtonVariant } from '@/lib/constants'
import { useSettingsStore } from '@/stores/settings' import { useSettingsStore } from '@/stores/settings'
import { useGraphStore } from '@/stores/graph'
import useRandomGraph from '@/hooks/useRandomGraph'
import { SettingsIcon, Undo2 } from 'lucide-react' import { SettingsIcon, Undo2, Shuffle } from 'lucide-react'
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
/** /**
@ -163,6 +165,9 @@ export default function Settings() {
const enableHealthCheck = useSettingsStore.use.enableHealthCheck() const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
// Random graph functionality for development/testing
const { randomGraph } = useRandomGraph()
const setEnableNodeDrag = useCallback( const setEnableNodeDrag = useCallback(
() => useSettingsStore.setState((pre) => ({ enableNodeDrag: !pre.enableNodeDrag })), () => useSettingsStore.setState((pre) => ({ enableNodeDrag: !pre.enableNodeDrag })),
[] []
@ -228,6 +233,11 @@ export default function Settings() {
useSettingsStore.setState({ graphLayoutMaxIterations: iterations }) useSettingsStore.setState({ graphLayoutMaxIterations: iterations })
}, []) }, [])
const handleGenerateRandomGraph = useCallback(() => {
const graph = randomGraph()
useGraphStore.getState().setSigmaGraph(graph)
}, [randomGraph])
const { t } = useTranslation(); const { t } = useTranslation();
const saveSettings = () => setOpened(false); const saveSettings = () => setOpened(false);
@ -376,6 +386,24 @@ export default function Settings() {
defaultValue={15} defaultValue={15}
onEditFinished={setGraphLayoutMaxIterations} onEditFinished={setGraphLayoutMaxIterations}
/> />
<Separator />
{/* Development/Testing Section */}
<div className="flex flex-col gap-2">
<label className="text-sm leading-none font-medium text-muted-foreground">
Development
</label>
<Button
onClick={handleGenerateRandomGraph}
variant="outline"
size="sm"
className="flex items-center gap-2"
>
<Shuffle className="h-3.5 w-3.5" />
Generate Random Graph
</Button>
</div>
<Separator /> <Separator />
<Button <Button
onClick={saveSettings} onClick={saveSettings}

View file

@ -56,6 +56,16 @@ const useRandomGraph = () => {
image: faker.image.urlLoremFlickr() image: faker.image.urlLoremFlickr()
}) })
}) })
// Add edge attributes
graph.edges().forEach((edge: string) => {
graph.mergeEdgeAttributes(edge, {
label: faker.lorem.words(faker.number.int({ min: 1, max: 3 })),
size: faker.number.float({ min: 1, max: 5 }),
color: randomColor()
})
})
return graph as Graph<NodeType, EdgeType> return graph as Graph<NodeType, EdgeType>
}, [faker]) }, [faker])

View file

@ -5,16 +5,16 @@ export const webuiPrefix = '/webui/'
export const controlButtonVariant: ButtonVariantType = 'ghost' export const controlButtonVariant: ButtonVariantType = 'ghost'
export const labelColorDarkTheme = '#B2EBF2' export const labelColorDarkTheme = '#FFFFFF'
export const LabelColorHighlightedDarkTheme = '#000' export const LabelColorHighlightedDarkTheme = '#000000'
export const nodeColorDisabled = '#E2E2E2' export const nodeColorDisabled = '#E2E2E2'
export const nodeBorderColor = '#EEEEEE' export const nodeBorderColor = '#EEEEEE'
export const nodeBorderColorSelected = '#F57F17' export const nodeBorderColorSelected = '#F57F17'
export const edgeColorDarkTheme = '#969696' export const edgeColorDarkTheme = '#888888'
export const edgeColorSelected = '#F57F17' export const edgeColorSelected = '#F57F17'
export const edgeColorHighlighted = '#B2EBF2' export const edgeColorHighlighted = '#FFFFFF'
export const searchResultLimit = 50 export const searchResultLimit = 50
export const labelListLimit = 100 export const labelListLimit = 100