Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dark theme toggle #10721

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
101 changes: 81 additions & 20 deletions web/client/components/app/StandardApp.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
/*
* Copyright 2016, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import React, { useState } from 'react';
import { Provider } from 'react-redux';
import PropTypes from 'prop-types';
import { DragDropContext as dragDropContext } from 'react-dnd';
Expand All @@ -28,13 +20,86 @@ import isArray from 'lodash/isArray';

import './appPolyfill';

import Theme from '../theme/Theme';

const DefaultAppLoaderComponent = () => (
<span>
<div className="_ms2_init_spinner _ms2_init_center"><div></div></div>
<div className="_ms2_init_text _ms2_init_center">Loading MapStore</div>
</span>
);


const AppWithThemeToggle = ({ appComponent: AppComponent, ...props }) => {

const [appTheme, setAppTheme] = useState('default');
const [isToggled, setIsToggled] = useState(false);

const toggleButtonStyle = {
position: 'fixed',
bottom: '36px',
right: '2px',
width: '37px',
height: '23px',
borderRadius: '15px',
backgroundColor: isToggled ? '#4CAF50' : '#ccc',
transition: 'background-color 0.3s',
cursor: 'pointer',
zIndex: 1000,
display: 'flex',
justifyContent: isToggled ? 'flex-end' : 'flex-start',
alignItems: 'center',
padding: '5px',
};

const sliderCircleStyle = {
width: '17px', // Circle size
height: '17px',
borderRadius: '50%',
backgroundColor: 'white',
transition: 'transform 0.3s', // Apply smooth sliding effect to the circle
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
transform: isToggled ? 'translateX(3px)' : 'translateX(-2px)', // Move circle within the toggle area
};

// Sun SVG (for default theme)
const sunIcon = (
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-sun" viewBox="0 0 16 16">
<path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6m0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8M8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0m0 13a.5.5 0 0 1 .5.5v2a.5.0 0 0 1-1 0v-2A.5.5 0 0 1 8 13m8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5M3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8m10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0m-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0m9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707M4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708"/>
</svg>
);

// Moon SVG (for dark theme)
const moonIcon = (
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="#333" class="bi bi-moon" viewBox="0 0 16 16">
<path d="M6 .278a.77.77 0 0 1 .08.858 7.2 7.2 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277q.792-.001 1.533-.16a.79.79 0 0 1 .81.316.73.73 0 0 1-.031.893A8.35 8.35 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.75.75 0 0 1 6 .278M4.858 1.311A7.27 7.27 0 0 0 1.025 7.71c0 4.02 3.279 7.276 7.319 7.276a7.32 7.32 0 0 0 5.205-2.162q-.506.063-1.029.063c-4.61 0-8.343-3.714-8.343-8.29 0-1.167.242-2.278.681-3.286"/>
</svg>
);

return (
<>
<Theme theme={appTheme} />
<AppComponent {...props} />
<div
style={toggleButtonStyle}
onClick={() => {
setAppTheme(prevAppTheme => prevAppTheme === 'default' ? 'dark' : 'default');
setIsToggled(!isToggled);
}}
>
<div style={sliderCircleStyle}>
{isToggled ? moonIcon : sunIcon}
</div>
</div>
</>
);
};




/**
* Standard MapStore2 application component
*
Expand Down Expand Up @@ -76,7 +141,7 @@ class StandardApp extends React.Component {
};

state = {
initialized: false
initialized: false,
};

addProjDefinitions(config) {
Expand Down Expand Up @@ -132,23 +197,24 @@ class StandardApp extends React.Component {
onInit(config);
}
});

}

render() {
const {plugins, requires} = this.props.pluginsDef;
const {appStore, initialActions, appComponent, mode, ...other} = this.props;
const App = dragDropContext(html5Backend)(this.props.appComponent);
const Loader = this.props.loaderComponent;
const App = dragDropContext(html5Backend)(AppWithThemeToggle);

return this.state.initialized
? <Provider store={this.store}>
<App
{...other}
appComponent={this.props.appComponent}
plugins={{ ...PluginsUtils.getPlugins(plugins), requires }}
/>
</Provider>
: <Loader />;
: <this.props.loaderComponent />;
}

afterInit = () => {
if (this.props.printingEnabled) {
this.store.dispatch(loadPrintCapabilities(ConfigUtils.getConfigProp('printUrl')));
Expand Down Expand Up @@ -176,12 +242,7 @@ class StandardApp extends React.Component {
this.afterInit(config);
}
};
/**
* It returns an object of the same structure of the initialState but replacing strings like "{someExpression}" with the result of the expression between brackets.
* @param {object} state the object to parse
* @param {object} context context for expression
* @return {object} the modified object
*/

parseInitialState = (state, context) => {
return Object.keys(state || {}).reduce((previous, key) => {
return { ...previous, ...{ [key]: isObject(state[key]) ?
Expand Down
18 changes: 18 additions & 0 deletions web/client/components/theme/Theme.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,24 @@ const Theme = memo(({
}
}, [disabled]);

useEffect(() => {
const themeLinkId = 'ms-css-theme';
const existingLink = document.getElementById(themeLinkId);

if (existingLink)
existingLink.parentNode.removeChild(existingLink);

const newLink = document.createElement('link');
newLink.id = themeLinkId;
newLink.rel = 'stylesheet';
newLink.href = `dist/themes/${theme}.css`;
newLink.type = 'text/css';

document.head.appendChild(newLink); // Insert new link
console.log(`Applied new theme: ${theme}`); // TESTING LOG TO SHOW WHEN THEME CHANGES IN CONSOLE

}, [theme]);

return children ? Children.only(children) : null;
});

Expand Down