diff --git a/data/mockFiles.ts b/data/mockFiles.ts deleted file mode 100644 index 6b435f9..0000000 --- a/data/mockFiles.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { IGDriveFiles } from "types/api/files"; -import { APIFilesResponse } from "types/api/response"; - -const mockFiles: APIFilesResponse = { - timestamp: Date.now(), - responseTime: 0, - folders: [], - files: [], - nextPageToken: "nextpageToken", - readmeExists: true, - bannerExists: true, - passwordExists: false, -}; -const mockFilesProtected: APIFilesResponse = { - timestamp: Date.now(), - responseTime: 0, - folders: [], - files: [], - nextPageToken: "nextpageToken", - readmeExists: true, - bannerExists: true, - passwordExists: true, -}; - -for (let i = 0; i < 10; i++) { - const folder: IGDriveFiles = { - mimeType: "application/vnd.google-apps.folder", - encryptedId: `encryptedId${i + 1}`, - name: `folder${i + 1}`, - trashed: false, - modifiedTime: new Date().toISOString(), - }; - const file: IGDriveFiles = { - mimeType: "application/vnd.google-apps.file", - encryptedId: `encryptedId${i + 1}`, - name: `file${i + 1}`, - trashed: false, - modifiedTime: new Date().toISOString(), - fileExtension: "txt", - encryptedWebContentLink: "encryptedWebContentLink", - size: Math.floor(Math.random() * 1000000000), - thumbnailLink: "/og.png", - imageMediaMetadata: - Math.random() > 0.5 - ? { - width: 1920, - height: 1080, - rotation: 0, - } - : null, - videoMediaMetadata: - Math.random() > 0.5 - ? { - width: 1920, - height: 1080, - durationMillis: 100000, - } - : null, - }; - - mockFiles.folders.push(folder); - mockFiles.files.push(file); - - folder.name = `folder${i + 1}protected`; - file.name = `file${i + 1}protected`; - mockFilesProtected.folders.push(folder); - mockFilesProtected.files.push(file); -} - -export { mockFiles, mockFilesProtected }; diff --git a/public/fonts/Exo2-Bold.ttf b/public/fonts/Exo2-Bold.ttf deleted file mode 100644 index 8404285..0000000 Binary files a/public/fonts/Exo2-Bold.ttf and /dev/null differ diff --git a/public/fonts/Exo2-Regular.ttf b/public/fonts/Exo2-Regular.ttf deleted file mode 100644 index 63bc4bb..0000000 Binary files a/public/fonts/Exo2-Regular.ttf and /dev/null differ diff --git a/public/images/setup/google-cloud-1.png b/public/images/setup/google-cloud-1.png deleted file mode 100644 index f603793..0000000 Binary files a/public/images/setup/google-cloud-1.png and /dev/null differ diff --git a/public/next.svg b/public/next.svg deleted file mode 100644 index 5174b28..0000000 --- a/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/setup/googleCloud.md b/public/setup/googleCloud.md deleted file mode 100644 index e69de29..0000000 diff --git a/public/vercel.svg b/public/vercel.svg deleted file mode 100644 index d2f8422..0000000 --- a/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/app/@explorer.tsx b/src/app/@explorer.tsx deleted file mode 100644 index 9acdbc8..0000000 --- a/src/app/@explorer.tsx +++ /dev/null @@ -1,189 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import { useContext, useEffect, useMemo, useState } from "react"; -import toast from "react-hot-toast"; -import { z } from "zod"; -import { Schema_File } from "~/schema"; -import { cn } from "~/utils"; - -import Icon from "~/components/Icon"; -import { Button } from "~/components/ui/button"; -import { Separator } from "~/components/ui/separator"; - -import { LayoutContext } from "~/context/layoutContext"; - -import config from "~/config/gIndex.config"; - -import FileGrid from "./@file.grid"; -import FileList from "./@file.list"; -import { GetFiles } from "./actions"; - -type Props = { - files: z.infer[]; - nextPageToken?: string; - root?: boolean; -}; -export default function FileBrowser({ files, nextPageToken, root }: Props) { - const { layout } = useContext(LayoutContext); - const pathname = usePathname(); - const prevPath = useMemo(() => { - const path = pathname - .split("/") - .slice(0, -1) - .join("/") - .replace(/\/+/g, "/"); - - return new URL(path, config.basePath).pathname; - }, [pathname]); - - const [fileList, setFileList] = - useState[]>(files); - const [nextToken, setNextToken] = useState(nextPageToken); - const [loadMoreLoading, setLoadMoreLoading] = useState(false); - - const [loading, setLoading] = useState(true); - - useEffect(() => { - setLoading(false); - }, []); - - const onLoadMore = async () => { - setLoadMoreLoading(true); - try { - if (!nextToken) throw new Error("No more files to load"); - const data = await GetFiles({ pageToken: nextToken }); - const uniqueData = [...fileList, ...data.files].filter( - (item, index, array) => - index === array.findIndex((i) => i.encryptedId === item.encryptedId), - ); - setFileList(uniqueData); - // const uniqueData = new Set([...fileList, ...data.files]); - // setFileList([...uniqueData]); - setNextToken(data.nextPageToken); - } catch (error) { - const e = error as Error; - console.error(e.message); - toast.error(e.message); - } finally { - setLoadMoreLoading(false); - } - }; - - if (loading) { - return ( -
- -

Wait a moment while we load your files...

-
- ); - } - - return ( -
- {!root && ( - - )} - {!fileList.length && ( -
- - - There are no files in this folder - -
- )} - {layout === "list" && ( -
- {fileList.map((file) => ( -
- - -
- ))} -
- )} - {layout === "grid" && ( -
- {fileList.map((file) => ( -
- -
- ))} -
- )} - - {nextToken && ( - - )} -
- ); -} diff --git a/src/app/@file.grid.tsx b/src/app/@file.grid.tsx deleted file mode 100644 index 7359d8b..0000000 --- a/src/app/@file.grid.tsx +++ /dev/null @@ -1,342 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import nProgress from "nprogress"; -import { useMemo, useState } from "react"; -import toast from "react-hot-toast"; -import { z } from "zod"; -import { Schema_File } from "~/schema"; -import { cn } from "~/utils"; - -import Icon from "~/components/Icon"; -import { Button } from "~/components/ui/button"; -import { - Drawer, - DrawerClose, - DrawerContent, - DrawerDescription, - DrawerFooter, - DrawerHeader, - DrawerTitle, - DrawerTrigger, -} from "~/components/ui/drawer"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "~/components/ui/dropdown-menu"; - -import useMediaQuery from "~/hooks/useMediaQuery"; -import bytesToReadable from "~/utils/bytesFormat"; -import { durationToReadable } from "~/utils/durationFormat"; -import { getPreviewIcon } from "~/utils/previewHelper"; - -import config from "~/config/gIndex.config"; - -import { CreateDownloadToken } from "./actions"; - -type Props = { - data: z.infer; - disabled?: boolean; -}; -export default function FileGrid({ data, disabled }: Props) { - const pathname = usePathname(); - const filePath = useMemo(() => { - // const currentPath = pathname.startsWith("/e") ? pathname : `/e${pathname}`; - // Set to pathname to remove the /e prefix - const path = [pathname, encodeURIComponent(data.name)] - .join("/") - .replace(/\/+/g, "/"); - - return new URL(path, config.basePath).pathname; - }, [data, pathname]); - - const [thumbnailURL, setThumbnailURL] = useState( - `/api/thumb/${data.encryptedId}?size=2`, - ); - const [actionOpen, setActionOpen] = useState(false); - const isDesktop = useMediaQuery("(min-width: 768px)"); - - const onCopy = async (e: React.MouseEvent) => { - e.stopPropagation(); - try { - toast.promise( - navigator.clipboard.writeText( - new URL(filePath, config.basePath).toString(), - ), - { - loading: "Copying link...", - success: "Link copied!", - error: "Failed to copy link", - }, - ); - } catch (error) { - const e = error as Error; - console.error(e.message); - } - }; - const onDownload = async (e: React.MouseEvent) => { - e.stopPropagation(); - toast.loading("Creating download token...", { - id: `download-${data.encryptedId}`, - }); - try { - const token = await CreateDownloadToken(); - if (!token) throw new Error("Failed to create download token"); - toast.success("Opening download link...", { - id: `download-${data.encryptedId}`, - }); - - const timeout = setTimeout(() => { - clearTimeout(timeout); - window.open(`/api/download/${data.encryptedId}?token=${token}`); - }, 1000); - } catch (error) { - const e = error as Error; - console.error(e.message); - toast.error(e.message, { - id: `download-${data.encryptedId}`, - }); - } - }; - - return ( -
- {isDesktop ? ( - - - - - - - - Copy link - - {data.mimeType.includes("folder") ? null : ( - - - Download - - )} - - - ) : ( - - - - - - - Actions - - What would you like to do with this file? - - -
- - - - {data.mimeType.includes("folder") ? null : ( - - - - )} -
- - - - - - -
-
- )} - { - if (disabled) { - e.preventDefault(); - e.stopPropagation(); - await new Promise((resolve) => setTimeout(resolve, 500)); - nProgress.done(true); - } - }} - href={filePath} - className={cn( - "h-full w-full", - "rounded-[var(--radius)]", - "flex flex-col content-stretch justify-stretch gap-3", - "hover:bg-muted/50", - "transition", - "border border-border", - )} - > -
- {/* If it's media, show thumbnail */} -
- {data.thumbnailLink && - (data.mimeType.startsWith("video") || - data.mimeType.startsWith("image")) ? ( - <> - {data.name} { - if (thumbnailURL.includes("size=2")) { - setThumbnailURL(`/api/thumb/${data.encryptedId}`); - } - }} - className='rounded-top-[var(--radius)] absolute -z-0 h-32 w-full flex-shrink-0 flex-grow-0 object-cover opacity-50' - /> - {data.name} { - if (thumbnailURL.includes("size=2")) { - setThumbnailURL(`/api/thumb/${data.encryptedId}`); - } - }} - className='relative z-0 h-32 w-full flex-shrink-0 flex-grow-0 object-contain backdrop-blur' - /> - - {data.mimeType.startsWith("video") && ( - <> - -
- {durationToReadable( - data.videoMediaMetadata?.durationMillis || 0, - )} -
- - )} - - ) : ( - - )} -
- - {/* File data */} -
- - {config.siteConfig.showFileExtension - ? data.name - : data.fileExtension - ? data.name.replace(new RegExp(`.${data.fileExtension}$`), "") - : data.name} - -
- - {data.mimeType.includes("folder") - ? "folder" - : data.fileExtension || "unknown"} - - {!data.mimeType.includes("folder") && ( - <> - {config.siteConfig.showFileExtension ? null : ( - - )} - - {bytesToReadable(data.size || 0)} - - - )} -
-
- - {new Date(data.modifiedTime).toLocaleDateString()} - -
-
-
- -
- ); -} diff --git a/src/app/@file.list.tsx b/src/app/@file.list.tsx deleted file mode 100644 index 2108656..0000000 --- a/src/app/@file.list.tsx +++ /dev/null @@ -1,330 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import nProgress from "nprogress"; -import { useMemo, useState } from "react"; -import toast from "react-hot-toast"; -import { z } from "zod"; -import { Schema_File } from "~/schema"; -import { cn } from "~/utils"; - -import Icon from "~/components/Icon"; -import { Button } from "~/components/ui/button"; -import { - Drawer, - DrawerClose, - DrawerContent, - DrawerDescription, - DrawerFooter, - DrawerHeader, - DrawerTitle, - DrawerTrigger, -} from "~/components/ui/drawer"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "~/components/ui/dropdown-menu"; - -import useMediaQuery from "~/hooks/useMediaQuery"; -import bytesToReadable from "~/utils/bytesFormat"; -import { durationToReadable } from "~/utils/durationFormat"; -import { getPreviewIcon } from "~/utils/previewHelper"; - -import config from "~/config/gIndex.config"; - -import { CreateDownloadToken } from "./actions"; - -type Props = { - data: z.infer; - disabled?: boolean; -}; -export default function FileList({ data, disabled }: Props) { - const pathname = usePathname(); - - const filePath = useMemo(() => { - const path = [pathname, encodeURIComponent(data.name)] - .join("/") - .replace(/\/+/g, "/"); - - return new URL(path, config.basePath).pathname; - }, [data, pathname]); - - const [thumbnailURL, setThumbnailURL] = useState( - `/api/thumb/${data.encryptedId}?size=2`, - ); - const [actionOpen, setActionOpen] = useState(false); - const isDesktop = useMediaQuery("(min-width: 768px)"); - - const onCopy = async (e: React.MouseEvent) => { - e.stopPropagation(); - try { - toast.promise( - navigator.clipboard.writeText( - new URL(filePath, config.basePath).toString(), - ), - { - loading: "Copying link...", - success: "Link copied!", - error: "Failed to copy link", - }, - ); - } catch (error) { - const e = error as Error; - console.error(e.message); - } - }; - const onDownload = async (e: React.MouseEvent) => { - e.stopPropagation(); - toast.loading("Creating download token...", { - id: `download-${data.encryptedId}`, - }); - try { - const token = await CreateDownloadToken(); - if (!token) throw new Error("Failed to create download token"); - toast.success("Opening download link...", { - id: `download-${data.encryptedId}`, - }); - - const timeout = setTimeout(() => { - clearTimeout(timeout); - window.open(`/api/download/${data.encryptedId}?token=${token}`); - }, 1000); - } catch (error) { - const e = error as Error; - console.error(e.message); - toast.error(e.message, { - id: `download-${data.encryptedId}`, - }); - } - }; - - return ( -
-
- {isDesktop ? ( - - - - - - - - Copy link - - {data.mimeType.includes("folder") ? null : ( - - - Download - - )} - - - ) : ( - - - - - - - Actions - - What would you like to do with this file? - - -
- - - - {data.mimeType.includes("folder") ? null : ( - - - - )} -
- - - - - - -
-
- )} -
- { - if (disabled) { - e.preventDefault(); - e.stopPropagation(); - await new Promise((resolve) => setTimeout(resolve, 500)); - nProgress.done(true); - } - }} - href={filePath} - className={cn( - "relative", - // "w-full", - "px-1.5 py-1 pr-10", // since action button is size-8 - "rounded-[var(--radius)]", - "flex flex-grow items-center justify-between gap-3", - "hover:bg-muted/50", - "transition", - )} - > -
- {/* If it's media, show thumbnail */} -
- {data.thumbnailLink && - (data.mimeType.startsWith("video") || - data.mimeType.startsWith("image")) ? ( - <> - {data.name} { - if (thumbnailURL.includes("size=2")) { - setThumbnailURL(`/api/thumb/${data.encryptedId}`); - } - }} - className='size-16 flex-shrink-0 flex-grow-0 rounded-[var(--radius)] object-cover tablet:size-20' - /> - - {data.mimeType.startsWith("video") && ( - <> - -
- {durationToReadable( - data.videoMediaMetadata?.durationMillis || 0, - )} -
- - )} - - ) : ( - - )} -
- - {/* File data */} -
- - {config.siteConfig.showFileExtension - ? data.name - : data.fileExtension - ? data.name.replace(new RegExp(`.${data.fileExtension}$`), "") - : data.name} - -
- - {data.mimeType.includes("folder") - ? "folder" - : data.fileExtension || "unknown"} - - {!data.mimeType.includes("folder") && ( - <> - {config.siteConfig.showFileExtension ? null : ( - - )} - - {bytesToReadable(data.size || 0)} - - - )} -
-
- - {new Date(data.modifiedTime).toLocaleDateString()} - -
-
-
- -
- ); -} diff --git a/src/app/@header.breadcrumb.tsx b/src/app/@header.breadcrumb.tsx deleted file mode 100644 index d371315..0000000 --- a/src/app/@header.breadcrumb.tsx +++ /dev/null @@ -1,185 +0,0 @@ -"use client"; - -import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"; -import Link from "next/link"; -import { Fragment, useState } from "react"; -import { z } from "zod"; -import { Schema_Breadcrumb } from "~/schema"; - -import Icon from "~/components/Icon"; -import { - Breadcrumb, - BreadcrumbEllipsis, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, -} from "~/components/ui/breadcrumb"; -import { Button } from "~/components/ui/button"; -import { - Drawer, - DrawerClose, - DrawerContent, - DrawerFooter, - DrawerHeader, - DrawerTitle, - DrawerTrigger, -} from "~/components/ui/drawer"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, -} from "~/components/ui/dropdown-menu"; -import { Separator } from "~/components/ui/separator"; -import { Skeleton } from "~/components/ui/skeleton"; - -import useMediaQuery from "~/hooks/useMediaQuery"; - -import config from "~/config/gIndex.config"; - -type Props = { - data: z.infer[]; - loading?: boolean; -}; -export default function HeaderBreadcrumb({ data, loading }: Props) { - const [open, setOpen] = useState(false); - const isDesktop = useMediaQuery("(min-width: 768px)"); - - if (loading) return ; - - return ( -
- - - - - -
- - ~ -
- -
-
- {!!data.length ? ( - <> - - - {data.length > config.siteConfig.breadcrumbMax ? ( - <> - {isDesktop ? ( - - - - - - {data - .slice(0, -config.siteConfig.breadcrumbMax + 1) - .map((item, _, array) => ( - - - item.href) - .join("/")}`} - className='w-full' - > - {item.label} - - - - ))} - - - ) : ( - - - - - - - - Navigate to parent directories - - - -
- {data - .slice(0, -config.siteConfig.breadcrumbMax + 1) - .map((item, _, array) => ( - - - item.href) - .join("/")}`} - className='w-full py-1.5' - > - {item.label} - - - - ))} -
- - - - - - -
-
- )} - - - - ) : null} - - {data.slice(-config.siteConfig.breadcrumbMax + 1).map((item) => ( - - - {item.href ? ( - <> - - item.href) - .join("/") - .replace(/\/\//g, "/")}`} - > - {item.label} - - - - ) : ( - - {item.label} - - )} - - {item.href && } - - ))} - - ) : null} -
-
-
- ); -} diff --git a/src/app/@header.title.tsx b/src/app/@header.title.tsx deleted file mode 100644 index 3a9da84..0000000 --- a/src/app/@header.title.tsx +++ /dev/null @@ -1,26 +0,0 @@ -"use client"; - -import { PropsWithChildren } from "react"; - -import { Skeleton } from "~/components/ui/skeleton"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "~/components/ui/tooltip"; - -export default function HeaderTitle({ - children, - loading, -}: PropsWithChildren<{ loading?: boolean }>) { - if (loading) return ; - - return ( - - -

{children}

-
- {children} -
- ); -} diff --git a/src/app/@header.tsx b/src/app/@header.tsx deleted file mode 100644 index 9ef6071..0000000 --- a/src/app/@header.tsx +++ /dev/null @@ -1,44 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { z } from "zod"; -import { Schema_Breadcrumb } from "~/schema"; -import { cn } from "~/utils"; - -import HeaderBreadcrumb from "./@header.breadcrumb"; - -type Props = { - name: string; - breadcrumb?: z.infer[]; -}; -export default function Header({ name, breadcrumb }: Props) { - const [loading, setLoading] = useState(true); - - useEffect(() => { - setLoading(false); - }, []); - - return ( -
-
- {/* {name} */} - - - {/* */} -
-
- ); -} diff --git a/src/app/@not-found.tsx b/src/app/@not-found.tsx deleted file mode 100644 index 9b73830..0000000 --- a/src/app/@not-found.tsx +++ /dev/null @@ -1,45 +0,0 @@ -"use client"; - -import { cn } from "~/utils"; - -import Icon from "~/components/Icon"; -import { Button } from "~/components/ui/button"; - -import useRouter from "~/hooks/usePRouter"; - -export default function NotFoundComponent() { - const router = useRouter(); - return ( -
- - - The file you are looking for does not exist - - -
- - -
-
- ); -} diff --git a/src/app/@preview.audio.tsx b/src/app/@preview.audio.tsx deleted file mode 100644 index ba5d2dc..0000000 --- a/src/app/@preview.audio.tsx +++ /dev/null @@ -1,132 +0,0 @@ -"use client"; - -import dynamic from "next/dynamic"; -import { useEffect, useState } from "react"; -import "react-h5-audio-player/lib/styles.css"; -import { z } from "zod"; -import { Schema_File } from "~/schema"; -import { cn } from "~/utils"; - -import Icon from "~/components/Icon"; - -import { CreateDownloadToken } from "./actions"; - -// import "./r5-style.css"; - -const Plyr = dynamic(() => import("plyr-react"), { - ssr: false, - loading: () => ( -
- -

Loading player...

-
- ), -}); - -type Props = { - file: z.infer; -}; -export default function PreviewAudio({ file }: Props) { - const [audioSrc, setAudioSrc] = useState(""); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(""); - - useEffect(() => { - (async () => { - try { - if (!file.encryptedWebContentLink) { - setError("No audio to preview"); - return; - } - const token = await CreateDownloadToken(); - setAudioSrc(`/api/stream/${file.encryptedId}?token=${token}`); - } catch (error) { - const e = error as Error; - console.error(e); - setError(e.message); - } finally { - setLoading(false); - } - })(); - }, [file]); - - return ( -
- {loading ? ( -
- -

Loading player...

-
- ) : error ? ( -
- - {error} -
- ) : ( -
- -
- )} -
- ); -} diff --git a/src/app/@preview.image.tsx b/src/app/@preview.image.tsx deleted file mode 100644 index 0d60d27..0000000 --- a/src/app/@preview.image.tsx +++ /dev/null @@ -1,130 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { z } from "zod"; -import { Schema_File } from "~/schema"; -import { cn } from "~/utils"; - -import Icon from "~/components/Icon"; -import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert"; - -import config from "~/config/gIndex.config"; - -import { CreateDownloadToken } from "./actions"; - -type Props = { - file: z.infer; -}; -export default function PreviewImage({ file }: Props) { - const [imgSrc, setImgSrc] = useState( - `/api/thumb/${file.encryptedId}?size=4`, - ); - const [imgLoaded, setImgLoaded] = useState(false); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(""); - - useEffect(() => { - (async () => { - try { - if (!file.encryptedWebContentLink) { - setError("No image to preview"); - return; - } - const token = await CreateDownloadToken(); - - const streamURL = new URL( - `/api/thumb/${file.encryptedId}?size=1000`, - config.basePath, - ); - streamURL.searchParams.set("token", token); - fetch(streamURL, { - headers: { - Range: `bytes=0-${(file.size || 1) - 1}`, - }, - }) - .then((res) => { - if (!res.ok) { - throw new Error("Could not load image"); - } - return res.blob(); - }) - .then((blob) => { - const urlobject = URL.createObjectURL(blob); - setImgSrc(urlobject); - const timeout = setTimeout(() => { - setImgLoaded(true); - clearTimeout(timeout); - }, 150); // Add a delay to show the image - }); - } catch (error) { - const e = error as Error; - console.error(e); - setError(e.message); - } finally { - setLoading(false); - } - })(); - }, [file]); - - return ( -
- {loading ? ( -
- -

Loading image...

-
- ) : error ? ( -
- - {error} -
- ) : ( -
- {file.name} { - console.error(e); - setError( - "Could not preview this image, try downloading the file", - ); - }} - /> - - -
- -
- Preview Only - - This image is a preview and may not be the full resolution. - Please download the file for the full resolution. - -
-
-
-
- )} -
- ); -} diff --git a/src/app/@preview.layout.tsx b/src/app/@preview.layout.tsx deleted file mode 100644 index 01119cf..0000000 --- a/src/app/@preview.layout.tsx +++ /dev/null @@ -1,107 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { z } from "zod"; -import { Schema_File } from "~/schema"; -import { cn } from "~/utils"; - -import Icon from "~/components/Icon"; -import { Card, CardContent } from "~/components/ui/card"; - -import { getFileType } from "~/utils/previewHelper"; - -import config from "~/config/gIndex.config"; - -import PreviewAction from "./@preview.action"; -import PreviewAudio from "./@preview.audio"; -import PreviewDoc from "./@preview.doc"; -import PreviewImage from "./@preview.image"; -import PreviewManga from "./@preview.manga"; -import PreviewRich from "./@preview.rich"; -import PreviewUnknown from "./@preview.unknown"; -import PreviewVideo from "./@preview.video"; -import RichHeader from "./@rich-header"; - -type Props = { - data: z.infer; - fileType: "unknown" | ReturnType; -}; -export default function FilePreviewLayout({ data, fileType }: Props) { - const [view, setView] = useState<"markdown" | "raw">("markdown"); - - return ( -
- - - - {config.apiConfig.streamMaxSize && - Number(data.size || 0) > config.apiConfig.streamMaxSize ? ( -
- -

Preview not available

-

- Looks like this file size exceed the preview size limit -

-
- ) : ( - /** - * TODO: Might need a better way to handle large files preview - * like manga, pdf, etc - * - * For now it's downloading the whole file and then previewing it - * which is not a good implementation - */ -
- {fileType === "image" ? ( - - ) : fileType === "audio" ? ( - - ) : fileType === "video" ? ( - - ) : fileType === "code" ? ( - - ) : fileType === "text" ? ( - - ) : fileType === "markdown" ? ( - - ) : fileType === "document" ? ( - - ) : fileType === "pdf" ? ( - - ) : fileType === "manga" ? ( - - ) : ( - - )} -
- )} -
-
- -
- ); -} diff --git a/src/app/@preview.rich.tsx b/src/app/@preview.rich.tsx deleted file mode 100644 index 1d56239..0000000 --- a/src/app/@preview.rich.tsx +++ /dev/null @@ -1,128 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { z } from "zod"; -import { Schema_File } from "~/schema"; -import { cn } from "~/utils"; - -import Icon from "~/components/Icon"; -import { Button } from "~/components/ui/button"; - -import Markdown from "./@markdown"; -import { GetContent } from "./actions"; - -type Props = { - file: z.infer; - view: "markdown" | "raw"; - code?: boolean; -}; -export default function PreviewRich({ file, code, view }: Props) { - const [fetchedContent, setFetchedContent] = useState(""); - const [content, setContent] = useState(""); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(""); - - const [expand, setExpand] = useState(false); - - useEffect(() => { - (async () => { - try { - const text = await GetContent(file.encryptedId); - if (!text) { - setError("Looks like there is no content to preview"); - return; - } - // setFetchedContent(text); - // if (code) { - // setContent(`\`\`\`${file.fileExtension}\n${text}\`\`\``); - // } else { - setContent(text.trim()); - // } - } catch (error) { - const e = error as Error; - console.error(e); - setError(e.message); - } finally { - setLoading(false); - } - })(); - }, [file, code]); - - return ( -
- {loading ? ( -
- -

Loading content...

-
- ) : error ? ( -
- - {error} -
- ) : ( -
-
- -
-
- -
-
- )} -
- ); -} diff --git a/src/app/@preview.unknown.tsx b/src/app/@preview.unknown.tsx deleted file mode 100644 index b044810..0000000 --- a/src/app/@preview.unknown.tsx +++ /dev/null @@ -1,52 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { cn } from "~/utils"; - -import Icon from "~/components/Icon"; - -export default function PreviewUnknown() { - const [loading, setLoading] = useState(true); - - useEffect(() => { - setLoading(false); - }, []); - - return ( -
- {loading ? ( -
- -

Loading content...

-
- ) : ( -
- -

Preview not available

-

- This file type is not supported for preview, try downloading the - file instead. -

-
- )} -
- ); -} diff --git a/src/app/@preview.video.tsx b/src/app/@preview.video.tsx deleted file mode 100644 index 1a4e634..0000000 --- a/src/app/@preview.video.tsx +++ /dev/null @@ -1,130 +0,0 @@ -"use client"; - -import dynamic from "next/dynamic"; -import { useEffect, useState } from "react"; -import { z } from "zod"; -import { Schema_File } from "~/schema"; -import { cn } from "~/utils"; - -import Icon from "~/components/Icon"; - -import { CreateDownloadToken } from "./actions"; - -const Plyr = dynamic(() => import("plyr-react"), { - ssr: false, - loading: () => ( -
- -

Loading player...

-
- ), -}); - -type Props = { - file: z.infer; -}; -export default function PreviewVideo({ file }: Props) { - const [videoSrc, setVideoSrc] = useState(""); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(""); - - useEffect(() => { - (async () => { - try { - if (!file.encryptedWebContentLink) { - setError("No video to preview"); - return; - } - const token = await CreateDownloadToken(); - setVideoSrc(`/api/stream/${file.encryptedId}?token=${token}`); - } catch (error) { - const e = error as Error; - console.error(e); - setError(e.message); - } finally { - setLoading(false); - } - })(); - }, [file]); - - return ( -
- {loading ? ( -
- -

Loading video...

-
- ) : error ? ( -
- - {error} -
- ) : ( -
- -
- )} -
- ); -} diff --git a/src/app/@readme.tsx b/src/app/@readme.tsx deleted file mode 100644 index 8990720..0000000 --- a/src/app/@readme.tsx +++ /dev/null @@ -1,38 +0,0 @@ -"use client"; - -import { useState } from "react"; - -import { Card, CardContent } from "~/components/ui/card"; - -import Markdown from "./@markdown"; -import RichHeader from "./@rich-header"; - -type Props = { - content: string; - title: string; -}; -export default function Readme({ content, title }: Props) { - const [view, setView] = useState<"markdown" | "raw">("markdown"); - - return ( -
- - - - - - -
- ); -} diff --git a/src/app/@rich-header.tsx b/src/app/@rich-header.tsx deleted file mode 100644 index 3c984f5..0000000 --- a/src/app/@rich-header.tsx +++ /dev/null @@ -1,53 +0,0 @@ -"use client"; - -import { Button } from "~/components/ui/button"; -import { CardHeader } from "~/components/ui/card"; -import { Separator } from "~/components/ui/separator"; - -import { getFileType } from "~/utils/previewHelper"; - -type Props = { - title: string; - view: "markdown" | "raw"; - onViewChange: (value: "markdown" | "raw") => void; - fileType: ReturnType | "unknown"; -}; -export default function RichHeader({ - title, - view, - onViewChange, - fileType, -}: Props) { - return ( - -
- {/* */} -

- {title} -

- {/*
*/} - {["markdown", "code", "text"].includes(fileType) && ( -
- - -
- )} -
- -
- ); -} diff --git a/src/app/[...rest]/deploy/@form.api-config.tsx b/src/app/[...rest]/deploy/@form.api-config.tsx deleted file mode 100644 index 48f9ba3..0000000 --- a/src/app/[...rest]/deploy/@form.api-config.tsx +++ /dev/null @@ -1,533 +0,0 @@ -"use client"; - -import toast from "react-hot-toast"; -import { z } from "zod"; -import { - ConfigurationCategory, - ConfigurationKeys, - ConfigurationValue, - Schema_App_Configuration, -} from "~/schema"; - -import { Button } from "~/components/ui/button"; -import { Input } from "~/components/ui/input"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "~/components/ui/select"; -import { Separator } from "~/components/ui/separator"; - -import { decryptData } from "~/utils/encryptionHelper/hash"; -import { parseConfigFile } from "~/utils/parseConfigFile"; - -import ConfigInput from "./@form.input-config"; - -type Props = { - state: { - get: z.input; - set: < - T extends ConfigurationCategory = ConfigurationCategory, - K extends ConfigurationKeys = ConfigurationKeys, - >( - category: T, - key: K, - value: ConfigurationValue, - ) => void; - }; - error: { - get: Partial, string>>; - set: >(key: T, value: string) => void; - }; - onReset: (category: ConfigurationCategory) => void; -}; -export default function ApiConfig({ - state: { get, set }, - error, - onReset, -}: Props) { - return ( -
{ - e.preventDefault(); - onReset("api"); - }} - > -
-

API

-
- - -
-
- - - -
- > - key='rootFolder' - title='Root Folder ID' - description={`Starting point of the drive, will be used as the root folder to display files and folders. -This ID will be encrypted in the config file.`} - error={error.get.rootFolder} - required - > - { - if (error.get.rootFolder) { - error.set("rootFolder", ""); - } - set("api", "rootFolder", e.target.value); - }} - onBlur={async () => { - try { - const value = get.api.rootFolder; - error.set("rootFolder", ""); - - if (!value) throw new Error("Root Folder ID is required"); - } catch (err) { - const e = err as Error; - error.set("rootFolder", e.message); - } - }} - /> - - - > - key='isTeamDrive' - title='Use Team Drive' - description={`If you are using Shared Drive, you NEED to enable this option.`} - error={error.get.isTeamDrive} - required - > - - - - {get.api.isTeamDrive && ( - > - key='sharedDrive' - title='Shared Drive ID' - description={`The Drive ID of the Shared Drive. -This ID will be encrypted in the config file`} - error={error.get.sharedDrive} - required={get.api.isTeamDrive} - > - { - if (error.get.sharedDrive) { - error.set("sharedDrive", ""); - } - set("api", "sharedDrive", e.target.value); - }} - onBlur={async () => { - try { - const value = get.api.sharedDrive; - const isTeamDrive = get.api.isTeamDrive; - error.set("sharedDrive", ""); - - if (!isTeamDrive) return; - if (!value) - throw new Error( - "Shared Drive ID is required if using Team Drive", - ); - } catch (err) { - const e = err as Error; - error.set("sharedDrive", e.message); - } - }} - /> - - )} - -
- > - key='itemsPerPage' - title='Items Per Page' - description={`Set how many items to display per page in the file list. -It's recommended to set this to a reasonable number, since it will affect the load time of the page. - -Default is 50.`} - error={error.get.itemsPerPage} - required - > - { - if (error.get.itemsPerPage) { - error.set("itemsPerPage", ""); - } - set("api", "itemsPerPage", parseInt(e.target.value)); - }} - onBlur={async () => { - try { - const value = get.api.itemsPerPage; - error.set("itemsPerPage", ""); - - if (value <= 0) - throw new Error("Items Per Page must be more than 0"); - } catch (err) { - const e = err as Error; - error.set("itemsPerPage", e.message); - } - }} - /> - - - > - key='searchResult' - title='Search Result' - description={`Set how many items to display in the search result. -It's recommended to set this to a small number, since it will affect the load time of the page. - -Default is 5.`} - error={error.get.searchResult} - required - > - { - if (error.get.searchResult) { - error.set("searchResult", ""); - } - set("api", "searchResult", parseInt(e.target.value)); - }} - onBlur={async () => { - try { - const value = get.api.searchResult; - error.set("searchResult", ""); - - if (value <= 0) - throw new Error("Search Result must be more than 0"); - } catch (err) { - const e = err as Error; - error.set("searchResult", e.message); - } - }} - /> - -
- - > - key='proxyThumbnail' - title='Proxy Thumbnail' - description={`Proxy the thumbnail image via API route. - -If your files thumbnail are not accessible, you can set this to true. -This will fetch the thumbnail image via API route, but it will increase the load on your server.`} - error={error.get.proxyThumbnail} - required - > - - - - > - key='allowDownloadProtectedFile' - title='Allow Download Protected File' - description={`Allow users to download password protected files. - -If set to true, users will be able to download the file without entering password as long as they have the link -If set to false, the download link will have a temporary token attached to it, the token will expire after certain duration (default is 6 hours)`} - error={error.get.allowDownloadProtectedFile} - required - > - - - - > - key='temporaryTokenDuration' - title='Temporary Token Duration (in hours)' - description={`Duration of the temporary token used for protected files download link.`} - error={error.get.temporaryTokenDuration} - required - > - { - if (error.get.temporaryTokenDuration) { - error.set("temporaryTokenDuration", ""); - } - set("api", "temporaryTokenDuration", parseInt(e.target.value)); - }} - onBlur={async () => { - try { - const value = get.api.temporaryTokenDuration; - error.set("temporaryTokenDuration", ""); - - if (value <= 0) - throw new Error( - "Temporary Token Duration must be more than 0", - ); - } catch (err) { - const e = err as Error; - error.set("temporaryTokenDuration", e.message); - } - }} - /> - - - > - key='maxFileSize' - title='Max Direct Download Size (in MB)' - description={`Max file size that can be downloaded directly from the server, instead of download link from Google Drive. -Please refer to your deploy platform for the maximum response size limit. -If you are using Vercel, the maximum response size is around 4 - 4.5MB. - -Set to 0 to disable the limit.`} - error={error.get.maxFileSize} - required - > - { - if (error.get.maxFileSize) { - error.set("maxFileSize", ""); - } - set("api", "maxFileSize", parseInt(e.target.value) * 1024 * 1024); - }} - onBlur={async () => { - try { - const value = get.api.maxFileSize; - error.set("maxFileSize", ""); - - if (value < 0) - throw new Error( - "Max file size must be more than or equal to 0", - ); - } catch (err) { - const e = err as Error; - error.set("maxFileSize", e.message); - } - }} - /> - - - > - key='streamMaxSize' - title='Max Stream Size (in MB)' - description={`For previewing large files, the file will be streamed in chunks. -There are response limit in some deploy platform like Vercel, also Google Drive will return 403 error if we tried using fetch to download large file on client. - -Make sure it's within reasonable size, since it will count towards your server bandwidth usage. -This will also affect the maximum file size that can be previewed. Especially for media files like video, audio, and images. -(Manga preview will automatically limited to the first 5MB) - -Default is 100MB, set to 0 to disable the limit.`} - error={error.get.streamMaxSize} - required - > - { - if (error.get.maxFileSize) { - error.set("streamMaxSize", ""); - } - set( - "api", - "streamMaxSize", - parseInt(e.target.value) * 1024 * 1024, - ); - }} - onBlur={async () => { - try { - const value = get.api.streamMaxSize; - error.set("streamMaxSize", ""); - - if (value < 0) - throw new Error( - "Max stream size must be more than or equal to 0", - ); - } catch (err) { - const e = err as Error; - error.set("streamMaxSize", e.message); - } - }} - /> - -
- - ); -} diff --git a/src/app/[...rest]/deploy/@form.env-config.tsx b/src/app/[...rest]/deploy/@form.env-config.tsx deleted file mode 100644 index c7244a0..0000000 --- a/src/app/[...rest]/deploy/@form.env-config.tsx +++ /dev/null @@ -1,359 +0,0 @@ -"use client"; - -import { useState } from "react"; -import toast from "react-hot-toast"; -import { z } from "zod"; -import { - ConfigState, - ConfigurationCategory, - ConfigurationKeys, - ConfigurationValue, - Schema_App_Configuration, - Schema_ServiceAccount, -} from "~/schema"; - -import { GenerateAESKey, VerifyAESKey } from "~/app/actions"; -import { Button } from "~/components/ui/button"; -import { Input } from "~/components/ui/input"; -import { Separator } from "~/components/ui/separator"; - -import ConfigInput from "./@form.input-config"; - -type Props = { - state: { - get: z.input; - set: < - T extends ConfigurationCategory = ConfigurationCategory, - K extends ConfigurationKeys = ConfigurationKeys, - >( - category: T, - key: K, - value: ConfigurationValue, - ) => void; - }; - error: { - get: Partial, string>>; - set: >( - key: T, - value: string, - ) => void; - }; - onReset: (category: ConfigurationCategory) => void; -}; -export default function EnvironmentConfig({ - state: { get, set }, - error, - onReset, -}: Props) { - const [encryptionState, setEncryptionState] = useState("idle"); - const [gdServiceState, setGdServiceState] = useState("idle"); - const [revealPassword, setRevealPassword] = useState(false); - - return ( -
{ - e.preventDefault(); - onReset("environment"); - }} - > -
-

Environment

-
- - -
-
- - - -
- > - key='ENCRYPTION_KEY' - title='Encryption Key' - description='The encryption key for the site, must be a alphanumeric string without spaces' - error={error.get.ENCRYPTION_KEY} - required - action={{ - label: "Generate", - async onClick(e) { - e.preventDefault(); - setEncryptionState("loading"); - error.set("ENCRYPTION_KEY", ""); - - try { - const keyStr = await GenerateAESKey(); - const valid = await VerifyAESKey("This is a test", keyStr); - if (!valid) - throw new Error("Invalid key generated, please try again"); - - set("environment", "ENCRYPTION_KEY", keyStr); - } catch (err) { - const e = err as Error; - console.error(e); - error.set("ENCRYPTION_KEY", e.message); - toast.error(e.message); - } finally { - setEncryptionState("idle"); - } - }, - state: encryptionState, - }} - > - { - if (error.get.ENCRYPTION_KEY) { - error.set("ENCRYPTION_KEY", ""); - } - set("environment", "ENCRYPTION_KEY", e.target.value); - }} - onBlur={async () => { - try { - const value = get.environment.ENCRYPTION_KEY; - error.set("ENCRYPTION_KEY", ""); - - if (!value) throw new Error("Encryption key is required"); - - if (value.includes(" ")) - throw new Error("Encryption key must not contain spaces"); - - const valid = await VerifyAESKey("This is a test", value); - if (!valid) - throw new Error( - "The encryption key is invalid, please generate a new one", - ); - } catch (err) { - const e = err as Error; - error.set("ENCRYPTION_KEY", e.message); - } - }} - /> - - - > - key='GD_SERVICE_B64' - title='Google Drive Service Account' - description={`The base64 encoded Google Drive Service Account JSON file -To avoid error when inputting, please use the "Load JSON" button to load the file directly`} - error={error.get.GD_SERVICE_B64} - required - action={{ - label: "Load JSON", - async onClick(e) { - e.preventDefault(); - setGdServiceState("loading"); - - try { - const fileInput = document.createElement("input"); - fileInput.type = "file"; - fileInput.accept = ".json"; - fileInput.onchange = async (fileEvent) => { - const file = (fileEvent.target as HTMLInputElement) - .files?.[0]; - if (!file) return toast.error("No file selected"); - - const reader = new FileReader(); - reader.onload = async (readerEvent) => { - const result = readerEvent.target?.result as string; - if (!result) return toast.error("Failed to read file"); - - const objectFile = JSON.parse(result); - const parse = Schema_ServiceAccount.safeParse(objectFile); - if (!parse.success) - return toast.error( - "Invalid Service Account JSON, please select a valid Google Drive Service Account JSON file", - ); - - set("environment", "GD_SERVICE_B64", btoa(result)); - error.set("GD_SERVICE_B64", ""); - - toast.success( - "Google Drive Service Account JSON file loaded", - ); - fileInput.value = ""; - }; - reader.readAsText(file); - }; - fileInput.click(); - } catch (err) { - const e = err as Error; - console.error(e); - error.set("GD_SERVICE_B64", e.message); - toast.error(e.message); - } finally { - setGdServiceState("idle"); - } - }, - state: gdServiceState, - }} - > - { - if (error.get.GD_SERVICE_B64) { - error.set("GD_SERVICE_B64", ""); - } - set("environment", "GD_SERVICE_B64", e.target.value); - }} - /> - - - > - key='NEXT_PUBLIC_DOMAIN' - title='Domain' - description={`The domain for the site, without the protocol -(e.g. drive-demo.mbaharip.com or mbaharip.com)`} - error={error.get.NEXT_PUBLIC_DOMAIN} - > - { - if (error.get.NEXT_PUBLIC_DOMAIN) { - error.set("NEXT_PUBLIC_DOMAIN", ""); - } - set("environment", "NEXT_PUBLIC_DOMAIN", e.target.value); - }} - onBlur={async () => { - try { - error.set("NEXT_PUBLIC_DOMAIN", ""); - } catch (err) { - const e = err as Error; - error.set("NEXT_PUBLIC_DOMAIN", e.message); - } - }} - /> - - - > - key='SITE_PASSWORD' - title='Site Password' - description='The password to access the site' - error={error.get.SITE_PASSWORD} - required={get.site.privateIndex} - action={{ - label: revealPassword ? "Hide" : "Reveal", - onClick(e) { - e.preventDefault(); - setRevealPassword((prev) => !prev); - }, - state: "idle", - }} - > - { - if (error.get.SITE_PASSWORD) { - error.set("SITE_PASSWORD", ""); - } - set("environment", "SITE_PASSWORD", e.target.value); - }} - onBlur={async () => { - try { - error.set("SITE_PASSWORD", ""); - if (get.site.privateIndex && !get.environment.SITE_PASSWORD) { - throw new Error( - "Site Password is required when Private Index is enabled", - ); - } - } catch (err) { - const e = err as Error; - console.error(e); - error.set("SITE_PASSWORD", e.message); - } - }} - /> - -
- - ); -} diff --git a/src/app/[...rest]/deploy/@form.input-config.tsx b/src/app/[...rest]/deploy/@form.input-config.tsx deleted file mode 100644 index 880bc01..0000000 --- a/src/app/[...rest]/deploy/@form.input-config.tsx +++ /dev/null @@ -1,120 +0,0 @@ -"use client"; - -import { PropsWithChildren } from "react"; -import { ConfigState } from "~/schema"; -import { cn } from "~/utils"; - -import Icon from "~/components/Icon"; -import { Button } from "~/components/ui/button"; -import { Label } from "~/components/ui/label"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "~/components/ui/tooltip"; - -type Props = { - key: T extends string ? T : string; - title: string; - description?: string; - required?: boolean; - action?: { - label: string; - onClick: (e: React.MouseEvent) => void; - state: ConfigState; - }; - error?: string; -}; -export default function ConfigInput(props: PropsWithChildren>) { - return ( -
-
- - {!props.required && ( - (optional) - )} - {props.description && ( - - e.preventDefault()} - className='cursor-default' - > - - - -

- {props.description} -

-
-
- )} -
- -
-
- {props.children} -
- {props.action && ( - - )} -
- -
- - {props.error} - -
-
- ); -} diff --git a/src/app/[...rest]/deploy/@form.input-theme.tsx b/src/app/[...rest]/deploy/@form.input-theme.tsx deleted file mode 100644 index 3553ca3..0000000 --- a/src/app/[...rest]/deploy/@form.input-theme.tsx +++ /dev/null @@ -1,181 +0,0 @@ -"use client"; - -import { PopoverTrigger } from "@radix-ui/react-popover"; -import React, { PropsWithChildren } from "react"; -import { HslColor, HslColorPicker } from "react-colorful"; -import { z } from "zod"; -import { Schema_Theme } from "~/schema"; - -import Icon from "~/components/Icon"; -import { Input } from "~/components/ui/input"; -import { Label } from "~/components/ui/label"; -import { Popover, PopoverContent } from "~/components/ui/popover"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "~/components/ui/tooltip"; - -type Props = { - key: keyof z.infer; - title: string; - description?: string; - value: HslColor; - onChange: (color: HslColor) => void; - children?: React.ReactNode; -}; -export default function ThemeInput(props: PropsWithChildren) { - // const [value, setValue] = useState(props.value); - // const [debouncedValue, setDebouncedValue] = useState(props.value); - - // useEffect(() => { - // setValue(props.value); - - // // eslint-disable-next-line react-hooks/exhaustive-deps - // }, [props.value]); - - // useEffect(() => { - // const timeout = setTimeout(() => { - // setDebouncedValue(value); - // }, 250); - // return () => clearTimeout(timeout); - - // // eslint-disable-next-line react-hooks/exhaustive-deps - // }, [value]); - - // useEffect(() => { - // props.onChange(debouncedValue); - - // // eslint-disable-next-line react-hooks/exhaustive-deps - // }, [debouncedValue]); - - return ( -
-
- {props.title} - {props.description && ( - - e.preventDefault()} - className='cursor-default' - > - - - -

- {props.description} -

-
-
- )} -
- -
- {props.children ? ( - props.children - ) : ( - <> - - hsl({props.value.h}, {props.value.s}%, {props.value.l}%) - - - -
-
-
- - - -
-
- - { - const value = props.value; - props.onChange({ - h: Number(e.target.value), - s: value.s, - l: value.l, - }); - }} - /> -
-
- - { - const value = props.value; - props.onChange({ - h: value.h, - s: Number(e.target.value), - l: value.l, - }); - }} - /> -
-
- - { - const value = props.value; - props.onChange({ - h: value.h, - s: value.s, - l: Number(e.target.value), - }); - }} - /> -
-
-
- - - )} -
-
- ); -} diff --git a/src/app/[...rest]/deploy/@form.site-config.tsx b/src/app/[...rest]/deploy/@form.site-config.tsx deleted file mode 100644 index 84d5f57..0000000 --- a/src/app/[...rest]/deploy/@form.site-config.tsx +++ /dev/null @@ -1,418 +0,0 @@ -import toast from "react-hot-toast"; -import { z } from "zod"; -import { - ConfigurationCategory, - ConfigurationKeys, - ConfigurationValue, - Schema_App_Configuration, -} from "~/schema"; - -import { Button } from "~/components/ui/button"; -import { Input } from "~/components/ui/input"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "~/components/ui/select"; -import { Separator } from "~/components/ui/separator"; - -import { parseConfigFile } from "~/utils/parseConfigFile"; - -import config from "~/config/gIndex.config"; - -import ConfigInput from "./@form.input-config"; - -type Props = { - state: { - get: z.input; - set: < - T extends ConfigurationCategory = ConfigurationCategory, - K extends ConfigurationKeys = ConfigurationKeys, - >( - category: T, - key: K, - value: ConfigurationValue, - ) => void; - }; - error: { - get: Partial, string>>; - set: >(key: T, value: string) => void; - }; - onReset: (category: ConfigurationCategory) => void; -}; -export default function SiteConfig({ - state: { get, set }, - error, - onReset, -}: Props) { - return ( -
{ - e.preventDefault(); - onReset("site"); - }} - > -
-

Site

-
- - -
-
- - - -
-
-
-
- favicon - {get.site.siteName} -
- -
- favicon - - {get.site - .siteNameTemplate!.replace("%s", "Page Title") - .replace("%t", get.site.siteName)} - -
-
- -
- -
-
- > - key='siteName' - title='Index Site Name' - error={error.get.siteName} - required - > - { - if (error.get.siteName) { - error.set("siteName", ""); - } - set("site", "siteName", e.target.value); - }} - onBlur={async () => { - try { - const value = get.site.siteName; - error.set("siteName", ""); - - if (!value) throw new Error("Site name is required"); - } catch (err) { - const e = err as Error; - error.set("siteName", e.message); - } - }} - /> - - - > - key='siteNameTemplate' - title='Site Name Template' - description={`The template for the site name. - -Usable variables: -%s - Page Title -%t - Site Name`} - error={error.get.siteNameTemplate} - > - { - if (error.get.siteNameTemplate) { - error.set("siteNameTemplate", ""); - } - set("site", "siteNameTemplate", e.target.value); - }} - onBlur={async () => { - try { - const value = get.site.siteNameTemplate; - error.set("siteNameTemplate", ""); - } catch (err) { - const e = err as Error; - error.set("siteNameTemplate", e.message); - } - }} - /> - - - > - key='siteDescription' - title='Site Description' - error={error.get.siteDescription} - required - > - { - if (error.get.siteDescription) { - error.set("siteDescription", ""); - } - set("site", "siteDescription", e.target.value); - }} - onBlur={async () => { - try { - const value = get.site.siteDescription; - error.set("siteDescription", ""); - - if (!value) throw new Error("Site description is required"); - } catch (err) { - const e = err as Error; - error.set("siteDescription", e.message); - } - }} - /> - - -
- > - key='siteAuthor' - title='Site Author' - error={error.get.siteAuthor} - description={`Will be used for metadata, and also affect the footer variable`} - > - { - if (error.get.siteAuthor) { - error.set("siteAuthor", ""); - } - set("site", "siteAuthor", e.target.value); - }} - onBlur={async () => { - try { - const value = get.site.siteAuthor; - error.set("siteAuthor", ""); - } catch (err) { - const e = err as Error; - error.set("siteAuthor", e.message); - } - }} - /> - - - > - key='twitterHandle' - title='Twitter Handle' - error={error.get.twitterHandle} - description={`Will be used for metadata, and also affect the footer variable`} - > - { - if (error.get.twitterHandle) { - error.set("twitterHandle", ""); - } - set("site", "twitterHandle", e.target.value); - }} - onBlur={async () => { - try { - const value = get.site.twitterHandle; - error.set("twitterHandle", ""); - } catch (err) { - const e = err as Error; - error.set("twitterHandle", e.message); - } - }} - /> - -
-
- -
- Opengraph Preview -
- - {(get.site.siteNameTemplate || "%s") - .replace("%s", "Page Title") - .replace("%t", get.site.siteName)} - - - {get.environment.NEXT_PUBLIC_DOMAIN || "http://localhost:3000"} - - - {get.site.siteDescription} - -
-
-
- - - - > - key='privateIndex' - title='Private Index' - description={`Lock the whole site behind a password -Will use the site password set in the "Environment" category`} - error={error.get.privateIndex} - required - > - - - - > - key='showFileExtension' - title='Show File Extension' - description={`Show file extension in file explorer - -e.g. "file.mp4" instead of "file"`} - error={error.get.showFileExtension} - required - > - - -
- - ); -} diff --git a/src/app/[...rest]/deploy/@form.theme.tsx b/src/app/[...rest]/deploy/@form.theme.tsx deleted file mode 100644 index 97cd3cd..0000000 --- a/src/app/[...rest]/deploy/@form.theme.tsx +++ /dev/null @@ -1,235 +0,0 @@ -"use client"; - -import { z } from "zod"; -import { Schema_Theme, ThemeKeys } from "~/schema"; - -import { Input } from "~/components/ui/input"; -import { Separator } from "~/components/ui/separator"; -import { Slider } from "~/components/ui/slider"; - -import { parseThemeValue } from "~/utils/parseConfigFile"; - -import ThemeInput from "./@form.input-theme"; - -type Props = { - currentTheme: "light" | "dark"; - state: { - get: { - light: z.input; - dark: z.input; - }; - set: (theme: "light" | "dark", key: ThemeKeys, value: string) => void; - }; -}; -export default function ThemeForm({ - currentTheme, - state: { get, set }, -}: Props) { - return ( -
- { - set(currentTheme, "background", `${val.h} ${val.s} ${val.l}`); - }} - /> - { - set(currentTheme, "foreground", `${val.h} ${val.s} ${val.l}`); - }} - /> - - - - { - set(currentTheme, "card", `${val.h} ${val.s} ${val.l}`); - }} - /> - { - set(currentTheme, "card-foreground", `${val.h} ${val.s} ${val.l}`); - }} - /> - - { - set(currentTheme, "popover", `${val.h} ${val.s} ${val.l}`); - }} - /> - { - set(currentTheme, "popover-foreground", `${val.h} ${val.s} ${val.l}`); - }} - /> - - - - { - set(currentTheme, "primary", `${val.h} ${val.s} ${val.l}`); - }} - /> - { - set(currentTheme, "primary-foreground", `${val.h} ${val.s} ${val.l}`); - }} - /> - { - set(currentTheme, "secondary", `${val.h} ${val.s} ${val.l}`); - }} - /> - { - set( - currentTheme, - "secondary-foreground", - `${val.h} ${val.s} ${val.l}`, - ); - }} - /> - { - set(currentTheme, "accent", `${val.h} ${val.s} ${val.l}`); - }} - /> - { - set(currentTheme, "accent-foreground", `${val.h} ${val.s} ${val.l}`); - }} - /> - { - set(currentTheme, "muted", `${val.h} ${val.s} ${val.l}`); - }} - /> - { - set(currentTheme, "muted-foreground", `${val.h} ${val.s} ${val.l}`); - }} - /> - { - set(currentTheme, "destructive", `${val.h} ${val.s} ${val.l}`); - }} - /> - { - set( - currentTheme, - "destructive-foreground", - `${val.h} ${val.s} ${val.l}`, - ); - }} - /> - - - - { - set(currentTheme, "border", `${val.h} ${val.s} ${val.l}`); - }} - /> - { - set(currentTheme, "input", `${val.h} ${val.s} ${val.l}`); - }} - /> - { - set(currentTheme, "ring", `${val.h} ${val.s} ${val.l}`); - }} - /> - {}} - > -
- { - const value = val[0] / 16; - set("light", "radius", `${value}rem`); - set("dark", "radius", `${value}rem`); - }} - /> - { - const value = parseFloat(e.currentTarget.value) / 16; - set("light", "radius", `${value}rem`); - set("dark", "radius", `${value}rem`); - }} - type='number' - className='w-16 min-w-0 ' - min={0} - max={24} - /> -
-
-
- ); -} diff --git a/src/app/[...rest]/deploy/@theme-preview.tsx b/src/app/[...rest]/deploy/@theme-preview.tsx deleted file mode 100644 index ea8e9ea..0000000 --- a/src/app/[...rest]/deploy/@theme-preview.tsx +++ /dev/null @@ -1,369 +0,0 @@ -"use client"; - -import nProgress from "nprogress"; -import { CSSProperties, PropsWithChildren } from "react"; -import toast from "react-hot-toast"; -import { z } from "zod"; -import { Schema_Theme } from "~/schema"; - -import FileGrid from "~/app/@file.grid"; -import FileList from "~/app/@file.list"; -import { Button } from "~/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "~/components/ui/card"; -import { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "~/components/ui/dialog"; -import { - Drawer, - DrawerClose, - DrawerContent, - DrawerDescription, - DrawerFooter, - DrawerHeader, - DrawerTitle, - DrawerTrigger, -} from "~/components/ui/drawer"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "~/components/ui/dropdown-menu"; -import { Input } from "~/components/ui/input"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "~/components/ui/popover"; -import { Separator } from "~/components/ui/separator"; -import { - Sheet, - SheetClose, - SheetContent, - SheetDescription, - SheetFooter, - SheetHeader, - SheetTitle, - SheetTrigger, -} from "~/components/ui/sheet"; -import { Textarea } from "~/components/ui/textarea"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "~/components/ui/tooltip"; - -type Props = { - theme: z.input; -}; -export default function ThemePreview({ theme }: Props) { - return ( -
- - - - - - - - - - - - - Title - Description - - - Content - Footer - - - - - -