diff --git a/packages/frontend/src/App.tsx b/packages/frontend/src/App.tsx index a3104b9..0822b7d 100644 --- a/packages/frontend/src/App.tsx +++ b/packages/frontend/src/App.tsx @@ -1,5 +1,5 @@ import { Box, Button, Flex, Heading, Spinner, Text, useBreakpointValue, IconButton, Drawer, DrawerBody, DrawerHeader, DrawerOverlay, DrawerContent, useDisclosure, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter, VStack } from "@chakra-ui/react"; -import { ChevronLeftIcon, ChevronRightIcon, SettingsIcon } from "@chakra-ui/icons"; +import { ChevronLeftIcon, ChevronRightIcon, SettingsIcon, DownloadIcon } from "@chakra-ui/icons"; import React, { useState, useRef, useEffect, useCallback } from "react"; import { useNavigate, useLocation, Routes, Route } from "react-router-dom"; import { PaginatedSongList } from "./components/PaginatedSongList"; @@ -9,6 +9,7 @@ import { Configuration } from "./pages/Configuration"; import { useXmlParser } from "./hooks/useXmlParser"; import { usePaginatedSongs } from "./hooks/usePaginatedSongs"; import { formatTotalDuration } from "./utils/formatters"; +import { exportToXml } from "./services/xmlService"; import { api } from "./services/api"; import type { Song, PlaylistNode } from "./types/interfaces"; @@ -114,6 +115,24 @@ export default function RekordboxReader() { searchQuery } = usePaginatedSongs({ pageSize: 50, playlistName: currentPlaylist }); + // Export library to XML + const handleExportLibrary = useCallback(() => { + try { + const xmlContent = exportToXml(songs, playlists); + const blob = new Blob([xmlContent], { type: 'application/xml' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `rekordbox-library-${new Date().toISOString().split('T')[0]}.xml`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } catch (error) { + console.error('Failed to export library:', error); + } + }, [songs, playlists]); + // Check if database is initialized (has songs or playlists) - moved after useDisclosure useEffect(() => { const checkDatabaseInitialized = async () => { @@ -464,12 +483,24 @@ export default function RekordboxReader() { Rekordbox Reader + {/* Export Library Button */} + } + aria-label="Export Library" + variant="ghost" + ml="auto" + mr={2} + color="gray.300" + _hover={{ color: "white", bg: "whiteAlpha.200" }} + onClick={handleExportLibrary} + isDisabled={songs.length === 0} + /> + {/* Configuration Button */} } aria-label="Configuration" variant="ghost" - ml="auto" color="gray.300" _hover={{ color: "white", bg: "whiteAlpha.200" }} onClick={() => navigate('/config')}