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",
|
||||
"bcrypt": "^5.1.1",
|
||||
"bootstrap": "^5.3.1",
|
||||
"bootstrap-icons": "^1.10.5",
|
||||
"diff": "^5.1.0",
|
||||
"diff2html": "^3.4.40",
|
||||
"eslint-plugin-svelte": "^2.33.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"node-git-server": "^1.0.0",
|
||||
"prisma": "^5.2.0",
|
||||
"simple-git": "^3.19.1",
|
||||
@ -29,6 +31,7 @@
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/bootstrap": "^5.2.6",
|
||||
"@types/diff": "^5.0.3",
|
||||
"@types/js-cookie": "^3.0.3",
|
||||
"@types/node": "^20.5.6",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^6.4.1",
|
||||
@ -919,6 +922,12 @@
|
||||
"@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": {
|
||||
"version": "7.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
|
||||
@ -1327,6 +1336,21 @@
|
||||
"@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": {
|
||||
"version": "1.1.11",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
|
@ -17,6 +17,7 @@
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/bootstrap": "^5.2.6",
|
||||
"@types/diff": "^5.0.3",
|
||||
"@types/js-cookie": "^3.0.3",
|
||||
"@types/node": "^20.5.6",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^6.4.1",
|
||||
@ -38,10 +39,12 @@
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"bcrypt": "^5.1.1",
|
||||
"bootstrap": "^5.3.1",
|
||||
"bootstrap-icons": "^1.10.5",
|
||||
"diff": "^5.1.0",
|
||||
"diff2html": "^3.4.40",
|
||||
"eslint-plugin-svelte": "^2.33.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"node-git-server": "^1.0.0",
|
||||
"prisma": "^5.2.0",
|
||||
"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 {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
interface Locals {
|
||||
theme: 'light' | 'dark';
|
||||
}
|
||||
// interface PageData {}
|
||||
// interface Platform {}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html id="html-element" lang="en" data-bs-theme="%theme%">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
|
@ -24,6 +24,9 @@ try {
|
||||
startGitServer();
|
||||
|
||||
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') {
|
||||
return new Response('ok', {
|
||||
headers: {
|
||||
@ -46,6 +49,8 @@ export const handle = (async ({ event, resolve }) => {
|
||||
throw redirect(302, '/login');
|
||||
}
|
||||
}
|
||||
const res = await resolve(event);
|
||||
const res = await resolve(event, {
|
||||
transformPageChunk: ({ html }) => html.replace('%theme%', event.locals.theme)
|
||||
});
|
||||
return res;
|
||||
}) 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 '$lib/styles/global.css';
|
||||
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 () => {
|
||||
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>
|
||||
|
||||
<body class="container">
|
||||
|
@ -1,29 +1,65 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
import { theme } from '../stores';
|
||||
</script>
|
||||
|
||||
<header style="font-size: 24px" class="mt-2 d-flex align-items-center justify-content-center">
|
||||
<ul class="nav col-12 col-md-auto justify-content-center">
|
||||
<li><a href="/admin" class="nav-link px-2">Home</a></li>
|
||||
<li><a href="/admin/reviews" class="nav-link px-2">Reviews</a></li>
|
||||
<li><a href="/admin/submissions" class="nav-link px-2">Submissions</a></li>
|
||||
<li><a href="/admin/problems" class="nav-link px-2">Problems</a></li>
|
||||
<li><a href="/admin/scoreboard" class="nav-link px-2">Scoreboards</a></li>
|
||||
<li><a href="/admin/teams" class="nav-link px-2">Teams</a></li>
|
||||
<li><a href="/admin/contests" class="nav-link px-2">Contests</a></li>
|
||||
<li><button on:click={onLogout} class="nav-link px-2">Logout</button></li>
|
||||
</ul>
|
||||
</header>
|
||||
<hr />
|
||||
<nav class="main-nav mt-2 mb-3 navbar navbar-expand-lg bg-body-secondary">
|
||||
<div class="container-fluid">
|
||||
<button
|
||||
class="navbar-toggler"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#navbarSupportedContent"
|
||||
aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false"
|
||||
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>
|
||||
</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 />
|
||||
<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