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')}