Add Settings and Licenses

This commit is contained in:
Kevin Kandlbinder 2022-05-05 00:58:32 +02:00
parent 6a7e2276f6
commit 87f685c5d7
13 changed files with 21214 additions and 12 deletions

View file

@ -1,6 +1,6 @@
{
"name": "kevins-data-toolbox",
"version": "2.4.7",
"version": "2.5.0",
"private": true,
"dependencies": {
"@loadable/component": "^5.15.0",

View file

@ -3,7 +3,8 @@
"title": "Kevins Datenkasten",
"navigation": {
"tools": "Werkzeuge",
"about": "Über"
"about": "Über",
"settings": "Einstellungen"
}
},
"home": {
@ -95,5 +96,18 @@
"description": "Im Werkzeug ist ein fatales Problem aufgetreten und die Aufgabe konnte nicht abgeschlossen werden. Bitte erneut versuchen."
}
}
},
"licenses": {
"title": "Quellcode-Lizenzen"
},
"settings": {
"title": "Einstellungen",
"currentVersion": "Aktuelle Version",
"offlineUnavailable": "Offline-Installation in deinem Browser nicht verfügbar.",
"checkingForUpdate": "Prüfe auf Aktualisierungen...",
"checkForUpdate": "Prüfe auf Aktualisierungen",
"version": "Version",
"about": "Über",
"source": "Quellcode"
}
}

View file

@ -3,7 +3,8 @@
"title": "Kevins Data-Toolbox",
"navigation": {
"tools": "Tools",
"about": "About"
"about": "About",
"settings": "Settings"
}
},
"home": {
@ -95,5 +96,18 @@
"description": "The tool encountered a fatal error and was unable to complete its task. Please retry."
}
}
},
"licenses": {
"title": "Open-Source-Licenses"
},
"settings": {
"title": "Settings",
"currentVersion": "Current Version",
"offlineUnavailable": "Offline-installation not available on your browser.",
"checkingForUpdate": "Checking for updates...",
"checkForUpdate": "Check for Updates",
"version": "Version",
"about": "About",
"source": "Source Code"
}
}

View file

@ -9,21 +9,22 @@ import Navigation from "./components/Navigation";
import * as styles from "./App.module.scss";
import NotFoundPage from "./pages/NotFound";
import ToolLoader from "./tools/ToolLoader";
import prerenderedLoadable from "./helpers/prerenderedLoadable";
import {version} from "../package.json"
const HomePage = prerenderedLoadable(() => import('./pages/Home'));
const ToolsPage = prerenderedLoadable(() => import('./pages/Tools'));
const AboutPage = prerenderedLoadable(() => import('./pages/About'));
const HomePage = React.lazy(() => import('./pages/Home'));
const ToolsPage = React.lazy(() => import('./pages/Tools'));
const AboutPage = React.lazy(() => import('./pages/About'));
const SettingsPage = React.lazy(() => import('./pages/Settings'));
const LicensesPage = React.lazy(() => import('./pages/Licenses'));
function App() {
return (
<Suspense fallback="Kevin's Data-Toolbox is loading...">
<Suspense fallback={<h1>Kevin's Data-Toolbox is loading...</h1>}>
<Router>
<div className={styles.appContainer}>
<Navigation/>
<Suspense fallback="Kevin's Data-Toolbox is loading...">
<Suspense fallback={<div className={styles.layoutBox}><h1>Kevin's Data-Toolbox is loading...</h1></div>}>
<Routes>
<Route path="/" element={<HomePage/>} />
@ -33,6 +34,10 @@ function App() {
<Route path="/tool/:tool" element={<ToolLoader/>} />
<Route path="/about" element={<AboutPage/>} />
<Route path="/settings" element={<SettingsPage/>} />
<Route path="/licenses" element={<LicensesPage/>} />
<Route path="*" element={<NotFoundPage/>} />
</Routes>

View file

@ -4,7 +4,7 @@ import { Link } from "react-router-dom";
import { useTranslation } from 'react-i18next';
import * as styles from "./Navigation.module.scss";
import { Globe, Info, List, RefreshCw } from "lucide-react";
import { Globe, List, RefreshCw, Wrench } from "lucide-react";
import LanguageChooser from "./LanguageChooser";
import ServiceWorkerAPI from "../services/serviceWorkers"
@ -20,7 +20,7 @@ const Navigation = () => {
<Link to={"/"}>{t("site.title")}</Link>
<span className={styles.spacer}></span>
<Link to={"/tools"} title={t("site.navigation.tools")}><List/></Link>
<Link to={"/about"} title={t("site.navigation.about")}><Info/></Link>
<Link to={"/settings"} title={t("site.navigation.settings")}><Wrench/></Link>
<Link to={"#"} onClick={() => {setLangChooserActive(true)}} title="Change Language"><Globe/></Link>
{updateAvailable && <Link to={"#"} onClick={() => {ServiceWorkerAPI.forceUpdate()}} title="Update Available"><RefreshCw /></Link>}

21010
src/licenses.ts Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
@import "../common";

4
src/pages/Licenses.module.scss.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
export const center: string;
export const flexList: string;
export const layoutBox: string;
export const title: string;

27
src/pages/Licenses.tsx Normal file
View file

@ -0,0 +1,27 @@
import React from "react";
import * as styles from "./Licenses.module.scss";
import { useTranslation } from 'react-i18next';
import { Helmet } from "react-helmet";
import text from "../licenses";
const LicensesPage = () => {
const { t } = useTranslation();
return <>
<Helmet><title>{t("licenses.title")} | {t("site.title")}</title></Helmet>
<main>
<div className={styles.layoutBox}>
<h1>{t("licenses.title")}</h1>
<pre style={{wordWrap: "break-word", overflow: "hidden", lineBreak: "anywhere", whiteSpace: "break-spaces"}}>
{text}
</pre>
</div>
</main>
</>;
}
export default LicensesPage;

View file

@ -0,0 +1,42 @@
@use "sass:math";
@import "../common";
.settingsSection {
display: flex;
flex-direction: column;
padding: $layoutPadding 0;
border-bottom: thin solid rgba(0, 0, 0, .1);
@media(prefers-color-scheme: dark) {
border-bottom: thin solid rgba(255, 255, 255, .1);
}
> .sectionHeader {
font-weight: 700;
margin-bottom: $layoutPadding;
}
> a {
display: flex;
justify-content: center;
color: white;
text-decoration: none;
border-top: thin solid rgba(0, 0, 0, .1);
border-bottom: thin solid rgba(0, 0, 0, .1);
padding: math.div($layoutPadding, 2);
margin: 0 math.div($layoutPadding, 2);
@media(prefers-color-scheme: dark) {
border-top: thin solid rgba(255, 255, 255, .1);
border-bottom: thin solid rgba(255, 255, 255, .1);
}
> span {
flex-grow: 1;
}
}
> a ~ a {
border-top: none;
}
}

6
src/pages/Settings.module.scss.d.ts vendored Normal file
View file

@ -0,0 +1,6 @@
export const center: string;
export const flexList: string;
export const layoutBox: string;
export const sectionHeader: string;
export const settingsSection: string;
export const title: string;

44
src/pages/Settings.tsx Normal file
View file

@ -0,0 +1,44 @@
import { ChevronRight } from "lucide-react";
import React from "react";
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { version } from "../../package.json";
import ServiceWorkerAPI from "../services/serviceWorkers";
import * as styles from "./Settings.module.scss";
const Settings = () => {
const {t} = useTranslation();
const {checkingForUpdate, checkForUpdate} = ServiceWorkerAPI.useCheckUpdate()
return <>
<Helmet><title>{t("settings.title")} | {t("site.title")}</title></Helmet>
<div className={styles.layoutBox}>
<h1>{t("settings.title")}</h1>
<div className={styles.settingsSection}>
<span className={styles.sectionHeader}>{t("settings.version")}</span>
<div>
<span>{t("settings.currentVersion")}: {version}</span>{" "}
{!ServiceWorkerAPI.serviceWorkerAvailable() && <span>{t("settings.offlineUnavailable")}</span>}
{ServiceWorkerAPI.serviceWorkerAvailable() && (<button onClick={() => {checkForUpdate()}} disabled={checkingForUpdate ? true : undefined}>{checkingForUpdate ? t("settings.checkingForUpdate") : t("settings.checkForUpdate")}</button>)}
</div>
</div>
<div className={styles.settingsSection}>
<span className={styles.sectionHeader}>{t("settings.about")}</span>
<Link to={"/about"}><span>{t("about.title")}</span><ChevronRight/></Link>
<Link to={"/licenses"}><span>{t("licenses.title")}</span><ChevronRight/></Link>
<a href={"https://github.com/Unkn0wnCat/data-toolbox-site"} target={"_blank"} rel="noreferrer"><span>{t("settings.source")}</span><ChevronRight/></a>
</div>
</div>
</>;
}
export default Settings;

View file

@ -98,13 +98,48 @@ const useUpdatePending = () => {
return updatePending
}
const serviceWorkerAvailable = () => {
return 'serviceWorker' in navigator;
}
const checkUpdate = async (): Promise<boolean> => {
if('serviceWorker' in navigator) {
try {
const reg = await navigator.serviceWorker.getRegistration()
if(!reg) return false;
await reg.update();
} catch(e) {
return false;
}
}
return false;
}
const useCheckUpdate = () => {
const [checkingForUpdate, setChecking] = useState(false);
const checkForUpdate = async () => {
setChecking(true);
await checkUpdate();
setChecking(false);
}
return {checkingForUpdate, checkForUpdate};
}
const ServiceWorkerAPI = {
on,
off,
forceUpdate,
isUpdatePending,
events,
useUpdatePending
useUpdatePending,
serviceWorkerAvailable,
useCheckUpdate
}
export default ServiceWorkerAPI