mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Migrate Indicator to react
This commit is contained in:
parent
090e2991cb
commit
2e90f669e5
4 changed files with 353 additions and 0 deletions
|
@ -5,6 +5,14 @@
|
|||
height: 0.28em;
|
||||
}
|
||||
|
||||
.itemLinearProgress {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.itemProgressBarForeground {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
|
260
src/components/indicators/useIndicator.tsx
Normal file
260
src/components/indicators/useIndicator.tsx
Normal file
|
@ -0,0 +1,260 @@
|
|||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||
import { LocationType } from '@jellyfin/sdk/lib/generated-client';
|
||||
import React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import LinearProgress, {
|
||||
linearProgressClasses
|
||||
} from '@mui/material/LinearProgress';
|
||||
import FiberSmartRecordIcon from '@mui/icons-material/FiberSmartRecord';
|
||||
import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord';
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
import VideocamIcon from '@mui/icons-material/Videocam';
|
||||
import FolderIcon from '@mui/icons-material/Folder';
|
||||
import PhotoAlbumIcon from '@mui/icons-material/PhotoAlbum';
|
||||
import PhotoIcon from '@mui/icons-material/Photo';
|
||||
import classNames from 'classnames';
|
||||
import datetime from 'scripts/datetime';
|
||||
import itemHelper from 'components/itemHelper';
|
||||
import AutoTimeProgressBar from 'elements/emby-progressbar/AutoTimeProgressBar';
|
||||
import type { ItemDto, NullableString } from 'types/itemDto';
|
||||
import type { ProgressOptions } from 'types/progressOptions';
|
||||
|
||||
const TypeIcon = {
|
||||
Video: <VideocamIcon className='indicatorIcon' />,
|
||||
Folder: <FolderIcon className='indicatorIcon' />,
|
||||
PhotoAlbum: <PhotoAlbumIcon className='indicatorIcon' />,
|
||||
Photo: <PhotoIcon className='indicatorIcon' />
|
||||
};
|
||||
|
||||
const getTypeIcon = (itemType: NullableString) => {
|
||||
return TypeIcon[itemType as keyof typeof TypeIcon];
|
||||
};
|
||||
|
||||
const enableProgressIndicator = (
|
||||
itemType: NullableString,
|
||||
itemMediaType: NullableString
|
||||
) => {
|
||||
return (
|
||||
(itemMediaType === 'Video' && itemType !== BaseItemKind.TvChannel)
|
||||
|| itemType === BaseItemKind.AudioBook
|
||||
|| itemType === 'AudioPodcast'
|
||||
);
|
||||
};
|
||||
|
||||
const enableAutoTimeProgressIndicator = (
|
||||
itemType: NullableString,
|
||||
itemStartDate: NullableString,
|
||||
itemEndDate: NullableString
|
||||
) => {
|
||||
return (
|
||||
(itemType === BaseItemKind.Program
|
||||
|| itemType === 'Timer'
|
||||
|| itemType === BaseItemKind.Recording)
|
||||
&& Boolean(itemStartDate)
|
||||
&& Boolean(itemEndDate)
|
||||
);
|
||||
};
|
||||
|
||||
const enablePlayedIndicator = (item: ItemDto) => {
|
||||
return itemHelper.canMarkPlayed(item);
|
||||
};
|
||||
|
||||
const useIndicator = (item: ItemDto) => {
|
||||
const getMediaSourceIndicator = () => {
|
||||
const mediaSourceCount = item.MediaSourceCount ?? 0;
|
||||
if (mediaSourceCount > 1) {
|
||||
return <Box className='mediaSourceIndicator'>mediaSourceCount</Box>;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const getMissingIndicator = () => {
|
||||
if (
|
||||
item.Type === BaseItemKind.Episode
|
||||
&& item.LocationType === LocationType.Virtual
|
||||
) {
|
||||
if (item.PremiereDate) {
|
||||
try {
|
||||
const premiereDate = datetime
|
||||
.parseISO8601Date(item.PremiereDate)
|
||||
.getTime();
|
||||
if (premiereDate > new Date().getTime()) {
|
||||
return <Box className='unairedIndicator'>Unaired</Box>;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
return <Box className='missingIndicator'>Missing</Box>;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const getTimerIndicator = (className?: string) => {
|
||||
const indicatorIconClass = classNames('timerIndicator', className);
|
||||
|
||||
let status;
|
||||
|
||||
if (item.Type === 'SeriesTimer') {
|
||||
return <FiberSmartRecordIcon className={indicatorIconClass} />;
|
||||
} else if (item.TimerId || item.SeriesTimerId) {
|
||||
status = item.Status || 'Cancelled';
|
||||
} else if (item.Type === 'Timer') {
|
||||
status = item.Status;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (item.SeriesTimerId) {
|
||||
return (
|
||||
<FiberSmartRecordIcon
|
||||
className={`${indicatorIconClass} ${
|
||||
status === 'Cancelled' ? 'timerIndicator-inactive' : ''
|
||||
}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <FiberManualRecordIcon className={indicatorIconClass} />;
|
||||
};
|
||||
|
||||
const getTypeIndicator = () => {
|
||||
const icon = getTypeIcon(item.Type);
|
||||
if (icon) {
|
||||
return <Box className='indicator videoIndicator'>{icon}</Box>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getChildCountIndicator = () => {
|
||||
const childCount = item.ChildCount ?? 0;
|
||||
|
||||
if (childCount > 1) {
|
||||
return (
|
||||
<Box className='countIndicator indicator childCountIndicator'>
|
||||
{datetime.toLocaleString(item.ChildCount)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const getPlayedIndicator = () => {
|
||||
if (enablePlayedIndicator(item)) {
|
||||
const userData = item.UserData || {};
|
||||
if (userData.UnplayedItemCount) {
|
||||
return (
|
||||
<Box className='countIndicator indicator unplayedItemCount'>
|
||||
{datetime.toLocaleString(userData.UnplayedItemCount)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
(userData.PlayedPercentage
|
||||
&& userData.PlayedPercentage >= 100)
|
||||
|| userData.Played
|
||||
) {
|
||||
return (
|
||||
<Box className='playedIndicator indicator'>
|
||||
<CheckIcon className='indicatorIcon' />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const getProgress = (pct: number, progressOptions?: ProgressOptions) => {
|
||||
const progressBarClass = classNames(
|
||||
'itemLinearProgress',
|
||||
progressOptions?.containerClass
|
||||
);
|
||||
|
||||
return (
|
||||
<LinearProgress
|
||||
className={progressBarClass}
|
||||
variant='determinate'
|
||||
value={pct}
|
||||
sx={{
|
||||
[`& .${linearProgressClasses.bar}`]: {
|
||||
borderRadius: 5,
|
||||
backgroundColor: '#00a4dc'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getProgressBar = (progressOptions?: ProgressOptions) => {
|
||||
if (
|
||||
enableProgressIndicator(item.Type, item.MediaType)
|
||||
&& item.Type !== 'Recording'
|
||||
) {
|
||||
const playedPercentage = progressOptions?.userData?.PlayedPercentage ?
|
||||
progressOptions.userData.PlayedPercentage :
|
||||
item?.UserData?.PlayedPercentage;
|
||||
if (playedPercentage && playedPercentage < 100) {
|
||||
return getProgress(playedPercentage);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
enableAutoTimeProgressIndicator(
|
||||
item.Type,
|
||||
item.StartDate,
|
||||
item.EndDate
|
||||
)
|
||||
) {
|
||||
let startDate = 0;
|
||||
let endDate = 1;
|
||||
|
||||
try {
|
||||
startDate = datetime.parseISO8601Date(item.StartDate).getTime();
|
||||
endDate = datetime.parseISO8601Date(item.EndDate).getTime();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
const now = new Date().getTime();
|
||||
const total = endDate - startDate;
|
||||
const pct = 100 * ((now - startDate) / total);
|
||||
|
||||
if (pct > 0 && pct < 100) {
|
||||
const isRecording =
|
||||
item.Type === 'Timer'
|
||||
|| item.Type === BaseItemKind.Recording
|
||||
|| Boolean(item.TimerId);
|
||||
return (
|
||||
<AutoTimeProgressBar
|
||||
pct={pct}
|
||||
progressOptions={progressOptions}
|
||||
isRecording={isRecording}
|
||||
starTtime={startDate}
|
||||
endTtime={endDate}
|
||||
dataAutoMode='time'
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return {
|
||||
getProgress,
|
||||
getProgressBar,
|
||||
getMediaSourceIndicator,
|
||||
getMissingIndicator,
|
||||
getTimerIndicator,
|
||||
getTypeIndicator,
|
||||
getChildCountIndicator,
|
||||
getPlayedIndicator
|
||||
};
|
||||
};
|
||||
|
||||
export default useIndicator;
|
77
src/elements/emby-progressbar/AutoTimeProgressBar.tsx
Normal file
77
src/elements/emby-progressbar/AutoTimeProgressBar.tsx
Normal file
|
@ -0,0 +1,77 @@
|
|||
import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { ProgressOptions } from 'types/progressOptions';
|
||||
import LinearProgress, { linearProgressClasses } from '@mui/material/LinearProgress';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface AutoTimeProgressBarProps {
|
||||
pct: number;
|
||||
starTtime: number;
|
||||
endTtime: number;
|
||||
isRecording: boolean;
|
||||
dataAutoMode?: string;
|
||||
progressOptions?: ProgressOptions;
|
||||
}
|
||||
|
||||
const AutoTimeProgressBar: FC<AutoTimeProgressBarProps> = ({
|
||||
pct,
|
||||
dataAutoMode,
|
||||
isRecording,
|
||||
starTtime,
|
||||
endTtime,
|
||||
progressOptions
|
||||
}) => {
|
||||
const [progress, setProgress] = useState(pct);
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const onAutoTimeProgress = useCallback(() => {
|
||||
const start = parseInt(starTtime.toString(), 10);
|
||||
const end = parseInt(endTtime.toString(), 10);
|
||||
|
||||
const now = new Date().getTime();
|
||||
const total = end - start;
|
||||
let percentage = 100 * ((now - start) / total);
|
||||
|
||||
percentage = Math.min(100, percentage);
|
||||
percentage = Math.max(0, percentage);
|
||||
|
||||
setProgress(percentage);
|
||||
}, [endTtime, starTtime]);
|
||||
|
||||
useEffect(() => {
|
||||
if (timerRef.current) {
|
||||
clearInterval(timerRef.current);
|
||||
}
|
||||
|
||||
if (dataAutoMode === 'time') {
|
||||
timerRef.current = setInterval(onAutoTimeProgress, 60000);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (timerRef.current) {
|
||||
clearInterval(timerRef.current);
|
||||
timerRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [dataAutoMode, onAutoTimeProgress]);
|
||||
|
||||
const progressBarClass = classNames(
|
||||
'itemLinearProgress',
|
||||
progressOptions?.containerClass
|
||||
);
|
||||
|
||||
return (
|
||||
<LinearProgress
|
||||
className={progressBarClass}
|
||||
variant='determinate'
|
||||
value={progress}
|
||||
sx={{
|
||||
[`& .${linearProgressClasses.bar}`]: {
|
||||
borderRadius: 5,
|
||||
backgroundColor: isRecording ? '#cb272a' : '#00a4dc'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AutoTimeProgressBar;
|
8
src/types/progressOptions.ts
Normal file
8
src/types/progressOptions.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { UserItemDataDto } from '@jellyfin/sdk/lib/generated-client';
|
||||
|
||||
export interface ProgressOptions {
|
||||
containerClass: string,
|
||||
type?: string | null,
|
||||
userData?: UserItemDataDto,
|
||||
mediaType?: string
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue