From 111924789c3cab48eb91819b9745f113dc2acc4e Mon Sep 17 00:00:00 2001 From: radityaharya Date: Mon, 5 Aug 2024 15:16:03 +0000 Subject: [PATCH] chore: refactor 'save as' nodes and playlist node to share the same hooks --- next.config.mjs | 3 - src/components/nodes/Library/Playlist.tsx | 151 ++------------- src/components/nodes/Library/SaveAsAppend.tsx | 167 ++++------------ .../nodes/Library/SaveAsReplace.tsx | 180 ++++-------------- .../nodes/Primitives/PlaylistCommand.tsx | 22 +++ src/hooks/usePlaylistState.ts | 156 +++++++++++++++ 6 files changed, 262 insertions(+), 417 deletions(-) create mode 100644 src/components/nodes/Primitives/PlaylistCommand.tsx create mode 100644 src/hooks/usePlaylistState.ts diff --git a/next.config.mjs b/next.config.mjs index 2ffa386..a649240 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,9 +1,6 @@ await import("./src/env.js"); const config = { - experimental: { - instrumentationHook: true - }, output: process.env.STANDALONE_OUTPUT ? "standalone" : undefined, images: { remotePatterns: [ diff --git a/src/components/nodes/Library/Playlist.tsx b/src/components/nodes/Library/Playlist.tsx index ee3f90d..58f2cf1 100644 --- a/src/components/nodes/Library/Playlist.tsx +++ b/src/components/nodes/Library/Playlist.tsx @@ -39,49 +39,26 @@ import { Separator } from "~/components/ui/separator"; import { CardWithHeader } from "../Primitives/Card"; import InputPrimitive from "../Primitives/Input"; -import * as z from "zod"; - import { Form, FormField, FormItem, FormLabel } from "@/components/ui/form"; import useBasicNodeState from "~/hooks/useBasicNodeState"; +import { usePlaylistState } from "~/hooks/usePlaylistState"; import Debug from "../Primitives/Debug"; -import { PlaylistItem as PlaylistItemPrimitive } from "../Primitives/PlaylistItem"; +import PlaylistCommand from "../Primitives/PlaylistCommand"; + type PlaylistProps = { id: string; // TODO type on playlist data: Workflow.Playlist; }; -const formSchema = z.object({ - playlistId: z.string().min(1, { - message: "Playlist is required.", - }), - limit: z.number().optional(), - offset: z.number().optional(), -}); - -const PlaylistItem = ({ - playlist, - onSelect, -}: { - playlist: Workflow.Playlist; - onSelect: () => void; -}) => ( - - - -); - const PlaylistComponent: React.FC = ({ id, data }) => { - const [open, setOpen] = React.useState(false); - const [selectedPlaylist, setSelectedPlaylist] = - React.useState(data); - const [search, setSearch] = React.useState(""); - const { + open, + setOpen, + selectedPlaylist, + setSelectedPlaylist, + search, + setSearch, state, isValid, targetConnections, @@ -91,109 +68,11 @@ const PlaylistComponent: React.FC = ({ id, data }) => { register, getNodeData, updateNodeData, - } = useBasicNodeState(id, formSchema); - - const { session, userPlaylists, setUserPlaylistsStore } = useStore( - (state) => ({ - session: state.session, - userPlaylists: state.userPlaylists, - setUserPlaylistsStore: state.setUserPlaylists, - }), - ); - - React.useEffect(() => { - if (data) { - form?.setValue("playlistId", data.playlistId); - updateNodeData(id, data); - form?.trigger("playlistId"); - } - }, [data, form, id, updateNodeData]); - - const watch = form!.watch(); - const prevWatchRef = React.useRef(watch); - const prevSelectedPlaylistRef = React.useRef(selectedPlaylist); - - // biome-ignore lint/correctness/useExhaustiveDependencies: - React.useEffect(() => { - if ( - JSON.stringify(prevWatchRef.current) !== JSON.stringify(watch) || - JSON.stringify(prevSelectedPlaylistRef.current) !== - JSON.stringify(selectedPlaylist) - ) { - updateNodeData(id, { - ...watch, - ...selectedPlaylist, - }); - } - prevWatchRef.current = watch; - prevSelectedPlaylistRef.current = selectedPlaylist; - }, [watch, selectedPlaylist, data, id, updateNodeData]); - - // biome-ignore lint/correctness/useExhaustiveDependencies: - React.useEffect(() => { - const searchPlaylist = async () => { - if (search.length > 0) { - try { - const response = await fetch( - `/api/user/${session.user.providerAccountId}/playlists?q=${search}`, - ); - const data = await response.json(); - const newPlaylists = userPlaylists.concat(data); - const dedupedPlaylists = newPlaylists.reduce((acc, current) => { - const x = acc.find( - (item) => item.playlistId === current.playlistId, - ); - if (!x) { - return acc.concat([current]); - } else { - return acc; - } - }, []); - - setUserPlaylistsStore(dedupedPlaylists); - } catch (err) { - console.error(err); - } - } - }; - - const userPlaylistsFetch = async () => { - try { - const response = await fetch( - `/api/user/${session.user.providerAccountId}/playlists`, - ); - const data = await response.json(); - setUserPlaylistsStore(data as any[]); - } catch (err) { - console.error(err); - } - }; - - function setUserPlaylists() { - if (search.length > 0) { - searchPlaylist().catch((err) => { - console.error(err); - }); - } else { - userPlaylistsFetch().catch((err) => { - console.error(err); - }); - } - } - - // debounce({delay: 500}, setUserPlaylists)(); - setUserPlaylists(); - }, [search, session?.user?.providerAccountId, setUserPlaylistsStore]); - - const handleSelect = (playlist) => { - console.info("handle select", playlist); - form?.setValue("playlistId", playlist.playlistId, { - shouldValidate: true, - }); - console.info("data after update", getNodeData(id)); - setSelectedPlaylist(playlist as Workflow.Playlist); - setOpen(false); - }; + session, + userPlaylists, + setUserPlaylistsStore, + handleSelect, + } = usePlaylistState(id, data); return ( = ({ id, data }) => { {userPlaylists.length > 0 ? userPlaylists.map((playlist) => ( - handleSelect(playlist)} diff --git a/src/components/nodes/Library/SaveAsAppend.tsx b/src/components/nodes/Library/SaveAsAppend.tsx index 013191e..96856c8 100644 --- a/src/components/nodes/Library/SaveAsAppend.tsx +++ b/src/components/nodes/Library/SaveAsAppend.tsx @@ -1,8 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ "use client"; -import { Position, useHandleConnections } from "@xyflow/react"; -import React from "react"; +import { Position } from "@xyflow/react"; import NodeHandle from "../Primitives/NodeHandle"; import { ChevronsUpDown } from "lucide-react"; @@ -25,17 +24,14 @@ import { import { ScrollArea } from "@/components/ui/scroll-area"; import Image from "next/image"; -import useStore from "~/app/states/store"; -import { CardWithHeader } from "../Primitives/Card"; - -import { zodResolver } from "@hookform/resolvers/zod"; -import { useForm } from "react-hook-form"; import * as z from "zod"; +import { CardWithHeader } from "../Primitives/Card"; import { Form, FormField, FormItem, FormLabel } from "@/components/ui/form"; +import { usePlaylistState } from "~/hooks/usePlaylistState"; import Debug from "../Primitives/Debug"; -import { PlaylistItem as PlaylistItemPrimitive } from "../Primitives/PlaylistItem"; +import PlaylistCommand from "../Primitives/PlaylistCommand"; type PlaylistProps = { id: string; @@ -48,132 +44,35 @@ const formSchema = z.object({ }), }); -const PlaylistItem = ({ - playlist, - onSelect, -}: { - playlist: Workflow.Playlist; - onSelect: () => void; -}) => ( - - - -); - function SaveAsAppendComponent({ id, data }: PlaylistProps) { - const [open, setOpen] = React.useState(false); - const [selectedPlaylist, setSelectedPlaylist] = - React.useState(data); - const [search, setSearch] = React.useState(""); - - const { session, updateNodeData, userPlaylists, nodes } = useStore( - (state) => ({ - session: state.session, - updateNodeData: state.updateNodeData, - userPlaylists: state.userPlaylists, - nodes: state.nodes, - }), - ); - - const form = useForm({ - resolver: zodResolver(formSchema), - mode: "all", - shouldUnregister: false, - }); - const { formState, register } = form; - - const TargetConnections = useHandleConnections({ - type: "target", - }); - const SourceConnections = useHandleConnections({ - type: "source", - }); - - const watch = form.watch(); - const prevWatchRef = React.useRef(watch); - const prevSelectedPlaylistRef = React.useRef(selectedPlaylist); - - // biome-ignore lint/correctness/useExhaustiveDependencies: - React.useEffect(() => { - if (data) { - form?.setValue("playlistId", data.playlistId); - updateNodeData(id, data); - form?.trigger("playlistId"); - - // Fetch playlist info - fetch(`/api/user/@me/playlist/${data.playlistId}`) - .then((response) => response.json()) - .then((playlist: Workflow.Playlist) => { - setSelectedPlaylist(playlist); - }) - .catch((error) => console.error("Error:", error)); - } - }, []); - - React.useEffect(() => { - if ( - JSON.stringify(prevWatchRef.current) !== JSON.stringify(watch) || - JSON.stringify(prevSelectedPlaylistRef.current) !== - JSON.stringify(selectedPlaylist) - ) { - updateNodeData(id, { - id: watch.playlistId, - ...watch, - ...selectedPlaylist, - }); - } - prevWatchRef.current = watch; - prevSelectedPlaylistRef.current = selectedPlaylist; - }, [id, watch, selectedPlaylist, updateNodeData]); - - React.useEffect(() => { - const userPlaylists = async () => { - try { - const response = await fetch( - `/api/user/${session.user.providerAccountId}/playlists`, - ); - const data = await response.json(); - useStore.setState({ userPlaylists: data }); - } catch (err) { - console.error(err); - } - }; - - // debounce({delay: 500}, setUserPlaylists)(); - userPlaylists() - .then(() => { - console.info("user playlists updated"); - }) - .catch((err) => { - console.error(err); - }); - }, [session.user.providerAccountId]); - - function getNodeData(id: string) { - const node = nodes.find((node) => node.id === id); - return node?.data; - } - - const handleSelect = (playlist) => { - console.info("handle select", playlist); - form.setValue("playlistId", playlist.playlistId, { - shouldValidate: true, - }); - console.info("data after update", getNodeData(id)); - setSelectedPlaylist(playlist as Workflow.Playlist); - setOpen(false); - }; + const { + open, + setOpen, + selectedPlaylist, + setSelectedPlaylist, + search, + setSearch, + state, + isValid, + targetConnections, + sourceConnections, + form, + formState, + register, + getNodeData, + updateNodeData, + session, + userPlaylists, + setUserPlaylistsStore, + handleSelect, + } = usePlaylistState(id, data); return ( -
- console.info(data))}> + + console.info(data))}>
( @@ -243,7 +142,7 @@ function SaveAsAppendComponent({ id, data }: PlaylistProps) { {userPlaylists.length > 0 ? userPlaylists.map((playlist) => ( - handleSelect(playlist)} @@ -293,9 +192,9 @@ function SaveAsAppendComponent({ id, data }: PlaylistProps) { ); diff --git a/src/components/nodes/Library/SaveAsReplace.tsx b/src/components/nodes/Library/SaveAsReplace.tsx index 8aa12ab..83d636d 100644 --- a/src/components/nodes/Library/SaveAsReplace.tsx +++ b/src/components/nodes/Library/SaveAsReplace.tsx @@ -1,12 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ "use client"; - -import { Position, useHandleConnections } from "@xyflow/react"; -import React from "react"; -import NodeHandle from "../Primitives/NodeHandle"; - -import { ChevronsUpDown } from "lucide-react"; - import { Button } from "@/components/ui/button"; import { Command, @@ -16,26 +9,22 @@ import { CommandItem, CommandList, } from "@/components/ui/command"; +import { Form, FormField, FormItem, FormLabel } from "@/components/ui/form"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; - import { ScrollArea } from "@/components/ui/scroll-area"; - +import { Position } from "@xyflow/react"; +import { ChevronsUpDown } from "lucide-react"; import Image from "next/image"; -import useStore from "~/app/states/store"; - -import { CardWithHeader } from "../Primitives/Card"; - -import { zodResolver } from "@hookform/resolvers/zod"; -import { useForm } from "react-hook-form"; import * as z from "zod"; - -import { Form, FormField, FormItem, FormLabel } from "@/components/ui/form"; +import { usePlaylistState } from "~/hooks/usePlaylistState"; +import { CardWithHeader } from "../Primitives/Card"; import Debug from "../Primitives/Debug"; -import { PlaylistItem as PlaylistItemPrimitive } from "../Primitives/PlaylistItem"; +import NodeHandle from "../Primitives/NodeHandle"; +import PlaylistCommand from "../Primitives/PlaylistCommand"; type PlaylistProps = { id: string; @@ -48,132 +37,35 @@ const formSchema = z.object({ }), }); -const PlaylistItem = ({ - playlist, - onSelect, -}: { - playlist: Workflow.Playlist; - onSelect: () => void; -}) => ( - - - -); - function SaveAsReplaceComponent({ id, data }: PlaylistProps) { - const [open, setOpen] = React.useState(false); - const [selectedPlaylist, setSelectedPlaylist] = - React.useState(data); - const [search, setSearch] = React.useState(""); - - const { session, updateNodeData, userPlaylists, nodes } = useStore( - (state) => ({ - session: state.session, - updateNodeData: state.updateNodeData, - userPlaylists: state.userPlaylists, - nodes: state.nodes, - }), - ); - - const form = useForm({ - resolver: zodResolver(formSchema), - mode: "all", - shouldUnregister: false, - }); - const { formState, register } = form; - - const TargetConnections = useHandleConnections({ - type: "target", - }); - const SourceConnections = useHandleConnections({ - type: "source", - }); - - const watch = form.watch(); - const prevWatchRef = React.useRef(watch); - const prevSelectedPlaylistRef = React.useRef(selectedPlaylist); - - // biome-ignore lint/correctness/useExhaustiveDependencies: - React.useEffect(() => { - if (data) { - form?.setValue("playlistId", data.playlistId); - updateNodeData(id, data); - form?.trigger("playlistId"); - - // Fetch playlist info - fetch(`/api/user/@me/playlist/${data.playlistId}`) - .then((response) => response.json()) - .then((playlist: Workflow.Playlist) => { - setSelectedPlaylist(playlist); - }) - .catch((error) => console.error("Error:", error)); - } - }, []); - - React.useEffect(() => { - if ( - JSON.stringify(prevWatchRef.current) !== JSON.stringify(watch) || - JSON.stringify(prevSelectedPlaylistRef.current) !== - JSON.stringify(selectedPlaylist) - ) { - updateNodeData(id, { - id: watch.playlistId, - ...watch, - ...selectedPlaylist, - }); - } - prevWatchRef.current = watch; - prevSelectedPlaylistRef.current = selectedPlaylist; - }, [id, watch, selectedPlaylist, updateNodeData]); - - React.useEffect(() => { - const userPlaylists = async () => { - try { - const response = await fetch( - `/api/user/${session.user.providerAccountId}/playlists`, - ); - const data = await response.json(); - useStore.setState({ userPlaylists: data }); - } catch (err) { - console.error(err); - } - }; - - // debounce({delay: 500}, setUserPlaylists)(); - userPlaylists() - .then(() => { - console.info("user playlists updated"); - }) - .catch((err) => { - console.error(err); - }); - }, [session?.user?.providerAccountId]); - - function getNodeData(id: string) { - const node = nodes.find((node) => node.id === id); - return node?.data; - } - - const handleSelect = (playlist) => { - console.info("handle select", playlist); - form.setValue("playlistId", playlist.playlistId, { - shouldValidate: true, - }); - console.info("data after update", getNodeData(id)); - setSelectedPlaylist(playlist as Workflow.Playlist); - setOpen(false); - }; + const { + open, + setOpen, + selectedPlaylist, + setSelectedPlaylist, + search, + setSearch, + state, + isValid, + targetConnections, + sourceConnections, + form, + formState, + register, + getNodeData, + updateNodeData, + session, + userPlaylists, + setUserPlaylistsStore, + handleSelect, + } = usePlaylistState(id, data); return ( -
- console.info(data))}> + + console.info(data))}>
( @@ -243,7 +135,7 @@ function SaveAsReplaceComponent({ id, data }: PlaylistProps) { {userPlaylists.length > 0 ? userPlaylists.map((playlist) => ( - handleSelect(playlist)} @@ -293,9 +185,9 @@ function SaveAsReplaceComponent({ id, data }: PlaylistProps) { ); diff --git a/src/components/nodes/Primitives/PlaylistCommand.tsx b/src/components/nodes/Primitives/PlaylistCommand.tsx new file mode 100644 index 0000000..170e0b0 --- /dev/null +++ b/src/components/nodes/Primitives/PlaylistCommand.tsx @@ -0,0 +1,22 @@ +import { CommandItem } from "@/components/ui/command"; +import React from "react"; +import { PlaylistItem as PlaylistItemPrimitive } from "../Primitives/PlaylistItem"; +type PlaylistCommandProps = { + playlist: Workflow.Playlist; + onSelect: () => void; +}; + +const PlaylistCommand: React.FC = ({ + playlist, + onSelect, +}) => ( + + + +); + +export default PlaylistCommand; diff --git a/src/hooks/usePlaylistState.ts b/src/hooks/usePlaylistState.ts new file mode 100644 index 0000000..3ec4543 --- /dev/null +++ b/src/hooks/usePlaylistState.ts @@ -0,0 +1,156 @@ +import React from "react"; +import { z } from "zod"; +import useStore from "~/app/states/store"; +import useBasicNodeState from "~/hooks/useBasicNodeState"; + +const formSchema = z.object({ + playlistId: z.string().min(1, { + message: "Playlist is required.", + }), + limit: z.number().optional(), + offset: z.number().optional(), + description: z.string().optional(), +}); + +export const usePlaylistState = (id: string, data: Workflow.Playlist) => { + const [open, setOpen] = React.useState(false); + const [selectedPlaylist, setSelectedPlaylist] = + React.useState(data); + const [search, setSearch] = React.useState(""); + + const { + state, + isValid, + targetConnections, + sourceConnections, + form, + formState, + register, + getNodeData, + updateNodeData, + } = useBasicNodeState(id, formSchema); + + const { session, userPlaylists, setUserPlaylistsStore } = useStore( + (state) => ({ + session: state.session, + userPlaylists: state.userPlaylists, + setUserPlaylistsStore: state.setUserPlaylists, + }), + ); + + React.useEffect(() => { + if (data) { + form?.setValue("playlistId", data.playlistId); + updateNodeData(id, data); + form?.trigger("playlistId"); + } + }, [data, form, id, updateNodeData]); + + const watch = form!.watch(); + const prevWatchRef = React.useRef(watch); + const prevSelectedPlaylistRef = React.useRef(selectedPlaylist); + + // biome-ignore lint/correctness/useExhaustiveDependencies: + React.useEffect(() => { + if ( + JSON.stringify(prevWatchRef.current) !== JSON.stringify(watch) || + JSON.stringify(prevSelectedPlaylistRef.current) !== + JSON.stringify(selectedPlaylist) + ) { + updateNodeData(id, { + ...watch, + ...selectedPlaylist, + }); + } + prevWatchRef.current = watch; + prevSelectedPlaylistRef.current = selectedPlaylist; + }, [watch, selectedPlaylist, data, id, updateNodeData]); + + // biome-ignore lint/correctness/useExhaustiveDependencies: + React.useEffect(() => { + const searchPlaylist = async () => { + if (search.length > 0) { + try { + const response = await fetch( + `/api/user/${session.user.providerAccountId}/playlists?q=${search}`, + ); + const data = await response.json(); + const newPlaylists = userPlaylists.concat(data); + const dedupedPlaylists = newPlaylists.reduce((acc, current) => { + const x = acc.find( + (item) => item.playlistId === current.playlistId, + ); + if (!x) { + return acc.concat([current]); + } else { + return acc; + } + }, []); + + setUserPlaylistsStore(dedupedPlaylists); + } catch (err) { + console.error(err); + } + } + }; + + const userPlaylistsFetch = async () => { + try { + const response = await fetch( + `/api/user/${session.user.providerAccountId}/playlists`, + ); + const data = await response.json(); + setUserPlaylistsStore(data as any[]); + } catch (err) { + console.error(err); + } + }; + + function setUserPlaylists() { + if (search.length > 0) { + searchPlaylist().catch((err) => { + console.error(err); + }); + } else { + userPlaylistsFetch().catch((err) => { + console.error(err); + }); + } + } + + // debounce({delay: 500}, setUserPlaylists)(); + setUserPlaylists(); + }, [search, session?.user?.providerAccountId, setUserPlaylistsStore]); + + const handleSelect = (playlist) => { + console.info("handle select", playlist); + form?.setValue("playlistId", playlist.playlistId, { + shouldValidate: true, + }); + console.info("data after update", getNodeData(id)); + setSelectedPlaylist(playlist as Workflow.Playlist); + setOpen(false); + }; + + return { + open, + setOpen, + selectedPlaylist, + setSelectedPlaylist, + search, + setSearch, + state, + isValid, + targetConnections, + sourceConnections, + form, + formState, + register, + getNodeData, + updateNodeData, + session, + userPlaylists, + setUserPlaylistsStore, + handleSelect, + }; +};