]> git.localhorst.tv Git - alttp.git/commitdiff
allow filtering of techniques by ruleset
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 13 Jan 2023 17:31:56 +0000 (18:31 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 13 Jan 2023 17:31:56 +0000 (18:31 +0100)
app/Http/Controllers/TechniqueController.php
resources/js/components/pages/Techniques.js
resources/js/components/techniques/Overview.js
resources/js/components/techniques/TechFilter.js [new file with mode: 0644]
resources/js/i18n/de.js
resources/js/i18n/en.js
resources/sass/techniques.scss

index 98623259f494f701e78a8fa3dedcd21da7350ccf..8694c046de5e1dd1520158c883e55ef8486a52bd 100644 (file)
@@ -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();
        }
 
index 4bc0066baefd69654ac06f4ed678fc24230f89f8..ddecf5603e7752130588f2edad28fe90d9db3ebe 100644 (file)
@@ -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 <ErrorBoundary>
-               <Overview namespace={namespace} techniques={techniques} />
+               <Overview
+                       filter={filter}
+                       namespace={namespace}
+                       setFilter={updateFilter}
+                       techniques={techniques}
+                       type={type}
+               />
        </ErrorBoundary>;
 };
 
index 16868655b12fe473a2f7425415939504fa914a74..f82a8777ab3e4725c6fe2b58fbd1575188ab2d89 100644 (file)
@@ -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,
 }) => <Container>
-       <h1>{i18n.t(`${namespace}.heading`)}</h1>
+       <div className="d-flex align-items-center justify-content-between">
+               <h1>{i18n.t(`${namespace}.heading`)}</h1>
+               {type === 'tech' ?
+                       <TechFilter filter={filter} setFilter={setFilter} />
+               : null}
+       </div>
        <List techniques={techniques} />
 </Container>;
 
 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 (file)
index 0000000..008288a
--- /dev/null
@@ -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 <div className="tech-filter">
+               <div>{t('techniques.rulesetFilterHeading')}</div>
+               <div className="ruleset-box">
+                       {['competitive', 'owg', 'mg', 'nl'].map(r =>
+                               <Form.Check
+                                       checked={!!(filter && filter.ruleset && filter.ruleset[r] === '1')}
+                                       key={r}
+                                       id={`tech.filter.ruleset.${r}`}
+                                       name={`ruleset.${r}`}
+                                       label={t(`techniques.rulesetCodes.${r}`)}
+                                       onChange={handleChange}
+                                       title={t(`techniques.rulesetDescriptions.${r}`)}
+                                       type="checkbox"
+                               />
+                       )}
+               </div>
+       </div>;
+};
+
+TechFilter.propTypes = {
+       filter: PropTypes.shape({
+               ruleset: PropTypes.shape({
+               }),
+       }),
+       setFilter: PropTypes.func,
+};
+
+export default TechFilter;
index 1334fde3c9cca4f46dbae0856d55f276251e6fec..0812fd486b88d0aad321d5fd7bd13b34fcba25d4 100644 (file)
@@ -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: {
index d4e61bb7a7c5474de8f6ec82256de445e9cf5bff..030b023766103f0e8756817baeec399b10d16a9d 100644 (file)
@@ -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: {
index a28c4386f05acfcc989fa56c15f195fe931800f5..8355e70b61a8baccb281fe851b7e46fef17f2da9 100644 (file)
@@ -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 {