Add initial album detail page
This commit is contained in:
parent
b2f9c73300
commit
1d82c84c9e
4 changed files with 514 additions and 0 deletions
287
src/renderer/features/albums/components/album-detail-content.tsx
Normal file
287
src/renderer/features/albums/components/album-detail-content.tsx
Normal file
|
@ -0,0 +1,287 @@
|
||||||
|
import { MutableRefObject, useCallback, useMemo } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DropdownMenu,
|
||||||
|
getColumnDefs,
|
||||||
|
GridCarousel,
|
||||||
|
TextTitle,
|
||||||
|
VirtualTable,
|
||||||
|
} from '/@/renderer/components';
|
||||||
|
import { CellContextMenuEvent, ColDef } from '@ag-grid-community/core';
|
||||||
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
import { Group, Stack } from '@mantine/core';
|
||||||
|
import { useSetState } from '@mantine/hooks';
|
||||||
|
import sortBy from 'lodash/sortBy';
|
||||||
|
import { RiHeartLine, RiMoreFill } from 'react-icons/ri';
|
||||||
|
import { useParams } from 'react-router';
|
||||||
|
import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query';
|
||||||
|
import { useSongListStore } from '/@/renderer/store';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
|
import { useContainerQuery } from '/@/renderer/hooks';
|
||||||
|
import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add';
|
||||||
|
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||||
|
import { openContextMenu } from '/@/renderer/features/context-menu';
|
||||||
|
import { LibraryItem, Play } from '/@/renderer/types';
|
||||||
|
import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
|
||||||
|
import { PlayButton, PLAY_TYPES } from '/@/renderer/features/shared';
|
||||||
|
import { useAlbumList } from '/@/renderer/features/albums/queries/album-list-query';
|
||||||
|
import { AlbumListSort, SortOrder } from '/@/renderer/api/types';
|
||||||
|
|
||||||
|
const ContentContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-width: 1920px;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.ag-theme-alpine-dark {
|
||||||
|
--ag-header-background-color: rgba(0, 0, 0, 0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ag-header-container {
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ag-header-cell-resize {
|
||||||
|
top: 25%;
|
||||||
|
width: 7px;
|
||||||
|
height: 50%;
|
||||||
|
background-color: rgb(70, 70, 70, 20%);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface AlbumDetailContentProps {
|
||||||
|
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
|
||||||
|
const { albumId } = useParams() as { albumId: string };
|
||||||
|
const detailQuery = useAlbumDetail({ id: albumId });
|
||||||
|
const cq = useContainerQuery();
|
||||||
|
const handlePlayQueueAdd = useHandlePlayQueueAdd();
|
||||||
|
|
||||||
|
const page = useSongListStore();
|
||||||
|
|
||||||
|
const columnDefs: ColDef[] = useMemo(
|
||||||
|
() =>
|
||||||
|
getColumnDefs(page.table.columns).filter((c) => c.colId !== 'album' && c.colId !== 'artist'),
|
||||||
|
[page.table.columns],
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('columnDefs :>> ', columnDefs);
|
||||||
|
|
||||||
|
const defaultColumnDefs: ColDef = useMemo(() => {
|
||||||
|
return {
|
||||||
|
lockPinned: true,
|
||||||
|
lockVisible: true,
|
||||||
|
resizable: true,
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [pagination, setPagination] = useSetState({
|
||||||
|
artist: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleNextPage = useCallback(
|
||||||
|
(key: 'artist') => {
|
||||||
|
setPagination({
|
||||||
|
[key]: pagination[key as keyof typeof pagination] + 1,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[pagination, setPagination],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handlePreviousPage = useCallback(
|
||||||
|
(key: 'artist') => {
|
||||||
|
setPagination({
|
||||||
|
[key]: pagination[key as keyof typeof pagination] - 1,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[pagination, setPagination],
|
||||||
|
);
|
||||||
|
|
||||||
|
const itemsPerPage = cq.isXl ? 9 : cq.isLg ? 7 : cq.isMd ? 5 : cq.isSm ? 4 : 3;
|
||||||
|
|
||||||
|
const artistQuery = useAlbumList(
|
||||||
|
{
|
||||||
|
jfParams: {
|
||||||
|
albumArtistIds: detailQuery?.data?.albumArtists[0]?.id,
|
||||||
|
},
|
||||||
|
limit: itemsPerPage,
|
||||||
|
ndParams: {
|
||||||
|
artist_id: detailQuery?.data?.albumArtists[0]?.id,
|
||||||
|
},
|
||||||
|
sortBy: AlbumListSort.YEAR,
|
||||||
|
sortOrder: SortOrder.DESC,
|
||||||
|
startIndex: pagination.artist * itemsPerPage,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cacheTime: 1000 * 60,
|
||||||
|
enabled: detailQuery?.data?.albumArtists[0]?.id !== undefined,
|
||||||
|
keepPreviousData: true,
|
||||||
|
staleTime: 1000 * 60,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const carousels = [
|
||||||
|
{
|
||||||
|
data: artistQuery?.data?.items,
|
||||||
|
loading: artistQuery?.isLoading || artistQuery.isFetching,
|
||||||
|
pagination: {
|
||||||
|
handleNextPage: () => handleNextPage('artist'),
|
||||||
|
handlePreviousPage: () => handlePreviousPage('artist'),
|
||||||
|
hasPreviousPage: pagination.artist > 0,
|
||||||
|
itemsPerPage,
|
||||||
|
},
|
||||||
|
title: (
|
||||||
|
<TextTitle
|
||||||
|
fw="bold"
|
||||||
|
order={3}
|
||||||
|
>
|
||||||
|
More from this artist
|
||||||
|
</TextTitle>
|
||||||
|
),
|
||||||
|
uniqueId: 'mostPlayed',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const playButtonBehavior = usePlayButtonBehavior();
|
||||||
|
|
||||||
|
const handlePlay = async (playType?: Play) => {
|
||||||
|
handlePlayQueueAdd?.({
|
||||||
|
byData: detailQuery?.data?.songs,
|
||||||
|
play: playType || playButtonBehavior,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContextMenu = (e: CellContextMenuEvent) => {
|
||||||
|
if (!e.event) return;
|
||||||
|
const clickEvent = e.event as MouseEvent;
|
||||||
|
clickEvent.preventDefault();
|
||||||
|
|
||||||
|
const selectedNodes = e.api.getSelectedNodes();
|
||||||
|
const selectedIds = selectedNodes.map((node) => node.data.id);
|
||||||
|
let selectedRows = sortBy(selectedNodes, ['rowIndex']).map((node) => node.data);
|
||||||
|
|
||||||
|
if (!selectedIds.includes(e.data.id)) {
|
||||||
|
e.api.deselectAll();
|
||||||
|
e.node.setSelected(true);
|
||||||
|
selectedRows = [e.data];
|
||||||
|
}
|
||||||
|
|
||||||
|
openContextMenu({
|
||||||
|
data: selectedRows,
|
||||||
|
menuItems: SONG_CONTEXT_MENU_ITEMS,
|
||||||
|
type: LibraryItem.SONG,
|
||||||
|
xPos: clickEvent.clientX,
|
||||||
|
yPos: clickEvent.clientY,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContentContainer>
|
||||||
|
<Group
|
||||||
|
pb="2rem"
|
||||||
|
pt="1rem"
|
||||||
|
spacing="lg"
|
||||||
|
>
|
||||||
|
<PlayButton onClick={() => handlePlay(playButtonBehavior)} />
|
||||||
|
<Group spacing="xs">
|
||||||
|
<Button
|
||||||
|
compact
|
||||||
|
disabled
|
||||||
|
variant="subtle"
|
||||||
|
>
|
||||||
|
<RiHeartLine size={20} />
|
||||||
|
</Button>
|
||||||
|
<DropdownMenu position="bottom-start">
|
||||||
|
<DropdownMenu.Target>
|
||||||
|
<Button
|
||||||
|
compact
|
||||||
|
variant="subtle"
|
||||||
|
>
|
||||||
|
<RiMoreFill size={20} />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenu.Target>
|
||||||
|
<DropdownMenu.Dropdown>
|
||||||
|
{PLAY_TYPES.filter((type) => type.play !== playButtonBehavior).map((type) => (
|
||||||
|
<DropdownMenu.Item
|
||||||
|
key={`playtype-${type.play}`}
|
||||||
|
onClick={() => handlePlay(type.play)}
|
||||||
|
>
|
||||||
|
{type.label}
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
))}
|
||||||
|
<DropdownMenu.Divider />
|
||||||
|
<DropdownMenu.Item disabled>Add to playlist</DropdownMenu.Item>
|
||||||
|
</DropdownMenu.Dropdown>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
<VirtualTable
|
||||||
|
ref={tableRef}
|
||||||
|
animateRows
|
||||||
|
detailRowAutoHeight
|
||||||
|
maintainColumnOrder
|
||||||
|
suppressCellFocus
|
||||||
|
suppressCopyRowsToClipboard
|
||||||
|
suppressLoadingOverlay
|
||||||
|
suppressMoveWhenRowDragging
|
||||||
|
suppressPaginationPanel
|
||||||
|
suppressRowDrag
|
||||||
|
suppressScrollOnNewData
|
||||||
|
columnDefs={columnDefs}
|
||||||
|
defaultColDef={defaultColumnDefs}
|
||||||
|
enableCellChangeFlash={false}
|
||||||
|
getRowId={(data) => data.data.id}
|
||||||
|
rowData={detailQuery.data?.songs}
|
||||||
|
rowHeight={60}
|
||||||
|
rowSelection="multiple"
|
||||||
|
onCellContextMenu={handleContextMenu}
|
||||||
|
onColumnResized={() => console.log('resize')}
|
||||||
|
onGridReady={(params) => {
|
||||||
|
params.api.setDomLayout('autoHeight');
|
||||||
|
params.api.sizeColumnsToFit();
|
||||||
|
}}
|
||||||
|
onGridSizeChanged={(params) => {
|
||||||
|
params.api.sizeColumnsToFit();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack
|
||||||
|
ref={cq.ref}
|
||||||
|
mt="5rem"
|
||||||
|
>
|
||||||
|
{carousels.map((carousel, index) => (
|
||||||
|
<GridCarousel
|
||||||
|
key={`carousel-${carousel.uniqueId}-${index}`}
|
||||||
|
cardRows={[
|
||||||
|
{
|
||||||
|
property: 'name',
|
||||||
|
route: {
|
||||||
|
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
|
||||||
|
slugs: [{ idProperty: 'id', slugProperty: 'albumId' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arrayProperty: 'name',
|
||||||
|
property: 'albumArtists',
|
||||||
|
route: {
|
||||||
|
route: AppRoute.LIBRARY_ALBUMARTISTS_DETAIL,
|
||||||
|
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
containerWidth={cq.width}
|
||||||
|
data={carousel.data}
|
||||||
|
loading={carousel.loading}
|
||||||
|
pagination={carousel.pagination}
|
||||||
|
uniqueId={carousel.uniqueId}
|
||||||
|
>
|
||||||
|
<GridCarousel.Title>{carousel.title}</GridCarousel.Title>
|
||||||
|
</GridCarousel>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</ContentContainer>
|
||||||
|
);
|
||||||
|
};
|
182
src/renderer/features/albums/components/album-detail-header.tsx
Normal file
182
src/renderer/features/albums/components/album-detail-header.tsx
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
import { Center, Group } from '@mantine/core';
|
||||||
|
import { Fragment } from 'react';
|
||||||
|
import { RiAlbumFill } from 'react-icons/ri';
|
||||||
|
import { generatePath, useParams } from 'react-router';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Text, TextTitle } from '/@/renderer/components';
|
||||||
|
import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query';
|
||||||
|
import { useContainerQuery } from '/@/renderer/hooks';
|
||||||
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
|
|
||||||
|
const HeaderContainer = styled.div`
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-auto-columns: 1fr;
|
||||||
|
grid-template-areas: 'image info';
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
grid-template-columns: 250px minmax(0, 1fr);
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
height: 30vh;
|
||||||
|
min-height: 340px;
|
||||||
|
max-height: 500px;
|
||||||
|
padding: 5rem 2rem 2rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CoverImageWrapper = styled.div`
|
||||||
|
z-index: 15;
|
||||||
|
display: flex;
|
||||||
|
grid-area: image;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
filter: drop-shadow(0 0 8px rgb(0, 0, 0, 50%));
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MetadataWrapper = styled.div`
|
||||||
|
z-index: 15;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
grid-area: info;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledImage = styled.img`
|
||||||
|
object-fit: cover;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BackgroundImage = styled.div<{ background: string }>`
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
z-index: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: ${(props) => props.background};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BackgroundImageOverlay = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(180deg, rgba(25, 26, 28, 5%), var(--main-bg));
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface AlbumDetailHeaderProps {
|
||||||
|
background: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AlbumDetailHeader = ({ background }: AlbumDetailHeaderProps) => {
|
||||||
|
const { albumId } = useParams() as { albumId: string };
|
||||||
|
const detailQuery = useAlbumDetail({ id: albumId });
|
||||||
|
const cq = useContainerQuery();
|
||||||
|
|
||||||
|
const titleSize = cq.isXl
|
||||||
|
? '6rem'
|
||||||
|
: cq.isLg
|
||||||
|
? '5.5rem'
|
||||||
|
: cq.isMd
|
||||||
|
? '4.5rem'
|
||||||
|
: cq.isSm
|
||||||
|
? '3.5rem'
|
||||||
|
: '2rem';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HeaderContainer ref={cq.ref}>
|
||||||
|
<BackgroundImage background={background} />
|
||||||
|
<BackgroundImageOverlay />
|
||||||
|
<CoverImageWrapper>
|
||||||
|
{detailQuery?.data?.imageUrl ? (
|
||||||
|
<StyledImage
|
||||||
|
alt="cover"
|
||||||
|
height={225}
|
||||||
|
src={detailQuery?.data.imageUrl}
|
||||||
|
width={225}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Center
|
||||||
|
sx={{
|
||||||
|
background: 'var(--placeholder-bg)',
|
||||||
|
borderRadius: 'var(--card-default-radius)',
|
||||||
|
height: `${80}px`,
|
||||||
|
width: `${80}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RiAlbumFill
|
||||||
|
color="var(--placeholder-fg)"
|
||||||
|
size={35}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</CoverImageWrapper>
|
||||||
|
<MetadataWrapper>
|
||||||
|
<Group>
|
||||||
|
<Text
|
||||||
|
$link
|
||||||
|
component={Link}
|
||||||
|
fw="600"
|
||||||
|
sx={{ textTransform: 'uppercase' }}
|
||||||
|
to={AppRoute.LIBRARY_ALBUMS}
|
||||||
|
>
|
||||||
|
Album
|
||||||
|
</Text>
|
||||||
|
{detailQuery?.data?.releaseYear && (
|
||||||
|
<>
|
||||||
|
<Text>•</Text>
|
||||||
|
<Text>{detailQuery?.data?.releaseYear}</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
<TextTitle
|
||||||
|
fw="900"
|
||||||
|
lh="1"
|
||||||
|
mb="0.12em"
|
||||||
|
mt=".08em"
|
||||||
|
sx={{ fontSize: titleSize }}
|
||||||
|
>
|
||||||
|
{detailQuery?.data?.name}
|
||||||
|
</TextTitle>
|
||||||
|
<Group
|
||||||
|
spacing="xs"
|
||||||
|
sx={{
|
||||||
|
WebkitBoxOrient: 'vertical',
|
||||||
|
WebkitLineClamp: 2,
|
||||||
|
display: '-webkit-box',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{detailQuery?.data?.albumArtists.map((artist, index) => (
|
||||||
|
<Fragment key={`artist-${artist.id}`}>
|
||||||
|
{index > 0 && (
|
||||||
|
<Text
|
||||||
|
$noSelect
|
||||||
|
sx={{
|
||||||
|
display: 'inline-block',
|
||||||
|
padding: '0 0.5rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
•
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
<Text
|
||||||
|
$link
|
||||||
|
component={Link}
|
||||||
|
fw="600"
|
||||||
|
to={generatePath(AppRoute.LIBRARY_ALBUMARTISTS_DETAIL, {
|
||||||
|
albumArtistId: artist.id,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{artist.name}
|
||||||
|
</Text>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
</MetadataWrapper>
|
||||||
|
</HeaderContainer>
|
||||||
|
);
|
||||||
|
};
|
40
src/renderer/features/albums/routes/album-detail-route.tsx
Normal file
40
src/renderer/features/albums/routes/album-detail-route.tsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { PageHeader, ScrollArea } from '/@/renderer/components';
|
||||||
|
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query';
|
||||||
|
import { useParams } from 'react-router';
|
||||||
|
import { useFastAverageColor } from '/@/renderer/hooks';
|
||||||
|
import { AlbumDetailContent } from '/@/renderer/features/albums/components/album-detail-content';
|
||||||
|
import { AlbumDetailHeader } from '/@/renderer/features/albums/components/album-detail-header';
|
||||||
|
|
||||||
|
const AlbumDetailRoute = () => {
|
||||||
|
const tableRef = useRef<AgGridReactType | null>(null);
|
||||||
|
const { albumId } = useParams() as { albumId: string };
|
||||||
|
const detailQuery = useAlbumDetail({ id: albumId });
|
||||||
|
const background = useFastAverageColor(detailQuery.data?.imageUrl);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatedPage>
|
||||||
|
<PageHeader
|
||||||
|
useOpacity
|
||||||
|
position="absolute"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ScrollArea
|
||||||
|
h="100%"
|
||||||
|
offsetScrollbars={false}
|
||||||
|
styles={{
|
||||||
|
scrollbar: {
|
||||||
|
marginTop: '35px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AlbumDetailHeader background={background} />
|
||||||
|
<AlbumDetailContent tableRef={tableRef} />
|
||||||
|
</ScrollArea>
|
||||||
|
</AnimatedPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AlbumDetailRoute;
|
|
@ -7,6 +7,7 @@ import {
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
import { AppRoute } from './routes';
|
import { AppRoute } from './routes';
|
||||||
import { RouteErrorBoundary } from '/@/renderer/features/action-required';
|
import { RouteErrorBoundary } from '/@/renderer/features/action-required';
|
||||||
|
import AlbumDetailRoute from '/@/renderer/features/albums/routes/album-detail-route';
|
||||||
import HomeRoute from '/@/renderer/features/home/routes/home-route';
|
import HomeRoute from '/@/renderer/features/home/routes/home-route';
|
||||||
import { DefaultLayout } from '/@/renderer/layouts';
|
import { DefaultLayout } from '/@/renderer/layouts';
|
||||||
import { AppOutlet } from '/@/renderer/router/app-outlet';
|
import { AppOutlet } from '/@/renderer/router/app-outlet';
|
||||||
|
@ -54,6 +55,10 @@ export const AppRouter = () => {
|
||||||
element={<AlbumListRoute />}
|
element={<AlbumListRoute />}
|
||||||
path={AppRoute.LIBRARY_ALBUMS}
|
path={AppRoute.LIBRARY_ALBUMS}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
element={<AlbumDetailRoute />}
|
||||||
|
path={AppRoute.LIBRARY_ALBUMS_DETAIL}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
element={<SongListRoute />}
|
element={<SongListRoute />}
|
||||||
path={AppRoute.LIBRARY_SONGS}
|
path={AppRoute.LIBRARY_SONGS}
|
||||||
|
|
Reference in a new issue