Spaces:
Paused
Paused
Commit
β’
c90a662
1
Parent(s):
40e9c34
added preview
Browse files- src/app/studio/[ownerId]/main.tsx +17 -9
- src/app/studio/[ownerId]/page.tsx +6 -1
- src/components/business/refresh.tsx +2 -0
- src/components/business/tasks/columns.tsx +6 -8
- src/components/business/tasks/video-tasks-queue.tsx +13 -4
- src/components/business/{videoForm.tsx β video-form.tsx} +0 -0
- src/components/business/video-player.tsx +29 -0
- src/server/actions.ts +2 -1
- src/server/base.ts +2 -2
src/app/studio/[ownerId]/main.tsx
CHANGED
@@ -1,18 +1,26 @@
|
|
1 |
-
"use
|
|
|
|
|
2 |
|
3 |
-
import { getTasks } from "@/server"
|
4 |
import { VideoTasksQueue } from "@/components/business/tasks/video-tasks-queue"
|
5 |
import { RefreshStudio } from "@/components/business/refresh"
|
6 |
-
import { VideoForm } from "@/components/business/
|
|
|
|
|
7 |
|
8 |
-
export default
|
9 |
-
const
|
10 |
|
11 |
return (
|
12 |
-
<div className="
|
13 |
-
<
|
14 |
-
|
15 |
-
|
|
|
|
|
|
|
|
|
|
|
16 |
</div>
|
17 |
)
|
18 |
}
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import { useState } from "react"
|
4 |
|
|
|
5 |
import { VideoTasksQueue } from "@/components/business/tasks/video-tasks-queue"
|
6 |
import { RefreshStudio } from "@/components/business/refresh"
|
7 |
+
import { VideoForm } from "@/components/business/video-form"
|
8 |
+
import { VideoTask } from "@/app/types"
|
9 |
+
import { VideoPlayer } from "@/components/business/video-player"
|
10 |
|
11 |
+
export default function Main({ videoTasks }: { videoTasks: VideoTask[] }) {
|
12 |
+
const [selectedVideo, selectVideo] = useState<VideoTask>()
|
13 |
|
14 |
return (
|
15 |
+
<div className="flex flex-col md:flex-row">
|
16 |
+
<div className="h-full flex flex-col space-y-4 w-full md:w-[600px] px-4 py-8">
|
17 |
+
<VideoForm />
|
18 |
+
<VideoTasksQueue videoTasks={videoTasks} onSelectVideo={selectVideo} />
|
19 |
+
<RefreshStudio />
|
20 |
+
</div>
|
21 |
+
<div className="flex flex-col w-auto">
|
22 |
+
<VideoPlayer video={selectedVideo} />
|
23 |
+
</div>
|
24 |
</div>
|
25 |
)
|
26 |
}
|
src/app/studio/[ownerId]/page.tsx
CHANGED
@@ -1,15 +1,20 @@
|
|
|
|
|
|
1 |
import Head from "next/head"
|
2 |
|
3 |
import Main from "./main"
|
|
|
4 |
|
5 |
export default async function StudioPage({ params: { ownerId } }: { params: { ownerId: string }}) {
|
|
|
|
|
6 |
return (
|
7 |
<div>
|
8 |
<Head>
|
9 |
<meta name="viewport" content="width=device-width, initial-scale=0.86, maximum-scale=5.0, minimum-scale=0.86" />
|
10 |
</Head>
|
11 |
<main className="dark fixed inset-0 flex flex-col items-center bg-stone-900 text-stone-10 overflow-y-scroll">
|
12 |
-
<Main
|
13 |
</main>
|
14 |
</div>
|
15 |
)
|
|
|
1 |
+
"use server"
|
2 |
+
|
3 |
import Head from "next/head"
|
4 |
|
5 |
import Main from "./main"
|
6 |
+
import { getTasks } from "@/server"
|
7 |
|
8 |
export default async function StudioPage({ params: { ownerId } }: { params: { ownerId: string }}) {
|
9 |
+
const videoTasks = await getTasks(ownerId)
|
10 |
+
|
11 |
return (
|
12 |
<div>
|
13 |
<Head>
|
14 |
<meta name="viewport" content="width=device-width, initial-scale=0.86, maximum-scale=5.0, minimum-scale=0.86" />
|
15 |
</Head>
|
16 |
<main className="dark fixed inset-0 flex flex-col items-center bg-stone-900 text-stone-10 overflow-y-scroll">
|
17 |
+
<Main videoTasks={videoTasks} />
|
18 |
</main>
|
19 |
</div>
|
20 |
)
|
src/components/business/refresh.tsx
CHANGED
@@ -9,6 +9,7 @@ export function RefreshStudio() {
|
|
9 |
const pathname = usePathname()
|
10 |
const [isPending, startTransition] = useTransition()
|
11 |
|
|
|
12 |
useEffect(() => {
|
13 |
const slug = `${pathname.split("/").pop()}`
|
14 |
setInterval(() => {
|
@@ -22,6 +23,7 @@ export function RefreshStudio() {
|
|
22 |
})
|
23 |
}, 2000)
|
24 |
}, [])
|
|
|
25 |
|
26 |
// TODO we could display a spinner here
|
27 |
return <></>
|
|
|
9 |
const pathname = usePathname()
|
10 |
const [isPending, startTransition] = useTransition()
|
11 |
|
12 |
+
/*
|
13 |
useEffect(() => {
|
14 |
const slug = `${pathname.split("/").pop()}`
|
15 |
setInterval(() => {
|
|
|
23 |
})
|
24 |
}, 2000)
|
25 |
}, [])
|
26 |
+
*/
|
27 |
|
28 |
// TODO we could display a spinner here
|
29 |
return <></>
|
src/components/business/tasks/columns.tsx
CHANGED
@@ -32,12 +32,10 @@ export const columns: ColumnDef<VideoTask>[] = [
|
|
32 |
},
|
33 |
{
|
34 |
accessorKey: "id",
|
35 |
-
header: ({ column }) =>
|
36 |
-
|
37 |
-
),
|
38 |
-
cell: ({ row }) => <div className="w-[80px]">{`${row.getValue("id") || ''}`.split("-")[0]}..</div>,
|
39 |
enableSorting: false,
|
40 |
-
enableHiding:
|
41 |
},
|
42 |
{
|
43 |
accessorKey: "videoPrompt",
|
@@ -63,7 +61,7 @@ export const columns: ColumnDef<VideoTask>[] = [
|
|
63 |
const progress = Number(row.getValue("progressPercent") || 0)
|
64 |
|
65 |
return (
|
66 |
-
<div className="flex w-[
|
67 |
<span>{progress}%</span>
|
68 |
</div>
|
69 |
)
|
@@ -77,12 +75,12 @@ export const columns: ColumnDef<VideoTask>[] = [
|
|
77 |
header: ({ column }) => (
|
78 |
null // no header
|
79 |
),
|
80 |
-
cell: ({ row }) => <div className="w-[
|
81 |
<a
|
82 |
className="hover:underline cursor-pointer"
|
83 |
target="_blank"
|
84 |
href={`${process.env.NEXT_PUBLIC_DOWNLOAD_URL}/${row.getValue("fileName")}`}>
|
85 |
-
<video src={`${process.env.NEXT_PUBLIC_DOWNLOAD_URL}/${row.getValue("fileName")}?progress=${row.getValue("progressPercent") || 0}`} muted
|
86 |
</a>
|
87 |
</div>,
|
88 |
enableSorting: false,
|
|
|
32 |
},
|
33 |
{
|
34 |
accessorKey: "id",
|
35 |
+
header: ({ column }) => null,
|
36 |
+
cell: ({ row }) => null,
|
|
|
|
|
37 |
enableSorting: false,
|
38 |
+
enableHiding: true,
|
39 |
},
|
40 |
{
|
41 |
accessorKey: "videoPrompt",
|
|
|
61 |
const progress = Number(row.getValue("progressPercent") || 0)
|
62 |
|
63 |
return (
|
64 |
+
<div className="flex w-[30px] items-center">
|
65 |
<span>{progress}%</span>
|
66 |
</div>
|
67 |
)
|
|
|
75 |
header: ({ column }) => (
|
76 |
null // no header
|
77 |
),
|
78 |
+
cell: ({ row }) => <div className="w-[120px]">
|
79 |
<a
|
80 |
className="hover:underline cursor-pointer"
|
81 |
target="_blank"
|
82 |
href={`${process.env.NEXT_PUBLIC_DOWNLOAD_URL}/${row.getValue("fileName")}`}>
|
83 |
+
<video src={`${process.env.NEXT_PUBLIC_DOWNLOAD_URL}/${row.getValue("fileName")}?progress=${row.getValue("progressPercent") || 0}`} muted />
|
84 |
</a>
|
85 |
</div>,
|
86 |
enableSorting: false,
|
src/components/business/tasks/video-tasks-queue.tsx
CHANGED
@@ -29,9 +29,11 @@ import { VideoTask } from "@/app/types"
|
|
29 |
import { useState } from "react"
|
30 |
|
31 |
export function VideoTasksQueue({
|
32 |
-
|
|
|
33 |
}: {
|
34 |
-
|
|
|
35 |
}) {
|
36 |
const [rowSelection, setRowSelection] = useState({})
|
37 |
const [columnVisibility, setColumnVisibility] =
|
@@ -42,7 +44,7 @@ export function VideoTasksQueue({
|
|
42 |
const [sorting, setSorting] = useState<SortingState>([])
|
43 |
|
44 |
const table = useReactTable({
|
45 |
-
data:
|
46 |
columns: columns as ColumnDef<VideoTask, any>[],
|
47 |
state: {
|
48 |
sorting,
|
@@ -92,7 +94,14 @@ export function VideoTasksQueue({
|
|
92 |
data-state={row.getIsSelected() && "selected"}
|
93 |
>
|
94 |
{row.getVisibleCells().map((cell) => (
|
95 |
-
<TableCell key={cell.id}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
96 |
{flexRender(
|
97 |
cell.column.columnDef.cell,
|
98 |
cell.getContext()
|
|
|
29 |
import { useState } from "react"
|
30 |
|
31 |
export function VideoTasksQueue({
|
32 |
+
videoTasks = [],
|
33 |
+
onSelectVideo,
|
34 |
}: {
|
35 |
+
videoTasks: VideoTask[]
|
36 |
+
onSelectVideo: (task: VideoTask) => void
|
37 |
}) {
|
38 |
const [rowSelection, setRowSelection] = useState({})
|
39 |
const [columnVisibility, setColumnVisibility] =
|
|
|
44 |
const [sorting, setSorting] = useState<SortingState>([])
|
45 |
|
46 |
const table = useReactTable({
|
47 |
+
data: videoTasks,
|
48 |
columns: columns as ColumnDef<VideoTask, any>[],
|
49 |
state: {
|
50 |
sorting,
|
|
|
94 |
data-state={row.getIsSelected() && "selected"}
|
95 |
>
|
96 |
{row.getVisibleCells().map((cell) => (
|
97 |
+
<TableCell key={cell.id}
|
98 |
+
onClick={() => {
|
99 |
+
const videoId = `${row.getValue("id") || ""}`
|
100 |
+
const video = videoTasks.find(({ id }) => id === videoId)
|
101 |
+
if (video) {
|
102 |
+
onSelectVideo(video)
|
103 |
+
}
|
104 |
+
}}>
|
105 |
{flexRender(
|
106 |
cell.column.columnDef.cell,
|
107 |
cell.getContext()
|
src/components/business/{videoForm.tsx β video-form.tsx}
RENAMED
File without changes
|
src/components/business/video-player.tsx
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import { VideoTask } from "@/app/types"
|
4 |
+
|
5 |
+
export const VideoPlayer = ({ video }: { video?: VideoTask }) => {
|
6 |
+
|
7 |
+
if (typeof video === "undefined") {
|
8 |
+
return <p>No video to display</p>
|
9 |
+
}
|
10 |
+
|
11 |
+
return (
|
12 |
+
<div className="w-full py-8 px-2">
|
13 |
+
<video
|
14 |
+
src={`${
|
15 |
+
process.env.NEXT_PUBLIC_DOWNLOAD_URL
|
16 |
+
}/${
|
17 |
+
video.fileName
|
18 |
+
}?progress=${
|
19 |
+
video.progressPercent
|
20 |
+
}`}
|
21 |
+
muted
|
22 |
+
autoPlay
|
23 |
+
loop
|
24 |
+
controls
|
25 |
+
className="w-full rounded-md overflow-hidden"
|
26 |
+
/>
|
27 |
+
</div>
|
28 |
+
)
|
29 |
+
}
|
src/server/actions.ts
CHANGED
@@ -6,11 +6,12 @@ import { submitNewTask } from "."
|
|
6 |
export async function formSubmit(formData: FormData) {
|
7 |
|
8 |
const ownerId = `${formData.get("ownerId") || ""}`
|
|
|
9 |
await submitNewTask({
|
10 |
prompt: `${formData.get("prompt") || ""}`,
|
11 |
ownerId,
|
12 |
})
|
13 |
-
|
14 |
// for doc see https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions
|
15 |
revalidatePath(`/studio/${ownerId}`)
|
16 |
}
|
|
|
6 |
export async function formSubmit(formData: FormData) {
|
7 |
|
8 |
const ownerId = `${formData.get("ownerId") || ""}`
|
9 |
+
console.log('submitting to ', ownerId)
|
10 |
await submitNewTask({
|
11 |
prompt: `${formData.get("prompt") || ""}`,
|
12 |
ownerId,
|
13 |
})
|
14 |
+
console.log('calling revalidate', ownerId)
|
15 |
// for doc see https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions
|
16 |
revalidatePath(`/studio/${ownerId}`)
|
17 |
}
|
src/server/base.ts
CHANGED
@@ -45,9 +45,9 @@ export const post = async <S, T>(path: string = '', payload: S, defaultValue: T)
|
|
45 |
Authorization: `Bearer ${process.env.VC_SECRET_ACCESS_TOKEN}`,
|
46 |
},
|
47 |
body: JSON.stringify(payload),
|
48 |
-
cache: 'no-store',
|
49 |
// we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
|
50 |
-
|
51 |
})
|
52 |
// The return value is *not* serialized
|
53 |
// You can return Date, Map, Set, etc.
|
|
|
45 |
Authorization: `Bearer ${process.env.VC_SECRET_ACCESS_TOKEN}`,
|
46 |
},
|
47 |
body: JSON.stringify(payload),
|
48 |
+
// cache: 'no-store',
|
49 |
// we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
|
50 |
+
next: { revalidate: 1 }
|
51 |
})
|
52 |
// The return value is *not* serialized
|
53 |
// You can return Date, Map, Set, etc.
|