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