Improve navbar and add dark theme

This commit is contained in:
orosmatthew 2023-08-26 11:41:37 -04:00
parent fcc23d5da9
commit c1d86146af
9 changed files with 130 additions and 27 deletions

32
web/package-lock.json generated
View File

@ -13,10 +13,12 @@
"@types/fs-extra": "^11.0.1", "@types/fs-extra": "^11.0.1",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"bootstrap": "^5.3.1", "bootstrap": "^5.3.1",
"bootstrap-icons": "^1.10.5",
"diff": "^5.1.0", "diff": "^5.1.0",
"diff2html": "^3.4.40", "diff2html": "^3.4.40",
"eslint-plugin-svelte": "^2.33.0", "eslint-plugin-svelte": "^2.33.0",
"fs-extra": "^11.1.1", "fs-extra": "^11.1.1",
"js-cookie": "^3.0.5",
"node-git-server": "^1.0.0", "node-git-server": "^1.0.0",
"prisma": "^5.2.0", "prisma": "^5.2.0",
"simple-git": "^3.19.1", "simple-git": "^3.19.1",
@ -29,6 +31,7 @@
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/bootstrap": "^5.2.6", "@types/bootstrap": "^5.2.6",
"@types/diff": "^5.0.3", "@types/diff": "^5.0.3",
"@types/js-cookie": "^3.0.3",
"@types/node": "^20.5.6", "@types/node": "^20.5.6",
"@types/uuid": "^9.0.2", "@types/uuid": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^6.4.1", "@typescript-eslint/eslint-plugin": "^6.4.1",
@ -919,6 +922,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/js-cookie": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.3.tgz",
"integrity": "sha512-Xe7IImK09HP1sv2M/aI+48a20VX+TdRJucfq4vfRVy6nWN8PYPOEnlMRSgxJAgYQIXJVL8dZ4/ilAM7dWNaOww==",
"dev": true
},
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.12", "version": "7.0.12",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
@ -1327,6 +1336,21 @@
"@popperjs/core": "^2.11.8" "@popperjs/core": "^2.11.8"
} }
}, },
"node_modules/bootstrap-icons": {
"version": "1.10.5",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.10.5.tgz",
"integrity": "sha512-oSX26F37V7QV7NCE53PPEL45d7EGXmBgHG3pDpZvcRaKVzWMqIRL9wcqJUyEha1esFtM3NJzvmxFXDxjJYD0jQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
]
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -2385,6 +2409,14 @@
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
}, },
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
"engines": {
"node": ">=14"
}
},
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",

View File

@ -17,6 +17,7 @@
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/bootstrap": "^5.2.6", "@types/bootstrap": "^5.2.6",
"@types/diff": "^5.0.3", "@types/diff": "^5.0.3",
"@types/js-cookie": "^3.0.3",
"@types/node": "^20.5.6", "@types/node": "^20.5.6",
"@types/uuid": "^9.0.2", "@types/uuid": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^6.4.1", "@typescript-eslint/eslint-plugin": "^6.4.1",
@ -38,10 +39,12 @@
"@types/fs-extra": "^11.0.1", "@types/fs-extra": "^11.0.1",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"bootstrap": "^5.3.1", "bootstrap": "^5.3.1",
"bootstrap-icons": "^1.10.5",
"diff": "^5.1.0", "diff": "^5.1.0",
"diff2html": "^3.4.40", "diff2html": "^3.4.40",
"eslint-plugin-svelte": "^2.33.0", "eslint-plugin-svelte": "^2.33.0",
"fs-extra": "^11.1.1", "fs-extra": "^11.1.1",
"js-cookie": "^3.0.5",
"node-git-server": "^1.0.0", "node-git-server": "^1.0.0",
"prisma": "^5.2.0", "prisma": "^5.2.0",
"simple-git": "^3.19.1", "simple-git": "^3.19.1",

4
web/src/app.d.ts vendored
View File

@ -3,7 +3,9 @@
declare global { declare global {
namespace App { namespace App {
// interface Error {} // interface Error {}
// interface Locals {} interface Locals {
theme: 'light' | 'dark';
}
// interface PageData {} // interface PageData {}
// interface Platform {} // interface Platform {}
} }

View File

@ -1,5 +1,5 @@
<!doctype html> <!doctype html>
<html lang="en"> <html id="html-element" lang="en" data-bs-theme="%theme%">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" /> <link rel="icon" href="%sveltekit.assets%/favicon.png" />

View File

@ -24,6 +24,9 @@ try {
startGitServer(); startGitServer();
export const handle = (async ({ event, resolve }) => { export const handle = (async ({ event, resolve }) => {
const theme = event.cookies.get('theme') as 'light' | 'dark' | undefined;
event.locals.theme = theme ?? 'dark';
if (event.request.method === 'OPTIONS') { if (event.request.method === 'OPTIONS') {
return new Response('ok', { return new Response('ok', {
headers: { headers: {
@ -46,6 +49,8 @@ export const handle = (async ({ event, resolve }) => {
throw redirect(302, '/login'); throw redirect(302, '/login');
} }
} }
const res = await resolve(event); const res = await resolve(event, {
transformPageChunk: ({ html }) => html.replace('%theme%', event.locals.theme)
});
return res; return res;
}) satisfies Handle; }) satisfies Handle;

View File

@ -0,0 +1,5 @@
import type { LayoutServerLoad } from './$types';
export const load = (async ({ locals }) => {
return { theme: locals.theme };
}) satisfies LayoutServerLoad;

View File

@ -2,9 +2,26 @@
import 'bootstrap/dist/css/bootstrap.min.css'; import 'bootstrap/dist/css/bootstrap.min.css';
import '$lib/styles/global.css'; import '$lib/styles/global.css';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import type { LayoutData } from './$types';
import { theme } from './stores';
import { browser } from '$app/environment';
import Cookies from 'js-cookie';
import 'bootstrap-icons/font/bootstrap-icons.min.css';
onMount(async () => { onMount(async () => {
await import('bootstrap'); await import('bootstrap');
}); });
export let data: LayoutData;
$theme = data.theme;
if (browser) {
theme.subscribe((value) => {
document.getElementById('html-element')?.setAttribute('data-bs-theme', value);
Cookies.set('theme', value, { sameSite: 'strict' });
});
}
</script> </script>
<body class="container"> <body class="container">

View File

@ -1,29 +1,65 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { theme } from '../stores';
async function onLogout() {
const res = await fetch('/logout', { method: 'POST' });
if (res.status === 200) {
const resData = await res.json();
if (resData.success === true) {
goto('/login');
}
}
}
</script> </script>
<header style="font-size: 24px" class="mt-2 d-flex align-items-center justify-content-center"> <nav class="main-nav mt-2 mb-3 navbar navbar-expand-lg bg-body-secondary">
<ul class="nav col-12 col-md-auto justify-content-center"> <div class="container-fluid">
<li><a href="/admin" class="nav-link px-2">Home</a></li> <button
<li><a href="/admin/reviews" class="nav-link px-2">Reviews</a></li> class="navbar-toggler"
<li><a href="/admin/submissions" class="nav-link px-2">Submissions</a></li> type="button"
<li><a href="/admin/problems" class="nav-link px-2">Problems</a></li> data-bs-toggle="collapse"
<li><a href="/admin/scoreboard" class="nav-link px-2">Scoreboards</a></li> data-bs-target="#navbarSupportedContent"
<li><a href="/admin/teams" class="nav-link px-2">Teams</a></li> aria-controls="navbarSupportedContent"
<li><a href="/admin/contests" class="nav-link px-2">Contests</a></li> aria-expanded="false"
<li><button on:click={onLogout} class="nav-link px-2">Logout</button></li> aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon" />
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item"><a href="/admin" class="nav-link">Home</a></li>
<li class="nav-item"><a href="/admin/reviews" class="nav-link">Reviews</a></li>
<li class="nav-item"><a href="/admin/submissions" class="nav-link">Submissions</a></li>
<li class="nav-item"><a href="/admin/problems" class="nav-link">Problems</a></li>
<li class="nav-item"><a href="/admin/scoreboard" class="nav-link">Scoreboards</a></li>
<li class="nav-item"><a href="/admin/teams" class="nav-link">Teams</a></li>
<li class="nav-item"><a href="/admin/contests" class="nav-link">Contests</a></li>
</ul> </ul>
</header> </div>
<hr /> </div>
<div class="nav-sticky-right">
<button
on:click={() => {
$theme = $theme === 'light' ? 'dark' : 'light';
}}
type="button"
aria-label="theme"
class="btn"><i class={`bi bi-${$theme == 'light' ? 'sun' : 'moon'}`} /></button
>
<button
on:click={async () => {
const res = await fetch('/logout', { method: 'POST' });
const data = await res.json();
if (data.success) {
goto('/login');
}
}}
type="button"
class="btn btn-outline-secondary">Logout</button
>
</div>
</nav>
<slot /> <slot />
<div style="height: 100px" />
<style>
.main-nav {
border-radius: 10px;
position: relative;
}
.nav-sticky-right {
position: absolute;
right: 10px;
top: 8px;
}
</style>

3
web/src/routes/stores.ts Normal file
View File

@ -0,0 +1,3 @@
import { writable } from 'svelte/store';
export const theme = writable<'light' | 'dark'>('dark');