diff --git a/src/app/AppLayout/AppLayout.tsx b/src/app/AppLayout/AppLayout.tsx index fe71a9413..c7fb4f8da 100644 --- a/src/app/AppLayout/AppLayout.tsx +++ b/src/app/AppLayout/AppLayout.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useContext } from 'react'; import { Brand, @@ -25,7 +25,8 @@ import { ToolbarGroup, ToolbarItem, Tooltip, - PageSidebarBody + PageSidebarBody, + Switch } from '@patternfly/react-core'; import { Dropdown, @@ -50,6 +51,7 @@ import { useConnectedUser } from '@app/services/userManagementHook'; import { KeycloakService } from '@services/keycloakService'; import { BarsIcon, ExternalLinkAltIcon, InfoCircleIcon, QuestionCircleIcon } from '@patternfly/react-icons'; import { ConsoleACL } from '@services/securityService'; +import { ThemeContext } from '@app/providers/ThemeProvider'; interface IAppLayout { init: string; @@ -59,6 +61,7 @@ interface IAppLayout { const AppLayout: React.FunctionComponent = ({ init, children }) => { const { t } = useTranslation(); const brandname = t('brandname.brandname'); + const {theme,toggleTheme} = useContext(ThemeContext); const history = useHistory(); const { connectedUser } = useConnectedUser(); @@ -161,6 +164,13 @@ const AppLayout: React.FunctionComponent = ({ init, children }) => { align={{ default: 'alignRight' }} spacer={{ default: 'spacerNone', md: 'spacerMd' }} > + { const { t } = useTranslation(); const encodingDocs = t('brandname.encoding-docs-link'); - + const {syntaxHighLighterTheme} = useContext(ThemeContext) const yamlConfig = useFetchConfigurationYAML(props.cacheName); const xmlConfig = useFetchConfigurationXML(props.cacheName); @@ -68,7 +68,7 @@ const CacheConfiguration = (props: { cacheName: string; editable: boolean; confi {config} - + {config} @@ -78,7 +78,7 @@ const CacheConfiguration = (props: { cacheName: string; editable: boolean; confi return ( - + JSON} tabContentId="tab1" tabContentRef={contentRef1} /> XML} tabContentId="tab2" tabContentRef={contentRef2} /> YAML} tabContentId="tab3" tabContentRef={contentRef3} /> diff --git a/src/app/Caches/Configuration/DetailConfigurations.tsx b/src/app/Caches/Configuration/DetailConfigurations.tsx index 40124bb6b..dab8bc268 100644 --- a/src/app/Caches/Configuration/DetailConfigurations.tsx +++ b/src/app/Caches/Configuration/DetailConfigurations.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, useContext } from 'react'; import { Button, Bullseye, @@ -28,13 +28,13 @@ import { Thead, Tr, Th, Tbody, Td, ExpandableRowContent } from '@patternfly/reac import { Table } from '@patternfly/react-table/deprecated'; import { DataContainerBreadcrumb } from '@app/Common/DataContainerBreadcrumb'; import SyntaxHighlighter from 'react-syntax-highlighter'; -import { githubGist } from 'react-syntax-highlighter/dist/esm/styles/hljs'; import { useTranslation } from 'react-i18next'; import { SearchIcon, CubeIcon } from '@patternfly/react-icons'; import { useFetchCacheTemplates } from '@app/services/cachesHook'; import { TableErrorState } from '@app/Common/TableErrorState'; import { onSearch } from '@app/utils/searchFilter'; import { global_spacer_sm, global_spacer_md } from '@patternfly/react-tokens'; +import { ThemeContext } from '@app/providers/ThemeProvider'; const DetailConfigurations: React.FunctionComponent = (props) => { const { t } = useTranslation(); @@ -49,6 +49,8 @@ const DetailConfigurations: React.FunctionComponent = (props) => { perPage: 10 }); + const {syntaxHighLighterTheme} = useContext(ThemeContext) + useEffect(() => { setFilteredTemplates(cacheTemplates); }, [cacheTemplates]); @@ -223,7 +225,7 @@ const DetailConfigurations: React.FunctionComponent = (props) => { diff --git a/src/app/Caches/Create/CacheConfigEditor.tsx b/src/app/Caches/Create/CacheConfigEditor.tsx index f57893072..fe8694846 100644 --- a/src/app/Caches/Create/CacheConfigEditor.tsx +++ b/src/app/Caches/Create/CacheConfigEditor.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { Alert, @@ -20,6 +20,7 @@ import { CubeIcon } from '@patternfly/react-icons'; import { useTranslation } from 'react-i18next'; import { ConsoleServices } from '@services/ConsoleServices'; import { useApiAlert } from '@app/utils/useApiAlert'; +import { ThemeContext } from '@app/providers/ThemeProvider'; const CacheConfigEditor = (props: { cmName: string; @@ -27,6 +28,7 @@ const CacheConfigEditor = (props: { cacheEditorModifier: (CacheEditorStep) => void; setReviewConfig: (string) => void; }) => { + const {theme} = useContext(ThemeContext); const sampleConfig = '{\n' + ' "distributed-cache": {\n' + @@ -127,6 +129,7 @@ const CacheConfigEditor = (props: { id="cache-config" language={Language.json} height="200px" + isDarkTheme={theme === 'dark'} /> { + const {theme} = useContext(ThemeContext); const { configuration, setConfiguration } = useCreateCache(); const { t } = useTranslation(); const brandname = t('brandname.brandname'); @@ -155,6 +157,7 @@ const PersistentCacheConfigurator = () => { onChange={changeAndValidate} language={Language.json} height={'sizeToFit'} + isDarkTheme={theme === 'dark'} /> {displayValidationError()} diff --git a/src/app/Caches/Create/ReviewCacheConfig.tsx b/src/app/Caches/Create/ReviewCacheConfig.tsx index 8bb49c4de..6b7c32d2b 100644 --- a/src/app/Caches/Create/ReviewCacheConfig.tsx +++ b/src/app/Caches/Create/ReviewCacheConfig.tsx @@ -1,14 +1,16 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { Flex, Form, FormGroup, Text, TextContent, TextVariants } from '@patternfly/react-core'; import { CodeEditor } from '@patternfly/react-code-editor'; import { useTranslation } from 'react-i18next'; import { CacheConfigUtils } from '@services/cacheConfigUtils'; import { useCreateCache } from '@app/services/createCacheHook'; +import { ThemeContext } from '@app/providers/ThemeProvider'; const ReviewCacheConfig = (props: { setReviewConfig: (string) => void }) => { const { configuration } = useCreateCache(); const { t } = useTranslation(); const [config, setConfig] = useState(CacheConfigUtils.createCacheConfigFromData(configuration)); + const {theme} = useContext(ThemeContext); useEffect(() => { const jsonFormatConfig = CacheConfigUtils.createCacheConfigFromData(configuration); @@ -30,6 +32,7 @@ const ReviewCacheConfig = (props: { setReviewConfig: (string) => void }) => { code={config} height="sizeToFit" isCopyEnabled + isDarkTheme={theme === 'dark'} copyButtonSuccessTooltipText={t('caches.create.review.copied-tooltip')} copyButtonToolTipText={t('caches.create.review.copy-tooltip')} /> diff --git a/src/app/Caches/DetailCache.tsx b/src/app/Caches/DetailCache.tsx index 82c0ffbf8..fcabc683f 100644 --- a/src/app/Caches/DetailCache.tsx +++ b/src/app/Caches/DetailCache.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { Alert, AlertActionLink, @@ -54,10 +54,12 @@ import { useTranslation } from 'react-i18next'; import { RebalancingCache } from '@app/Rebalancing/RebalancingCache'; import { CacheConfigUtils } from '@services/cacheConfigUtils'; import { EncodingType } from '@services/infinispanRefData'; +import { ThemeContext } from '@app/providers/ThemeProvider'; const DetailCache = (props: { cacheName: string }) => { const cacheName = props.cacheName; const { t } = useTranslation(); + const {theme} = useContext(ThemeContext); const brandname = t('brandname.brandname'); const encodingDocs = t('brandname.encoding-docs-link'); const { connectedUser } = useConnectedUser(); @@ -134,6 +136,7 @@ const DetailCache = (props: { cacheName: string }) => { ); } + return ( { activeKey={activeTabKey2} aria-label="Entries tab" component={TabsComponent.nav} - style={{ backgroundColor: global_BackgroundColor_100.value }} + style={theme === 'dark'? {} : { backgroundColor: global_BackgroundColor_100.value }} onSelect={(event, tabIndex) => setActiveTabKey2(tabIndex)} > { const { cacheEntries, totalEntriesCount, loadingEntries, errorEntries, infoEntries, reloadEntries, getByKey } = @@ -64,6 +64,8 @@ const CacheEntries = (props: { cacheName: string }) => { perPage: 10 }); + const {syntaxHighLighterTheme} = useContext(ThemeContext) + useEffect(() => { if (cache.encoding.key == EncodingType.Protobuf) { setSelectSearchOption(ContentType.string); @@ -147,7 +149,7 @@ const CacheEntries = (props: { cacheName: string }) => { diff --git a/src/app/Caches/Query/QueryEntries.tsx b/src/app/Caches/Query/QueryEntries.tsx index 00ef49a57..fdee5852e 100644 --- a/src/app/Caches/Query/QueryEntries.tsx +++ b/src/app/Caches/Query/QueryEntries.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect,useContext } from 'react'; import { Bullseye, Button, @@ -20,12 +20,12 @@ import { import { SearchIcon, ExclamationCircleIcon, HelpIcon } from '@patternfly/react-icons'; import displayUtils from '../../../services/displayUtils'; import SyntaxHighlighter from 'react-syntax-highlighter'; -import { githubGist } from 'react-syntax-highlighter/dist/esm/styles/hljs'; import { useTranslation } from 'react-i18next'; import { ConsoleServices } from '@services/ConsoleServices'; import { Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table'; import { Table } from '@patternfly/react-table/deprecated'; import { global_danger_color_200, global_spacer_md, global_spacer_sm } from '@patternfly/react-tokens'; +import { ThemeContext } from '@app/providers/ThemeProvider'; const QueryEntries = (props: { cacheName: string; indexed: boolean; changeTab: () => void }) => { const [query, setQuery] = useState(''); @@ -41,6 +41,9 @@ const QueryEntries = (props: { cacheName: string; indexed: boolean; changeTab: ( total: 0 }); + const {syntaxHighLighterTheme} = useContext(ThemeContext) + + const columnNames = { value: 'Value' }; @@ -57,7 +60,7 @@ const QueryEntries = (props: { cacheName: string; indexed: boolean; changeTab: ( return ( diff --git a/src/app/Common/Health.tsx b/src/app/Common/Health.tsx index f5864da90..a026db979 100644 --- a/src/app/Common/Health.tsx +++ b/src/app/Common/Health.tsx @@ -1,13 +1,22 @@ -import React from 'react'; +import React,{useContext} from 'react'; import { Flex, FlexItem, Text, TextContent } from '@patternfly/react-core'; import { AlertIcon } from '@patternfly/react-core/dist/js/components/Alert/AlertIcon'; import displayUtils from '@services/displayUtils'; import { ComponentHealth } from '@services/infinispanRefData'; +import { ThemeContext } from '@app/providers/ThemeProvider'; +import { chart_global_label_Fill, global_Color_light_100 } from '@patternfly/react-tokens'; const Health = (props: { health: string; displayIcon?: boolean; cacheName?: string }) => { const health = ComponentHealth[props.health]; const displayIcon = props.displayIcon == undefined ? true : props.displayIcon; - + const {theme} = useContext(ThemeContext); + + const getHealthLabelColor = ()=>{ + const color = displayUtils.healthColor(health, false); + return (theme === 'dark' && color === chart_global_label_Fill.value) + ? global_Color_light_100.value + : color; + } return ( {displayIcon && ( @@ -28,7 +37,7 @@ const Health = (props: { health: string; displayIcon?: boolean; cacheName?: stri {displayUtils.healthLabel(health)} diff --git a/src/app/Common/Status.tsx b/src/app/Common/Status.tsx index d055d88fd..60c4c8432 100644 --- a/src/app/Common/Status.tsx +++ b/src/app/Common/Status.tsx @@ -20,7 +20,7 @@ const Status = (props: { status?: Status }) => { - + {status.name} diff --git a/src/app/ProtoSchema/ProtobufSchemasDisplay.tsx b/src/app/ProtoSchema/ProtobufSchemasDisplay.tsx index 5389af394..cdee08a80 100644 --- a/src/app/ProtoSchema/ProtobufSchemasDisplay.tsx +++ b/src/app/ProtoSchema/ProtobufSchemasDisplay.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useContext } from 'react'; import { Alert, AlertVariant, @@ -26,7 +26,6 @@ import { Table, Thead, Tr, Th, Tbody, Td, ExpandableRowContent, ActionsColumn, I import { DatabaseIcon, SearchIcon } from '@patternfly/react-icons'; import { global_spacer_md, global_spacer_sm, global_spacer_xl } from '@patternfly/react-tokens'; import SyntaxHighlighter from 'react-syntax-highlighter'; -import { githubGist } from 'react-syntax-highlighter/dist/esm/styles/hljs'; import { useApiAlert } from '@app/utils/useApiAlert'; import { useTranslation } from 'react-i18next'; import { ConsoleServices } from '@services/ConsoleServices'; @@ -38,6 +37,7 @@ import { EditSchema } from './EditSchema'; import { useFetchProtobufSchemas } from '@app/services/protobufHooks'; import { onSearch } from '@app/utils/searchFilter'; import './ProtobufSchemasDisplay.css'; +import { ThemeContext } from '@app/providers/ThemeProvider'; const ProtobufSchemasDisplay = (props: { setProtoSchemasCount: (number) => void; isVisible: boolean }) => { const { t } = useTranslation(); @@ -59,6 +59,7 @@ const ProtobufSchemasDisplay = (props: { setProtoSchemasCount: (number) => void; page: 1, perPage: 10 }); + const {syntaxHighLighterTheme} = useContext(ThemeContext) const isSchemaExpanded = (row) => expandedSchemaNames.includes(row.name); @@ -217,7 +218,7 @@ const ProtobufSchemasDisplay = (props: { setProtoSchemasCount: (number) => void; return ( - + {schemasContent.get(name)} diff --git a/src/app/Welcome/Welcome.tsx b/src/app/Welcome/Welcome.tsx index 443316c16..34e39c8a5 100644 --- a/src/app/Welcome/Welcome.tsx +++ b/src/app/Welcome/Welcome.tsx @@ -87,7 +87,6 @@ const Welcome = (props) => { }} component={'button'} className={'button'} - style={{ backgroundColor: global_BackgroundColor_100.value }} > {goToTheConsole} diff --git a/src/app/index.tsx b/src/app/index.tsx index 5a2d3acad..def8bad8a 100644 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -7,6 +7,7 @@ import '@app/app.css'; import { KeycloakService } from '@services/keycloakService'; import { ConsoleServices } from '@services/ConsoleServices'; import { UserContextProvider } from '@app/providers/UserContextProvider'; +import { ThemeProvider } from './providers/ThemeProvider'; const App = () => { const [init, setInit] = useState< @@ -74,11 +75,13 @@ const App = () => { const load = () => { return ( - - - - - + + + + + + + ); }; diff --git a/src/app/providers/ThemeProvider.tsx b/src/app/providers/ThemeProvider.tsx new file mode 100644 index 000000000..b3e90c9c2 --- /dev/null +++ b/src/app/providers/ThemeProvider.tsx @@ -0,0 +1,41 @@ +import React, { useEffect, useState } from 'react'; +import { getInitialTheme } from '@app/utils/themeUtils'; +import { githubGist, dracula } from 'react-syntax-highlighter/dist/esm/styles/hljs'; + +const initialState = { + theme : '', + toggleTheme : ()=>{}, + syntaxHighLighterTheme : '' +} + +export const ThemeContext = React.createContext(initialState); + + +const ThemeProvider = ({children})=>{ + const [theme,setTheme] = useState(getInitialTheme); + const syntaxHighLighterTheme = theme === 'dark'? dracula : githubGist; + + useEffect(()=>{ + if (theme === 'dark') { + document.documentElement.classList.add('pf-v5-theme-dark'); + } else { + document.documentElement.classList.remove('pf-v5-theme-dark'); + } + + if(localStorage){ + localStorage.setItem('theme',theme); + } + },[theme]) + + const toggleTheme = ()=>{ + setTheme((previousTheme)=> previousTheme === 'light'?'dark':'light'); + } + const themeState = { + theme, + toggleTheme, + syntaxHighLighterTheme + } + return {children} +} + +export {ThemeProvider} \ No newline at end of file diff --git a/src/app/utils/themeUtils.ts b/src/app/utils/themeUtils.ts new file mode 100644 index 000000000..72d5136d2 --- /dev/null +++ b/src/app/utils/themeUtils.ts @@ -0,0 +1,12 @@ + + +export const getInitialTheme = () : string => { + // Get theme from local storage cookie + if(localStorage && localStorage.getItem('theme')) + return localStorage.getItem('theme') || 'light'; + + // Check System preferred Theme + const prefersDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches; + + return prefersDarkTheme? 'dark' :'light'; +}