add: solid.js powered frontend

This commit is contained in:
alikia2x (寒寒) 2025-06-20 01:00:48 +08:00
parent 92c3c8eefe
commit edbae956ca
Signed by: alikia2x
GPG Key ID: 56209E0CCD8420C6
78 changed files with 3227 additions and 0 deletions

28
packages/solid/.gitignore vendored Normal file
View File

@ -0,0 +1,28 @@
dist
.wrangler
.output
.vercel
.netlify
.vinxi
app.config.timestamp_*.js
# Environment
.env
.env*.local
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
*.launch
.settings/
# Temp
gitignore
# System Files
.DS_Store
Thumbs.db

32
packages/solid/README.md Normal file
View File

@ -0,0 +1,32 @@
# SolidStart
Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com);
## Creating a project
```bash
# create a new project in the current directory
npm init solid@latest
# create a new project in my-app
npm init solid@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
Solid apps are built with _presets_, which optimise your project for deployment to different environments.
By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different preset, add it to the `devDependencies` in `package.json` and specify in your `app.config.js`.
## This project was created with the [Solid CLI](https://github.com/solidjs-community/solid-cli)

View File

@ -0,0 +1,8 @@
import { defineConfig } from "@solidjs/start/config";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
vite: {
plugins: [tailwindcss()]
}
});

1648
packages/solid/bun.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
{
"name": "example-basic",
"type": "module",
"scripts": {
"dev": "vinxi dev --port 9200",
"build": "vinxi build",
"start": "vinxi start",
"version": "vinxi version"
},
"dependencies": {
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.15.0",
"@solidjs/start": "^1.1.0",
"@tailwindcss/vite": "^4.1.10",
"@types/luxon": "^3.6.2",
"luxon": "^3.6.1",
"solid-js": "^1.9.5",
"tailwindcss": "^4.1.10",
"vinxi": "^0.5.3"
},
"engines": {
"node": ">=22"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.10",
"postcss": "^8.5.6"
}
}

View File

@ -0,0 +1,5 @@
export default {
plugins: {
"@tailwindcss/postcss": {}
}
};

106
packages/solid/src/app.css Normal file
View File

@ -0,0 +1,106 @@
@import url("./fonts/InterFont/Inter.css");
@import url("./fonts/MiSans/MiSans.css");
@import "tailwindcss";
@theme {
--color-background: #fff8f6;
--color-on-background: #2a1613;
--color-surface: #fff8f6;
--color-surface-dim: #f7d2cc;
--color-surface-bright: #fff8f6;
--color-surface-container-lowest: #ffffff;
--color-surface-container-low: #fff0ee;
--color-surface-container: #ffe9e6;
--color-surface-container-high: #ffe2dd;
--color-surface-container-highest: #ffdad4;
--color-on-surface: #2a1613;
--color-surface-variant: #ffdad4;
--color-on-surface-variant: #5f3e39;
--color-inverse-surface: #422b27;
--color-inverse-on-surface: #ffedea;
--color-outline: #946e68;
--color-outline-variant: #eabcb4;
--color-shadow: #000000;
--color-scrim: #000000;
--color-surface-tint: #c00100;
--color-primary: #a50100;
--color-on-primary: #ffffff;
--color-primary-container: #eb0000;
--color-on-primary-container: #ffffff;
--color-inverse-primary: #ffb4a8;
--color-secondary: #b4271a;
--color-on-secondary: #ffffff;
--color-secondary-container: #ff7460;
--color-on-secondary-container: #2f0000;
--color-tertiary: #6f4800;
--color-on-tertiary: #ffffff;
--color-tertiary-container: #9f6900;
--color-on-tertiary-container: #ffffff;
--color-error: #ba1a1a;
--color-on-error: #ffffff;
--color-error-container: #ffdad6;
--color-on-error-container: #410002;
--color-dark-background: #210e0b;
--color-dark-on-background: #ffdad4;
--color-dark-surface: #210e0b;
--color-dark-surface-dim: #210e0b;
--color-dark-surface-bright: #4b332f;
--color-dark-surface-container-lowest: #1b0907;
--color-dark-surface-container-low: #2a1613;
--color-dark-surface-container: #2f1a17;
--color-dark-surface-container-high: #3a2421;
--color-dark-surface-container-highest: #462f2b;
--color-dark-on-surface: #ffdad4;
--color-dark-surface-variant: #5f3e39;
--color-dark-on-surface-variant: #eabcb4;
--color-dark-inverse-surface: #ffdad4;
--color-dark-inverse-on-surface: #422b27;
--color-dark-outline: #b08780;
--color-dark-outline-variant: #5f3e39;
--color-dark-shadow: #000000;
--color-dark-scrim: #000000;
--color-dark-surface-tint: #ffb4a8;
--color-dark-primary: #ffb4a8;
--color-dark-on-primary: #690000;
--color-dark-primary-container: #de0000;
--color-dark-on-primary-container: #ffffff;
--color-dark-inverse-primary: #c00100;
--color-dark-secondary: #ffb4a8;
--color-dark-on-secondary: #690000;
--color-dark-secondary-container: #870100;
--color-dark-on-secondary-container: #ffc9c0;
--color-dark-tertiary: #feba54;
--color-dark-on-tertiary: #452b00;
--color-dark-tertiary-container: #966300;
--color-dark-on-tertiary-container: #ffffff;
--color-dark-error: #ffb4ab;
--color-dark-on-error: #690005;
--color-dark-error-container: #93000a;
--color-dark-on-error-container: #ffdad6;
}
a {
@apply text-primary dark:text-dark-primary;
}
:root {
font-family: "Inter", "MiSans", sans-serif;
font-weight: 400;
@apply bg-surface dark:bg-dark-surface text-on-surface dark:text-dark-on-surface;
}
[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
appearance: none;
}
@supports (font-variation-settings: normal) {
:root {
font-family: "Inter Variable", "MiSans VF", sans-serif;
font-optical-sizing: auto;
font-weight: 330;
}
}

View File

@ -0,0 +1,19 @@
import { MetaProvider } from "@solidjs/meta";
import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { Suspense } from "solid-js";
import "./app.css";
export default function App() {
return (
<Router
root={(props) => (
<MetaProvider>
<Suspense>{props.children}</Suspense>
</MetaProvider>
)}
>
<FileRoutes />
</Router>
);
}

View File

@ -0,0 +1,21 @@
.increment {
font-family: inherit;
font-size: inherit;
padding: 1em 2em;
color: #335d92;
background-color: rgba(68, 107, 158, 0.1);
border-radius: 2em;
border: 2px solid rgba(68, 107, 158, 0);
outline: none;
width: 200px;
font-variant-numeric: tabular-nums;
cursor: pointer;
}
.increment:focus {
border: 2px solid #335d92;
}
.increment:active {
background-color: rgba(68, 107, 158, 0.2);
}

View File

@ -0,0 +1,11 @@
import { createSignal } from "solid-js";
import "./Counter.css";
export default function Counter() {
const [count, setCount] = createSignal(0);
return (
<button class="increment" onClick={() => setCount(count() + 1)} type="button">
Clicks: {count()}
</button>
);
}

View File

@ -0,0 +1,4 @@
// @refresh reload
import { mount, StartClient } from "@solidjs/start/client";
mount(() => <StartClient />, document.getElementById("app")!);

View File

@ -0,0 +1,23 @@
// @refresh reload
import { createHandler, StartServer } from "@solidjs/start/server";
import { MetaProvider } from "@solidjs/meta";
export default createHandler(() => (
<StartServer
document={({ assets, children, scripts }) => (
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
<MetaProvider></MetaProvider>
{assets}
</head>
<body>
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
));

BIN
packages/solid/src/fonts/InterFont/Inter-Black.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-BlackItalic.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-Bold.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-BoldItalic.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-ExtraBold.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-ExtraLight.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-Italic.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-Light.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-LightItalic.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-Medium.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-MediumItalic.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-Regular.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-SemiBold.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-Thin.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/Inter-ThinItalic.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,449 @@
@font-face {
font-family: "Inter Variable";
font-style: normal;
font-weight: 100 900;
font-display: fallback;
src: url("InterVariable.woff2") format("woff2");
}
@font-face {
font-family: "Inter Variable";
font-style: italic;
font-weight: 100 900;
font-display: fallback;
src: url("InterVariable-Italic.woff2") format("woff2");
}
/* static fonts */
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 100;
font-display: fallback;
src: url("Inter-Thin.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 100;
font-display: fallback;
src: url("Inter-ThinItalic.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 200;
font-display: fallback;
src: url("Inter-ExtraLight.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 200;
font-display: fallback;
src: url("Inter-ExtraLightItalic.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url("Inter-Light.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 300;
font-display: fallback;
src: url("Inter-LightItalic.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url("Inter-Regular.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url("Inter-Italic.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 500;
font-display: fallback;
src: url("Inter-Medium.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 500;
font-display: fallback;
src: url("Inter-MediumItalic.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url("Inter-SemiBold.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 600;
font-display: fallback;
src: url("Inter-SemiBoldItalic.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 700;
font-display: fallback;
src: url("Inter-Bold.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 700;
font-display: fallback;
src: url("Inter-BoldItalic.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 800;
font-display: fallback;
src: url("Inter-ExtraBold.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 800;
font-display: fallback;
src: url("Inter-ExtraBoldItalic.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 900;
font-display: fallback;
src: url("Inter-Black.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 900;
font-display: fallback;
src: url("Inter-BlackItalic.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: normal;
font-weight: 100;
font-display: fallback;
src: url("InterDisplay-Thin.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: italic;
font-weight: 100;
font-display: fallback;
src: url("InterDisplay-ThinItalic.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: normal;
font-weight: 200;
font-display: fallback;
src: url("InterDisplay-ExtraLight.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: italic;
font-weight: 200;
font-display: fallback;
src: url("InterDisplay-ExtraLightItalic.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url("InterDisplay-Light.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: italic;
font-weight: 300;
font-display: fallback;
src: url("InterDisplay-LightItalic.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url("InterDisplay-Regular.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url("InterDisplay-Italic.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: normal;
font-weight: 500;
font-display: fallback;
src: url("InterDisplay-Medium.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: italic;
font-weight: 500;
font-display: fallback;
src: url("InterDisplay-MediumItalic.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url("InterDisplay-SemiBold.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: italic;
font-weight: 600;
font-display: fallback;
src: url("InterDisplay-SemiBoldItalic.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: normal;
font-weight: 700;
font-display: fallback;
src: url("InterDisplay-Bold.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: italic;
font-weight: 700;
font-display: fallback;
src: url("InterDisplay-BoldItalic.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: normal;
font-weight: 800;
font-display: fallback;
src: url("InterDisplay-ExtraBold.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: italic;
font-weight: 800;
font-display: fallback;
src: url("InterDisplay-ExtraBoldItalic.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: normal;
font-weight: 900;
font-display: fallback;
src: url("InterDisplay-Black.woff2") format("woff2");
}
@font-face {
font-family: "InterDisplay";
font-style: italic;
font-weight: 900;
font-display: fallback;
src: url("InterDisplay-BlackItalic.woff2") format("woff2");
}
@font-feature-values InterVariable {
@character-variant {
cv01: 1;
cv02: 2;
cv03: 3;
cv04: 4;
cv05: 5;
cv06: 6;
cv07: 7;
cv08: 8;
cv09: 9;
cv10: 10;
cv11: 11;
cv12: 12;
cv13: 13;
alt-1: 1; /* Alternate one */
alt-3: 9; /* Flat-top three */
open-4: 2; /* Open four */
open-6: 3; /* Open six */
open-9: 4; /* Open nine */
lc-l-with-tail: 5; /* Lower-case L with tail */
simplified-u: 6; /* Simplified u */
alt-double-s: 7; /* Alternate German double s */
uc-i-with-serif: 8; /* Upper-case i with serif */
uc-g-with-spur: 10; /* Capital G with spur */
single-story-a: 11; /* Single-story a */
compact-lc-f: 12; /* Compact f */
compact-lc-t: 13; /* Compact t */
}
@styleset {
ss01: 1;
ss02: 2;
ss03: 3;
ss04: 4;
ss05: 5;
ss06: 6;
ss07: 7;
ss08: 8;
open-digits: 1; /* Open digits */
disambiguation: 2; /* Disambiguation (with zero) */
disambiguation-except-zero: 4; /* Disambiguation (no zero) */
round-quotes-and-commas: 3; /* Round quotes &amp; commas */
square-punctuation: 7; /* Square punctuation */
square-quotes: 8; /* Square quotes */
circled-characters: 5; /* Circled characters */
squared-characters: 6; /* Squared characters */
}
}
@font-feature-values Inter {
@character-variant {
cv01: 1;
cv02: 2;
cv03: 3;
cv04: 4;
cv05: 5;
cv06: 6;
cv07: 7;
cv08: 8;
cv09: 9;
cv10: 10;
cv11: 11;
cv12: 12;
cv13: 13;
alt-1: 1; /* Alternate one */
alt-3: 9; /* Flat-top three */
open-4: 2; /* Open four */
open-6: 3; /* Open six */
open-9: 4; /* Open nine */
lc-l-with-tail: 5; /* Lower-case L with tail */
simplified-u: 6; /* Simplified u */
alt-double-s: 7; /* Alternate German double s */
uc-i-with-serif: 8; /* Upper-case i with serif */
uc-g-with-spur: 10; /* Capital G with spur */
single-story-a: 11; /* Single-story a */
compact-lc-f: 12; /* Compact f */
compact-lc-t: 13; /* Compact t */
}
@styleset {
ss01: 1;
ss02: 2;
ss03: 3;
ss04: 4;
ss05: 5;
ss06: 6;
ss07: 7;
ss08: 8;
open-digits: 1; /* Open digits */
disambiguation: 2; /* Disambiguation (with zero) */
disambiguation-except-zero: 4; /* Disambiguation (no zero) */
round-quotes-and-commas: 3; /* Round quotes &amp; commas */
square-punctuation: 7; /* Square punctuation */
square-quotes: 8; /* Square quotes */
circled-characters: 5; /* Circled characters */
squared-characters: 6; /* Squared characters */
}
}
@font-feature-values InterDisplay {
@character-variant {
cv01: 1;
cv02: 2;
cv03: 3;
cv04: 4;
cv05: 5;
cv06: 6;
cv07: 7;
cv08: 8;
cv09: 9;
cv10: 10;
cv11: 11;
cv12: 12;
cv13: 13;
alt-1: 1; /* Alternate one */
alt-3: 9; /* Flat-top three */
open-4: 2; /* Open four */
open-6: 3; /* Open six */
open-9: 4; /* Open nine */
lc-l-with-tail: 5; /* Lower-case L with tail */
simplified-u: 6; /* Simplified u */
alt-double-s: 7; /* Alternate German double s */
uc-i-with-serif: 8; /* Upper-case i with serif */
uc-g-with-spur: 10; /* Capital G with spur */
single-story-a: 11; /* Single-story a */
compact-lc-f: 12; /* Compact f */
compact-lc-t: 13; /* Compact t */
}
@styleset {
ss01: 1;
ss02: 2;
ss03: 3;
ss04: 4;
ss05: 5;
ss06: 6;
ss07: 7;
ss08: 8;
open-digits: 1; /* Open digits */
disambiguation: 2; /* Disambiguation (with zero) */
disambiguation-except-zero: 4; /* Disambiguation (no zero) */
round-quotes-and-commas: 3; /* Round quotes &amp; commas */
square-punctuation: 7; /* Square punctuation */
square-quotes: 8; /* Square quotes */
circled-characters: 5; /* Circled characters */
squared-characters: 6; /* Squared characters */
}
}

BIN
packages/solid/src/fonts/InterFont/InterDisplay-Black.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/InterDisplay-Bold.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/InterDisplay-Italic.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/InterDisplay-Light.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/InterDisplay-Medium.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/InterDisplay-Thin.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
packages/solid/src/fonts/InterFont/InterVariable.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans VF.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-Bold.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-Demibold.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-ExtraLight.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-Heavy.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-Light.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-Medium.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-Normal.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-Regular.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-Semibold.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/solid/src/fonts/MiSans/MiSans-Thin.woff2 (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,87 @@
@font-face {
font-family: "MiSans VF";
font-style: normal;
font-weight: 150 700;
font-display: fallback;
src: url("MiSans VF.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 100;
font-display: fallback;
src: url("MiSans-Thin.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 200;
font-display: fallback;
src: url("MiSans-ExtraLight.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url("MiSans-Light.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 360;
font-display: fallback;
src: url("MiSans-Normal.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url("MiSans-Regular.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 500;
font-display: fallback;
src: url("MiSans-Medium.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url("MiSans-Demibold.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 700;
font-display: fallback;
src: url("MiSans-Semibold.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 800;
font-display: fallback;
src: url("MiSans-Bold.woff2") format("woff2");
}
@font-face {
font-family: "MiSans";
font-style: normal;
font-weight: 900;
font-display: fallback;
src: url("MiSans-Heavy.woff2") format("woff2");
}

1
packages/solid/src/global.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="@solidjs/start/env" />

View File

@ -0,0 +1,25 @@
const N_1024 = BigInt(
"129023318876534346704360951712586568674758913224876821534686030409476129469193481910786173836188085930974906857867802234113909470848523288588793477904039083513378341278558405407018889387577114155572311708428733260891448259786041525189132461448841652472631435226032063278124857443496954605482776113964107326943"
);
const N_2048 = BigInt(
"23987552118069940970878653610463005981599204778388399885550631951871084945075866571231062435627294546200946516668493107358732376187241747090707087544153108117326163500579370560400058549184722138636116585329496684877258304519458316233517215780035360354808658620079068489084797380781488445517430961701007542207001544091884001098497324624368085682074645221148086075871342544591022944384890014176612259729018968864426602901247715051556212559854689574013699665035317257438297910516976812428036717668766321871780963854649899276251822244719887233041422346429752896925499321431273560130952088238625622570366815755926694833109"
);
const N_1792 = BigInt(
"23987552118069940970878653610463005981599204778388399885550631951871084945075866571231062435627294546200946516668493107358732376187241747090707087544153108117326163500579370560400058549184722138636116585329496684877258304519458316233517215780035360354808658620079068489084797380781488445517430961701007542207001544091884001098497324624368085682074645221148086075871342544591022944384890014176612259729018968864426602901247715051556212559854689574013699665035317257438297910516976812428036717668766321871780963854649899276251822244719887233041422346429752896925499321431273560130952088238625622570366815755926694833109"
);
const N_1536 = BigInt(
"1694330250214463438908848400950857073137355630337290254958754184668036770489801447652464038218330711288158361242955860326168191830448553710492926795708495297280933502917598985378231124113971732841791156356676046934277122699383776036675381503510992810963611269045078440132744168908318454891211962146563551929591147663448816841024591820348784855441153716551049843185172472891407933214238000452095646085222944171689449292644270516031799660928056315886939284985905227"
);
const N_3072 = BigInt(
"4432919939296042464443862503456460073874727648022810391370558006281079088795179408238989283371442564716849343712703672836423961818025813387453469700639513190304802553045342607888612037304066433501317127429264242784608682213025490491212489901736408833027611579294436675682774458141490718959615677971745638214649336218217578937534746160749039668886450447773018369168258067682196337978245372237157696236362344796867228581553446331915147012787367438751646936429739232247148712001806846526947508445039707404287951727838234648917450736371192435665040644040487427986702098273581288935278964444790007953559851323281510927332862225214878776790605026472021669614552481167977412450477230442015077669503312683966631454347169703030544483487968842349634064181183599641180349414682042575010303056241481622837185325228233789954078775053744988023738762706404546546146837242590884760044438874357295029411988267287001033032827035809135092270843"
);
const N_4096 = BigInt(
"703671044356805218391078271512201582198770553281951369783674142891088501340774249238173262580562112786670043634665390581120113644316651934154746357220932310140476300088580654571796404198410555061275065442553506658401183560336140989074165998202690496991174269748740565700402715364422506782445179963440819952745241176450402011121226863984008975377353558155910994380700267903933205531681076494639818328879475919332604951949178075254600102192323286738973253864238076198710173840170988339024438220034106150475640983877458155141500313471699516670799821379238743709125064098477109094533426340852518505385314780319279862586851512004686798362431227795743253799490998475141728082088984359237540124375439664236138519644100625154580910233437864328111620708697941949936338367445851449766581651338876219676721272448769082914348242483068204896479076062102236087066428603930888978596966798402915747531679758905013008059396214343112694563043918465373870648649652122703709658068801764236979191262744515840224548957285182453209028157886219424802426566456408109642062498413592155064289314088837031184200671561102160059065729282902863248815224399131391716503171191977463328439766546574118092303414702384104112719959325482439604572518549918705623086363111"
);
export const N_ARRAY = [N_1024, N_1536, N_1792, N_2048, N_3072, N_4096];

View File

@ -0,0 +1,8 @@
import { sql } from "@cvsa/core";
export async function aidExists(aid: number) {
const res = await sql`
SELECT 1 FROM bilibili_metadata WHERE aid = ${aid}
`;
return res.length > 0;
}

View File

@ -0,0 +1,15 @@
import { sql } from "@cvsa/core";
export async function getAidFromBV(bv: string) {
const res = await sql`
SELECT aid FROM bilibili_metadata WHERE bvid = ${bv}
`;
if (res.length <= 0) {
return null;
}
const row = res[0];
if (row && row.aid) {
return Number(row.aid);
}
return null;
}

View File

@ -0,0 +1,15 @@
import { BiliVideoMetadataType, sql } from "@cvsa/core";
export async function getVideoMetadata(aid: number) {
const res = await sql<BiliVideoMetadataType[]>`
SELECT * FROM bilibili_metadata WHERE aid = ${aid}
`;
if (res.length <= 0) {
return null;
}
const row = res[0];
if (row) {
return row;
}
return null;
}

View File

@ -0,0 +1,11 @@
import { VideoSnapshotType, sql } from "@cvsa/core";
export async function getAllSnapshots(aid: number) {
const res = await sql<VideoSnapshotType[]>`
SELECT * FROM video_snapshot WHERE aid = ${aid} ORDER BY created_at DESC
`;
if (res.length <= 0) {
return null;
}
return res;
}

View File

@ -0,0 +1,56 @@
import { UserType, sqlCred } from "@cvsa/core";
import { UserProfile } from "../userAuth";
export const getUserBySession = async (sessionID: string) => {
const users = await sqlCred<UserType[]>`
SELECT user_id as id, username, nickname, "role", user_created_at as created_at
FROM get_user_by_session_func(${sessionID});
`;
if (users.length === 0) {
return undefined;
}
const user = users[0];
return {
uid: user.id,
username: user.username,
nickname: user.nickname,
role: user.role,
createdAt: user.created_at
};
};
export const queryUserProfile = async (uid: number, sessionID?: string): Promise<UserProfile | null> => {
interface Result extends UserType {
logged_in: boolean;
}
const users = await sqlCred<Result[]>`
SELECT
u.id, u.username, u.nickname, u."role", u.created_at,
CASE
WHEN (ls.uid IS NOT NULL AND ls.deactivated_at IS NULL AND ls.expire_at > NOW()) THEN TRUE
ELSE FALSE
END AS logged_in
FROM
users u
LEFT JOIN
login_sessions ls ON ls.uid = u.id AND ls.id = ${sessionID || ""}
WHERE
u.id = ${uid};
`;
if (users.length === 0) {
return null;
}
const user = users[0];
return {
uid: user.id,
username: user.username,
nickname: user.nickname,
role: user.role,
createdAt: user.created_at,
isLoggedIn: user.logged_in
};
};

View File

@ -0,0 +1,70 @@
import axios, { AxiosRequestConfig, AxiosError, Method, AxiosResponse } from "axios";
export class ApiRequestError extends Error {
public code: number | undefined;
public response: unknown | undefined;
constructor(message: string, res?: unknown, code?: number) {
super(message);
this.name = "ApiRequestError";
this.code = code;
this.response = res;
}
}
type HttpMethod = Extract<Method, "GET" | "POST" | "PUT" | "DELETE" | "PATCH">;
const httpMethods = {
get: axios.get,
post: axios.post,
put: axios.put,
delete: axios.delete,
patch: axios.patch
};
export function fetcher(url: string): Promise<unknown>;
export function fetcher<JSON = unknown>(
url: string,
init?: Omit<AxiosRequestConfig, "method"> & { method?: Exclude<HttpMethod, "DELETE"> }
): Promise<JSON>;
export function fetcher(
url: string,
init: Omit<AxiosRequestConfig, "method"> & { method: "DELETE" }
): Promise<AxiosResponse>;
export async function fetcher<JSON = unknown>(
url: string,
init?: Omit<AxiosRequestConfig, "method"> & { method?: HttpMethod }
): Promise<JSON | AxiosResponse<any, any>> {
const { method = "get", data, ...config } = init || {};
const fullConfig: AxiosRequestConfig = {
method,
...config,
timeout: 10000
};
try {
const m = method.toLowerCase() as keyof typeof httpMethods;
if (["post", "patch", "put"].includes(m)) {
const response = await httpMethods[m](url, data, fullConfig);
return response.data;
} else if (m === "delete") {
const response = await axios.delete(url, fullConfig);
return response;
} else {
const response = await httpMethods[m](url, fullConfig);
return response.data;
}
} catch (error) {
const axiosError = error as AxiosError;
if (axiosError.response) {
const { status, data } = axiosError.response;
throw new ApiRequestError(`HTTP error! status: ${status}`, data, status);
} else if (axiosError.request) {
throw new ApiRequestError("No response received", undefined, -1);
} else {
throw new ApiRequestError(axiosError.message || "Unknown error");
}
}
}

View File

@ -0,0 +1,43 @@
import { cookies } from "next/headers";
import { getUserBySession, queryUserProfile } from "@/lib/db/user";
export interface User {
uid: number;
username: string;
nickname: string | null;
role: string;
createdAt: Date;
}
export interface UserProfile extends User {
isLoggedIn: boolean;
}
export async function getCurrentUser(): Promise<User | null> {
const cookieStore = await cookies();
const sessionID = cookieStore.get("session_id");
if (!sessionID) return null;
try {
const user = await getUserBySession(sessionID.value);
return user ?? null;
} catch (error) {
console.log(error);
return null;
}
}
export async function getUserProfile(uid: number): Promise<UserProfile | null> {
const cookieStore = await cookies();
const sessionID = cookieStore.get("session_id");
try {
const user = await queryUserProfile(uid, sessionID?.value);
return user ?? null;
} catch (error) {
console.log(error);
return null;
}
}

View File

@ -0,0 +1,117 @@
// Define interfaces for input and output
interface VdfProgressCallback {
(progress: number): void;
}
interface VdfResult {
result: bigint;
time: number; // Time taken in milliseconds
}
// The content of the Web Worker script
const workerContent = `addEventListener("message", async (event) => {
const { g, N, difficulty } = event.data;
// Although pow is not used in the iterative VDF, it's good to keep the original worker code structure.
// The iterative computeVDFWithProgress is better for progress reporting.
function pow(base, exponent, mod) {
let result = 1n;
base = base % mod;
while (exponent > 0n) {
if (exponent % 2n === 1n) {
result = (result * base) % mod;
}
base = (base * base) % mod;
exponent = exponent / 2n;
// Using BigInt division (/) which performs integer division
}
return result;
}
// Compute VDF iteratively to report progress
function computeVDFWithProgress(g, N, T, postProgress) {
let result = g;
let latestTime = performance.now();
const totalSteps = T; // T is the difficulty, representing 2^T squaring steps
for (let i = 0n; i < totalSteps; i++) {
result = (result * result) % N;
// Report progress periodically (approx. every 16ms to match typical frame rate)
if (performance.now() - latestTime > 16) {
// Calculate progress as a percentage
const progress = Number((i + 1n) * 10000n / totalSteps) / 100; // Using 10000 for better precision before dividing by 100
postProgress(progress);
latestTime = performance.now();
}
}
// Ensure final progress is reported
postProgress(100);
return result;
}
const startTime = performance.now();
// The worker computes g^(2^difficulty) mod N. The loop runs 'difficulty' times, performing squaring.
const result = computeVDFWithProgress(g, N, difficulty, (progress) => {
// Post progress back to the main thread
postMessage({ type: "progress", progress: progress });
});
const endTime = performance.now();
const timeTaken = endTime - startTime;
// Post the final result and time taken back to the main thread
postMessage({ type: "result", result: result.toString(), time: timeTaken });
});
`;
/**
* Computes the Verifiable Delay Function (VDF) result g^(2^difficulty) mod N
* in a Web Worker and reports progress.
* @param g - The base (bigint).
* @param N - The modulus (bigint).
* @param difficulty - The number of squaring steps (T) (bigint).
* @param onProgress - Optional callback function to receive progress updates (0-100).
* @returns A Promise that resolves with the VDF result and time taken.
*/
export function computeVdfInWorker(
g: bigint,
N: bigint,
difficulty: bigint,
onProgress?: VdfProgressCallback
): Promise<VdfResult> {
return new Promise((resolve, reject) => {
// Create a Blob containing the worker script
const blob = new Blob([workerContent], { type: "text/javascript" });
// Create a URL for the Blob
const workerUrl = URL.createObjectURL(blob);
// Create a new Web Worker
const worker = new window.Worker(workerUrl);
// Handle messages from the worker
worker.onmessage = (event) => {
const { type, progress, result, time } = event.data;
if (type === "progress") {
if (onProgress) {
onProgress(progress);
}
} else if (type === "result") {
// Resolve the promise with the result and time
resolve({ result: BigInt(result), time });
// Terminate the worker and revoke the URL
worker.terminate();
URL.revokeObjectURL(workerUrl);
}
};
// Handle potential errors in the worker
worker.onerror = (error) => {
reject(error);
// Terminate the worker and revoke the URL in case of error
worker.terminate();
URL.revokeObjectURL(workerUrl);
};
// Post the data to the worker to start the computation
worker.postMessage({ g, N, difficulty });
});
}

View File

@ -0,0 +1,19 @@
import { Title } from "@solidjs/meta";
import { HttpStatusCode } from "@solidjs/start";
export default function NotFound() {
return (
<main>
<Title>Not Found</Title>
<HttpStatusCode code={404} />
<h1>Page Not Found</h1>
<p>
Visit{" "}
<a href="https://start.solidjs.com" target="_blank">
start.solidjs.com
</a>{" "}
to learn how to build SolidStart apps.
</p>
</main>
);
}

View File

@ -0,0 +1,19 @@
import { Title } from "@solidjs/meta";
import Counter from "~/components/Counter";
export default function Home() {
return (
<main>
<Title>Hello World</Title>
<h1>Hello world!</h1>
<Counter />
<p>
Visit{" "}
<a href="https://start.solidjs.com" target="_blank">
start.solidjs.com
</a>{" "}
to learn how to build SolidStart apps.
</p>
</main>
);
}

View File

@ -0,0 +1,5 @@
import { RouteSectionProps } from "@solidjs/router";
export default function SongLayout(props: RouteSectionProps) {
return <div>{props.children}</div>;
}

View File

@ -0,0 +1,188 @@
import { getAllSnapshots } from "~/lib/db/snapshots/getAllSnapshots";
import { getAidFromBV } from "~/lib/db/bilibili_metadata/getAidFromBV";
import { getVideoMetadata } from "~/lib/db/bilibili_metadata/getVideoMetadata";
import { aidExists as idExists } from "~/lib/db/bilibili_metadata/aidExists";
import { BiliVideoMetadataType, VideoSnapshotType } from "@cvsa/core";
import { DateTime } from "luxon";
import { useParams } from "@solidjs/router";
import { createResource } from "solid-js";
import { Title } from "@solidjs/meta";
import { Suspense } from "solid-js";
import { For } from "solid-js";
const MetadataRow = ({ title, desc }: { title: string; desc: string | number | undefined | null }) => {
if (!desc) return <></>;
return (
<tr>
<td class="max-w-14 min-w-14 md:max-w-24 md:min-w-24 border dark:border-zinc-500 px-2 md:px-3 py-2 font-semibold">
{title}
</td>
<td class="break-all max-w-[calc(100vw-4.5rem)] border dark:border-zinc-500 px-4 py-2">{desc}</td>
</tr>
);
};
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export default function VideoInfoPage() {
const params = useParams();
const { id } = params;
const [data] = createResource(async () => {
let videoInfo: BiliVideoMetadataType | null = null;
let snapshots: VideoSnapshotType[] = [];
await sleep(1000);
async function getVideoAid(videoId: string | string[] | undefined) {
if (!videoId) return null;
const videoIdStr = Array.isArray(videoId) ? videoId[0] : videoId;
if (videoIdStr?.startsWith("av")) {
return parseInt(videoIdStr.slice(2));
} else if (videoIdStr?.startsWith("BV")) {
return getAidFromBV(videoIdStr);
}
return parseInt(videoIdStr);
}
const aid = await getVideoAid(id);
if (!aid) {
return null;
}
const exists = await idExists(aid);
if (!exists) {
return null;
}
try {
const videoData = await getVideoMetadata(aid);
const snapshotsData = await getAllSnapshots(aid);
videoInfo = videoData;
if (snapshotsData) {
snapshots = snapshotsData;
}
} catch (e) {
console.error(e);
}
if (!videoInfo) {
return null;
}
let title = "";
const backendURL = process.env.BACKEND_URL;
const res = await fetch(`${backendURL}/video/${id}/info`);
if (!res.ok) {
title = "页面未找到 - 中 V 档案馆";
}
const data = await res.json();
title = `${data.title} - 歌曲信息 - 中 V 档案馆`;
return {
v: videoInfo,
s: snapshots,
t: title
};
});
return (
<main class="flex flex-col items-center min-h-screen gap-8 mt-10 md:mt-6 relative z-0 overflow-x-auto pb-8">
<div>hi</div>
<div class="w-full lg:max-w-4xl lg:mx-auto lg:p-6">
<Suspense fallback={<div>loading</div>}>
<Title>{data()?.t}</Title>
<h1 class="text-2xl font-medium ml-2 mb-4">
:{" "}
<a href={`https://www.bilibili.com/video/av${data()?.v.aid}`} class="underline">
av{data()?.v.aid}
</a>
</h1>
<div class="mb-6">
<h2 class="px-2 mb-2 text-xl font-medium"></h2>
<div class="overflow-x-auto max-w-full px-2">
<table class="table-fixed">
<tbody>
<MetadataRow title="ID" desc={data()?.v.id} />
<MetadataRow title="av 号" desc={data()?.v.aid} />
<MetadataRow title="BV 号" desc={data()?.v.bvid} />
<MetadataRow title="标题" desc={data()?.v.title} />
<MetadataRow title="描述" desc={data()?.v.description} />
<MetadataRow title="UID" desc={data()?.v.uid} />
<MetadataRow title="标签" desc={data()?.v.tags} />
<MetadataRow
title="发布时间"
desc={
data()?.v.published_at
? DateTime.fromJSDate(new Date(data()?.v.published_at || "")).toFormat(
"yyyy-MM-dd HH:mm:ss"
)
: null
}
/>
<MetadataRow title="时长 (秒)" desc={data()?.v.duration} />
<MetadataRow
title="创建时间"
desc={DateTime.fromJSDate(new Date(data()?.v.created_at || "")).toFormat(
"yyyy-MM-dd HH:mm:ss"
)}
/>
<MetadataRow title="封面" desc={data()?.v?.cover_url} />
</tbody>
</table>
</div>
</div>
<div>
<h2 class="px-2 mb-2 text-xl font-medium"></h2>
<div class="overflow-x-auto px-2">
<table class="table-auto w-full">
<thead>
<tr>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
<th class="border dark:border-zinc-500 px-4 py-2 font-medium"></th>
</tr>
</thead>
<tbody>
<For each={data()?.s}>
{(snapshot) => (
<tr>
<td class="border dark:border-zinc-500 px-4 py-2">
{DateTime.fromJSDate(snapshot.created_at).toFormat(
"yyyy-MM-dd HH:mm:ss"
)}
</td>
<td class="border dark:border-zinc-500 px-4 py-2">{snapshot.views}</td>
<td class="border dark:border-zinc-500 px-4 py-2">{snapshot.coins}</td>
<td class="border dark:border-zinc-500 px-4 py-2">{snapshot.likes}</td>
<td class="border dark:border-zinc-500 px-4 py-2">
{snapshot.favorites}
</td>
<td class="border dark:border-zinc-500 px-4 py-2">{snapshot.shares}</td>
<td class="border dark:border-zinc-500 px-4 py-2">
{snapshot.danmakus}
</td>
<td class="border dark:border-zinc-500 px-4 py-2">
{snapshot.replies}
</td>
</tr>
)}
</For>
</tbody>
</table>
</div>
</div>
</Suspense>
</div>
</main>
);
}

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"allowJs": true,
"strict": true,
"noEmit": true,
"types": ["vinxi/types/client"],
"isolatedModules": true,
"paths": {
"~/*": ["./src/*"]
}
}
}