mirror of
https://github.com/Unkn0wnCat/data-toolbox-site.git
synced 2025-05-25 13:46:11 +02:00
Bootstrap Kevin's Data-Toolbox
This commit is contained in:
parent
4da936864f
commit
3f4d6da00b
30 changed files with 1255 additions and 120 deletions
|
@ -6,8 +6,16 @@
|
||||||
"@testing-library/jest-dom": "^5.11.4",
|
"@testing-library/jest-dom": "^5.11.4",
|
||||||
"@testing-library/react": "^11.1.0",
|
"@testing-library/react": "^11.1.0",
|
||||||
"@testing-library/user-event": "^12.1.10",
|
"@testing-library/user-event": "^12.1.10",
|
||||||
|
"i18next": "^20.2.1",
|
||||||
|
"i18next-browser-languagedetector": "^6.1.0",
|
||||||
|
"i18next-http-backend": "^1.2.1",
|
||||||
|
"node-sass": "^5.0.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"react-feather": "^2.0.9",
|
||||||
|
"react-helmet": "^6.1.0",
|
||||||
|
"react-i18next": "^11.8.12",
|
||||||
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "4.0.3",
|
||||||
"web-vitals": "^1.0.1"
|
"web-vitals": "^1.0.1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,37 +7,19 @@
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Web site created using create-react-app"
|
content="Tools crafted for your enjoyment."
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
<!--
|
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
|
||||||
-->
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
<!--
|
<title>Kevin's Data-Toolbox</title>
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;700;800&display=swap" rel="stylesheet">
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
|
||||||
-->
|
|
||||||
<title>React App</title>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run Kevin's Data-Toolbox.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<!--
|
<!--
|
||||||
This HTML file is a template.
|
Hello you source-dwelling human. If you like source-code, you should check out my GitHub-Account at https://github.com/Unkn0wnCat
|
||||||
If you open it directly in the browser, you will see an empty page.
|
|
||||||
|
|
||||||
You can add webfonts, meta tags, or analytics to this file.
|
|
||||||
The build step will place the bundled scripts into the <body> tag.
|
|
||||||
|
|
||||||
To begin the development, run `npm start` or `yarn start`.
|
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
|
||||||
-->
|
-->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
34
public/locales/de/translation.json
Normal file
34
public/locales/de/translation.json
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"site": {
|
||||||
|
"title": "Kevins Datenkasten",
|
||||||
|
"navigation": {
|
||||||
|
"tools": "Werkzeuge",
|
||||||
|
"about": "Über"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"heroPretitle": "Kevins",
|
||||||
|
"heroTitle": "Datenkasten",
|
||||||
|
"heroSubtitle": "Dein 1-Stopp-Daten-Shop!"
|
||||||
|
},
|
||||||
|
"tools": {
|
||||||
|
"toolCategories": "Kategorien",
|
||||||
|
"toolList": "Liste von Werkzeugen",
|
||||||
|
"noresults": "Keine Ergebnisse.",
|
||||||
|
"categories": {
|
||||||
|
"everything": "Alles"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"about": {
|
||||||
|
"title": "Über Kevins Datenkasten",
|
||||||
|
"p1": "Kevins Datenkasten ist meine Kollektion von kleinen, nützlichen Werkzeugen. Schau doch mal ob du was nüzliches findest!",
|
||||||
|
"p2": "Ich werde mehr Werkzeuge hinzufügen sobald ich diese fertig habe, also schau regelmäßig wieder rein!",
|
||||||
|
"morebyme": "Mehr von mir",
|
||||||
|
"visitKevinKdev": "Schau dir meine Website unter <1>KevinK.dev</1> an!"
|
||||||
|
|
||||||
|
},
|
||||||
|
"system": {
|
||||||
|
"notfound": "Seite nicht gefunden",
|
||||||
|
"language": "Sprache"
|
||||||
|
}
|
||||||
|
}
|
34
public/locales/en/translation.json
Normal file
34
public/locales/en/translation.json
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"site": {
|
||||||
|
"title": "Kevin's Data-Toolbox",
|
||||||
|
"navigation": {
|
||||||
|
"tools": "Tools",
|
||||||
|
"about": "About"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"heroPretitle": "Kevin's",
|
||||||
|
"heroTitle": "Data-Toolbox",
|
||||||
|
"heroSubtitle": "Your One-Stop-Data-Shop!"
|
||||||
|
},
|
||||||
|
"tools": {
|
||||||
|
"toolCategories": "Categories",
|
||||||
|
"toolList": "List of tools",
|
||||||
|
"noresults": "No results have been found.",
|
||||||
|
"categories": {
|
||||||
|
"everything": "Everything"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"about": {
|
||||||
|
"title": "About Kevin's Data-Toolbox",
|
||||||
|
"p1": "Kevin's Data-Toolbox is my collection of useful small tools. Feel free to look through them to see if there anything is of use to you!",
|
||||||
|
"p2": "There will be more tools added over time as I create them, so check back regularly to learn about new tools!",
|
||||||
|
"morebyme": "More By Me",
|
||||||
|
"visitKevinKdev": "Check out my website <1>KevinK.dev</1>!"
|
||||||
|
|
||||||
|
},
|
||||||
|
"system": {
|
||||||
|
"notfound": "Page Not Found",
|
||||||
|
"language": "Language"
|
||||||
|
}
|
||||||
|
}
|
38
src/App.css
38
src/App.css
|
@ -1,38 +0,0 @@
|
||||||
.App {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-logo {
|
|
||||||
height: 40vmin;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
|
||||||
.App-logo {
|
|
||||||
animation: App-logo-spin infinite 20s linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-header {
|
|
||||||
background-color: #282c34;
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: calc(10px + 2vmin);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-link {
|
|
||||||
color: #61dafb;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes App-logo-spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
47
src/App.js
47
src/App.js
|
@ -1,24 +1,37 @@
|
||||||
import logo from './logo.svg';
|
import React, { lazy, Suspense } from "react";
|
||||||
import './App.css';
|
import {
|
||||||
|
BrowserRouter as Router,
|
||||||
|
Switch,
|
||||||
|
Route
|
||||||
|
} from "react-router-dom";
|
||||||
|
import Navigation from "./components/Navigation";
|
||||||
|
|
||||||
|
import * as styles from "./App.module.scss";
|
||||||
|
import NotFoundPage from "./pages/NotFound";
|
||||||
|
import ToolLoader from "./tools/ToolLoader";
|
||||||
|
|
||||||
|
const HomePage = lazy(() => import('./pages/Home'));
|
||||||
|
const ToolsPage = lazy(() => import('./pages/Tools'));
|
||||||
|
const AboutPage = lazy(() => import('./pages/About'));
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<Suspense fallback="Kevin's Data-Toolbox is loading...">
|
||||||
<header className="App-header">
|
<Router>
|
||||||
<img src={logo} className="App-logo" alt="logo" />
|
<div className={styles.appContainer}>
|
||||||
<p>
|
<Navigation/>
|
||||||
Edit <code>src/App.js</code> and save to reload.
|
<Suspense fallback="Kevin's Data-Toolbox is loading...">
|
||||||
</p>
|
<Switch>
|
||||||
<a
|
<Route path="/about" component={AboutPage} />
|
||||||
className="App-link"
|
<Route path="/tools/:category?" component={ToolsPage} />
|
||||||
href="https://reactjs.org"
|
<Route path="/tool/:tool" component={ToolLoader} />
|
||||||
target="_blank"
|
<Route path="/" exact component={HomePage} />
|
||||||
rel="noopener noreferrer"
|
<Route path="*" component={NotFoundPage} />
|
||||||
>
|
</Switch>
|
||||||
Learn React
|
</Suspense>
|
||||||
</a>
|
|
||||||
</header>
|
|
||||||
</div>
|
</div>
|
||||||
|
</Router>
|
||||||
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
13
src/App.module.scss
Normal file
13
src/App.module.scss
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
@import "./common";
|
||||||
|
|
||||||
|
.appContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
|
||||||
|
padding-top: $layoutNavigationHeight;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
|
||||||
test('renders learn react link', () => {
|
/*test('renders learn react link', () => {
|
||||||
render(<App />);
|
render(<App />);
|
||||||
const linkElement = screen.getByText(/learn react/i);
|
const linkElement = screen.getByText(/about/i);
|
||||||
expect(linkElement).toBeInTheDocument();
|
expect(linkElement).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
*/
|
36
src/_common.scss
Normal file
36
src/_common.scss
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
$fontFamily: 'Open Sans', sans-serif;
|
||||||
|
|
||||||
|
$colorAccent: #f58428;
|
||||||
|
|
||||||
|
$layoutWidth: 1200px;
|
||||||
|
$layoutPadding: 20px;
|
||||||
|
$layoutNavigationHeight: 50px;
|
||||||
|
|
||||||
|
@mixin layoutBox() {
|
||||||
|
max-width: $layoutWidth;
|
||||||
|
padding: 0 $layoutPadding;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.layoutBox {
|
||||||
|
@include layoutBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: block;
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 3em;
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flexList {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
margin: $layoutPadding;
|
||||||
|
}
|
||||||
|
}
|
25
src/components/LanguageChooser.js
Normal file
25
src/components/LanguageChooser.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import * as styles from "./LanguageChooser.module.scss";
|
||||||
|
|
||||||
|
const LanguageChooser = (props) => {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
|
return ReactDOM.createPortal(
|
||||||
|
<div className={styles.lChooser + " " + (props.active ? styles.active : "")}>
|
||||||
|
<div>
|
||||||
|
<span className={styles.title}>{t("system.language")}</span>
|
||||||
|
<Link to={"#"} onClick={() => {i18n.changeLanguage("en"); props.onDone()}}>English</Link>
|
||||||
|
<Link to={"#"} onClick={() => {i18n.changeLanguage("de"); props.onDone()}}>Deutsch</Link>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LanguageChooser;
|
||||||
|
|
45
src/components/LanguageChooser.module.scss
Normal file
45
src/components/LanguageChooser.module.scss
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
@import "../common";
|
||||||
|
|
||||||
|
.lChooser {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1000;
|
||||||
|
background-color: #000000e0;
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
transition: opacity .25s;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: visible;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
position: absolute;
|
||||||
|
top: 10%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: $layoutPadding;
|
||||||
|
}
|
||||||
|
}
|
35
src/components/LinkBox.js
Normal file
35
src/components/LinkBox.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import * as styles from "./LinkBox.module.scss";
|
||||||
|
|
||||||
|
const LinkBox = (props) => {
|
||||||
|
return (!props.external ?
|
||||||
|
<Link className={styles.linkBox + (props.small ? " "+styles.small : "") + (props.highlight ? " "+styles.highlight : "")} to={props.to}>
|
||||||
|
<div className={styles.lbIcon}><props.icon/></div>
|
||||||
|
<span className={styles.lbText}>{props.text}</span>
|
||||||
|
</Link> :
|
||||||
|
<a className={styles.linkBox + (props.small ? " "+styles.small : "") + (props.highlight ? " "+styles.highlight : "")} href={props.to}>
|
||||||
|
<div className={styles.lbIcon}><props.icon/></div>
|
||||||
|
<span className={styles.lbText}>{props.text}</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkBox.defaultProps = {
|
||||||
|
"small": false,
|
||||||
|
"highlight": false,
|
||||||
|
"external": false
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkBox.props = {
|
||||||
|
"to": PropTypes.string,
|
||||||
|
"text": PropTypes.string.isRequired,
|
||||||
|
"icon": PropTypes.object.isRequired,
|
||||||
|
"small": PropTypes.bool,
|
||||||
|
"highlight": PropTypes.bool,
|
||||||
|
"external": PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LinkBox;
|
56
src/components/LinkBox.module.scss
Normal file
56
src/components/LinkBox.module.scss
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
@import "../common";
|
||||||
|
|
||||||
|
.linkBox {
|
||||||
|
padding: 30px;
|
||||||
|
color: $colorAccent;
|
||||||
|
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: 0 0 10px rgba(black, .25);
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 250px;
|
||||||
|
|
||||||
|
.lbIcon {
|
||||||
|
svg {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lbText {
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.highlight {
|
||||||
|
background-color: $colorAccent;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
width: unset;
|
||||||
|
border-radius: 100px;
|
||||||
|
display: flex;
|
||||||
|
padding: 5px 15px;
|
||||||
|
line-height: 25px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.lbIcon {
|
||||||
|
height: 25px;
|
||||||
|
svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin: 2.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lbText {
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
src/components/Navigation.js
Normal file
29
src/components/Navigation.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import * as styles from "./Navigation.module.scss";
|
||||||
|
import { Globe } from "react-feather";
|
||||||
|
import LanguageChooser from "./LanguageChooser";
|
||||||
|
|
||||||
|
const Navigation = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [langChooserActive, setLangChooserActive] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.navigation}>
|
||||||
|
<nav>
|
||||||
|
<Link to={"/"}>{t("site.title")}</Link>
|
||||||
|
<span className={styles.spacer}></span>
|
||||||
|
<Link to={"/tools"}>{t("site.navigation.tools")}</Link>
|
||||||
|
<Link to={"/about"}>{t("site.navigation.about")}</Link>
|
||||||
|
<Link to={"#"} onClick={() => {setLangChooserActive(true)}}><Globe/></Link>
|
||||||
|
<LanguageChooser active={langChooserActive} onDone={() => {setLangChooserActive(false)}} />
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Navigation;
|
39
src/components/Navigation.module.scss
Normal file
39
src/components/Navigation.module.scss
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
@import "../common";
|
||||||
|
|
||||||
|
.navigation {
|
||||||
|
position: fixed;
|
||||||
|
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: $layoutNavigationHeight;
|
||||||
|
background-color: #1c1c1c;
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
@supports(backdrop-filter: blur(5px)) {
|
||||||
|
background-color: #000000e0;
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
> nav {
|
||||||
|
max-width: $layoutWidth;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
height: $layoutNavigationHeight;
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
> a {
|
||||||
|
padding: 0 $layoutPadding;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
src/i18n.js
Normal file
31
src/i18n.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import i18n from 'i18next';
|
||||||
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
|
||||||
|
import Backend from 'i18next-http-backend';
|
||||||
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
|
// don't want to use this?
|
||||||
|
// have a look at the Quick start guide
|
||||||
|
// for passing in lng and translations on init
|
||||||
|
|
||||||
|
i18n
|
||||||
|
// load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales)
|
||||||
|
// learn more: https://github.com/i18next/i18next-http-backend
|
||||||
|
.use(Backend)
|
||||||
|
// detect user language
|
||||||
|
// learn more: https://github.com/i18next/i18next-browser-languageDetector
|
||||||
|
.use(LanguageDetector)
|
||||||
|
// pass the i18n instance to react-i18next.
|
||||||
|
.use(initReactI18next)
|
||||||
|
// init i18next
|
||||||
|
// for all options read: https://www.i18next.com/overview/configuration-options
|
||||||
|
.init({
|
||||||
|
fallbackLng: 'en',
|
||||||
|
debug: true,
|
||||||
|
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false, // not needed for react as it escapes by default
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export default i18n;
|
|
@ -1,13 +0,0 @@
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
||||||
monospace;
|
|
||||||
}
|
|
|
@ -1,9 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import './index.css';
|
import './index.scss';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from './reportWebVitals';
|
||||||
|
|
||||||
|
import './i18n';
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<App />
|
||||||
|
|
18
src/index.scss
Normal file
18
src/index.scss
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
@import "./common";
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body, #root {
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
font-family: $fontFamily;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $colorAccent;
|
||||||
|
text-decoration: underline dotted currentColor;
|
||||||
|
}
|
27
src/pages/About.js
Normal file
27
src/pages/About.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import * as styles from "./About.module.scss";
|
||||||
|
|
||||||
|
import { useTranslation, Trans } from 'react-i18next';
|
||||||
|
|
||||||
|
const AboutPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return ([
|
||||||
|
<div>
|
||||||
|
<div className={styles.layoutBox}>
|
||||||
|
<h1>{t("about.title")}</h1>
|
||||||
|
|
||||||
|
<p>{t("about.p1")}</p>
|
||||||
|
|
||||||
|
<p>{t("about.p2")}</p>
|
||||||
|
|
||||||
|
<h2>{t("about.morebyme")}</h2>
|
||||||
|
|
||||||
|
<p><Trans i18nKey={"about.visitKevinKdev"}> <a href="https://kevink.dev"> </a> </Trans></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AboutPage;
|
1
src/pages/About.module.scss
Normal file
1
src/pages/About.module.scss
Normal file
|
@ -0,0 +1 @@
|
||||||
|
@import "../common";
|
33
src/pages/Home.js
Normal file
33
src/pages/Home.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import React from "react";
|
||||||
|
import { List } from 'react-feather';
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import * as styles from "./Home.module.scss";
|
||||||
|
import LinkBox from "../components/LinkBox";
|
||||||
|
|
||||||
|
const HomePage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return ([
|
||||||
|
<div className={styles.heroBox}>
|
||||||
|
<div className={styles.layoutBox}>
|
||||||
|
<span className={styles.heroPretitle}>{t("home.heroPretitle")}</span>
|
||||||
|
<span className={styles.heroTitle}>{t("home.heroTitle")}</span>
|
||||||
|
<span className={styles.heroSubtitle}>{t("home.heroSubtitle")}</span>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
<div className={styles.categoryBox}>
|
||||||
|
<div className={styles.layoutBox}>
|
||||||
|
<span className={styles.title}>{t("tools.toolCategories")}</span>
|
||||||
|
|
||||||
|
<div className={styles.flexList}>
|
||||||
|
<LinkBox to={"/tools"} text={t("tools.categories.everything")} icon={List} />
|
||||||
|
{/*<LinkBox to={"/tools/osm"} text={"OSM"} icon={Map} />*/}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HomePage;
|
41
src/pages/Home.module.scss
Normal file
41
src/pages/Home.module.scss
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
@import "../common";
|
||||||
|
|
||||||
|
.heroBox {
|
||||||
|
margin-top: -$layoutNavigationHeight;
|
||||||
|
|
||||||
|
background-color: #1c1c1c;
|
||||||
|
background-image: url(https://source.unsplash.com/uq5RMAZdZG4/1920x1080);
|
||||||
|
|
||||||
|
> div {
|
||||||
|
padding-top: 100px;
|
||||||
|
padding-bottom: 100px;
|
||||||
|
min-height: 600px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0 0 10px black, 0 0 10px black;
|
||||||
|
|
||||||
|
&.heroPretitle {
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: 100;
|
||||||
|
margin-bottom: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.heroTitle {
|
||||||
|
font-size: 4em;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.heroSubtitle {
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: -20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
src/pages/NotFound.js
Normal file
21
src/pages/NotFound.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Frown } from 'react-feather';
|
||||||
|
|
||||||
|
import * as styles from "./NotFound.module.scss";
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const NotFoundPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return ([
|
||||||
|
<div>
|
||||||
|
<div className={styles.layoutBox}>
|
||||||
|
<h1>{t("system.notfound")} <Frown/></h1>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NotFoundPage;
|
1
src/pages/NotFound.module.scss
Normal file
1
src/pages/NotFound.module.scss
Normal file
|
@ -0,0 +1 @@
|
||||||
|
@import "../common";
|
45
src/pages/Tools.js
Normal file
45
src/pages/Tools.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import React from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import * as icons from 'react-feather';
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import * as styles from "./Tools.module.scss";
|
||||||
|
import LinkBox from "../components/LinkBox";
|
||||||
|
|
||||||
|
import { tools } from "../tools/tools.json";
|
||||||
|
|
||||||
|
const ToolsPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
let { category } = useParams();
|
||||||
|
|
||||||
|
if(category) category = category.toLowerCase();
|
||||||
|
|
||||||
|
let toolList = tools.filter((tool) => {
|
||||||
|
return !tool.hidden && (category == null || tool.category === category);
|
||||||
|
});
|
||||||
|
|
||||||
|
return ([
|
||||||
|
<div className={styles.categoryBox}>
|
||||||
|
<div className={styles.layoutBox}>
|
||||||
|
<span className={styles.title}>{t("tools.toolList")}</span>
|
||||||
|
|
||||||
|
<div className={styles.flexList}>
|
||||||
|
<LinkBox to={"/tools"} text={t("tools.categories.everything")} icon={icons["List"]} small={true} highlight={category == null} />
|
||||||
|
{/*<LinkBox to={"/tools/osm"} text={"OSM"} icon={icons["Map"]} small={true} highlight={category === "osm"} />*/}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.flexList}>
|
||||||
|
{toolList.map((tool, i) => {
|
||||||
|
return (<LinkBox key={"tool"+i} external={tool.external} to={tool.external ? tool.url : "/tool/"+tool.urlname} text={tool.name} icon={icons[tool.icon]} />);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{toolList.length === 0 ? <span>{t("tools.noresults")}</span> : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ToolsPage;
|
1
src/pages/Tools.module.scss
Normal file
1
src/pages/Tools.module.scss
Normal file
|
@ -0,0 +1 @@
|
||||||
|
@import "../common";
|
19
src/tools/ToolLoader.js
Normal file
19
src/tools/ToolLoader.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import React, { lazy } from "react";
|
||||||
|
import { useParams } from "react-router";
|
||||||
|
import NotFoundPage from "../pages/NotFound";
|
||||||
|
|
||||||
|
const HomePage = lazy(() => import('../pages/Home'));
|
||||||
|
|
||||||
|
const ToolLoader = () => {
|
||||||
|
const {tool} = useParams();
|
||||||
|
|
||||||
|
switch(tool) {
|
||||||
|
case "test":
|
||||||
|
return <HomePage/>;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return <NotFoundPage/>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ToolLoader;
|
20
src/tools/tools.json
Normal file
20
src/tools/tools.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "Test01",
|
||||||
|
"external": true,
|
||||||
|
"url": "https://kevink.dev",
|
||||||
|
"icon": "Smile",
|
||||||
|
"category": "osm",
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Test02",
|
||||||
|
"external": false,
|
||||||
|
"urlname": "test",
|
||||||
|
"icon": "Smile",
|
||||||
|
"category": "something",
|
||||||
|
"hidden": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue