diff --git a/CHANGELOG.md b/CHANGELOG.md index e3d3e1d..f991071 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +### v2.3.0 (TBA) +- Added custom theme editor ([#246](https://github.com/pawelmalak/flame/issues/246)) + ### v2.2.2 (2022-03-21) - Added option to get user location directly from the app ([#287](https://github.com/pawelmalak/flame/issues/287)) - Fixed bug with local search not working when using prefix ([#289](https://github.com/pawelmalak/flame/issues/289)) diff --git a/README.md b/README.md index 290f230..a8424c9 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Flame is self-hosted startpage for your server. Its design is inspired (heavily) - 📌 Pin your favourite items to the homescreen for quick and easy access - 🔍 Integrated search bar with local filtering, 11 web search providers and ability to add your own - 🔑 Authentication system to protect your settings, apps and bookmarks -- 🔨 Dozens of options to customize Flame interface to your needs, including support for custom CSS and 15 built-in color themes +- 🔨 Dozens of options to customize Flame interface to your needs, including support for custom CSS, 15 built-in color themes and custom theme builder - ☀️ Weather widget with current temperature, cloud coverage and animated weather status - 🐳 Docker integration to automatically pick and add apps based on their labels diff --git a/client/src/components/Settings/Themer/ThemeBuilder/ThemeBuilder.tsx b/client/src/components/Settings/Themer/ThemeBuilder/ThemeBuilder.tsx index 4ae2822..50a2c5a 100644 --- a/client/src/components/Settings/Themer/ThemeBuilder/ThemeBuilder.tsx +++ b/client/src/components/Settings/Themer/ThemeBuilder/ThemeBuilder.tsx @@ -1,7 +1,9 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; // Redux -import { useSelector } from 'react-redux'; +import { useSelector, useDispatch } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import { actionCreators } from '../../../../store'; import { State } from '../../../../store/reducers'; // Other @@ -21,11 +23,21 @@ interface Props { export const ThemeBuilder = ({ themes }: Props): JSX.Element => { const { auth: { isAuthenticated }, + theme: { themeInEdit }, } = useSelector((state: State) => state); + const { editTheme } = bindActionCreators(actionCreators, useDispatch()); + const [showModal, toggleShowModal] = useState(false); const [isInEdit, toggleIsInEdit] = useState(false); + useEffect(() => { + if (themeInEdit) { + toggleIsInEdit(false); + toggleShowModal(true); + } + }, [themeInEdit]); + return (
{/* MODALS */} @@ -45,6 +57,7 @@ export const ThemeBuilder = ({ themes }: Props): JSX.Element => {
- + {!themeInEdit ? ( + + ) : ( + + )} ); }; diff --git a/client/src/components/Settings/Themer/ThemeBuilder/ThemeEditor.tsx b/client/src/components/Settings/Themer/ThemeBuilder/ThemeEditor.tsx index 7a876f5..888e576 100644 --- a/client/src/components/Settings/Themer/ThemeBuilder/ThemeEditor.tsx +++ b/client/src/components/Settings/Themer/ThemeBuilder/ThemeEditor.tsx @@ -19,9 +19,15 @@ export const ThemeEditor = (props: Props): JSX.Element => { theme: { userThemes }, } = useSelector((state: State) => state); - const { deleteTheme } = bindActionCreators(actionCreators, useDispatch()); + const { deleteTheme, editTheme } = bindActionCreators( + actionCreators, + useDispatch() + ); - const updateHandler = (theme: Theme) => {}; + const updateHandler = (theme: Theme) => { + props.modalHandler(); + editTheme(theme); + }; const deleteHandler = (theme: Theme) => { if (window.confirm(`Are you sure you want to delete this theme?`)) { diff --git a/client/src/store/action-creators/theme.ts b/client/src/store/action-creators/theme.ts index 42df813..881b777 100644 --- a/client/src/store/action-creators/theme.ts +++ b/client/src/store/action-creators/theme.ts @@ -1,8 +1,11 @@ import { Dispatch } from 'redux'; import { AddThemeAction, + DeleteThemeAction, + EditThemeAction, FetchThemesAction, SetThemeAction, + UpdateThemeAction, } from '../actions/theme'; import { ActionType } from '../action-types'; import { Theme, ApiResponse, ThemeColors } from '../../interfaces'; @@ -71,3 +74,54 @@ export const addTheme = }); } }; + +export const deleteTheme = + (name: string) => async (dispatch: Dispatch) => { + try { + const res = await axios.delete>( + `/api/themes/${name}`, + { headers: applyAuth() } + ); + + dispatch({ + type: ActionType.deleteTheme, + payload: res.data.data, + }); + + dispatch({ + type: ActionType.createNotification, + payload: { + title: 'Success', + message: 'Theme deleted', + }, + }); + } catch (err) { + console.log(err); + } + }; + +export const editTheme = + (theme: Theme | null) => (dispatch: Dispatch) => { + dispatch({ + type: ActionType.editTheme, + payload: theme, + }); + }; + +export const updateTheme = + (theme: Theme) => async (dispatch: Dispatch) => { + try { + const res = await axios.put>( + `/api/themes/${theme.name}`, + theme, + { headers: applyAuth() } + ); + + dispatch({ + type: ActionType.updateTheme, + payload: res.data.data, + }); + } catch (err) { + console.log(err); + } + }; diff --git a/client/src/store/action-types/index.ts b/client/src/store/action-types/index.ts index 1425219..c9ba812 100644 --- a/client/src/store/action-types/index.ts +++ b/client/src/store/action-types/index.ts @@ -3,6 +3,9 @@ export enum ActionType { setTheme = 'SET_THEME', fetchThemes = 'FETCH_THEMES', addTheme = 'ADD_THEME', + deleteTheme = 'DELETE_THEME', + updateTheme = 'UPDATE_THEME', + editTheme = 'EDIT_THEME', // CONFIG getConfig = 'GET_CONFIG', updateConfig = 'UPDATE_CONFIG', diff --git a/client/src/store/actions/index.ts b/client/src/store/actions/index.ts index bd0b360..ab6d204 100644 --- a/client/src/store/actions/index.ts +++ b/client/src/store/actions/index.ts @@ -1,6 +1,13 @@ import { App } from '../../interfaces'; -import { SetThemeAction } from './theme'; +import { + AddThemeAction, + DeleteThemeAction, + EditThemeAction, + FetchThemesAction, + SetThemeAction, + UpdateThemeAction, +} from './theme'; import { AddQueryAction, @@ -54,6 +61,11 @@ import { export type Action = // Theme | SetThemeAction + | FetchThemesAction + | AddThemeAction + | DeleteThemeAction + | UpdateThemeAction + | EditThemeAction // Config | GetConfigAction | UpdateConfigAction diff --git a/client/src/store/actions/theme.ts b/client/src/store/actions/theme.ts index c6f1624..e3da5b4 100644 --- a/client/src/store/actions/theme.ts +++ b/client/src/store/actions/theme.ts @@ -15,3 +15,18 @@ export interface AddThemeAction { type: ActionType.addTheme; payload: Theme; } + +export interface DeleteThemeAction { + type: ActionType.deleteTheme; + payload: Theme[]; +} + +export interface UpdateThemeAction { + type: ActionType.updateTheme; + payload: Theme[]; +} + +export interface EditThemeAction { + type: ActionType.editTheme; + payload: Theme | null; +} diff --git a/client/src/store/reducers/theme.ts b/client/src/store/reducers/theme.ts index c5bd869..b4fb55c 100644 --- a/client/src/store/reducers/theme.ts +++ b/client/src/store/reducers/theme.ts @@ -1,12 +1,13 @@ import { Action } from '../actions'; import { ActionType } from '../action-types'; -import { Theme, ThemeColors } from '../../interfaces/Theme'; +import { Theme } from '../../interfaces/Theme'; import { arrayPartition, parsePABToTheme } from '../../utility'; interface ThemeState { activeTheme: Theme; themes: Theme[]; userThemes: Theme[]; + themeInEdit: Theme | null; } const savedTheme = localStorage.theme @@ -23,6 +24,7 @@ const initialState: ThemeState = { }, themes: [], userThemes: [], + themeInEdit: null, }; export const themeReducer = ( @@ -60,6 +62,27 @@ export const themeReducer = ( }; } + case ActionType.deleteTheme: { + return { + ...state, + userThemes: action.payload, + }; + } + + case ActionType.editTheme: { + return { + ...state, + themeInEdit: action.payload, + }; + } + + case ActionType.updateTheme: { + return { + ...state, + userThemes: action.payload, + }; + } + default: return state; }