Support sounds
This commit is contained in:
		
							parent
							
								
									09b128f27a
								
							
						
					
					
						commit
						dc7ca6e405
					
				
					 13 changed files with 73 additions and 16 deletions
				
			
		
							
								
								
									
										
											BIN
										
									
								
								web/public/static/sounds/beep.mp3
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/public/static/sounds/beep.mp3
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								web/public/static/sounds/juntos.mp3
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/public/static/sounds/juntos.mp3
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								web/public/static/sounds/mixkit-correct-answer-tone.mp3
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/public/static/sounds/mixkit-correct-answer-tone.mp3
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								web/public/static/sounds/mixkit-long-pop.mp3
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/public/static/sounds/mixkit-long-pop.mp3
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								web/public/static/sounds/mixkit-message-pop-alert.mp3
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/public/static/sounds/mixkit-message-pop-alert.mp3
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								web/public/static/sounds/mixkit-software-interface-start.mp3
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/public/static/sounds/mixkit-software-interface-start.mp3
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								web/public/static/sounds/pristine.mp3
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/public/static/sounds/pristine.mp3
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							|  | @ -1,8 +1,8 @@ | ||||||
| import {formatMessage, formatTitleWithDefault, openUrl, topicShortUrl} from "./utils"; | import {formatMessage, formatTitleWithDefault, openUrl, playSound, topicShortUrl} from "./utils"; | ||||||
| import prefs from "./Prefs"; | import prefs from "./Prefs"; | ||||||
| import subscriptionManager from "./SubscriptionManager"; | import subscriptionManager from "./SubscriptionManager"; | ||||||
| 
 | 
 | ||||||
| class NotificationManager { | class Notifier { | ||||||
|     async notify(subscriptionId, notification, onClickFallback) { |     async notify(subscriptionId, notification, onClickFallback) { | ||||||
|         const subscription = await subscriptionManager.get(subscriptionId); |         const subscription = await subscriptionManager.get(subscriptionId); | ||||||
|         const shouldNotify = await this.shouldNotify(subscription, notification); |         const shouldNotify = await this.shouldNotify(subscription, notification); | ||||||
|  | @ -13,7 +13,8 @@ class NotificationManager { | ||||||
|         const message = formatMessage(notification); |         const message = formatMessage(notification); | ||||||
|         const title = formatTitleWithDefault(notification, shortUrl); |         const title = formatTitleWithDefault(notification, shortUrl); | ||||||
| 
 | 
 | ||||||
|         console.log(`[NotificationManager, ${shortUrl}] Displaying notification ${notification.id}: ${message}`); |         // Show notification
 | ||||||
|  |         console.log(`[Notifier, ${shortUrl}] Displaying notification ${notification.id}: ${message}`); | ||||||
|         const n = new Notification(title, { |         const n = new Notification(title, { | ||||||
|             body: message, |             body: message, | ||||||
|             icon: '/static/img/favicon.png' |             icon: '/static/img/favicon.png' | ||||||
|  | @ -23,6 +24,17 @@ class NotificationManager { | ||||||
|         } else { |         } else { | ||||||
|             n.onclick = onClickFallback; |             n.onclick = onClickFallback; | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         // Play sound
 | ||||||
|  |         const sound = await prefs.sound(); | ||||||
|  |         if (sound && sound !== "none") { | ||||||
|  |             try { | ||||||
|  |                 await playSound(sound); | ||||||
|  |             } catch (e) { | ||||||
|  |                 console.log(`[Notifier, ${shortUrl}] Error playing audio`, e); | ||||||
|  |                 // FIXME show no sound allowed popup
 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     granted() { |     granted() { | ||||||
|  | @ -48,5 +60,5 @@ class NotificationManager { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const notificationManager = new NotificationManager(); | const notifier = new Notifier(); | ||||||
| export default notificationManager; | export default notifier; | ||||||
|  | @ -1,13 +1,13 @@ | ||||||
| import db from "./db"; | import db from "./db"; | ||||||
| 
 | 
 | ||||||
| class Prefs { | class Prefs { | ||||||
|     async setSelectedSubscriptionId(selectedSubscriptionId) { |     async setSound(sound) { | ||||||
|         db.prefs.put({key: 'selectedSubscriptionId', value: selectedSubscriptionId}); |         db.prefs.put({key: 'sound', value: sound.toString()}); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async selectedSubscriptionId() { |     async sound() { | ||||||
|         const selectedSubscriptionId = await db.prefs.get('selectedSubscriptionId'); |         const sound = await db.prefs.get('sound'); | ||||||
|         return (selectedSubscriptionId) ? selectedSubscriptionId.value : ""; |         return (sound) ? sound.value : "mixkit-correct-answer-tone"; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async setMinPriority(minPriority) { |     async setMinPriority(minPriority) { | ||||||
|  |  | ||||||
|  | @ -41,7 +41,7 @@ class SubscriptionManager { | ||||||
|         if (exists) { |         if (exists) { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         await db.notifications.add({ ...notification, subscriptionId }); |         await db.notifications.add({ ...notification, subscriptionId }); // FIXME consider put() for double tab
 | ||||||
|         await db.subscriptions.update(subscriptionId, { |         await db.subscriptions.update(subscriptionId, { | ||||||
|             last: notification.id |             last: notification.id | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  | @ -121,6 +121,11 @@ export const subscriptionRoute = (subscription) => { | ||||||
|     return `/${subscription.topic}`; |     return `/${subscription.topic}`; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export const playSound = async (sound) => { | ||||||
|  |     const audio = new Audio(`/static/sounds/${sound}.mp3`); | ||||||
|  |     return audio.play(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| // From: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
 | // From: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
 | ||||||
| export async function* fetchLinesIterator(fileURL, headers) { | export async function* fetchLinesIterator(fileURL, headers) { | ||||||
|     const utf8Decoder = new TextDecoder('utf-8'); |     const utf8Decoder = new TextDecoder('utf-8'); | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ import theme from "./theme"; | ||||||
| import connectionManager from "../app/ConnectionManager"; | import connectionManager from "../app/ConnectionManager"; | ||||||
| import Navigation from "./Navigation"; | import Navigation from "./Navigation"; | ||||||
| import ActionBar from "./ActionBar"; | import ActionBar from "./ActionBar"; | ||||||
| import notificationManager from "../app/NotificationManager"; | import notifier from "../app/Notifier"; | ||||||
| import NoTopics from "./NoTopics"; | import NoTopics from "./NoTopics"; | ||||||
| import Preferences from "./Preferences"; | import Preferences from "./Preferences"; | ||||||
| import {useLiveQuery} from "dexie-react-hooks"; | import {useLiveQuery} from "dexie-react-hooks"; | ||||||
|  | @ -26,6 +26,11 @@ import {subscriptionRoute} from "../app/utils"; | ||||||
| // TODO sound
 | // TODO sound
 | ||||||
| // TODO "copy url" toast
 | // TODO "copy url" toast
 | ||||||
| // TODO "copy link url" button
 | // TODO "copy link url" button
 | ||||||
|  | // TODO races when two tabs are open
 | ||||||
|  | // TODO sound mentions
 | ||||||
|  | //  https://notificationsounds.com/message-tones/pristine-609
 | ||||||
|  | //  https://notificationsounds.com/message-tones/juntos-607
 | ||||||
|  | //  https://notificationsounds.com/notification-sounds/beep-472
 | ||||||
| 
 | 
 | ||||||
| const App = () => { | const App = () => { | ||||||
|     return ( |     return ( | ||||||
|  | @ -40,7 +45,7 @@ const App = () => { | ||||||
| 
 | 
 | ||||||
| const Root = () => { | const Root = () => { | ||||||
|     const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false); |     const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false); | ||||||
|     const [notificationsGranted, setNotificationsGranted] = useState(notificationManager.granted()); |     const [notificationsGranted, setNotificationsGranted] = useState(notifier.granted()); | ||||||
|     const navigate = useNavigate(); |     const navigate = useNavigate(); | ||||||
|     const location = useLocation(); |     const location = useLocation(); | ||||||
|     const users = useLiveQuery(() => userManager.all()); |     const users = useLiveQuery(() => userManager.all()); | ||||||
|  | @ -54,7 +59,7 @@ const Root = () => { | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     const handleRequestPermission = () => { |     const handleRequestPermission = () => { | ||||||
|         notificationManager.maybeRequestPermission(granted => setNotificationsGranted(granted)); |         notifier.maybeRequestPermission(granted => setNotificationsGranted(granted)); | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     useEffect(() => { |     useEffect(() => { | ||||||
|  | @ -68,7 +73,7 @@ const Root = () => { | ||||||
|                 const added = await subscriptionManager.addNotification(subscriptionId, notification); |                 const added = await subscriptionManager.addNotification(subscriptionId, notification); | ||||||
|                 if (added) { |                 if (added) { | ||||||
|                     const defaultClickAction = (subscription) => navigate(subscriptionRoute(subscription)); // FIXME
 |                     const defaultClickAction = (subscription) => navigate(subscriptionRoute(subscription)); // FIXME
 | ||||||
|                     await notificationManager.notify(subscriptionId, notification, defaultClickAction) |                     await notifier.notify(subscriptionId, notification, defaultClickAction) | ||||||
|                 } |                 } | ||||||
|             } catch (e) { |             } catch (e) { | ||||||
|                 console.error(`[App] Error handling notification`, e); |                 console.error(`[App] Error handling notification`, e); | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ import {Paragraph} from "./styles"; | ||||||
| import EditIcon from '@mui/icons-material/Edit'; | import EditIcon from '@mui/icons-material/Edit'; | ||||||
| import CloseIcon from "@mui/icons-material/Close"; | import CloseIcon from "@mui/icons-material/Close"; | ||||||
| import IconButton from "@mui/material/IconButton"; | import IconButton from "@mui/material/IconButton"; | ||||||
|  | import PlayArrowIcon from '@mui/icons-material/PlayArrow'; | ||||||
| import Container from "@mui/material/Container"; | import Container from "@mui/material/Container"; | ||||||
| import TextField from "@mui/material/TextField"; | import TextField from "@mui/material/TextField"; | ||||||
| import MenuItem from "@mui/material/MenuItem"; | import MenuItem from "@mui/material/MenuItem"; | ||||||
|  | @ -31,6 +32,7 @@ import DialogTitle from "@mui/material/DialogTitle"; | ||||||
| import DialogContent from "@mui/material/DialogContent"; | import DialogContent from "@mui/material/DialogContent"; | ||||||
| import DialogActions from "@mui/material/DialogActions"; | import DialogActions from "@mui/material/DialogActions"; | ||||||
| import userManager from "../app/UserManager"; | import userManager from "../app/UserManager"; | ||||||
|  | import {playSound} from "../app/utils"; | ||||||
| 
 | 
 | ||||||
| const Preferences = () => { | const Preferences = () => { | ||||||
|     return ( |     return ( | ||||||
|  | @ -50,6 +52,7 @@ const Notifications = () => { | ||||||
|                 Notifications |                 Notifications | ||||||
|             </Typography> |             </Typography> | ||||||
|             <PrefGroup> |             <PrefGroup> | ||||||
|  |                 <Sound/> | ||||||
|                 <MinPriority/> |                 <MinPriority/> | ||||||
|                 <DeleteAfter/> |                 <DeleteAfter/> | ||||||
|             </PrefGroup> |             </PrefGroup> | ||||||
|  | @ -57,8 +60,40 @@ const Notifications = () => { | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | const Sound = () => { | ||||||
|  |     const sound = useLiveQuery(async () => prefs.sound()); | ||||||
|  |     const handleChange = async (ev) => { | ||||||
|  |         await prefs.setSound(ev.target.value); | ||||||
|  |     } | ||||||
|  |     if (!sound) { | ||||||
|  |         return null; // While loading
 | ||||||
|  |     } | ||||||
|  |     return ( | ||||||
|  |         <Pref title="Notification sound"> | ||||||
|  |             <div style={{ display: 'flex', width: '100%' }}> | ||||||
|  |                 <FormControl fullWidth variant="standard" sx={{ margin: 1 }}> | ||||||
|  |                     <Select value={sound} onChange={handleChange}> | ||||||
|  |                         <MenuItem value={"none"}>No sound</MenuItem> | ||||||
|  |                         <MenuItem value={"mixkit-correct-answer-tone"}>Ding</MenuItem> | ||||||
|  |                         <MenuItem value={"juntos"}>Juntos</MenuItem> | ||||||
|  |                         <MenuItem value={"pristine"}>Pristine</MenuItem> | ||||||
|  |                         <MenuItem value={"mixkit-software-interface-start"}>Dadum</MenuItem> | ||||||
|  |                         <MenuItem value={"mixkit-message-pop-alert"}>Pop</MenuItem> | ||||||
|  |                         <MenuItem value={"mixkit-long-pop"}>Pop swoosh</MenuItem> | ||||||
|  |                         <MenuItem value={"beep"}>Beep</MenuItem> | ||||||
|  |                     </Select> | ||||||
|  |                 </FormControl> | ||||||
|  |                 <IconButton onClick={() => playSound(sound)} disabled={sound === "none"}> | ||||||
|  |                     <PlayArrowIcon /> | ||||||
|  |                 </IconButton> | ||||||
|  |             </div> | ||||||
|  |         </Pref> | ||||||
|  |     ) | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| const MinPriority = () => { | const MinPriority = () => { | ||||||
|     const minPriority = useLiveQuery(() => prefs.minPriority()); |     const minPriority = useLiveQuery(async () => prefs.minPriority()); | ||||||
|     const handleChange = async (ev) => { |     const handleChange = async (ev) => { | ||||||
|         await prefs.setMinPriority(ev.target.value); |         await prefs.setMinPriority(ev.target.value); | ||||||
|     } |     } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue