Folders are working aswell!!

This commit is contained in:
Geert Rademakes 2025-04-24 23:48:23 +02:00
parent 3a13c24301
commit 9e32aa0a99
2 changed files with 231 additions and 21 deletions

View File

@ -251,6 +251,74 @@ export default function RekordboxReader() {
}
};
const handleCreateFolder = async (name: string) => {
const newFolder: PlaylistNode = {
id: uuidv4(),
name,
type: 'folder',
children: [],
};
const updatedPlaylists = [...playlists, newFolder];
const savedPlaylists = await api.savePlaylists(updatedPlaylists);
setPlaylists(savedPlaylists);
};
const handleMovePlaylist = async (playlistName: string, targetFolderName: string | null) => {
let updatedPlaylists = [...playlists];
let playlistToMove: PlaylistNode | null = null;
// Helper function to remove playlist from its current location
const removePlaylist = (nodes: PlaylistNode[]): PlaylistNode[] => {
return nodes.reduce((acc: PlaylistNode[], node) => {
if (node.name === playlistName) {
playlistToMove = node;
return acc;
}
if (node.type === 'folder' && node.children) {
return [...acc, {
...node,
children: removePlaylist(node.children)
}];
}
return [...acc, node];
}, []);
};
// First, remove the playlist from its current location
updatedPlaylists = removePlaylist(updatedPlaylists);
if (!playlistToMove) return; // Playlist not found
if (targetFolderName === null) {
// Move to root level
updatedPlaylists.push(playlistToMove);
} else {
// Move to target folder
const addToFolder = (nodes: PlaylistNode[]): PlaylistNode[] => {
return nodes.map(node => {
if (node.name === targetFolderName && node.type === 'folder') {
return {
...node,
children: [...(node.children || []), playlistToMove!]
};
}
if (node.type === 'folder' && node.children) {
return {
...node,
children: addToFolder(node.children)
};
}
return node;
});
};
updatedPlaylists = addToFolder(updatedPlaylists);
}
const savedPlaylists = await api.savePlaylists(updatedPlaylists);
setPlaylists(savedPlaylists);
};
const displayedSongs = currentPlaylist === "All Songs"
? songs
: songs.filter((song: Song) => {
@ -322,6 +390,8 @@ export default function RekordboxReader() {
if (isMobile) onClose();
}}
onPlaylistDelete={handlePlaylistDelete}
onFolderCreate={handleCreateFolder}
onPlaylistMove={handleMovePlaylist}
/>
);

View File

@ -19,9 +19,12 @@ import {
MenuItem,
IconButton,
Collapse,
MenuDivider,
MenuGroup,
useToast
} from "@chakra-ui/react";
import { ChevronDownIcon, DeleteIcon, ChevronRightIcon } from "@chakra-ui/icons";
import React, { useState } from "react";
import { ChevronDownIcon, DeleteIcon, ChevronRightIcon, AddIcon, ViewIcon } from "@chakra-ui/icons";
import React, { useState, useCallback } from "react";
import { PlaylistNode } from "../types/interfaces";
interface PlaylistManagerProps {
@ -30,6 +33,8 @@ interface PlaylistManagerProps {
onPlaylistCreate: (name: string) => void;
onPlaylistSelect: (name: string | null) => void;
onPlaylistDelete: (name: string) => void;
onFolderCreate: (name: string) => void;
onPlaylistMove: (playlistName: string, targetFolderName: string | null) => void;
}
const getButtonStyles = (isSelected: boolean) => ({
@ -57,6 +62,8 @@ interface PlaylistItemProps {
selectedItem: string | null;
onPlaylistSelect: (name: string | null) => void;
onPlaylistDelete: (name: string) => void;
onPlaylistMove: (playlistName: string, targetFolderName: string | null) => void;
allFolders: { name: string }[];
}
const PlaylistItem: React.FC<PlaylistItemProps> = ({
@ -65,9 +72,11 @@ const PlaylistItem: React.FC<PlaylistItemProps> = ({
selectedItem,
onPlaylistSelect,
onPlaylistDelete,
onPlaylistMove,
allFolders,
}) => {
const [isOpen, setIsOpen] = useState(true);
const indent = level * 16; // 16px indent per level
const indent = level * 20; // Increased indent per level
if (node.type === 'folder') {
return (
@ -98,6 +107,8 @@ const PlaylistItem: React.FC<PlaylistItemProps> = ({
selectedItem={selectedItem}
onPlaylistSelect={onPlaylistSelect}
onPlaylistDelete={onPlaylistDelete}
onPlaylistMove={onPlaylistMove}
allFolders={allFolders}
/>
))}
</VStack>
@ -127,6 +138,27 @@ const PlaylistItem: React.FC<PlaylistItemProps> = ({
_hover={{ color: "white", bg: "whiteAlpha.200" }}
/>
<MenuList bg="gray.800" borderColor="gray.700">
<MenuGroup title="Move to folder">
<MenuItem
bg="gray.800"
_hover={{ bg: "gray.700" }}
onClick={() => onPlaylistMove(node.name, null)}
>
Root level
</MenuItem>
<MenuDivider />
{allFolders.map((folder) => (
<MenuItem
key={folder.name}
bg="gray.800"
_hover={{ bg: "gray.700" }}
onClick={() => onPlaylistMove(node.name, folder.name)}
>
{folder.name}
</MenuItem>
))}
</MenuGroup>
<MenuDivider />
<MenuItem
bg="gray.800"
color="red.300"
@ -148,15 +180,43 @@ export const PlaylistManager: React.FC<PlaylistManagerProps> = ({
onPlaylistCreate,
onPlaylistSelect,
onPlaylistDelete,
onFolderCreate,
onPlaylistMove,
}) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const { isOpen: isPlaylistModalOpen, onOpen: onPlaylistModalOpen, onClose: onPlaylistModalClose } = useDisclosure();
const { isOpen: isFolderModalOpen, onOpen: onFolderModalOpen, onClose: onFolderModalClose } = useDisclosure();
const [newPlaylistName, setNewPlaylistName] = useState("");
const [newFolderName, setNewFolderName] = useState("");
// Helper function to get all folders from the playlist tree
const getAllFolders = useCallback((nodes: PlaylistNode[]): { name: string }[] => {
let result: { name: string }[] = [];
for (const node of nodes) {
if (node.type === 'folder') {
result.push({ name: node.name });
if (node.children) {
result = result.concat(getAllFolders(node.children));
}
}
}
return result;
}, []);
const allFolders = getAllFolders(playlists);
const handleCreatePlaylist = () => {
if (newPlaylistName.trim()) {
onPlaylistCreate(newPlaylistName);
setNewPlaylistName("");
onClose();
onPlaylistModalClose();
}
};
const handleCreateFolder = () => {
if (newFolderName.trim()) {
onFolderCreate(newFolderName);
setNewFolderName("");
onFolderModalClose();
}
};
@ -177,27 +237,47 @@ export const PlaylistManager: React.FC<PlaylistManagerProps> = ({
selectedItem={selectedItem}
onPlaylistSelect={onPlaylistSelect}
onPlaylistDelete={onPlaylistDelete}
onPlaylistMove={onPlaylistMove}
allFolders={allFolders}
/>
))}
</VStack>
<Flex gap={2}>
<Button
onClick={onOpen}
onClick={onPlaylistModalOpen}
colorScheme="blue"
size="sm"
width="100%"
flex={1}
leftIcon={<AddIcon />}
borderRadius="md"
_hover={{
transform: "translateY(-1px)",
boxShadow: "sm",
}}
>
Create New Playlist
New Playlist
</Button>
<Button
onClick={onFolderModalOpen}
colorScheme="teal"
size="sm"
flex={1}
leftIcon={<ViewIcon />}
borderRadius="md"
_hover={{
transform: "translateY(-1px)",
boxShadow: "sm",
}}
>
New Folder
</Button>
</Flex>
{/* New Playlist Modal */}
<Modal
isOpen={isOpen}
onClose={onClose}
isOpen={isPlaylistModalOpen}
onClose={onPlaylistModalClose}
isCentered
motionPreset="slideInBottom"
>
@ -242,7 +322,67 @@ export const PlaylistManager: React.FC<PlaylistManagerProps> = ({
</Button>
<Button
variant="ghost"
onClick={onClose}
onClick={onPlaylistModalClose}
color="gray.300"
_hover={{
bg: "whiteAlpha.200",
}}
>
Cancel
</Button>
</ModalFooter>
</ModalContent>
</Modal>
{/* New Folder Modal */}
<Modal
isOpen={isFolderModalOpen}
onClose={onFolderModalClose}
isCentered
motionPreset="slideInBottom"
>
<ModalOverlay bg="blackAlpha.700" backdropFilter="blur(5px)" />
<ModalContent
bg="gray.800"
borderRadius="xl"
boxShadow="xl"
border="1px"
borderColor="gray.700"
>
<ModalHeader color="white">Create New Folder</ModalHeader>
<ModalCloseButton color="white" />
<ModalBody pb={6}>
<Input
value={newFolderName}
onChange={(e) => setNewFolderName(e.target.value)}
placeholder="Enter folder name"
bg="gray.700"
border="none"
color="white"
_placeholder={{ color: "gray.400" }}
_focus={{
boxShadow: "0 0 0 1px teal.500",
borderColor: "teal.500",
}}
/>
</ModalBody>
<ModalFooter>
<Button
colorScheme="teal"
mr={3}
onClick={handleCreateFolder}
isDisabled={!newFolderName.trim()}
_hover={{
transform: "translateY(-1px)",
boxShadow: "sm",
}}
>
Create
</Button>
<Button
variant="ghost"
onClick={onFolderModalClose}
color="gray.300"
_hover={{
bg: "whiteAlpha.200",