Components: refactored rest of the components to use new state. Minor changes to exports, imports and props
This commit is contained in:
parent
89d935e27f
commit
969bdb7d24
29 changed files with 462 additions and 733 deletions
|
@ -1,38 +1,41 @@
|
|||
import { BrowserRouter, Route, Switch } from 'react-router-dom';
|
||||
import { fetchQueries, getConfig, setTheme } from './store/actions';
|
||||
import { actionCreators } from './store';
|
||||
import 'external-svg-loader';
|
||||
|
||||
// Redux
|
||||
import { store } from './store/store';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
// Utils
|
||||
import { checkVersion } from './utility';
|
||||
|
||||
// Routes
|
||||
import Home from './components/Home/Home';
|
||||
import Apps from './components/Apps/Apps';
|
||||
import Settings from './components/Settings/Settings';
|
||||
import Bookmarks from './components/Bookmarks/Bookmarks';
|
||||
import NotificationCenter from './components/NotificationCenter/NotificationCenter';
|
||||
|
||||
// Load config
|
||||
store.dispatch<any>(getConfig());
|
||||
|
||||
// Set theme
|
||||
if (localStorage.theme) {
|
||||
store.dispatch<any>(setTheme(localStorage.theme));
|
||||
}
|
||||
|
||||
// Check for updates
|
||||
checkVersion();
|
||||
|
||||
// fetch queries
|
||||
store.dispatch<any>(fetchQueries());
|
||||
import { Home } from './components/Home/Home';
|
||||
import { Apps } from './components/Apps/Apps';
|
||||
import { Settings } from './components/Settings/Settings';
|
||||
import { Bookmarks } from './components/Bookmarks/Bookmarks';
|
||||
import { NotificationCenter } from './components/NotificationCenter/NotificationCenter';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const App = (): JSX.Element => {
|
||||
const dispath = useDispatch();
|
||||
const { fetchQueries, getConfig, setTheme } = bindActionCreators(
|
||||
actionCreators,
|
||||
dispath
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getConfig();
|
||||
|
||||
if (localStorage.theme) {
|
||||
setTheme(localStorage.theme);
|
||||
}
|
||||
|
||||
checkVersion();
|
||||
|
||||
fetchQueries();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<>
|
||||
<BrowserRouter>
|
||||
<Switch>
|
||||
<Route exact path="/" component={Home} />
|
||||
|
@ -42,7 +45,7 @@ const App = (): JSX.Element => {
|
|||
</Switch>
|
||||
</BrowserRouter>
|
||||
<NotificationCenter />
|
||||
</Provider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
import classes from './AppCard.module.css';
|
||||
import Icon from '../../UI/Icons/Icon/Icon';
|
||||
import { Icon } from '../../UI';
|
||||
import { iconParser, urlParser } from '../../../utility';
|
||||
|
||||
import { App, Config, GlobalState } from '../../../interfaces';
|
||||
import { connect } from 'react-redux';
|
||||
import { App } from '../../../interfaces';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { State } from '../../../store/reducers';
|
||||
|
||||
interface ComponentProps {
|
||||
interface Props {
|
||||
app: App;
|
||||
pinHandler?: Function;
|
||||
config: Config;
|
||||
}
|
||||
|
||||
const AppCard = (props: ComponentProps): JSX.Element => {
|
||||
export const AppCard = (props: Props): JSX.Element => {
|
||||
const { config } = useSelector((state: State) => state.config);
|
||||
|
||||
const [displayUrl, redirectUrl] = urlParser(props.app.url);
|
||||
|
||||
let iconEl: JSX.Element;
|
||||
|
@ -42,7 +44,7 @@ const AppCard = (props: ComponentProps): JSX.Element => {
|
|||
return (
|
||||
<a
|
||||
href={redirectUrl}
|
||||
target={props.config.appsSameTab ? '' : '_blank'}
|
||||
target={config.appsSameTab ? '' : '_blank'}
|
||||
rel="noreferrer"
|
||||
className={classes.AppCard}
|
||||
>
|
||||
|
@ -54,11 +56,3 @@ const AppCard = (props: ComponentProps): JSX.Element => {
|
|||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
config: state.config.config,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(AppCard);
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
import { useState, useEffect, ChangeEvent, SyntheticEvent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { addApp, updateApp } from '../../../store/actions';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { App, NewApp } from '../../../interfaces';
|
||||
|
||||
import classes from './AppForm.module.css';
|
||||
|
||||
import ModalForm from '../../UI/Forms/ModalForm/ModalForm';
|
||||
import InputGroup from '../../UI/Forms/InputGroup/InputGroup';
|
||||
import Button from '../../UI/Buttons/Button/Button';
|
||||
import { ModalForm, InputGroup, Button } from '../../UI';
|
||||
import { inputHandler, newAppTemplate } from '../../../utility';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../../store';
|
||||
|
||||
interface ComponentProps {
|
||||
interface Props {
|
||||
modalHandler: () => void;
|
||||
addApp: (formData: NewApp | FormData) => any;
|
||||
updateApp: (id: number, formData: NewApp | FormData) => any;
|
||||
app?: App;
|
||||
}
|
||||
|
||||
const AppForm = (props: ComponentProps): JSX.Element => {
|
||||
export const AppForm = (props: Props): JSX.Element => {
|
||||
const dispatch = useDispatch();
|
||||
const { addApp, updateApp } = bindActionCreators(actionCreators, dispatch);
|
||||
|
||||
const [useCustomIcon, toggleUseCustomIcon] = useState<boolean>(false);
|
||||
const [customIcon, setCustomIcon] = useState<File | null>(null);
|
||||
const [formData, setFormData] = useState<NewApp>(newAppTemplate);
|
||||
|
@ -68,16 +68,16 @@ const AppForm = (props: ComponentProps): JSX.Element => {
|
|||
if (!props.app) {
|
||||
if (customIcon) {
|
||||
const data = createFormData();
|
||||
props.addApp(data);
|
||||
addApp(data);
|
||||
} else {
|
||||
props.addApp(formData);
|
||||
addApp(formData);
|
||||
}
|
||||
} else {
|
||||
if (customIcon) {
|
||||
const data = createFormData();
|
||||
props.updateApp(props.app.id, data);
|
||||
updateApp(props.app.id, data);
|
||||
} else {
|
||||
props.updateApp(props.app.id, formData);
|
||||
updateApp(props.app.id, formData);
|
||||
props.modalHandler();
|
||||
}
|
||||
}
|
||||
|
@ -192,5 +192,3 @@ const AppForm = (props: ComponentProps): JSX.Element => {
|
|||
</ModalForm>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(null, { addApp, updateApp })(AppForm);
|
||||
|
|
|
@ -2,15 +2,15 @@ import classes from './AppGrid.module.css';
|
|||
import { Link } from 'react-router-dom';
|
||||
import { App } from '../../../interfaces/App';
|
||||
|
||||
import AppCard from '../AppCard/AppCard';
|
||||
import { AppCard } from '../AppCard/AppCard';
|
||||
|
||||
interface ComponentProps {
|
||||
interface Props {
|
||||
apps: App[];
|
||||
totalApps?: number;
|
||||
searching: boolean;
|
||||
}
|
||||
|
||||
const AppGrid = (props: ComponentProps): JSX.Element => {
|
||||
export const AppGrid = (props: Props): JSX.Element => {
|
||||
let apps: JSX.Element;
|
||||
|
||||
if (props.apps.length > 0) {
|
||||
|
@ -49,5 +49,3 @@ const AppGrid = (props: ComponentProps): JSX.Element => {
|
|||
|
||||
return apps;
|
||||
};
|
||||
|
||||
export default AppGrid;
|
||||
|
|
|
@ -8,48 +8,45 @@ import {
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
// Redux
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
pinApp,
|
||||
deleteApp,
|
||||
reorderApps,
|
||||
updateConfig,
|
||||
createNotification,
|
||||
} from '../../../store/actions';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
// Typescript
|
||||
import { App, Config, GlobalState, NewNotification } from '../../../interfaces';
|
||||
import { App } from '../../../interfaces';
|
||||
|
||||
// CSS
|
||||
import classes from './AppTable.module.css';
|
||||
|
||||
// UI
|
||||
import Icon from '../../UI/Icons/Icon/Icon';
|
||||
import Table from '../../UI/Table/Table';
|
||||
import { Icon, Table } from '../../UI';
|
||||
import { State } from '../../../store/reducers';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../../store';
|
||||
|
||||
interface ComponentProps {
|
||||
apps: App[];
|
||||
config: Config;
|
||||
pinApp: (app: App) => void;
|
||||
deleteApp: (id: number) => void;
|
||||
interface Props {
|
||||
updateAppHandler: (app: App) => void;
|
||||
reorderApps: (apps: App[]) => void;
|
||||
updateConfig: (formData: any) => void;
|
||||
createNotification: (notification: NewNotification) => void;
|
||||
}
|
||||
|
||||
const AppTable = (props: ComponentProps): JSX.Element => {
|
||||
export const AppTable = (props: Props): JSX.Element => {
|
||||
const {
|
||||
apps: { apps },
|
||||
config: { config },
|
||||
} = useSelector((state: State) => state);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { pinApp, deleteApp, reorderApps, updateConfig, createNotification } =
|
||||
bindActionCreators(actionCreators, dispatch);
|
||||
|
||||
const [localApps, setLocalApps] = useState<App[]>([]);
|
||||
const [isCustomOrder, setIsCustomOrder] = useState<boolean>(false);
|
||||
|
||||
// Copy apps array
|
||||
useEffect(() => {
|
||||
setLocalApps([...props.apps]);
|
||||
}, [props.apps]);
|
||||
setLocalApps([...apps]);
|
||||
}, [apps]);
|
||||
|
||||
// Check ordering
|
||||
useEffect(() => {
|
||||
const order = props.config.useOrdering;
|
||||
const order = config.useOrdering;
|
||||
|
||||
if (order === 'orderId') {
|
||||
setIsCustomOrder(true);
|
||||
|
@ -62,7 +59,7 @@ const AppTable = (props: ComponentProps): JSX.Element => {
|
|||
);
|
||||
|
||||
if (proceed) {
|
||||
props.deleteApp(app.id);
|
||||
deleteApp(app.id);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -79,7 +76,7 @@ const AppTable = (props: ComponentProps): JSX.Element => {
|
|||
|
||||
const dragEndHanlder = (result: DropResult): void => {
|
||||
if (!isCustomOrder) {
|
||||
props.createNotification({
|
||||
createNotification({
|
||||
title: 'Error',
|
||||
message: 'Custom order is disabled',
|
||||
});
|
||||
|
@ -95,7 +92,7 @@ const AppTable = (props: ComponentProps): JSX.Element => {
|
|||
tmpApps.splice(result.destination.index, 0, movedApp);
|
||||
|
||||
setLocalApps(tmpApps);
|
||||
props.reorderApps(tmpApps);
|
||||
reorderApps(tmpApps);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -178,9 +175,9 @@ const AppTable = (props: ComponentProps): JSX.Element => {
|
|||
</div>
|
||||
<div
|
||||
className={classes.TableAction}
|
||||
onClick={() => props.pinApp(app)}
|
||||
onClick={() => pinApp(app)}
|
||||
onKeyDown={(e) =>
|
||||
keyboardActionHandler(e, app, props.pinApp)
|
||||
keyboardActionHandler(e, app, pinApp)
|
||||
}
|
||||
tabIndex={0}
|
||||
>
|
||||
|
@ -208,20 +205,3 @@ const AppTable = (props: ComponentProps): JSX.Element => {
|
|||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
apps: state.app.apps,
|
||||
config: state.config.config,
|
||||
};
|
||||
};
|
||||
|
||||
const actions = {
|
||||
pinApp,
|
||||
deleteApp,
|
||||
reorderApps,
|
||||
updateConfig,
|
||||
createNotification,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, actions)(AppTable);
|
||||
|
|
|
@ -2,39 +2,37 @@ import { useEffect, useState } from 'react';
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
// Redux
|
||||
import { connect } from 'react-redux';
|
||||
import { getApps } from '../../store/actions';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
// Typescript
|
||||
import { App, GlobalState } from '../../interfaces';
|
||||
import { App } from '../../interfaces';
|
||||
|
||||
// CSS
|
||||
import classes from './Apps.module.css';
|
||||
|
||||
// UI
|
||||
import { Container } from '../UI/Layout/Layout';
|
||||
import Headline from '../UI/Headlines/Headline/Headline';
|
||||
import Spinner from '../UI/Spinner/Spinner';
|
||||
import ActionButton from '../UI/Buttons/ActionButton/ActionButton';
|
||||
import Modal from '../UI/Modal/Modal';
|
||||
import { Headline, Spinner, ActionButton, Modal, Container } from '../UI';
|
||||
|
||||
// Subcomponents
|
||||
import AppGrid from './AppGrid/AppGrid';
|
||||
import AppForm from './AppForm/AppForm';
|
||||
import AppTable from './AppTable/AppTable';
|
||||
import { AppGrid } from './AppGrid/AppGrid';
|
||||
import { AppForm } from './AppForm/AppForm';
|
||||
import { AppTable } from './AppTable/AppTable';
|
||||
|
||||
// Utils
|
||||
import { appTemplate } from '../../utility';
|
||||
import { State } from '../../store/reducers';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../store';
|
||||
|
||||
interface ComponentProps {
|
||||
getApps: Function;
|
||||
apps: App[];
|
||||
loading: boolean;
|
||||
interface Props {
|
||||
searching: boolean;
|
||||
}
|
||||
|
||||
const Apps = (props: ComponentProps): JSX.Element => {
|
||||
const { getApps, apps, loading, searching = false } = props;
|
||||
export const Apps = (props: Props): JSX.Element => {
|
||||
const { apps, loading } = useSelector((state: State) => state.apps);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { getApps } = bindActionCreators(actionCreators, dispatch);
|
||||
|
||||
const [modalIsOpen, setModalIsOpen] = useState(false);
|
||||
const [isInEdit, setIsInEdit] = useState(false);
|
||||
|
@ -95,12 +93,3 @@ const Apps = (props: ComponentProps): JSX.Element => {
|
|||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
apps: state.app.apps,
|
||||
loading: state.app.loading,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, { getApps })(Apps);
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
import { Bookmark, Category, Config, GlobalState } from '../../../interfaces';
|
||||
import { Fragment } from 'react';
|
||||
|
||||
import { useSelector } from 'react-redux';
|
||||
import { State } from '../../../store/reducers';
|
||||
|
||||
import { Bookmark, Category } from '../../../interfaces';
|
||||
|
||||
import classes from './BookmarkCard.module.css';
|
||||
|
||||
import Icon from '../../UI/Icons/Icon/Icon';
|
||||
import { iconParser, urlParser } from '../../../utility';
|
||||
import { Fragment } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Icon } from '../../UI';
|
||||
|
||||
interface ComponentProps {
|
||||
import { iconParser, urlParser } from '../../../utility';
|
||||
|
||||
interface Props {
|
||||
category: Category;
|
||||
config: Config;
|
||||
}
|
||||
|
||||
const BookmarkCard = (props: ComponentProps): JSX.Element => {
|
||||
export const BookmarkCard = (props: Props): JSX.Element => {
|
||||
const { config } = useSelector((state: State) => state.config);
|
||||
|
||||
return (
|
||||
<div className={classes.BookmarkCard}>
|
||||
<h3>{props.category.name}</h3>
|
||||
|
@ -56,7 +62,7 @@ const BookmarkCard = (props: ComponentProps): JSX.Element => {
|
|||
return (
|
||||
<a
|
||||
href={redirectUrl}
|
||||
target={props.config.bookmarksSameTab ? '' : '_blank'}
|
||||
target={config.bookmarksSameTab ? '' : '_blank'}
|
||||
rel="noreferrer"
|
||||
key={`bookmark-${bookmark.id}`}
|
||||
>
|
||||
|
@ -69,11 +75,3 @@ const BookmarkCard = (props: ComponentProps): JSX.Element => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
config: state.config.config,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(BookmarkCard);
|
||||
|
|
|
@ -8,94 +8,68 @@ import {
|
|||
} from 'react';
|
||||
|
||||
// Redux
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
getCategories,
|
||||
addCategory,
|
||||
addBookmark,
|
||||
updateCategory,
|
||||
updateBookmark,
|
||||
createNotification,
|
||||
} from '../../../store/actions';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
// Typescript
|
||||
import {
|
||||
Bookmark,
|
||||
Category,
|
||||
GlobalState,
|
||||
NewBookmark,
|
||||
NewCategory,
|
||||
NewNotification,
|
||||
} from '../../../interfaces';
|
||||
import { ContentType } from '../Bookmarks';
|
||||
|
||||
// UI
|
||||
import ModalForm from '../../UI/Forms/ModalForm/ModalForm';
|
||||
import InputGroup from '../../UI/Forms/InputGroup/InputGroup';
|
||||
import Button from '../../UI/Buttons/Button/Button';
|
||||
import { ModalForm, InputGroup, Button } from '../../UI';
|
||||
|
||||
// CSS
|
||||
import classes from './BookmarkForm.module.css';
|
||||
import { newBookmarkTemplate, newCategoryTemplate } from '../../../utility';
|
||||
import { State } from '../../../store/reducers';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../../store';
|
||||
|
||||
interface ComponentProps {
|
||||
interface Props {
|
||||
modalHandler: () => void;
|
||||
contentType: ContentType;
|
||||
categories: Category[];
|
||||
category?: Category;
|
||||
bookmark?: Bookmark;
|
||||
addCategory: (formData: NewCategory) => void;
|
||||
addBookmark: (formData: NewBookmark | FormData) => void;
|
||||
updateCategory: (id: number, formData: NewCategory) => void;
|
||||
updateBookmark: (
|
||||
id: number,
|
||||
formData: NewBookmark | FormData,
|
||||
category: {
|
||||
prev: number;
|
||||
curr: number;
|
||||
}
|
||||
) => void;
|
||||
createNotification: (notification: NewNotification) => void;
|
||||
}
|
||||
|
||||
const BookmarkForm = (props: ComponentProps): JSX.Element => {
|
||||
export const BookmarkForm = (props: Props): JSX.Element => {
|
||||
const { categories } = useSelector((state: State) => state.bookmarks);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
addCategory,
|
||||
addBookmark,
|
||||
updateCategory,
|
||||
updateBookmark,
|
||||
createNotification,
|
||||
} = bindActionCreators(actionCreators, dispatch);
|
||||
|
||||
const [useCustomIcon, toggleUseCustomIcon] = useState<boolean>(false);
|
||||
const [customIcon, setCustomIcon] = useState<File | null>(null);
|
||||
const [categoryName, setCategoryName] = useState<NewCategory>({
|
||||
name: '',
|
||||
});
|
||||
const [categoryName, setCategoryName] =
|
||||
useState<NewCategory>(newCategoryTemplate);
|
||||
|
||||
const [formData, setFormData] = useState<NewBookmark>({
|
||||
name: '',
|
||||
url: '',
|
||||
categoryId: -1,
|
||||
icon: '',
|
||||
});
|
||||
const [formData, setFormData] = useState<NewBookmark>(newBookmarkTemplate);
|
||||
|
||||
// Load category data if provided for editing
|
||||
useEffect(() => {
|
||||
if (props.category) {
|
||||
setCategoryName({ name: props.category.name });
|
||||
setCategoryName({ ...props.category });
|
||||
} else {
|
||||
setCategoryName({ name: '' });
|
||||
setCategoryName(newCategoryTemplate);
|
||||
}
|
||||
}, [props.category]);
|
||||
|
||||
// Load bookmark data if provided for editing
|
||||
useEffect(() => {
|
||||
if (props.bookmark) {
|
||||
setFormData({
|
||||
name: props.bookmark.name,
|
||||
url: props.bookmark.url,
|
||||
categoryId: props.bookmark.categoryId,
|
||||
icon: props.bookmark.icon,
|
||||
});
|
||||
setFormData({ ...props.bookmark });
|
||||
} else {
|
||||
setFormData({
|
||||
name: '',
|
||||
url: '',
|
||||
categoryId: -1,
|
||||
icon: '',
|
||||
});
|
||||
setFormData(newBookmarkTemplate);
|
||||
}
|
||||
}, [props.bookmark]);
|
||||
|
||||
|
@ -118,12 +92,12 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => {
|
|||
// Add new
|
||||
if (props.contentType === ContentType.category) {
|
||||
// Add category
|
||||
props.addCategory(categoryName);
|
||||
setCategoryName({ name: '' });
|
||||
addCategory(categoryName);
|
||||
setCategoryName(newCategoryTemplate);
|
||||
} else if (props.contentType === ContentType.bookmark) {
|
||||
// Add bookmark
|
||||
if (formData.categoryId === -1) {
|
||||
props.createNotification({
|
||||
createNotification({
|
||||
title: 'Error',
|
||||
message: 'Please select category',
|
||||
});
|
||||
|
@ -132,16 +106,14 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => {
|
|||
|
||||
if (customIcon) {
|
||||
const data = createFormData();
|
||||
props.addBookmark(data);
|
||||
addBookmark(data);
|
||||
} else {
|
||||
props.addBookmark(formData);
|
||||
addBookmark(formData);
|
||||
}
|
||||
|
||||
setFormData({
|
||||
name: '',
|
||||
url: '',
|
||||
...newBookmarkTemplate,
|
||||
categoryId: formData.categoryId,
|
||||
icon: '',
|
||||
});
|
||||
|
||||
// setCustomIcon(null);
|
||||
|
@ -150,29 +122,24 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => {
|
|||
// Update
|
||||
if (props.contentType === ContentType.category && props.category) {
|
||||
// Update category
|
||||
props.updateCategory(props.category.id, categoryName);
|
||||
setCategoryName({ name: '' });
|
||||
updateCategory(props.category.id, categoryName);
|
||||
setCategoryName(newCategoryTemplate);
|
||||
} else if (props.contentType === ContentType.bookmark && props.bookmark) {
|
||||
// Update bookmark
|
||||
if (customIcon) {
|
||||
const data = createFormData();
|
||||
props.updateBookmark(props.bookmark.id, data, {
|
||||
updateBookmark(props.bookmark.id, data, {
|
||||
prev: props.bookmark.categoryId,
|
||||
curr: formData.categoryId,
|
||||
});
|
||||
} else {
|
||||
props.updateBookmark(props.bookmark.id, formData, {
|
||||
updateBookmark(props.bookmark.id, formData, {
|
||||
prev: props.bookmark.categoryId,
|
||||
curr: formData.categoryId,
|
||||
});
|
||||
}
|
||||
|
||||
setFormData({
|
||||
name: '',
|
||||
url: '',
|
||||
categoryId: -1,
|
||||
icon: '',
|
||||
});
|
||||
setFormData(newBookmarkTemplate);
|
||||
|
||||
setCustomIcon(null);
|
||||
}
|
||||
|
@ -231,7 +198,9 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => {
|
|||
placeholder="Social Media"
|
||||
required
|
||||
value={categoryName.name}
|
||||
onChange={(e) => setCategoryName({ name: e.target.value })}
|
||||
onChange={(e) =>
|
||||
setCategoryName({ name: e.target.value, isPublic: !!!!!false })
|
||||
}
|
||||
/>
|
||||
</InputGroup>
|
||||
</Fragment>
|
||||
|
@ -249,6 +218,7 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => {
|
|||
onChange={(e) => inputChangeHandler(e)}
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup>
|
||||
<label htmlFor="url">Bookmark URL</label>
|
||||
<input
|
||||
|
@ -271,6 +241,7 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => {
|
|||
</a>
|
||||
</span>
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup>
|
||||
<label htmlFor="categoryId">Bookmark Category</label>
|
||||
<select
|
||||
|
@ -281,7 +252,7 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => {
|
|||
value={formData.categoryId}
|
||||
>
|
||||
<option value={-1}>Select category</option>
|
||||
{props.categories.map((category: Category): JSX.Element => {
|
||||
{categories.map((category: Category): JSX.Element => {
|
||||
return (
|
||||
<option key={category.id} value={category.id}>
|
||||
{category.name}
|
||||
|
@ -290,6 +261,7 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => {
|
|||
})}
|
||||
</select>
|
||||
</InputGroup>
|
||||
|
||||
{!useCustomIcon ? (
|
||||
// mdi
|
||||
<InputGroup>
|
||||
|
@ -344,20 +316,3 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => {
|
|||
</ModalForm>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
categories: state.bookmark.categories,
|
||||
};
|
||||
};
|
||||
|
||||
const dispatchMap = {
|
||||
getCategories,
|
||||
addCategory,
|
||||
addBookmark,
|
||||
updateCategory,
|
||||
updateBookmark,
|
||||
createNotification,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, dispatchMap)(BookmarkForm);
|
||||
|
|
|
@ -4,15 +4,15 @@ import classes from './BookmarkGrid.module.css';
|
|||
|
||||
import { Category } from '../../../interfaces';
|
||||
|
||||
import BookmarkCard from '../BookmarkCard/BookmarkCard';
|
||||
import { BookmarkCard } from '../BookmarkCard/BookmarkCard';
|
||||
|
||||
interface ComponentProps {
|
||||
interface Props {
|
||||
categories: Category[];
|
||||
totalCategories?: number;
|
||||
searching: boolean;
|
||||
}
|
||||
|
||||
const BookmarkGrid = (props: ComponentProps): JSX.Element => {
|
||||
export const BookmarkGrid = (props: Props): JSX.Element => {
|
||||
let bookmarks: JSX.Element;
|
||||
|
||||
if (props.categories.length > 0) {
|
||||
|
@ -53,5 +53,3 @@ const BookmarkGrid = (props: ComponentProps): JSX.Element => {
|
|||
|
||||
return bookmarks;
|
||||
};
|
||||
|
||||
export default BookmarkGrid;
|
||||
|
|
|
@ -8,45 +8,39 @@ import {
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
// Redux
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
pinCategory,
|
||||
deleteCategory,
|
||||
deleteBookmark,
|
||||
createNotification,
|
||||
reorderCategories,
|
||||
} from '../../../store/actions';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { State } from '../../../store/reducers';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../../store';
|
||||
|
||||
// Typescript
|
||||
import {
|
||||
Bookmark,
|
||||
Category,
|
||||
Config,
|
||||
GlobalState,
|
||||
NewNotification,
|
||||
} from '../../../interfaces';
|
||||
import { Bookmark, Category } from '../../../interfaces';
|
||||
import { ContentType } from '../Bookmarks';
|
||||
|
||||
// CSS
|
||||
import classes from './BookmarkTable.module.css';
|
||||
|
||||
// UI
|
||||
import Table from '../../UI/Table/Table';
|
||||
import Icon from '../../UI/Icons/Icon/Icon';
|
||||
import { Table, Icon } from '../../UI';
|
||||
|
||||
interface ComponentProps {
|
||||
interface Props {
|
||||
contentType: ContentType;
|
||||
categories: Category[];
|
||||
config: Config;
|
||||
pinCategory: (category: Category) => void;
|
||||
deleteCategory: (id: number) => void;
|
||||
updateHandler: (data: Category | Bookmark) => void;
|
||||
deleteBookmark: (bookmarkId: number, categoryId: number) => void;
|
||||
createNotification: (notification: NewNotification) => void;
|
||||
reorderCategories: (categories: Category[]) => void;
|
||||
}
|
||||
|
||||
const BookmarkTable = (props: ComponentProps): JSX.Element => {
|
||||
export const BookmarkTable = (props: Props): JSX.Element => {
|
||||
const { config } = useSelector((state: State) => state.config);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
pinCategory,
|
||||
deleteCategory,
|
||||
deleteBookmark,
|
||||
createNotification,
|
||||
reorderCategories,
|
||||
} = bindActionCreators(actionCreators, dispatch);
|
||||
|
||||
const [localCategories, setLocalCategories] = useState<Category[]>([]);
|
||||
const [isCustomOrder, setIsCustomOrder] = useState<boolean>(false);
|
||||
|
||||
|
@ -57,7 +51,7 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => {
|
|||
|
||||
// Check ordering
|
||||
useEffect(() => {
|
||||
const order = props.config.useOrdering;
|
||||
const order = config.useOrdering;
|
||||
|
||||
if (order === 'orderId') {
|
||||
setIsCustomOrder(true);
|
||||
|
@ -70,7 +64,7 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => {
|
|||
);
|
||||
|
||||
if (proceed) {
|
||||
props.deleteCategory(category.id);
|
||||
deleteCategory(category.id);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -80,7 +74,7 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => {
|
|||
);
|
||||
|
||||
if (proceed) {
|
||||
props.deleteBookmark(bookmark.id, bookmark.categoryId);
|
||||
deleteBookmark(bookmark.id, bookmark.categoryId);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -96,7 +90,7 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => {
|
|||
|
||||
const dragEndHanlder = (result: DropResult): void => {
|
||||
if (!isCustomOrder) {
|
||||
props.createNotification({
|
||||
createNotification({
|
||||
title: 'Error',
|
||||
message: 'Custom order is disabled',
|
||||
});
|
||||
|
@ -112,7 +106,7 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => {
|
|||
tmpCategories.splice(result.destination.index, 0, movedApp);
|
||||
|
||||
setLocalCategories(tmpCategories);
|
||||
props.reorderCategories(tmpCategories);
|
||||
reorderCategories(tmpCategories);
|
||||
};
|
||||
|
||||
if (props.contentType === ContentType.category) {
|
||||
|
@ -186,12 +180,12 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => {
|
|||
</div>
|
||||
<div
|
||||
className={classes.TableAction}
|
||||
onClick={() => props.pinCategory(category)}
|
||||
onClick={() => pinCategory(category)}
|
||||
onKeyDown={(e) =>
|
||||
keyboardActionHandler(
|
||||
e,
|
||||
category,
|
||||
props.pinCategory
|
||||
pinCategory
|
||||
)
|
||||
}
|
||||
tabIndex={0}
|
||||
|
@ -265,19 +259,3 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => {
|
|||
);
|
||||
}
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
config: state.config.config,
|
||||
};
|
||||
};
|
||||
|
||||
const actions = {
|
||||
pinCategory,
|
||||
deleteCategory,
|
||||
deleteBookmark,
|
||||
createNotification,
|
||||
reorderCategories,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, actions)(BookmarkTable);
|
||||
|
|
|
@ -1,25 +1,30 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import { getCategories } from '../../store/actions';
|
||||
|
||||
// Redux
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { State } from '../../store/reducers';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../store';
|
||||
|
||||
// Typescript
|
||||
import { Category, Bookmark } from '../../interfaces';
|
||||
|
||||
// CSS
|
||||
import classes from './Bookmarks.module.css';
|
||||
|
||||
import { Container } from '../UI/Layout/Layout';
|
||||
import Headline from '../UI/Headlines/Headline/Headline';
|
||||
import ActionButton from '../UI/Buttons/ActionButton/ActionButton';
|
||||
// UI
|
||||
import { Container, Headline, ActionButton, Spinner, Modal } from '../UI';
|
||||
|
||||
import BookmarkGrid from './BookmarkGrid/BookmarkGrid';
|
||||
import { Category, GlobalState, Bookmark } from '../../interfaces';
|
||||
import Spinner from '../UI/Spinner/Spinner';
|
||||
import Modal from '../UI/Modal/Modal';
|
||||
import BookmarkForm from './BookmarkForm/BookmarkForm';
|
||||
import BookmarkTable from './BookmarkTable/BookmarkTable';
|
||||
// Components
|
||||
import { BookmarkGrid } from './BookmarkGrid/BookmarkGrid';
|
||||
import { BookmarkForm } from './BookmarkForm/BookmarkForm';
|
||||
import { BookmarkTable } from './BookmarkTable/BookmarkTable';
|
||||
|
||||
interface ComponentProps {
|
||||
loading: boolean;
|
||||
categories: Category[];
|
||||
getCategories: () => void;
|
||||
// Utils
|
||||
import { bookmarkTemplate, categoryTemplate } from '../../utility';
|
||||
|
||||
interface Props {
|
||||
searching: boolean;
|
||||
}
|
||||
|
||||
|
@ -28,8 +33,15 @@ export enum ContentType {
|
|||
bookmark,
|
||||
}
|
||||
|
||||
const Bookmarks = (props: ComponentProps): JSX.Element => {
|
||||
const { getCategories, categories, loading, searching = false } = props;
|
||||
export const Bookmarks = (props: Props): JSX.Element => {
|
||||
const { loading, categories } = useSelector(
|
||||
(state: State) => state.bookmarks
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { getCategories } = bindActionCreators(actionCreators, dispatch);
|
||||
|
||||
const { searching = false } = props;
|
||||
|
||||
const [modalIsOpen, setModalIsOpen] = useState(false);
|
||||
const [formContentType, setFormContentType] = useState(ContentType.category);
|
||||
|
@ -38,24 +50,10 @@ const Bookmarks = (props: ComponentProps): JSX.Element => {
|
|||
ContentType.category
|
||||
);
|
||||
const [isInUpdate, setIsInUpdate] = useState(false);
|
||||
const [categoryInUpdate, setCategoryInUpdate] = useState<Category>({
|
||||
name: '',
|
||||
id: -1,
|
||||
isPinned: false,
|
||||
orderId: 0,
|
||||
bookmarks: [],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
const [bookmarkInUpdate, setBookmarkInUpdate] = useState<Bookmark>({
|
||||
name: '',
|
||||
url: '',
|
||||
categoryId: -1,
|
||||
icon: '',
|
||||
id: -1,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
const [categoryInUpdate, setCategoryInUpdate] =
|
||||
useState<Category>(categoryTemplate);
|
||||
const [bookmarkInUpdate, setBookmarkInUpdate] =
|
||||
useState<Bookmark>(bookmarkTemplate);
|
||||
|
||||
useEffect(() => {
|
||||
if (categories.length === 0) {
|
||||
|
@ -161,12 +159,3 @@ const Bookmarks = (props: ComponentProps): JSX.Element => {
|
|||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
loading: state.bookmark.loading,
|
||||
categories: state.bookmark.categories,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, { getCategories })(Bookmarks);
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Config, GlobalState } from '../../../interfaces';
|
||||
import WeatherWidget from '../../Widgets/WeatherWidget/WeatherWidget';
|
||||
import { getDateTime } from './functions/getDateTime';
|
||||
import { greeter } from './functions/greeter';
|
||||
|
||||
// CSS
|
||||
import classes from './Header.module.css';
|
||||
|
||||
interface Props {
|
||||
config: Config;
|
||||
}
|
||||
// Components
|
||||
import { WeatherWidget } from '../../Widgets/WeatherWidget/WeatherWidget';
|
||||
|
||||
const Header = (props: Props): JSX.Element => {
|
||||
// Utils
|
||||
import { getDateTime } from './functions/getDateTime';
|
||||
import { greeter } from './functions/greeter';
|
||||
|
||||
export const Header = (): JSX.Element => {
|
||||
const [dateTime, setDateTime] = useState<string>(getDateTime());
|
||||
const [greeting, setGreeting] = useState<string>(greeter());
|
||||
|
||||
|
@ -39,11 +39,3 @@ const Header = (props: Props): JSX.Element => {
|
|||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
config: state.config.config,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(Header);
|
||||
|
|
|
@ -2,47 +2,38 @@ import { useState, useEffect, Fragment } from 'react';
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
// Redux
|
||||
import { connect } from 'react-redux';
|
||||
import { getApps, getCategories } from '../../store/actions';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { State } from '../../store/reducers';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../store';
|
||||
|
||||
// Typescript
|
||||
import { GlobalState } from '../../interfaces/GlobalState';
|
||||
import { App, Category, Config } from '../../interfaces';
|
||||
import { App, Category } from '../../interfaces';
|
||||
|
||||
// UI
|
||||
import Icon from '../UI/Icons/Icon/Icon';
|
||||
import { Container } from '../UI/Layout/Layout';
|
||||
import SectionHeadline from '../UI/Headlines/SectionHeadline/SectionHeadline';
|
||||
import Spinner from '../UI/Spinner/Spinner';
|
||||
import { Icon, Container, SectionHeadline, Spinner } from '../UI';
|
||||
|
||||
// CSS
|
||||
import classes from './Home.module.css';
|
||||
|
||||
// Components
|
||||
import AppGrid from '../Apps/AppGrid/AppGrid';
|
||||
import BookmarkGrid from '../Bookmarks/BookmarkGrid/BookmarkGrid';
|
||||
import SearchBar from '../SearchBar/SearchBar';
|
||||
import Header from './Header/Header';
|
||||
import { AppGrid } from '../Apps/AppGrid/AppGrid';
|
||||
import { BookmarkGrid } from '../Bookmarks/BookmarkGrid/BookmarkGrid';
|
||||
import { SearchBar } from '../SearchBar/SearchBar';
|
||||
import { Header } from './Header/Header';
|
||||
|
||||
interface ComponentProps {
|
||||
getApps: Function;
|
||||
getCategories: Function;
|
||||
appsLoading: boolean;
|
||||
apps: App[];
|
||||
categoriesLoading: boolean;
|
||||
categories: Category[];
|
||||
config: Config;
|
||||
}
|
||||
|
||||
const Home = (props: ComponentProps): JSX.Element => {
|
||||
export const Home = (): JSX.Element => {
|
||||
const {
|
||||
getApps,
|
||||
apps,
|
||||
appsLoading,
|
||||
getCategories,
|
||||
categories,
|
||||
categoriesLoading,
|
||||
} = props;
|
||||
apps: { apps, loading: appsLoading },
|
||||
bookmarks: { categories, loading: bookmarksLoading },
|
||||
config: { config },
|
||||
} = useSelector((state: State) => state);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { getApps, getCategories } = bindActionCreators(
|
||||
actionCreators,
|
||||
dispatch
|
||||
);
|
||||
|
||||
// Local search query
|
||||
const [localSearch, setLocalSearch] = useState<null | string>(null);
|
||||
|
@ -90,7 +81,7 @@ const Home = (props: ComponentProps): JSX.Element => {
|
|||
|
||||
return (
|
||||
<Container>
|
||||
{!props.config.hideSearch ? (
|
||||
{!config.hideSearch ? (
|
||||
<SearchBar
|
||||
setLocalSearch={setLocalSearch}
|
||||
appSearchResult={appSearchResult}
|
||||
|
@ -100,9 +91,9 @@ const Home = (props: ComponentProps): JSX.Element => {
|
|||
<div></div>
|
||||
)}
|
||||
|
||||
{!props.config.hideHeader ? <Header /> : <div></div>}
|
||||
{!config.hideHeader ? <Header /> : <div></div>}
|
||||
|
||||
{!props.config.hideApps ? (
|
||||
{!config.hideApps ? (
|
||||
<Fragment>
|
||||
<SectionHeadline title="Applications" link="/applications" />
|
||||
{appsLoading ? (
|
||||
|
@ -124,10 +115,10 @@ const Home = (props: ComponentProps): JSX.Element => {
|
|||
<div></div>
|
||||
)}
|
||||
|
||||
{!props.config.hideCategories ? (
|
||||
{!config.hideCategories ? (
|
||||
<Fragment>
|
||||
<SectionHeadline title="Bookmarks" link="/bookmarks" />
|
||||
{categoriesLoading ? (
|
||||
{bookmarksLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<BookmarkGrid
|
||||
|
@ -151,15 +142,3 @@ const Home = (props: ComponentProps): JSX.Element => {
|
|||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
appsLoading: state.app.loading,
|
||||
apps: state.app.apps,
|
||||
categoriesLoading: state.bookmark.loading,
|
||||
categories: state.bookmark.categories,
|
||||
config: state.config.config,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, { getApps, getCategories })(Home);
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { GlobalState, Notification as _Notification } from '../../interfaces';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Notification as NotificationInterface } from '../../interfaces';
|
||||
|
||||
import classes from './NotificationCenter.module.css';
|
||||
|
||||
import Notification from '../UI/Notification/Notification';
|
||||
import { Notification } from '../UI';
|
||||
import { State } from '../../store/reducers';
|
||||
|
||||
interface ComponentProps {
|
||||
notifications: _Notification[];
|
||||
}
|
||||
export const NotificationCenter = (): JSX.Element => {
|
||||
const { notifications } = useSelector((state: State) => state.notification);
|
||||
|
||||
const NotificationCenter = (props: ComponentProps): JSX.Element => {
|
||||
return (
|
||||
<div
|
||||
className={classes.NotificationCenter}
|
||||
style={{ height: `${props.notifications.length * 75}px` }}
|
||||
style={{ height: `${notifications.length * 75}px` }}
|
||||
>
|
||||
{props.notifications.map((notification: _Notification) => {
|
||||
{notifications.map((notification: NotificationInterface) => {
|
||||
return (
|
||||
<Notification
|
||||
title={notification.title}
|
||||
|
@ -29,11 +28,3 @@ const NotificationCenter = (props: ComponentProps): JSX.Element => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
notifications: state.notification.notifications,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(NotificationCenter);
|
||||
|
|
|
@ -1,42 +1,33 @@
|
|||
import { useRef, useEffect, KeyboardEvent } from 'react';
|
||||
|
||||
// Redux
|
||||
import { connect } from 'react-redux';
|
||||
import { createNotification } from '../../store/actions';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
// Typescript
|
||||
import {
|
||||
App,
|
||||
Category,
|
||||
Config,
|
||||
GlobalState,
|
||||
NewNotification,
|
||||
} from '../../interfaces';
|
||||
import { App, Category } from '../../interfaces';
|
||||
|
||||
// CSS
|
||||
import classes from './SearchBar.module.css';
|
||||
|
||||
// Utils
|
||||
import { searchParser, urlParser, redirectUrl } from '../../utility';
|
||||
import { State } from '../../store/reducers';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../store';
|
||||
|
||||
interface ComponentProps {
|
||||
createNotification: (notification: NewNotification) => void;
|
||||
interface Props {
|
||||
setLocalSearch: (query: string) => void;
|
||||
appSearchResult: App[] | null;
|
||||
bookmarkSearchResult: Category[] | null;
|
||||
config: Config;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
const SearchBar = (props: ComponentProps): JSX.Element => {
|
||||
const {
|
||||
setLocalSearch,
|
||||
createNotification,
|
||||
config,
|
||||
loading,
|
||||
appSearchResult,
|
||||
bookmarkSearchResult,
|
||||
} = props;
|
||||
export const SearchBar = (props: Props): JSX.Element => {
|
||||
const { config, loading } = useSelector((state: State) => state.config);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { createNotification } = bindActionCreators(actionCreators, dispatch);
|
||||
|
||||
const { setLocalSearch, appSearchResult, bookmarkSearchResult } = props;
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(document.createElement('input'));
|
||||
|
||||
|
@ -126,12 +117,3 @@ const SearchBar = (props: ComponentProps): JSX.Element => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
config: state.config.config,
|
||||
loading: state.config.loading,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, { createNotification })(SearchBar);
|
||||
|
|
|
@ -1,34 +1,33 @@
|
|||
import { Fragment } from 'react';
|
||||
|
||||
import classes from './AppDetails.module.css';
|
||||
import Button from '../../UI/Buttons/Button/Button';
|
||||
import { Button } from '../../UI';
|
||||
import { checkVersion } from '../../../utility';
|
||||
|
||||
const AppDetails = (): JSX.Element => {
|
||||
export const AppDetails = (): JSX.Element => {
|
||||
return (
|
||||
<Fragment>
|
||||
<p className={classes.AppVersion}>
|
||||
<a
|
||||
href='https://github.com/pawelmalak/flame'
|
||||
target='_blank'
|
||||
rel='noreferrer'>
|
||||
href="https://github.com/pawelmalak/flame"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Flame
|
||||
</a>
|
||||
{' '}
|
||||
</a>{' '}
|
||||
version {process.env.REACT_APP_VERSION}
|
||||
</p>
|
||||
<p className={classes.AppVersion}>
|
||||
See changelog {' '}
|
||||
See changelog{' '}
|
||||
<a
|
||||
href='https://github.com/pawelmalak/flame/blob/master/CHANGELOG.md'
|
||||
target='_blank'
|
||||
rel='noreferrer'>
|
||||
href="https://github.com/pawelmalak/flame/blob/master/CHANGELOG.md"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
</p>
|
||||
<Button click={() => checkVersion(true)}>Check for updates</Button>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default AppDetails;
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,41 +1,28 @@
|
|||
import { useState, useEffect, ChangeEvent, FormEvent } from 'react';
|
||||
|
||||
// Redux
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
createNotification,
|
||||
updateConfig,
|
||||
sortApps,
|
||||
sortCategories,
|
||||
} from '../../../store/actions';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
// Typescript
|
||||
import {
|
||||
Config,
|
||||
GlobalState,
|
||||
NewNotification,
|
||||
OtherSettingsForm,
|
||||
} from '../../../interfaces';
|
||||
import { OtherSettingsForm } from '../../../interfaces';
|
||||
|
||||
// UI
|
||||
import InputGroup from '../../UI/Forms/InputGroup/InputGroup';
|
||||
import Button from '../../UI/Buttons/Button/Button';
|
||||
import SettingsHeadline from '../../UI/Headlines/SettingsHeadline/SettingsHeadline';
|
||||
import { InputGroup, Button, SettingsHeadline } from '../../UI';
|
||||
|
||||
// Utils
|
||||
import { otherSettingsTemplate, inputHandler } from '../../../utility';
|
||||
import { State } from '../../../store/reducers';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../../store';
|
||||
|
||||
interface ComponentProps {
|
||||
createNotification: (notification: NewNotification) => void;
|
||||
updateConfig: (formData: OtherSettingsForm) => void;
|
||||
sortApps: () => void;
|
||||
sortCategories: () => void;
|
||||
loading: boolean;
|
||||
config: Config;
|
||||
}
|
||||
export const OtherSettings = (): JSX.Element => {
|
||||
const { loading, config } = useSelector((state: State) => state.config);
|
||||
|
||||
const OtherSettings = (props: ComponentProps): JSX.Element => {
|
||||
const { config } = props;
|
||||
const dispatch = useDispatch();
|
||||
const { updateConfig, sortApps, sortCategories } = bindActionCreators(
|
||||
actionCreators,
|
||||
dispatch
|
||||
);
|
||||
|
||||
// Initial state
|
||||
const [formData, setFormData] = useState<OtherSettingsForm>(
|
||||
|
@ -47,21 +34,21 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
setFormData({
|
||||
...config,
|
||||
});
|
||||
}, [props.loading]);
|
||||
}, [loading]);
|
||||
|
||||
// Form handler
|
||||
const formSubmitHandler = async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Save settings
|
||||
await props.updateConfig(formData);
|
||||
await updateConfig(formData);
|
||||
|
||||
// Update local page title
|
||||
document.title = formData.customTitle;
|
||||
|
||||
// Sort apps and categories with new settings
|
||||
props.sortApps();
|
||||
props.sortCategories();
|
||||
sortApps();
|
||||
sortCategories();
|
||||
};
|
||||
|
||||
// Input handler
|
||||
|
@ -338,19 +325,3 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
|||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
loading: state.config.loading,
|
||||
config: state.config.config,
|
||||
};
|
||||
};
|
||||
|
||||
const actions = {
|
||||
createNotification,
|
||||
updateConfig,
|
||||
sortApps,
|
||||
sortCategories,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, actions)(OtherSettings);
|
||||
|
|
|
@ -1,29 +1,31 @@
|
|||
import { Fragment, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
// Redux
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { State } from '../../../../store/reducers';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../../../store';
|
||||
|
||||
// Typescript
|
||||
import { Query } from '../../../../interfaces';
|
||||
|
||||
// CSS
|
||||
import classes from './CustomQueries.module.css';
|
||||
|
||||
import Modal from '../../../UI/Modal/Modal';
|
||||
import Icon from '../../../UI/Icons/Icon/Icon';
|
||||
import {
|
||||
Config,
|
||||
GlobalState,
|
||||
NewNotification,
|
||||
Query,
|
||||
} from '../../../../interfaces';
|
||||
import QueriesForm from './QueriesForm';
|
||||
import { deleteQuery, createNotification } from '../../../../store/actions';
|
||||
import Button from '../../../UI/Buttons/Button/Button';
|
||||
// UI
|
||||
import { Modal, Icon, Button } from '../../../UI';
|
||||
|
||||
interface Props {
|
||||
customQueries: Query[];
|
||||
deleteQuery: (prefix: string) => {};
|
||||
createNotification: (notification: NewNotification) => void;
|
||||
config: Config;
|
||||
}
|
||||
// Components
|
||||
import { QueriesForm } from './QueriesForm';
|
||||
|
||||
const CustomQueries = (props: Props): JSX.Element => {
|
||||
const { customQueries, deleteQuery, createNotification } = props;
|
||||
export const CustomQueries = (): JSX.Element => {
|
||||
const { customQueries, config } = useSelector((state: State) => state.config);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { deleteQuery, createNotification } = bindActionCreators(
|
||||
actionCreators,
|
||||
dispatch
|
||||
);
|
||||
|
||||
const [modalIsOpen, setModalIsOpen] = useState(false);
|
||||
const [editableQuery, setEditableQuery] = useState<Query | null>(null);
|
||||
|
@ -34,7 +36,7 @@ const CustomQueries = (props: Props): JSX.Element => {
|
|||
};
|
||||
|
||||
const deleteHandler = (query: Query) => {
|
||||
const currentProvider = props.config.defaultSearchProvider;
|
||||
const currentProvider = config.defaultSearchProvider;
|
||||
const isCurrent = currentProvider === query.prefix;
|
||||
|
||||
if (isCurrent) {
|
||||
|
@ -105,14 +107,3 @@ const CustomQueries = (props: Props): JSX.Element => {
|
|||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
customQueries: state.config.customQueries,
|
||||
config: state.config.config,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, { deleteQuery, createNotification })(
|
||||
CustomQueries
|
||||
);
|
||||
|
|
|
@ -1,20 +1,26 @@
|
|||
import { ChangeEvent, FormEvent, useState, useEffect } from 'react';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../../../store';
|
||||
|
||||
import { Query } from '../../../../interfaces';
|
||||
import Button from '../../../UI/Buttons/Button/Button';
|
||||
import InputGroup from '../../../UI/Forms/InputGroup/InputGroup';
|
||||
import ModalForm from '../../../UI/Forms/ModalForm/ModalForm';
|
||||
import { connect } from 'react-redux';
|
||||
import { addQuery, updateQuery } from '../../../../store/actions';
|
||||
|
||||
import { Button, InputGroup, ModalForm } from '../../../UI';
|
||||
|
||||
interface Props {
|
||||
modalHandler: () => void;
|
||||
addQuery: (query: Query) => {};
|
||||
updateQuery: (query: Query, Oldprefix: string) => {};
|
||||
query?: Query;
|
||||
}
|
||||
|
||||
const QueriesForm = (props: Props): JSX.Element => {
|
||||
const { modalHandler, addQuery, updateQuery, query } = props;
|
||||
export const QueriesForm = (props: Props): JSX.Element => {
|
||||
const dispatch = useDispatch();
|
||||
const { addQuery, updateQuery } = bindActionCreators(
|
||||
actionCreators,
|
||||
dispatch
|
||||
);
|
||||
|
||||
const { modalHandler, query } = props;
|
||||
|
||||
const [formData, setFormData] = useState<Query>({
|
||||
name: '',
|
||||
|
@ -77,6 +83,7 @@ const QueriesForm = (props: Props): JSX.Element => {
|
|||
onChange={(e) => inputChangeHandler(e)}
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup>
|
||||
<label htmlFor="name">Prefix</label>
|
||||
<input
|
||||
|
@ -89,6 +96,7 @@ const QueriesForm = (props: Props): JSX.Element => {
|
|||
onChange={(e) => inputChangeHandler(e)}
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup>
|
||||
<label htmlFor="name">Query Template</label>
|
||||
<input
|
||||
|
@ -101,9 +109,8 @@ const QueriesForm = (props: Props): JSX.Element => {
|
|||
onChange={(e) => inputChangeHandler(e)}
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
{query ? <Button>Update provider</Button> : <Button>Add provider</Button>}
|
||||
</ModalForm>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(null, { addQuery, updateQuery })(QueriesForm);
|
||||
|
|
|
@ -1,58 +1,49 @@
|
|||
// React
|
||||
import { useState, useEffect, FormEvent, ChangeEvent, Fragment } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
// State
|
||||
import { createNotification, updateConfig } from '../../../store/actions';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
// Typescript
|
||||
import {
|
||||
Config,
|
||||
GlobalState,
|
||||
NewNotification,
|
||||
Query,
|
||||
SearchForm,
|
||||
} from '../../../interfaces';
|
||||
import { Query, SearchForm } from '../../../interfaces';
|
||||
|
||||
// Components
|
||||
import CustomQueries from './CustomQueries/CustomQueries';
|
||||
import { CustomQueries } from './CustomQueries/CustomQueries';
|
||||
|
||||
// UI
|
||||
import Button from '../../UI/Buttons/Button/Button';
|
||||
import SettingsHeadline from '../../UI/Headlines/SettingsHeadline/SettingsHeadline';
|
||||
import InputGroup from '../../UI/Forms/InputGroup/InputGroup';
|
||||
import { Button, SettingsHeadline, InputGroup } from '../../UI';
|
||||
|
||||
// Utils
|
||||
import { inputHandler, searchSettingsTemplate } from '../../../utility';
|
||||
|
||||
// Data
|
||||
import { queries } from '../../../utility/searchQueries.json';
|
||||
import { State } from '../../../store/reducers';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../../store';
|
||||
|
||||
interface Props {
|
||||
createNotification: (notification: NewNotification) => void;
|
||||
updateConfig: (formData: SearchForm) => void;
|
||||
loading: boolean;
|
||||
customQueries: Query[];
|
||||
config: Config;
|
||||
}
|
||||
export const SearchSettings = (): JSX.Element => {
|
||||
const { loading, customQueries, config } = useSelector(
|
||||
(state: State) => state.config
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { updateConfig } = bindActionCreators(actionCreators, dispatch);
|
||||
|
||||
const SearchSettings = (props: Props): JSX.Element => {
|
||||
// Initial state
|
||||
const [formData, setFormData] = useState<SearchForm>(searchSettingsTemplate);
|
||||
|
||||
// Get config
|
||||
useEffect(() => {
|
||||
setFormData({
|
||||
...props.config,
|
||||
...config,
|
||||
});
|
||||
}, [props.loading]);
|
||||
}, [loading]);
|
||||
|
||||
// Form handler
|
||||
const formSubmitHandler = async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Save settings
|
||||
await props.updateConfig(formData);
|
||||
await updateConfig(formData);
|
||||
};
|
||||
|
||||
// Input handler
|
||||
|
@ -84,7 +75,7 @@ const SearchSettings = (props: Props): JSX.Element => {
|
|||
value={formData.defaultSearchProvider}
|
||||
onChange={(e) => inputChangeHandler(e)}
|
||||
>
|
||||
{[...queries, ...props.customQueries].map((query: Query, idx) => {
|
||||
{[...queries, ...customQueries].map((query: Query, idx) => {
|
||||
const isCustom = idx >= queries.length;
|
||||
|
||||
return (
|
||||
|
@ -95,6 +86,7 @@ const SearchSettings = (props: Props): JSX.Element => {
|
|||
})}
|
||||
</select>
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup>
|
||||
<label htmlFor="searchSameTab">
|
||||
Open search results in the same tab
|
||||
|
@ -109,6 +101,7 @@ const SearchSettings = (props: Props): JSX.Element => {
|
|||
<option value={0}>False</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup>
|
||||
<label htmlFor="hideSearch">Hide search bar</label>
|
||||
<select
|
||||
|
@ -121,6 +114,7 @@ const SearchSettings = (props: Props): JSX.Element => {
|
|||
<option value={0}>False</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup>
|
||||
<label htmlFor="disableAutofocus">Disable search bar autofocus</label>
|
||||
<select
|
||||
|
@ -133,6 +127,7 @@ const SearchSettings = (props: Props): JSX.Element => {
|
|||
<option value={0}>False</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
|
||||
<Button>Save changes</Button>
|
||||
</form>
|
||||
|
||||
|
@ -142,18 +137,3 @@ const SearchSettings = (props: Props): JSX.Element => {
|
|||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
loading: state.config.loading,
|
||||
customQueries: state.config.customQueries,
|
||||
config: state.config.config,
|
||||
};
|
||||
};
|
||||
|
||||
const actions = {
|
||||
createNotification,
|
||||
updateConfig,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, actions)(SearchSettings);
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
//
|
||||
import { NavLink, Link, Switch, Route } from 'react-router-dom';
|
||||
|
||||
// Typescript
|
||||
|
@ -8,21 +7,20 @@ import { Route as SettingsRoute } from '../../interfaces';
|
|||
import classes from './Settings.module.css';
|
||||
|
||||
// Components
|
||||
import Themer from '../Themer/Themer';
|
||||
import WeatherSettings from './WeatherSettings/WeatherSettings';
|
||||
import OtherSettings from './OtherSettings/OtherSettings';
|
||||
import AppDetails from './AppDetails/AppDetails';
|
||||
import StyleSettings from './StyleSettings/StyleSettings';
|
||||
import SearchSettings from './SearchSettings/SearchSettings';
|
||||
import { Themer } from '../Themer/Themer';
|
||||
import { WeatherSettings } from './WeatherSettings/WeatherSettings';
|
||||
import { OtherSettings } from './OtherSettings/OtherSettings';
|
||||
import { AppDetails } from './AppDetails/AppDetails';
|
||||
import { StyleSettings } from './StyleSettings/StyleSettings';
|
||||
import { SearchSettings } from './SearchSettings/SearchSettings';
|
||||
|
||||
// UI
|
||||
import { Container } from '../UI/Layout/Layout';
|
||||
import Headline from '../UI/Headlines/Headline/Headline';
|
||||
import { Container, Headline } from '../UI';
|
||||
|
||||
// Data
|
||||
import { routes } from './settings.json';
|
||||
|
||||
const Settings = (): JSX.Element => {
|
||||
export const Settings = (): JSX.Element => {
|
||||
return (
|
||||
<Container>
|
||||
<Headline title="Settings" subtitle={<Link to="/">Go back</Link>} />
|
||||
|
@ -57,5 +55,3 @@ const Settings = (): JSX.Element => {
|
|||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
|
|
@ -2,54 +2,55 @@ import { useState, useEffect, ChangeEvent, FormEvent } from 'react';
|
|||
import axios from 'axios';
|
||||
|
||||
// Redux
|
||||
import { connect } from 'react-redux';
|
||||
import { createNotification } from '../../../store/actions';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../../store';
|
||||
|
||||
// Typescript
|
||||
import { ApiResponse, NewNotification } from '../../../interfaces';
|
||||
import { ApiResponse } from '../../../interfaces';
|
||||
|
||||
// UI
|
||||
import InputGroup from '../../UI/Forms/InputGroup/InputGroup';
|
||||
import Button from '../../UI/Buttons/Button/Button';
|
||||
import { InputGroup, Button } from '../../UI';
|
||||
|
||||
interface ComponentProps {
|
||||
createNotification: (notification: NewNotification) => void;
|
||||
}
|
||||
export const StyleSettings = (): JSX.Element => {
|
||||
const dispatch = useDispatch();
|
||||
const { createNotification } = bindActionCreators(actionCreators, dispatch);
|
||||
|
||||
const StyleSettings = (props: ComponentProps): JSX.Element => {
|
||||
const [customStyles, setCustomStyles] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
axios.get<ApiResponse<string>>('/api/config/0/css')
|
||||
.then(data => setCustomStyles(data.data.data))
|
||||
.catch(err => console.log(err.response));
|
||||
}, [])
|
||||
axios
|
||||
.get<ApiResponse<string>>('/api/config/0/css')
|
||||
.then((data) => setCustomStyles(data.data.data))
|
||||
.catch((err) => console.log(err.response));
|
||||
}, []);
|
||||
|
||||
const inputChangeHandler = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
e.preventDefault();
|
||||
setCustomStyles(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
const formSubmitHandler = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
axios.put<ApiResponse<{}>>('/api/config/0/css', { styles: customStyles })
|
||||
axios
|
||||
.put<ApiResponse<{}>>('/api/config/0/css', { styles: customStyles })
|
||||
.then(() => {
|
||||
props.createNotification({
|
||||
createNotification({
|
||||
title: 'Success',
|
||||
message: 'CSS saved. Reload page to see changes'
|
||||
})
|
||||
message: 'CSS saved. Reload page to see changes',
|
||||
});
|
||||
})
|
||||
.catch(err => console.log(err.response));
|
||||
}
|
||||
.catch((err) => console.log(err.response));
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={(e) => formSubmitHandler(e)}>
|
||||
<InputGroup>
|
||||
<label htmlFor='customStyles'>Custom CSS</label>
|
||||
<label htmlFor="customStyles">Custom CSS</label>
|
||||
<textarea
|
||||
id='customStyles'
|
||||
name='customStyles'
|
||||
id="customStyles"
|
||||
name="customStyles"
|
||||
value={customStyles}
|
||||
onChange={(e) => inputChangeHandler(e)}
|
||||
spellCheck={false}
|
||||
|
@ -57,7 +58,5 @@ const StyleSettings = (props: ComponentProps): JSX.Element => {
|
|||
</InputGroup>
|
||||
<Button>Save CSS</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(null, { createNotification })(StyleSettings);
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,34 +2,29 @@ import { useState, ChangeEvent, useEffect, FormEvent } from 'react';
|
|||
import axios from 'axios';
|
||||
|
||||
// Redux
|
||||
import { connect } from 'react-redux';
|
||||
import { createNotification, updateConfig } from '../../../store/actions';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../../store';
|
||||
|
||||
// Typescript
|
||||
import {
|
||||
ApiResponse,
|
||||
Config,
|
||||
GlobalState,
|
||||
NewNotification,
|
||||
Weather,
|
||||
WeatherForm,
|
||||
} from '../../../interfaces';
|
||||
import { ApiResponse, Weather, WeatherForm } from '../../../interfaces';
|
||||
|
||||
// UI
|
||||
import InputGroup from '../../UI/Forms/InputGroup/InputGroup';
|
||||
import Button from '../../UI/Buttons/Button/Button';
|
||||
import { InputGroup, Button } from '../../UI';
|
||||
|
||||
// Utils
|
||||
import { inputHandler, weatherSettingsTemplate } from '../../../utility';
|
||||
import { State } from '../../../store/reducers';
|
||||
|
||||
interface ComponentProps {
|
||||
createNotification: (notification: NewNotification) => void;
|
||||
updateConfig: (formData: WeatherForm) => void;
|
||||
loading: boolean;
|
||||
config: Config;
|
||||
}
|
||||
export const WeatherSettings = (): JSX.Element => {
|
||||
const { loading, config } = useSelector((state: State) => state.config);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { createNotification, updateConfig } = bindActionCreators(
|
||||
actionCreators,
|
||||
dispatch
|
||||
);
|
||||
|
||||
const WeatherSettings = (props: ComponentProps): JSX.Element => {
|
||||
// Initial state
|
||||
const [formData, setFormData] = useState<WeatherForm>(
|
||||
weatherSettingsTemplate
|
||||
|
@ -38,9 +33,9 @@ const WeatherSettings = (props: ComponentProps): JSX.Element => {
|
|||
// Get config
|
||||
useEffect(() => {
|
||||
setFormData({
|
||||
...props.config,
|
||||
...config,
|
||||
});
|
||||
}, [props.loading]);
|
||||
}, [loading]);
|
||||
|
||||
// Form handler
|
||||
const formSubmitHandler = async (e: FormEvent) => {
|
||||
|
@ -48,26 +43,26 @@ const WeatherSettings = (props: ComponentProps): JSX.Element => {
|
|||
|
||||
// Check for api key input
|
||||
if ((formData.lat || formData.long) && !formData.WEATHER_API_KEY) {
|
||||
props.createNotification({
|
||||
createNotification({
|
||||
title: 'Warning',
|
||||
message: 'API key is missing. Weather Module will NOT work',
|
||||
});
|
||||
}
|
||||
|
||||
// Save settings
|
||||
await props.updateConfig(formData);
|
||||
await updateConfig(formData);
|
||||
|
||||
// Update weather
|
||||
axios
|
||||
.get<ApiResponse<Weather>>('/api/weather/update')
|
||||
.then(() => {
|
||||
props.createNotification({
|
||||
createNotification({
|
||||
title: 'Success',
|
||||
message: 'Weather updated',
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
props.createNotification({
|
||||
createNotification({
|
||||
title: 'Error',
|
||||
message: err.response.data.error,
|
||||
});
|
||||
|
@ -108,6 +103,7 @@ const WeatherSettings = (props: ComponentProps): JSX.Element => {
|
|||
. Key is required for weather module to work.
|
||||
</span>
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup>
|
||||
<label htmlFor="lat">Location latitude</label>
|
||||
<input
|
||||
|
@ -131,6 +127,7 @@ const WeatherSettings = (props: ComponentProps): JSX.Element => {
|
|||
</a>
|
||||
</span>
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup>
|
||||
<label htmlFor="long">Location longitude</label>
|
||||
<input
|
||||
|
@ -144,6 +141,7 @@ const WeatherSettings = (props: ComponentProps): JSX.Element => {
|
|||
lang="en-150"
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup>
|
||||
<label htmlFor="isCelsius">Temperature unit</label>
|
||||
<select
|
||||
|
@ -156,18 +154,8 @@ const WeatherSettings = (props: ComponentProps): JSX.Element => {
|
|||
<option value={0}>Fahrenheit</option>
|
||||
</select>
|
||||
</InputGroup>
|
||||
|
||||
<Button>Save changes</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
loading: state.config.loading,
|
||||
config: state.config.config,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, { createNotification, updateConfig })(
|
||||
WeatherSettings
|
||||
);
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
import { Theme } from '../../interfaces/Theme';
|
||||
import classes from './ThemePreview.module.css';
|
||||
|
||||
interface ComponentProps {
|
||||
interface Props {
|
||||
theme: Theme;
|
||||
applyTheme: Function;
|
||||
}
|
||||
|
||||
const ThemePreview = (props: ComponentProps): JSX.Element => {
|
||||
export const ThemePreview = (props: Props): JSX.Element => {
|
||||
return (
|
||||
<div className={classes.ThemePreview} onClick={() => props.applyTheme(props.theme.name)}>
|
||||
<div
|
||||
className={classes.ThemePreview}
|
||||
onClick={() => props.applyTheme(props.theme.name)}
|
||||
>
|
||||
<div className={classes.ColorsPreview}>
|
||||
<div
|
||||
className={classes.ColorPreview}
|
||||
|
@ -25,7 +28,5 @@ const ThemePreview = (props: ComponentProps): JSX.Element => {
|
|||
</div>
|
||||
<p>{props.theme.name}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ThemePreview;
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,37 +1,29 @@
|
|||
import { Fragment } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../store';
|
||||
|
||||
import classes from './Themer.module.css';
|
||||
|
||||
import { themes } from './themes.json';
|
||||
import { Theme } from '../../interfaces/Theme';
|
||||
import ThemePreview from './ThemePreview';
|
||||
import { ThemePreview } from './ThemePreview';
|
||||
|
||||
import { setTheme } from '../../store/actions';
|
||||
|
||||
interface ComponentProps {
|
||||
setTheme: Function;
|
||||
}
|
||||
|
||||
const Themer = (props: ComponentProps): JSX.Element => {
|
||||
export const Themer = (): JSX.Element => {
|
||||
const dispatch = useDispatch();
|
||||
const { setTheme } = bindActionCreators(actionCreators, dispatch);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div>
|
||||
<div className={classes.ThemerGrid}>
|
||||
{themes.map((theme: Theme, idx: number): JSX.Element => (
|
||||
<ThemePreview
|
||||
key={idx}
|
||||
theme={theme}
|
||||
applyTheme={props.setTheme}
|
||||
/>
|
||||
))}
|
||||
{themes.map(
|
||||
(theme: Theme, idx: number): JSX.Element => (
|
||||
<ThemePreview key={idx} theme={theme} applyTheme={setTheme} />
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default connect(null, { setTheme })(Themer);
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,23 +2,21 @@ import { useState, useEffect, Fragment } from 'react';
|
|||
import axios from 'axios';
|
||||
|
||||
// Redux
|
||||
import { connect } from 'react-redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
// Typescript
|
||||
import { Weather, ApiResponse, GlobalState, Config } from '../../../interfaces';
|
||||
import { Weather, ApiResponse, Config } from '../../../interfaces';
|
||||
|
||||
// CSS
|
||||
import classes from './WeatherWidget.module.css';
|
||||
|
||||
// UI
|
||||
import WeatherIcon from '../../UI/Icons/WeatherIcon/WeatherIcon';
|
||||
import { WeatherIcon } from '../../UI';
|
||||
import { State } from '../../../store/reducers';
|
||||
|
||||
interface ComponentProps {
|
||||
configLoading: boolean;
|
||||
config: Config;
|
||||
}
|
||||
export const WeatherWidget = (): JSX.Element => {
|
||||
const { loading, config } = useSelector((state: State) => state.config);
|
||||
|
||||
const WeatherWidget = (props: ComponentProps): JSX.Element => {
|
||||
const [weather, setWeather] = useState<Weather>({
|
||||
externalLastUpdate: '',
|
||||
tempC: 0,
|
||||
|
@ -68,8 +66,8 @@ const WeatherWidget = (props: ComponentProps): JSX.Element => {
|
|||
return (
|
||||
<div className={classes.WeatherWidget}>
|
||||
{isLoading ||
|
||||
props.configLoading ||
|
||||
(props.config.WEATHER_API_KEY && weather.id > 0 && (
|
||||
loading ||
|
||||
(config.WEATHER_API_KEY && weather.id > 0 && (
|
||||
<Fragment>
|
||||
<div className={classes.WeatherIcon}>
|
||||
<WeatherIcon
|
||||
|
@ -78,7 +76,7 @@ const WeatherWidget = (props: ComponentProps): JSX.Element => {
|
|||
/>
|
||||
</div>
|
||||
<div className={classes.WeatherDetails}>
|
||||
{props.config.isCelsius ? (
|
||||
{config.isCelsius ? (
|
||||
<span>{weather.tempC}°C</span>
|
||||
) : (
|
||||
<span>{weather.tempF}°F</span>
|
||||
|
@ -90,12 +88,3 @@ const WeatherWidget = (props: ComponentProps): JSX.Element => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
configLoading: state.config.loading,
|
||||
config: state.config.config,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(WeatherWidget);
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
|
||||
import { Provider } from 'react-redux';
|
||||
import { store } from './store/store';
|
||||
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
);
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
import { State as AppState } from '../store/reducers/app';
|
||||
import { State as ThemeState } from '../store/reducers/theme';
|
||||
import { State as BookmarkState } from '../store/reducers/bookmark';
|
||||
import { State as NotificationState } from '../store/reducers/notification';
|
||||
import { State as ConfigState } from '../store/reducers/config';
|
||||
|
||||
export interface GlobalState {
|
||||
theme: ThemeState;
|
||||
app: AppState;
|
||||
bookmark: BookmarkState;
|
||||
notification: NotificationState;
|
||||
config: ConfigState;
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
export * from './App';
|
||||
export * from './Theme';
|
||||
export * from './GlobalState';
|
||||
export * from './Api';
|
||||
export * from './Weather';
|
||||
export * from './Bookmark';
|
||||
|
|
Loading…
Reference in a new issue