Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 8 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Routes>
<Route path="/" element={<Home />} ></Route>
<Route path="ua" element={<UnitsPage who="ua"/>} />
<Route path="ru" element={<UnitsPage who="ru"/>} />
<Route path="/*" element={<Navigate to="/" />} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
<Routes>
<Route path="/" element={<Home />} ></Route>
<Route path="ua" element={<UnitsPage who="ua" />} />
<Route path="ru" element={<UnitsPage who="ru" />} />
<Route path="/*" element={<Navigate to="/" />} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
);
};

Expand Down
178 changes: 82 additions & 96 deletions src/UnitsPage.tsx → src/hooks/useUnitsPage.ts
Original file line number Diff line number Diff line change
@@ -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<HTMLInputElement>) => void;
debouncedQueryHandler: (e: React.ChangeEvent<HTMLInputElement>) => void;
resetForm: () => void;
onCompactMode: () => void;
onHamburgerClick: (e: React.MouseEvent<HTMLDivElement>) => void;
onHamburgerItemClick: () => void;
}

export const useUnitsPage = ({ who }: UseUnitsPageProps): UseUnitsPageReturn => {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState([]);
const [data, setData] = useState<Unit[]>([]);
const [query, setQuery] = useState<string[]>([]);
const [isCompactMode, setIsCompactMode] = useState(true);

const { who } = props;
// Determine title and datafile based on 'who' prop
let title = "";
let datafile = '';
switch (who) {
Expand All @@ -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<HTMLInputElement>): void => {
const aQuery = (e.target.value).toLocaleLowerCase();
if (aQuery === '') {
Expand All @@ -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<HTMLDivElement>) => {
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');
Expand All @@ -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));
}
Expand All @@ -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
Expand All @@ -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= (
<div key={idx} id={jumpKey} className="unit-wrapper">
<Unit {...unitData} compact={isCompactMode} searchmode={searchmode} />
</div>
);
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 <HashLink to={`#${jumperKey}`} className="jumper-item" onClick={onHamburgerItemClick}>{bu.name}</HashLink>;
});

const content = isLoading
? <div className="loading">Loading...</div>
: (<div className="units">{units}</div>);

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 (
<div className="unitspage">
<header>
<Link to="/" id="homelink">[ Home ]</Link>
<h3 className="units-title">{title}</h3>
<button id="compactmode" onClick={onCompactMode}>[ {compactModeText} ]</button>
</header>
<div id="opbox">
<div id="filterbox">Filter Units:
<form>
<input type="text" onChange={debouncedQueryHandler} placeholder="type a query"/>
<button type="reset" className={resetButtonClass} onClick={() => resetForm()}>&times;</button>
</form>
</div>
<div id="helpbox"><a href="https://twitter.com/intent/tweet?text=@UAControlMap" target="_blank">Tweet us to report any corrections/mistakes/additions</a></div>
<div id="jumperbox">
<div id="hamburger" onClick={onHamburgerClick}>
<span className="bar"></span>
<span className="bar"></span>
<span className="bar"></span>
</div>
<div id="jumpercontent">{jumperContent}</div>
</div>
</div>
<div className='scrollbox'>
<div className="max-wrapper">{units}</div>
</div>
<Tooltip anchorSelect=".patch-tooltip" id="patch-tooltip"/>
</div>

);
return {
// isLoading,
filteredData,
query,
isCompactMode,
baseUnits,
searchmode,
title,
compactModeText,
resetButtonClass,
onQueryChangeHandler,
debouncedQueryHandler,
resetForm,
onCompactMode,
onHamburgerClick,
onHamburgerItemClick,
};
};

export default UnitsPage;
Loading