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;
|
height: 0.28em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.itemLinearProgress {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
border-radius: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
.itemProgressBarForeground {
|
.itemProgressBarForeground {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
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