uXnIx^hYKAN;bfxs|3)i3Mj#{#5kdqke-aHHErq)}#nwL(`qCEOo@Y95vw
zLMDbS&E7&Jvuo$L!i*KS^p{gH9~UO9BD1S7Nrm%`DH3?f#vXS4(eA7_;SOUQwu&Tr
zc$6{zikMF#1BpIXq`Faos%HCme=+N6BzBf5yY`x;QJHG#wsK*UGdfT!yyR&iYadMp
zb_q1^MPD6*#XK^+$i^h>b6>ZO_kXzTK!<0wP&==)RP(SI#T8|){ene7hDXotcbyr$
zD|wMKfb_hVgwPIK7J^APzPH~+D!0xWT+6T=WWEb-n6=NJXSZBU1M)$in
zyZrUogQj-zKy^+4y^9ALU*1Z4l9e{$TguIh&wJY|)10iFt(5v7C4$c`)xIGIO-c#2
z)@Kizo+SdcW6l#(XY_?*9^ZM})WNS1Q+KaSslcyhX$i8vrWs7#l7S}wAr31|n}oMZ
zw*j2@I>I&m?vSY*OHKd!Jn`Eh(=nEn$bZKRsD%Fegb??>fbc}7QE9Zi6PxGgLSsWIZW7%a1{80xRrvimc
zj`C<%50b)e8SFSPqyN+5>Q?e)8ZjM@nz#y#lm$oK+n%$zO3g1+cj{4}_Z<}V@_oll
zrwW0JWjY>n$3bsc2dlf5wl|Lzsx-jqxC9-nh8#03S1W&Ux9faFJ?!97~3JCe(NQ`aDmL
zY-I9!_Z~N2-o$J=dBP1@G5Ac5_i|Og$#?kypXa2BsmNfNcgi%YNcEol!_7aXz3;P4
zN3!j_V)^19=9!9YhD%PnX%#N;OQQ-S8!YpAd7+V47I|89+8k0ETd>dpI{HlS!|ygM
z#oOPBan+r}`To)#?6|3gibB>$(%7G%aE@8s!kHb#Co@e*73)hTxO?WO>t>k&<6O>_j
zW?g*$;fZB(heK+qA}XHgS0xY#m-WARV%GlYHt}suTQ+SxI`p}lS`7x5n_6Ve>sM6G
zvVbz?qm1{=zqIS82Y0n}<0|AGv!e=coH`?_Anzd3%5ED*s$3o8*n#edV?ni^qSw!(
z7RUEU6N(EY)EaluPUC{JdPL7@^%z;%!&wP>trUg$rP-tY#w{o4@Xi3JMFs4`xnhGZ
zx&1NWi_{~p-2c*-SlAoWXD)e6eAPgas&iQ)G>lfv9(-GV?;RwsCoJ(4`#;}z9#5pf
zRRbm5H-@9KkJn!@;A$Oh7F7+jid{pK=!|DBCfwF*Zo~Z7rm)7uR|}MgT~yMY)Z1G=
z_V)d=N=B{3uQ@loNXy)p3fA~(qv>BQ&@}tVXgBp*MKjh^3zYF@oQ@B~mkf69+q-va
opDt+wGAi5~`1$#aPkszs^qO7Xh^!g;eh%!pJtMAs#2tD6KdPj*_5c6?
diff --git a/package.json b/package.json
index a6d5cad..d810455 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "next-gdrive-index",
- "version": "2.0.4",
+ "version": "2.4.2",
"private": true,
"scripts": {
"dev": "next dev -p 3000",
@@ -10,7 +10,8 @@
"lint": "next lint",
"lint:fix": "next lint --fix",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json}\" --cache",
- "format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json}\" --cache"
+ "format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json}\" --cache",
+ "cli": "node ./scripts/cli.mjs"
},
"dependencies": {
"@hookform/resolvers": "^3.10.0",
@@ -88,12 +89,16 @@
"@typescript-eslint/eslint-plugin": "^8.1.0",
"@typescript-eslint/parser": "^8.1.0",
"autoprefixer": "^10.4.19",
+ "chalk": "^5.4.1",
+ "commander": "^13.1.0",
"encoding": "^0.1.13",
"eslint": "^8.57.0",
"eslint-config-next": "^15.0.1",
+ "ora": "^8.2.0",
"postcss": "^8.4.38",
"prettier": "3.0.0",
"prettier-plugin-tailwindcss": "0.5.12",
+ "prompts": "^2.4.2",
"tailwindcss": "^3.4.1",
"typescript": "^5.5.3"
}
diff --git a/src/config/gIndex.config.ts b/src/config/gIndex.config.ts
index d0a13ae..2a4f994 100644
--- a/src/config/gIndex.config.ts
+++ b/src/config/gIndex.config.ts
@@ -8,7 +8,7 @@ const config: z.input = {
* If possible, please don't change this value
* Even if you're creating a PR, just let me change it myself
*/
- version: "2.0.4",
+ version: "2.4.2",
/**
* Base path of the app, used for generating links
*
@@ -27,7 +27,7 @@ const config: z.input = {
*
* @default false
*/
- showGuideButton: true,
+ showGuideButton: false,
/**
* How long the cache will be stored in the browser
diff --git a/src/lib/configurationHelper.ts b/src/lib/configurationHelper.ts
index 42bac5a..5d5c0e8 100644
--- a/src/lib/configurationHelper.ts
+++ b/src/lib/configurationHelper.ts
@@ -13,7 +13,7 @@ import config from "~/config/gIndex.config";
export const versionExpectMap: Record<"v1" | "v2" | "latest", string[]> = {
v1: ["1.0.0", "1.0.1", "1.0.2", "1.0.3"],
v2: ["2.0.0", "2.0.1", "2.0.2", "2.0.3"],
- latest: ["2.0.4"],
+ latest: ["2.0.4", "2.4.0", "2.4.1", "2.4.2"],
};
export type PickFileResponse =
| {
@@ -166,7 +166,7 @@ export const initialConfiguration: z.input = {
};
export const configurationTemplate = `import { type z } from "zod";
-import { BASE_URL } from "~/constant";
+import { BASE_URL, IS_DEV } from "~/constant";
import { type Schema_Config } from "~/types/schema";
@@ -184,7 +184,7 @@ const config: z.input = {
* @default process.env.NEXT_PUBLIC_DOMAIN
* @fallback process.env.NEXT_PUBLIC_VERCEL_URL
*/
- basePath: \`https://\${BASE_URL}\`,
+ basePath: IS_DEV ? "http://localhost:3000" : \`https://\${BASE_URL}\`,
/**
* Show deploy guide dropdown on navbar
From 18be794b146aeb3e7c3eb8c42d3b582202d327b0 Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Mon, 10 Mar 2025 21:55:27 +0700
Subject: [PATCH 04/10] feat/playlist: Move playlist to media player instead of
card
---
src/components/preview/PreviewLayout.tsx | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/components/preview/PreviewLayout.tsx b/src/components/preview/PreviewLayout.tsx
index 9edae28..be3de26 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, CardFooter, CardHeader, CardTitle } from "~/components/ui/card";
+import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import { type getFileType } from "~/lib/previewHelper";
import { cn } from "~/lib/utils";
@@ -23,8 +23,6 @@ import { type Schema_File } from "~/types/schema";
import config from "config";
-import MediaPlaylistLayout from "./MediaPlaylistLayout";
-
type Props = {
data: z.infer;
fileType: "unknown" | ReturnType;
@@ -32,7 +30,7 @@ type Props = {
playlist: z.infer[];
paths: string[];
};
-export default function PreviewLayout({ data, paths, fileType, token, playlist }: Props) {
+export default function PreviewLayout({ data, fileType, token, playlist }: Props) {
const [view, setView] = useState<"markdown" | "raw">("markdown");
const PreviewComponent = useMemo(() => {
switch (fileType) {
@@ -44,6 +42,7 @@ export default function PreviewLayout({ data, paths, fileType, token, playlist }
);
case "code":
@@ -82,7 +81,7 @@ export default function PreviewLayout({ data, paths, fileType, token, playlist }
default:
return ;
}
- }, [fileType, data, view, token]);
+ }, [fileType, data, view, token, playlist]);
return (
-
+ {/*
-
+ */}
Date: Mon, 10 Mar 2025 21:55:55 +0700
Subject: [PATCH 05/10] feat/playlist: Embed playlist to media player
---
src/components/preview/Media.tsx | 229 ++++++++++++++++++++++---------
1 file changed, 165 insertions(+), 64 deletions(-)
diff --git a/src/components/preview/Media.tsx b/src/components/preview/Media.tsx
index a9a1213..a967a00 100644
--- a/src/components/preview/Media.tsx
+++ b/src/components/preview/Media.tsx
@@ -21,6 +21,7 @@ import "@vidstack/react/player/styles/default/layouts/audio.css";
import "@vidstack/react/player/styles/default/layouts/video.css";
import "@vidstack/react/player/styles/default/theme.css";
import { ChevronLeft, ChevronRight } from "lucide-react";
+import Link from "next/link";
import { useCallback, useRef, useState } from "react";
import { type z } from "zod";
@@ -28,7 +29,8 @@ import { PageLoader } from "~/components/layout";
import Icon from "~/components/ui/icon";
import useLoading from "~/hooks/useLoading";
-import { MediaPlayerIcons } from "~/lib/previewHelper";
+import { MediaPlayerIcons, getPreviewIcon } from "~/lib/previewHelper";
+import { cn, formatDate } from "~/lib/utils";
import { type Schema_File } from "~/types/schema";
@@ -37,8 +39,9 @@ import "~/styles/vidstack.css";
type Props = {
file: z.infer;
type: "video" | "audio";
+ playlist: z.infer[];
};
-export default function PreviewMedia({ file, type }: Props) {
+export default function PreviewMedia({ file, type, playlist }: Props) {
const player = useRef(null);
const [canPlay, setCanPlay] = useState(false);
@@ -85,7 +88,7 @@ export default function PreviewMedia({ file, type }: Props) {
seekBackwardButton: null,
seekForwardButton: null,
playButton: (
-
+
{canPlay ? (
@@ -144,6 +147,12 @@ export default function PreviewMedia({ file, type }: Props) {
+
+
),
+ beforeSettingsMenu: (
+
+ ),
settingsMenu: (
-
-
-
-
-
- {/* Loop */}
-
-
-
-
- Loop
- {isLoop ? "On" : "Off"}
-
-
-
-
- setIsLoop(true)}
+ <>
+
+
+
+
+
+ {/* Loop */}
+
+
+
+
+ Loop
+ {isLoop ? "On" : "Off"}
+
+
+
+
-
- On
-
- setIsLoop(false)}
- >
-
- Off
-
-
-
-
+ setIsLoop(true)}
+ >
+
+ On
+
+ setIsLoop(false)}
+ >
+
+ Off
+
+
+
+
- {/* Playback Speed */}
-
+ {/* Playback Speed */}
+
- {/* Audio Gain */}
-
-
-
+ {/* Audio Gain */}
+
+
+
+ >
),
}}
/>
@@ -392,3 +409,87 @@ function AudioGain() {
);
}
+function PlaylistMenu({
+ playlist,
+ file,
+ placement = "top end",
+}: {
+ playlist: z.infer
[];
+ file: z.infer;
+ placement?: Menu.ItemsProps["placement"];
+}) {
+ return (
+
+
+
+
+
+ {playlist.map((item) => {
+ const isCurrent = `${item.name}#${item.size}` === `${file.name}#${file.size}`;
+
+ const Wrapper = ({ children, className }: React.PropsWithChildren<{ className?: string }>) =>
+ isCurrent ? (
+
+ {children}
+
+ ) : (
+
+ {children}
+
+ );
+
+ return (
+
+ {/* Thumbnail */}
+
+ {item.mimeType.includes("video") ? (
+

+ ) : (
+
+
+
+ )}
+
+
+
+
{item.name}
+
{formatDate(item.modifiedTime)}
+
+
+ );
+ })}
+
+
+ );
+}
From 4704211edeb1bb062292d8b783dcc0ce44fdd9e6 Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Mon, 10 Mar 2025 21:56:15 +0700
Subject: [PATCH 06/10] fix: Reload preview cause to submit form
---
src/components/internal/ConfiguratorPage.Site.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/internal/ConfiguratorPage.Site.tsx b/src/components/internal/ConfiguratorPage.Site.tsx
index 0bd68eb..57f70b1 100644
--- a/src/components/internal/ConfiguratorPage.Site.tsx
+++ b/src/components/internal/ConfiguratorPage.Site.tsx
@@ -906,6 +906,7 @@ function FooterField({ form, onResetField }: FormProps) {
onClick={() => {
setContent(formatFooterContent(form.watch("site.footer"), form.getValues("site")));
}}
+ type='button'
>
Reload Preview
From 54e8c2a463a9a4856110a58884aeebd73e5ce2c3 Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Mon, 10 Mar 2025 21:56:33 +0700
Subject: [PATCH 07/10] other: Remove test api route
---
src/app/api/test/fileSize/route.ts | 111 -----------------------------
1 file changed, 111 deletions(-)
delete 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
deleted file mode 100644
index 2f5ad7f..0000000
--- a/src/app/api/test/fileSize/route.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-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 7b3b555ad42e31e9c54a51af321f93dcabafd7a0 Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Mon, 10 Mar 2025 21:57:09 +0700
Subject: [PATCH 08/10] other: Add root & shared drive id check on dev server
---
src/app/api/internal/check/route.ts | 32 +++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
create mode 100644 src/app/api/internal/check/route.ts
diff --git a/src/app/api/internal/check/route.ts b/src/app/api/internal/check/route.ts
new file mode 100644
index 0000000..1e772c4
--- /dev/null
+++ b/src/app/api/internal/check/route.ts
@@ -0,0 +1,32 @@
+import { NextResponse } from "next/server";
+
+import { encryptionService } from "~/lib/utils.server";
+
+import config from "~/config/gIndex.config";
+
+export const dynamic = "force-dynamic";
+
+export async function GET() {
+ try {
+ if (process.env.NODE_ENV !== "development") {
+ throw new Error("This route is only available in development environment");
+ }
+
+ const rootId = await encryptionService.decrypt(config.apiConfig.rootFolder);
+ const sharedDriveId = config.apiConfig.sharedDrive
+ ? await encryptionService.decrypt(config.apiConfig.sharedDrive)
+ : undefined;
+
+ return NextResponse.json(
+ {
+ rootId,
+ sharedDriveId,
+ },
+ { status: 200 },
+ );
+ } catch (error) {
+ const e = error as Error;
+ console.error(e);
+ return new Response(e.message, { status: 500 });
+ }
+}
From 26334ef66b2192c133bedcf70624d0a4051e9f47 Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Mon, 10 Mar 2025 22:08:28 +0700
Subject: [PATCH 09/10] fix: `showGuideButton` on demo site
---
src/config/gIndex.config.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/config/gIndex.config.ts b/src/config/gIndex.config.ts
index 2a4f994..dab50a8 100644
--- a/src/config/gIndex.config.ts
+++ b/src/config/gIndex.config.ts
@@ -27,7 +27,7 @@ const config: z.input = {
*
* @default false
*/
- showGuideButton: false,
+ showGuideButton: true,
/**
* How long the cache will be stored in the browser
From 644fbd42dfe63cc3bad1880b2284deb13b0815ab Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Mon, 10 Mar 2025 22:09:01 +0700
Subject: [PATCH 10/10] fix: Add `Fragment` to `showGuideButton` conditional
rendering
---
src/components/layout/Navbar.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx
index 7a3681b..f6858f0 100644
--- a/src/components/layout/Navbar.tsx
+++ b/src/components/layout/Navbar.tsx
@@ -256,7 +256,7 @@ export default function Navbar() {
className='mx-2 my-auto h-6'
/>
- {!!config.showGuideButton && (
+ {!!config.showGuideButton ? (
+ ) : (
+ <>>
)}