feat: add notification upon completion

Request coming from #37
This commit is contained in:
Stefano Brilli 2021-04-11 12:25:13 +02:00
parent b84380cd5d
commit 01a37b0def
7 changed files with 108 additions and 3 deletions

View File

@ -15,7 +15,10 @@ import Box from '@material-ui/core/Box';
import { makeStyles } from '@material-ui/core/styles';
import { TransitionProps } from '@material-ui/core/transitions';
import { Button } from '@material-ui/core';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox';
import { W95UploadDialog } from './win95/upload-dialog';
import { setNotifyWhenFinished } from '../redux/actions';
const useStyles = makeStyles(theme => ({
progressPerc: {
@ -27,6 +30,12 @@ const useStyles = makeStyles(theme => ({
uploadLabel: {
marginTop: theme.spacing(3),
},
spacer: {
flex: '1 1 auto',
},
checkBox: {
marginLeft: 0,
},
}));
const Transition = React.forwardRef(function Transition(
@ -53,16 +62,20 @@ export const UploadDialog = (props: {}) => {
titleCurrent,
titleConverting,
} = useShallowEqualSelector(state => state.uploadDialog);
const { vintageMode, notifyWhenFinished, hasNotificationSupport } = useShallowEqualSelector(state => state.appState);
const handleCancelUpload = useCallback(() => {
dispatch(uploadDialogActions.setCancelUpload(true));
}, [dispatch]);
const handleNotifyWhenFinishedChanged = useCallback(() => {
dispatch(setNotifyWhenFinished(!notifyWhenFinished));
}, [dispatch, notifyWhenFinished]);
let progressValue = Math.floor((writtenProgress / totalProgress) * 100);
let bufferValue = Math.floor((encryptedProgress / totalProgress) * 100);
let convertedValue = Math.floor((trackConverting / trackTotal) * 100);
const vintageMode = useShallowEqualSelector(state => state.appState.vintageMode);
if (vintageMode) {
const p = {
visible,
@ -81,6 +94,9 @@ export const UploadDialog = (props: {}) => {
progressValue,
bufferValue,
convertedValue,
notifyWhenFinished,
hasNotificationSupport,
handleNotifyWhenFinishedChanged,
};
return <W95UploadDialog {...p} />;
}
@ -121,6 +137,15 @@ export const UploadDialog = (props: {}) => {
<Box className={classes.progressPerc}>{progressValue}%</Box>
</DialogContent>
<DialogActions>
{hasNotificationSupport ? (
<FormControlLabel
className={classes.checkBox}
disabled={!hasNotificationSupport}
control={<Checkbox checked={notifyWhenFinished} onChange={handleNotifyWhenFinishedChanged} name="notifyOnEnd" />}
label="Notify when completed"
/>
) : null}
<div className={classes.spacer}></div>
<Button disabled={cancelled} onClick={handleCancelUpload}>
{cancelled ? `Stopping after current track...` : `Cancel Recording`}
</Button>

View File

@ -1,5 +1,5 @@
import React from 'react';
import { WindowHeader, Button, Progress } from 'react95';
import { WindowHeader, Button, Progress, Checkbox } from 'react95';
import { DialogOverlay, DialogWindow, DialogFooter, DialogWindowContent } from './common';
export const W95UploadDialog = (props: {
@ -17,6 +17,9 @@ export const W95UploadDialog = (props: {
progressValue: number;
bufferValue: number;
convertedValue: number;
notifyWhenFinished: boolean;
hasNotificationSupport: boolean;
handleNotifyWhenFinishedChanged: () => void;
}) => {
if (!props.visible) {
return null;
@ -42,6 +45,15 @@ export const W95UploadDialog = (props: {
<Progress value={props.progressValue} />
<DialogFooter>
{props.hasNotificationSupport ? (
<Checkbox
name="notifyOnEnd"
label="Notify when completed"
checked={props.notifyWhenFinished}
onChange={props.handleNotifyWhenFinishedChanged}
/>
) : null}
<div style={{ flex: '1 1 auto' }}></div>
<Button disabled={props.cancelled} onClick={props.handleCancelUpload}>
{props.cancelled ? `Stopping after current track...` : `Cancel Recording`}
</Button>

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -42,6 +42,11 @@ serviceRegistry.mediaRecorderService = new MediaRecorderService();
store.dispatch(appActions.setBrowserSupported(false));
}
if (!('Notification' in window) || Notification.permission === 'denied') {
store.dispatch(appActions.setNotificationSupport(false));
store.dispatch(appActions.setNotifyWhenFinished(false));
}
// eslint-disable-next-line
let deferredPrompt: any;
window.addEventListener('beforeinstallprompt', (e: any) => {

View File

@ -9,9 +9,10 @@ import { actions as mainActions } from './main-feature';
import serviceRegistry from '../services/registry';
import { Wireformat, getTracks } from 'netmd-js';
import { AnyAction } from '@reduxjs/toolkit';
import { getAvailableCharsForTrackTitle, framesToSec, sleepWithProgressCallback, sleep } from '../utils';
import { getAvailableCharsForTrackTitle, framesToSec, sleepWithProgressCallback, sleep, askNotificationPermission } from '../utils';
import * as mm from 'music-metadata-browser';
import { TitleFormatType, UploadFormat } from './convert-dialog-feature';
import NotificationCompleteIconUrl from '../images/record-complete-notification-icon.png';
export function control(action: 'play' | 'stop' | 'next' | 'prev' | 'goto', params?: unknown) {
return async function(dispatch: AppDispatch, getState: () => RootState) {
@ -234,6 +235,24 @@ export function recordTracks(indexes: number[], deviceId: string) {
};
}
export function setNotifyWhenFinished(value: boolean) {
return async function(dispatch: AppDispatch, getState: () => RootState) {
if (Notification.permission !== 'granted') {
const confirmation = window.confirm(`Enable Notification on recording completed?`);
if (!confirmation) {
return;
}
const result = await askNotificationPermission();
if (result !== 'granted') {
dispatch(appStateActions.setNotificationSupport(false));
dispatch(appStateActions.setNotifyWhenFinished(false));
return;
}
}
dispatch(appStateActions.setNotifyWhenFinished(value));
};
}
export const WireformatDict: { [k: string]: Wireformat } = {
SP: Wireformat.pcm,
LP2: Wireformat.lp2,
@ -289,6 +308,20 @@ export function convertAndUpload(files: File[], format: UploadFormat, titleForma
return getState().uploadDialog.cancelled;
};
function showFinishedNotificationIfNeeded() {
const { notifyWhenFinished, hasNotificationSupport } = getState().appState;
if (!hasNotificationSupport || !notifyWhenFinished) {
return;
}
const notification = new Notification('MiniDisc recording completed', {
icon: NotificationCompleteIconUrl,
});
notification.onclick = function() {
window.focus();
this.close();
};
}
let trackUpdate: {
current: number;
converting: number;
@ -399,6 +432,7 @@ export function convertAndUpload(files: File[], format: UploadFormat, titleForma
}
dispatch(batchActions(actionToDispatch));
showFinishedNotificationIfNeeded();
listContent()(dispatch);
};
}

View File

@ -13,6 +13,8 @@ export interface AppState {
darkMode: boolean;
vintageMode: boolean;
aboutDialogVisible: boolean;
notifyWhenFinished: boolean;
hasNotificationSupport: boolean;
}
const initialState: AppState = {
@ -24,6 +26,8 @@ const initialState: AppState = {
darkMode: loadPreference('darkMode', false),
vintageMode: loadPreference('vintageMode', false),
aboutDialogVisible: false,
notifyWhenFinished: loadPreference('notifyWhenFinished', false),
hasNotificationSupport: true,
};
export const slice = createSlice({
@ -49,6 +53,13 @@ export const slice = createSlice({
state.darkMode = action.payload;
savePreference('darkMode', state.darkMode);
},
setNotifyWhenFinished: (state, action: PayloadAction<boolean>) => {
state.notifyWhenFinished = action.payload;
savePreference('notifyWhenFinished', action.payload);
},
setNotificationSupport: (state, action: PayloadAction<boolean>) => {
state.hasNotificationSupport = action.payload;
},
setVintageMode: (state, action: PayloadAction<boolean>) => {
state.vintageMode = action.payload;
savePreference('vintageMode', state.vintageMode);

View File

@ -117,4 +117,22 @@ export function forWideDesktop(theme: Theme) {
return theme.breakpoints.up(700 + theme.spacing(2) * 2) + ` and (min-height: 750px)`;
}
export function askNotificationPermission(): Promise<NotificationPermission> {
// Adapted from: https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API
function checkNotificationPromise() {
try {
Notification.requestPermission().then();
} catch (e) {
return false;
}
return true;
}
if (checkNotificationPromise()) {
return Notification.requestPermission();
} else {
return new Promise(resolve => Notification.requestPermission(resolve));
}
}
declare let process: any;