rewrote react frontend with svelte
137
.gitignore
vendored
|
@ -1,136 +1,5 @@
|
||||||
_/
|
/_/
|
||||||
|
|
||||||
# Unmodified
|
|
||||||
# Created by https://www.toptal.com/developers/gitignore/api/firebase,node
|
|
||||||
# Edit at https://www.toptal.com/developers/gitignore?templates=firebase,node
|
|
||||||
|
|
||||||
### Firebase ###
|
|
||||||
.idea
|
|
||||||
**/node_modules/*
|
|
||||||
**/.firebaserc
|
|
||||||
|
|
||||||
### Firebase Patch ###
|
|
||||||
.runtimeconfig.json
|
|
||||||
.firebase/
|
.firebase/
|
||||||
|
.firebaserc
|
||||||
### Node ###
|
ui-debug.log
|
||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
.pnpm-debug.log*
|
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
||||||
|
|
||||||
# Runtime data
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
*.lcov
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
|
||||||
jspm_packages/
|
|
||||||
|
|
||||||
# Snowpack dependency directory (https://snowpack.dev/)
|
|
||||||
web_modules/
|
|
||||||
|
|
||||||
# TypeScript cache
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Microbundle cache
|
|
||||||
.rpt2_cache/
|
|
||||||
.rts2_cache_cjs/
|
|
||||||
.rts2_cache_es/
|
|
||||||
.rts2_cache_umd/
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variables file
|
|
||||||
.env
|
|
||||||
.env.test
|
|
||||||
.env.production
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
|
||||||
.cache
|
|
||||||
.parcel-cache
|
|
||||||
|
|
||||||
# Next.js build output
|
|
||||||
.next
|
|
||||||
out
|
|
||||||
|
|
||||||
# Nuxt.js build / generate output
|
|
||||||
.nuxt
|
|
||||||
dist
|
|
||||||
|
|
||||||
# Gatsby files
|
|
||||||
.cache/
|
|
||||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
|
||||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
||||||
# public
|
|
||||||
|
|
||||||
# vuepress build output
|
|
||||||
.vuepress/dist
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
.serverless/
|
|
||||||
|
|
||||||
# FuseBox cache
|
|
||||||
.fusebox/
|
|
||||||
|
|
||||||
# DynamoDB Local files
|
|
||||||
.dynamodb/
|
|
||||||
|
|
||||||
# TernJS port file
|
|
||||||
.tern-port
|
|
||||||
|
|
||||||
# Stores VSCode versions used for testing VSCode extensions
|
|
||||||
.vscode-test
|
|
||||||
|
|
||||||
# yarn v2
|
|
||||||
.yarn/cache
|
|
||||||
.yarn/unplugged
|
|
||||||
.yarn/build-state.yml
|
|
||||||
.yarn/install-state.gz
|
|
||||||
.pnp.*
|
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/firebase,node
|
|
||||||
|
|
3
.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"recommendations": ["svelte.svelte-vscode", "esbenp.prettier-vscode"]
|
||||||
|
}
|
|
@ -1,6 +1,3 @@
|
||||||
# llama-bot-web-interface
|
# llama-bot-web-interface
|
||||||
|
|
||||||

|
## [Documentation](https://llama-bot.github.io/llama-bot-docs/docs/web-interface/overview)
|
||||||
[](https://github.com/prettier/prettier)
|
|
||||||
|
|
||||||
[Documentation](https://llama-bot.github.io/llama-bot-docs/docs/web-interface/overview)
|
|
||||||
|
|
24
frontend/.eslintrc.cjs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
extends: [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"prettier",
|
||||||
|
],
|
||||||
|
plugins: ["svelte3", "@typescript-eslint"],
|
||||||
|
ignorePatterns: ["*.cjs"],
|
||||||
|
overrides: [{ files: ["*.svelte"], processor: "svelte3/svelte3" }],
|
||||||
|
settings: {
|
||||||
|
"svelte3/typescript": () => require("typescript"),
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: "module",
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2017: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
}
|
29
frontend/.gitignore
vendored
|
@ -1,26 +1,15 @@
|
||||||
# dependencies
|
/build/
|
||||||
/node_modules
|
/.svelte-kit/
|
||||||
/.pnp
|
/package/
|
||||||
.pnp.js
|
/node_modules/
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env.local
|
.env
|
||||||
.env.development.local
|
.env.*
|
||||||
.env.test.local
|
!.env.example
|
||||||
.env.production.local
|
.vercel
|
||||||
.vscode
|
.output
|
||||||
.idea
|
|
||||||
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { CapacitorConfig } from "@capacitor/cli"
|
|
||||||
|
|
||||||
const config: CapacitorConfig = {
|
|
||||||
appId: "io.ionic.starter",
|
|
||||||
appName: "llama-bot-web-interface",
|
|
||||||
webDir: "build",
|
|
||||||
bundledWebRuntime: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default config
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"name": "llama-bot-web-interface",
|
|
||||||
"integrations": {
|
|
||||||
"capacitor": {}
|
|
||||||
},
|
|
||||||
"type": "react"
|
|
||||||
}
|
|
|
@ -1,71 +1,38 @@
|
||||||
{
|
{
|
||||||
"name": "llama-bot-web-interface",
|
"type": "module",
|
||||||
"version": "1.0.0",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"dev": "svelte-kit dev",
|
||||||
"serve": "npm run build && firebase serve",
|
"build": "svelte-kit build",
|
||||||
"build": "react-scripts build",
|
"preview": "svelte-kit preview",
|
||||||
"test": "react-scripts test",
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||||
"analyze": "source-map-explorer 'build/static/js/*.js'"
|
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
|
"lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
|
||||||
|
"format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
"@fontsource/noto-sans": "^4.5.4",
|
||||||
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
"@lukeed/uuid": "^2.0.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
"cookie": "^0.4.2"
|
||||||
"@fortawesome/react-fontawesome": "^0.1.15",
|
|
||||||
"evergreen-ui": "^6.5.1",
|
|
||||||
"react": "^17.0.2",
|
|
||||||
"react-dom": "^17.0.2",
|
|
||||||
"react-router": "^5.2.1",
|
|
||||||
"react-router-dom": "^5.3.0",
|
|
||||||
"react-spinners": "^0.11.0",
|
|
||||||
"styled-components": "^5.3.1",
|
|
||||||
"web-vitals": "^2.1.2",
|
|
||||||
"workbox-background-sync": "^6.3.0",
|
|
||||||
"workbox-broadcast-update": "^6.3.0",
|
|
||||||
"workbox-cacheable-response": "^6.3.0",
|
|
||||||
"workbox-core": "^6.3.0",
|
|
||||||
"workbox-expiration": "^6.3.0",
|
|
||||||
"workbox-google-analytics": "^6.3.0",
|
|
||||||
"workbox-navigation-preload": "^6.3.0",
|
|
||||||
"workbox-precaching": "^6.3.0",
|
|
||||||
"workbox-range-requests": "^6.3.0",
|
|
||||||
"workbox-routing": "^6.3.0",
|
|
||||||
"workbox-strategies": "^6.3.0",
|
|
||||||
"workbox-streams": "^6.3.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
"@sveltejs/adapter-auto": "^1.0.0-next.17",
|
||||||
"@testing-library/react": "^12.1.2",
|
"@sveltejs/adapter-static": "^1.0.0-next.28",
|
||||||
"@testing-library/user-event": "^13.3.0",
|
"@sveltejs/kit": "^1.0.0-next.278",
|
||||||
"@types/jest": "^27.0.2",
|
"@types/cookie": "^0.4.1",
|
||||||
"@types/node": "^16.10.4",
|
"@typescript-eslint/eslint-plugin": "^5.12.0",
|
||||||
"@types/react": "^17.0.29",
|
"@typescript-eslint/parser": "^5.12.0",
|
||||||
"@types/react-dom": "^17.0.9",
|
"eslint": "^8.9.0",
|
||||||
"@types/react-router": "^5.1.17",
|
"eslint-config-prettier": "^8.4.0",
|
||||||
"@types/react-router-dom": "^5.3.1",
|
"eslint-plugin-svelte3": "^3.4.0",
|
||||||
"@types/styled-components": "^5.1.15",
|
"prettier": "^2.5.1",
|
||||||
"react-scripts": "^4.0.3",
|
"prettier-plugin-svelte": "^2.6.0",
|
||||||
"source-map-explorer": "^2.5.2",
|
"sass": "^1.49.8",
|
||||||
"typescript": "^4.4.4"
|
"svelte": "^3.46.4",
|
||||||
},
|
"svelte-check": "^2.4.5",
|
||||||
"eslintConfig": {
|
"svelte-icons": "^2.1.0",
|
||||||
"extends": [
|
"svelte-loading-spinners": "^0.1.7",
|
||||||
"react-app",
|
"svelte-preprocess": "^4.10.3",
|
||||||
"react-app/jest"
|
"tslib": "^2.3.1",
|
||||||
]
|
"typescript": "^4.5.5"
|
||||||
},
|
|
||||||
"browserslist": {
|
|
||||||
"production": [
|
|
||||||
">0.2%",
|
|
||||||
"not dead",
|
|
||||||
"not op_mini all"
|
|
||||||
],
|
|
||||||
"development": [
|
|
||||||
"last 1 chrome version",
|
|
||||||
"last 1 firefox version",
|
|
||||||
"last 1 safari version"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>Llama Bot</title>
|
|
||||||
<meta name="description" content="Llama bot web interface" />
|
|
||||||
|
|
||||||
<meta name="theme-color" content="#5DADEC" />
|
|
||||||
|
|
||||||
<link
|
|
||||||
rel="shortcut icon"
|
|
||||||
type="image/png"
|
|
||||||
href="%PUBLIC_URL%/assets/icon/llama-color.png"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="icon"
|
|
||||||
type="image/png"
|
|
||||||
href="%PUBLIC_URL%/assets/icon/llama-color.png"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<base href="/" />
|
|
||||||
|
|
||||||
<meta name="color-scheme" content="light dark" />
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
|
||||||
/>
|
|
||||||
<meta name="format-detection" content="telephone=no" />
|
|
||||||
<meta name="msapplication-tap-highlight" content="no" />
|
|
||||||
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
|
||||||
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR&family=Source+Code+Pro&display=swap"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- IOS stuff -->
|
|
||||||
<link
|
|
||||||
rel="apple-touch-icon"
|
|
||||||
href="%PUBLIC_URL%/assets/icon/llama-color.png"
|
|
||||||
/>
|
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
||||||
<meta name="apple-mobile-web-app-title" content="Llama Bot" />
|
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>
|
|
||||||
Please enable JavaScript / 자바스크립트를 활성화해주세요
|
|
||||||
</noscript>
|
|
||||||
<div id="root"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"short_name": "Ionic App",
|
|
||||||
"name": "My Ionic App",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "assets/icon/favicon.png",
|
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
|
||||||
"type": "image/x-icon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "assets/icon/icon.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "512x512",
|
|
||||||
"purpose": "maskable"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"start_url": ".",
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#ffffff",
|
|
||||||
"background_color": "#ffffff"
|
|
||||||
}
|
|
Before Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 167 KiB |
|
@ -1,8 +0,0 @@
|
||||||
import React from "react"
|
|
||||||
import { render } from "@testing-library/react"
|
|
||||||
import App from "./App"
|
|
||||||
|
|
||||||
test("renders without crashing", () => {
|
|
||||||
const { baseElement } = render(<App />)
|
|
||||||
expect(baseElement).toBeDefined()
|
|
||||||
})
|
|
|
@ -1,93 +0,0 @@
|
||||||
import { lazy, Suspense } from "react"
|
|
||||||
import { ThemeProvider } from "evergreen-ui"
|
|
||||||
import { BrowserRouter as Router, Route } from "react-router-dom"
|
|
||||||
import styled, { createGlobalStyle, css } from "styled-components"
|
|
||||||
|
|
||||||
import Loader from "react-spinners/CircleLoader"
|
|
||||||
import Navbar from "./components/Navbar"
|
|
||||||
import Footer from "./components/Footer"
|
|
||||||
|
|
||||||
import darkTheme from "./theme/dark"
|
|
||||||
|
|
||||||
const Home = lazy(() => import("./pages/Home"))
|
|
||||||
const Servers = lazy(() => import("./pages/Servers"))
|
|
||||||
// const Dashboard = lazy(() => import("./pages/Dashboard"))
|
|
||||||
|
|
||||||
// wrapping it using css because prettier extension does not work well with styled-components
|
|
||||||
// https://github.com/styled-components/vscode-styled-components/issues/175
|
|
||||||
const _globalStyle = css`
|
|
||||||
html,
|
|
||||||
body,
|
|
||||||
#root {
|
|
||||||
font-family: "Noto Sans KR", sans-serif;
|
|
||||||
|
|
||||||
height: 100vh;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const GlobalStyle = createGlobalStyle`
|
|
||||||
${_globalStyle}
|
|
||||||
`
|
|
||||||
|
|
||||||
const StyledSpinContainer = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
/* center elements */
|
|
||||||
display: flex;
|
|
||||||
align-content: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
/* fade in */
|
|
||||||
animation-delay: 0.2s;
|
|
||||||
animation-duration: 2s;
|
|
||||||
animation-name: fadein;
|
|
||||||
|
|
||||||
@keyframes fadein {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const App: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<GlobalStyle />
|
|
||||||
<ThemeProvider value={darkTheme}>
|
|
||||||
<Suspense
|
|
||||||
fallback={
|
|
||||||
<StyledSpinContainer>
|
|
||||||
<Loader size={150} />
|
|
||||||
</StyledSpinContainer>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Navbar />
|
|
||||||
<Router>
|
|
||||||
<Route exact path="/">
|
|
||||||
<Home />
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route exact path="/servers">
|
|
||||||
<Servers />
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
{/* <Route exact path="/server/:server/dashboard">
|
|
||||||
<Dashboard />
|
|
||||||
</Route> */}
|
|
||||||
</Router>
|
|
||||||
<Footer />
|
|
||||||
</Suspense>
|
|
||||||
</ThemeProvider>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App
|
|
15
frontend/src/app.d.ts
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/// <reference types="@sveltejs/kit" />
|
||||||
|
|
||||||
|
// See https://kit.svelte.dev/docs/typescript
|
||||||
|
// for information about these interfaces
|
||||||
|
declare namespace App {
|
||||||
|
interface Locals {
|
||||||
|
userid: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Platform {}
|
||||||
|
|
||||||
|
interface Session {}
|
||||||
|
|
||||||
|
interface Stuff {}
|
||||||
|
}
|
15
frontend/src/app.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="description" content="Llama bot web interface" />
|
||||||
|
<link rel="icon" href="/assets/icon/llama-color.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
|
%svelte.head%
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
%svelte.body%
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,50 +0,0 @@
|
||||||
import styled from "styled-components"
|
|
||||||
|
|
||||||
const StyledExploreContainer = styled.div`
|
|
||||||
text-align: center;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
|
|
||||||
strong {
|
|
||||||
font-size: 20px;
|
|
||||||
line-height: 26px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 22px;
|
|
||||||
color: #8c8c8c;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
interface ContainerProps {
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const ExploreContainer: React.FC<ContainerProps> = ({ name }) => {
|
|
||||||
return (
|
|
||||||
<StyledExploreContainer className="container">
|
|
||||||
<strong>{name}</strong>
|
|
||||||
<p>
|
|
||||||
Explore{" "}
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
href="https://ionicframework.com/docs/components"
|
|
||||||
>
|
|
||||||
UI Components
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</StyledExploreContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ExploreContainer
|
|
|
@ -1,58 +0,0 @@
|
||||||
import React from "react"
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
|
||||||
import { faGithub } from "@fortawesome/free-brands-svg-icons"
|
|
||||||
import styled from "styled-components"
|
|
||||||
|
|
||||||
const StyledFooter = styled.div`
|
|
||||||
min-height: 5rem;
|
|
||||||
margin-top: auto;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
color: black;
|
|
||||||
background-color: rgb(0, 21, 41);
|
|
||||||
`
|
|
||||||
|
|
||||||
const StyledFooterContainer = styled.div`
|
|
||||||
width: 1500px;
|
|
||||||
|
|
||||||
color: grey;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 1rem 0 1rem;
|
|
||||||
`
|
|
||||||
|
|
||||||
const GithubLink = styled.a`
|
|
||||||
font-size: 2rem;
|
|
||||||
|
|
||||||
color: grey;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const Footer: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<StyledFooter>
|
|
||||||
<StyledFooterContainer>
|
|
||||||
<div>
|
|
||||||
Created by <b>developomp</b>
|
|
||||||
</div>
|
|
||||||
<GithubLink
|
|
||||||
href="https://github.com/llama-bot/llama-bot-web-interface"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faGithub} />
|
|
||||||
</GithubLink>
|
|
||||||
</StyledFooterContainer>
|
|
||||||
</StyledFooter>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Footer
|
|
|
@ -1,123 +0,0 @@
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
|
|
||||||
import styled from "styled-components"
|
|
||||||
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
|
||||||
import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons"
|
|
||||||
import { Button, LogOutIcon } from "evergreen-ui"
|
|
||||||
|
|
||||||
const StyledLlamaBotText = styled.b`
|
|
||||||
font-size: 1.5rem;
|
|
||||||
`
|
|
||||||
|
|
||||||
const StyledHeader = styled.div`
|
|
||||||
height: 3.75rem;
|
|
||||||
|
|
||||||
color: white;
|
|
||||||
line-height: 100%;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
justify-items: space-between;
|
|
||||||
|
|
||||||
padding: 0 0.5rem 0 0.5rem;
|
|
||||||
|
|
||||||
div {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
div:nth-child(1) {
|
|
||||||
flex: 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: hsla(0, 0%, 100%, 0.65);
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
padding: 0 1rem;
|
|
||||||
|
|
||||||
:hover {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
margin-top: 0.4rem;
|
|
||||||
font-size: x-small;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const StyledLoginButton = styled(Button)`
|
|
||||||
color: white;
|
|
||||||
padding: 8 12 8 12;
|
|
||||||
border-radius: 5;
|
|
||||||
background-color: indianred;
|
|
||||||
|
|
||||||
:hover {
|
|
||||||
background-color: firebrick !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:active {
|
|
||||||
background-color: darkred !important;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const Navbar: React.FC = () => {
|
|
||||||
const [isLoggedIn, setIsLoggedIn] = useState(false)
|
|
||||||
const [userName, setUserName] = useState("")
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window
|
|
||||||
.fetch("/api/user-data", { credentials: "same-origin" })
|
|
||||||
.then((data) => data.json())
|
|
||||||
.then((data) => {
|
|
||||||
setIsLoggedIn(true)
|
|
||||||
setUserName(`${data.username}#${data.discriminator}`)
|
|
||||||
})
|
|
||||||
.catch()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledHeader>
|
|
||||||
<div>
|
|
||||||
<img
|
|
||||||
src={"assets/icon/llama.png"}
|
|
||||||
alt="llama logo"
|
|
||||||
style={{ width: "60px" }}
|
|
||||||
/>
|
|
||||||
<StyledLlamaBotText>Llama Bot</StyledLlamaBotText>
|
|
||||||
<a
|
|
||||||
href="https://llama-bot.github.io/llama-bot-docs/docs/web-interface/overview"
|
|
||||||
target="_"
|
|
||||||
>
|
|
||||||
Docs
|
|
||||||
<FontAwesomeIcon icon={faExternalLinkAlt} />
|
|
||||||
</a>
|
|
||||||
<a href="https://discord.gg/aQqamSCUcS" target="_">
|
|
||||||
Discord
|
|
||||||
<FontAwesomeIcon icon={faExternalLinkAlt} />
|
|
||||||
</a>
|
|
||||||
<a href="https://status.llama.developomp.com" target="_">
|
|
||||||
Status
|
|
||||||
<FontAwesomeIcon icon={faExternalLinkAlt} />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{isLoggedIn && `Logged in as ${userName}`}
|
|
||||||
|
|
||||||
<StyledLoginButton
|
|
||||||
appearance="mini1mal"
|
|
||||||
onClick={() => {
|
|
||||||
window.location.href = isLoggedIn
|
|
||||||
? "/api/logout"
|
|
||||||
: "/api/login"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isLoggedIn ? `${(<LogOutIcon />)} Logout` : "Login"}
|
|
||||||
</StyledLoginButton>
|
|
||||||
</div>
|
|
||||||
</StyledHeader>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Navbar
|
|
1
frontend/src/constants.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const titlePrefix = "Llama Bot | "
|
|
@ -1,8 +0,0 @@
|
||||||
import { createContext, Dispatch, SetStateAction } from "react"
|
|
||||||
|
|
||||||
const SidebarCollapsedContext = createContext({
|
|
||||||
isSidebarCollapsed: false,
|
|
||||||
setSidebarCollapsed: (() => {}) as Dispatch<SetStateAction<boolean>>,
|
|
||||||
})
|
|
||||||
|
|
||||||
export { SidebarCollapsedContext }
|
|
24
frontend/src/hooks.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import cookie from "cookie"
|
||||||
|
import { v4 as uuid } from "@lukeed/uuid"
|
||||||
|
import type { Handle } from "@sveltejs/kit"
|
||||||
|
|
||||||
|
export const handle: Handle = async ({ event, resolve }) => {
|
||||||
|
const cookies = cookie.parse(event.request.headers.get("cookie") || "")
|
||||||
|
event.locals.userid = cookies.userid || uuid()
|
||||||
|
|
||||||
|
const response = await resolve(event)
|
||||||
|
|
||||||
|
if (!cookies.userid) {
|
||||||
|
// if this is the first time the user has visited this app,
|
||||||
|
// set a cookie so that we recognise them when they return
|
||||||
|
response.headers.set(
|
||||||
|
"set-cookie",
|
||||||
|
cookie.serialize("userid", event.locals.userid, {
|
||||||
|
path: "/",
|
||||||
|
httpOnly: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
|
@ -1,22 +0,0 @@
|
||||||
import React from "react"
|
|
||||||
import ReactDOM from "react-dom"
|
|
||||||
import App from "./App"
|
|
||||||
import * as serviceWorkerRegistration from "./serviceWorkerRegistration"
|
|
||||||
import reportWebVitals from "./reportWebVitals"
|
|
||||||
|
|
||||||
ReactDOM.render(
|
|
||||||
<React.StrictMode>
|
|
||||||
<App />
|
|
||||||
</React.StrictMode>,
|
|
||||||
document.getElementById("root")
|
|
||||||
)
|
|
||||||
|
|
||||||
// If you want your app to work offline and load faster, you can change
|
|
||||||
// unregister() to register() below. Note this comes with some pitfalls.
|
|
||||||
// Learn more about service workers: https://cra.link/PWA
|
|
||||||
serviceWorkerRegistration.unregister()
|
|
||||||
|
|
||||||
// If you want to start measuring performance in your app, pass a function
|
|
||||||
// to log results (for example: reportWebVitals(console.log))
|
|
||||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
|
||||||
reportWebVitals()
|
|
96
frontend/src/lib/Footer.svelte
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Github from "svelte-icons/fa/FaGithub.svelte"
|
||||||
|
import Discord from "svelte-icons/fa/FaDiscord.svelte"
|
||||||
|
import Heartbeat from "svelte-icons/fa/FaHeartbeat.svelte"
|
||||||
|
import Book from "svelte-icons/fa/FaBook.svelte"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<div class="container">
|
||||||
|
<div class="created-by">
|
||||||
|
Created by <b>developomp</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- right links -->
|
||||||
|
<div class="icons">
|
||||||
|
<a
|
||||||
|
class="icon"
|
||||||
|
alt="Documentation"
|
||||||
|
href="https://llama-bot.github.io/llama-bot-docs/docs/web-interface/overview"
|
||||||
|
target="_"
|
||||||
|
>
|
||||||
|
<Book />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="icon"
|
||||||
|
alt="Server status"
|
||||||
|
href="https://status.llama.developomp.com"
|
||||||
|
target="_"
|
||||||
|
>
|
||||||
|
<Heartbeat />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="icon"
|
||||||
|
alt="Discord server"
|
||||||
|
href="https://discord.gg/aQqamSCUcS"
|
||||||
|
target="_"
|
||||||
|
>
|
||||||
|
<Discord />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="icon"
|
||||||
|
alt="Github repository"
|
||||||
|
href="https://github.com/llama-bot"
|
||||||
|
target="_"
|
||||||
|
>
|
||||||
|
<Github />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
footer {
|
||||||
|
--height: 8rem;
|
||||||
|
|
||||||
|
background-color: var(--dark);
|
||||||
|
|
||||||
|
height: var(--height);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
padding: 0 var(--h-padding);
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: min(100%, var(--max-width));
|
||||||
|
height: var(--height);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between; // spread content
|
||||||
|
align-items: center; // vertically center elements
|
||||||
|
|
||||||
|
.created-by {
|
||||||
|
color: darkgrey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icons {
|
||||||
|
height: 24px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
color: lightgrey;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
139
frontend/src/lib/Header.svelte
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from "$app/stores"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
|
let isLoggedIn = false
|
||||||
|
let userName = ""
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
// window
|
||||||
|
// .fetch("/api/user-data", { credentials: "same-origin" })
|
||||||
|
// .then((data) => data.json())
|
||||||
|
// .then((data) => {
|
||||||
|
// setIsLoggedIn(true)
|
||||||
|
// setUserName(`${data.username}#${data.discriminator}`)
|
||||||
|
// })
|
||||||
|
// .catch()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<nav>
|
||||||
|
<div class="left-content">
|
||||||
|
<img class="icon" src="assets/icon/llama.png" alt="llama logo" />
|
||||||
|
|
||||||
|
<b>Llama Bot</b>
|
||||||
|
|
||||||
|
<div class="links">
|
||||||
|
<a
|
||||||
|
class:active={$page.url.pathname === "/"}
|
||||||
|
sveltekit:prefetch
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class:active={$page.url.pathname === "/about"}
|
||||||
|
sveltekit:prefetch
|
||||||
|
href="/about"
|
||||||
|
>
|
||||||
|
About
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="login">
|
||||||
|
{#if isLoggedIn}
|
||||||
|
Logged in as {userName}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="login-button">
|
||||||
|
<a href={isLoggedIn ? "/api/logout" : "/api/login"}>
|
||||||
|
{isLoggedIn ? "Logout" : "Login"}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
header {
|
||||||
|
--height: 4rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
height: var(--height);
|
||||||
|
|
||||||
|
padding: 0 var(--h-padding);
|
||||||
|
|
||||||
|
background-color: var(--dark);
|
||||||
|
color: var(--light);
|
||||||
|
|
||||||
|
nav {
|
||||||
|
width: min(100%, var(--max-width)); // cap width
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
height: var(--height);
|
||||||
|
|
||||||
|
.left-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
img.icon {
|
||||||
|
width: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
b {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.8rem;
|
||||||
|
margin-left: 2rem;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-bottom: 3px solid white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: right;
|
||||||
|
|
||||||
|
.login-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 1rem;
|
||||||
|
|
||||||
|
background-color: indianred;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: firebrick !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: darkred !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
29
frontend/src/lib/Sidebar.svelte
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Heart from "svelte-icons/fa/FaHeart.svelte"
|
||||||
|
import List from "svelte-icons/fa/FaList.svelte"
|
||||||
|
import History from "svelte-icons/fa/FaHistory.svelte"
|
||||||
|
import Issue from "svelte-icons/fa/FaExclamationCircle.svelte"
|
||||||
|
|
||||||
|
const entries = [
|
||||||
|
{
|
||||||
|
title: "Favorites",
|
||||||
|
url: "/favorites",
|
||||||
|
icon: Heart,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Modules",
|
||||||
|
url: "/modules",
|
||||||
|
icon: List,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Logs",
|
||||||
|
url: "/logs",
|
||||||
|
icon: History,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Incidents",
|
||||||
|
url: "/incidents",
|
||||||
|
icon: Issue,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
|
@ -1,16 +0,0 @@
|
||||||
import styled from "styled-components"
|
|
||||||
|
|
||||||
const StyledHome = styled.div`
|
|
||||||
text-align: center;
|
|
||||||
`
|
|
||||||
|
|
||||||
const Home: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<StyledHome>
|
|
||||||
<h3>Home</h3>
|
|
||||||
<h4>Log in to list servers</h4>
|
|
||||||
</StyledHome>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Home
|
|
|
@ -1,9 +0,0 @@
|
||||||
const Incidents: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h3>Incidents</h3>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Incidents
|
|
|
@ -1,9 +0,0 @@
|
||||||
const Logs: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h3>Logs</h3>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Logs
|
|
|
@ -1,5 +0,0 @@
|
||||||
const Modules: React.FC = () => {
|
|
||||||
return <h3>Modules</h3>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Modules
|
|
|
@ -1,15 +0,0 @@
|
||||||
import styled from "styled-components"
|
|
||||||
|
|
||||||
const StyledServers = styled.div`
|
|
||||||
text-align: center;
|
|
||||||
`
|
|
||||||
|
|
||||||
const Servers: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<StyledServers>
|
|
||||||
<h3>Available servers</h3>
|
|
||||||
</StyledServers>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Servers
|
|
1
frontend/src/react-app-env.d.ts
vendored
|
@ -1 +0,0 @@
|
||||||
/// <reference types="react-scripts" />
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { ReportHandler } from "web-vitals"
|
|
||||||
|
|
||||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
|
||||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
|
||||||
import("web-vitals").then(
|
|
||||||
({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
|
||||||
getCLS(onPerfEntry)
|
|
||||||
getFID(onPerfEntry)
|
|
||||||
getFCP(onPerfEntry)
|
|
||||||
getLCP(onPerfEntry)
|
|
||||||
getTTFB(onPerfEntry)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default reportWebVitals
|
|
49
frontend/src/routes/__layout.svelte
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Header from "$lib/Header.svelte"
|
||||||
|
import Footer from "$lib/Footer.svelte"
|
||||||
|
|
||||||
|
import { Circle2 } from "svelte-loading-spinners"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Header />
|
||||||
|
<main>
|
||||||
|
<slot />
|
||||||
|
This loader does nothing
|
||||||
|
<Circle2 />
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
|
||||||
|
<style lang="scss" global>
|
||||||
|
@import "@fontsource/noto-sans/index.css";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* global CSS variables */
|
||||||
|
--dark: #001529;
|
||||||
|
--light: #f0f2f5;
|
||||||
|
--max-width: 1500px;
|
||||||
|
--h-padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
font-family: "Noto Sans";
|
||||||
|
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
background-color: var(--light);
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
align-self: center;
|
||||||
|
|
||||||
|
width: min(100%, var(--max-width)); // cap width
|
||||||
|
}
|
||||||
|
</style>
|
12
frontend/src/routes/about.svelte
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { titlePrefix } from "../constants"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>{titlePrefix}About</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<h1>About</h1>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
12
frontend/src/routes/index.svelte
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { titlePrefix } from "../constants"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>{titlePrefix}Home</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<h1>Home</h1>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
|
@ -1,81 +0,0 @@
|
||||||
/// <reference lib="webworker" />
|
|
||||||
/* eslint-disable no-restricted-globals */
|
|
||||||
|
|
||||||
// This service worker can be customized!
|
|
||||||
// See https://developers.google.com/web/tools/workbox/modules
|
|
||||||
// for the list of available Workbox modules, or add any other
|
|
||||||
// code you'd like.
|
|
||||||
// You can also remove this file if you'd prefer not to use a
|
|
||||||
// service worker, and the Workbox build step will be skipped.
|
|
||||||
|
|
||||||
import { clientsClaim } from "workbox-core"
|
|
||||||
import { ExpirationPlugin } from "workbox-expiration"
|
|
||||||
import { precacheAndRoute, createHandlerBoundToURL } from "workbox-precaching"
|
|
||||||
import { registerRoute } from "workbox-routing"
|
|
||||||
import { StaleWhileRevalidate } from "workbox-strategies"
|
|
||||||
|
|
||||||
declare const self: ServiceWorkerGlobalScope
|
|
||||||
|
|
||||||
clientsClaim()
|
|
||||||
|
|
||||||
// Precache all of the assets generated by your build process.
|
|
||||||
// Their URLs are injected into the manifest variable below.
|
|
||||||
// This variable must be present somewhere in your service worker file,
|
|
||||||
// even if you decide not to use precaching. See https://cra.link/PWA
|
|
||||||
precacheAndRoute(self.__WB_MANIFEST)
|
|
||||||
|
|
||||||
// Set up App Shell-style routing, so that all navigation requests
|
|
||||||
// are fulfilled with your index.html shell. Learn more at
|
|
||||||
// https://developers.google.com/web/fundamentals/architecture/app-shell
|
|
||||||
const fileExtensionRegexp = new RegExp("/[^/?]+\\.[^/]+$")
|
|
||||||
registerRoute(
|
|
||||||
// Return false to exempt requests from being fulfilled by index.html.
|
|
||||||
({ request, url }: { request: Request; url: URL }) => {
|
|
||||||
// If this isn't a navigation, skip.
|
|
||||||
if (request.mode !== "navigate") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is a URL that starts with /_, skip.
|
|
||||||
if (url.pathname.startsWith("/_")) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this looks like a URL for a resource, because it contains
|
|
||||||
// a file extension, skip.
|
|
||||||
if (url.pathname.match(fileExtensionRegexp)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true to signal that we want to use the handler.
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
createHandlerBoundToURL(process.env.PUBLIC_URL + "/index.html")
|
|
||||||
)
|
|
||||||
|
|
||||||
// An example runtime caching route for requests that aren't handled by the
|
|
||||||
// precache, in this case same-origin .png requests like those from in public/
|
|
||||||
registerRoute(
|
|
||||||
// Add in any other file extensions or routing criteria as needed.
|
|
||||||
({ url }) =>
|
|
||||||
url.origin === self.location.origin && url.pathname.endsWith(".png"),
|
|
||||||
// Customize this strategy as needed, e.g., by changing to CacheFirst.
|
|
||||||
new StaleWhileRevalidate({
|
|
||||||
cacheName: "images",
|
|
||||||
plugins: [
|
|
||||||
// Ensure that once this runtime cache reaches a maximum size the
|
|
||||||
// least-recently used images are removed.
|
|
||||||
new ExpirationPlugin({ maxEntries: 50 }),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// This allows the web app to trigger skipWaiting via
|
|
||||||
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
|
|
||||||
self.addEventListener("message", (event) => {
|
|
||||||
if (event.data && event.data.type === "SKIP_WAITING") {
|
|
||||||
self.skipWaiting()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Any other custom service worker logic can go here.
|
|
|
@ -1,147 +0,0 @@
|
||||||
// This optional code is used to register a service worker.
|
|
||||||
// register() is not called by default.
|
|
||||||
|
|
||||||
// This lets the app load faster on subsequent visits in production, and gives
|
|
||||||
// it offline capabilities. However, it also means that developers (and users)
|
|
||||||
// will only see deployed updates on subsequent visits to a page, after all the
|
|
||||||
// existing tabs open on the page have been closed, since previously cached
|
|
||||||
// resources are updated in the background.
|
|
||||||
|
|
||||||
// To learn more about the benefits of this model and instructions on how to
|
|
||||||
// opt-in, read https://cra.link/PWA
|
|
||||||
|
|
||||||
const isLocalhost = Boolean(
|
|
||||||
window.location.hostname === "localhost" ||
|
|
||||||
// [::1] is the IPv6 localhost address.
|
|
||||||
window.location.hostname === "[::1]" ||
|
|
||||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
|
||||||
window.location.hostname.match(
|
|
||||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config = {
|
|
||||||
onSuccess?: (registration: ServiceWorkerRegistration) => void
|
|
||||||
onUpdate?: (registration: ServiceWorkerRegistration) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function register(config?: Config) {
|
|
||||||
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
|
|
||||||
// The URL constructor is available in all browsers that support SW.
|
|
||||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
|
|
||||||
if (publicUrl.origin !== window.location.origin) {
|
|
||||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
|
||||||
// from what our page is served on. This might happen if a CDN is used to
|
|
||||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("load", () => {
|
|
||||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
|
|
||||||
|
|
||||||
if (isLocalhost) {
|
|
||||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
|
||||||
checkValidServiceWorker(swUrl, config)
|
|
||||||
|
|
||||||
// Add some additional logging to localhost, pointing developers to the
|
|
||||||
// service worker/PWA documentation.
|
|
||||||
navigator.serviceWorker.ready.then(() => {
|
|
||||||
console.log(
|
|
||||||
"This web app is being served cache-first by a service " +
|
|
||||||
"worker. To learn more, visit https://cra.link/PWA"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Is not localhost. Just register service worker
|
|
||||||
registerValidSW(swUrl, config)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerValidSW(swUrl: string, config?: Config) {
|
|
||||||
navigator.serviceWorker
|
|
||||||
.register(swUrl)
|
|
||||||
.then((registration) => {
|
|
||||||
registration.onupdatefound = () => {
|
|
||||||
const installingWorker = registration.installing
|
|
||||||
if (installingWorker == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
installingWorker.onstatechange = () => {
|
|
||||||
if (installingWorker.state === "installed") {
|
|
||||||
if (navigator.serviceWorker.controller) {
|
|
||||||
// At this point, the updated precached content has been fetched,
|
|
||||||
// but the previous service worker will still serve the older
|
|
||||||
// content until all client tabs are closed.
|
|
||||||
console.log(
|
|
||||||
"New content is available and will be used when all " +
|
|
||||||
"tabs for this page are closed. See https://cra.link/PWA."
|
|
||||||
)
|
|
||||||
|
|
||||||
// Execute callback
|
|
||||||
if (config && config.onUpdate) {
|
|
||||||
config.onUpdate(registration)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// At this point, everything has been precached.
|
|
||||||
// It's the perfect time to display a
|
|
||||||
// "Content is cached for offline use." message.
|
|
||||||
console.log("Content is cached for offline use.")
|
|
||||||
|
|
||||||
// Execute callback
|
|
||||||
if (config && config.onSuccess) {
|
|
||||||
config.onSuccess(registration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("Error during service worker registration:", error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkValidServiceWorker(swUrl: string, config?: Config) {
|
|
||||||
// Check if the service worker can be found. If it can't reload the page.
|
|
||||||
fetch(swUrl, {
|
|
||||||
headers: { "Service-Worker": "script" },
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
// Ensure service worker exists, and that we really are getting a JS file.
|
|
||||||
const contentType = response.headers.get("content-type")
|
|
||||||
if (
|
|
||||||
response.status === 404 ||
|
|
||||||
(contentType != null &&
|
|
||||||
contentType.indexOf("javascript") === -1)
|
|
||||||
) {
|
|
||||||
// No service worker found. Probably a different app. Reload the page.
|
|
||||||
navigator.serviceWorker.ready.then((registration) => {
|
|
||||||
registration.unregister().then(() => {
|
|
||||||
window.location.reload()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Service worker found. Proceed as normal.
|
|
||||||
registerValidSW(swUrl, config)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
console.log(
|
|
||||||
"No internet connection found. App is running in offline mode."
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unregister() {
|
|
||||||
if ("serviceWorker" in navigator) {
|
|
||||||
navigator.serviceWorker.ready
|
|
||||||
.then((registration) => {
|
|
||||||
registration.unregister()
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
|
||||||
// allows you to do things like:
|
|
||||||
// expect(element).toHaveTextContent(/react/i)
|
|
||||||
// learn more: https://github.com/testing-library/jest-dom
|
|
||||||
import "@testing-library/jest-dom/extend-expect"
|
|
||||||
|
|
||||||
// Mock matchmedia
|
|
||||||
window.matchMedia =
|
|
||||||
window.matchMedia ||
|
|
||||||
function () {
|
|
||||||
return {
|
|
||||||
matches: false,
|
|
||||||
addListener: function () {},
|
|
||||||
removeListener: function () {},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
import { defaultTheme, Theme } from "evergreen-ui"
|
|
||||||
|
|
||||||
const theme: Theme = {
|
|
||||||
...defaultTheme,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default theme
|
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
BIN
frontend/static/assets/icon/logo-bg.png
Normal file
After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
frontend/static/favicon.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
18
frontend/svelte.config.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import adapter from "@sveltejs/adapter-static"
|
||||||
|
import preprocess from "svelte-preprocess"
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
|
const config = {
|
||||||
|
preprocess: preprocess(),
|
||||||
|
|
||||||
|
kit: {
|
||||||
|
adapter: adapter({
|
||||||
|
pages: "build",
|
||||||
|
assets: "build",
|
||||||
|
fallback: "index.html",
|
||||||
|
precompress: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config
|
|
@ -1,20 +1,41 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"strict": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"resolveJsonModule": true,
|
"module": "es2020",
|
||||||
|
"lib": ["es2020", "DOM"],
|
||||||
|
"target": "es2020",
|
||||||
|
/**
|
||||||
|
svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
|
||||||
|
to enforce using \`import type\` instead of \`import\` for Types.
|
||||||
|
*/
|
||||||
|
"importsNotUsedAsValues": "error",
|
||||||
|
/**
|
||||||
|
TypeScript doesn't know about import usages in the template because it only sees the
|
||||||
|
script of a Svelte file. Therefore preserve all value imports. Requires TS 4.5 or higher.
|
||||||
|
*/
|
||||||
|
"preserveValueImports": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"resolveJsonModule": true,
|
||||||
"jsx": "react-jsx"
|
/**
|
||||||
|
To have warnings/errors of the Svelte compiler at the correct position,
|
||||||
|
enable source maps by default.
|
||||||
|
*/
|
||||||
|
"sourceMap": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"paths": {
|
||||||
|
"$lib": ["src/lib"],
|
||||||
|
"$lib/*": ["src/lib/*"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": [
|
||||||
|
"src/**/*.d.ts",
|
||||||
|
"src/**/*.js",
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.svelte"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
12161
frontend/yarn.lock
8
functions/.gitignore
vendored
|
@ -1,6 +1,8 @@
|
||||||
# Compiled JavaScript files
|
/node_modules/
|
||||||
/lib
|
|
||||||
|
|
||||||
# sevrets
|
# Compiled JavaScript files
|
||||||
|
/lib/
|
||||||
|
|
||||||
|
# secrets
|
||||||
/src/secret.json
|
/src/secret.json
|
||||||
/src/firebase-adminsdk.json
|
/src/firebase-adminsdk.json
|
||||||
|
|