start video streaming
This commit is contained in:
parent
4b51fbc908
commit
445fde7b6b
15 changed files with 448 additions and 225 deletions
1
bun.lock
1
bun.lock
|
@ -5,6 +5,7 @@
|
|||
"name": "streamarr",
|
||||
"dependencies": {
|
||||
"@solid-primitives/context": "^0.3.0",
|
||||
"@solid-primitives/event-listener": "^2.4.0",
|
||||
"@solidjs/meta": "^0.29.4",
|
||||
"@solidjs/router": "^0.15.3",
|
||||
"@solidjs/start": "^1.1.3",
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@solid-primitives/context": "^0.3.0",
|
||||
"@solid-primitives/event-listener": "^2.4.0",
|
||||
"@solidjs/meta": "^0.29.4",
|
||||
"@solidjs/router": "^0.15.3",
|
||||
"@solidjs/start": "^1.1.3",
|
||||
|
|
20
src/api/stream/video.ts
Normal file
20
src/api/stream/video.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { json } from "@solidjs/router";
|
||||
import vid from "../../../public/videos/bbb_sunflower_2160p_60fps_normal.mp4";
|
||||
import { APIEvent } from "@solidjs/start/server";
|
||||
|
||||
export const GET = async (event: APIEvent) => {
|
||||
"use server";
|
||||
|
||||
console.log(event);
|
||||
|
||||
// async function* packetGenerator() {
|
||||
// for (let i = 0; i < 10; i++) {
|
||||
// yield `packet ${i}`;
|
||||
// await new Promise((res) => setTimeout(res, 1000));
|
||||
// }
|
||||
// }
|
||||
|
||||
// console.log(vid);
|
||||
|
||||
return "OK";
|
||||
};
|
|
@ -10,7 +10,7 @@ export const ListItem: Component<{ entry: Entry }> = (props) => {
|
|||
<main>
|
||||
<strong>{props.entry.title}</strong>
|
||||
|
||||
<a href={`/content/${props.entry.id}`}>Watch now</a>
|
||||
<a href={`/watch/${props.entry.id}`}>Watch now</a>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { Component, createEffect, createSignal, Index, onMount } from "solid-js";
|
||||
import {
|
||||
Component,
|
||||
createEffect,
|
||||
createSignal,
|
||||
Index,
|
||||
onMount,
|
||||
} from "solid-js";
|
||||
import type { Entry, Category } from "../content";
|
||||
import { ListItem } from "./list-item";
|
||||
import { List } from "~/components/list";
|
||||
|
@ -15,7 +21,11 @@ export const Overview: Component<OverviewProps> = (props) => {
|
|||
|
||||
onMount(() => {
|
||||
new MutationObserver(() => {
|
||||
container()?.querySelector(`.${css.list} > ul > div:nth-child(4) > main > a`)?.focus({ preventScroll: true });
|
||||
container()
|
||||
?.querySelector<HTMLElement>(
|
||||
`.${css.list} > ul > div:nth-child(4) > main > a`,
|
||||
)
|
||||
?.focus({ preventScroll: true });
|
||||
}).observe(document.body, { subtree: true, childList: true });
|
||||
});
|
||||
|
||||
|
@ -25,11 +35,15 @@ export const Overview: Component<OverviewProps> = (props) => {
|
|||
|
||||
<Index each={props.categories}>
|
||||
{(category) => (
|
||||
<List class={css.list} label={category().label} items={category().entries}>
|
||||
<List
|
||||
class={css.list}
|
||||
label={category().label}
|
||||
items={category().entries}
|
||||
>
|
||||
{(entry) => <ListItem entry={entry()} />}
|
||||
</List>
|
||||
)}
|
||||
</Index>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
1
src/features/player/index.ts
Normal file
1
src/features/player/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { Player } from "./player";
|
0
src/features/player/player.module.css
Normal file
0
src/features/player/player.module.css
Normal file
161
src/features/player/player.tsx
Normal file
161
src/features/player/player.tsx
Normal file
|
@ -0,0 +1,161 @@
|
|||
import {
|
||||
createEventListenerMap,
|
||||
makeEventListener,
|
||||
makeEventListenerStack,
|
||||
} from "@solid-primitives/event-listener";
|
||||
import { createAsync, json, query } from "@solidjs/router";
|
||||
import {
|
||||
Component,
|
||||
createEffect,
|
||||
createMemo,
|
||||
createResource,
|
||||
createSignal,
|
||||
onMount,
|
||||
} from "solid-js";
|
||||
import { isServer } from "solid-js/web";
|
||||
|
||||
const streamKaas = query(async () => {
|
||||
"use server";
|
||||
|
||||
const stream = new WritableStream();
|
||||
|
||||
async function* packetGenerator() {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
yield `packet ${i}`;
|
||||
await new Promise((res) => setTimeout(res, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const writer = stream.getWriter();
|
||||
|
||||
try {
|
||||
await writer.ready;
|
||||
|
||||
for await (const packet of packetGenerator()) {
|
||||
writer.write(packet);
|
||||
}
|
||||
} finally {
|
||||
writer.releaseLock();
|
||||
}
|
||||
// response.body.wr
|
||||
})();
|
||||
|
||||
return new Response(packetGenerator());
|
||||
}, "kaas");
|
||||
|
||||
interface PlayerProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const Player: Component<PlayerProps> = (props) => {
|
||||
const [video, setVideo] = createSignal<HTMLVideoElement>();
|
||||
const stream = createAsync(async () => {
|
||||
const res = await streamKaas();
|
||||
|
||||
console.log(res);
|
||||
|
||||
return "";
|
||||
});
|
||||
// const [kaas, { refetch }] = createResource(async () => {
|
||||
// if (isServer) {
|
||||
// return "";
|
||||
// }
|
||||
// const response = await fetch("http://localhost:3000/api/stream/video", {
|
||||
// method: "GET",
|
||||
// });
|
||||
|
||||
// console.log(response.body);
|
||||
|
||||
// for await (const packet of response.body) {
|
||||
// console.log(new TextDecoder().decode(packet));
|
||||
// }
|
||||
|
||||
// return "";
|
||||
// });
|
||||
|
||||
// onMount(() => refetch());
|
||||
|
||||
// createEffect(() => console.log(stream()));
|
||||
|
||||
// const progress = createMemo(() => {
|
||||
// const
|
||||
// });
|
||||
|
||||
createEventListenerMap(() => video()!, {
|
||||
durationchange(e) {
|
||||
console.log("durationchange", e);
|
||||
},
|
||||
loadeddata(e) {
|
||||
console.log("loadeddata", e);
|
||||
},
|
||||
loadedmetadata(e) {
|
||||
console.log("loadedmetadata", e);
|
||||
},
|
||||
ratechange(e) {
|
||||
console.log("ratechange", e);
|
||||
},
|
||||
seeked(e) {
|
||||
console.log("seeked", e);
|
||||
},
|
||||
seeking(e) {
|
||||
console.log("seeking", e);
|
||||
},
|
||||
stalled(e) {
|
||||
console.log("stalled", e);
|
||||
},
|
||||
|
||||
play(e) {
|
||||
console.log("play", e);
|
||||
},
|
||||
playing(e) {
|
||||
console.log("playing", e);
|
||||
},
|
||||
pause(e) {
|
||||
console.log("pause", e);
|
||||
},
|
||||
suspend(e) {
|
||||
console.log("suspend", e);
|
||||
},
|
||||
|
||||
volumechange(e) {
|
||||
console.log("volumechange", e);
|
||||
},
|
||||
|
||||
waiting(e) {
|
||||
console.log("waiting", e);
|
||||
},
|
||||
|
||||
progress(e) {
|
||||
console.log(e);
|
||||
},
|
||||
|
||||
timeupdate(e) {
|
||||
console.log("timeupdate", e);
|
||||
},
|
||||
});
|
||||
|
||||
const toggle = () => {
|
||||
const el = video();
|
||||
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
el[el.paused ? "play" : "pause"]();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>{props.id}</h1>
|
||||
|
||||
<video ref={setVideo} muted preload="metadata">
|
||||
<source src="/videos/bbb_sunflower_2160p_60fps_normal.mp4" />
|
||||
</video>
|
||||
|
||||
<button onclick={toggle}>play/pause</button>
|
||||
|
||||
<progress />
|
||||
</>
|
||||
);
|
||||
};
|
171
src/features/shell/nav.module.css
Normal file
171
src/features/shell/nav.module.css
Normal file
|
@ -0,0 +1,171 @@
|
|||
.nav {
|
||||
grid-area: 2 / 1 / 3 / 2;
|
||||
display: block grid;
|
||||
grid-auto-flow: row;
|
||||
justify-content: space-between;
|
||||
inline-size: 5em;
|
||||
block-size: 100%;
|
||||
padding: 1em;
|
||||
background: inherit;
|
||||
z-index: 0;
|
||||
transition: z-index 0.3s step-end;
|
||||
|
||||
& > ul {
|
||||
position: relative;
|
||||
display: block grid;
|
||||
grid-template-columns: 2.5rem auto;
|
||||
align-content: center;
|
||||
inline-size: 4rem;
|
||||
gap: 1rem;
|
||||
transform-origin: left center;
|
||||
padding: 0;
|
||||
padding-inline-start: 0.5rem;
|
||||
margin: 0;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset-inline-start: 100%;
|
||||
inset-block: 0;
|
||||
inline-size: 20vw;
|
||||
/* background:
|
||||
radial-gradient(ellipse at left center 100% 100%, #f00, transparent),
|
||||
linear-gradient(to right, #0003, transparent); */
|
||||
background-image: linear-gradient(to right, #0003, transparent);
|
||||
mask: radial-gradient(
|
||||
ellipse 20vw 100% at left center,
|
||||
black,
|
||||
transparent
|
||||
);
|
||||
backdrop-filter: blur(5px);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s var(--ease-3);
|
||||
}
|
||||
|
||||
& > a {
|
||||
position: relative;
|
||||
grid-column: span 2;
|
||||
display: block grid;
|
||||
grid-template-columns: subgrid;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
transform-origin: center left;
|
||||
transition:
|
||||
transform 2s var(--ease-spring-5),
|
||||
opacity 0.3s var(--ease-3);
|
||||
color: var(--stone-4);
|
||||
font-size: 2rem;
|
||||
line-height: 1.5;
|
||||
|
||||
& > span {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s var(--ease-3);
|
||||
text-shadow: 0 0 1em #000;
|
||||
}
|
||||
|
||||
& > svg {
|
||||
fill: var(--stone-4);
|
||||
inline-size: 2.5rem;
|
||||
block-size: 2.5rem;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--yellow-4);
|
||||
list-style: disc;
|
||||
|
||||
&::before {
|
||||
content: "•";
|
||||
position: absolute;
|
||||
inset-inline-start: -1rem;
|
||||
}
|
||||
|
||||
& > svg {
|
||||
fill: var(--yellow-4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus))::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus)) > a:not(:is(:hover, :focus)) {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus)) > a {
|
||||
transform: scale(
|
||||
max(1, calc(1.5 - (0.2 * abs(var(--target) - var(--sibling-index)))))
|
||||
);
|
||||
|
||||
& > span {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(1)) {
|
||||
--target: 1;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(2)) {
|
||||
--target: 2;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(3)) {
|
||||
--target: 3;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(4)) {
|
||||
--target: 4;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(5)) {
|
||||
--target: 5;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(6)) {
|
||||
--target: 6;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(7)) {
|
||||
--target: 7;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(8)) {
|
||||
--target: 8;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(9)) {
|
||||
--target: 9;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(10)) {
|
||||
--target: 10;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(11)) {
|
||||
--target: 11;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(12)) {
|
||||
--target: 12;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(13)) {
|
||||
--target: 13;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(14)) {
|
||||
--target: 14;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(15)) {
|
||||
--target: 15;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(a:hover, :focus-within) {
|
||||
z-index: 1;
|
||||
transition: z-index 0.3s step-start;
|
||||
}
|
||||
}
|
25
src/features/shell/nav.tsx
Normal file
25
src/features/shell/nav.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { A } from "@solidjs/router";
|
||||
import { AiOutlineHome, AiOutlineStar, AiOutlineSearch } from "solid-icons/ai";
|
||||
import { Component } from "solid-js";
|
||||
import css from "./nav.module.css";
|
||||
|
||||
export const Nav: Component = (props) => {
|
||||
return (
|
||||
<nav class={css.nav}>
|
||||
<ul>
|
||||
<A href="/" activeClass={css.active} end={true}>
|
||||
<AiOutlineHome />
|
||||
<span>Home</span>
|
||||
</A>
|
||||
<A href="/library" activeClass={css.active}>
|
||||
<AiOutlineStar />
|
||||
<span>Library</span>
|
||||
</A>
|
||||
<A href="/search" activeClass={css.active}>
|
||||
<AiOutlineSearch />
|
||||
<span>Search</span>
|
||||
</A>
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
};
|
|
@ -3,27 +3,27 @@
|
|||
display: grid;
|
||||
grid: auto 1fr / 5em 1fr;
|
||||
grid-template-areas:
|
||||
'top top'
|
||||
'nav content'
|
||||
;
|
||||
"top top"
|
||||
"nav content";
|
||||
inline-size: 100%;
|
||||
block-size: 100%;
|
||||
z-index: 0;
|
||||
overflow: clip;
|
||||
container-type: inline-size;
|
||||
background-color: var(--surface-1);
|
||||
|
||||
/* &:has(.nav a:hover) > .body {
|
||||
filter: blur(3px);
|
||||
} */
|
||||
}
|
||||
|
||||
|
||||
.body {
|
||||
grid-area: 2 / 1 / 3 / 3;
|
||||
inline-size: 100%;
|
||||
block-size: 100%;
|
||||
background: linear-gradient(180deg, transparent, transparent 90vh, var(--surface-500) 90vh, var(--surface-500));
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
transparent,
|
||||
transparent 90vh,
|
||||
var(--surface-500) 90vh,
|
||||
var(--surface-500)
|
||||
);
|
||||
overflow: clip auto;
|
||||
padding-inline-start: 5em;
|
||||
transition: filter var(--duration-moderate-1) var(--ease-3);
|
||||
|
@ -37,177 +37,3 @@
|
|||
min-block-size: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.top {
|
||||
grid-area: top;
|
||||
display: block grid;
|
||||
grid-auto-flow: column;
|
||||
justify-content: end;
|
||||
z-index: 1;
|
||||
background-color: inherit;
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
.nav {
|
||||
grid-area: nav;
|
||||
display: block grid;
|
||||
grid-auto-flow: row;
|
||||
justify-content: space-between;
|
||||
inline-size: 5em;
|
||||
block-size: 100%;
|
||||
padding: 1em;
|
||||
background: inherit;
|
||||
z-index: 0;
|
||||
transition: z-index .3s step-end;
|
||||
|
||||
& > ul {
|
||||
position: relative;
|
||||
display: block grid;
|
||||
grid-template-columns: 2.5rem auto;
|
||||
align-content: center;
|
||||
inline-size: 4rem;
|
||||
gap: 1rem;
|
||||
transform-origin: left center;
|
||||
padding: 0;
|
||||
padding-inline-start: .5rem;
|
||||
margin: 0;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset-inline-start: 100%;
|
||||
inset-block: 0;
|
||||
inline-size: 20vw;
|
||||
/* background:
|
||||
radial-gradient(ellipse at left center 100% 100%, #f00, transparent),
|
||||
linear-gradient(to right, #0003, transparent); */
|
||||
background-image: linear-gradient(to right, #0003, transparent);
|
||||
mask: radial-gradient(ellipse 20vw 100% at left center, black, transparent);
|
||||
backdrop-filter: blur(5px);
|
||||
opacity: 0;
|
||||
transition: opacity .3s var(--ease-3);
|
||||
}
|
||||
|
||||
& > a {
|
||||
position: relative;
|
||||
grid-column: span 2;
|
||||
display: block grid;
|
||||
grid-template-columns: subgrid;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
transform-origin: center left;
|
||||
transition: transform 2s var(--ease-spring-5), opacity 0.3s var(--ease-3);
|
||||
color: var(--stone-4);
|
||||
font-size: 2rem;
|
||||
line-height: 1.5;
|
||||
|
||||
& > span {
|
||||
opacity: 0;
|
||||
transition: opacity .3s var(--ease-3);
|
||||
text-shadow: 0 0 1em #000;
|
||||
}
|
||||
|
||||
& > svg {
|
||||
fill: var(--stone-4);
|
||||
inline-size: 2.5rem;
|
||||
block-size: 2.5rem;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--yellow-4);
|
||||
list-style: disc;
|
||||
|
||||
&::before {
|
||||
content: '•';
|
||||
position: absolute;
|
||||
inset-inline-start: -1rem;
|
||||
}
|
||||
|
||||
& > svg {
|
||||
fill: var(--yellow-4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus))::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus)) > a:not(:is(:hover, :focus)) {
|
||||
opacity: .25;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus)) > a {
|
||||
transform: scale(max(1, calc(1.5 - (.2 * abs(var(--target) - var(--sibling-index))))));
|
||||
|
||||
& > span {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(1)) {
|
||||
--target: 1;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(2)) {
|
||||
--target: 2;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(3)) {
|
||||
--target: 3;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(4)) {
|
||||
--target: 4;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(5)) {
|
||||
--target: 5;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(6)) {
|
||||
--target: 6;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(7)) {
|
||||
--target: 7;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(8)) {
|
||||
--target: 8;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(9)) {
|
||||
--target: 9;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(10)) {
|
||||
--target: 10;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(11)) {
|
||||
--target: 11;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(12)) {
|
||||
--target: 12;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(13)) {
|
||||
--target: 13;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(14)) {
|
||||
--target: 14;
|
||||
}
|
||||
|
||||
&:has(a:is(:hover, :focus):nth-child(15)) {
|
||||
--target: 15;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(a:hover, :focus-within) {
|
||||
z-index: 1;
|
||||
transition: z-index .3s step-start;
|
||||
}
|
||||
}
|
|
@ -1,11 +1,6 @@
|
|||
import { A } from "@solidjs/router";
|
||||
import {
|
||||
AiOutlineHome,
|
||||
AiOutlineStar,
|
||||
AiOutlineSearch,
|
||||
} from "solid-icons/ai";
|
||||
import { ParentComponent, Component } from "solid-js";
|
||||
import { ColorSchemePicker } from "../theme";
|
||||
import { ParentComponent } from "solid-js";
|
||||
import { Top } from "./top";
|
||||
import { Nav } from "./nav";
|
||||
import css from "./shell.module.css";
|
||||
|
||||
export const Shell: ParentComponent = (props) => {
|
||||
|
@ -20,32 +15,3 @@ export const Shell: ParentComponent = (props) => {
|
|||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
const Top: Component = (props) => {
|
||||
return (
|
||||
<aside class={css.top}>
|
||||
<ColorSchemePicker />
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
const Nav: Component = (props) => {
|
||||
return (
|
||||
<nav class={css.nav}>
|
||||
<ul>
|
||||
<A href="/" activeClass={css.active} end={true}>
|
||||
<AiOutlineHome />
|
||||
<span>Home</span>
|
||||
</A>
|
||||
<A href="/library" activeClass={css.active}>
|
||||
<AiOutlineStar />
|
||||
<span>Library</span>
|
||||
</A>
|
||||
<A href="/search" activeClass={css.active}>
|
||||
<AiOutlineSearch />
|
||||
<span>Search</span>
|
||||
</A>
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
|
9
src/features/shell/top.module.css
Normal file
9
src/features/shell/top.module.css
Normal file
|
@ -0,0 +1,9 @@
|
|||
.top {
|
||||
grid-area: 1 / 1 / 2 / 3;
|
||||
display: block grid;
|
||||
grid-auto-flow: column;
|
||||
justify-content: end;
|
||||
z-index: 1;
|
||||
background-color: inherit;
|
||||
padding: 0.5em;
|
||||
}
|
11
src/features/shell/top.tsx
Normal file
11
src/features/shell/top.tsx
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { Component } from "solid-js";
|
||||
import { ColorSchemePicker } from "../theme";
|
||||
import css from "./top.module.css";
|
||||
|
||||
export const Top: Component = (props) => {
|
||||
return (
|
||||
<aside class={css.top}>
|
||||
<ColorSchemePicker />
|
||||
</aside>
|
||||
);
|
||||
};
|
17
src/routes/(shell)/watch/:item.tsx
Normal file
17
src/routes/(shell)/watch/:item.tsx
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { Params, useParams } from "@solidjs/router";
|
||||
import { createEffect } from "solid-js";
|
||||
import { Player } from "~/features/player";
|
||||
|
||||
interface ItemParams extends Params {
|
||||
item: string;
|
||||
}
|
||||
|
||||
export default function Item() {
|
||||
const params = useParams<ItemParams>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Player id={params.item} />
|
||||
</>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue