mirror of
https://github.com/pawelmalak/flame.git
synced 2024-12-18 23:41:39 +03:00
Add and delete custom search provider actions and controllers
This commit is contained in:
parent
459523dfd2
commit
a885440fef
1
api.js
1
api.js
@ -20,6 +20,7 @@ api.use('/api/config', require('./routes/config'));
|
||||
api.use('/api/weather', require('./routes/weather'));
|
||||
api.use('/api/categories', require('./routes/category'));
|
||||
api.use('/api/bookmarks', require('./routes/bookmark'));
|
||||
api.use('/api/queries', require('./routes/queries'));
|
||||
|
||||
// Custom error handler
|
||||
api.use(errorHandler);
|
||||
|
@ -19,9 +19,7 @@ import {
|
||||
// UI
|
||||
import InputGroup from '../../UI/Forms/InputGroup/InputGroup';
|
||||
import Button from '../../UI/Buttons/Button/Button';
|
||||
|
||||
// CSS
|
||||
import classes from './OtherSettings.module.css';
|
||||
import SettingsHeadline from '../../UI/Headlines/SettingsHeadline/SettingsHeadline';
|
||||
|
||||
// Utils
|
||||
import { searchConfig } from '../../../utility';
|
||||
@ -104,7 +102,7 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
||||
return (
|
||||
<form onSubmit={(e) => formSubmitHandler(e)}>
|
||||
{/* OTHER OPTIONS */}
|
||||
<h2 className={classes.SettingsSection}>Miscellaneous</h2>
|
||||
<SettingsHeadline text="Miscellaneous" />
|
||||
<InputGroup>
|
||||
<label htmlFor="customTitle">Custom page title</label>
|
||||
<input
|
||||
@ -118,7 +116,7 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
||||
</InputGroup>
|
||||
|
||||
{/* BEAHVIOR OPTIONS */}
|
||||
<h2 className={classes.SettingsSection}>App Behavior</h2>
|
||||
<SettingsHeadline text="App Behavior" />
|
||||
<InputGroup>
|
||||
<label htmlFor="pinAppsByDefault">
|
||||
Pin new applications by default
|
||||
@ -186,7 +184,7 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
||||
</InputGroup>
|
||||
|
||||
{/* MODULES OPTIONS */}
|
||||
<h2 className={classes.SettingsSection}>Modules</h2>
|
||||
<SettingsHeadline text="Modules" />
|
||||
<InputGroup>
|
||||
<label htmlFor="hideHeader">Hide greeting and date</label>
|
||||
<select
|
||||
@ -225,7 +223,7 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
||||
</InputGroup>
|
||||
|
||||
{/* DOCKER SETTINGS */}
|
||||
<h2 className={classes.SettingsSection}>Docker</h2>
|
||||
<SettingsHeadline text="Docker" />
|
||||
<InputGroup>
|
||||
<label htmlFor="dockerApps">Use Docker API</label>
|
||||
<select
|
||||
@ -254,7 +252,7 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
|
||||
</InputGroup>
|
||||
|
||||
{/* KUBERNETES SETTINGS */}
|
||||
<h2 className={classes.SettingsSection}>Kubernetes</h2>
|
||||
<SettingsHeadline text="Kubernetes" />
|
||||
<InputGroup>
|
||||
<label htmlFor="kubernetesApps">Use Kubernetes Ingress API</label>
|
||||
<select
|
||||
|
@ -0,0 +1,26 @@
|
||||
.QueriesGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.QueriesGrid span {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.ActionIcons {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ActionIcons svg {
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.ActionIcons svg:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.Separator {
|
||||
grid-column: 1 / 4;
|
||||
border-bottom: 1px solid var(--color-primary);
|
||||
margin: 10px 0;
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
import { Fragment, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import classes from './CustomQueries.module.css';
|
||||
|
||||
import ModalForm from '../../../UI/Forms/ModalForm/ModalForm';
|
||||
import Modal from '../../../UI/Modal/Modal';
|
||||
import Icon from '../../../UI/Icons/Icon/Icon';
|
||||
import { GlobalState, Query } from '../../../../interfaces';
|
||||
import InputGroup from '../../../UI/Forms/InputGroup/InputGroup';
|
||||
import QueriesForm from './QueriesForm';
|
||||
import { deleteQuery } from '../../../../store/actions';
|
||||
import Button from '../../../UI/Buttons/Button/Button';
|
||||
|
||||
interface Props {
|
||||
customQueries: Query[];
|
||||
deleteQuery: (prefix: string) => {};
|
||||
}
|
||||
|
||||
const CustomQueries = (props: Props): JSX.Element => {
|
||||
const { customQueries, deleteQuery } = props;
|
||||
|
||||
const [modalIsOpen, setModalIsOpen] = useState(false);
|
||||
|
||||
const deleteHandler = (query: Query) => {
|
||||
if (window.confirm(`Are you sure you want to delete this provider?`)) {
|
||||
deleteQuery(query.prefix);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Modal
|
||||
isOpen={modalIsOpen}
|
||||
setIsOpen={() => setModalIsOpen(!modalIsOpen)}
|
||||
>
|
||||
<QueriesForm modalHandler={() => setModalIsOpen(!modalIsOpen)} />
|
||||
</Modal>
|
||||
|
||||
<div>
|
||||
<div className={classes.QueriesGrid}>
|
||||
{customQueries.length > 0 && (
|
||||
<Fragment>
|
||||
<span>Name</span>
|
||||
<span>Prefix</span>
|
||||
<span>Actions</span>
|
||||
|
||||
<div className={classes.Separator}></div>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
{customQueries.map((q: Query, idx) => (
|
||||
<Fragment key={idx}>
|
||||
<span>{q.name}</span>
|
||||
<span>{q.prefix}</span>
|
||||
<span className={classes.ActionIcons}>
|
||||
<span>
|
||||
<Icon icon="mdiPencil" />
|
||||
</span>
|
||||
<span onClick={() => deleteHandler(q)}>
|
||||
<Icon icon="mdiDelete" />
|
||||
</span>
|
||||
</span>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Button click={() => setModalIsOpen(true)}>
|
||||
Add new search provider
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => {
|
||||
return {
|
||||
customQueries: state.config.customQueries,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, { deleteQuery })(CustomQueries);
|
@ -0,0 +1,59 @@
|
||||
import { useState } from 'react';
|
||||
import Button from '../../../UI/Buttons/Button/Button';
|
||||
import InputGroup from '../../../UI/Forms/InputGroup/InputGroup';
|
||||
import ModalForm from '../../../UI/Forms/ModalForm/ModalForm';
|
||||
|
||||
interface Props {
|
||||
modalHandler: () => void;
|
||||
// addApp: (formData: NewApp | FormData) => any;
|
||||
// updateApp: (id: number, formData: NewApp | FormData) => any;
|
||||
// app?: App;
|
||||
}
|
||||
|
||||
const QueriesForm = (props: Props): JSX.Element => {
|
||||
const [formData, setFormData] = useState();
|
||||
|
||||
return (
|
||||
<ModalForm modalHandler={props.modalHandler} formHandler={() => {}}>
|
||||
<InputGroup>
|
||||
<label htmlFor="name">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
id="name"
|
||||
placeholder="Google"
|
||||
required
|
||||
// value={formData.name}
|
||||
// onChange={(e) => inputChangeHandler(e)}
|
||||
/>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<label htmlFor="name">Prefix</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
id="name"
|
||||
placeholder="g"
|
||||
required
|
||||
// value={formData.name}
|
||||
// onChange={(e) => inputChangeHandler(e)}
|
||||
/>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<label htmlFor="name">Query Template</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
id="name"
|
||||
placeholder="https://www.google.com/search?q="
|
||||
required
|
||||
// value={formData.name}
|
||||
// onChange={(e) => inputChangeHandler(e)}
|
||||
/>
|
||||
</InputGroup>
|
||||
<Button>Add provider</Button>
|
||||
</ModalForm>
|
||||
);
|
||||
};
|
||||
|
||||
export default QueriesForm;
|
@ -1,5 +1,5 @@
|
||||
// React
|
||||
import { useState, useEffect, FormEvent, ChangeEvent } from 'react';
|
||||
import { useState, useEffect, FormEvent, ChangeEvent, Fragment } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
// State
|
||||
@ -13,15 +13,19 @@ import {
|
||||
SearchForm,
|
||||
} from '../../../interfaces';
|
||||
|
||||
// Utils
|
||||
import { searchConfig } from '../../../utility';
|
||||
import InputGroup from '../../UI/Forms/InputGroup/InputGroup';
|
||||
|
||||
// Data
|
||||
import { queries } from '../../../utility/searchQueries.json';
|
||||
// Components
|
||||
import CustomQueries from './CustomQueries/CustomQueries';
|
||||
|
||||
// UI
|
||||
import Button from '../../UI/Buttons/Button/Button';
|
||||
import SettingsHeadline from '../../UI/Headlines/SettingsHeadline/SettingsHeadline';
|
||||
import InputGroup from '../../UI/Forms/InputGroup/InputGroup';
|
||||
|
||||
// Utils
|
||||
import { searchConfig } from '../../../utility';
|
||||
|
||||
// Data
|
||||
import { queries } from '../../../utility/searchQueries.json';
|
||||
|
||||
interface Props {
|
||||
createNotification: (notification: NewNotification) => void;
|
||||
@ -73,7 +77,13 @@ const SearchSettings = (props: Props): JSX.Element => {
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={(e) => formSubmitHandler(e)}>
|
||||
<Fragment>
|
||||
{/* GENERAL SETTINGS */}
|
||||
<form
|
||||
onSubmit={(e) => formSubmitHandler(e)}
|
||||
style={{ marginBottom: '30px' }}
|
||||
>
|
||||
<SettingsHeadline text="General" />
|
||||
<InputGroup>
|
||||
<label htmlFor="defaultSearchProvider">Default Search Provider</label>
|
||||
<select
|
||||
@ -82,11 +92,15 @@ const SearchSettings = (props: Props): JSX.Element => {
|
||||
value={formData.defaultSearchProvider}
|
||||
onChange={(e) => inputChangeHandler(e)}
|
||||
>
|
||||
{[...queries, ...props.customQueries].map((query: Query, idx) => (
|
||||
{[...queries, ...props.customQueries].map((query: Query, idx) => {
|
||||
const isCustom = idx >= queries.length;
|
||||
|
||||
return (
|
||||
<option key={idx} value={query.prefix}>
|
||||
{query.name}
|
||||
{isCustom && '+'} {query.name}
|
||||
</option>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
@ -117,6 +131,11 @@ const SearchSettings = (props: Props): JSX.Element => {
|
||||
</InputGroup>
|
||||
<Button>Save changes</Button>
|
||||
</form>
|
||||
|
||||
{/* CUSTOM QUERIES */}
|
||||
<SettingsHeadline text="Custom search providers" />
|
||||
<CustomQueries />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
.SettingsSection {
|
||||
.SettingsHeadline {
|
||||
color: var(--color-primary);
|
||||
padding-bottom: 3px;
|
||||
margin-bottom: 10px;
|
@ -0,0 +1,11 @@
|
||||
const classes = require('./SettingsHeadline.module.css');
|
||||
|
||||
interface Props {
|
||||
text: string;
|
||||
}
|
||||
|
||||
const SettingsHeadline = (props: Props): JSX.Element => {
|
||||
return <h2 className={classes.SettingsHeadline}>{props.text}</h2>;
|
||||
};
|
||||
|
||||
export default SettingsHeadline;
|
@ -28,7 +28,11 @@ import {
|
||||
GetConfigAction,
|
||||
UpdateConfigAction,
|
||||
} from './';
|
||||
import { FetchQueriesAction } from './config';
|
||||
import {
|
||||
AddQueryAction,
|
||||
DeleteQueryAction,
|
||||
FetchQueriesAction,
|
||||
} from './config';
|
||||
|
||||
export enum ActionTypes {
|
||||
// Theme
|
||||
@ -65,6 +69,8 @@ export enum ActionTypes {
|
||||
getConfig = 'GET_CONFIG',
|
||||
updateConfig = 'UPDATE_CONFIG',
|
||||
fetchQueries = 'FETCH_QUERIES',
|
||||
addQuery = 'ADD_QUERY',
|
||||
deleteQuery = 'DELETE_QUERY',
|
||||
}
|
||||
|
||||
export type Action =
|
||||
@ -96,4 +102,6 @@ export type Action =
|
||||
// Config
|
||||
| GetConfigAction
|
||||
| UpdateConfigAction
|
||||
| FetchQueriesAction;
|
||||
| FetchQueriesAction
|
||||
| AddQueryAction
|
||||
| DeleteQueryAction;
|
||||
|
@ -60,9 +60,7 @@ export interface FetchQueriesAction {
|
||||
export const fetchQueries =
|
||||
() => async (dispatch: Dispatch<FetchQueriesAction>) => {
|
||||
try {
|
||||
const res = await axios.get<ApiResponse<Query[]>>(
|
||||
'/api/config/0/queries'
|
||||
);
|
||||
const res = await axios.get<ApiResponse<Query[]>>('/api/queries');
|
||||
|
||||
dispatch<FetchQueriesAction>({
|
||||
type: ActionTypes.fetchQueries,
|
||||
@ -72,3 +70,43 @@ export const fetchQueries =
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
export interface AddQueryAction {
|
||||
type: ActionTypes.addQuery;
|
||||
payload: Query;
|
||||
}
|
||||
|
||||
export const addQuery =
|
||||
(query: Query) => async (dispatch: Dispatch<AddQueryAction>) => {
|
||||
try {
|
||||
const res = await axios.post<ApiResponse<Query>>('/api/queries', query);
|
||||
|
||||
dispatch<AddQueryAction>({
|
||||
type: ActionTypes.addQuery,
|
||||
payload: res.data.data,
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
export interface DeleteQueryAction {
|
||||
type: ActionTypes.deleteQuery;
|
||||
payload: Query[];
|
||||
}
|
||||
|
||||
export const deleteQuery =
|
||||
(prefix: string) => async (dispatch: Dispatch<DeleteQueryAction>) => {
|
||||
try {
|
||||
const res = await axios.delete<ApiResponse<Query[]>>(
|
||||
`/api/queries/${prefix}`
|
||||
);
|
||||
|
||||
dispatch<DeleteQueryAction>({
|
||||
type: ActionTypes.deleteQuery,
|
||||
payload: res.data.data,
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
@ -34,6 +34,20 @@ const fetchQueries = (state: State, action: Action): State => {
|
||||
};
|
||||
};
|
||||
|
||||
const addQuery = (state: State, action: Action): State => {
|
||||
return {
|
||||
...state,
|
||||
customQueries: [...state.customQueries, action.payload],
|
||||
};
|
||||
};
|
||||
|
||||
const deleteQuery = (state: State, action: Action): State => {
|
||||
return {
|
||||
...state,
|
||||
customQueries: action.payload,
|
||||
};
|
||||
};
|
||||
|
||||
const configReducer = (state: State = initialState, action: Action) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.getConfig:
|
||||
@ -42,6 +56,10 @@ const configReducer = (state: State = initialState, action: Action) => {
|
||||
return updateConfig(state, action);
|
||||
case ActionTypes.fetchQueries:
|
||||
return fetchQueries(state, action);
|
||||
case ActionTypes.addQuery:
|
||||
return addQuery(state, action);
|
||||
case ActionTypes.deleteQuery:
|
||||
return deleteQuery(state, action);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -175,16 +175,3 @@ exports.updateCss = asyncWrapper(async (req, res, next) => {
|
||||
data: {},
|
||||
});
|
||||
});
|
||||
|
||||
// @desc Get custom queries file
|
||||
// @route GET /api/config/0/queries
|
||||
// @access Public
|
||||
exports.getQueries = asyncWrapper(async (req, res, next) => {
|
||||
const file = new File(join(__dirname, '../data/customQueries.json'));
|
||||
const content = JSON.parse(file.read());
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: content.queries,
|
||||
});
|
||||
});
|
||||
|
53
controllers/queries/index.js
Normal file
53
controllers/queries/index.js
Normal file
@ -0,0 +1,53 @@
|
||||
const asyncWrapper = require('../../middleware/asyncWrapper');
|
||||
const File = require('../../utils/File');
|
||||
const { join } = require('path');
|
||||
|
||||
const QUERIES_PATH = join(__dirname, '../../data/customQueries.json');
|
||||
|
||||
// @desc Add custom search query
|
||||
// @route POST /api/queries
|
||||
// @access Public
|
||||
exports.addQuery = asyncWrapper(async (req, res, next) => {
|
||||
const file = new File(QUERIES_PATH);
|
||||
let content = JSON.parse(file.read());
|
||||
|
||||
// Add new query
|
||||
content.queries.push(req.body);
|
||||
file.write(content, true);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: req.body,
|
||||
});
|
||||
});
|
||||
|
||||
// @desc Get custom queries file
|
||||
// @route GET /api/queries
|
||||
// @access Public
|
||||
exports.getQueries = asyncWrapper(async (req, res, next) => {
|
||||
const file = new File(QUERIES_PATH);
|
||||
const content = JSON.parse(file.read());
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: content.queries,
|
||||
});
|
||||
});
|
||||
|
||||
// @desc Delete query
|
||||
// @route DELETE /api/queries/:prefix
|
||||
// @access Public
|
||||
exports.deleteQuery = asyncWrapper(async (req, res, next) => {
|
||||
const file = new File(QUERIES_PATH);
|
||||
let content = JSON.parse(file.read());
|
||||
|
||||
content.queries = content.queries.filter(
|
||||
(q) => q.prefix != req.params.prefix
|
||||
);
|
||||
file.write(content, true);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: content.queries,
|
||||
});
|
||||
});
|
@ -10,7 +10,6 @@ const {
|
||||
deletePair,
|
||||
updateCss,
|
||||
getCss,
|
||||
getQueries,
|
||||
} = require('../controllers/config');
|
||||
|
||||
router.route('/').post(createPair).get(getAllPairs).put(updateValues);
|
||||
@ -19,6 +18,4 @@ router.route('/:key').get(getSinglePair).put(updateValue).delete(deletePair);
|
||||
|
||||
router.route('/0/css').get(getCss).put(updateCss);
|
||||
|
||||
router.route('/0/queries').get(getQueries);
|
||||
|
||||
module.exports = router;
|
||||
|
13
routes/queries.js
Normal file
13
routes/queries.js
Normal file
@ -0,0 +1,13 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const {
|
||||
getQueries,
|
||||
addQuery,
|
||||
deleteQuery,
|
||||
} = require('../controllers/queries/');
|
||||
|
||||
router.route('/').post(addQuery).get(getQueries);
|
||||
router.route('/:prefix').delete(deleteQuery);
|
||||
|
||||
module.exports = router;
|
Loading…
Reference in New Issue
Block a user