From 834e36b5a3cb9fbdd1d23e25bdefeb518f59e01c Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 27 Jun 2023 01:31:50 -0400 Subject: [PATCH] Move distinct components to individual files --- .../components/GridActionsCellLink.tsx | 17 +++ .../components/activityTable/LogLevelChip.tsx | 34 +++++ .../components/activityTable/OverviewCell.tsx | 64 +++++++++ .../routes/dashboard/activity.tsx | 135 ++++++------------ src/components/UserAvatar.tsx | 4 +- src/strings/en-us.json | 1 + 6 files changed, 157 insertions(+), 98 deletions(-) create mode 100644 src/apps/experimental/components/GridActionsCellLink.tsx create mode 100644 src/apps/experimental/components/activityTable/LogLevelChip.tsx create mode 100644 src/apps/experimental/components/activityTable/OverviewCell.tsx diff --git a/src/apps/experimental/components/GridActionsCellLink.tsx b/src/apps/experimental/components/GridActionsCellLink.tsx new file mode 100644 index 0000000000..faf7619c9e --- /dev/null +++ b/src/apps/experimental/components/GridActionsCellLink.tsx @@ -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; + +/** + * 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) => ( + + + +); + +export default GridActionsCellLink; diff --git a/src/apps/experimental/components/activityTable/LogLevelChip.tsx b/src/apps/experimental/components/activityTable/LogLevelChip.tsx new file mode 100644 index 0000000000..8053f770c1 --- /dev/null +++ b/src/apps/experimental/components/activityTable/LogLevelChip.tsx @@ -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 ( + + ); +}; + +export default LogLevelChip; diff --git a/src/apps/experimental/components/activityTable/OverviewCell.tsx b/src/apps/experimental/components/activityTable/OverviewCell.tsx new file mode 100644 index 0000000000..69702de5f6 --- /dev/null +++ b/src/apps/experimental/components/activityTable/OverviewCell.tsx @@ -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 = ({ 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 ( + + + {displayValue} + + {ShortOverview && Overview && ( + + + + + + + + )} + + ); +}; + +export default OverviewCell; diff --git a/src/apps/experimental/routes/dashboard/activity.tsx b/src/apps/experimental/routes/dashboard/activity.tsx index 159096aeea..f007e104d3 100644 --- a/src/apps/experimental/routes/dashboard/activity.tsx +++ b/src/apps/experimental/routes/dashboard/activity.tsx @@ -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 { getUserApi } from '@jellyfin/sdk/lib/utils/api/user-api'; 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 Info from '@mui/icons-material/Info'; +import PermMedia from '@mui/icons-material/PermMedia'; 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 ToggleButton from '@mui/material/ToggleButton'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; -import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; 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 UserAvatar from 'components/UserAvatar'; @@ -23,6 +19,10 @@ import { parseISO8601Date, toLocaleDateString, toLocaleTimeString } from 'script import globalize from 'scripts/globalize'; 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 VIEW_PARAM = 'useractivity'; @@ -40,88 +40,6 @@ const getActivityView = (param: string | null) => { 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 ( - - ); -}; - -const OverviewCell: FC = ({ 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 ( - - - {displayValue} - - {ShortOverview && Overview && ( - - - - - - - - )} - - ); -}; - const Activity = () => { const { api } = useApi(); const [ searchParams, setSearchParams ] = useSearchParams(); @@ -144,10 +62,16 @@ const Activity = () => { width: 60, valueGetter: ({ row }) => users[row.UserId]?.Name, renderCell: ({ row }) => ( - + + + ) } ] : []; @@ -188,7 +112,7 @@ const Activity = () => { { field: 'Overview', headerName: globalize.translate('LabelOverview'), - width: 220, + width: 200, valueGetter: ({ row }) => row.ShortOverview ?? row.Overview, renderCell: ({ row }) => ( @@ -197,7 +121,28 @@ const Activity = () => { { field: 'Type', headerName: globalize.translate('LabelType'), - width: 150 + width: 120 + }, + { + field: 'actions', + type: 'actions', + getActions: ({ row }) => { + const actions = []; + + if (row.ItemId) { + actions.push( + } + label={globalize.translate('LabelMediaDetails')} + title={globalize.translate('LabelMediaDetails')} + to={`/details?id=${row.ItemId}`} + /> + ); + } + + return actions; + } } ]; diff --git a/src/components/UserAvatar.tsx b/src/components/UserAvatar.tsx index 0f17802805..3a2790e835 100644 --- a/src/components/UserAvatar.tsx +++ b/src/components/UserAvatar.tsx @@ -7,17 +7,15 @@ import { useApi } from 'hooks/useApi'; interface UserAvatarProps { user?: UserDto - showTitle?: boolean } -const UserAvatar: FC = ({ user, showTitle = false }) => { +const UserAvatar: FC = ({ user }) => { const { api } = useApi(); const theme = useTheme(); return user ? (