Better UI!
This commit is contained in:
parent
7e08b0e567
commit
83fe6994b8
@ -1,7 +1,19 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body, #root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#root {
|
||||
max-width: 1280px;
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Box, Button, Flex, Heading, Input, Spinner, Text, useStyleConfig } from "@chakra-ui/react";
|
||||
import { Box, Button, Flex, Heading, Input, Spinner, Text, useStyleConfig, useBreakpointValue, IconButton, Drawer, DrawerBody, DrawerHeader, DrawerOverlay, DrawerContent, DrawerCloseButton, useDisclosure, Container, Menu, MenuButton, MenuList, MenuItem } from "@chakra-ui/react";
|
||||
import { ChevronLeftIcon, ChevronRightIcon, HamburgerIcon, ViewIcon, SettingsIcon } from "@chakra-ui/icons";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import { SongList } from "./components/SongList";
|
||||
@ -10,7 +11,7 @@ import { api } from "./services/api";
|
||||
import { Song } from "./types/interfaces";
|
||||
import "./App.css";
|
||||
|
||||
const StyledFileInput = () => {
|
||||
const StyledFileInput = ({ isMobile = false }) => {
|
||||
const { handleFileUpload } = useXmlParser();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
@ -22,7 +23,7 @@ const StyledFileInput = () => {
|
||||
<Box
|
||||
position="relative"
|
||||
width="auto"
|
||||
maxW="300px"
|
||||
maxW={isMobile ? "100%" : "300px"}
|
||||
>
|
||||
<Input
|
||||
type="file"
|
||||
@ -54,6 +55,7 @@ const StyledFileInput = () => {
|
||||
bg: "gray.500"
|
||||
}}
|
||||
onClick={handleClick}
|
||||
size={isMobile ? "sm" : "md"}
|
||||
>
|
||||
Choose XML File
|
||||
</Button>
|
||||
@ -67,6 +69,10 @@ export default function RekordboxReader() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const initialLoadDone = useRef(false);
|
||||
const mobileFileInputRef = useRef<HTMLInputElement>(null);
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
const isMobile = useBreakpointValue({ base: true, md: false });
|
||||
|
||||
// Get the current playlist from URL or default to "All Songs"
|
||||
const currentPlaylist = location.pathname === "/"
|
||||
@ -169,11 +175,58 @@ export default function RekordboxReader() {
|
||||
);
|
||||
}
|
||||
|
||||
const playlistManager = (
|
||||
<PlaylistManager
|
||||
playlists={playlists}
|
||||
selectedItem={currentPlaylist}
|
||||
onPlaylistCreate={handleCreatePlaylist}
|
||||
onPlaylistSelect={(name) => {
|
||||
handlePlaylistSelect(name || "All Songs");
|
||||
if (isMobile) onClose();
|
||||
}}
|
||||
onPlaylistDelete={handlePlaylistDelete}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Flex direction="column" gap={4} mb={6}>
|
||||
<Heading size="md" mb={2}>Rekordbox Reader</Heading>
|
||||
<Flex gap={4} align="center">
|
||||
<Box
|
||||
position="fixed"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
overflow="hidden"
|
||||
margin={0}
|
||||
padding={0}
|
||||
>
|
||||
<Flex direction="column" h="100%" w="100%">
|
||||
{/* Header */}
|
||||
<Flex
|
||||
px={isMobile ? 2 : 4}
|
||||
py={2}
|
||||
bg="gray.800"
|
||||
borderBottom="1px"
|
||||
borderColor="gray.700"
|
||||
align="center"
|
||||
gap={2}
|
||||
w="full"
|
||||
>
|
||||
{isMobile && (
|
||||
<IconButton
|
||||
aria-label="Open menu"
|
||||
icon={<ChevronRightIcon />}
|
||||
onClick={onOpen}
|
||||
variant="solid"
|
||||
colorScheme="blue"
|
||||
size="md"
|
||||
fontSize="20px"
|
||||
/>
|
||||
)}
|
||||
<Heading size={isMobile ? "sm" : "md"}>Rekordbox Reader</Heading>
|
||||
|
||||
{/* Desktop Actions */}
|
||||
{!isMobile && (
|
||||
<Flex gap={2} align="center" ml="auto">
|
||||
<StyledFileInput />
|
||||
{songs.length > 0 && (
|
||||
<Button onClick={handleExport} size="sm" width="auto">
|
||||
@ -181,18 +234,103 @@ export default function RekordboxReader() {
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex gap={4} alignItems="start">
|
||||
<Box w="200px">
|
||||
<PlaylistManager
|
||||
playlists={playlists}
|
||||
selectedItem={currentPlaylist}
|
||||
onPlaylistCreate={handleCreatePlaylist}
|
||||
onPlaylistSelect={handlePlaylistSelect}
|
||||
onPlaylistDelete={handlePlaylistDelete}
|
||||
)}
|
||||
|
||||
{/* Mobile Actions */}
|
||||
{isMobile && (
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
icon={<SettingsIcon />}
|
||||
variant="ghost"
|
||||
ml="auto"
|
||||
size="md"
|
||||
color="gray.300"
|
||||
_hover={{ color: "white", bg: "whiteAlpha.200" }}
|
||||
/>
|
||||
<MenuList bg="gray.800" borderColor="gray.700">
|
||||
<MenuItem
|
||||
bg="gray.800"
|
||||
_hover={{ bg: "gray.700" }}
|
||||
>
|
||||
<StyledFileInput isMobile />
|
||||
</MenuItem>
|
||||
{songs.length > 0 && (
|
||||
<MenuItem
|
||||
bg="gray.800"
|
||||
_hover={{ bg: "gray.700" }}
|
||||
onClick={handleExport}
|
||||
color="gray.100"
|
||||
>
|
||||
Export XML
|
||||
</MenuItem>
|
||||
)}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
{/* Main Content */}
|
||||
<Flex flex={1} overflow="hidden" w="full">
|
||||
{/* Sidebar - Desktop */}
|
||||
{!isMobile && (
|
||||
<Box
|
||||
w="300px"
|
||||
minW="300px"
|
||||
p={4}
|
||||
borderRight="1px"
|
||||
borderColor="gray.700"
|
||||
overflowY="auto"
|
||||
bg="gray.900"
|
||||
>
|
||||
{playlistManager}
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
)}
|
||||
|
||||
{/* Sidebar - Mobile */}
|
||||
<Drawer
|
||||
isOpen={isOpen}
|
||||
placement="left"
|
||||
onClose={onClose}
|
||||
size="full"
|
||||
>
|
||||
<DrawerOverlay />
|
||||
<DrawerContent bg="gray.900" p={0}>
|
||||
<DrawerHeader
|
||||
borderBottomWidth="1px"
|
||||
bg="gray.800"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
px={2}
|
||||
py={2}
|
||||
>
|
||||
<Text>Playlists</Text>
|
||||
<IconButton
|
||||
aria-label="Close menu"
|
||||
icon={<ChevronLeftIcon />}
|
||||
onClick={onClose}
|
||||
variant="ghost"
|
||||
ml="auto"
|
||||
color="blue.400"
|
||||
_hover={{ color: "blue.300", bg: "whiteAlpha.200" }}
|
||||
/>
|
||||
</DrawerHeader>
|
||||
<DrawerBody p={2}>
|
||||
{playlistManager}
|
||||
</DrawerBody>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
{/* Main Content Area */}
|
||||
<Flex
|
||||
flex={1}
|
||||
gap={4}
|
||||
p={isMobile ? 2 : 4}
|
||||
overflowY="hidden"
|
||||
w="full"
|
||||
>
|
||||
{/* Song List */}
|
||||
<Box flex={1} overflowY="auto" w="full">
|
||||
<SongList
|
||||
songs={displayedSongs}
|
||||
onAddToPlaylist={handleAddSongsToPlaylist}
|
||||
@ -203,7 +341,36 @@ export default function RekordboxReader() {
|
||||
currentPlaylist={currentPlaylist}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Details Panel */}
|
||||
{!isMobile && (
|
||||
<Box
|
||||
w="350px"
|
||||
minW="350px"
|
||||
p={4}
|
||||
borderLeft="1px"
|
||||
borderColor="gray.700"
|
||||
overflowY="auto"
|
||||
bg="gray.900"
|
||||
sx={{
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '8px',
|
||||
borderRadius: '8px',
|
||||
backgroundColor: 'gray.900',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
backgroundColor: 'gray.700',
|
||||
borderRadius: '8px',
|
||||
},
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
}}
|
||||
>
|
||||
<SongDetails song={selectedSong} />
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@ -8,17 +8,7 @@ interface SongDetailsProps {
|
||||
export const SongDetails: React.FC<SongDetailsProps> = ({ song }) => {
|
||||
if (!song) {
|
||||
return (
|
||||
<Box
|
||||
w="300px"
|
||||
position="sticky"
|
||||
top={4}
|
||||
h="calc(100vh - 2rem)"
|
||||
bg="gray.800"
|
||||
p={4}
|
||||
borderRadius="md"
|
||||
borderLeft="1px"
|
||||
borderColor="gray.700"
|
||||
>
|
||||
<Box h="full" p={4}>
|
||||
<Text color="gray.500">Select a song to view details</Text>
|
||||
</Box>
|
||||
);
|
||||
@ -36,37 +26,11 @@ export const SongDetails: React.FC<SongDetailsProps> = ({ song }) => {
|
||||
{ label: "Mix", value: song.mix },
|
||||
{ label: "Rating", value: song.rating },
|
||||
{ label: "Comments", value: song.comments },
|
||||
].filter(detail => detail.value); // Only show fields that have values
|
||||
].filter(detail => detail.value);
|
||||
|
||||
return (
|
||||
<Box
|
||||
w="300px"
|
||||
position="sticky"
|
||||
top={4}
|
||||
h="calc(100vh - 2rem)"
|
||||
bg="gray.800"
|
||||
borderRadius="md"
|
||||
borderLeft="1px"
|
||||
borderColor="gray.700"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
>
|
||||
<Box p={4} flex="1" overflowY="auto" css={{
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '4px',
|
||||
},
|
||||
'&::-webkit-scrollbar-track': {
|
||||
background: 'transparent',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
background: 'var(--chakra-colors-gray-600)',
|
||||
borderRadius: '2px',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb:hover': {
|
||||
background: 'var(--chakra-colors-gray-500)',
|
||||
},
|
||||
}}>
|
||||
<VStack align="stretch" spacing={4}>
|
||||
<Box h="full">
|
||||
<VStack align="stretch" spacing={4} p={4}>
|
||||
<Box>
|
||||
<Text fontSize="lg" fontWeight="bold" color="white">
|
||||
{song.title}
|
||||
@ -114,6 +78,5 @@ export const SongDetails: React.FC<SongDetailsProps> = ({ song }) => {
|
||||
)}
|
||||
</VStack>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user