From 08786ed4d78956d6efbc025b3f6ec6f5f4150f69 Mon Sep 17 00:00:00 2001 From: mbaharip <62494292+mbahArip@users.noreply.github.com> Date: Fri, 31 Jan 2025 15:30:49 +0700 Subject: [PATCH 1/7] wip: test stream download(?) --- src/app/api/test/fileSize/route.ts | 111 +++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/app/api/test/fileSize/route.ts diff --git a/src/app/api/test/fileSize/route.ts b/src/app/api/test/fileSize/route.ts new file mode 100644 index 0000000..2f5ad7f --- /dev/null +++ b/src/app/api/test/fileSize/route.ts @@ -0,0 +1,111 @@ +import { type NextRequest, NextResponse } from "next/server"; +import stream from "stream"; +import { promisify } from "util"; + +import { gdrive } from "~/lib/utils.server"; + +const pipeline = promisify(stream.pipeline); +const fileId = "1KvMU9sy8fQKKmVKU5xf_u3Y2JZefe0kt"; + +export async function GET(request: NextRequest) { + try { + const start = Date.now(); + console.log("Fetching metadata", Date.now() - start); + const meta = await gdrive.files.get({ + fileId, + fields: "size,mimeType,name", + supportsAllDrives: true, + }); + const { data: metadata } = meta; + + console.log("Fetching file", Date.now() - start); + // const data = await gdrive.files.get( + // { + // fileId: fileId, + // alt: "media", + // acknowledgeAbuse: true, + // }, + // { + // responseType: "stream", + // }, + // ); + + console.log("Setting headers", Date.now() - start); + const headers = new Headers(); + headers.set("Content-Type", metadata.mimeType ?? "application/octet-stream"); + if (metadata.size) headers.set("Content-Length", metadata.size.toString()); + headers.set("Content-Disposition", `attachment; filename="${metadata.name}"`); + + // console.log("Streaming file", Date.now() - start); + // const contentStream = data.data; + // const webStream = new ReadableStream({ + // async start(controller) { + // contentStream.on("data", (chunk) => { + // controller.enqueue(chunk); + // }); + // contentStream.on("end", () => { + // controller.close(); + // }); + // contentStream.on("error", (err) => { + // console.error(err); + // controller.error(err); + // }); + // }, + // cancel() { + // contentStream.destroy(); + // }, + // }); + + // console.log("Returning response", Date.now() - start); + return new Response( + new ReadableStream({ + async start(controller) { + try { + gdrive.files.get( + { + fileId: fileId, + alt: "media", + acknowledgeAbuse: true, + supportsAllDrives: true, + }, + { + responseType: "stream", + }, + (err, res) => { + if (err) throw err; + res?.data + .on("data", (chunk) => { + controller.enqueue(chunk); + }) + .on("end", () => { + controller.close(); + }) + .on("error", (err) => { + console.error(err); + controller.error(err); + }); + }, + ); + } catch (error) { + controller.error(error); + console.error(error); + } + }, + }), + { + headers, + }, + ); + } catch (error) { + const e = error as Error; + console.error(e); + return NextResponse.json( + { + error: e.message, + }, + { + status: 500, + }, + ); + } +} From acc9bbf48fadb14692c219f388f7c173f77cf383 Mon Sep 17 00:00:00 2001 From: mbaharip <62494292+mbahArip@users.noreply.github.com> Date: Thu, 6 Mar 2025 10:10:27 +0700 Subject: [PATCH 2/7] feat/playlist: Add / Upgrade packages --- bun.lockb | Bin 326262 -> 334798 bytes package.json | 13 +++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/bun.lockb b/bun.lockb index d1a8d9dfc87c69a648f0721a9582a1ee5a1f23f2..454367ecca92717d69ffe131fc12a6738d235e8c 100644 GIT binary patch delta 64467 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 diff --git a/package.json b/package.json index a32e083..a6d5cad 100644 --- a/package.json +++ b/package.json @@ -15,14 +15,14 @@ "dependencies": { "@hookform/resolvers": "^3.10.0", "@oslojs/encoding": "^1.1.0", - "@radix-ui/react-accordion": "^1.1.2", + "@radix-ui/react-accordion": "^1.2.3", "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-aspect-ratio": "^1.0.3", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.1.3", "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-context-menu": "^2.2.4", - "@radix-ui/react-dialog": "^1.1.4", + "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-hover-card": "^1.0.7", "@radix-ui/react-label": "^2.1.1", @@ -33,25 +33,26 @@ "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-scroll-area": "^1.2.2", "@radix-ui/react-select": "^2.1.2", - "@radix-ui/react-separator": "^1.1.0", + "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slider": "^1.2.1", - "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-switch": "^1.1.2", "@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-toggle-group": "^1.0.4", - "@radix-ui/react-tooltip": "^1.1.4", + "@radix-ui/react-tooltip": "^1.1.8", "@tanstack/react-virtual": "^3.11.2", "@vidstack/react": "^1.12.12", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "1.0.0", "embla-carousel-react": "^8.5.1", + "embla-carousel-wheel-gestures": "^8.0.1", "fflate": "^0.8.2", "framer-motion": "^11.13.3", "googleapis": "^144.0.0", - "lucide-react": "^0.468.0", + "lucide-react": "^0.477.0", "next": "^15.1.6", "next-themes": "^0.4.4", "nextjs-toploader": "^1.6.11", From c44ae5ccd3696926d0f66197a991fd5f92ca7d88 Mon Sep 17 00:00:00 2001 From: mbaharip <62494292+mbahArip@users.noreply.github.com> Date: Thu, 6 Mar 2025 10:11:02 +0700 Subject: [PATCH 3/7] fix: Use `import` instead of `require` --- tailwind.config.ts | 223 +++++++++++++++++++++++++-------------------- 1 file changed, 125 insertions(+), 98 deletions(-) diff --git a/tailwind.config.ts b/tailwind.config.ts index 2209800..92fe347 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,4 +1,7 @@ +import twTypography from '@tailwindcss/typography'; +import twVidstack from "@vidstack/react/tailwind.cjs"; import type { Config } from "tailwindcss"; +import twAnimate from "tailwindcss-animate"; import tw from "tailwindcss/defaultTheme"; export default { @@ -7,107 +10,131 @@ export default { content: ["./pages/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}", "./src/**/*.{ts,tsx}"], prefix: "", theme: { - container: { - center: true, - padding: { - DEFAULT: "0.5rem", - mobile: "1rem", - tablet: "1.5rem", - desktop: "2rem", - }, - screens: { - mobile: "480px", - tablet: "768px", - desktop: "1280px", - }, - }, - extend: { - fontFamily: { - sans: ["var(--font-outfit)", ...tw.fontFamily.sans], - mono: ["var(--font-jetbrains-mono)", ...tw.fontFamily.mono], - }, - colors: { - border: "hsl(var(--border))", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", - }, - secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", - }, - destructive: { - DEFAULT: "hsl(var(--destructive))", - foreground: "hsl(var(--destructive-foreground))", - }, - muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", - }, - accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", - }, - popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", - }, - card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", - }, - chart: { - "1": "hsl(var(--chart-1))", - "2": "hsl(var(--chart-2))", - "3": "hsl(var(--chart-3))", - "4": "hsl(var(--chart-4))", - "5": "hsl(var(--chart-5))", - }, - }, - screens: { - mobile: "480px", - tablet: "768px", - desktop: "1280px", - }, - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)", - }, - keyframes: { - "accordion-down": { - from: { - height: "0", - }, - to: { - height: "var(--radix-accordion-content-height)", - }, - }, - "accordion-up": { - from: { - height: "var(--radix-accordion-content-height)", - }, - to: { - height: "0", - }, - }, - }, - animation: { - "accordion-down": "accordion-down 0.2s ease-out", - "accordion-up": "accordion-up 0.2s ease-out", - }, - }, + container: { + center: true, + padding: { + DEFAULT: '0.5rem', + mobile: '1rem', + tablet: '1.5rem', + desktop: '2rem' + }, + screens: { + mobile: '480px', + tablet: '768px', + desktop: '1280px' + } + }, + extend: { + fontFamily: { + sans: [ + 'var(--font-outfit)', + ...tw.fontFamily.sans + ], + mono: [ + 'var(--font-jetbrains-mono)', + ...tw.fontFamily.mono + ] + }, + colors: { + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' + }, + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + }, + chart: { + '1': 'hsl(var(--chart-1))', + '2': 'hsl(var(--chart-2))', + '3': 'hsl(var(--chart-3))', + '4': 'hsl(var(--chart-4))', + '5': 'hsl(var(--chart-5))' + } + }, + screens: { + mobile: '480px', + tablet: '768px', + desktop: '1280px' + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + }, + keyframes: { + 'accordion-down': { + from: { + height: '0' + }, + to: { + height: 'var(--radix-accordion-content-height)' + } + }, + 'accordion-up': { + from: { + height: 'var(--radix-accordion-content-height)' + }, + to: { + height: '0' + } + }, + 'accordion-down': { + from: { + height: '0' + }, + to: { + height: 'var(--radix-accordion-content-height)' + } + }, + 'accordion-up': { + from: { + height: 'var(--radix-accordion-content-height)' + }, + to: { + height: '0' + } + } + }, + animation: { + 'accordion-down': 'accordion-down 0.2s ease-out', + 'accordion-up': 'accordion-up 0.2s ease-out', + 'accordion-down': 'accordion-down 0.2s ease-out', + 'accordion-up': 'accordion-up 0.2s ease-out' + } + } }, plugins: [ - require("tailwindcss-animate"), - require("@tailwindcss/typography"), - require("@vidstack/react/tailwind.cjs")({ + twAnimate, + twTypography, + twVidstack({ selector: ".media-player", prefix: "media", }), ], -} satisfies Config; +} satisfies Config; \ No newline at end of file From 2499c9cd785c37700c384507f9370c87d4bac4ef Mon Sep 17 00:00:00 2001 From: mbaharip <62494292+mbahArip@users.noreply.github.com> Date: Thu, 6 Mar 2025 10:11:56 +0700 Subject: [PATCH 4/7] feat/playlist: Add fetch siblings media from the same parent folder --- src/actions/files.ts | 89 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/src/actions/files.ts b/src/actions/files.ts index d070e7c..f748a02 100644 --- a/src/actions/files.ts +++ b/src/actions/files.ts @@ -9,6 +9,8 @@ import { Schema_File, Schema_File_Shortcut } from "~/types/schema"; import config from "config"; +import { ValidatePaths } from "./paths"; + /** * List files in a folder * @param {object} options @@ -372,3 +374,90 @@ export async function GetContent(id: string): Promise[]>> { + const pathIds = await ValidatePaths(paths); + if (!pathIds.success) + return { + success: false, + message: "Failed to validate paths", + error: pathIds.error, + }; + const folderPaths = pathIds.data.filter((item) => item.mimeType === "application/vnd.google-apps.folder"); + + const parentId = folderPaths[folderPaths.length - 1]?.id ?? config.apiConfig.rootFolder; + const isSharedDrive = !!(config.apiConfig.isTeamDrive && config.apiConfig.sharedDrive); + const decryptedParentId = await encryptionService.decrypt(parentId); + const decryptedSharedDrive = isSharedDrive + ? await encryptionService.decrypt(config.apiConfig.sharedDrive!) + : undefined; + + const filterName = config.apiConfig.hiddenFiles.map((item) => `not name = '${item}'`).join(" and "); + const filterQuery: string = [ + ...config.apiConfig.defaultQuery, + `'${decryptedParentId}' in parents`, + filterName, + "(mimeType contains 'video' or mimeType contains 'audio')", + ].join(" and "); + + const { data } = await gdrive.files.list({ + q: filterQuery, + fields: `files(${config.apiConfig.defaultField})`, + orderBy: config.apiConfig.defaultOrder, + pageSize: 100, + ...(decryptedSharedDrive && { + supportsAllDrives: true, + includeItemsFromAllDrives: true, + driveId: decryptedSharedDrive, + corpora: "drive", + }), + }); + if (!data.files?.length) return { success: true, message: "No siblings media found", data: [] }; + + const files: z.infer[] = []; + for (const file of data.files) { + files.push({ + encryptedId: await encryptionService.encrypt(file.id!), + encryptedWebContentLink: file.webContentLink ? await encryptionService.encrypt(file.webContentLink) : undefined, + name: file.name!, + mimeType: file.mimeType!, + trashed: file.trashed ?? false, + modifiedTime: new Date(file.modifiedTime!).toLocaleDateString(), + fileExtension: file.fileExtension ?? undefined, + size: file.size ? Number(file.size) : undefined, + thumbnailLink: file.thumbnailLink ?? undefined, + imageMediaMetadata: file.imageMediaMetadata + ? { + width: Number(file.imageMediaMetadata.width), + height: Number(file.imageMediaMetadata.height), + rotation: Number(file.imageMediaMetadata.rotation ?? 0), + } + : undefined, + videoMediaMetadata: file.videoMediaMetadata + ? { + durationMillis: Number(file.videoMediaMetadata.durationMillis), + height: Number(file.videoMediaMetadata.height), + width: Number(file.videoMediaMetadata.width), + } + : undefined, + }); + } + + const parsed = Schema_File.array().safeParse(files); + if (!parsed.success) + return { + success: false, + message: "Failed to parse siblings media", + error: parsed.error.message, + }; + + return { + success: true, + message: "Siblings media fetched", + data: parsed.data, + }; +} From a69ac2b00e87cced6fe978007688f139e495d020 Mon Sep 17 00:00:00 2001 From: mbaharip <62494292+mbahArip@users.noreply.github.com> Date: Thu, 6 Mar 2025 10:12:35 +0700 Subject: [PATCH 5/7] feat/playlist: Add UI for playlist --- src/app/[...rest]/page.tsx | 15 +- src/components/preview/Media.tsx | 5 +- .../preview/MediaPlaylistLayout.tsx | 296 ++++++++++++++++++ src/components/preview/PreviewLayout.tsx | 25 +- src/components/ui/accordion.tsx | 57 ++++ src/hooks/use-mobile.tsx | 19 ++ src/styles/globals.css | 9 + 7 files changed, 413 insertions(+), 13 deletions(-) create mode 100644 src/components/preview/MediaPlaylistLayout.tsx create mode 100644 src/components/ui/accordion.tsx create mode 100644 src/hooks/use-mobile.tsx diff --git a/src/app/[...rest]/page.tsx b/src/app/[...rest]/page.tsx index 5b2f3c9..07f7571 100644 --- a/src/app/[...rest]/page.tsx +++ b/src/app/[...rest]/page.tsx @@ -1,5 +1,6 @@ import { type Metadata, type ResolvedMetadata } from "next"; import { notFound } from "next/navigation"; +import { type z } from "zod"; import { FileActions, FileBreadcrumb, FileExplorerLayout, FileReadme } from "~/components/explorer"; import { ErrorComponent, Password } from "~/components/layout"; @@ -9,7 +10,9 @@ import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { getFileType } from "~/lib/previewHelper"; import { formatPathToBreadcrumb } from "~/lib/utils"; -import { GetBanner, GetFile, GetReadme, ListFiles } from "~/actions/files"; +import { type Schema_File } from "~/types/schema"; + +import { GetBanner, GetFile, GetReadme, GetSiblingsMedia, ListFiles } from "~/actions/files"; import { CheckPagePassword } from "~/actions/password"; import { ValidatePaths } from "~/actions/paths"; import { CreateFileToken } from "~/actions/token"; @@ -131,6 +134,14 @@ export default async function RestPage({ params }: Props) { const token = await CreateFileToken(file.data); if (!token.success) return ; + // Check if file is media (video / audio) + let playlistFiles: z.infer[] = []; + if (file.data.mimeType.includes("video") || file.data.mimeType.includes("audio")) { + const sib = await GetSiblingsMedia(rest); + if (!sib.success) return ; + playlistFiles = sib.data; + } + return ( ); diff --git a/src/components/preview/Media.tsx b/src/components/preview/Media.tsx index 6e9208c..a9a1213 100644 --- a/src/components/preview/Media.tsx +++ b/src/components/preview/Media.tsx @@ -39,11 +39,11 @@ type Props = { type: "video" | "audio"; }; export default function PreviewMedia({ file, type }: Props) { - const loading = useLoading(); const player = useRef(null); const [canPlay, setCanPlay] = useState(false); const [isLoop, setIsLoop] = useState(false); + const loading = useLoading(); const smallAudioLayoutQuery = useCallback(({ width }) => { return width < 576; @@ -53,7 +53,7 @@ export default function PreviewMedia({ file, type }: Props) { }, []); return ( -
+
{loading ? ( ) : ( @@ -65,7 +65,6 @@ export default function PreviewMedia({ file, type }: Props) { type: file.mimeType as AudioMimeType | VideoMimeType, }} loop={isLoop} - autoPlay playsInline crossOrigin viewType={type === "audio" ? "audio" : "video"} diff --git a/src/components/preview/MediaPlaylistLayout.tsx b/src/components/preview/MediaPlaylistLayout.tsx new file mode 100644 index 0000000..946b1eb --- /dev/null +++ b/src/components/preview/MediaPlaylistLayout.tsx @@ -0,0 +1,296 @@ +"use client"; + +import Link from "next/link"; +import { Fragment, useEffect, useMemo, useState } from "react"; +import { type z } from "zod"; + +import { useResponsive } from "~/context/responsiveContext"; +import useLoading from "~/hooks/useLoading"; +import { getPreviewIcon } from "~/lib/previewHelper"; +import { cn } from "~/lib/utils"; + +import { type Schema_File } from "~/types/schema"; + +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "../ui/accordion"; +import { Badge } from "../ui/badge"; +import { Button } from "../ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"; +import { + Carousel, + type CarouselApi, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +} from "../ui/carousel"; +import Icon from "../ui/icon"; +import { Skeleton } from "../ui/skeleton"; + +type MediaPlaylistLayoutProps = { + type?: "separated" | "inside"; + paths: string[]; + currentItem: z.infer; + playlist: z.infer[]; +}; + +export default function MediaPlaylistLayout({ + type = "separated", + paths, + currentItem, + playlist, +}: MediaPlaylistLayoutProps) { + const loading = useLoading(); + const { isDesktop } = useResponsive(); + + const [api, setApi] = useState(); + const [currentIndex, setCurrentIndex] = useState(0); + const [totalIndex, setTotalIndex] = useState(0); + + const uniquePlaylist = useMemo[]>( + () => playlist.filter((item) => `${item.name}#${item.size}` !== `${currentItem.name}#${currentItem.size}`), + [currentItem, playlist], + ); + + useEffect(() => { + if (!api) return; + + setTotalIndex(api.scrollSnapList().length); + setCurrentIndex(api.selectedScrollSnap()); + + api.on("select", () => { + setCurrentIndex(api.selectedScrollSnap()); + }); + }, [api]); + + // <= 1, because it always include current media + if (!uniquePlaylist.length) return null; + if (type === "separated") + return ( + + + Playlist + + + {loading ? ( +
+
+ + + {isDesktop && } +
+
+ ) : ( + item.encryptedId === currentItem.encryptedId) ?? 0) + 1, + containScroll: "keepSnaps", + }} + > + + {playlist.map((item) => { + const isCurrent = `${item.name}#${item.size}` === `${currentItem.name}#${currentItem.size}`; + const itemUrl = paths.slice(0, paths.length - 1).join("/") + `/${encodeURIComponent(item.name)}`; + const Wrapper = ({ children }: { children: React.ReactNode }) => + isCurrent ? {children} : {children}; + + return ( + + +
+ {isCurrent && Playing} + {item.mimeType.includes("video") ? ( + {`Preview + ) : ( +
+ +
+ )} +
+

+ {item.name} +

+
+
+
+
+ ); + })} +
+ + +
+ )} +
+
+ ); + if (type === "inside") + return ( + + + +
+ + Playlist +
+
+ + {loading ? ( +
+ + + + +
+ ) : ( +
+ {/* Current media */} + + + {/* Playlist */} + + + {uniquePlaylist.map((item) => { + const itemUrl = paths.slice(0, paths.length - 1).join("/") + `/${encodeURIComponent(item.name)}`; + + return ( + + + + + + ); + })} + +
+
+ {Array.from({ + length: totalIndex, + }).map((_, index) => ( +
+ ))} +
+ +
+ + + +
+
+ +
+ )} + + + + ); + + return <>; +} + +type ItemProps = { + item: z.infer; + isCurrent: boolean; + className?: string; +}; +function Item({ item, isCurrent, className }: ItemProps) { + return ( +
+ {isCurrent && Playing} + {item.mimeType.includes("video") ? ( + {`Preview + ) : ( +
+ +
+ )} +
+

+ {item.name} +

+
+
+ ); +} diff --git a/src/components/preview/PreviewLayout.tsx b/src/components/preview/PreviewLayout.tsx index 04c98a2..9edae28 100644 --- a/src/components/preview/PreviewLayout.tsx +++ b/src/components/preview/PreviewLayout.tsx @@ -14,7 +14,7 @@ import { PreviewUnknown, } from "~/components/preview"; import { Button } from "~/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "~/components/ui/card"; import { type getFileType } from "~/lib/previewHelper"; import { cn } from "~/lib/utils"; @@ -23,29 +23,27 @@ import { type Schema_File } from "~/types/schema"; import config from "config"; +import MediaPlaylistLayout from "./MediaPlaylistLayout"; + type Props = { data: z.infer; fileType: "unknown" | ReturnType; token: string; + playlist: z.infer[]; + paths: string[]; }; -export default function PreviewLayout({ data, fileType, token }: Props) { +export default function PreviewLayout({ data, paths, fileType, token, playlist }: Props) { const [view, setView] = useState<"markdown" | "raw">("markdown"); const PreviewComponent = useMemo(() => { switch (fileType) { case "image": return ; case "video": - return ( - - ); case "audio": return ( ); case "code": @@ -135,6 +133,15 @@ export default function PreviewLayout({ data, fileType, token }: Props) { <>{PreviewComponent} )} + + + + , + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AccordionItem.displayName = "AccordionItem" + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + +)) +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)) +AccordionContent.displayName = AccordionPrimitive.Content.displayName + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/src/hooks/use-mobile.tsx b/src/hooks/use-mobile.tsx new file mode 100644 index 0000000..2b0fe1d --- /dev/null +++ b/src/hooks/use-mobile.tsx @@ -0,0 +1,19 @@ +import * as React from "react" + +const MOBILE_BREAKPOINT = 768 + +export function useIsMobile() { + const [isMobile, setIsMobile] = React.useState(undefined) + + React.useEffect(() => { + const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) + const onChange = () => { + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) + } + mql.addEventListener("change", onChange) + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) + return () => mql.removeEventListener("change", onChange) + }, []) + + return !!isMobile +} diff --git a/src/styles/globals.css b/src/styles/globals.css index fc30c8b..dabd7a7 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -177,3 +177,12 @@ body { } @layer base { } + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} From e048b558d5d75d5529e1dfba15a66d8769f961d5 Mon Sep 17 00:00:00 2001 From: mbaharip <62494292+mbahArip@users.noreply.github.com> Date: Thu, 6 Mar 2025 10:12:48 +0700 Subject: [PATCH 6/7] Add new known issue --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c5c1631..bf6a616 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,11 @@ For now, I don't have any plan to implement this, because I think it's not neces While I think it's important, I don't have any plan to implement this for now. -### ~Encryption cause error~ +### TS video file doesn't recognized + +Somehow google return the mime type as code instead of video. I don't know how to fix this, so for now you can't preview the TS video file. + +### ~~Encryption cause error~~ ~~It seems the configurator are generating wrong encrypted folder ID, so it will cause error when you try to access the folder.~~ Should be fixed on v2.0.4, waiting for feedback / confirmation From 7215f143cbe8d98a1a06653aa9e61e467e193837 Mon Sep 17 00:00:00 2001 From: mbaharip <62494292+mbahArip@users.noreply.github.com> Date: Thu, 6 Mar 2025 10:24:24 +0700 Subject: [PATCH 7/7] fix: duplicate keyframes properties --- tailwind.config.ts | 216 ++++++++++++++++++++------------------------- 1 file changed, 96 insertions(+), 120 deletions(-) diff --git a/tailwind.config.ts b/tailwind.config.ts index 92fe347..0f55235 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,4 +1,4 @@ -import twTypography from '@tailwindcss/typography'; +import twTypography from "@tailwindcss/typography"; import twVidstack from "@vidstack/react/tailwind.cjs"; import type { Config } from "tailwindcss"; import twAnimate from "tailwindcss-animate"; @@ -10,124 +10,100 @@ export default { content: ["./pages/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}", "./src/**/*.{ts,tsx}"], prefix: "", theme: { - container: { - center: true, - padding: { - DEFAULT: '0.5rem', - mobile: '1rem', - tablet: '1.5rem', - desktop: '2rem' - }, - screens: { - mobile: '480px', - tablet: '768px', - desktop: '1280px' - } - }, - extend: { - fontFamily: { - sans: [ - 'var(--font-outfit)', - ...tw.fontFamily.sans - ], - mono: [ - 'var(--font-jetbrains-mono)', - ...tw.fontFamily.mono - ] - }, - colors: { - border: 'hsl(var(--border))', - input: 'hsl(var(--input))', - ring: 'hsl(var(--ring))', - background: 'hsl(var(--background))', - foreground: 'hsl(var(--foreground))', - primary: { - DEFAULT: 'hsl(var(--primary))', - foreground: 'hsl(var(--primary-foreground))' - }, - secondary: { - DEFAULT: 'hsl(var(--secondary))', - foreground: 'hsl(var(--secondary-foreground))' - }, - destructive: { - DEFAULT: 'hsl(var(--destructive))', - foreground: 'hsl(var(--destructive-foreground))' - }, - muted: { - DEFAULT: 'hsl(var(--muted))', - foreground: 'hsl(var(--muted-foreground))' - }, - accent: { - DEFAULT: 'hsl(var(--accent))', - foreground: 'hsl(var(--accent-foreground))' - }, - popover: { - DEFAULT: 'hsl(var(--popover))', - foreground: 'hsl(var(--popover-foreground))' - }, - card: { - DEFAULT: 'hsl(var(--card))', - foreground: 'hsl(var(--card-foreground))' - }, - chart: { - '1': 'hsl(var(--chart-1))', - '2': 'hsl(var(--chart-2))', - '3': 'hsl(var(--chart-3))', - '4': 'hsl(var(--chart-4))', - '5': 'hsl(var(--chart-5))' - } - }, - screens: { - mobile: '480px', - tablet: '768px', - desktop: '1280px' - }, - borderRadius: { - lg: 'var(--radius)', - md: 'calc(var(--radius) - 2px)', - sm: 'calc(var(--radius) - 4px)' - }, - keyframes: { - 'accordion-down': { - from: { - height: '0' - }, - to: { - height: 'var(--radix-accordion-content-height)' - } - }, - 'accordion-up': { - from: { - height: 'var(--radix-accordion-content-height)' - }, - to: { - height: '0' - } - }, - 'accordion-down': { - from: { - height: '0' - }, - to: { - height: 'var(--radix-accordion-content-height)' - } - }, - 'accordion-up': { - from: { - height: 'var(--radix-accordion-content-height)' - }, - to: { - height: '0' - } - } - }, - animation: { - 'accordion-down': 'accordion-down 0.2s ease-out', - 'accordion-up': 'accordion-up 0.2s ease-out', - 'accordion-down': 'accordion-down 0.2s ease-out', - 'accordion-up': 'accordion-up 0.2s ease-out' - } - } + container: { + center: true, + padding: { + DEFAULT: "0.5rem", + mobile: "1rem", + tablet: "1.5rem", + desktop: "2rem", + }, + screens: { + mobile: "480px", + tablet: "768px", + desktop: "1280px", + }, + }, + extend: { + fontFamily: { + sans: ["var(--font-outfit)", ...tw.fontFamily.sans], + mono: ["var(--font-jetbrains-mono)", ...tw.fontFamily.mono], + }, + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + chart: { + "1": "hsl(var(--chart-1))", + "2": "hsl(var(--chart-2))", + "3": "hsl(var(--chart-3))", + "4": "hsl(var(--chart-4))", + "5": "hsl(var(--chart-5))", + }, + }, + screens: { + mobile: "480px", + tablet: "768px", + desktop: "1280px", + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + keyframes: { + "accordion-down": { + from: { + height: "0", + }, + to: { + height: "var(--radix-accordion-content-height)", + }, + }, + "accordion-up": { + from: { + height: "var(--radix-accordion-content-height)", + }, + to: { + height: "0", + }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, }, plugins: [ twAnimate, @@ -137,4 +113,4 @@ export default { prefix: "media", }), ], -} satisfies Config; \ No newline at end of file +} satisfies Config;