1
0

add: new layout of the song info page, switched global font to HarmonyOS Sans.

This commit is contained in:
alikia2x (寒寒) 2025-08-03 02:01:08 +08:00
parent 87db7cfd12
commit 13e58a8539
64 changed files with 483 additions and 925 deletions

View File

@ -32,6 +32,10 @@
<excludeFolder url="file://$MODULE_DIR$/src" />
<excludeFolder url="file://$MODULE_DIR$/packages/crawler/.cache" />
<excludeFolder url="file://$MODULE_DIR$/packages/solid/.vinxi" />
<excludeFolder url="file://$MODULE_DIR$/.jj" />
<excludeFolder url="file://$MODULE_DIR$/.jj/repo" />
<excludeFolder url="file://$MODULE_DIR$/.jj/working_copy" />
<excludeFolder url="file://$MODULE_DIR$/packages/solid/.output" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />

6
.idea/prettier.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PrettierConfiguration">
<option name="myConfigurationMode" value="AUTOMATIC" />
</component>
</project>

View File

@ -4,7 +4,7 @@
"": {
"name": "example-basic",
"dependencies": {
"@m3-components/solid": "0.1.9",
"@m3-components/solid": "0.1.10",
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.15.3",
"@solidjs/start": "^1.1.7",
@ -182,7 +182,7 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
"@m3-components/solid": ["@m3-components/solid@0.1.9", "", { "dependencies": { "animejs": "^4.0.2", "solid-js": "^1.9.5", "tailwind-variants": "^1.0.0" } }, "sha512-gM+qx6C1FEd+dzEeLcpJspKx9kg2L2JyB/BCU8t0ivM7tTHXYVkR5ZHp+gMx/PdWu/wXLGEF6EOreUtnvUs2xg=="],
"@m3-components/solid": ["@m3-components/solid@0.1.10", "", { "dependencies": { "animejs": "^4.0.2", "solid-js": "^1.9.5", "tailwind-variants": "^1.0.0" } }, "sha512-dTD8Q++kIXEyKKFCOvdS5DylbUL94T0A+1kMPVMDuAKAavnWVyRkcwV6yh5Ih2fTXGyWLhnSi1jeM0eN1FADZg=="],
"@mapbox/node-pre-gyp": ["@mapbox/node-pre-gyp@2.0.0", "", { "dependencies": { "consola": "^3.2.3", "detect-libc": "^2.0.0", "https-proxy-agent": "^7.0.5", "node-fetch": "^2.6.7", "nopt": "^8.0.0", "semver": "^7.5.3", "tar": "^7.4.0" }, "bin": { "node-pre-gyp": "bin/node-pre-gyp" } }, "sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg=="],

View File

@ -8,7 +8,7 @@
"version": "vinxi version"
},
"dependencies": {
"@m3-components/solid": "0.1.9",
"@m3-components/solid": "0.1.10",
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.15.3",
"@solidjs/start": "^1.1.7",

View File

@ -1,5 +1,4 @@
@import url("./fonts/InterFont/Inter.css");
@import url("./fonts/MiSans/MiSans.css");
@import url("https://assets.projectcvsa.com/hm-sans/index.css");
@import "tailwindcss";
@ -132,8 +131,7 @@ a:not(.clear) {
}
:root {
font-family: "Inter", "MiSans", sans-serif;
font-weight: 400;
font-family: "HarmonyOS Sans SC", sans-serif;
@apply bg-surface text-on-surface;
}
@ -142,11 +140,3 @@ a:not(.clear) {
-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

@ -4,17 +4,9 @@ import { FileRoutes } from "@solidjs/start/router";
import { onMount, Suspense } from "solid-js";
import "./app.css";
import "@m3-components/solid/index.css";
import { setActiveTab } from "./components/shell/Navigation";
import { setActiveTab, tabMap } from "./components/shell/Navigation";
import { minimatch } from "minimatch";
const tabMap = {
"/": 0,
"/song*": 1,
"/song/**/*": 1,
"/albums": 2,
"/album/**/*": 2
};
export const refreshTab = (path: string) => {
for (const [key, value] of Object.entries(tabMap)) {
if (!minimatch(path, key)) continue;

View File

@ -1,5 +1,5 @@
import { tv } from "tailwind-variants";
import { navigationExpanded, NavigationRegion } from "./Navigation";
import { navigationExpanded, NavigationMobile, NavigationRegion } from "./Navigation";
import { DivProps } from "../common";
import { Component } from "solid-js";
import { BeforeLeaveEventArgs, useBeforeLeave } from "@solidjs/router";
@ -7,11 +7,11 @@ import { refreshTab } from "~/app";
export const BodyRegion: Component<DivProps> = (props) => {
const bodyStyle = tv({
base: "relative px-6 pt-20",
base: "relative",
variants: {
open: {
true: "left-55 pr-55",
false: "left-24 pr-24"
true: "px-5 md:left-55 md:pr-55",
false: "px-5 md:left-24 md:pr-24"
}
}
});
@ -22,7 +22,11 @@ export const BodyRegion: Component<DivProps> = (props) => {
);
};
export const Layout: Component<DivProps> = (props) => {
interface LayoutProps extends DivProps {
lang?: "zh" | "en";
}
export const Layout: Component<LayoutProps> = (props) => {
useBeforeLeave((e: BeforeLeaveEventArgs) => {
if (typeof e.to === "number") {
refreshTab(e.to.toString());
@ -32,7 +36,8 @@ export const Layout: Component<DivProps> = (props) => {
});
return (
<div class="relatve w-screen min-h-screen">
<NavigationRegion />
<NavigationRegion lang={props.lang} />
<NavigationMobile lang={props.lang} />
<BodyRegion>
{props.children}
</BodyRegion>

View File

@ -1,4 +1,4 @@
import { createSignal, For } from "solid-js";
import { Component, createSignal, For } from "solid-js";
import { HomeIcon } from "../icons/Home";
import { MusicIcon } from "../icons/Music";
import {
@ -6,41 +6,148 @@ import {
NavigationRail,
NavigationRailAction,
NavigationRailActions,
NavigationRailMenu
NavigationRailMenu,
AppBar,
AppBarLeadingElement,
AppBarSearchBox,
AppBarTrailingElementGroup,
AppBarTrailingElement,
IconButton
} from "@m3-components/solid";
import { A } from "@solidjs/router";
import { AlbumIcon } from "~/components/icons/Album";
import { SearchIcon } from "../icons/Search";
import { Portal } from "solid-js/web";
export const [activeTab, setActiveTab] = createSignal(-1);
export const [navigationExpanded, setNavigationExpanded] = createSignal(false);
export const actions = [
interface Action {
icon: Component;
label: string;
href: string;
}
export const actions: Action[] = [
{
icon: <HomeIcon />,
icon: HomeIcon,
label: "主页",
href: "/"
},
{
icon: <MusicIcon />,
icon: MusicIcon,
label: "歌曲",
href: "/songs"
},
{
icon: <AlbumIcon />,
icon: AlbumIcon,
label: "专辑",
href: "/albums"
}
];
export const NavigationRegion = () => {
export const actionsEn: Action[] = [
{
icon: HomeIcon,
label: "Home",
href: "/en/"
},
{
icon: MusicIcon,
label: "Songs",
href: "/en/songs"
},
{
icon: AlbumIcon,
label: "Albums",
href: "/en/albums"
}
];
export const tabMap = {
"/": 0,
"/song*": 1,
"/song/**/*": 1,
"/albums": 2,
"/album/**/*": 2,
"/en/": 0,
"/en/songs": 1,
"/en/song*": 1,
"/en/song/**/*": 1,
"/en/albums": 2,
"/en/album/**/*": 2
};
const searchT = {
zh: "搜索",
en: "Search"
};
export const NavigationMobile: Component<{ lang?: "zh" | "en" }> = (props) => {
let el: HTMLDivElement | undefined;
return (
<NavigationRail class="top-0 bg-surface-container" width={220} expanded={navigationExpanded()}>
<NavigationRailMenu onClick={() => setNavigationExpanded(!navigationExpanded())} />
<NavigationRailFAB text="搜索" color="primary">
<>
<NavigationRailMenu
class="top-0 left-0 flex fixed z-100 md:hidden"
onClick={() => setNavigationExpanded(!navigationExpanded())}
/>
<AppBar class="md:hidden" variant="search">
<AppBarLeadingElement>
<NavigationRailMenu
class="flex fixed z-100 md:hidden"
onClick={() => setNavigationExpanded(!navigationExpanded())}
/>
</AppBarLeadingElement>
<AppBarSearchBox placeholder="搜索" />
<AppBarTrailingElementGroup>
<AppBarTrailingElement>
<IconButton></IconButton>
</AppBarTrailingElement>
</AppBarTrailingElementGroup>
</AppBar>
<Portal mount={document.getElementById("modal") || undefined}>
<div class="fixed md:hidden top-0 left-0 h-full z-50">
<NavigationRail
class="md:hidden top-0 bg-surface-container rounded-r-2xl shadow-shadow shadow-2xl"
width={220}
expanded={true}
>
<NavigationRailMenu class="opacity-0 pointer-events-none" />
<NavigationRailFAB text={searchT[props.lang || "zh"]} color="primary">
<SearchIcon />
</NavigationRailFAB>
<NavigationRailActions>
<For each={props.lang == "en" ? actionsEn : actions}>
{(action, index) => (
<A href={action.href} class="clear">
<NavigationRailAction
activated={activeTab() == index()}
label={action.label}
icon={action.icon}
onClick={() => {
setActiveTab(index);
}}
/>
</A>
)}
</For>
</NavigationRailActions>
</NavigationRail>
</div>
</Portal>
</>
);
};
export const NavigationRegion: Component<{ lang?: "zh" | "en" }> = (props) => {
return (
<NavigationRail class="hidden md:flex top-0 bg-surface-container" width={220} expanded={navigationExpanded()}>
<NavigationRailMenu class="md:flex left-7" onClick={() => setNavigationExpanded(!navigationExpanded())} />
<NavigationRailFAB text={searchT[props.lang || "zh"]} color="primary">
<SearchIcon />
</NavigationRailFAB>
<NavigationRailActions>
<For each={actions}>
<For each={props.lang == "en" ? actionsEn : actions}>
{(action, index) => (
<A href={action.href} class="clear">
<NavigationRailAction

View File

@ -16,6 +16,7 @@ export default createHandler(() => (
</head>
<body>
<div id="app" style="overflow-x: hidden">{children}</div>
<div id="modal"></div>
{scripts}
</body>
</html>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,449 +0,0 @@
@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 */
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,87 +0,0 @@
@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");
}

View File

@ -0,0 +1,75 @@
import { Layout } from "~/components/shell/Layout";
import { query } from "@solidjs/router";
import { Card, CardContent, CardMedia, Typography } from "@m3-components/solid";
export default function Info() {
return (
<Layout lang="en">
<title></title>
<main class="w-full pt-14 lg:max-w-lg xl:max-w-xl lg:mx-auto">
<Card variant="outlined">
<CardMedia
round={false}
src="https://i0.hdslb.com/bfs/archive/8ad220336f96e4d2ea05baada3bc04592d56b2a5.jpg"
referrerpolicy="no-referrer"
/>
<CardContent>
<div class="flex flex-col">
<Typography.Headline variant="small"></Typography.Headline>
<Typography.Body class="font-medium text-on-surface-variant" variant="large">
Chen Hai Hui Xian Yuan
</Typography.Body>
</div>
<div class="mt-4 grid grid-cols-2 grid-rows-3 gap-2 ">
<div class="flex flex-col">
<Typography.Body class="font-semibold" variant="small">
PUBLISHER
</Typography.Body>
<Typography.Body variant="large">
</Typography.Body>
</div>
<div class="flex flex-col">
<Typography.Body class="font-semibold" variant="small">
DURATION
</Typography.Body>
<Typography.Body variant="large">4:28</Typography.Body>
</div>
<div class="flex flex-col">
<Typography.Body class="font-semibold" variant="small">
SINGER
</Typography.Body>
<Typography.Body variant="large">
<a href="#"></a> <span class="text-on-surface-variant">(Chiyu)</span>
</Typography.Body>
</div>
<div class="flex flex-col">
<Typography.Body class="font-semibold" variant="small">
PUBLISH TIME
</Typography.Body>
<Typography.Body variant="large">2024-12-15 12:15:00</Typography.Body>
</div>
<div class="flex flex-col">
<Typography.Body class="font-semibold" variant="small">
VIEWS
</Typography.Body>
<Typography.Body variant="large">12.4K (12,422)</Typography.Body>
</div>
<div class="flex flex-col">
<Typography.Body class="font-semibold" variant="small">
LINKS
</Typography.Body>
<Typography.Body class="flex gap-2" variant="large">
<a href="https://www.bilibili.com/video/BV1eaq9Y3EVV/">bilibili</a>
<a href="https://vocadb.net/S/742394">VocaDB</a>
</Typography.Body>
</div>
</div>
</CardContent>
</Card>
</main>
</Layout>
);
}

View File

@ -48,7 +48,7 @@ export default function Home() {
return (
<Layout>
<title>V档案馆</title>
<main class="w-full lg:max-w-3xl xl:max-w-4xl 2xl:max-w-6xl lg:mx-auto">
<main class="w-full pt-20 lg:max-w-3xl xl:max-w-4xl 2xl:max-w-6xl lg:mx-auto">
<h1 class="text-4xl mb-8"> V </h1>
<h2 class="text-2xl font-normal"></h2>
<div

View File

@ -0,0 +1,209 @@
import { DateTime } from "luxon";
import { useParams } from "@solidjs/router";
import { createResource } from "solid-js";
import { Suspense } from "solid-js";
import { For } from "solid-js";
import { useCachedFetch } from "~/lib/dbCache";
import { dbMain } from "~/drizzle";
import { bilibiliMetadata, videoSnapshot } from "~db/main/schema";
import { desc, eq } from "drizzle-orm";
import { BilibiliMetadataType, VideoSnapshotType } from "~db/outerSchema";
import { Context, useRequestContext } from "~/components/requestContext";
import { Layout } from "~/components/shell/Layout";
async function getAllSnapshots(aid: number, context: Context) {
"use server";
return useCachedFetch(
async () => {
return dbMain
.select()
.from(videoSnapshot)
.where(eq(videoSnapshot.aid, aid))
.orderBy(desc(videoSnapshot.createdAt));
},
"all-snapshots",
context,
[aid]
);
}
async function getVideoMetadata(avORbv: number | string, context: Context) {
"use server";
if (typeof avORbv === "number") {
return useCachedFetch(
async () => {
return dbMain.select().from(bilibiliMetadata).where(eq(bilibiliMetadata.aid, avORbv)).limit(1);
},
"bili-metadata",
context,
[avORbv]
);
} else {
return useCachedFetch(
async () => {
return dbMain.select().from(bilibiliMetadata).where(eq(bilibiliMetadata.bvid, avORbv)).limit(1);
},
"bili-metadata",
context,
[avORbv]
);
}
}
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>
);
};
export default function VideoInfoPage() {
const params = useParams();
const { id } = params;
const context = useRequestContext();
const [data] = createResource(async () => {
let videoInfo: BilibiliMetadataType | null = null;
let snapshots: VideoSnapshotType[] = [];
try {
const videoData = await getVideoMetadata(id, context);
if (videoData.length === 0) {
return null;
}
const snapshotsData = await getAllSnapshots(videoData[0].aid, context);
videoInfo = videoData[0];
if (snapshotsData) {
snapshots = snapshotsData;
}
} catch (e) {
console.error(e);
}
if (!videoInfo) {
return null;
}
const title = `${videoInfo.title} - 歌曲信息 - 中 V 档案馆`;
return {
v: videoInfo,
s: snapshots,
t: title
};
});
return (
<Layout>
<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 class="w-full lg:max-w-4xl lg:mx-auto lg:p-6">
<Suspense fallback={<div>loading</div>}>
<title>{data()?.t}</title>
<span>{data()?.t}</span>
<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.publishedAt
? DateTime.fromJSDate(
new Date(data()?.v.publishedAt || "")
).toFormat("yyyy-MM-dd HH:mm:ss")
: null
}
/>
<MetadataRow title="时长 (秒)" desc={data()?.v.duration} />
<MetadataRow
title="创建时间"
desc={DateTime.fromJSDate(new Date(data()?.v.createdAt || "")).toFormat(
"yyyy-MM-dd HH:mm:ss"
)}
/>
<MetadataRow title="封面" desc={data()?.v?.coverUrl} />
</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(new Date(snapshot.createdAt)).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>
</Layout>
);
}

View File

@ -1,209 +1,61 @@
import { DateTime } from "luxon";
import { useParams } from "@solidjs/router";
import { createResource } from "solid-js";
import { Suspense } from "solid-js";
import { For } from "solid-js";
import { useCachedFetch } from "~/lib/dbCache";
import { dbMain } from "~/drizzle";
import { bilibiliMetadata, videoSnapshot } from "~db/main/schema";
import { desc, eq } from "drizzle-orm";
import { BilibiliMetadataType, VideoSnapshotType } from "~db/outerSchema";
import { Context, useRequestContext } from "~/components/requestContext";
import { Layout } from "~/components/shell/Layout";
import { Card, CardContent, CardMedia, Typography } from "@m3-components/solid";
async function getAllSnapshots(aid: number, context: Context) {
"use server";
return useCachedFetch(
async () => {
return dbMain
.select()
.from(videoSnapshot)
.where(eq(videoSnapshot.aid, aid))
.orderBy(desc(videoSnapshot.createdAt));
},
"all-snapshots",
context,
[aid]
);
}
async function getVideoMetadata(avORbv: number | string, context: Context) {
"use server";
if (typeof avORbv === "number") {
return useCachedFetch(
async () => {
return dbMain.select().from(bilibiliMetadata).where(eq(bilibiliMetadata.aid, avORbv)).limit(1);
},
"bili-metadata",
context,
[avORbv]
);
} else {
return useCachedFetch(
async () => {
return dbMain.select().from(bilibiliMetadata).where(eq(bilibiliMetadata.bvid, avORbv)).limit(1);
},
"bili-metadata",
context,
[avORbv]
);
}
}
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>
);
};
export default function VideoInfoPage() {
const params = useParams();
const { id } = params;
const context = useRequestContext();
const [data] = createResource(async () => {
let videoInfo: BilibiliMetadataType | null = null;
let snapshots: VideoSnapshotType[] = [];
try {
const videoData = await getVideoMetadata(id, context);
if (videoData.length === 0) {
return null;
}
const snapshotsData = await getAllSnapshots(videoData[0].aid, context);
videoInfo = videoData[0];
if (snapshotsData) {
snapshots = snapshotsData;
}
} catch (e) {
console.error(e);
}
if (!videoInfo) {
return null;
}
const title = `${videoInfo.title} - 歌曲信息 - 中 V 档案馆`;
return {
v: videoInfo,
s: snapshots,
t: title
};
});
export default function Info() {
return (
<Layout>
<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 class="w-full lg:max-w-4xl lg:mx-auto lg:p-6">
<Suspense fallback={<div>loading</div>}>
<title>{data()?.t}</title>
<span>{data()?.t}</span>
<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.publishedAt
? DateTime.fromJSDate(
new Date(data()?.v.publishedAt || "")
).toFormat("yyyy-MM-dd HH:mm:ss")
: null
}
/>
<MetadataRow title="时长 (秒)" desc={data()?.v.duration} />
<MetadataRow
title="创建时间"
desc={DateTime.fromJSDate(new Date(data()?.v.createdAt || "")).toFormat(
"yyyy-MM-dd HH:mm:ss"
)}
/>
<MetadataRow title="封面" desc={data()?.v?.coverUrl} />
</tbody>
</table>
<title></title>
<div
class="w-full md:grid md:grid-cols-[1fr_540px_minmax(200px,_1fr)] lg:grid-cols-[1fr_576px_1fr]
xl:grid-cols-[1fr_648px_1fr]"
>
<nav></nav>
<main class="mt-14 lg:mt-8 md:pl-6 lg:pl-0">
<Card variant="outlined" class="w-full">
<CardMedia
round={false}
src="https://i0.hdslb.com/bfs/archive/8ad220336f96e4d2ea05baada3bc04592d56b2a5.jpg"
referrerpolicy="no-referrer"
class="w-full"
/>
<CardContent>
<Typography.Display class="mb-3" variant="small">
</Typography.Display>
<div class="grid grid-cols-2 grid-rows-3 gap-1">
<Typography.Body variant="large">稿</Typography.Body>
<Typography.Body variant="large">4:28</Typography.Body>
<Typography.Body variant="large"></Typography.Body>
<Typography.Body variant="large">
<span class="inline-flex gap-2">
<a href="https://www.bilibili.com/video/BV1eaq9Y3EVV/"></a>
<a href="https://vocadb.net/S/742394">VocaDB</a>
</span>
</Typography.Body>
<Typography.Body variant="large">2024-12-15 12:15:00</Typography.Body>
<Typography.Body variant="large">1.24 (12,422)</Typography.Body>
</div>
</div>
</CardContent>
</Card>
<article class="mt-6">
<Typography.Headline variant="medium"></Typography.Headline>
<Typography.Body class="mt-2" variant="large">
<span class="font-medium"></span><a href="#"></a>
<span>
&VeryThinSpace;2024&VeryThinSpace;&VeryThinSpace;12&VeryThinSpace;&VeryThinSpace;15&VeryThinSpace;
</span>
稿
<a href="#"></a>&ThinSpace;<a href="#">Synthesizer V</a>&ThinSpace;
<span></span>
<span></span>, <a href="#"></a>
</Typography.Body>
</article>
</main>
<nav>
<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(new Date(snapshot.createdAt)).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>
</nav>
</div>
</Layout>
);
}