Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/apps/dashboard/components/Toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React, { useCallback } from 'react';
import Snackbar, { SnackbarProps } from '@mui/material/Snackbar';
import IconButton from '@mui/material/IconButton';
import CloseIcon from '@mui/icons-material/Close';

const Toast = (props: SnackbarProps) => {
const onCloseClick = useCallback((e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
props.onClose?.(e, 'clickaway');
}, [ props ]);

const action = (
<IconButton
size='small'
color='inherit'
onClick={onCloseClick}
>
<CloseIcon fontSize='small' />
</IconButton>
);

return (
<Snackbar
autoHideDuration={3300}
action={action}
{ ...props }
/>
);
};

export default Toast;
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import Box from '@mui/material/Box';
import globalize from 'lib/globalize';
import React, { FunctionComponent, useCallback } from 'react';
import React, { FunctionComponent, useCallback, useState } from 'react';
import Stack from '@mui/material/Stack';
import FormGroup from '@mui/material/FormGroup';
import FormControl from '@mui/material/FormControl';
Expand All @@ -16,7 +16,7 @@ import Checkbox from '@mui/material/Checkbox';
import ContentCopy from '@mui/icons-material/ContentCopy';
import IconButton from '@mui/material/IconButton';
import { copy } from 'scripts/clipboard';
import toast from 'components/toast/toast';
import Toast from 'apps/dashboard/components/Toast';

type IProps = {
backup: BackupManifestDto;
Expand All @@ -25,10 +25,16 @@ type IProps = {
};

const BackupInfoDialog: FunctionComponent<IProps> = ({ backup, open, onClose }: IProps) => {
const [ isCopiedToastOpen, setIsCopiedToastOpen ] = useState(false);

const handleToastClose = useCallback(() => {
setIsCopiedToastOpen(false);
}, []);

const copyPath = useCallback(async () => {
if (backup.Path) {
await copy(backup.Path);
toast({ text: globalize.translate('Copied') });
setIsCopiedToastOpen(true);
}
}, [ backup.Path ]);

Expand All @@ -39,6 +45,11 @@ const BackupInfoDialog: FunctionComponent<IProps> = ({ backup, open, onClose }:
maxWidth={'sm'}
fullWidth
>
<Toast
open={isCopiedToastOpen}
onClose={handleToastClose}
message={globalize.translate('Copied')}
/>
<DialogTitle>
{backup.DateCreated}
</DialogTitle>
Expand Down
16 changes: 13 additions & 3 deletions src/apps/dashboard/routes/logs/file.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Loading from 'components/loading/LoadingComponent';
import Page from 'components/Page';
import React, { useCallback } from 'react';
import React, { useCallback, useState } from 'react';
import { useParams } from 'react-router-dom';
import { useServerLog } from 'apps/dashboard/features/logs/api/useServerLog';
import Alert from '@mui/material/Alert';
Expand All @@ -13,8 +13,8 @@ import Typography from '@mui/material/Typography';
import ContentCopy from '@mui/icons-material/ContentCopy';
import FileDownload from '@mui/icons-material/FileDownload';
import globalize from 'lib/globalize';
import toast from 'components/toast/toast';
import { copy } from 'scripts/clipboard';
import Toast from 'apps/dashboard/components/Toast';

export const Component = () => {
const { file: fileName } = useParams();
Expand All @@ -24,13 +24,18 @@ export const Component = () => {
data: log,
refetch
} = useServerLog(fileName ?? '');
const [ isCopiedToastOpen, setIsCopiedToastOpen ] = useState(false);

const retry = useCallback(() => refetch(), [refetch]);

const handleToastClose = useCallback(() => {
setIsCopiedToastOpen(false);
}, []);

const copyToClipboard = useCallback(async () => {
if (log) {
await copy(log);
toast({ text: globalize.translate('CopyLogSuccess') });
setIsCopiedToastOpen(true);
}
}, [log]);

Expand All @@ -52,6 +57,11 @@ export const Component = () => {
title={fileName}
className='mainAnimatedPage type-interior'
>
<Toast
open={isCopiedToastOpen}
onClose={handleToastClose}
message={globalize.translate('CopyLogSuccess')}
/>
<Container className='content-primary'>
<Box>
<Typography variant='h1'>{fileName}</Typography>
Expand Down
14 changes: 12 additions & 2 deletions src/apps/dashboard/routes/users/access.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { useSearchParams } from 'react-router-dom';

import loading from '../../../../components/loading/loading';
import globalize from '../../../../lib/globalize';
import toast from '../../../../components/toast/toast';
import SectionTabs from '../../../../components/dashboard/users/SectionTabs';
import Button from '../../../../elements/emby-button/Button';
import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
import AccessContainer from '../../../../components/dashboard/users/AccessContainer';
import CheckBoxElement from '../../../../elements/CheckBoxElement';
import Page from '../../../../components/Page';
import Toast from 'apps/dashboard/components/Toast';

type ItemsArr = {
Name?: string | null;
Expand All @@ -23,6 +23,7 @@ type ItemsArr = {
const UserLibraryAccess = () => {
const [ searchParams ] = useSearchParams();
const userId = searchParams.get('userId');
const [ isSettingsSavedToastOpen, setIsSettingsSavedToastOpen ] = useState(false);
const [ userName, setUserName ] = useState('');
const [channelsItems, setChannelsItems] = useState<ItemsArr[]>([]);
const [mediaFoldersItems, setMediaFoldersItems] = useState<ItemsArr[]>([]);
Expand All @@ -31,6 +32,10 @@ const UserLibraryAccess = () => {

const element = useRef<HTMLDivElement>(null);

const handleToastClose = useCallback(() => {
setIsSettingsSavedToastOpen(false);
}, []);

const triggerChange = (select: HTMLInputElement) => {
const evt = new Event('change', { bubbles: false, cancelable: true });
select.dispatchEvent(evt);
Expand Down Expand Up @@ -220,7 +225,7 @@ const UserLibraryAccess = () => {

const onSaveComplete = () => {
loading.hide();
toast(globalize.translate('SettingsSaved'));
setIsSettingsSavedToastOpen(true);
};

(page.querySelector('.chkEnableAllDevices') as HTMLInputElement).addEventListener('change', function (this: HTMLInputElement) {
Expand All @@ -243,6 +248,11 @@ const UserLibraryAccess = () => {
id='userLibraryAccessPage'
className='mainAnimatedPage type-interior'
>
<Toast
open={isSettingsSavedToastOpen}
onClose={handleToastClose}
message={globalize.translate('SettingsSaved')}
/>
<div ref={element} className='content-primary'>
<div className='verticalSection'>
<SectionTitleContainer
Expand Down
14 changes: 12 additions & 2 deletions src/apps/dashboard/routes/users/add.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import React, { useCallback, useEffect, useState, useRef } from 'react';
import Dashboard from '../../../../utils/dashboard';
import globalize from '../../../../lib/globalize';
import loading from '../../../../components/loading/loading';
import toast from '../../../../components/toast/toast';
import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
import Input from '../../../../elements/emby-input/Input';
import Button from '../../../../elements/emby-button/Button';
import AccessContainer from '../../../../components/dashboard/users/AccessContainer';
import CheckBoxElement from '../../../../elements/CheckBoxElement';
import Page from '../../../../components/Page';
import Toast from 'apps/dashboard/components/Toast';

type UserInput = {
Name?: string;
Expand All @@ -25,8 +25,13 @@ type ItemsArr = {
const UserNew = () => {
const [ channelsItems, setChannelsItems ] = useState<ItemsArr[]>([]);
const [ mediaFoldersItems, setMediaFoldersItems ] = useState<ItemsArr[]>([]);
const [ isErrorToastOpen, setIsErrorToastOpen ] = useState(false);
const element = useRef<HTMLDivElement>(null);

const handleToastClose = useCallback(() => {
setIsErrorToastOpen(false);
}, []);

const getItemsResult = (items: BaseItemDto[]) => {
return items.map(item =>
({
Expand Down Expand Up @@ -150,7 +155,7 @@ const UserNew = () => {
console.error('[usernew] failed to update user policy', err);
});
}, function () {
toast(globalize.translate('ErrorDefault'));
setIsErrorToastOpen(true);
loading.hide();
});
};
Expand Down Expand Up @@ -185,6 +190,11 @@ const UserNew = () => {
id='newUserPage'
className='mainAnimatedPage type-interior'
>
<Toast
open={isErrorToastOpen}
onClose={handleToastClose}
message={globalize.translate('ErrorDefault')}
/>
<div ref={element} className='content-primary'>
<div className='verticalSection'>
<SectionTitleContainer
Expand Down
20 changes: 19 additions & 1 deletion src/apps/dashboard/routes/users/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { UserDto } from '@jellyfin/sdk/lib/generated-client';
import React, { useEffect, useState, useRef } from 'react';
import React, { useEffect, useState, useRef, useCallback } from 'react';

import Dashboard from '../../../../utils/dashboard';
import globalize from '../../../../lib/globalize';
Expand All @@ -14,6 +14,8 @@ import '../../../../components/cardbuilder/card.scss';
import '../../../../components/indicators/indicators.scss';
import '../../../../styles/flexstyles.scss';
import Page from '../../../../components/Page';
import { useLocation } from 'react-router-dom';
import Toast from 'apps/dashboard/components/Toast';

type MenuEntry = {
name?: string;
Expand All @@ -22,10 +24,16 @@ type MenuEntry = {
};

const UserProfiles = () => {
const location = useLocation();
const [ isSettingsSavedToastOpen, setIsSettingsSavedToastOpen ] = useState(false);
const [ users, setUsers ] = useState<UserDto[]>([]);

const element = useRef<HTMLDivElement>(null);

const handleToastClose = useCallback(() => {
setIsSettingsSavedToastOpen(false);
}, []);

const loadData = () => {
loading.show();
window.ApiClient.getUsers().then(function (result) {
Expand All @@ -39,6 +47,11 @@ const UserProfiles = () => {
useEffect(() => {
const page = element.current;

if (location.state?.openSavedToast) {
setIsSettingsSavedToastOpen(true);
window.history.replaceState({}, '');
}

if (!page) {
console.error('Unexpected null reference');
return;
Expand Down Expand Up @@ -161,6 +174,11 @@ const UserProfiles = () => {
className='mainAnimatedPage type-interior userProfilesPage fullWidthContent'
title={globalize.translate('HeaderUsers')}
>
<Toast
open={isSettingsSavedToastOpen}
onClose={handleToastClose}
message={globalize.translate('SettingsSaved')}
/>
<div ref={element} className='content-primary'>
<div className='verticalSection'>
<SectionTitleContainer
Expand Down
14 changes: 12 additions & 2 deletions src/apps/dashboard/routes/users/parentalcontrol.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import Button from '../../../../elements/emby-button/Button';
import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
import SectionTabs from '../../../../components/dashboard/users/SectionTabs';
import loading from '../../../../components/loading/loading';
import toast from '../../../../components/toast/toast';
import CheckBoxElement from '../../../../elements/CheckBoxElement';
import SelectElement from '../../../../elements/SelectElement';
import Page from '../../../../components/Page';
import prompt from '../../../../components/prompt/prompt';
import { ServerConnections } from 'lib/jellyfin-apiclient';
import Toast from 'apps/dashboard/components/Toast';

type NamedItem = {
name: string;
Expand Down Expand Up @@ -74,11 +74,16 @@ const UserParentalControl = () => {
const [ accessSchedules, setAccessSchedules ] = useState<AccessSchedule[]>([]);
const [ allowedTags, setAllowedTags ] = useState<string[]>([]);
const [ blockedTags, setBlockedTags ] = useState<string[]>([]);
const [ isSettingsSavedToastOpen, setIsSettingsSavedToastOpen ] = useState(false);
const libraryMenu = useMemo(async () => ((await import('../../../../scripts/libraryMenu')).default), []);

const element = useRef<HTMLDivElement>(null);
const parentalRatingsRef = useRef<ParentalRating[]>([]);

const handleToastClose = useCallback(() => {
setIsSettingsSavedToastOpen(false);
}, []);

const loadUnratedItems = useCallback((user: UserDto) => {
const page = element.current;

Expand Down Expand Up @@ -300,7 +305,7 @@ const UserParentalControl = () => {

const onSaveComplete = () => {
loading.hide();
toast(globalize.translate('SettingsSaved'));
setIsSettingsSavedToastOpen(true);
};

const saveUser = handleSaveUser(page, parentalRatingsRef, getSchedulesFromPage, getAllowedTagsFromPage, getBlockedTagsFromPage, onSaveComplete);
Expand Down Expand Up @@ -387,6 +392,11 @@ const UserParentalControl = () => {
id='userParentalControlPage'
className='mainAnimatedPage type-interior'
>
<Toast
open={isSettingsSavedToastOpen}
onClose={handleToastClose}
message={globalize.translate('SettingsSaved')}
/>
<div ref={element} className='content-primary'>
<div className='verticalSection'>
<SectionTitleContainer
Expand Down
19 changes: 6 additions & 13 deletions src/apps/dashboard/routes/users/profile.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { BaseItemDto, NameIdPair, SyncPlayUserAccessType, UserDto } from '@jellyfin/sdk/lib/generated-client';
import escapeHTML from 'escape-html';
import React, { useCallback, useEffect, useState, useRef, useMemo } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useNavigate, useSearchParams } from 'react-router-dom';

import Dashboard from '../../../../utils/dashboard';
import globalize from '../../../../lib/globalize';
import Button from '../../../../elements/emby-button/Button';
import CheckBoxElement from '../../../../elements/CheckBoxElement';
Expand All @@ -12,7 +11,6 @@ import Input from '../../../../elements/emby-input/Input';
import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
import SectionTabs from '../../../../components/dashboard/users/SectionTabs';
import loading from '../../../../components/loading/loading';
import toast from '../../../../components/toast/toast';
import SelectElement from '../../../../elements/SelectElement';
import Page from '../../../../components/Page';

Expand All @@ -25,16 +23,8 @@ const getCheckedElementDataIds = (elements: NodeListOf<Element>) => (
.map(e => e.getAttribute('data-id'))
);

function onSaveComplete() {
Dashboard.navigate('/dashboard/users')
.catch(err => {
console.error('[useredit] failed to navigate to user profile', err);
});
loading.hide();
toast(globalize.translate('SettingsSaved'));
}

const UserEdit = () => {
const navigate = useNavigate();
const [ searchParams ] = useSearchParams();
const userId = searchParams.get('userId');
const [ userDto, setUserDto ] = useState<UserDto>();
Expand Down Expand Up @@ -228,7 +218,10 @@ const UserEdit = () => {
window.ApiClient.updateUser(user).then(() => (
window.ApiClient.updateUserPolicy(user.Id || '', user.Policy || { PasswordResetProviderId: '', AuthenticationProviderId: '' })
)).then(() => {
onSaveComplete();
navigate('/dashboard/users', {
state: { openSavedToast: true }
});
loading.hide();
}).catch(err => {
console.error('[useredit] failed to update user', err);
});
Expand Down
Loading