diff --git a/package-lock.json b/package-lock.json index ce3ad3b5c..3643bfbd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,6 +72,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.0.tgz", "integrity": "sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.18.6", @@ -1009,6 +1010,7 @@ "url": "https://tidelift.com/funding/github/npm/browserslist" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001449", "electron-to-chromium": "^1.4.284", @@ -1832,6 +1834,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -1843,6 +1846,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -1901,6 +1905,7 @@ "version": "6.8.2", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.2.tgz", "integrity": "sha512-N/oAF1Shd7g4tWy+75IIufCGsHBqT74tnzHQhbiUTYILYF0Blk65cg+HPZqwC+6SqEyx033nKqU7by38v3lBZg==", + "peer": true, "dependencies": { "@remix-run/router": "1.3.3", "react-router": "6.8.2" @@ -2022,6 +2027,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.58.3.tgz", "integrity": "sha512-Q7RaEtYf6BflYrQ+buPudKR26/lH+10EmO9bBqbmPh/KeLqv8bjpTNqxe71ocONqXq+jYiCbpPUmQMS+JJPk4A==", "dev": true, + "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -2329,6 +2335,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-4.1.4.tgz", "integrity": "sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==", "dev": true, + "peer": true, "dependencies": { "esbuild": "^0.16.14", "postcss": "^8.4.21", diff --git a/src/App.tsx b/src/App.tsx index b56fc2509..4e410b31f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,17 +1,17 @@ import { Routes, Route, Navigate } from 'react-router-dom'; import Home from './Home'; -import UnitsPage from './UnitsPage'; +import UnitsPage from './pages/UnitsPage'; const App = () => { return ( - - } > - } /> - } /> - } /> - } /> - + + } > + } /> + } /> + } /> + } /> + ); }; diff --git a/src/UnitsPage.tsx b/src/hooks/useUnitsPage.ts similarity index 57% rename from src/UnitsPage.tsx rename to src/hooks/useUnitsPage.ts index fbfdce640..9917b3075 100644 --- a/src/UnitsPage.tsx +++ b/src/hooks/useUnitsPage.ts @@ -1,20 +1,35 @@ -import React, { useEffect, useState, useMemo } from 'react'; -import {Link} from 'react-router-dom'; +import { useEffect, useState, useMemo } from 'react'; import debounce from 'lodash.debounce'; -import 'react-tooltip/dist/react-tooltip.css'; -import Unit from './Unit'; -import { Tooltip } from 'react-tooltip'; -import { HashLink } from 'react-router-hash-link'; - - -const UnitsPage = (props: UnitsPage) => { +interface UseUnitsPageProps { + who: string; +} + +interface UseUnitsPageReturn { + // isLoading: boolean; + filteredData: Unit[]; + query: string[]; + isCompactMode: boolean; + baseUnits: baseUnit[]; + searchmode: boolean; + title: string; + compactModeText: string; + resetButtonClass: string; + onQueryChangeHandler: (e: React.ChangeEvent) => void; + debouncedQueryHandler: (e: React.ChangeEvent) => void; + resetForm: () => void; + onCompactMode: () => void; + onHamburgerClick: (e: React.MouseEvent) => void; + onHamburgerItemClick: () => void; +} + +export const useUnitsPage = ({ who }: UseUnitsPageProps): UseUnitsPageReturn => { const [isLoading, setIsLoading] = useState(true); - const [data, setData] = useState([]); + const [data, setData] = useState([]); const [query, setQuery] = useState([]); const [isCompactMode, setIsCompactMode] = useState(true); - const { who } = props; + // Determine title and datafile based on 'who' prop let title = ""; let datafile = ''; switch (who) { @@ -25,11 +40,12 @@ const UnitsPage = (props: UnitsPage) => { case 'ru': title = 'Russian Units'; datafile = 'ru.json'; - break; + break; default: break; } + // Query change handler const onQueryChangeHandler = (e: React.ChangeEvent): void => { const aQuery = (e.target.value).toLocaleLowerCase(); if (aQuery === '') { @@ -38,47 +54,54 @@ const UnitsPage = (props: UnitsPage) => { const queryWords = (e.target.value).toLocaleLowerCase().split(' '); setQuery(queryWords); } - } + }; + + // Debounced query handler const debouncedQueryHandler = useMemo( - () => debounce(onQueryChangeHandler, 750) - , []); + () => debounce(onQueryChangeHandler, 750), + [] + ); + // Reset form const resetForm = () => { setQuery([]); - } + }; + // Toggle compact mode const onCompactMode = () => { setIsCompactMode(!isCompactMode); - } + }; + // Fetch data from JSON file const fetchData = () => { fetch(`./${datafile}`) .then((res) => res.json()) .then((res) => { setData(res); setIsLoading(false); - }) - } + }); + }; useEffect(() => { fetchData(); }, []); + // Hamburger menu handlers const onHamburgerClick = (e: React.MouseEvent) => { const elem = e.currentTarget; elem.classList.toggle('checked'); const jumperContent = document.getElementById('jumpercontent'); - jumperContent?.classList.toggle('show') - } + jumperContent?.classList.toggle('show'); + }; + const onHamburgerItemClick = () => { const hamburger = document.getElementById('hamburger'); hamburger?.classList.toggle('checked'); const jumperContent = document.getElementById('jumpercontent'); - jumperContent?.classList.toggle('show') - } + jumperContent?.classList.toggle('show'); + }; - - // filter data + // Filter logic const queryCheck = (aString: string): boolean => { for (let i = 0; i < query.length; i++) { const pattern = new RegExp(query[i], 'g'); @@ -87,11 +110,12 @@ const UnitsPage = (props: UnitsPage) => { } } return true; // all found - } - const filterUnit = (unit: Unit) => { + }; + + const filterUnit = (unit: Unit): boolean => { const { name, subunits, meta, patches } = unit; // first go into the most nested unit and work your way up - let filteredSubUnits: any = []; + let filteredSubUnits: Unit[] = []; if (Array.isArray(subunits)) { filteredSubUnits = subunits.filter((subunit) => filterUnit(subunit)); } @@ -106,12 +130,11 @@ const UnitsPage = (props: UnitsPage) => { isPatternInUnitName = queryCheck(name.toLocaleLowerCase()); // name check } if (meta && meta.description !== undefined) { - isPatternInDescription = queryCheck(meta.description.toLocaleLowerCase()); // name check + isPatternInDescription = queryCheck(meta.description.toLocaleLowerCase()); // description check } if (meta && meta.tags !== undefined) { - console.log(meta.tags) isPatternInTags = queryCheck(meta.tags.toLocaleLowerCase()); // tags check - } + } const patternFound = isPatternInUnitName || isPatternInTags || isPatternInDescription; // in compact mode add an empty patch in case there was a pattern match // otherwise nothing would be shown @@ -128,82 +151,45 @@ const UnitsPage = (props: UnitsPage) => { // If the unit itself has matches, just return true // otherwise return false return patternFound; - } + }; + + // Filter data based on query let filteredData = JSON.parse(JSON.stringify(data)) as Unit[]; // deep clone of the array of object + if (query.length > 0) { filteredData = filteredData.filter(unit => filterUnit(unit)); } - // generate all unit components - const units: any = []; - - // are we in search mode? - const searchmode = query && query.length > 0; - - // loop through all base units - const baseUnits: baseUnit[] = [] + // Generate base units for navigation + const baseUnits: baseUnit[] = []; filteredData.forEach((unitData: Unit, idx) => { - const jumpKey = `cat-${idx}`; - const unit= ( -
- -
- ); - units.push(unit); const baseUnit: baseUnit = { name: unitData.name, jumpKey: idx - } - baseUnits.push(baseUnit) + }; + baseUnits.push(baseUnit); }); - const jumperContent = baseUnits.map((bu, idx) => { - const jumperKey = `cat-${idx}`; - return {bu.name}; - }); - - const content = isLoading - ?
Loading...
- : (
{units}
); - - const compactModeText = isCompactMode - ? 'Details' - : 'Compact'; - + // Computed values + const searchmode = query && query.length > 0; + const compactModeText = isCompactMode ? 'Details' : 'Compact'; const resetButtonClass = searchmode ? 'show' : 'hide'; - // finally, return everything - return ( -
-
- [ Home ] -

{title}

- -
-
-
Filter Units: -
- - -
-
- -
-
- - - -
-
{jumperContent}
-
-
-
-
{units}
-
- -
- - ); + return { + // isLoading, + filteredData, + query, + isCompactMode, + baseUnits, + searchmode, + title, + compactModeText, + resetButtonClass, + onQueryChangeHandler, + debouncedQueryHandler, + resetForm, + onCompactMode, + onHamburgerClick, + onHamburgerItemClick, + }; }; - -export default UnitsPage; \ No newline at end of file diff --git a/src/pages/UnitsPage.tsx b/src/pages/UnitsPage.tsx new file mode 100644 index 000000000..0b8d1533b --- /dev/null +++ b/src/pages/UnitsPage.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import 'react-tooltip/dist/react-tooltip.css'; +import Unit from '../Unit'; +import { Tooltip } from 'react-tooltip'; +import { HashLink } from 'react-router-hash-link'; +import { useUnitsPage } from '../hooks/useUnitsPage'; + +const UnitsPage = (props: UnitsPage) => { + const { + // isLoading, + filteredData, + isCompactMode, + baseUnits, + searchmode, + title, + compactModeText, + resetButtonClass, + debouncedQueryHandler, + resetForm, + onCompactMode, + onHamburgerClick, + onHamburgerItemClick, + } = useUnitsPage({ who: props.who }); + + // Generate all unit components + const units = filteredData.map((unitData: Unit, idx) => { + const jumpKey = `cat-${idx}`; + return ( +
+ +
+ ); + }); + + const jumperContent = baseUnits.map((bu, idx) => { + const jumperKey = `cat-${bu.jumpKey}`; + return ( + + {bu.name} + + ); + }); + + // TODO: add loading state + // const content = isLoading + // ?
Loading...
+ // : (
{units}
); + + // finally, return everything + return ( +
+
+ [ Home ] +

{title}

+ +
+
+
Filter Units: +
+ + +
+
+ +
+
+ + + +
+
{jumperContent}
+
+
+
+
{units}
+
+ +
+ + ); +}; + +export default UnitsPage; \ No newline at end of file