Initial commit
This commit is contained in:
commit
564cc84db1
14 changed files with 2864 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/node_modules/
|
||||||
|
/dist/
|
||||||
|
/.idea/
|
||||||
|
*.env
|
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
20.11
|
14
index.html
Normal file
14
index.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Tea Dashboard (Loading…)</title>
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<script type="module" src="./src/main.ts"></script>
|
||||||
|
<link rel="stylesheet" href="./src/global.css"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<div id="tooltips"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
36
package.json
Normal file
36
package.json
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"name": "@tea-project/web-frontend",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
|
"@types/node": "^20.12.12",
|
||||||
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
|
"browserslist": "^4.23.0",
|
||||||
|
"modern-normalize": "^2.0.0",
|
||||||
|
"sass": "^1.77.2",
|
||||||
|
"type-fest": "^4.18.2",
|
||||||
|
"typescript": "^5.4.5",
|
||||||
|
"unocss": "^0.60.2",
|
||||||
|
"unplugin-icons": "^0.19.0",
|
||||||
|
"unplugin-vue-router": "^0.8.6",
|
||||||
|
"vite": "^5.2.11",
|
||||||
|
"windicss": "^3.5.6"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fontsource-variable/fraunces": "^5.0.21",
|
||||||
|
"@fontsource-variable/manrope": "^5.0.20",
|
||||||
|
"@iconify-json/solar": "^1.1.9",
|
||||||
|
"@iconify-json/svg-spinners": "^1.1.2",
|
||||||
|
"@unhead/vue": "^1.9.10",
|
||||||
|
"@vueuse/core": "^10.9.0",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"vue": "^3.4.27",
|
||||||
|
"vue-router": "^4.3.2"
|
||||||
|
}
|
||||||
|
}
|
2494
pnpm-lock.yaml
generated
Normal file
2494
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
21
src/App.vue
Normal file
21
src/App.vue
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<template>
|
||||||
|
<NavigationMenuWrapper>
|
||||||
|
<div class="p-8 flex-shrink-0 flex-grow-0">
|
||||||
|
<SuspenseRouterView/>
|
||||||
|
</div>
|
||||||
|
</NavigationMenuWrapper>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style module lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useHead } from "@unhead/vue"
|
||||||
|
import SuspenseRouterView from "./components/reusable/SuspenseRouterView.vue"
|
||||||
|
import NavigationMenuWrapper from "@/components/NavigationMenuWrapper.vue"
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
titleTemplate: title => title === undefined ? "Tea Dashboard" : `${title} — Tea Dashboard`
|
||||||
|
})
|
||||||
|
</script>
|
71
src/components/NavigationMenuWrapper.vue
Normal file
71
src/components/NavigationMenuWrapper.vue
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<template>
|
||||||
|
<nav class="w-70 bg-gray-900 fixed top-0 left-0 bottom-0 p-8 flex flex-col gap-3" :class="$style.root">
|
||||||
|
<ul class="list-none p-0 grid grid-cols-2 gap-1">
|
||||||
|
<li v-for="type in mediaTypes" :key="type.id">
|
||||||
|
<router-link
|
||||||
|
class="relative rounded-sm w-full p-2 no-underline flex flex-col items-center gap-1 group"
|
||||||
|
:to="`/media/${type.id}`"
|
||||||
|
:style="{ color: type.color }"
|
||||||
|
>
|
||||||
|
<component class="absolute -top-5 left-0 opacity-5 blur-5 pointer-events-none group-hover:blur-lg group-hover:opacity-30 transition duration-300" :is="type.blob"/>
|
||||||
|
<component class="text-3xl" :is="type.icon"/>
|
||||||
|
<span class="text-base">{{ type.label }}</span>
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<div class="pl-70">
|
||||||
|
<slot/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style module lang="scss">
|
||||||
|
.root {
|
||||||
|
box-shadow: 0 0 4px 0 theme("colors.gray.700");
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: url("../assets/noise.png") repeat;
|
||||||
|
opacity: 15%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Component } from "vue"
|
||||||
|
import BooksIcon from "virtual:icons/ph/books-light"
|
||||||
|
import FilmStripIcon from "virtual:icons/ph/film-strip-light"
|
||||||
|
import LiteratureBlob from "./blobs/LiteratureBlob.vue"
|
||||||
|
import MoviesBlob from "./blobs/MoviesBlob.vue"
|
||||||
|
|
||||||
|
interface MediaType {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
icon: Component
|
||||||
|
blob: Component
|
||||||
|
color: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const mediaTypes: MediaType[] = [
|
||||||
|
{
|
||||||
|
id: "literature",
|
||||||
|
label: "Literature",
|
||||||
|
icon: BooksIcon,
|
||||||
|
blob: LiteratureBlob,
|
||||||
|
color: "#11b970"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "films",
|
||||||
|
label: "Movies",
|
||||||
|
icon: FilmStripIcon,
|
||||||
|
blob: MoviesBlob,
|
||||||
|
color: "#d2b214"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
</script>
|
62
src/components/SuspenseRouterView.vue
Normal file
62
src/components/SuspenseRouterView.vue
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<template>
|
||||||
|
<router-view v-slot="{ Component }">
|
||||||
|
<template v-if="Component">
|
||||||
|
<suspense @pending="loading = true" @resolve="loading = false">
|
||||||
|
<!-- The key makes that components are not reused if only params changed -->
|
||||||
|
<component :is="Component" :key="JSON.stringify($route.params)"/>
|
||||||
|
</suspense>
|
||||||
|
</template>
|
||||||
|
</router-view>
|
||||||
|
<div
|
||||||
|
class="pt-10 absolute inset-0 bg-background"
|
||||||
|
:class="$style.loadingOverlay"
|
||||||
|
:data-is-visible="loading || forceLoading"
|
||||||
|
>
|
||||||
|
<div class="flex justify-center gap-3 text-2xl p-8" :class="$style.loadingIndicator">
|
||||||
|
<LoadingIcon/>
|
||||||
|
<span>{{ loadingText }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style module lang="scss">
|
||||||
|
.loadingOverlay {
|
||||||
|
opacity: 0;
|
||||||
|
transition: 100ms ease opacity;
|
||||||
|
|
||||||
|
&[data-is-visible="true"] {
|
||||||
|
opacity: 100%;
|
||||||
|
transition-duration: 200ms;
|
||||||
|
transition-delay: 200ms;
|
||||||
|
|
||||||
|
.loadingIndicator {
|
||||||
|
opacity: 100%;
|
||||||
|
transition-delay: 1500ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-is-visible="false"] {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadingIndicator {
|
||||||
|
opacity: 0;
|
||||||
|
transition: 200ms ease opacity;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue"
|
||||||
|
import LoadingIcon from "./LoadingIcon.vue"
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
loadingText: {
|
||||||
|
type: String,
|
||||||
|
default: "Loading…"
|
||||||
|
},
|
||||||
|
forceLoading: Boolean
|
||||||
|
})
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
</script>
|
0
src/generated-types/.gitkeep
Normal file
0
src/generated-types/.gitkeep
Normal file
35
src/global.css
Normal file
35
src/global.css
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
@import "modern-normalize/modern-normalize.css";
|
||||||
|
|
||||||
|
/* fraunces-latin-wght-italic */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Fraunces Variable";
|
||||||
|
font-style: italic;
|
||||||
|
font-display: block;
|
||||||
|
font-weight: 100 900;
|
||||||
|
src: url("@fontsource-variable/fraunces/files/fraunces-latin-wght-italic.woff2") format('woff2-variations');
|
||||||
|
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* manrope-latin-wght-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Manrope Variable';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 200 800;
|
||||||
|
src: url(@fontsource-variable/manrope/files/manrope-latin-wght-normal.woff2) format('woff2-variations');
|
||||||
|
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body, #app {
|
||||||
|
@apply font-sans;
|
||||||
|
width: 100%;
|
||||||
|
background: theme("colors.gray.3");
|
||||||
|
color: hsl(0 0% 10%);
|
||||||
|
min-height: 100vh;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: currentColor;
|
||||||
|
}
|
19
src/main.ts
Normal file
19
src/main.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import "uno.css"
|
||||||
|
|
||||||
|
import { createApp } from "vue"
|
||||||
|
import App from "./App.vue"
|
||||||
|
import { createRouter, createWebHistory } from "vue-router/auto"
|
||||||
|
import { createHead } from "@unhead/vue"
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory()
|
||||||
|
})
|
||||||
|
|
||||||
|
const head = createHead()
|
||||||
|
|
||||||
|
app
|
||||||
|
.use(router)
|
||||||
|
.use(head)
|
||||||
|
.mount("#app")
|
38
tsconfig.json
Normal file
38
tsconfig.json
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"declaration": false,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"dom"
|
||||||
|
],
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"allowJs": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"rootDir": "src",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"stripInternal": true,
|
||||||
|
"noUncheckedIndexedAccess": false,
|
||||||
|
"target": "esnext",
|
||||||
|
"types": [
|
||||||
|
"vite/client",
|
||||||
|
"vite-plugin-pages/client",
|
||||||
|
"unplugin-icons/types/vue",
|
||||||
|
"unplugin-vue-router/client"
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"./src/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.vue",
|
||||||
|
]
|
||||||
|
}
|
46
uno.config.ts
Normal file
46
uno.config.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import { defineConfig, transformerDirectives, presetWind } from "unocss"
|
||||||
|
import colors from "windicss/colors"
|
||||||
|
|
||||||
|
const headlessUiVariantRegex = /^ui-(\w+):/
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetWind({
|
||||||
|
dark: "media",
|
||||||
|
arbitraryVariants: false,
|
||||||
|
preflight: true
|
||||||
|
})
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
fontFamily: {
|
||||||
|
sans: `"Manrope Variable", sans-serif`,
|
||||||
|
serif: "Fraunces Variable, serif",
|
||||||
|
system: "sans-serif"
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
dark: colors.dark,
|
||||||
|
light: colors.light,
|
||||||
|
gray: colors.warmGray
|
||||||
|
}
|
||||||
|
},
|
||||||
|
variants: [
|
||||||
|
matcher => {
|
||||||
|
const matches = matcher.match(headlessUiVariantRegex)
|
||||||
|
|
||||||
|
if (matches === null) {
|
||||||
|
return matcher
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefix = matches[0]
|
||||||
|
const state = matches[1]
|
||||||
|
|
||||||
|
return {
|
||||||
|
matcher: matcher.slice(prefix.length),
|
||||||
|
selector: s => `${s}[data-headlessui-state~="${state}"]`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
transformers: [
|
||||||
|
transformerDirectives()
|
||||||
|
]
|
||||||
|
})
|
23
vite.config.ts
Normal file
23
vite.config.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { defineConfig } from "vite"
|
||||||
|
import vuePlugin from "@vitejs/plugin-vue"
|
||||||
|
import iconsPlugin from "unplugin-icons/vite"
|
||||||
|
import unoCssPlugin from "unocss/vite"
|
||||||
|
import vueRouterPlugin from "unplugin-vue-router/vite"
|
||||||
|
import { resolve } from "node:path"
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vueRouterPlugin({
|
||||||
|
importMode: "async",
|
||||||
|
dts: "./src/generated-types/vue-router.d.ts"
|
||||||
|
}),
|
||||||
|
vuePlugin(),
|
||||||
|
iconsPlugin(),
|
||||||
|
unoCssPlugin()
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": resolve(__dirname, "./src")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
Loading…
Add table
Reference in a new issue