Functionality to delete and edit custom themes
This commit is contained in:
parent
ad92de141b
commit
668edb03d3
10 changed files with 157 additions and 12 deletions
|
@ -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)
|
### v2.2.2 (2022-03-21)
|
||||||
- Added option to get user location directly from the app ([#287](https://github.com/pawelmalak/flame/issues/287))
|
- 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))
|
- Fixed bug with local search not working when using prefix ([#289](https://github.com/pawelmalak/flame/issues/289))
|
||||||
|
|
|
@ -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
|
- 📌 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
|
- 🔍 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
|
- 🔑 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
|
- ☀️ Weather widget with current temperature, cloud coverage and animated weather status
|
||||||
- 🐳 Docker integration to automatically pick and add apps based on their labels
|
- 🐳 Docker integration to automatically pick and add apps based on their labels
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
// Redux
|
// 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';
|
import { State } from '../../../../store/reducers';
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
|
@ -21,11 +23,21 @@ interface Props {
|
||||||
export const ThemeBuilder = ({ themes }: Props): JSX.Element => {
|
export const ThemeBuilder = ({ themes }: Props): JSX.Element => {
|
||||||
const {
|
const {
|
||||||
auth: { isAuthenticated },
|
auth: { isAuthenticated },
|
||||||
|
theme: { themeInEdit },
|
||||||
} = useSelector((state: State) => state);
|
} = useSelector((state: State) => state);
|
||||||
|
|
||||||
|
const { editTheme } = bindActionCreators(actionCreators, useDispatch());
|
||||||
|
|
||||||
const [showModal, toggleShowModal] = useState(false);
|
const [showModal, toggleShowModal] = useState(false);
|
||||||
const [isInEdit, toggleIsInEdit] = useState(false);
|
const [isInEdit, toggleIsInEdit] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (themeInEdit) {
|
||||||
|
toggleIsInEdit(false);
|
||||||
|
toggleShowModal(true);
|
||||||
|
}
|
||||||
|
}, [themeInEdit]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.ThemeBuilder}>
|
<div className={classes.ThemeBuilder}>
|
||||||
{/* MODALS */}
|
{/* MODALS */}
|
||||||
|
@ -45,6 +57,7 @@ export const ThemeBuilder = ({ themes }: Props): JSX.Element => {
|
||||||
<div className={classes.Buttons}>
|
<div className={classes.Buttons}>
|
||||||
<Button
|
<Button
|
||||||
click={() => {
|
click={() => {
|
||||||
|
editTheme(null);
|
||||||
toggleIsInEdit(false);
|
toggleIsInEdit(false);
|
||||||
toggleShowModal(!showModal);
|
toggleShowModal(!showModal);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -19,10 +19,13 @@ interface Props {
|
||||||
|
|
||||||
export const ThemeCreator = ({ modalHandler }: Props): JSX.Element => {
|
export const ThemeCreator = ({ modalHandler }: Props): JSX.Element => {
|
||||||
const {
|
const {
|
||||||
theme: { activeTheme },
|
theme: { activeTheme, themeInEdit },
|
||||||
} = useSelector((state: State) => state);
|
} = useSelector((state: State) => state);
|
||||||
|
|
||||||
const { addTheme } = bindActionCreators(actionCreators, useDispatch());
|
const { addTheme, updateTheme } = bindActionCreators(
|
||||||
|
actionCreators,
|
||||||
|
useDispatch()
|
||||||
|
);
|
||||||
|
|
||||||
const [formData, setFormData] = useState<Theme>({
|
const [formData, setFormData] = useState<Theme>({
|
||||||
name: '',
|
name: '',
|
||||||
|
@ -38,6 +41,12 @@ export const ThemeCreator = ({ modalHandler }: Props): JSX.Element => {
|
||||||
setFormData({ ...formData, colors: activeTheme.colors });
|
setFormData({ ...formData, colors: activeTheme.colors });
|
||||||
}, [activeTheme]);
|
}, [activeTheme]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (themeInEdit) {
|
||||||
|
setFormData(themeInEdit);
|
||||||
|
}
|
||||||
|
}, [themeInEdit]);
|
||||||
|
|
||||||
const inputChangeHandler = (e: ChangeEvent<HTMLInputElement>) => {
|
const inputChangeHandler = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
|
|
||||||
|
@ -62,8 +71,11 @@ export const ThemeCreator = ({ modalHandler }: Props): JSX.Element => {
|
||||||
const formHandler = (e: FormEvent) => {
|
const formHandler = (e: FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// add new theme
|
if (!themeInEdit) {
|
||||||
addTheme(formData);
|
addTheme(formData);
|
||||||
|
} else {
|
||||||
|
updateTheme(formData);
|
||||||
|
}
|
||||||
|
|
||||||
// close modal
|
// close modal
|
||||||
modalHandler();
|
modalHandler();
|
||||||
|
@ -125,7 +137,11 @@ export const ThemeCreator = ({ modalHandler }: Props): JSX.Element => {
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button>Add theme</Button>
|
{!themeInEdit ? (
|
||||||
|
<Button>Add theme</Button>
|
||||||
|
) : (
|
||||||
|
<Button>Update theme</Button>
|
||||||
|
)}
|
||||||
</ModalForm>
|
</ModalForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,9 +19,15 @@ export const ThemeEditor = (props: Props): JSX.Element => {
|
||||||
theme: { userThemes },
|
theme: { userThemes },
|
||||||
} = useSelector((state: State) => state);
|
} = 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) => {
|
const deleteHandler = (theme: Theme) => {
|
||||||
if (window.confirm(`Are you sure you want to delete this theme?`)) {
|
if (window.confirm(`Are you sure you want to delete this theme?`)) {
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import {
|
import {
|
||||||
AddThemeAction,
|
AddThemeAction,
|
||||||
|
DeleteThemeAction,
|
||||||
|
EditThemeAction,
|
||||||
FetchThemesAction,
|
FetchThemesAction,
|
||||||
SetThemeAction,
|
SetThemeAction,
|
||||||
|
UpdateThemeAction,
|
||||||
} from '../actions/theme';
|
} from '../actions/theme';
|
||||||
import { ActionType } from '../action-types';
|
import { ActionType } from '../action-types';
|
||||||
import { Theme, ApiResponse, ThemeColors } from '../../interfaces';
|
import { Theme, ApiResponse, ThemeColors } from '../../interfaces';
|
||||||
|
@ -71,3 +74,54 @@ export const addTheme =
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const deleteTheme =
|
||||||
|
(name: string) => async (dispatch: Dispatch<DeleteThemeAction>) => {
|
||||||
|
try {
|
||||||
|
const res = await axios.delete<ApiResponse<Theme[]>>(
|
||||||
|
`/api/themes/${name}`,
|
||||||
|
{ headers: applyAuth() }
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.deleteTheme,
|
||||||
|
payload: res.data.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch<any>({
|
||||||
|
type: ActionType.createNotification,
|
||||||
|
payload: {
|
||||||
|
title: 'Success',
|
||||||
|
message: 'Theme deleted',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const editTheme =
|
||||||
|
(theme: Theme | null) => (dispatch: Dispatch<EditThemeAction>) => {
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.editTheme,
|
||||||
|
payload: theme,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateTheme =
|
||||||
|
(theme: Theme) => async (dispatch: Dispatch<UpdateThemeAction>) => {
|
||||||
|
try {
|
||||||
|
const res = await axios.put<ApiResponse<Theme[]>>(
|
||||||
|
`/api/themes/${theme.name}`,
|
||||||
|
theme,
|
||||||
|
{ headers: applyAuth() }
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.updateTheme,
|
||||||
|
payload: res.data.data,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -3,6 +3,9 @@ export enum ActionType {
|
||||||
setTheme = 'SET_THEME',
|
setTheme = 'SET_THEME',
|
||||||
fetchThemes = 'FETCH_THEMES',
|
fetchThemes = 'FETCH_THEMES',
|
||||||
addTheme = 'ADD_THEME',
|
addTheme = 'ADD_THEME',
|
||||||
|
deleteTheme = 'DELETE_THEME',
|
||||||
|
updateTheme = 'UPDATE_THEME',
|
||||||
|
editTheme = 'EDIT_THEME',
|
||||||
// CONFIG
|
// CONFIG
|
||||||
getConfig = 'GET_CONFIG',
|
getConfig = 'GET_CONFIG',
|
||||||
updateConfig = 'UPDATE_CONFIG',
|
updateConfig = 'UPDATE_CONFIG',
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
import { App } from '../../interfaces';
|
import { App } from '../../interfaces';
|
||||||
|
|
||||||
import { SetThemeAction } from './theme';
|
import {
|
||||||
|
AddThemeAction,
|
||||||
|
DeleteThemeAction,
|
||||||
|
EditThemeAction,
|
||||||
|
FetchThemesAction,
|
||||||
|
SetThemeAction,
|
||||||
|
UpdateThemeAction,
|
||||||
|
} from './theme';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AddQueryAction,
|
AddQueryAction,
|
||||||
|
@ -54,6 +61,11 @@ import {
|
||||||
export type Action =
|
export type Action =
|
||||||
// Theme
|
// Theme
|
||||||
| SetThemeAction
|
| SetThemeAction
|
||||||
|
| FetchThemesAction
|
||||||
|
| AddThemeAction
|
||||||
|
| DeleteThemeAction
|
||||||
|
| UpdateThemeAction
|
||||||
|
| EditThemeAction
|
||||||
// Config
|
// Config
|
||||||
| GetConfigAction
|
| GetConfigAction
|
||||||
| UpdateConfigAction
|
| UpdateConfigAction
|
||||||
|
|
|
@ -15,3 +15,18 @@ export interface AddThemeAction {
|
||||||
type: ActionType.addTheme;
|
type: ActionType.addTheme;
|
||||||
payload: Theme;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { Action } from '../actions';
|
import { Action } from '../actions';
|
||||||
import { ActionType } from '../action-types';
|
import { ActionType } from '../action-types';
|
||||||
import { Theme, ThemeColors } from '../../interfaces/Theme';
|
import { Theme } from '../../interfaces/Theme';
|
||||||
import { arrayPartition, parsePABToTheme } from '../../utility';
|
import { arrayPartition, parsePABToTheme } from '../../utility';
|
||||||
|
|
||||||
interface ThemeState {
|
interface ThemeState {
|
||||||
activeTheme: Theme;
|
activeTheme: Theme;
|
||||||
themes: Theme[];
|
themes: Theme[];
|
||||||
userThemes: Theme[];
|
userThemes: Theme[];
|
||||||
|
themeInEdit: Theme | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const savedTheme = localStorage.theme
|
const savedTheme = localStorage.theme
|
||||||
|
@ -23,6 +24,7 @@ const initialState: ThemeState = {
|
||||||
},
|
},
|
||||||
themes: [],
|
themes: [],
|
||||||
userThemes: [],
|
userThemes: [],
|
||||||
|
themeInEdit: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const themeReducer = (
|
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:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue