parent
b84380cd5d
commit
01a37b0def
|
@ -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>
|
||||
|
|
|
@ -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 |
|
@ -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) => {
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
18
src/utils.ts
18
src/utils.ts
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue