From 018b4d3b301cfbdefb5791a041648a5911bddf4a Mon Sep 17 00:00:00 2001 From: Rudis Muiznieks Date: Thu, 13 Apr 2023 19:51:16 -0500 Subject: [PATCH] added toast notifications --- site/index.html | 2 ++ site/radiostasis.js | 52 +++++++++++++++++++++++++++++++-------- site/toastify.min.css | 15 +++++++++++ site/toastify.min.js | 15 +++++++++++ src/Player.ts | 21 ++++++++++------ src/Playlist.ts | 38 +++++++++++++++++++++++++--- src/typings/toastify.d.ts | 36 +++++++++++++++++++++++++++ 7 files changed, 159 insertions(+), 20 deletions(-) create mode 100644 site/toastify.min.css create mode 100644 site/toastify.min.js create mode 100644 src/typings/toastify.d.ts diff --git a/site/index.html b/site/index.html index 48a9ad2..6b2d57c 100644 --- a/site/index.html +++ b/site/index.html @@ -5,6 +5,7 @@ + @@ -155,6 +156,7 @@ + diff --git a/site/radiostasis.js b/site/radiostasis.js index fc45040..a8eb16e 100644 --- a/site/radiostasis.js +++ b/site/radiostasis.js @@ -101,7 +101,7 @@ var Player = /** @class */ (function () { this.stopPlaybackAndResetUi(); this.setPauseButtonUI(); this.episode = episode; - this.playlist.removeEpisode(episode); + this.playlist.removeEpisode(episode, false, false); this.updateNowPlayingUI(true); void fetch("/api/r/".concat(episode.file)) .then(function (res) { return __awaiter(_this, void 0, void 0, function () { @@ -111,7 +111,7 @@ var Player = /** @class */ (function () { switch (_a.label) { case 0: if (!res.ok) { - this.setErrorUI("API returned ".concat(res.status)); + this.setErrorUI("Error fetching episode (".concat(res.status, ")")); return [2 /*return*/]; } return [4 /*yield*/, res.json()]; @@ -139,14 +139,14 @@ var Player = /** @class */ (function () { _this.stopTicker(); }, onend: function () { return _this.nextEpisode(); }, - onloaderror: function () { return _this.setErrorUI('Playback error'); }, + onloaderror: function () { return _this.setErrorUI('Error playing episode.'); }, }); return [2 /*return*/]; } }); }); }) .catch(function () { - _this.setErrorUI('Fetch episode error'); + _this.setErrorUI('Error downloading episode.'); }); }; Player.prototype.isPlaying = function () { @@ -157,9 +157,16 @@ var Player = /** @class */ (function () { }; Player.prototype.setErrorUI = function (message) { this.stopPlaybackAndResetUi(); - this.seriesName.innerHTML = 'Error playing episode'; - this.episodeName.innerHTML = message; - this.nowPlaying.classList.add('error'); + Toastify({ + text: message, + gravity: 'bottom', + position: 'right', + style: { + marginBottom: '10ex', + background: '#a00', + color: '#fff', + }, + }).showToast(); }; Player.prototype.playPause = function () { if (this.howl && this.howl.playing()) @@ -351,28 +358,35 @@ var Playlist = /** @class */ (function () { for (var _i = 0, episodes_1 = episodes; _i < episodes_1.length; _i++) { var episode = episodes_1[_i]; if (this.isQueued(episode)) - this.removeEpisode(episode, true); + this.removeEpisode(episode, true, false); var litem = this.createQueueListItem(episode); this.episodes.push([episode, litem]); this.episodeHash.add(episode.id); this.queueList.appendChild(litem); } + this.notify(episodes.length == 1 + ? 'Episode added to queue.' + : "".concat(episodes.length, " episodes added to queue.")); this.playlistChanged(); }; Playlist.prototype.unshiftEpisodes = function (episodes) { for (var i = episodes.length - 1; i >= 0; i--) { var episode = episodes[i]; if (this.isQueued(episode)) - this.removeEpisode(episode, true); + this.removeEpisode(episode, true, false); var litem = this.createQueueListItem(episode); this.episodes.unshift([episode, litem]); this.episodeHash.add(episode.id); this.queueList.prepend(litem); } + this.notify(episodes.length == 1 + ? 'Episode added to queue.' + : "".concat(episodes.length, " episodes added to queue.")); this.playlistChanged(); }; - Playlist.prototype.removeEpisode = function (episode, ignoreChanged) { + Playlist.prototype.removeEpisode = function (episode, ignoreChanged, notify) { if (ignoreChanged === void 0) { ignoreChanged = false; } + if (notify === void 0) { notify = true; } if (this.isQueued(episode)) { var idx = this.episodes.findIndex(function (e) { return e[0].id == episode.id; }); var deleted = this.episodes.splice(idx, 1); @@ -381,6 +395,8 @@ var Playlist = /** @class */ (function () { } if (!ignoreChanged) this.playlistChanged(); + if (notify) + this.notify('Episode removed from queue.'); }; Playlist.prototype.isQueued = function (episode) { return this.episodeHash.has(episode.id); @@ -389,10 +405,14 @@ var Playlist = /** @class */ (function () { this.playlistChanged(); }; Playlist.prototype.clearPlaylist = function () { + var removed = this.episodes.length; this.episodes.length = 0; this.episodeHash.clear(); this.queueList.innerHTML = ''; this.playlistChanged(); + if (removed > 0) { + this.notify('Playlist cleared.'); + } }; Playlist.prototype.playlistChanged = function () { for (var _i = 0, _a = this.changedHandlers; _i < _a.length; _i++) { @@ -449,6 +469,18 @@ var Playlist = /** @class */ (function () { item.appendChild(controls); return item; }; + Playlist.prototype.notify = function (message) { + Toastify({ + text: message, + gravity: 'bottom', + position: 'right', + style: { + marginBottom: '10ex', + background: '#090', + color: '#fff', + }, + }).showToast(); + }; return Playlist; }()); var Radiostasis = /** @class */ (function () { diff --git a/site/toastify.min.css b/site/toastify.min.css new file mode 100644 index 0000000..427c25b --- /dev/null +++ b/site/toastify.min.css @@ -0,0 +1,15 @@ +/** + * Minified by jsDelivr using clean-css v5.3.0. + * Original file: /npm/toastify-js@1.12.0/src/toastify.css + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +/*! + * Toastify js 1.12.0 + * https://github.com/apvarun/toastify-js + * @license MIT licensed + * + * Copyright (C) 2018 Varun A P + */ +.toastify{padding:12px 20px;color:#fff;display:inline-block;box-shadow:0 3px 6px -1px rgba(0,0,0,.12),0 10px 36px -4px rgba(77,96,232,.3);background:-webkit-linear-gradient(315deg,#73a5ff,#5477f5);background:linear-gradient(135deg,#73a5ff,#5477f5);position:fixed;opacity:0;transition:all .4s cubic-bezier(.215, .61, .355, 1);border-radius:2px;cursor:pointer;text-decoration:none;max-width:calc(50% - 20px);z-index:2147483647}.toastify.on{opacity:1}.toast-close{background:0 0;border:0;color:#fff;cursor:pointer;font-family:inherit;font-size:1em;opacity:.4;padding:0 5px}.toastify-right{right:15px}.toastify-left{left:15px}.toastify-top{top:-150px}.toastify-bottom{bottom:-150px}.toastify-rounded{border-radius:25px}.toastify-avatar{width:1.5em;height:1.5em;margin:-7px 5px;border-radius:2px}.toastify-center{margin-left:auto;margin-right:auto;left:0;right:0;max-width:fit-content;max-width:-moz-fit-content}@media only screen and (max-width:360px){.toastify-left,.toastify-right{margin-left:auto;margin-right:auto;left:0;right:0;max-width:fit-content}} +/*# sourceMappingURL=/sm/cb4335d1b03e933ed85cb59fffa60cf51f07567ed09831438c60f59afd166464.map */ \ No newline at end of file diff --git a/site/toastify.min.js b/site/toastify.min.js new file mode 100644 index 0000000..29af385 --- /dev/null +++ b/site/toastify.min.js @@ -0,0 +1,15 @@ +/** + * Minified by jsDelivr using Terser v5.14.1. + * Original file: /npm/toastify-js@1.12.0/src/toastify.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +/*! + * Toastify js 1.12.0 + * https://github.com/apvarun/toastify-js + * @license MIT licensed + * + * Copyright (C) 2018 Varun A P + */ +!function(t,o){"object"==typeof module&&module.exports?module.exports=o():t.Toastify=o()}(this,(function(t){var o=function(t){return new o.lib.init(t)};function i(t,o){return o.offset[t]?isNaN(o.offset[t])?o.offset[t]:o.offset[t]+"px":"0px"}function s(t,o){return!(!t||"string"!=typeof o)&&!!(t.className&&t.className.trim().split(/\s+/gi).indexOf(o)>-1)}return o.defaults={oldestFirst:!0,text:"Toastify is awesome!",node:void 0,duration:3e3,selector:void 0,callback:function(){},destination:void 0,newWindow:!1,close:!1,gravity:"toastify-top",positionLeft:!1,position:"",backgroundColor:"",avatar:"",className:"",stopOnFocus:!0,onClick:function(){},offset:{x:0,y:0},escapeMarkup:!0,ariaLive:"polite",style:{background:""}},o.lib=o.prototype={toastify:"1.12.0",constructor:o,init:function(t){return t||(t={}),this.options={},this.toastElement=null,this.options.text=t.text||o.defaults.text,this.options.node=t.node||o.defaults.node,this.options.duration=0===t.duration?0:t.duration||o.defaults.duration,this.options.selector=t.selector||o.defaults.selector,this.options.callback=t.callback||o.defaults.callback,this.options.destination=t.destination||o.defaults.destination,this.options.newWindow=t.newWindow||o.defaults.newWindow,this.options.close=t.close||o.defaults.close,this.options.gravity="bottom"===t.gravity?"toastify-bottom":o.defaults.gravity,this.options.positionLeft=t.positionLeft||o.defaults.positionLeft,this.options.position=t.position||o.defaults.position,this.options.backgroundColor=t.backgroundColor||o.defaults.backgroundColor,this.options.avatar=t.avatar||o.defaults.avatar,this.options.className=t.className||o.defaults.className,this.options.stopOnFocus=void 0===t.stopOnFocus?o.defaults.stopOnFocus:t.stopOnFocus,this.options.onClick=t.onClick||o.defaults.onClick,this.options.offset=t.offset||o.defaults.offset,this.options.escapeMarkup=void 0!==t.escapeMarkup?t.escapeMarkup:o.defaults.escapeMarkup,this.options.ariaLive=t.ariaLive||o.defaults.ariaLive,this.options.style=t.style||o.defaults.style,t.backgroundColor&&(this.options.style.background=t.backgroundColor),this},buildToast:function(){if(!this.options)throw"Toastify is not initialized";var t=document.createElement("div");for(var o in t.className="toastify on "+this.options.className,this.options.position?t.className+=" toastify-"+this.options.position:!0===this.options.positionLeft?(t.className+=" toastify-left",console.warn("Property `positionLeft` will be depreciated in further versions. Please use `position` instead.")):t.className+=" toastify-right",t.className+=" "+this.options.gravity,this.options.backgroundColor&&console.warn('DEPRECATION NOTICE: "backgroundColor" is being deprecated. Please use the "style.background" property.'),this.options.style)t.style[o]=this.options.style[o];if(this.options.ariaLive&&t.setAttribute("aria-live",this.options.ariaLive),this.options.node&&this.options.node.nodeType===Node.ELEMENT_NODE)t.appendChild(this.options.node);else if(this.options.escapeMarkup?t.innerText=this.options.text:t.innerHTML=this.options.text,""!==this.options.avatar){var s=document.createElement("img");s.src=this.options.avatar,s.className="toastify-avatar","left"==this.options.position||!0===this.options.positionLeft?t.appendChild(s):t.insertAdjacentElement("afterbegin",s)}if(!0===this.options.close){var e=document.createElement("button");e.type="button",e.setAttribute("aria-label","Close"),e.className="toast-close",e.innerHTML="✖",e.addEventListener("click",function(t){t.stopPropagation(),this.removeElement(this.toastElement),window.clearTimeout(this.toastElement.timeOutValue)}.bind(this));var n=window.innerWidth>0?window.innerWidth:screen.width;("left"==this.options.position||!0===this.options.positionLeft)&&n>360?t.insertAdjacentElement("afterbegin",e):t.appendChild(e)}if(this.options.stopOnFocus&&this.options.duration>0){var a=this;t.addEventListener("mouseover",(function(o){window.clearTimeout(t.timeOutValue)})),t.addEventListener("mouseleave",(function(){t.timeOutValue=window.setTimeout((function(){a.removeElement(t)}),a.options.duration)}))}if(void 0!==this.options.destination&&t.addEventListener("click",function(t){t.stopPropagation(),!0===this.options.newWindow?window.open(this.options.destination,"_blank"):window.location=this.options.destination}.bind(this)),"function"==typeof this.options.onClick&&void 0===this.options.destination&&t.addEventListener("click",function(t){t.stopPropagation(),this.options.onClick()}.bind(this)),"object"==typeof this.options.offset){var l=i("x",this.options),r=i("y",this.options),p="left"==this.options.position?l:"-"+l,d="toastify-top"==this.options.gravity?r:"-"+r;t.style.transform="translate("+p+","+d+")"}return t},showToast:function(){var t;if(this.toastElement=this.buildToast(),!(t="string"==typeof this.options.selector?document.getElementById(this.options.selector):this.options.selector instanceof HTMLElement||"undefined"!=typeof ShadowRoot&&this.options.selector instanceof ShadowRoot?this.options.selector:document.body))throw"Root element is not defined";var i=o.defaults.oldestFirst?t.firstChild:t.lastChild;return t.insertBefore(this.toastElement,i),o.reposition(),this.options.duration>0&&(this.toastElement.timeOutValue=window.setTimeout(function(){this.removeElement(this.toastElement)}.bind(this),this.options.duration)),this},hideToast:function(){this.toastElement.timeOutValue&&clearTimeout(this.toastElement.timeOutValue),this.removeElement(this.toastElement)},removeElement:function(t){t.className=t.className.replace(" on",""),window.setTimeout(function(){this.options.node&&this.options.node.parentNode&&this.options.node.parentNode.removeChild(this.options.node),t.parentNode&&t.parentNode.removeChild(t),this.options.callback.call(t),o.reposition()}.bind(this),400)}},o.reposition=function(){for(var t,o={top:15,bottom:15},i={top:15,bottom:15},e={top:15,bottom:15},n=document.getElementsByClassName("toastify"),a=0;a0?window.innerWidth:screen.width)<=360?(n[a].style[t]=e[t]+"px",e[t]+=l+15):!0===s(n[a],"toastify-left")?(n[a].style[t]=o[t]+"px",o[t]+=l+15):(n[a].style[t]=i[t]+"px",i[t]+=l+15)}return this},o.lib.init.prototype=o.lib,o})); +//# sourceMappingURL=/sm/e1ebbfe1bf0b0061f0726ebc83434e1c2f8308e6354c415fd05ecccdaad47617.map \ No newline at end of file diff --git a/src/Player.ts b/src/Player.ts index bdd0297..6da93df 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -111,13 +111,13 @@ class Player { this.stopPlaybackAndResetUi(); this.setPauseButtonUI(); this.episode = episode; - this.playlist.removeEpisode(episode); + this.playlist.removeEpisode(episode, false, false); this.updateNowPlayingUI(true); void fetch(`/api/r/${episode.file}`) .then(async (res) => { if (!res.ok) { - this.setErrorUI(`API returned ${res.status}`); + this.setErrorUI(`Error fetching episode (${res.status})`); return; } const link = await res.json(); @@ -142,11 +142,11 @@ class Player { this.stopTicker(); }, onend: () => this.nextEpisode(), - onloaderror: () => this.setErrorUI('Playback error'), + onloaderror: () => this.setErrorUI('Error playing episode.'), }); }) .catch(() => { - this.setErrorUI('Fetch episode error'); + this.setErrorUI('Error downloading episode.'); }); } @@ -159,9 +159,16 @@ class Player { private setErrorUI(message: string): void { this.stopPlaybackAndResetUi(); - this.seriesName.innerHTML = 'Error playing episode'; - this.episodeName.innerHTML = message; - this.nowPlaying.classList.add('error'); + Toastify({ + text: message, + gravity: 'bottom', + position: 'right', + style: { + marginBottom: '10ex', + background: '#a00', + color: '#fff', + }, + }).showToast(); } private playPause(): void { diff --git a/src/Playlist.ts b/src/Playlist.ts index 5674ac8..6b52e0f 100644 --- a/src/Playlist.ts +++ b/src/Playlist.ts @@ -63,28 +63,42 @@ class Playlist { public pushEpisodes(episodes: Array): void { for (const episode of episodes) { - if (this.isQueued(episode)) this.removeEpisode(episode, true); + if (this.isQueued(episode)) this.removeEpisode(episode, true, false); const litem = this.createQueueListItem(episode); this.episodes.push([episode, litem]); this.episodeHash.add(episode.id); this.queueList.appendChild(litem); } + this.notify( + episodes.length == 1 + ? 'Episode added to queue.' + : `${episodes.length} episodes added to queue.` + ); this.playlistChanged(); } public unshiftEpisodes(episodes: Array): void { for (let i = episodes.length - 1; i >= 0; i--) { const episode = episodes[i]; - if (this.isQueued(episode)) this.removeEpisode(episode, true); + if (this.isQueued(episode)) this.removeEpisode(episode, true, false); const litem = this.createQueueListItem(episode); this.episodes.unshift([episode, litem]); this.episodeHash.add(episode.id); this.queueList.prepend(litem); } + this.notify( + episodes.length == 1 + ? 'Episode added to queue.' + : `${episodes.length} episodes added to queue.` + ); this.playlistChanged(); } - public removeEpisode(episode: Episode, ignoreChanged = false): void { + public removeEpisode( + episode: Episode, + ignoreChanged = false, + notify = true + ): void { if (this.isQueued(episode)) { const idx = this.episodes.findIndex((e) => e[0].id == episode.id); const deleted = this.episodes.splice(idx, 1); @@ -92,6 +106,7 @@ class Playlist { deleted[0][1].remove(); } if (!ignoreChanged) this.playlistChanged(); + if (notify) this.notify('Episode removed from queue.'); } public isQueued(episode: Episode): boolean { @@ -103,10 +118,14 @@ class Playlist { } public clearPlaylist(): void { + const removed = this.episodes.length; this.episodes.length = 0; this.episodeHash.clear(); this.queueList.innerHTML = ''; this.playlistChanged(); + if (removed > 0) { + this.notify('Playlist cleared.'); + } } private playlistChanged(): void { @@ -162,4 +181,17 @@ class Playlist { item.appendChild(controls); return item; } + + private notify(message: string): void { + Toastify({ + text: message, + gravity: 'bottom', + position: 'right', + style: { + marginBottom: '10ex', + background: '#090', + color: '#fff', + }, + }).showToast(); + } } diff --git a/src/typings/toastify.d.ts b/src/typings/toastify.d.ts new file mode 100644 index 0000000..42840ce --- /dev/null +++ b/src/typings/toastify.d.ts @@ -0,0 +1,36 @@ +declare namespace StartToastifyInstance { + function reposition(): void; + interface Offset { + x: number | string; + y: number | string; + } + interface Options { + text?: string | undefined; + node?: Node | undefined; + duration?: number | undefined; + selector?: string | Node | undefined; + destination?: string | undefined; + newWindow?: boolean | undefined; + close?: boolean | undefined; + gravity?: 'top' | 'bottom' | undefined; + position?: 'left' | 'center' | 'right' | undefined; + avatar?: string | undefined; + className?: string | undefined; + stopOnFocus?: boolean | undefined; + callback?: (() => void) | undefined; + onClick?: (() => void) | undefined; + offset?: Offset | undefined; + escapeMarkup?: boolean | undefined; + style?: { [cssRule: string]: string }; + oldestFirst?: boolean | undefined; + } +} +declare class Toastify { + readonly options: StartToastifyInstance.Options; + readonly toastElement: Element | null; + showToast(): void; + hideToast(): void; +} +declare function StartToastifyInstance(options?: Toastify.Options): Toastify; +export as namespace Toastify; +export = StartToastifyInstance;