Folders are working aswell!!
This commit is contained in:
parent
3a13c24301
commit
9e32aa0a99
@ -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"
|
const displayedSongs = currentPlaylist === "All Songs"
|
||||||
? songs
|
? songs
|
||||||
: songs.filter((song: Song) => {
|
: songs.filter((song: Song) => {
|
||||||
@ -322,6 +390,8 @@ export default function RekordboxReader() {
|
|||||||
if (isMobile) onClose();
|
if (isMobile) onClose();
|
||||||
}}
|
}}
|
||||||
onPlaylistDelete={handlePlaylistDelete}
|
onPlaylistDelete={handlePlaylistDelete}
|
||||||
|
onFolderCreate={handleCreateFolder}
|
||||||
|
onPlaylistMove={handleMovePlaylist}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -19,9 +19,12 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
IconButton,
|
IconButton,
|
||||||
Collapse,
|
Collapse,
|
||||||
|
MenuDivider,
|
||||||
|
MenuGroup,
|
||||||
|
useToast
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { ChevronDownIcon, DeleteIcon, ChevronRightIcon } from "@chakra-ui/icons";
|
import { ChevronDownIcon, DeleteIcon, ChevronRightIcon, AddIcon, ViewIcon } from "@chakra-ui/icons";
|
||||||
import React, { useState } from "react";
|
import React, { useState, useCallback } from "react";
|
||||||
import { PlaylistNode } from "../types/interfaces";
|
import { PlaylistNode } from "../types/interfaces";
|
||||||
|
|
||||||
interface PlaylistManagerProps {
|
interface PlaylistManagerProps {
|
||||||
@ -30,6 +33,8 @@ interface PlaylistManagerProps {
|
|||||||
onPlaylistCreate: (name: string) => void;
|
onPlaylistCreate: (name: string) => void;
|
||||||
onPlaylistSelect: (name: string | null) => void;
|
onPlaylistSelect: (name: string | null) => void;
|
||||||
onPlaylistDelete: (name: string) => void;
|
onPlaylistDelete: (name: string) => void;
|
||||||
|
onFolderCreate: (name: string) => void;
|
||||||
|
onPlaylistMove: (playlistName: string, targetFolderName: string | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getButtonStyles = (isSelected: boolean) => ({
|
const getButtonStyles = (isSelected: boolean) => ({
|
||||||
@ -57,6 +62,8 @@ interface PlaylistItemProps {
|
|||||||
selectedItem: string | null;
|
selectedItem: string | null;
|
||||||
onPlaylistSelect: (name: string | null) => void;
|
onPlaylistSelect: (name: string | null) => void;
|
||||||
onPlaylistDelete: (name: string) => void;
|
onPlaylistDelete: (name: string) => void;
|
||||||
|
onPlaylistMove: (playlistName: string, targetFolderName: string | null) => void;
|
||||||
|
allFolders: { name: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const PlaylistItem: React.FC<PlaylistItemProps> = ({
|
const PlaylistItem: React.FC<PlaylistItemProps> = ({
|
||||||
@ -65,9 +72,11 @@ const PlaylistItem: React.FC<PlaylistItemProps> = ({
|
|||||||
selectedItem,
|
selectedItem,
|
||||||
onPlaylistSelect,
|
onPlaylistSelect,
|
||||||
onPlaylistDelete,
|
onPlaylistDelete,
|
||||||
|
onPlaylistMove,
|
||||||
|
allFolders,
|
||||||
}) => {
|
}) => {
|
||||||
const [isOpen, setIsOpen] = useState(true);
|
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') {
|
if (node.type === 'folder') {
|
||||||
return (
|
return (
|
||||||
@ -98,6 +107,8 @@ const PlaylistItem: React.FC<PlaylistItemProps> = ({
|
|||||||
selectedItem={selectedItem}
|
selectedItem={selectedItem}
|
||||||
onPlaylistSelect={onPlaylistSelect}
|
onPlaylistSelect={onPlaylistSelect}
|
||||||
onPlaylistDelete={onPlaylistDelete}
|
onPlaylistDelete={onPlaylistDelete}
|
||||||
|
onPlaylistMove={onPlaylistMove}
|
||||||
|
allFolders={allFolders}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</VStack>
|
</VStack>
|
||||||
@ -127,6 +138,27 @@ const PlaylistItem: React.FC<PlaylistItemProps> = ({
|
|||||||
_hover={{ color: "white", bg: "whiteAlpha.200" }}
|
_hover={{ color: "white", bg: "whiteAlpha.200" }}
|
||||||
/>
|
/>
|
||||||
<MenuList bg="gray.800" borderColor="gray.700">
|
<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
|
<MenuItem
|
||||||
bg="gray.800"
|
bg="gray.800"
|
||||||
color="red.300"
|
color="red.300"
|
||||||
@ -148,15 +180,43 @@ export const PlaylistManager: React.FC<PlaylistManagerProps> = ({
|
|||||||
onPlaylistCreate,
|
onPlaylistCreate,
|
||||||
onPlaylistSelect,
|
onPlaylistSelect,
|
||||||
onPlaylistDelete,
|
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 [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 = () => {
|
const handleCreatePlaylist = () => {
|
||||||
if (newPlaylistName.trim()) {
|
if (newPlaylistName.trim()) {
|
||||||
onPlaylistCreate(newPlaylistName);
|
onPlaylistCreate(newPlaylistName);
|
||||||
setNewPlaylistName("");
|
setNewPlaylistName("");
|
||||||
onClose();
|
onPlaylistModalClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateFolder = () => {
|
||||||
|
if (newFolderName.trim()) {
|
||||||
|
onFolderCreate(newFolderName);
|
||||||
|
setNewFolderName("");
|
||||||
|
onFolderModalClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -177,27 +237,47 @@ export const PlaylistManager: React.FC<PlaylistManagerProps> = ({
|
|||||||
selectedItem={selectedItem}
|
selectedItem={selectedItem}
|
||||||
onPlaylistSelect={onPlaylistSelect}
|
onPlaylistSelect={onPlaylistSelect}
|
||||||
onPlaylistDelete={onPlaylistDelete}
|
onPlaylistDelete={onPlaylistDelete}
|
||||||
|
onPlaylistMove={onPlaylistMove}
|
||||||
|
allFolders={allFolders}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</VStack>
|
</VStack>
|
||||||
|
|
||||||
<Button
|
<Flex gap={2}>
|
||||||
onClick={onOpen}
|
<Button
|
||||||
colorScheme="blue"
|
onClick={onPlaylistModalOpen}
|
||||||
size="sm"
|
colorScheme="blue"
|
||||||
width="100%"
|
size="sm"
|
||||||
borderRadius="md"
|
flex={1}
|
||||||
_hover={{
|
leftIcon={<AddIcon />}
|
||||||
transform: "translateY(-1px)",
|
borderRadius="md"
|
||||||
boxShadow: "sm",
|
_hover={{
|
||||||
}}
|
transform: "translateY(-1px)",
|
||||||
>
|
boxShadow: "sm",
|
||||||
Create New Playlist
|
}}
|
||||||
</Button>
|
>
|
||||||
|
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
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isPlaylistModalOpen}
|
||||||
onClose={onClose}
|
onClose={onPlaylistModalClose}
|
||||||
isCentered
|
isCentered
|
||||||
motionPreset="slideInBottom"
|
motionPreset="slideInBottom"
|
||||||
>
|
>
|
||||||
@ -242,7 +322,67 @@ export const PlaylistManager: React.FC<PlaylistManagerProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
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"
|
color="gray.300"
|
||||||
_hover={{
|
_hover={{
|
||||||
bg: "whiteAlpha.200",
|
bg: "whiteAlpha.200",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user