mirror of
https://github.com/zoriya/Kyoo.git
synced 2026-03-10 11:53:38 -04:00
Add video mapper component
This commit is contained in:
parent
1965b5294f
commit
b84d712037
@ -28,17 +28,6 @@ export default function Layout() {
|
||||
},
|
||||
headerTintColor: color as string,
|
||||
}}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="info/[slug]"
|
||||
options={{
|
||||
presentation: "transparentModal",
|
||||
headerShown: false,
|
||||
contentStyle: {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
3
front/src/app/(app)/series/[slug]/videos.tsx
Normal file
3
front/src/app/(app)/series/[slug]/videos.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
import { VideosModal } from "~/ui/admin";
|
||||
|
||||
export default VideosModal;
|
||||
@ -3,7 +3,7 @@ import type { Entry } from "~/models";
|
||||
export * from "./entry-box";
|
||||
export * from "./entry-line";
|
||||
|
||||
export const entryDisplayNumber = (entry: Entry) => {
|
||||
export const entryDisplayNumber = (entry: Partial<Entry>) => {
|
||||
switch (entry.kind) {
|
||||
case "episode":
|
||||
return `S${entry.seasonNumber}:E${entry.episodeNumber}`;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { z } from "zod/v4";
|
||||
import { Entry } from "./entry";
|
||||
import { Entry, Episode, MovieEntry, Special } from "./entry";
|
||||
import { Extra } from "./extra";
|
||||
import { Show } from "./show";
|
||||
import { zdate } from "./utils/utils";
|
||||
@ -37,14 +37,21 @@ export const Video = z.object({
|
||||
});
|
||||
|
||||
export const FullVideo = Video.extend({
|
||||
slugs: z.array(z.string()),
|
||||
progress: z.object({
|
||||
percent: z.int().min(0).max(100),
|
||||
time: z.int().min(0),
|
||||
playedDate: zdate().nullable(),
|
||||
videoId: z.string().nullable(),
|
||||
}),
|
||||
entries: z.array(Entry),
|
||||
entries: z.array(
|
||||
z.discriminatedUnion("kind", [
|
||||
Episode.omit({ progress: true, videos: true }),
|
||||
MovieEntry.omit({ progress: true, videos: true }),
|
||||
Special.omit({ progress: true, videos: true }),
|
||||
]),
|
||||
),
|
||||
progress: z.optional(
|
||||
z.object({
|
||||
percent: z.int().min(0).max(100),
|
||||
time: z.int().min(0),
|
||||
playedDate: zdate().nullable(),
|
||||
videoId: z.string().nullable(),
|
||||
}),
|
||||
),
|
||||
previous: z.object({ video: z.string(), entry: Entry }).nullable().optional(),
|
||||
next: z.object({ video: z.string(), entry: Entry }).nullable().optional(),
|
||||
show: Show.optional(),
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Close from "@material-symbols/svg-400/rounded/close.svg";
|
||||
import { useRouter } from "expo-router";
|
||||
import { Stack, useRouter } from "expo-router";
|
||||
import type { ReactNode } from "react";
|
||||
import { Pressable, ScrollView, View } from "react-native";
|
||||
import { cn } from "~/utils";
|
||||
@ -9,37 +9,50 @@ import { Heading } from "./text";
|
||||
export const Modal = ({
|
||||
title,
|
||||
children,
|
||||
scroll = true,
|
||||
}: {
|
||||
title: string;
|
||||
children: ReactNode;
|
||||
scroll?: boolean;
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
className="absolute inset-0 cursor-default! items-center justify-center bg-black/60 max-md:px-4"
|
||||
onPress={() => {
|
||||
if (router.canGoBack()) router.back();
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
presentation: "transparentModal",
|
||||
headerShown: false,
|
||||
contentStyle: {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Pressable
|
||||
className={cn(
|
||||
"w-full max-w-3xl rounded-md bg-background p-6",
|
||||
"max-h-[90vh] cursor-default! overflow-hidden",
|
||||
)}
|
||||
onPress={(e) => e.stopPropagation()}
|
||||
className="absolute inset-0 cursor-default! items-center justify-center bg-black/60 max-md:px-4"
|
||||
onPress={() => {
|
||||
if (router.canGoBack()) router.back();
|
||||
}}
|
||||
>
|
||||
<View className="mb-4 flex-row items-center justify-between">
|
||||
<Heading>{title}</Heading>
|
||||
<IconButton
|
||||
icon={Close}
|
||||
onPress={() => {
|
||||
if (router.canGoBack()) router.back();
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<ScrollView>{children}</ScrollView>
|
||||
<Pressable
|
||||
className={cn(
|
||||
"w-full max-w-3xl rounded-md bg-background",
|
||||
"max-h-[90vh] cursor-default overflow-hidden *:p-6",
|
||||
)}
|
||||
onPress={(e) => e.preventDefault()}
|
||||
>
|
||||
<View className="mb-4 flex-row items-center justify-between">
|
||||
<Heading>{title}</Heading>
|
||||
<IconButton
|
||||
icon={Close}
|
||||
onPress={() => {
|
||||
if (router.canGoBack()) router.back();
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
{scroll ? <ScrollView>{children}</ScrollView> : children}
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
1
front/src/ui/admin/index.tsx
Normal file
1
front/src/ui/admin/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export * from "./videos-modal";
|
||||
40
front/src/ui/admin/videos-modal.tsx
Normal file
40
front/src/ui/admin/videos-modal.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { View } from "react-native";
|
||||
import { entryDisplayNumber } from "~/components/entries";
|
||||
import { FullVideo } from "~/models";
|
||||
import { Modal, P, Select, Skeleton } from "~/primitives";
|
||||
import { InfiniteFetch, type QueryIdentifier } from "~/query";
|
||||
import { useQueryState } from "~/utils";
|
||||
|
||||
export const VideosModal = () => {
|
||||
const [slug] = useQueryState<string>("slug", undefined!);
|
||||
|
||||
return (
|
||||
<Modal title="toto" scroll={false}>
|
||||
<InfiniteFetch
|
||||
query={VideosModal.query(slug)}
|
||||
layout={{ layout: "vertical", gap: 8, numColumns: 1, size: 48 }}
|
||||
Render={({ item }) => (
|
||||
<View className="h-12 flex-row items-center justify-between hover:bg-card">
|
||||
<P>{item.path}</P>
|
||||
<Select
|
||||
label={"toto"}
|
||||
value={1}
|
||||
values={[1, 2, 3]}
|
||||
getLabel={() =>
|
||||
item.entries.map((x) => entryDisplayNumber(x)).join(", ")
|
||||
}
|
||||
onValueChange={(x) => {}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
Loader={() => <Skeleton />}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
VideosModal.query = (slug: string): QueryIdentifier<FullVideo> => ({
|
||||
parser: FullVideo,
|
||||
path: ["api", "series", slug, "videos"],
|
||||
infinite: true,
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user