mirror of
https://github.com/jeffvli/sonixd.git
synced 2026-05-19 06:08:59 -04:00
Update album page
- Add additional album stats - Add blurred header image Add taglink, title color to album Increase album view image size
This commit is contained in:
@@ -34,7 +34,7 @@ const GenericPage = ({ header, children, hideDivider, ...rest }: any) => {
|
||||
id="page-container"
|
||||
$backgroundSrc={
|
||||
misc.dynamicBackground
|
||||
? !backgroundImage.match('placeholder')
|
||||
? !backgroundImage?.match('placeholder')
|
||||
? backgroundImage
|
||||
: undefined
|
||||
: undefined
|
||||
@@ -43,7 +43,7 @@ const GenericPage = ({ header, children, hideDivider, ...rest }: any) => {
|
||||
<PageHeader
|
||||
id="page-header"
|
||||
padding={rest.padding}
|
||||
style={{ paddingBottom: hideDivider && !rest.padding ? '20px' : '0px' }}
|
||||
style={{ paddingBottom: hideDivider && !rest.padding ? '10px' : '0px' }}
|
||||
>
|
||||
{header}
|
||||
</PageHeader>
|
||||
|
||||
@@ -10,7 +10,12 @@ import {
|
||||
StyledInputGroup,
|
||||
StyledInputGroupButton,
|
||||
} from '../shared/styled';
|
||||
import { CoverArtWrapper, PageHeaderTitle } from './styled';
|
||||
import {
|
||||
CoverArtWrapper,
|
||||
PageHeaderSubtitleWrapper,
|
||||
PageHeaderTitle,
|
||||
PageHeaderWrapper,
|
||||
} from './styled';
|
||||
import cacheImage from '../shared/cacheImage';
|
||||
import CustomTooltip from '../shared/CustomTooltip';
|
||||
|
||||
@@ -31,6 +36,7 @@ const GenericPageHeader = ({
|
||||
viewTypeSetting,
|
||||
cacheImages,
|
||||
showTitleTooltip,
|
||||
isDark,
|
||||
}: any) => {
|
||||
const history = useHistory();
|
||||
const [openSearch, setOpenSearch] = useState(false);
|
||||
@@ -47,8 +53,8 @@ const GenericPageHeader = ({
|
||||
<LazyLoadImage
|
||||
src={image}
|
||||
alt="header-img"
|
||||
height={imageHeight || '145px'}
|
||||
width={imageHeight || '145px'}
|
||||
height={imageHeight || '195px'}
|
||||
width={imageHeight || '195px'}
|
||||
visibleByDefault
|
||||
afterLoad={() => {
|
||||
if (cacheImages.enabled) {
|
||||
@@ -62,13 +68,7 @@ const GenericPageHeader = ({
|
||||
</CoverArtWrapper>
|
||||
)}
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: image ? 'inline-block' : 'undefined',
|
||||
width: image ? 'calc(100% - 160px)' : '100%',
|
||||
marginLeft: image ? '15px' : '0px',
|
||||
}}
|
||||
>
|
||||
<PageHeaderWrapper isDark={isDark} hasImage={image} imageHeight={imageHeight || 195}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
@@ -104,6 +104,7 @@ const GenericPageHeader = ({
|
||||
<Icon icon="search" />
|
||||
</InputGroup.Addon>
|
||||
<StyledInput
|
||||
opacity={0.6}
|
||||
id="local-search-input"
|
||||
value={searchQuery}
|
||||
onChange={handleSearch}
|
||||
@@ -160,18 +161,8 @@ const GenericPageHeader = ({
|
||||
height: '50%',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
alignSelf: 'center',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
width: '60%',
|
||||
}}
|
||||
>
|
||||
{subtitle}
|
||||
</span>
|
||||
<span style={{ alignSelf: 'center' }}>
|
||||
<PageHeaderSubtitleWrapper>{subtitle}</PageHeaderSubtitleWrapper>
|
||||
<span style={{ alignSelf: 'flex-end' }}>
|
||||
{subsidetitle && <span style={{ display: 'inline-block' }}>{subsidetitle}</span>}
|
||||
{showViewTypeButtons && (
|
||||
<span style={{ display: 'inline-block' }}>
|
||||
@@ -184,7 +175,7 @@ const GenericPageHeader = ({
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</PageHeaderWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -107,6 +107,7 @@ const Layout = ({ footer, children, disableSidebar, font }: any) => {
|
||||
<FlexboxGrid
|
||||
justify="space-between"
|
||||
style={{
|
||||
zIndex: 2,
|
||||
padding: '0 10px 0 10px',
|
||||
margin: '10px 5px 5px 5px',
|
||||
}}
|
||||
|
||||
@@ -156,6 +156,7 @@ export const PageHeader = styled(Header)<{ padding?: string }>`
|
||||
export const PageContent = styled(Content)<{ padding?: string }>`
|
||||
position: relative;
|
||||
padding: ${(props) => (props.padding ? props.padding : '10px')};
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
// Sidebar.tsx
|
||||
@@ -205,5 +206,82 @@ export const PageHeaderTitle = styled.h1`
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
font-size: ${(props) => props.theme.fonts.size.pageTitle};
|
||||
font-size: 4vw;
|
||||
|
||||
@media screen and (min-width: 1280px) {
|
||||
font-size: 48px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const PageHeaderWrapper = styled.div<{ $hasImage: boolean; $imageHeight: string }>`
|
||||
display: ${(props) => (props.$hasImage ? 'inline-block' : 'undefined')};
|
||||
width: ${(props) => (props.$hasImage ? `calc(100% - ${props.$imageHeight + 15}px)` : '100%')};
|
||||
margin-left: ${(props) => (props.$hasImage ? '15px' : '0px')};
|
||||
vertical-align: top;
|
||||
color: ${(props) => (props.$hasImage ? '#D8D8D8' : props.theme.colors.layout.page.color)};
|
||||
`;
|
||||
|
||||
export const PageHeaderSubtitleWrapper = styled.span`
|
||||
align-self: center;
|
||||
width: 70%;
|
||||
font-size: 14px;
|
||||
`;
|
||||
|
||||
export const PageHeaderSubtitleDataLine = styled.div<{ $top?: boolean }>`
|
||||
margin-top: ${(props) => (props.$top ? '0px' : '10px')};
|
||||
`;
|
||||
|
||||
export const FlatBackground = styled.div<{ $expanded: boolean; $color: string }>`
|
||||
background: ${(props) => props.$color};
|
||||
top: 32px;
|
||||
left: ${(props) => (props.$expanded ? '165px' : '56px')};
|
||||
height: 200px;
|
||||
position: absolute;
|
||||
width: ${(props) => (props.$expanded ? `calc(100% - 165px)` : 'calc(100% - 56px)')};
|
||||
z-index: 1;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
`;
|
||||
|
||||
export const BlurredBackgroundWrapper = styled.div<{ $expanded: boolean }>`
|
||||
clip: rect(0, auto, auto, 0);
|
||||
-webkit-clip-path: inset(0 0);
|
||||
clip-path: inset(0 0);
|
||||
position: absolute;
|
||||
left: ${(props) => (props.$expanded ? '165px' : '56px')};
|
||||
width: ${(props) => (props.$expanded ? `calc(100% - 165px)` : 'calc(100% - 56px)')};
|
||||
top: 32px;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
background: #0b0908;
|
||||
`;
|
||||
|
||||
export const BlurredBackground = styled.img<{ $expanded: boolean; $image: string }>`
|
||||
background-image: ${(props) => `url(${props.$image})`};
|
||||
background-position: center 30%;
|
||||
background-size: cover;
|
||||
filter: blur(10px) brightness(0.4);
|
||||
|
||||
outline: none !important;
|
||||
border: none !important;
|
||||
margin: 0px !important;
|
||||
padding: 0px !important;
|
||||
width: 100%;
|
||||
height: 202px;
|
||||
z-index: -1;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
`;
|
||||
|
||||
export const GradientBackground = styled.div<{ $expanded: boolean; $color: string }>`
|
||||
background: ${(props) => `linear-gradient(0deg, transparent 10%, ${props.$color} 100%)`};
|
||||
top: 32px;
|
||||
left: ${(props) => (props.$expanded ? '165px' : '56px')};
|
||||
height: calc(100% - 130px);
|
||||
position: absolute;
|
||||
width: ${(props) => (props.$expanded ? `calc(100% - 165px)` : 'calc(100% - 56px)')};
|
||||
z-index: 1;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
`;
|
||||
|
||||
@@ -34,9 +34,20 @@ import GenericPageHeader from '../layout/GenericPageHeader';
|
||||
import { setStatus } from '../../redux/playerSlice';
|
||||
import { addModalPage } from '../../redux/miscSlice';
|
||||
import { notifyToast } from '../shared/toast';
|
||||
import { filterPlayQueue, getPlayedSongsNotification, isCached } from '../../shared/utils';
|
||||
import { StyledLink } from '../shared/styled';
|
||||
import {
|
||||
filterPlayQueue,
|
||||
formatDate,
|
||||
formatDuration,
|
||||
getPlayedSongsNotification,
|
||||
isCached,
|
||||
} from '../../shared/utils';
|
||||
import { StyledTagLink } from '../shared/styled';
|
||||
import { setActive } from '../../redux/albumSlice';
|
||||
import {
|
||||
BlurredBackground,
|
||||
BlurredBackgroundWrapper,
|
||||
PageHeaderSubtitleDataLine,
|
||||
} from '../layout/styled';
|
||||
|
||||
interface AlbumParams {
|
||||
id: string;
|
||||
@@ -171,76 +182,60 @@ const AlbumView = ({ ...rest }: any) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<GenericPage
|
||||
hideDivider
|
||||
header={
|
||||
<GenericPageHeader
|
||||
image={
|
||||
isCached(`${misc.imageCachePath}album_${albumId}.jpg`)
|
||||
? `${misc.imageCachePath}album_${albumId}.jpg`
|
||||
: data.image
|
||||
}
|
||||
cacheImages={{
|
||||
enabled: settings.getSync('cacheImages'),
|
||||
cacheType: 'album',
|
||||
id: data.albumId,
|
||||
}}
|
||||
title={data.name}
|
||||
showTitleTooltip
|
||||
subtitle={
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{data.artist && (
|
||||
<StyledLink
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
if (!rest.isModal) {
|
||||
history.push(`/library/artist/${data.artistId}`);
|
||||
} else {
|
||||
dispatch(
|
||||
addModalPage({
|
||||
pageType: 'artist',
|
||||
id: data.artistId,
|
||||
})
|
||||
);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e: any) => {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (!rest.isModal) {
|
||||
history.push(`/library/artist/${data.artistId}`);
|
||||
} else {
|
||||
dispatch(
|
||||
addModalPage({
|
||||
pageType: 'artist',
|
||||
id: data.artistId,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{data.artist}
|
||||
</StyledLink>
|
||||
)}
|
||||
{data.genre && (
|
||||
<>
|
||||
{' • '}
|
||||
<StyledLink
|
||||
<>
|
||||
{!rest.isModal && (
|
||||
<BlurredBackgroundWrapper
|
||||
image={!data?.image.match('placeholder') ? data.image : null}
|
||||
expanded={misc.expandSidebar}
|
||||
>
|
||||
<BlurredBackground
|
||||
image={!data?.image.match('placeholder') ? data.image : null}
|
||||
expanded={misc.expandSidebar}
|
||||
/>
|
||||
</BlurredBackgroundWrapper>
|
||||
)}
|
||||
|
||||
<GenericPage
|
||||
hideDivider
|
||||
header={
|
||||
<GenericPageHeader
|
||||
isDark={!rest.isModal}
|
||||
image={
|
||||
isCached(`${misc.imageCachePath}album_${albumId}.jpg`)
|
||||
? `${misc.imageCachePath}album_${albumId}.jpg`
|
||||
: data.image
|
||||
}
|
||||
cacheImages={{
|
||||
enabled: settings.getSync('cacheImages'),
|
||||
cacheType: 'album',
|
||||
id: data.albumId,
|
||||
}}
|
||||
imageHeight={200}
|
||||
title={data.name}
|
||||
showTitleTooltip
|
||||
subtitle={
|
||||
<div>
|
||||
<PageHeaderSubtitleDataLine $top>
|
||||
<strong>ALBUM</strong> {' • '} {data.songCount} songs,{' '}
|
||||
{formatDuration(data.duration)}
|
||||
{data.year && (
|
||||
<>
|
||||
{' • '}
|
||||
{data.year}
|
||||
</>
|
||||
)}
|
||||
</PageHeaderSubtitleDataLine>
|
||||
<PageHeaderSubtitleDataLine>
|
||||
Added {formatDate(data.created)}
|
||||
</PageHeaderSubtitleDataLine>
|
||||
<PageHeaderSubtitleDataLine>
|
||||
{data.artist && (
|
||||
<StyledTagLink
|
||||
tabIndex={0}
|
||||
tooltip={data.artist}
|
||||
onClick={() => {
|
||||
if (!rest.isModal) {
|
||||
dispatch(setActive({ ...album.active, filter: data.genre }));
|
||||
setTimeout(() => {
|
||||
history.push(`/library/album?sortType=${data.genre}`);
|
||||
}, 50);
|
||||
history.push(`/library/artist/${data.artistId}`);
|
||||
} else {
|
||||
dispatch(
|
||||
addModalPage({
|
||||
@@ -253,6 +248,28 @@ const AlbumView = ({ ...rest }: any) => {
|
||||
onKeyDown={(e: any) => {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (!rest.isModal) {
|
||||
history.push(`/library/artist/${data.artistId}`);
|
||||
} else {
|
||||
dispatch(
|
||||
addModalPage({
|
||||
pageType: 'artist',
|
||||
id: data.artistId,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{data.artist}
|
||||
</StyledTagLink>
|
||||
)}
|
||||
{data.genre && (
|
||||
<>
|
||||
<StyledTagLink
|
||||
tabIndex={0}
|
||||
tooltip={data.genre}
|
||||
onClick={() => {
|
||||
if (!rest.isModal) {
|
||||
dispatch(setActive({ ...album.active, filter: data.genre }));
|
||||
setTimeout(() => {
|
||||
@@ -266,71 +283,82 @@ const AlbumView = ({ ...rest }: any) => {
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{data.genre}
|
||||
</StyledLink>
|
||||
</>
|
||||
)}
|
||||
|
||||
{data.year && (
|
||||
<>
|
||||
{' • '}
|
||||
{data.year}
|
||||
</>
|
||||
)}
|
||||
}}
|
||||
onKeyDown={(e: any) => {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (!rest.isModal) {
|
||||
dispatch(setActive({ ...album.active, filter: data.genre }));
|
||||
setTimeout(() => {
|
||||
history.push(`/library/album?sortType=${data.genre}`);
|
||||
}, 50);
|
||||
} else {
|
||||
dispatch(
|
||||
addModalPage({
|
||||
pageType: 'artist',
|
||||
id: data.artistId,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{data.genre}
|
||||
</StyledTagLink>
|
||||
</>
|
||||
)}
|
||||
</PageHeaderSubtitleDataLine>
|
||||
<div style={{ marginTop: '10px' }}>
|
||||
<ButtonToolbar>
|
||||
<PlayButton appearance="primary" size="lg" onClick={handlePlay} />
|
||||
<PlayAppendNextButton
|
||||
appearance="primary"
|
||||
size="lg"
|
||||
onClick={() => handlePlayAppend('next')}
|
||||
/>
|
||||
<PlayAppendButton
|
||||
appearance="primary"
|
||||
size="lg"
|
||||
onClick={() => handlePlayAppend('later')}
|
||||
/>
|
||||
<FavoriteButton size="lg" isFavorite={data.starred} onClick={handleFavorite} />
|
||||
</ButtonToolbar>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginTop: '10px' }}>
|
||||
<ButtonToolbar>
|
||||
<PlayButton appearance="primary" size="md" onClick={handlePlay} />
|
||||
<PlayAppendNextButton
|
||||
appearance="primary"
|
||||
size="md"
|
||||
onClick={() => handlePlayAppend('next')}
|
||||
/>
|
||||
<PlayAppendButton
|
||||
appearance="primary"
|
||||
size="md"
|
||||
onClick={() => handlePlayAppend('later')}
|
||||
/>
|
||||
<FavoriteButton size="md" isFavorite={data.starred} onClick={handleFavorite} />
|
||||
</ButtonToolbar>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
searchQuery={searchQuery}
|
||||
handleSearch={(e: any) => setSearchQuery(e)}
|
||||
clearSearchQuery={() => setSearchQuery('')}
|
||||
showSearchBar
|
||||
}
|
||||
searchQuery={searchQuery}
|
||||
handleSearch={(e: any) => setSearchQuery(e)}
|
||||
clearSearchQuery={() => setSearchQuery('')}
|
||||
showSearchBar
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ListViewType
|
||||
data={searchQuery !== '' ? filteredData : data.song}
|
||||
tableColumns={settings.getSync('musicListColumns')}
|
||||
handleRowClick={handleRowClick}
|
||||
handleRowDoubleClick={handleRowDoubleClick}
|
||||
tableHeight={700}
|
||||
virtualized
|
||||
rowHeight={Number(settings.getSync('musicListRowHeight'))}
|
||||
fontSize={Number(settings.getSync('musicListFontSize'))}
|
||||
cacheImages={{
|
||||
enabled: settings.getSync('cacheImages'),
|
||||
cacheType: 'album',
|
||||
cacheIdProperty: 'albumId',
|
||||
}}
|
||||
listType="music"
|
||||
isModal={rest.isModal}
|
||||
disabledContextMenuOptions={[
|
||||
'removeSelected',
|
||||
'moveSelectedTo',
|
||||
'deletePlaylist',
|
||||
'viewInModal',
|
||||
]}
|
||||
handleFavorite={handleRowFavorite}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ListViewType
|
||||
data={searchQuery !== '' ? filteredData : data.song}
|
||||
tableColumns={settings.getSync('musicListColumns')}
|
||||
handleRowClick={handleRowClick}
|
||||
handleRowDoubleClick={handleRowDoubleClick}
|
||||
tableHeight={700}
|
||||
virtualized
|
||||
rowHeight={Number(settings.getSync('musicListRowHeight'))}
|
||||
fontSize={Number(settings.getSync('musicListFontSize'))}
|
||||
cacheImages={{
|
||||
enabled: settings.getSync('cacheImages'),
|
||||
cacheType: 'album',
|
||||
cacheIdProperty: 'albumId',
|
||||
}}
|
||||
listType="music"
|
||||
isModal={rest.isModal}
|
||||
disabledContextMenuOptions={[
|
||||
'removeSelected',
|
||||
'moveSelectedTo',
|
||||
'deletePlaylist',
|
||||
'viewInModal',
|
||||
]}
|
||||
handleFavorite={handleRowFavorite}
|
||||
/>
|
||||
</GenericPage>
|
||||
</GenericPage>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
13
src/components/shared/TagLink.tsx
Normal file
13
src/components/shared/TagLink.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import { Tag } from 'rsuite';
|
||||
import CustomTooltip from './CustomTooltip';
|
||||
|
||||
const TagLink = ({ tooltip, children, ...rest }: any) => {
|
||||
return (
|
||||
<CustomTooltip text={tooltip}>
|
||||
<Tag {...rest}>{children}</Tag>
|
||||
</CustomTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default TagLink;
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
Tag,
|
||||
} from 'rsuite';
|
||||
import styled from 'styled-components';
|
||||
import TagLink from './TagLink';
|
||||
|
||||
export const HeaderButton = styled(Button)`
|
||||
margin-left: 5px;
|
||||
@@ -140,7 +141,7 @@ export const StyledInputNumber = styled(InputNumber)<{ width: number }>`
|
||||
width: ${(props) => `${props.width}px`};
|
||||
`;
|
||||
|
||||
export const StyledInput = styled(Input)<{ width: number }>`
|
||||
export const StyledInput = styled(Input)<{ width: number; opacity?: number }>`
|
||||
border: 1px #3c3f43 solid !important;
|
||||
border-radius: ${(props) => props.theme.other.input.borderRadius} !important;
|
||||
|
||||
@@ -148,6 +149,7 @@ export const StyledInput = styled(Input)<{ width: number }>`
|
||||
background: ${(props) => props.theme.colors.input.background} !important;
|
||||
width: ${(props) => `${props.width}px`};
|
||||
border-radius: ${(props) => props.theme.other.input.borderRadius};
|
||||
opacity: ${(props) => props.opacity};
|
||||
`;
|
||||
|
||||
export const StyledCheckbox = styled(Checkbox)`
|
||||
@@ -512,5 +514,17 @@ export const StyledTag = styled(Tag)`
|
||||
color: ${(props) => props.theme.colors.tag.text} !important;
|
||||
background: ${(props) => props.theme.colors.tag.background};
|
||||
border-radius: ${(props) => props.theme.other.tag.borderRadius};
|
||||
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const StyledTagLink = styled(TagLink)`
|
||||
color: ${(props) => props.theme.colors.tag.text} !important;
|
||||
background: ${(props) => props.theme.colors.tag.background};
|
||||
border-radius: ${(props) => props.theme.other.tag.borderRadius};
|
||||
|
||||
max-width: 13rem;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
@@ -118,6 +118,22 @@ export const formatSongDuration = (duration: number) => {
|
||||
return `${minutes}:${seconds}`;
|
||||
};
|
||||
|
||||
export const formatDuration = (duration: number) => {
|
||||
const hours = Math.floor(duration / 60 / 60);
|
||||
const minutes = Math.floor((duration / 60) % 60);
|
||||
const seconds = String(duration % 60).padStart(2, '0');
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours} hr ${minutes} min ${seconds} sec`;
|
||||
}
|
||||
|
||||
if (Number.isNaN(minutes)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `${minutes} min ${seconds} sec`;
|
||||
};
|
||||
|
||||
export const formatDate = (date: string) => {
|
||||
return moment(date).format('MMM D YYYY');
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user