From 2c5535946aa15278c6e969c9a9b24d32a45e5b12 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Fri, 13 Jan 2023 18:31:56 +0100 Subject: [PATCH] allow filtering of techniques by ruleset --- app/Http/Controllers/TechniqueController.php | 31 ++++++++++++ resources/js/components/pages/Techniques.js | 38 +++++++++++--- .../js/components/techniques/Overview.js | 14 +++++- .../js/components/techniques/TechFilter.js | 49 +++++++++++++++++++ resources/js/i18n/de.js | 1 + resources/js/i18n/en.js | 1 + resources/sass/techniques.scss | 12 +++++ 7 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 resources/js/components/techniques/TechFilter.js diff --git a/app/Http/Controllers/TechniqueController.php b/app/Http/Controllers/TechniqueController.php index 9862325..8694c04 100644 --- a/app/Http/Controllers/TechniqueController.php +++ b/app/Http/Controllers/TechniqueController.php @@ -12,6 +12,11 @@ class TechniqueController extends Controller public function search(Request $request) { $validatedData = $request->validate([ 'phrase' => 'string|nullable', + 'ruleset' => 'array|nullable', + 'ruleset.competitive' => 'boolean', + 'ruleset.mg' => 'boolean', + 'ruleset.nl' => 'boolean', + 'ruleset.owg' => 'boolean', 'type' => 'string|nullable', ]); @@ -29,6 +34,32 @@ class TechniqueController extends Controller }); } + if (isset($validatedData['ruleset'])) { + $com = isset($validatedData['ruleset']['competitive']) && $validatedData['ruleset']['competitive']; + $owg = isset($validatedData['ruleset']['owg']) && $validatedData['ruleset']['owg']; + $mg = isset($validatedData['ruleset']['mg']) && $validatedData['ruleset']['mg']; + $nl = isset($validatedData['ruleset']['nl']) && $validatedData['ruleset']['nl']; + $any = $com || $owg || $mg || $nl; + $all = $com && $owg && $mg && $nl; + if ($any && !$all) { + $techs->where(function(Builder $query) use ($com, $owg, $mg, $nl) { + $query->whereNull('rulesets'); + if ($com) { + $query->orWhere('rulesets->competitive', '=', true); + } + if ($owg) { + $query->orWhere('rulesets->owg', '=', true); + } + if ($mg) { + $query->orWhere('rulesets->mg', '=', true); + } + if ($nl) { + $query->orWhere('rulesets->nl', '=', true); + } + }); + } + } + return $techs->get()->toJson(); } diff --git a/resources/js/components/pages/Techniques.js b/resources/js/components/pages/Techniques.js index 4bc0066..ddecf56 100644 --- a/resources/js/components/pages/Techniques.js +++ b/resources/js/components/pages/Techniques.js @@ -13,17 +13,35 @@ import i18n from '../../i18n'; const Techniques = ({ namespace, type }) => { const [error, setError] = React.useState(null); + const [filter, setFilter] = React.useState({}); const [loading, setLoading] = React.useState(true); const [techniques, setTechniques] = React.useState([]); + React.useEffect(() => { + const savedFilter = localStorage.getItem(`content.filter.${type}`); + if (savedFilter) { + setFilter(JSON.parse(savedFilter)); + } else { + setFilter(filter => filter ? {} : filter); + } + }, [type]); + + const updateFilter = React.useCallback(newFilter => { + localStorage.setItem(`content.filter.${type}`, JSON.stringify(newFilter)); + setFilter(newFilter); + }, [type]); + React.useEffect(() => { const ctrl = new AbortController(); - setLoading(true); + if (!techniques.length) { + setLoading(true); + } window.document.title = i18n.t(`${namespace}.heading`); axios .get(`/api/content`, { params: { type, + ...filter, }, signal: ctrl.signal }) @@ -33,14 +51,16 @@ const Techniques = ({ namespace, type }) => { setTechniques(response.data.sort(compareTranslation('title', i18n.language))); }) .catch(error => { - setError(error); - setLoading(false); - setTechniques([]); + if (!axios.isCancel(error)) { + setError(error); + setLoading(false); + setTechniques([]); + } }); return () => { ctrl.abort(); }; - }, [namespace, type]); + }, [filter, namespace, type]); React.useEffect(() => { window.document.title = i18n.t(`${namespace}.heading`); @@ -60,7 +80,13 @@ const Techniques = ({ namespace, type }) => { } return - + ; }; diff --git a/resources/js/components/techniques/Overview.js b/resources/js/components/techniques/Overview.js index 1686865..f82a877 100644 --- a/resources/js/components/techniques/Overview.js +++ b/resources/js/components/techniques/Overview.js @@ -4,20 +4,32 @@ import { Container } from 'react-bootstrap'; import { withTranslation } from 'react-i18next'; import List from './List'; +import TechFilter from './TechFilter'; import i18n from '../../i18n'; const Overview = ({ + filter, namespace, + setFilter, techniques, + type, }) => -

{i18n.t(`${namespace}.heading`)}

+
+

{i18n.t(`${namespace}.heading`)}

+ {type === 'tech' ? + + : null} +
; Overview.propTypes = { + filter: PropTypes.shape({}), namespace: PropTypes.string, + setFilter: PropTypes.func, techniques: PropTypes.arrayOf(PropTypes.shape({ })), + type: PropTypes.string, }; export default withTranslation()(Overview); diff --git a/resources/js/components/techniques/TechFilter.js b/resources/js/components/techniques/TechFilter.js new file mode 100644 index 0000000..008288a --- /dev/null +++ b/resources/js/components/techniques/TechFilter.js @@ -0,0 +1,49 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { Form } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +const TechFilter = ({ filter, setFilter }) => { + const { t } = useTranslation(); + + const handleChange = React.useCallback(e => { + if (e.target.name.startsWith('ruleset.')) { + const r = e.target.name.substring(8); + setFilter({ + ...filter, + ruleset: { + ...filter.ruleset || {}, + [r]: e.target.checked ? '1' : '0', + }, + }); + } + }, [filter]); + + return
+
{t('techniques.rulesetFilterHeading')}
+
+ {['competitive', 'owg', 'mg', 'nl'].map(r => + + )} +
+
; +}; + +TechFilter.propTypes = { + filter: PropTypes.shape({ + ruleset: PropTypes.shape({ + }), + }), + setFilter: PropTypes.func, +}; + +export default TechFilter; diff --git a/resources/js/i18n/de.js b/resources/js/i18n/de.js index 1334fde..0812fd4 100644 --- a/resources/js/i18n/de.js +++ b/resources/js/i18n/de.js @@ -501,6 +501,7 @@ export default { nl: 'No Logic', owg: 'Overworld Glitches', }, + rulesetFilterHeading: 'Zeige nur Techniken, die in folgenden Regelsätzen erlaubt sind:', seeAlso: 'Siehe auch', }, tournaments: { diff --git a/resources/js/i18n/en.js b/resources/js/i18n/en.js index d4e61bb..030b023 100644 --- a/resources/js/i18n/en.js +++ b/resources/js/i18n/en.js @@ -501,6 +501,7 @@ export default { nl: 'No Logic', owg: 'Overworld Glitches', }, + rulesetFilterHeading: 'Only show techniques allowed in the following rulesets:', seeAlso: 'See also', }, tournaments: { diff --git a/resources/sass/techniques.scss b/resources/sass/techniques.scss index a28c438..8355e70 100644 --- a/resources/sass/techniques.scss +++ b/resources/sass/techniques.scss @@ -4,6 +4,18 @@ gap: 0 1ex; padding: 0.5ex; border-radius: 0.5ex; + + .form-check label { + margin-top: 0; + } +} + +.tech-filter { + max-width: 18em; + text-align: right; + .form-check { + text-align: left; + } } .tech-list { -- 2.39.2