diff --git a/client/src/components/Apps/AppCard/AppCard.tsx b/client/src/components/Apps/AppCard/AppCard.tsx index 172a680..803e5dd 100644 --- a/client/src/components/Apps/AppCard/AppCard.tsx +++ b/client/src/components/Apps/AppCard/AppCard.tsx @@ -2,12 +2,13 @@ import classes from './AppCard.module.css'; import Icon from '../../UI/Icons/Icon/Icon'; import { iconParser, urlParser } from '../../../utility'; -import { App } from '../../../interfaces'; -import { searchConfig } from '../../../utility'; +import { App, Config, GlobalState } from '../../../interfaces'; +import { connect } from 'react-redux'; interface ComponentProps { app: App; pinHandler?: Function; + config: Config; } const AppCard = (props: ComponentProps): JSX.Element => { @@ -29,7 +30,7 @@ const AppCard = (props: ComponentProps): JSX.Element => {
@@ -41,8 +42,8 @@ const AppCard = (props: ComponentProps): JSX.Element => { return (
{iconEl}
@@ -54,4 +55,10 @@ const AppCard = (props: ComponentProps): JSX.Element => { ); }; -export default AppCard; +const mapStateToProps = (state: GlobalState) => { + return { + config: state.config.config, + }; +}; + +export default connect(mapStateToProps)(AppCard); diff --git a/client/src/components/Apps/AppTable/AppTable.tsx b/client/src/components/Apps/AppTable/AppTable.tsx index 6ef6e6c..3f68d76 100644 --- a/client/src/components/Apps/AppTable/AppTable.tsx +++ b/client/src/components/Apps/AppTable/AppTable.tsx @@ -1,13 +1,24 @@ import { Fragment, KeyboardEvent, useState, useEffect } from 'react'; -import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd'; +import { + DragDropContext, + Droppable, + Draggable, + DropResult, +} from 'react-beautiful-dnd'; import { Link } from 'react-router-dom'; // Redux import { connect } from 'react-redux'; -import { pinApp, deleteApp, reorderApps, updateConfig, createNotification } from '../../../store/actions'; +import { + pinApp, + deleteApp, + reorderApps, + updateConfig, + createNotification, +} from '../../../store/actions'; // Typescript -import { App, GlobalState, NewNotification } from '../../../interfaces'; +import { App, Config, GlobalState, NewNotification } from '../../../interfaces'; // CSS import classes from './AppTable.module.css'; @@ -16,11 +27,9 @@ import classes from './AppTable.module.css'; import Icon from '../../UI/Icons/Icon/Icon'; import Table from '../../UI/Table/Table'; -// Utils -import { searchConfig } from '../../../utility'; - interface ComponentProps { apps: App[]; + config: Config; pinApp: (app: App) => void; deleteApp: (id: number) => void; updateAppHandler: (app: App) => void; @@ -36,38 +45,44 @@ const AppTable = (props: ComponentProps): JSX.Element => { // Copy apps array useEffect(() => { setLocalApps([...props.apps]); - }, [props.apps]) + }, [props.apps]); // Check ordering useEffect(() => { - const order = searchConfig('useOrdering', ''); + const order = props.config.useOrdering; if (order === 'orderId') { setIsCustomOrder(true); } - }, []) + }, []); const deleteAppHandler = (app: App): void => { - const proceed = window.confirm(`Are you sure you want to delete ${app.name} at ${app.url} ?`); + const proceed = window.confirm( + `Are you sure you want to delete ${app.name} at ${app.url} ?` + ); if (proceed) { props.deleteApp(app.id); } - } + }; // Support keyboard navigation for actions - const keyboardActionHandler = (e: KeyboardEvent, app: App, handler: Function) => { + const keyboardActionHandler = ( + e: KeyboardEvent, + app: App, + handler: Function + ) => { if (e.key === 'Enter') { handler(app); } - } + }; const dragEndHanlder = (result: DropResult): void => { if (!isCustomOrder) { props.createNotification({ title: 'Error', - message: 'Custom order is disabled' - }) + message: 'Custom order is disabled', + }); return; } @@ -81,32 +96,39 @@ const AppTable = (props: ComponentProps): JSX.Element => { setLocalApps(tmpApps); props.reorderApps(tmpApps); - } + }; return (
- {isCustomOrder - ?

You can drag and drop single rows to reorder application

- :

Custom order is disabled. You can change it in settings

- } + {isCustomOrder ? ( +

You can drag and drop single rows to reorder application

+ ) : ( +

+ Custom order is disabled. You can change it in{' '} + settings +

+ )}
- + {(provided) => ( - +
{localApps.map((app: App, index): JSX.Element => { return ( - + {(provided, snapshot) => { const style = { - border: snapshot.isDragging ? '1px solid var(--color-accent)' : 'none', + border: snapshot.isDragging + ? '1px solid var(--color-accent)' + : 'none', borderRadius: '4px', ...provided.draggableProps.style, }; @@ -118,63 +140,85 @@ const AppTable = (props: ComponentProps): JSX.Element => { ref={provided.innerRef} style={style} > - - - + + + {!snapshot.isDragging && ( )} - ) + ); }} - ) + ); })}
{app.name}{app.url}{app.icon}{app.name}{app.url}{app.icon}
deleteAppHandler(app)} - onKeyDown={(e) => keyboardActionHandler(e, app, deleteAppHandler)} - tabIndex={0}> - + onKeyDown={(e) => + keyboardActionHandler( + e, + app, + deleteAppHandler + ) + } + tabIndex={0} + > +
props.updateAppHandler(app)} - onKeyDown={(e) => keyboardActionHandler(e, app, props.updateAppHandler)} - tabIndex={0}> - + onKeyDown={(e) => + keyboardActionHandler( + e, + app, + props.updateAppHandler + ) + } + tabIndex={0} + > +
props.pinApp(app)} - onKeyDown={(e) => keyboardActionHandler(e, app, props.pinApp)} - tabIndex={0}> - {app.isPinned - ? - : + onKeyDown={(e) => + keyboardActionHandler(e, app, props.pinApp) } + tabIndex={0} + > + {app.isPinned ? ( + + ) : ( + + )}
)}
- ) -} + ); +}; const mapStateToProps = (state: GlobalState) => { return { - apps: state.app.apps - } -} + apps: state.app.apps, + config: state.config.config, + }; +}; const actions = { pinApp, deleteApp, reorderApps, updateConfig, - createNotification -} + createNotification, +}; -export default connect(mapStateToProps, actions)(AppTable); \ No newline at end of file +export default connect(mapStateToProps, actions)(AppTable); diff --git a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx index b332a6f..93ead02 100644 --- a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx +++ b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx @@ -1,12 +1,14 @@ -import { Bookmark, Category } from '../../../interfaces'; +import { Bookmark, Category, Config, GlobalState } from '../../../interfaces'; import classes from './BookmarkCard.module.css'; import Icon from '../../UI/Icons/Icon/Icon'; -import { iconParser, urlParser, searchConfig } from '../../../utility'; +import { iconParser, urlParser } from '../../../utility'; import { Fragment } from 'react'; +import { connect } from 'react-redux'; interface ComponentProps { category: Category; + config: Config; } const BookmarkCard = (props: ComponentProps): JSX.Element => { @@ -54,7 +56,7 @@ const BookmarkCard = (props: ComponentProps): JSX.Element => { return (
@@ -68,4 +70,10 @@ const BookmarkCard = (props: ComponentProps): JSX.Element => { ); }; -export default BookmarkCard; +const mapStateToProps = (state: GlobalState) => { + return { + config: state.config.config, + }; +}; + +export default connect(mapStateToProps)(BookmarkCard); diff --git a/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx b/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx index 02779d5..90c34aa 100644 --- a/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx +++ b/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx @@ -1,13 +1,30 @@ import { KeyboardEvent, useState, useEffect, Fragment } from 'react'; -import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd'; +import { + DragDropContext, + Droppable, + Draggable, + DropResult, +} from 'react-beautiful-dnd'; import { Link } from 'react-router-dom'; // Redux import { connect } from 'react-redux'; -import { pinCategory, deleteCategory, deleteBookmark, createNotification, reorderCategories } from '../../../store/actions'; +import { + pinCategory, + deleteCategory, + deleteBookmark, + createNotification, + reorderCategories, +} from '../../../store/actions'; // Typescript -import { Bookmark, Category, NewNotification } from '../../../interfaces'; +import { + Bookmark, + Category, + Config, + GlobalState, + NewNotification, +} from '../../../interfaces'; import { ContentType } from '../Bookmarks'; // CSS @@ -17,12 +34,10 @@ import classes from './BookmarkTable.module.css'; import Table from '../../UI/Table/Table'; import Icon from '../../UI/Icons/Icon/Icon'; -// Utils -import { searchConfig } from '../../../utility'; - interface ComponentProps { contentType: ContentType; categories: Category[]; + config: Config; pinCategory: (category: Category) => void; deleteCategory: (id: number) => void; updateHandler: (data: Category | Bookmark) => void; @@ -38,45 +53,53 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => { // Copy categories array useEffect(() => { setLocalCategories([...props.categories]); - }, [props.categories]) + }, [props.categories]); // Check ordering useEffect(() => { - const order = searchConfig('useOrdering', ''); + const order = props.config.useOrdering; if (order === 'orderId') { setIsCustomOrder(true); } - }) + }); const deleteCategoryHandler = (category: Category): void => { - const proceed = window.confirm(`Are you sure you want to delete ${category.name}? It will delete ALL assigned bookmarks`); + const proceed = window.confirm( + `Are you sure you want to delete ${category.name}? It will delete ALL assigned bookmarks` + ); if (proceed) { props.deleteCategory(category.id); } - } + }; const deleteBookmarkHandler = (bookmark: Bookmark): void => { - const proceed = window.confirm(`Are you sure you want to delete ${bookmark.name}?`); + const proceed = window.confirm( + `Are you sure you want to delete ${bookmark.name}?` + ); if (proceed) { props.deleteBookmark(bookmark.id, bookmark.categoryId); } - } + }; - const keyboardActionHandler = (e: KeyboardEvent, category: Category, handler: Function) => { + const keyboardActionHandler = ( + e: KeyboardEvent, + category: Category, + handler: Function + ) => { if (e.key === 'Enter') { handler(category); } - } + }; const dragEndHanlder = (result: DropResult): void => { if (!isCustomOrder) { props.createNotification({ title: 'Error', - message: 'Custom order is disabled' - }) + message: 'Custom order is disabled', + }); return; } @@ -90,136 +113,171 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => { setLocalCategories(tmpCategories); props.reorderCategories(tmpCategories); - } + }; if (props.contentType === ContentType.category) { return (
- {isCustomOrder - ?

You can drag and drop single rows to reorder categories

- :

Custom order is disabled. You can change it in settings

- } + {isCustomOrder ? ( +

You can drag and drop single rows to reorder categories

+ ) : ( +

+ Custom order is disabled. You can change it in{' '} + settings +

+ )}
- + {(provided) => ( - - {localCategories.map((category: Category, index): JSX.Element => { - return ( - - {(provided, snapshot) => { - const style = { - border: snapshot.isDragging ? '1px solid var(--color-accent)' : 'none', - borderRadius: '4px', - ...provided.draggableProps.style, - }; +
+ {localCategories.map( + (category: Category, index): JSX.Element => { + return ( + + {(provided, snapshot) => { + const style = { + border: snapshot.isDragging + ? '1px solid var(--color-accent)' + : 'none', + borderRadius: '4px', + ...provided.draggableProps.style, + }; - return ( - - - {!snapshot.isDragging && ( - - )} - - ) - }} - - ) - })} + return ( + + + {!snapshot.isDragging && ( + + )} + + ); + }} + + ); + } + )}
{category.name} -
deleteCategoryHandler(category)} - onKeyDown={(e) => keyboardActionHandler(e, category, deleteCategoryHandler)} - tabIndex={0}> - -
-
props.updateHandler(category)} - tabIndex={0}> - -
-
props.pinCategory(category)} - onKeyDown={(e) => keyboardActionHandler(e, category, props.pinCategory)} - tabIndex={0}> - {category.isPinned - ? - : - } -
-
{category.name} +
+ deleteCategoryHandler(category) + } + onKeyDown={(e) => + keyboardActionHandler( + e, + category, + deleteCategoryHandler + ) + } + tabIndex={0} + > + +
+
+ props.updateHandler(category) + } + tabIndex={0} + > + +
+
props.pinCategory(category)} + onKeyDown={(e) => + keyboardActionHandler( + e, + category, + props.pinCategory + ) + } + tabIndex={0} + > + {category.isPinned ? ( + + ) : ( + + )} +
+
)}
- ) + ); } else { - const bookmarks: {bookmark: Bookmark, categoryName: string}[] = []; + const bookmarks: { bookmark: Bookmark; categoryName: string }[] = []; props.categories.forEach((category: Category) => { category.bookmarks.forEach((bookmark: Bookmark) => { bookmarks.push({ bookmark, - categoryName: category.name + categoryName: category.name, }); - }) - }) + }); + }); return ( - - {bookmarks.map((bookmark: {bookmark: Bookmark, categoryName: string}) => { - return ( - - - - - - - - ) - })} +
{bookmark.bookmark.name}{bookmark.bookmark.url}{bookmark.bookmark.icon}{bookmark.categoryName} -
deleteBookmarkHandler(bookmark.bookmark)} - tabIndex={0}> - -
-
props.updateHandler(bookmark.bookmark)} - tabIndex={0}> - -
-
+ {bookmarks.map( + (bookmark: { bookmark: Bookmark; categoryName: string }) => { + return ( + + + + + + + + ); + } + )}
{bookmark.bookmark.name}{bookmark.bookmark.url}{bookmark.bookmark.icon}{bookmark.categoryName} +
deleteBookmarkHandler(bookmark.bookmark)} + tabIndex={0} + > + +
+
props.updateHandler(bookmark.bookmark)} + tabIndex={0} + > + +
+
- ) + ); } -} +}; + +const mapStateToProps = (state: GlobalState) => { + return { + config: state.config.config, + }; +}; const actions = { pinCategory, deleteCategory, deleteBookmark, createNotification, - reorderCategories -} + reorderCategories, +}; -export default connect(null, actions)(BookmarkTable); \ No newline at end of file +export default connect(mapStateToProps, actions)(BookmarkTable); diff --git a/client/src/components/Home/Home.tsx b/client/src/components/Home/Home.tsx index fd711aa..18d81bc 100644 --- a/client/src/components/Home/Home.tsx +++ b/client/src/components/Home/Home.tsx @@ -7,7 +7,7 @@ import { getApps, getCategories } from '../../store/actions'; // Typescript import { GlobalState } from '../../interfaces/GlobalState'; -import { App, Category } from '../../interfaces'; +import { App, Category, Config } from '../../interfaces'; // UI import Icon from '../UI/Icons/Icon/Icon'; @@ -28,9 +28,6 @@ import SearchBar from '../SearchBar/SearchBar'; import { greeter } from './functions/greeter'; import { dateTime } from './functions/dateTime'; -// Utils -import { searchConfig } from '../../utility'; - interface ComponentProps { getApps: Function; getCategories: Function; @@ -38,6 +35,7 @@ interface ComponentProps { apps: App[]; categoriesLoading: boolean; categories: Category[]; + config: Config; } const Home = (props: ComponentProps): JSX.Element => { @@ -77,7 +75,7 @@ const Home = (props: ComponentProps): JSX.Element => { let interval: any; // Start interval only when hideHeader is false - if (searchConfig('hideHeader', 0) !== 1) { + if (!props.config.hideHeader) { interval = setInterval(() => { setHeader({ dateTime: dateTime(), @@ -103,13 +101,13 @@ const Home = (props: ComponentProps): JSX.Element => { return ( - {searchConfig('hideSearch', 0) !== 1 ? ( + {!props.config.hideSearch ? ( ) : (
)} - {searchConfig('hideHeader', 0) !== 1 ? ( + {!props.config.hideHeader ? (

{header.dateTime}

@@ -124,7 +122,7 @@ const Home = (props: ComponentProps): JSX.Element => {
)} - {searchConfig('hideApps', 0) !== 1 ? ( + {!props.config.hideApps ? ( {appsLoading ? ( @@ -148,7 +146,7 @@ const Home = (props: ComponentProps): JSX.Element => {
)} - {searchConfig('hideCategories', 0) !== 1 ? ( + {!props.config.hideCategories ? ( {categoriesLoading ? ( @@ -182,6 +180,7 @@ const mapStateToProps = (state: GlobalState) => { apps: state.app.apps, categoriesLoading: state.bookmark.loading, categories: state.bookmark.categories, + config: state.config.config, }; }; diff --git a/client/src/components/Settings/OtherSettings/OtherSettings.tsx b/client/src/components/Settings/OtherSettings/OtherSettings.tsx index c3525f8..3d82fa4 100644 --- a/client/src/components/Settings/OtherSettings/OtherSettings.tsx +++ b/client/src/components/Settings/OtherSettings/OtherSettings.tsx @@ -11,9 +11,10 @@ import { // Typescript import { + Config, GlobalState, NewNotification, - SettingsForm, + OtherSettingsForm, } from '../../../interfaces'; // UI @@ -22,50 +23,29 @@ import Button from '../../UI/Buttons/Button/Button'; import SettingsHeadline from '../../UI/Headlines/SettingsHeadline/SettingsHeadline'; // Utils -import { searchConfig } from '../../../utility'; +import { otherSettingsTemplate, inputHandler } from '../../../utility'; interface ComponentProps { createNotification: (notification: NewNotification) => void; - updateConfig: (formData: SettingsForm) => void; + updateConfig: (formData: OtherSettingsForm) => void; sortApps: () => void; sortCategories: () => void; loading: boolean; + config: Config; } const OtherSettings = (props: ComponentProps): JSX.Element => { + const { config } = props; + // Initial state - const [formData, setFormData] = useState({ - customTitle: document.title, - pinAppsByDefault: 1, - pinCategoriesByDefault: 1, - hideHeader: 0, - hideApps: 0, - hideCategories: 0, - useOrdering: 'createdAt', - appsSameTab: 0, - bookmarksSameTab: 0, - dockerApps: 1, - dockerHost: 'localhost', - kubernetesApps: 1, - unpinStoppedApps: 1, - }); + const [formData, setFormData] = useState( + otherSettingsTemplate + ); // Get config useEffect(() => { setFormData({ - customTitle: searchConfig('customTitle', 'Flame'), - pinAppsByDefault: searchConfig('pinAppsByDefault', 1), - pinCategoriesByDefault: searchConfig('pinCategoriesByDefault', 1), - hideHeader: searchConfig('hideHeader', 0), - hideApps: searchConfig('hideApps', 0), - hideCategories: searchConfig('hideCategories', 0), - useOrdering: searchConfig('useOrdering', 'createdAt'), - appsSameTab: searchConfig('appsSameTab', 0), - bookmarksSameTab: searchConfig('bookmarksSameTab', 0), - dockerApps: searchConfig('dockerApps', 0), - dockerHost: searchConfig('dockerHost', 'localhost'), - kubernetesApps: searchConfig('kubernetesApps', 0), - unpinStoppedApps: searchConfig('unpinStoppedApps', 0), + ...config, }); }, [props.loading]); @@ -87,17 +67,13 @@ const OtherSettings = (props: ComponentProps): JSX.Element => { // Input handler const inputChangeHandler = ( e: ChangeEvent, - isNumber?: boolean + options?: { isNumber?: boolean; isBool?: boolean } ) => { - let value: string | number = e.target.value; - - if (isNumber) { - value = parseFloat(value); - } - - setFormData({ - ...formData, - [e.target.name]: value, + inputHandler({ + e, + options, + setStateHandler: setFormData, + state: formData, }); }; @@ -126,8 +102,8 @@ const OtherSettings = (props: ComponentProps): JSX.Element => { inputChangeHandler(e, true)} + value={formData.pinCategoriesByDefault ? 1 : 0} + onChange={(e) => inputChangeHandler(e, { isBool: true })} > @@ -165,8 +141,8 @@ const OtherSettings = (props: ComponentProps): JSX.Element => { inputChangeHandler(e, true)} + value={formData.bookmarksSameTab ? 1 : 0} + onChange={(e) => inputChangeHandler(e, { isBool: true })} > @@ -192,8 +168,8 @@ const OtherSettings = (props: ComponentProps): JSX.Element => { inputChangeHandler(e, true)} + value={formData.hideApps ? 1 : 0} + onChange={(e) => inputChangeHandler(e, { isBool: true })} > @@ -216,8 +192,8 @@ const OtherSettings = (props: ComponentProps): JSX.Element => { inputChangeHandler(e, true)} + value={formData.dockerApps ? 1 : 0} + onChange={(e) => inputChangeHandler(e, { isBool: true })} > @@ -256,8 +232,8 @@ const OtherSettings = (props: ComponentProps): JSX.Element => { inputChangeHandler(e, true)} + value={formData.kubernetesApps ? 1 : 0} + onChange={(e) => inputChangeHandler(e, { isBool: true })} > @@ -286,6 +262,7 @@ const OtherSettings = (props: ComponentProps): JSX.Element => { const mapStateToProps = (state: GlobalState) => { return { loading: state.config.loading, + config: state.config.config, }; }; diff --git a/client/src/components/Settings/SearchSettings/CustomQueries/CustomQueries.tsx b/client/src/components/Settings/SearchSettings/CustomQueries/CustomQueries.tsx index c5dac62..a694f42 100644 --- a/client/src/components/Settings/SearchSettings/CustomQueries/CustomQueries.tsx +++ b/client/src/components/Settings/SearchSettings/CustomQueries/CustomQueries.tsx @@ -5,16 +5,21 @@ import classes from './CustomQueries.module.css'; import Modal from '../../../UI/Modal/Modal'; import Icon from '../../../UI/Icons/Icon/Icon'; -import { GlobalState, NewNotification, Query } from '../../../../interfaces'; +import { + Config, + GlobalState, + NewNotification, + Query, +} from '../../../../interfaces'; import QueriesForm from './QueriesForm'; import { deleteQuery, createNotification } from '../../../../store/actions'; import Button from '../../../UI/Buttons/Button/Button'; -import { searchConfig } from '../../../../utility'; interface Props { customQueries: Query[]; deleteQuery: (prefix: string) => {}; createNotification: (notification: NewNotification) => void; + config: Config; } const CustomQueries = (props: Props): JSX.Element => { @@ -29,7 +34,7 @@ const CustomQueries = (props: Props): JSX.Element => { }; const deleteHandler = (query: Query) => { - const currentProvider = searchConfig('defaultSearchProvider', 'l'); + const currentProvider = props.config.defaultSearchProvider; const isCurrent = currentProvider === query.prefix; if (isCurrent) { @@ -104,6 +109,7 @@ const CustomQueries = (props: Props): JSX.Element => { const mapStateToProps = (state: GlobalState) => { return { customQueries: state.config.customQueries, + config: state.config.config, }; }; diff --git a/client/src/components/Settings/SearchSettings/SearchSettings.tsx b/client/src/components/Settings/SearchSettings/SearchSettings.tsx index b2ac422..a403fa6 100644 --- a/client/src/components/Settings/SearchSettings/SearchSettings.tsx +++ b/client/src/components/Settings/SearchSettings/SearchSettings.tsx @@ -7,6 +7,7 @@ import { createNotification, updateConfig } from '../../../store/actions'; // Typescript import { + Config, GlobalState, NewNotification, Query, @@ -22,7 +23,7 @@ import SettingsHeadline from '../../UI/Headlines/SettingsHeadline/SettingsHeadli import InputGroup from '../../UI/Forms/InputGroup/InputGroup'; // Utils -import { searchConfig } from '../../../utility'; +import { inputHandler, searchSettingsTemplate } from '../../../utility'; // Data import { queries } from '../../../utility/searchQueries.json'; @@ -32,22 +33,17 @@ interface Props { updateConfig: (formData: SearchForm) => void; loading: boolean; customQueries: Query[]; + config: Config; } const SearchSettings = (props: Props): JSX.Element => { // Initial state - const [formData, setFormData] = useState({ - hideSearch: 0, - defaultSearchProvider: 'l', - searchSameTab: 0, - }); + const [formData, setFormData] = useState(searchSettingsTemplate); // Get config useEffect(() => { setFormData({ - hideSearch: searchConfig('hideSearch', 0), - defaultSearchProvider: searchConfig('defaultSearchProvider', 'l'), - searchSameTab: searchConfig('searchSameTab', 0), + ...props.config, }); }, [props.loading]); @@ -62,17 +58,13 @@ const SearchSettings = (props: Props): JSX.Element => { // Input handler const inputChangeHandler = ( e: ChangeEvent, - isNumber?: boolean + options?: { isNumber?: boolean; isBool?: boolean } ) => { - let value: string | number = e.target.value; - - if (isNumber) { - value = parseFloat(value); - } - - setFormData({ - ...formData, - [e.target.name]: value, + inputHandler({ + e, + options, + setStateHandler: setFormData, + state: formData, }); }; @@ -110,8 +102,8 @@ const SearchSettings = (props: Props): JSX.Element => { inputChangeHandler(e, true)} + value={formData.hideSearch ? 1 : 0} + onChange={(e) => inputChangeHandler(e, { isBool: true })} > @@ -143,6 +135,7 @@ const mapStateToProps = (state: GlobalState) => { return { loading: state.config.loading, customQueries: state.config.customQueries, + config: state.config.config, }; }; diff --git a/client/src/components/Settings/WeatherSettings/WeatherSettings.tsx b/client/src/components/Settings/WeatherSettings/WeatherSettings.tsx index 1378d44..04c9fa5 100644 --- a/client/src/components/Settings/WeatherSettings/WeatherSettings.tsx +++ b/client/src/components/Settings/WeatherSettings/WeatherSettings.tsx @@ -6,38 +6,40 @@ import { connect } from 'react-redux'; import { createNotification, updateConfig } from '../../../store/actions'; // Typescript -import { ApiResponse, GlobalState, NewNotification, Weather, WeatherForm } from '../../../interfaces'; +import { + ApiResponse, + Config, + GlobalState, + NewNotification, + Weather, + WeatherForm, +} from '../../../interfaces'; // UI import InputGroup from '../../UI/Forms/InputGroup/InputGroup'; import Button from '../../UI/Buttons/Button/Button'; // Utils -import { searchConfig } from '../../../utility'; +import { inputHandler, weatherSettingsTemplate } from '../../../utility'; interface ComponentProps { createNotification: (notification: NewNotification) => void; updateConfig: (formData: WeatherForm) => void; loading: boolean; + config: Config; } const WeatherSettings = (props: ComponentProps): JSX.Element => { // Initial state - const [formData, setFormData] = useState({ - WEATHER_API_KEY: '', - lat: 0, - long: 0, - isCelsius: 1 - }) + const [formData, setFormData] = useState( + weatherSettingsTemplate + ); // Get config useEffect(() => { setFormData({ - WEATHER_API_KEY: searchConfig('WEATHER_API_KEY', ''), - lat: searchConfig('lat', 0), - long: searchConfig('long', 0), - isCelsius: searchConfig('isCelsius', 1) - }) + ...props.config, + }); }, [props.loading]); // Form handler @@ -48,120 +50,124 @@ const WeatherSettings = (props: ComponentProps): JSX.Element => { if ((formData.lat || formData.long) && !formData.WEATHER_API_KEY) { props.createNotification({ title: 'Warning', - message: 'API key is missing. Weather Module will NOT work' - }) + message: 'API key is missing. Weather Module will NOT work', + }); } // Save settings await props.updateConfig(formData); - + // Update weather - axios.get>('/api/weather/update') + axios + .get>('/api/weather/update') .then(() => { props.createNotification({ title: 'Success', - message: 'Weather updated' - }) + message: 'Weather updated', + }); }) .catch((err) => { props.createNotification({ title: 'Error', - message: err.response.data.error - }) + message: err.response.data.error, + }); }); - } + }; // Input handler - const inputChangeHandler = (e: ChangeEvent, isNumber?: boolean) => { - let value: string | number = e.target.value; - - if (isNumber) { - value = parseFloat(value); - } - - setFormData({ - ...formData, - [e.target.name]: value - }) - } + const inputChangeHandler = ( + e: ChangeEvent, + options?: { isNumber?: boolean; isBool?: boolean } + ) => { + inputHandler({ + e, + options, + setStateHandler: setFormData, + state: formData, + }); + }; return (
formSubmitHandler(e)}> - + inputChangeHandler(e)} /> Using - - {' '}Weather API + + {' '} + Weather API . Key is required for weather module to work. - + inputChangeHandler(e, true)} - step='any' - lang='en-150' + onChange={(e) => inputChangeHandler(e, { isNumber: true })} + step="any" + lang="en-150" /> You can use - {' '}latlong.net + href="https://www.latlong.net/convert-address-to-lat-long.html" + target="blank" + > + {' '} + latlong.net - + inputChangeHandler(e, true)} - step='any' - lang='en-150' + onChange={(e) => inputChangeHandler(e, { isNumber: true })} + step="any" + lang="en-150" /> - + - +
- ) -} + ); +}; const mapStateToProps = (state: GlobalState) => { return { - loading: state.config.loading - } -} + loading: state.config.loading, + config: state.config.config, + }; +}; -export default connect(mapStateToProps, { createNotification, updateConfig })(WeatherSettings); \ No newline at end of file +export default connect(mapStateToProps, { createNotification, updateConfig })( + WeatherSettings +); diff --git a/client/src/components/Widgets/WeatherWidget/WeatherWidget.tsx b/client/src/components/Widgets/WeatherWidget/WeatherWidget.tsx index edf6fee..862a398 100644 --- a/client/src/components/Widgets/WeatherWidget/WeatherWidget.tsx +++ b/client/src/components/Widgets/WeatherWidget/WeatherWidget.tsx @@ -5,7 +5,7 @@ import axios from 'axios'; import { connect } from 'react-redux'; // Typescript -import { Weather, ApiResponse, Config, GlobalState } from '../../../interfaces'; +import { Weather, ApiResponse, GlobalState, Config } from '../../../interfaces'; // CSS import classes from './WeatherWidget.module.css'; @@ -13,12 +13,9 @@ import classes from './WeatherWidget.module.css'; // UI import WeatherIcon from '../../UI/Icons/WeatherIcon/WeatherIcon'; -// Utils -import { searchConfig } from '../../../utility'; - interface ComponentProps { configLoading: boolean; - config: Config[]; + config: Config; } const WeatherWidget = (props: ComponentProps): JSX.Element => { @@ -32,26 +29,28 @@ const WeatherWidget = (props: ComponentProps): JSX.Element => { conditionCode: 1000, id: -1, createdAt: new Date(), - updatedAt: new Date() + updatedAt: new Date(), }); const [isLoading, setIsLoading] = useState(true); // Initial request to get data useEffect(() => { - axios.get>('/api/weather') - .then(data => { + axios + .get>('/api/weather') + .then((data) => { const weatherData = data.data.data[0]; if (weatherData) { setWeather(weatherData); } setIsLoading(false); }) - .catch(err => console.log(err)); + .catch((err) => console.log(err)); }, []); // Open socket for data updates useEffect(() => { - const socketProtocol = document.location.protocol === 'http:' ? 'ws:' : 'wss:'; + const socketProtocol = + document.location.protocol === 'http:' ? 'ws:' : 'wss:'; const socketAddress = `${socketProtocol}//${window.location.host}/socket`; const webSocketClient = new WebSocket(socketAddress); @@ -59,43 +58,44 @@ const WeatherWidget = (props: ComponentProps): JSX.Element => { const data = JSON.parse(e.data); setWeather({ ...weather, - ...data - }) - } + ...data, + }); + }; return () => webSocketClient.close(); }, []); return (
- {(isLoading || props.configLoading || searchConfig('WEATHER_API_KEY', '')) && - (weather.id > 0 && - ( -
- -
-
- {searchConfig('isCelsius', true) - ? {weather.tempC}°C - : {weather.tempF}°F - } - {weather.cloud}% -
-
) - ) - } + {isLoading || + props.configLoading || + (props.config.WEATHER_API_KEY && weather.id > 0 && ( + +
+ +
+
+ {props.config.isCelsius ? ( + {weather.tempC}°C + ) : ( + {weather.tempF}°F + )} + {weather.cloud}% +
+
+ ))}
- ) -} + ); +}; const mapStateToProps = (state: GlobalState) => { return { configLoading: state.config.loading, - config: state.config.config - } -} + config: state.config.config, + }; +}; -export default connect(mapStateToProps)(WeatherWidget); \ No newline at end of file +export default connect(mapStateToProps)(WeatherWidget); diff --git a/client/src/interfaces/Config.ts b/client/src/interfaces/Config.ts index 281402c..d0152c5 100644 --- a/client/src/interfaces/Config.ts +++ b/client/src/interfaces/Config.ts @@ -1,8 +1,22 @@ -import { Model } from './'; - -export interface Config extends Model { - key: string; - value: string; - valueType: string; - isLocked: boolean; -} \ No newline at end of file +export interface Config { + WEATHER_API_KEY: string; + lat: number; + long: number; + isCelsius: boolean; + customTitle: string; + pinAppsByDefault: boolean; + pinCategoriesByDefault: boolean; + hideHeader: boolean; + useOrdering: string; + appsSameTab: boolean; + bookmarksSameTab: boolean; + searchSameTab: boolean; + hideApps: boolean; + hideCategories: boolean; + hideSearch: boolean; + defaultSearchProvider: string; + dockerApps: boolean; + dockerHost: string; + kubernetesApps: boolean; + unpinStoppedApps: boolean; +} diff --git a/client/src/interfaces/Forms.ts b/client/src/interfaces/Forms.ts index 9b195da..9123d62 100644 --- a/client/src/interfaces/Forms.ts +++ b/client/src/interfaces/Forms.ts @@ -2,30 +2,27 @@ export interface WeatherForm { WEATHER_API_KEY: string; lat: number; long: number; - isCelsius: number; + isCelsius: boolean; } export interface SearchForm { - hideSearch: number; + hideSearch: boolean; defaultSearchProvider: string; - searchSameTab: number; + searchSameTab: boolean; } -export interface SettingsForm { +export interface OtherSettingsForm { customTitle: string; - pinAppsByDefault: number; - pinCategoriesByDefault: number; - hideHeader: number; - hideApps: number; - hideCategories: number; - // hideSearch: number; - // defaultSearchProvider: string; + pinAppsByDefault: boolean; + pinCategoriesByDefault: boolean; + hideHeader: boolean; + hideApps: boolean; + hideCategories: boolean; useOrdering: string; - appsSameTab: number; - bookmarksSameTab: number; - // searchSameTab: number; - dockerApps: number; + appsSameTab: boolean; + bookmarksSameTab: boolean; + dockerApps: boolean; dockerHost: string; - kubernetesApps: number; - unpinStoppedApps: number; + kubernetesApps: boolean; + unpinStoppedApps: boolean; } diff --git a/client/src/utility/index.ts b/client/src/utility/index.ts index caff9c3..ad08042 100644 --- a/client/src/utility/index.ts +++ b/client/src/utility/index.ts @@ -1,7 +1,8 @@ export * from './iconParser'; export * from './urlParser'; -export * from './searchConfig'; export * from './checkVersion'; export * from './sortData'; export * from './searchParser'; export * from './redirectUrl'; +export * from './templateObjects'; +export * from './inputHandler'; diff --git a/client/src/utility/inputHandler.ts b/client/src/utility/inputHandler.ts new file mode 100644 index 0000000..98e805a --- /dev/null +++ b/client/src/utility/inputHandler.ts @@ -0,0 +1,39 @@ +import { ChangeEvent, SetStateAction } from 'react'; + +type Event = ChangeEvent; + +interface Options { + isNumber?: boolean; + isBool?: boolean; +} + +interface Params { + e: Event; + options?: Options; + setStateHandler: (v: SetStateAction) => void; + state: T; +} + +export const inputHandler = (params: Params): void => { + const { e, options, setStateHandler, state } = params; + + const rawValue = e.target.value; + let value: string | number | boolean = e.target.value; + + if (options) { + const { isNumber = false, isBool = false } = options; + + if (isNumber) { + value = parseFloat(rawValue); + } + + if (isBool) { + value = !!parseInt(rawValue); + } + } + + setStateHandler({ + ...state, + [e.target.name]: value, + }); +}; diff --git a/client/src/utility/searchConfig.ts b/client/src/utility/searchConfig.ts deleted file mode 100644 index 4e46091..0000000 --- a/client/src/utility/searchConfig.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { store } from '../store/store'; - -/** - * Search config store with given key - * @param key Config pair key to search - * @param _default Value to return if key is not found - */ -export const searchConfig = (key: string, _default: any) => { - const state = store.getState(); - - const pair = state.config.config.find(p => p.key === key); - - if (pair) { - if (pair.valueType === 'number') { - return parseFloat(pair.value); - } else if (pair.valueType === 'boolean') { - return parseInt(pair.value); - } else { - return pair.value; - } - } - - return _default; -} \ No newline at end of file diff --git a/client/src/utility/searchParser.ts b/client/src/utility/searchParser.ts index e14617c..cff9bfb 100644 --- a/client/src/utility/searchParser.ts +++ b/client/src/utility/searchParser.ts @@ -1,7 +1,6 @@ import { queries } from './searchQueries.json'; import { Query, SearchResult } from '../interfaces'; import { store } from '../store/store'; -import { searchConfig } from '.'; export const searchParser = (searchQuery: string): SearchResult => { const result: SearchResult = { @@ -16,7 +15,7 @@ export const searchParser = (searchQuery: string): SearchResult => { }, }; - const customQueries = store.getState().config.customQueries; + const { customQueries, config } = store.getState().config; // Check if url or ip was passed const urlRegex = @@ -27,9 +26,7 @@ export const searchParser = (searchQuery: string): SearchResult => { // Match prefix and query const splitQuery = searchQuery.match(/^\/([a-z]+)[ ](.+)$/i); - const prefix = splitQuery - ? splitQuery[1] - : searchConfig('defaultSearchProvider', 'l'); + const prefix = splitQuery ? splitQuery[1] : config.defaultSearchProvider; const search = splitQuery ? encodeURIComponent(splitQuery[2]) @@ -47,7 +44,7 @@ export const searchParser = (searchQuery: string): SearchResult => { if (prefix === 'l') { result.isLocal = true; } else { - result.sameTab = searchConfig('searchSameTab', false); + result.sameTab = config.searchSameTab; } return result; diff --git a/client/src/utility/templateObjects/configTemplate.ts b/client/src/utility/templateObjects/configTemplate.ts new file mode 100644 index 0000000..bbc7998 --- /dev/null +++ b/client/src/utility/templateObjects/configTemplate.ts @@ -0,0 +1,24 @@ +import { Config } from '../../interfaces'; + +export const configTemplate: Config = { + WEATHER_API_KEY: '', + lat: 0, + long: 0, + isCelsius: true, + customTitle: 'Flame', + pinAppsByDefault: true, + pinCategoriesByDefault: true, + hideHeader: false, + useOrdering: 'createdAt', + appsSameTab: false, + bookmarksSameTab: false, + searchSameTab: false, + hideApps: false, + hideCategories: false, + hideSearch: false, + defaultSearchProvider: 'l', + dockerApps: false, + dockerHost: 'localhost', + kubernetesApps: false, + unpinStoppedApps: false, +}; diff --git a/client/src/utility/templateObjects/index.ts b/client/src/utility/templateObjects/index.ts new file mode 100644 index 0000000..3f2d57c --- /dev/null +++ b/client/src/utility/templateObjects/index.ts @@ -0,0 +1,2 @@ +export * from './configTemplate'; +export * from './settingsTemplate'; diff --git a/client/src/utility/templateObjects/settingsTemplate.ts b/client/src/utility/templateObjects/settingsTemplate.ts new file mode 100644 index 0000000..674931b --- /dev/null +++ b/client/src/utility/templateObjects/settingsTemplate.ts @@ -0,0 +1,30 @@ +import { OtherSettingsForm, SearchForm, WeatherForm } from '../../interfaces'; + +export const otherSettingsTemplate: OtherSettingsForm = { + customTitle: document.title, + pinAppsByDefault: true, + pinCategoriesByDefault: true, + hideHeader: false, + hideApps: false, + hideCategories: false, + useOrdering: 'createdAt', + appsSameTab: false, + bookmarksSameTab: false, + dockerApps: true, + dockerHost: 'localhost', + kubernetesApps: true, + unpinStoppedApps: true, +}; + +export const weatherSettingsTemplate: WeatherForm = { + WEATHER_API_KEY: '', + lat: 0, + long: 0, + isCelsius: true, +}; + +export const searchSettingsTemplate: SearchForm = { + hideSearch: false, + searchSameTab: false, + defaultSearchProvider: 'l', +};