add status
This commit is contained in:
parent
d06f43682a
commit
a45002f693
18 changed files with 233 additions and 16 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,4 +3,5 @@ dist
|
|||
certs
|
||||
.env.local
|
||||
.env.production
|
||||
.env.staging
|
||||
stepData.ndjson
|
6
Makefile
6
Makefile
|
@ -1,3 +1,3 @@
|
|||
deploy:
|
||||
pnpm run build --base=/ddr
|
||||
rsync -azrP dist/ root@veil:/home/blogDeploy/public/ddr
|
||||
deploy-staging:
|
||||
pnpm run build --mode staging --base=/ddr-staging
|
||||
rsync -azrP dist/ root@veil:/home/blogDeploy/public/ddr-staging
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<title>DDR Companion</title>
|
||||
<meta name="description" content="My Awesome App description">
|
||||
<link rel="icon" href="/ddr/favicon.ico">
|
||||
<link rel="icon" href="%BASE_URL%/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
</head>
|
||||
|
|
5
lib/utils.ts
Normal file
5
lib/utils.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "tsc",
|
||||
"generate-pwa-assets": "pwa-assets-generator"
|
||||
},
|
||||
|
@ -35,6 +36,8 @@
|
|||
"@mui/icons-material": "^5.15.17",
|
||||
"@mui/material": "^5.15.17",
|
||||
"@tanstack/react-query": "^5.36.1",
|
||||
"classnames": "^2.5.1",
|
||||
"jotai": "^2.8.0",
|
||||
"ndjson": "^2.0.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
|
|
@ -23,6 +23,12 @@ importers:
|
|||
'@tanstack/react-query':
|
||||
specifier: ^5.36.1
|
||||
version: 5.36.1(react@18.3.1)
|
||||
classnames:
|
||||
specifier: ^2.5.1
|
||||
version: 2.5.1
|
||||
jotai:
|
||||
specifier: ^2.8.0
|
||||
version: 2.8.0(@types/react@18.3.2)(react@18.3.1)
|
||||
ndjson:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
|
@ -1607,6 +1613,9 @@ packages:
|
|||
chownr@1.1.4:
|
||||
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
|
||||
|
||||
classnames@2.5.1:
|
||||
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
|
||||
|
||||
clone-response@1.0.3:
|
||||
resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==}
|
||||
|
||||
|
@ -2152,6 +2161,18 @@ packages:
|
|||
resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==}
|
||||
hasBin: true
|
||||
|
||||
jotai@2.8.0:
|
||||
resolution: {integrity: sha512-yZNMC36FdLOksOr8qga0yLf14miCJlEThlp5DeFJNnqzm2+ZG7wLcJzoOyij5K6U6Xlc5ljQqPDlJRgqW0Y18g==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
peerDependencies:
|
||||
'@types/react': '>=17.0.0'
|
||||
react: '>=17.0.0'
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
|
||||
js-green-licenses@4.0.0:
|
||||
resolution: {integrity: sha512-kcgTOaZmpDpINcRAOKKhjHtBN6zibMVTC8qfPUOpowQtI/6fUgdmwJLJ0ycCb0pUO3ZYKn++56sy8IlG60p5mg==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
@ -4643,6 +4664,8 @@ snapshots:
|
|||
|
||||
chownr@1.1.4: {}
|
||||
|
||||
classnames@2.5.1: {}
|
||||
|
||||
clone-response@1.0.3:
|
||||
dependencies:
|
||||
mimic-response: 1.0.1
|
||||
|
@ -5233,6 +5256,11 @@ snapshots:
|
|||
|
||||
jiti@1.21.0: {}
|
||||
|
||||
jotai@2.8.0(@types/react@18.3.2)(react@18.3.1):
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.2
|
||||
react: 18.3.1
|
||||
|
||||
js-green-licenses@4.0.0:
|
||||
dependencies:
|
||||
gaxios: 5.1.3
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
.container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -8,4 +9,7 @@
|
|||
|
||||
.content {
|
||||
flex-grow: 1;
|
||||
|
||||
min-height: 0;
|
||||
overflow-y: scroll;
|
||||
}
|
23
src/App.tsx
23
src/App.tsx
|
@ -1,7 +1,3 @@
|
|||
import { BottomNavigation, BottomNavigationAction, Icon } from "@mui/material";
|
||||
import LineWeightIcon from "@mui/icons-material/LineWeight";
|
||||
import ManageSearchIcon from "@mui/icons-material/ManageSearch";
|
||||
import SettingsIcon from "@mui/icons-material/Settings";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
createBrowserRouter,
|
||||
|
@ -20,7 +16,10 @@ import Scores from "./pages/Scores";
|
|||
import Settings from "./pages/Settings";
|
||||
import ImportChartWorker from "./importCharts?worker";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { CHART_STORE_CREATION_EVENT } from "./globals";
|
||||
import { CHART_STORE_CREATION_EVENT, dbStatusAtom } from "./globals";
|
||||
import NavBar from "./components/NavBar";
|
||||
import WarningBars from "./components/WarningBars";
|
||||
import { useAtom, useSetAtom } from "jotai";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
|
@ -55,10 +54,12 @@ export function AppWrapper() {
|
|||
return (
|
||||
<>
|
||||
<div className={styles.container}>
|
||||
<WarningBars />
|
||||
<div className={styles.content}>
|
||||
<Outlet />
|
||||
</div>
|
||||
<BottomNavigation
|
||||
<NavBar />
|
||||
{/* <BottomNavigation
|
||||
showLabels
|
||||
value={navId}
|
||||
onChange={(event, newValue) => {
|
||||
|
@ -68,17 +69,25 @@ export function AppWrapper() {
|
|||
<BottomNavigationAction label="Charts" icon={<ManageSearchIcon />} />
|
||||
<BottomNavigationAction label="Scores" icon={<LineWeightIcon />} />
|
||||
<BottomNavigationAction label="Settings" icon={<SettingsIcon />} />
|
||||
</BottomNavigation>
|
||||
</BottomNavigation> */}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const setDbStatus = useSetAtom(dbStatusAtom);
|
||||
|
||||
useEffect(() => {
|
||||
const worker = new ImportChartWorker();
|
||||
worker.postMessage("start");
|
||||
worker.onmessage = (evt) => {
|
||||
console.log("got event from web worker", evt);
|
||||
switch (evt.kind) {
|
||||
case "dbIsBlocked": {
|
||||
setDbStatus((db) => ({ ...db, status: "upgrading" }));
|
||||
}
|
||||
}
|
||||
if (evt.kind === "chartStoreCreate") {
|
||||
document.dispatchEvent(CHART_STORE_CREATION_EVENT);
|
||||
}
|
||||
|
|
7
src/components/Chip.module.scss
Normal file
7
src/components/Chip.module.scss
Normal file
|
@ -0,0 +1,7 @@
|
|||
.chip {
|
||||
display: flex;
|
||||
border-radius: 1000px;
|
||||
border: 1px solid lightgray;
|
||||
padding: 2px 6px;
|
||||
font-size: 10pt;
|
||||
}
|
14
src/components/Chip.tsx
Normal file
14
src/components/Chip.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import classNames from "classnames";
|
||||
import styles from "./Chip.module.scss";
|
||||
import type { PropsWithChildren } from "react";
|
||||
|
||||
export interface ChipProps {
|
||||
className?: string | undefined;
|
||||
}
|
||||
|
||||
export default function Chip({
|
||||
className,
|
||||
children,
|
||||
}: PropsWithChildren<ChipProps>) {
|
||||
return <div className={classNames(className, styles.chip)}>{children}</div>;
|
||||
}
|
16
src/components/NavBar.module.scss
Normal file
16
src/components/NavBar.module.scss
Normal file
|
@ -0,0 +1,16 @@
|
|||
.bar {
|
||||
display: flex;
|
||||
border-top: 1px solid lightgray;
|
||||
}
|
||||
|
||||
.searchBar {
|
||||
flex-grow: 1;
|
||||
|
||||
display: flex;
|
||||
|
||||
>input {
|
||||
flex-grow: 1;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
}
|
20
src/components/NavBar.tsx
Normal file
20
src/components/NavBar.tsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
import styles from "./NavBar.module.scss";
|
||||
import SettingsIcon from "@mui/icons-material/Settings";
|
||||
import HelpIcon from "@mui/icons-material/Help";
|
||||
|
||||
export default function NavBar() {
|
||||
return (
|
||||
<nav className={styles.bar}>
|
||||
<div className={styles.searchBar}>
|
||||
<input
|
||||
placeholder="Search charts..."
|
||||
// biome-ignore lint/a11y/noAutofocus: this is the only one in the page
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SettingsIcon />
|
||||
<HelpIcon />
|
||||
</nav>
|
||||
);
|
||||
}
|
25
src/components/WarningBars.module.scss
Normal file
25
src/components/WarningBars.module.scss
Normal file
|
@ -0,0 +1,25 @@
|
|||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid lightgray;
|
||||
font-size: 10pt;
|
||||
height: 24px;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
.modeBar {
|
||||
--bar1: rgb(237, 224, 40);
|
||||
--bar2: rgb(240, 232, 118);
|
||||
background: repeating-linear-gradient(45deg,
|
||||
var(--bar1),
|
||||
var(--bar1) 10px,
|
||||
var(--bar2) 10px,
|
||||
var(--bar2) 20px);
|
||||
backdrop-filter: blur(10px);
|
||||
color: black;
|
||||
}
|
61
src/components/WarningBars.tsx
Normal file
61
src/components/WarningBars.tsx
Normal file
|
@ -0,0 +1,61 @@
|
|||
import classNames from "classnames";
|
||||
import styles from "./WarningBars.module.scss";
|
||||
import WarningIcon from "@mui/icons-material/Warning";
|
||||
import Chip from "./Chip";
|
||||
import { dbStatusAtom } from "../globals";
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export default function WarningBars() {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<ModeBar />
|
||||
<StatusBar />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Warn if this is not the production mode */
|
||||
function ModeBar() {
|
||||
const mode = import.meta.env.MODE;
|
||||
if (mode === "production") return null;
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.bar, styles.modeBar)}>
|
||||
<span>
|
||||
<WarningIcon fontSize="small" />
|
||||
This is the <b>{mode}</b> site. Click{" "}
|
||||
<a
|
||||
href="https://mzhang.io/ddr"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
here
|
||||
</a>{" "}
|
||||
to go to the production site.
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StatusBar() {
|
||||
const statuses = [];
|
||||
|
||||
statuses.push(
|
||||
<Chip>
|
||||
db: <DbStatusChip />
|
||||
</Chip>,
|
||||
);
|
||||
|
||||
return <div className={classNames(styles.bar)}>{statuses}</div>;
|
||||
}
|
||||
|
||||
function DbStatusChip(): ReactNode {
|
||||
const dbStatus = useAtomValue(dbStatusAtom);
|
||||
switch (dbStatus.status) {
|
||||
case "uninit":
|
||||
return "uninit";
|
||||
case "upgrading":
|
||||
return "upgrading";
|
||||
}
|
||||
}
|
|
@ -14,4 +14,9 @@ body,
|
|||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex-grow: 1;
|
||||
}
|
|
@ -1,4 +1,17 @@
|
|||
// export const APP_DATA_VERSION = 1;
|
||||
import { atom } from "jotai";
|
||||
|
||||
const PROD_DATA_VERSION = 1;
|
||||
const DEV_DATA_VERSION = Math.floor(new Date().getTime() / 1000) % 1000000;
|
||||
|
||||
export const APP_DATA_VERSION =
|
||||
Math.floor(new Date().getTime() / 1000) % 1000000;
|
||||
import.meta.env.MODE === "production" ? PROD_DATA_VERSION : DEV_DATA_VERSION;
|
||||
export const CHART_STORE_CREATION_EVENT = new Event("chartStoreCreate");
|
||||
|
||||
export interface DbStatus {
|
||||
persistence: "unknown" | "persisted" | "unpersisted";
|
||||
status: "uninit" | "upgrading";
|
||||
}
|
||||
export const dbStatusAtom = atom<DbStatus>({
|
||||
persistence: "unknown",
|
||||
status: "uninit",
|
||||
});
|
||||
|
|
|
@ -91,6 +91,7 @@ function openDb() {
|
|||
|
||||
db.addEventListener("blocked", (evt) => {
|
||||
console.log("BLOCKED", evt);
|
||||
self.postMessage({ kind: "dbIsBlocked" });
|
||||
});
|
||||
|
||||
db.addEventListener("upgradeneeded", (evt) => {
|
||||
|
|
|
@ -3,8 +3,13 @@ import react from "@vitejs/plugin-react-swc";
|
|||
import { VitePWA } from "vite-plugin-pwa";
|
||||
import basicSsl from "@vitejs/plugin-basic-ssl";
|
||||
|
||||
export default defineConfig({
|
||||
base: process.env.BASE_URL ?? undefined,
|
||||
const baseUrls = {
|
||||
production: "/ddr",
|
||||
staging: "/ddr-staging",
|
||||
};
|
||||
|
||||
export default defineConfig(({ mode }) => ({
|
||||
base: baseUrls[mode],
|
||||
server: {
|
||||
https: {},
|
||||
},
|
||||
|
@ -26,4 +31,4 @@ export default defineConfig({
|
|||
certDir: "/Users/.../.devServer/cert",
|
||||
}),
|
||||
],
|
||||
});
|
||||
}));
|
||||
|
|
Loading…
Reference in a new issue