Skip to content

Commit

Permalink
feat: import/export settings
Browse files Browse the repository at this point in the history
  • Loading branch information
AXeL-dev committed Sep 10, 2022
1 parent 9ec20b8 commit b8d934e
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 45 deletions.
1 change: 1 addition & 0 deletions src/ui/components/pages/Channels/ChannelList/Actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ function ChannelListActions(props: ChannelListActionsProps) {
}}
accept=".json"
onClick={(event: MouseEvent<HTMLInputElement>) => {
event.stopPropagation();
event.currentTarget.value = '';
}}
onChange={importChannels}
Expand Down
1 change: 1 addition & 0 deletions src/ui/components/pages/Channels/NoChannels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export default function NoChannels(props: NoChannelsProps) {
}}
accept=".json"
onClick={(event: MouseEvent<HTMLInputElement>) => {
event.stopPropagation();
event.currentTarget.value = '';
}}
onChange={importChannels}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ function ImportVideosData(props: IImportVideosDataProps) {
}}
accept=".json"
onClick={(event: MouseEvent<HTMLInputElement>) => {
event.stopPropagation();
event.currentTarget.value = '';
}}
onChange={importVideos}
Expand Down
42 changes: 42 additions & 0 deletions src/ui/components/shared/Sidebar/Actions/HomeActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React, { useState } from 'react';
import { IconButton } from '@mui/material';
import { Nullable } from 'types';
import DisplaySettingsIcon from '@mui/icons-material/DisplaySettings';
import HomeDisplayOptionsDialog from 'ui/components/pages/Home/DisplayOptionsDialog';

interface HomeActionsProps {}

export function HomeActions(props: HomeActionsProps) {
const [openedDialog, setOpenedDialog] = useState<Nullable<string>>(null);

const openDialog = (dialog: string) => {
setOpenedDialog(dialog);
};

const closeDialog = () => {
setOpenedDialog(null);
};

return (
<>
<IconButton
aria-label="display-options"
color="default"
size="small"
sx={{
position: 'absolute',
right: 0,
backgroundColor: (theme) => theme.palette.primary.dark,
color: (theme) => theme.palette.common.white,
}}
onClick={() => openDialog('display-options')}
>
<DisplaySettingsIcon fontSize="small" />
</IconButton>
<HomeDisplayOptionsDialog
open={openedDialog === 'display-options'}
onClose={closeDialog}
/>
</>
);
}
129 changes: 129 additions & 0 deletions src/ui/components/shared/Sidebar/Actions/SettingsActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import React, { useState, useRef, ChangeEvent, MouseEvent } from 'react';
import { Nullable } from 'types';
import {
IconButton,
MenuItem,
ListItemIcon,
ListItemText,
} from '@mui/material';
import { StyledMenu } from 'ui/components/shared';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import DownloadIcon from '@mui/icons-material/Download';
import UploadIcon from '@mui/icons-material/Upload';
import { useAppSelector, useAppDispatch } from 'store';
import { selectSettings } from 'store/selectors/settings';
import { downloadFile, readFile } from 'helpers/file';
import { setSettings } from 'store/reducers/settings';

interface SettingsActionsProps {}

export function SettingsActions(props: SettingsActionsProps) {
const settings = useAppSelector(selectSettings);
const dispatch = useAppDispatch();
const [anchorEl, setAnchorEl] = useState<Nullable<HTMLElement>>(null);
const fileInputRef = useRef<Nullable<HTMLInputElement>>(null);
const open = Boolean(anchorEl);

const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};

const handleClose = () => {
setAnchorEl(null);
};

const handleImport = (event: ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) {
return;
}
try {
readFile(file)
.then((content) => {
const data = JSON.parse(content as string);
dispatch(setSettings(data));
})
.finally(() => {
handleClose();
});
} catch (e) {
console.error(e);
}
};

const handleExport = () => {
const data = JSON.stringify(settings, null, 4);
const file = new Blob([data], { type: 'text/json' });
downloadFile(file, 'settings.json');
handleClose();
};

return (
<>
<IconButton
id="settings-actions-button"
aria-label="settings-actions"
aria-controls={open ? 'settings-actions-menu' : undefined}
aria-expanded={open ? 'true' : undefined}
aria-haspopup="true"
color="default"
size="small"
sx={{
position: 'absolute',
right: 0,
backgroundColor: (theme) => theme.palette.primary.dark,
color: (theme) => theme.palette.common.white,
}}
onClick={handleClick}
>
<MoreVertIcon fontSize="small" />
</IconButton>
<StyledMenu
id="settings-actions-menu"
MenuListProps={{
'aria-labelledby': 'settings-actions-button',
dense: true,
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
>
<MenuItem onClick={handleExport}>
<ListItemIcon>
<DownloadIcon />
</ListItemIcon>
<ListItemText>Export</ListItemText>
</MenuItem>
<MenuItem
onClick={() => {
fileInputRef.current?.click();
}}
>
<ListItemIcon>
<UploadIcon />
</ListItemIcon>
<ListItemText>Import</ListItemText>
</MenuItem>
</StyledMenu>
<input
type="file"
ref={fileInputRef}
style={{
display: 'none',
visibility: 'hidden',
overflow: 'hidden',
width: 0,
height: 0,
}}
accept=".json"
onClick={(event: MouseEvent<HTMLInputElement>) => {
event.stopPropagation();
event.currentTarget.value = '';
}}
onChange={handleImport}
/>
</>
);
}
2 changes: 2 additions & 0 deletions src/ui/components/shared/Sidebar/Actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './HomeActions';
export * from './SettingsActions';
7 changes: 1 addition & 6 deletions src/ui/components/shared/Sidebar/ListItemLink/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from 'react';
import { Box } from '@mui/material';
import { Link, LinkProps, useLocation } from 'react-router-dom';
import ListItemIcon from '@mui/material/ListItemIcon';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import ListItem from './ListItem';
import ListItemText from './ListItemText';
import Badge from './Badge';
Expand All @@ -12,12 +11,11 @@ interface ListItemLinkProps {
text: string;
badge?: React.ReactNode;
actions?: (selected: boolean) => React.ReactNode;
hasWarning?: boolean;
to: string;
}

export default function ListItemLink(props: ListItemLinkProps) {
const { icon, text, badge, actions, hasWarning, to } = props;
const { icon, text, badge, actions, to } = props;
const location = useLocation();
const selected = location.pathname === to;

Expand Down Expand Up @@ -47,9 +45,6 @@ export default function ListItemLink(props: ListItemLinkProps) {
{text}
{badge && selected ? <Badge badgeContent={badge} /> : null}
{actions && actions(selected)}
{hasWarning && !selected ? (
<ErrorOutlineIcon sx={{ ml: 3 }} color="warning" />
) : null}
</Box>
}
/>
Expand Down
51 changes: 12 additions & 39 deletions src/ui/components/shared/Sidebar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,25 @@
import React, { useState } from 'react';
import { Box, Typography, Link, List, IconButton } from '@mui/material';
import React from 'react';
import { Box, Typography, Link, List } from '@mui/material';
import ExploreIcon from '@mui/icons-material/Explore';
import SubscriptionsIcon from '@mui/icons-material/Subscriptions';
import SettingsIcon from '@mui/icons-material/Settings';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import ContactSupportIcon from '@mui/icons-material/ContactSupport';
import DisplaySettingsIcon from '@mui/icons-material/DisplaySettings';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import ListItemLink from './ListItemLink';
import Header from './Header';
import { useAppSelector } from 'store';
import { selectChannelsCount } from 'store/selectors/channels';
import { selectSettings } from 'store/selectors/settings';
import { selectApp } from 'store/selectors/app';
import { Nullable } from 'types';
import HomeDisplayOptionsDialog from 'ui/components/pages/Home/DisplayOptionsDialog';
import { HomeActions, SettingsActions } from './Actions';

interface SidebarProps {}

export function Sidebar(props: SidebarProps) {
const app = useAppSelector(selectApp);
const channelsCount = useAppSelector(selectChannelsCount);
const settings = useAppSelector(selectSettings);
const [openedDialog, setOpenedDialog] = useState<Nullable<string>>(null);

const openDialog = (dialog: string) => {
setOpenedDialog(dialog);
};

const closeDialog = () => {
setOpenedDialog(null);
};

return (
<Box
Expand Down Expand Up @@ -68,30 +58,7 @@ export function Sidebar(props: SidebarProps) {
icon={<ExploreIcon />}
text="Home"
to="/"
actions={(selected) =>
selected && (
<>
<IconButton
aria-label="display-options"
color="default"
size="small"
sx={{
position: 'absolute',
right: 0,
backgroundColor: (theme) => theme.palette.primary.dark,
color: (theme) => theme.palette.common.white,
}}
onClick={() => openDialog('display-options')}
>
<DisplaySettingsIcon fontSize="small" />
</IconButton>
<HomeDisplayOptionsDialog
open={openedDialog === 'display-options'}
onClose={closeDialog}
/>
</>
)
}
actions={(selected) => selected && <HomeActions />}
/>
<ListItemLink
icon={<SubscriptionsIcon />}
Expand All @@ -103,7 +70,13 @@ export function Sidebar(props: SidebarProps) {
icon={<SettingsIcon />}
text="Settings"
to="/settings"
hasWarning={app.loaded && !settings.apiKey}
actions={(selected) =>
selected ? (
<SettingsActions />
) : app.loaded && !settings.apiKey ? (
<ErrorOutlineIcon sx={{ ml: 3 }} color="warning" />
) : null
}
/>
<ListItemLink
icon={<InfoOutlinedIcon />}
Expand Down

0 comments on commit b8d934e

Please sign in to comment.