Skip to content

Commit

Permalink
Add What's New modal and delete sequences. (#98)
Browse files Browse the repository at this point in the history
  • Loading branch information
whitesoup12 authored Nov 21, 2023
1 parent f427067 commit 4fdc2d8
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,16 @@ public ResponseEntity<?> deleteSequence(@PathVariable(name = "sequenceKey") Long
return this.controlPanelService.deleteSequence(sequenceKey);
}

@DeleteMapping(value = "/controlPanel/deleteAllInactiveSequences")
@DeleteMapping(value = "/controlPanel/deleteInactiveSequences")
@RequiresAccess
public ResponseEntity<?> deleteAllInactiveSequences() {
return this.controlPanelService.deleteAllInactiveSequences();
public ResponseEntity<?> deleteInactiveSequences() {
return this.controlPanelService.deleteInactiveSequences();
}

@DeleteMapping(value = "/controlPanel/deleteAllSequences")
@RequiresAccess
public ResponseEntity<?> deleteAllSequences() {
return this.controlPanelService.deleteAllSequences();
}

@PostMapping(value = "/controlPanel/playSequence")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -484,13 +484,20 @@ public ResponseEntity<?> deleteSequence(Long sequenceKey) {
return ResponseEntity.status(200).build();
}

public ResponseEntity<?> deleteAllInactiveSequences() {
public ResponseEntity<?> deleteInactiveSequences() {
TokenDTO tokenDTO = this.authUtil.getJwtPayload();
List<Playlist> playlists = this.playlistRepository.findAllByRemoteTokenAndIsSequenceActiveOrderBySequenceKeyAsc(tokenDTO.getRemoteToken(), false);
this.playlistRepository.deleteAll(playlists);
return ResponseEntity.status(200).build();
}

public ResponseEntity<?> deleteAllSequences() {
TokenDTO tokenDTO = this.authUtil.getJwtPayload();
List<Playlist> playlists = this.playlistRepository.findAllByRemoteToken(tokenDTO.getRemoteToken());
this.playlistRepository.deleteAll(playlists);
return ResponseEntity.status(200).build();
}

public ResponseEntity<?> playSequence(@RequestBody SequenceKeyRequest request) {
TokenDTO tokenDTO = this.authUtil.getJwtPayload();
RemotePreference remotePreference = this.remotePreferenceRepository.findByRemoteToken(tokenDTO.getRemoteToken());
Expand Down
57 changes: 57 additions & 0 deletions remote-falcon-web/src/layout/MainLayout/WhatsNew.modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import CloseIcon from '@mui/icons-material/Close';
import { CardContent, CardActions, Grid, Divider, IconButton, Typography } from '@mui/material';
import PropTypes from 'prop-types';

import MainCard from 'ui-component/cards/MainCard';
import RFLoadingButton from 'ui-component/RFLoadingButton';

const WhatsNew = ({ handleClose }) => (
<MainCard
sx={{
position: 'absolute',
width: { xs: 450, lg: 900 },
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)'
}}
title="What's New"
content={false}
secondary={
<IconButton onClick={handleClose} size="large">
<CloseIcon fontSize="small" />
</IconButton>
}
>
<CardContent>
<Typography variant="h4">
<ul>
<li style={{ paddingBottom: '1em' }}>
This modal is new! Sometimes people might miss messages on Facebook or Discord, so this is another way to communicate new
features or changes.
</li>
<li style={{ paddingBottom: '1em' }}>
Added an option to the Sequences page to Delete Inactive or Delete All Sequences. Fair warning, if you choose to delete
sequences, it will remove any customizations (display name, artist, etc.).{' '}
<a href="https://docs.remotefalcon.com/docs/control-panel/sequences#delete-sequences" target="_blank" rel="noreferrer">
Docs link
</a>
</li>
</ul>
</Typography>
</CardContent>
<Divider />
<CardActions>
<Grid container justifyContent="flex-end">
<RFLoadingButton onClick={handleClose} color="primary">
Got It!
</RFLoadingButton>
</Grid>
</CardActions>
</MainCard>
);

WhatsNew.propTypes = {
handleClose: PropTypes.func
};

export default WhatsNew;
23 changes: 21 additions & 2 deletions remote-falcon-web/src/layout/MainLayout/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import React, { useMemo, useState } from 'react';

import { AppBar, Box, Container, CssBaseline, Toolbar, useMediaQuery } from '@mui/material';
import { AppBar, Box, Container, CssBaseline, Modal, Toolbar, useMediaQuery } from '@mui/material';
import { styled, useTheme } from '@mui/material/styles';
import { IconChevronRight } from '@tabler/icons';
import { Outlet } from 'react-router-dom';
Expand All @@ -12,8 +12,10 @@ import { drawerWidth } from 'store/constant';
import { openDrawer } from 'store/slices/menu';
import Breadcrumbs from 'ui-component/extended/Breadcrumbs';

import { closeCreateNewSequenceGroup } from '../../views/pages/controlPanel/sequences/helpers';
import Header from './Header';
import Sidebar from './Sidebar';
import WhatsNew from './WhatsNew.modal';

const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({
...theme.typography.mainContent,
Expand Down Expand Up @@ -68,8 +70,17 @@ const MainLayout = () => {
const { drawerOpen } = useSelector((state) => state.menu);
const { container } = useConfig();

const [whatsNewOpen, setWhatsNewOpen] = useState(false);

const newStuffDateString = '2023-11-21';
const newStuffDate = Date.parse(newStuffDateString);

React.useEffect(() => {
dispatch(openDrawer(!matchDownMd));
const whatsNewDateViewed = window.localStorage.getItem('whatsNew');
if (!whatsNewDateViewed || newStuffDate > Date.parse(whatsNewDateViewed)) {
setWhatsNewOpen(true);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [matchDownMd]);

Expand All @@ -82,6 +93,11 @@ const MainLayout = () => {
[]
);

const closeWhatsNew = () => {
window.localStorage.setItem('whatsNew', newStuffDateString);
setWhatsNewOpen(false);
};

return (
<Box sx={{ display: 'flex' }}>
<CssBaseline />
Expand All @@ -104,6 +120,9 @@ const MainLayout = () => {

{/* main content */}
<Main theme={theme} open={drawerOpen}>
<Modal open={whatsNewOpen} aria-labelledby="simple-modal-title" aria-describedby="simple-modal-description">
<WhatsNew handleClose={() => closeWhatsNew()} />
</Modal>
{/* breadcrumb */}
{container && (
<Container maxWidth="lg">
Expand Down
10 changes: 10 additions & 0 deletions remote-falcon-web/src/services/controlPanel/sequences.services.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,13 @@ export const updateSequenceOrderService = async (sequences) => {
const response = await axios.post('/remotefalcon/api/controlPanel/updateSequenceOrder', sequences);
return response;
};

export const deleteInactiveSequencesService = async () => {
const response = await axios.delete('/remotefalcon/api/controlPanel/deleteInactiveSequences');
return response;
};

export const deleteAllSequencesService = async () => {
const response = await axios.delete('/remotefalcon/api/controlPanel/deleteAllSequences');
return response;
};
121 changes: 121 additions & 0 deletions remote-falcon-web/src/ui-component/RFSplitButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import * as React from 'react';

import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import Button from '@mui/material/Button';
import ButtonGroup from '@mui/material/ButtonGroup';
import ClickAwayListener from '@mui/material/ClickAwayListener';
import Grow from '@mui/material/Grow';
import MenuItem from '@mui/material/MenuItem';
import MenuList from '@mui/material/MenuList';
import Paper from '@mui/material/Paper';
import Popper from '@mui/material/Popper';
import { useTheme } from '@mui/material/styles';
import PropTypes from 'prop-types';

const options = ['Delete Inactive Sequences', 'Delete All Sequences'];

const RFSplitButton = ({ disabled, onClick, color, variant, sx }) => {
const theme = useTheme();

let background = theme.palette.primary.main;
let backgroundHover = theme.palette.primary.dark;
if (color === 'error') {
background = theme.palette.error.main;
backgroundHover = theme.palette.error.dark;
}

const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef(null);
const [selectedIndex, setSelectedIndex] = React.useState(0);

const handleMenuItemClick = (event, index) => {
setSelectedIndex(index);
setOpen(false);
};

const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};

const handleClose = (event) => {
if (anchorRef.current && anchorRef.current.contains(event.target)) {
return;
}

setOpen(false);
};

return (
<>
<ButtonGroup variant="contained" ref={anchorRef} aria-label="split button">
<Button
sx={sx || { background, '&:hover': { background: backgroundHover } }}
disabled={disabled}
variant={variant || 'contained'}
size="large"
onClick={() => onClick(options, selectedIndex)}
>
{options[selectedIndex]}
</Button>
<Button
sx={sx || { background, '&:hover': { background: backgroundHover } }}
size="small"
aria-controls={open ? 'split-button-menu' : undefined}
aria-expanded={open ? 'true' : undefined}
aria-label="select delete"
aria-haspopup="menu"
onClick={handleToggle}
>
<ArrowDropDownIcon />
</Button>
</ButtonGroup>
<Popper
sx={{
zIndex: 1
}}
open={open}
anchorEl={anchorRef.current}
role={undefined}
transition
disablePortal
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom'
}}
>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList id="split-button-menu" autoFocusItem>
{options.map((option, index) => (
<MenuItem
key={option}
disabled={index === 2}
selected={index === selectedIndex}
onClick={(event) => handleMenuItemClick(event, index)}
>
{option}
</MenuItem>
))}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</>
);
};

RFSplitButton.propTypes = {
disabled: PropTypes.bool,
onClick: PropTypes.func,
color: PropTypes.string,
variant: PropTypes.string,
sx: PropTypes.object,
callback: PropTypes.func
};

export default RFSplitButton;
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {
deleteSequenceService,
saveSequenceGroupService,
deleteSequenceGroupService,
updateSequenceOrderService
updateSequenceOrderService,
deleteInactiveSequencesService,
deleteAllSequencesService
} from 'services/controlPanel/sequences.services';
import { setSequences } from 'store/slices/controlPanel';
import { showAlert, mixpanelTrack } from 'views/pages/globalPageHelpers';
Expand Down Expand Up @@ -243,3 +245,29 @@ export const reorderSequences = async (result, sequences, setShowLinearProgress,
fetchSequences();
setShowLinearProgress(false);
};

export const deleteSequences = async (options, selectedIndex, setShowLinearProgress, dispatch, coreInfo, fetchSequences) => {
if (selectedIndex === 0) {
setShowLinearProgress(true);
const response = await deleteInactiveSequencesService();
if (response?.status === 200) {
showAlert({ dispatch, message: 'All Inactive Sequences Deleted' });
mixpanelTrack('Inactive Sequences Deleted', coreInfo);
await fetchSequences();
} else {
showAlert({ dispatch, alert: 'error' });
}
setShowLinearProgress(false);
} else if (selectedIndex === 1) {
setShowLinearProgress(true);
const response = await deleteAllSequencesService();
if (response?.status === 200) {
showAlert({ dispatch, message: 'All Sequences Deleted' });
mixpanelTrack('All Sequences Deleted', coreInfo);
await fetchSequences();
} else {
showAlert({ dispatch, alert: 'error' });
}
setShowLinearProgress(false);
}
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState, useEffect, useCallback } from 'react';
import * as React from 'react';

import { Box, Grid, TableRow, TableCell, TableContainer, Table, TableHead, TableBody, LinearProgress, Modal, Stack } from '@mui/material';
import { useTheme } from '@mui/material/styles';
Expand All @@ -14,6 +15,7 @@ import SequencesSkeleton from 'ui-component/cards/Skeleton/SequencesSkeleton';
import RFLoadingButton from 'ui-component/RFLoadingButton';
import { showAlert, mixpanelTrack } from 'views/pages/globalPageHelpers';

import RFSplitButton from '../../../../ui-component/RFSplitButton';
import CreateNewSequenceGroup from './CreateNewSequenceGroup.modal';
import {
saveSequenceChanges,
Expand All @@ -30,7 +32,8 @@ import {
handleSequenceGroupNameChange,
closeManageSequenceGroups,
sortSequencesAlphabetically,
reorderSequences
reorderSequences,
deleteSequences
} from './helpers';
import ManageSequenceGroups from './ManageSequenceGroups.modal';
import SequenceRow from './SequenceRow';
Expand Down Expand Up @@ -114,6 +117,12 @@ const Sequences = () => {
>
Sort Alphabetically
</RFLoadingButton>
<RFSplitButton
color="error"
onClick={(options, selectedIndex) =>
deleteSequences(options, selectedIndex, setShowLinearProgress, dispatch, coreInfo, () => fetchSequences())
}
/>
</Stack>
<Table size="small" aria-label="collapsible table">
<TableHead sx={{ '& th,& td': { whiteSpace: 'nowrap' } }}>
Expand Down

0 comments on commit 4fdc2d8

Please sign in to comment.