Improve navbar and add dark theme
This commit is contained in:
parent
fcc23d5da9
commit
c1d86146af
32
web/package-lock.json
generated
32
web/package-lock.json
generated
@ -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",
|
||||||
|
@ -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
4
web/src/app.d.ts
vendored
@ -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 {}
|
||||||
}
|
}
|
||||||
|
@ -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" />
|
||||||
|
@ -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;
|
||||||
|
5
web/src/routes/+layout.server.ts
Normal file
5
web/src/routes/+layout.server.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import type { LayoutServerLoad } from './$types';
|
||||||
|
|
||||||
|
export const load = (async ({ locals }) => {
|
||||||
|
return { theme: locals.theme };
|
||||||
|
}) satisfies LayoutServerLoad;
|
@ -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">
|
||||||
|
@ -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"
|
||||||
</ul>
|
>
|
||||||
</header>
|
<span class="navbar-toggler-icon" />
|
||||||
<hr />
|
</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>
|
||||||
|
</div>
|
||||||
|
</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
3
web/src/routes/stores.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export const theme = writable<'light' | 'dark'>('dark');
|
Loading…
Reference in New Issue
Block a user