1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Move distinct components to individual files

This commit is contained in:
Bill Thornton 2023-06-27 01:31:50 -04:00
parent 97f703f7d7
commit 834e36b5a3
6 changed files with 157 additions and 98 deletions

View file

@ -0,0 +1,17 @@
import React, { type RefAttributes } from 'react';
import { Link } from 'react-router-dom';
import { GridActionsCellItem, type GridActionsCellItemProps } from '@mui/x-data-grid';
type GridActionsCellLinkProps = { to: string } & GridActionsCellItemProps & RefAttributes<HTMLButtonElement>;
/**
* Link component to use in mui's data-grid action column due to a current bug with passing props to custom link components.
* @see https://github.com/mui/mui-x/issues/4654
*/
const GridActionsCellLink = ({ to, ...props }: GridActionsCellLinkProps) => (
<Link to={to}>
<GridActionsCellItem {...props} />
</Link>
);
export default GridActionsCellLink;

View file

@ -0,0 +1,34 @@
import { LogLevel } from '@jellyfin/sdk/lib/generated-client/models/log-level';
import Chip from '@mui/material/Chip';
import React from 'react';
import globalize from 'scripts/globalize';
const LogLevelChip = ({ level }: { level: LogLevel }) => {
let color: 'info' | 'warning' | 'error' | undefined = undefined;
switch (level) {
case LogLevel.Information:
color = 'info';
break;
case LogLevel.Warning:
color = 'warning';
break;
case LogLevel.Error:
case LogLevel.Critical:
color = 'error';
break;
}
const levelText = globalize.translate(`LogLevel.${level}`);
return (
<Chip
size='small'
color={color}
label={levelText}
title={levelText}
/>
);
};
export default LogLevelChip;

View file

@ -0,0 +1,64 @@
import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry';
import Info from '@mui/icons-material/Info';
import Box from '@mui/material/Box';
import ClickAwayListener from '@mui/material/ClickAwayListener';
import IconButton from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';
import React, { FC, useCallback, useState } from 'react';
const OverviewCell: FC<ActivityLogEntry> = ({ Overview, ShortOverview }) => {
const displayValue = ShortOverview ?? Overview;
const [ open, setOpen ] = useState(false);
const onTooltipClose = useCallback(() => {
setOpen(false);
}, []);
const onTooltipOpen = useCallback(() => {
setOpen(true);
}, []);
if (!displayValue) return null;
return (
<Box
sx={{
display: 'flex',
width: '100%',
alignItems: 'center'
}}
>
<Box
sx={{
flexGrow: 1,
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
component='div'
title={displayValue}
>
{displayValue}
</Box>
{ShortOverview && Overview && (
<ClickAwayListener onClickAway={onTooltipClose}>
<Tooltip
title={Overview}
placement='top'
arrow
onClose={onTooltipClose}
open={open}
disableFocusListener
disableHoverListener
disableTouchListener
>
<IconButton onClick={onTooltipOpen}>
<Info />
</IconButton>
</Tooltip>
</ClickAwayListener>
)}
</Box>
);
};
export default OverviewCell;

View file

@ -1,20 +1,16 @@
import React, { FC, useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { getActivityLogApi } from '@jellyfin/sdk/lib/utils/api/activity-log-api'; import { getActivityLogApi } from '@jellyfin/sdk/lib/utils/api/activity-log-api';
import { getUserApi } from '@jellyfin/sdk/lib/utils/api/user-api'; import { getUserApi } from '@jellyfin/sdk/lib/utils/api/user-api';
import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry'; import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry';
import { LogLevel } from '@jellyfin/sdk/lib/generated-client/models/log-level';
import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto'; import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto';
import Info from '@mui/icons-material/Info'; import PermMedia from '@mui/icons-material/PermMedia';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Chip from '@mui/material/Chip';
import ClickAwayListener from '@mui/material/ClickAwayListener';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import ToggleButton from '@mui/material/ToggleButton'; import ToggleButton from '@mui/material/ToggleButton';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { DataGrid, type GridColDef } from '@mui/x-data-grid'; import { DataGrid, type GridColDef } from '@mui/x-data-grid';
import { useSearchParams } from 'react-router-dom'; import { Link, useSearchParams } from 'react-router-dom';
import Page from 'components/Page'; import Page from 'components/Page';
import UserAvatar from 'components/UserAvatar'; import UserAvatar from 'components/UserAvatar';
@ -23,6 +19,10 @@ import { parseISO8601Date, toLocaleDateString, toLocaleTimeString } from 'script
import globalize from 'scripts/globalize'; import globalize from 'scripts/globalize';
import { toBoolean } from 'utils/string'; import { toBoolean } from 'utils/string';
import LogLevelChip from '../../components/activityTable/LogLevelChip';
import OverviewCell from '../../components/activityTable/OverviewCell';
import GridActionsCellLink from '../../components/GridActionsCellLink';
const DEFAULT_PAGE_SIZE = 25; const DEFAULT_PAGE_SIZE = 25;
const VIEW_PARAM = 'useractivity'; const VIEW_PARAM = 'useractivity';
@ -40,88 +40,6 @@ const getActivityView = (param: string | null) => {
const getRowId = (row: ActivityLogEntry) => row.Id ?? -1; const getRowId = (row: ActivityLogEntry) => row.Id ?? -1;
const LogLevelChip = ({ level }: { level: LogLevel }) => {
let color: 'info' | 'warning' | 'error' | undefined = undefined;
switch (level) {
case LogLevel.Information:
color = 'info';
break;
case LogLevel.Warning:
color = 'warning';
break;
case LogLevel.Error:
case LogLevel.Critical:
color = 'error';
break;
}
const levelText = globalize.translate(`LogLevel.${level}`);
return (
<Chip
size='small'
color={color}
label={levelText}
title={levelText}
/>
);
};
const OverviewCell: FC<ActivityLogEntry> = ({ Overview, ShortOverview }) => {
const displayValue = ShortOverview ?? Overview;
const [ open, setOpen ] = useState(false);
const onTooltipClose = useCallback(() => {
setOpen(false);
}, []);
const onTooltipOpen = useCallback(() => {
setOpen(true);
}, []);
if (!displayValue) return null;
return (
<Box
sx={{
display: 'flex',
width: '100%',
alignItems: 'center'
}}
>
<Box
sx={{
flexGrow: 1,
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
component='div'
title={displayValue}
>
{displayValue}
</Box>
{ShortOverview && Overview && (
<ClickAwayListener onClickAway={onTooltipClose}>
<Tooltip
title={Overview}
placement='top'
arrow
onClose={onTooltipClose}
open={open}
disableFocusListener
disableHoverListener
disableTouchListener
>
<IconButton onClick={onTooltipOpen}>
<Info />
</IconButton>
</Tooltip>
</ClickAwayListener>
)}
</Box>
);
};
const Activity = () => { const Activity = () => {
const { api } = useApi(); const { api } = useApi();
const [ searchParams, setSearchParams ] = useSearchParams(); const [ searchParams, setSearchParams ] = useSearchParams();
@ -144,10 +62,16 @@ const Activity = () => {
width: 60, width: 60,
valueGetter: ({ row }) => users[row.UserId]?.Name, valueGetter: ({ row }) => users[row.UserId]?.Name,
renderCell: ({ row }) => ( renderCell: ({ row }) => (
<UserAvatar <IconButton
user={users[row.UserId]} size='large'
showTitle color='inherit'
/> sx={{ padding: 0 }}
title={users[row.UserId]?.Name ?? undefined}
component={Link}
to={`/useredit.html?userId=${row.UserId}`}
>
<UserAvatar user={users[row.UserId]} />
</IconButton>
) )
} }
] : []; ] : [];
@ -188,7 +112,7 @@ const Activity = () => {
{ {
field: 'Overview', field: 'Overview',
headerName: globalize.translate('LabelOverview'), headerName: globalize.translate('LabelOverview'),
width: 220, width: 200,
valueGetter: ({ row }) => row.ShortOverview ?? row.Overview, valueGetter: ({ row }) => row.ShortOverview ?? row.Overview,
renderCell: ({ row }) => ( renderCell: ({ row }) => (
<OverviewCell {...row} /> <OverviewCell {...row} />
@ -197,7 +121,28 @@ const Activity = () => {
{ {
field: 'Type', field: 'Type',
headerName: globalize.translate('LabelType'), headerName: globalize.translate('LabelType'),
width: 150 width: 120
},
{
field: 'actions',
type: 'actions',
getActions: ({ row }) => {
const actions = [];
if (row.ItemId) {
actions.push(
<GridActionsCellLink
size='large'
icon={<PermMedia />}
label={globalize.translate('LabelMediaDetails')}
title={globalize.translate('LabelMediaDetails')}
to={`/details?id=${row.ItemId}`}
/>
);
}
return actions;
}
} }
]; ];

View file

@ -7,17 +7,15 @@ import { useApi } from 'hooks/useApi';
interface UserAvatarProps { interface UserAvatarProps {
user?: UserDto user?: UserDto
showTitle?: boolean
} }
const UserAvatar: FC<UserAvatarProps> = ({ user, showTitle = false }) => { const UserAvatar: FC<UserAvatarProps> = ({ user }) => {
const { api } = useApi(); const { api } = useApi();
const theme = useTheme(); const theme = useTheme();
return user ? ( return user ? (
<Avatar <Avatar
alt={user.Name ?? undefined} alt={user.Name ?? undefined}
title={showTitle && user.Name ? user.Name : undefined}
src={ src={
api && user.Id && user.PrimaryImageTag ? api && user.Id && user.PrimaryImageTag ?
`${api.basePath}/Users/${user.Id}/Images/Primary?tag=${user.PrimaryImageTag}` : `${api.basePath}/Users/${user.Id}/Images/Primary?tag=${user.PrimaryImageTag}` :

View file

@ -730,6 +730,7 @@
"LabelMaxDaysForNextUp": "Max days in 'Next Up'", "LabelMaxDaysForNextUp": "Max days in 'Next Up'",
"LabelMaxDaysForNextUpHelp": "Set the maximum amount of days a show should stay in the 'Next Up' list without watching it.", "LabelMaxDaysForNextUpHelp": "Set the maximum amount of days a show should stay in the 'Next Up' list without watching it.",
"LabelMaxVideoResolution": "Maximum Allowed Video Transcoding Resolution", "LabelMaxVideoResolution": "Maximum Allowed Video Transcoding Resolution",
"LabelMediaDetails": "Media details",
"LabelLineup": "Lineup", "LabelLineup": "Lineup",
"LabelLocalCustomCss": "Custom CSS code for styling which applies to this client only. You may want to disable server custom CSS code.", "LabelLocalCustomCss": "Custom CSS code for styling which applies to this client only. You may want to disable server custom CSS code.",
"LabelLocalHttpServerPortNumber": "Local HTTP port number", "LabelLocalHttpServerPortNumber": "Local HTTP port number",