added toast notifications

This commit is contained in:
Rudis Muiznieks 2023-04-13 19:51:16 -05:00
parent e512862aed
commit 018b4d3b30
Signed by: rudism
GPG Key ID: CABF2F86EF7884F9
7 changed files with 159 additions and 20 deletions

View File

@ -5,6 +5,7 @@
<meta charset='utf-8'> <meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel='stylesheet' href='/style.css'> <link rel='stylesheet' href='/style.css'>
<link rel='stylesheet' href='/toastify.min.css'>
<link rel='icon' type='image/png' sizes='32x32' href='/favicon-32x32.png'> <link rel='icon' type='image/png' sizes='32x32' href='/favicon-32x32.png'>
<link rel='icon' type='image/png' sizes='16x16' href='/favicon-16x16.png'> <link rel='icon' type='image/png' sizes='16x16' href='/favicon-16x16.png'>
<link rel='icon' type='image/svg' href='/favicon.svg'> <link rel='icon' type='image/svg' href='/favicon.svg'>
@ -155,6 +156,7 @@
</footer> </footer>
<script src='/htmx.min.js'></script> <script src='/htmx.min.js'></script>
<script src='/howler.min.js'></script> <script src='/howler.min.js'></script>
<script src='/toastify.min.js'></script>
<script src='/radiostasis.js'></script> <script src='/radiostasis.js'></script>
</body> </body>
</html> </html>

View File

@ -101,7 +101,7 @@ var Player = /** @class */ (function () {
this.stopPlaybackAndResetUi(); this.stopPlaybackAndResetUi();
this.setPauseButtonUI(); this.setPauseButtonUI();
this.episode = episode; this.episode = episode;
this.playlist.removeEpisode(episode); this.playlist.removeEpisode(episode, false, false);
this.updateNowPlayingUI(true); this.updateNowPlayingUI(true);
void fetch("/api/r/".concat(episode.file)) void fetch("/api/r/".concat(episode.file))
.then(function (res) { return __awaiter(_this, void 0, void 0, function () { .then(function (res) { return __awaiter(_this, void 0, void 0, function () {
@ -111,7 +111,7 @@ var Player = /** @class */ (function () {
switch (_a.label) { switch (_a.label) {
case 0: case 0:
if (!res.ok) { if (!res.ok) {
this.setErrorUI("API returned ".concat(res.status)); this.setErrorUI("Error fetching episode (".concat(res.status, ")"));
return [2 /*return*/]; return [2 /*return*/];
} }
return [4 /*yield*/, res.json()]; return [4 /*yield*/, res.json()];
@ -139,14 +139,14 @@ var Player = /** @class */ (function () {
_this.stopTicker(); _this.stopTicker();
}, },
onend: function () { return _this.nextEpisode(); }, onend: function () { return _this.nextEpisode(); },
onloaderror: function () { return _this.setErrorUI('Playback error'); }, onloaderror: function () { return _this.setErrorUI('Error playing episode.'); },
}); });
return [2 /*return*/]; return [2 /*return*/];
} }
}); });
}); }) }); })
.catch(function () { .catch(function () {
_this.setErrorUI('Fetch episode error'); _this.setErrorUI('Error downloading episode.');
}); });
}; };
Player.prototype.isPlaying = function () { Player.prototype.isPlaying = function () {
@ -157,9 +157,16 @@ var Player = /** @class */ (function () {
}; };
Player.prototype.setErrorUI = function (message) { Player.prototype.setErrorUI = function (message) {
this.stopPlaybackAndResetUi(); this.stopPlaybackAndResetUi();
this.seriesName.innerHTML = 'Error playing episode'; Toastify({
this.episodeName.innerHTML = message; text: message,
this.nowPlaying.classList.add('error'); gravity: 'bottom',
position: 'right',
style: {
marginBottom: '10ex',
background: '#a00',
color: '#fff',
},
}).showToast();
}; };
Player.prototype.playPause = function () { Player.prototype.playPause = function () {
if (this.howl && this.howl.playing()) 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++) { for (var _i = 0, episodes_1 = episodes; _i < episodes_1.length; _i++) {
var episode = episodes_1[_i]; var episode = episodes_1[_i];
if (this.isQueued(episode)) if (this.isQueued(episode))
this.removeEpisode(episode, true); this.removeEpisode(episode, true, false);
var litem = this.createQueueListItem(episode); var litem = this.createQueueListItem(episode);
this.episodes.push([episode, litem]); this.episodes.push([episode, litem]);
this.episodeHash.add(episode.id); this.episodeHash.add(episode.id);
this.queueList.appendChild(litem); this.queueList.appendChild(litem);
} }
this.notify(episodes.length == 1
? 'Episode added to queue.'
: "".concat(episodes.length, " episodes added to queue."));
this.playlistChanged(); this.playlistChanged();
}; };
Playlist.prototype.unshiftEpisodes = function (episodes) { Playlist.prototype.unshiftEpisodes = function (episodes) {
for (var i = episodes.length - 1; i >= 0; i--) { for (var i = episodes.length - 1; i >= 0; i--) {
var episode = episodes[i]; var episode = episodes[i];
if (this.isQueued(episode)) if (this.isQueued(episode))
this.removeEpisode(episode, true); this.removeEpisode(episode, true, false);
var litem = this.createQueueListItem(episode); var litem = this.createQueueListItem(episode);
this.episodes.unshift([episode, litem]); this.episodes.unshift([episode, litem]);
this.episodeHash.add(episode.id); this.episodeHash.add(episode.id);
this.queueList.prepend(litem); this.queueList.prepend(litem);
} }
this.notify(episodes.length == 1
? 'Episode added to queue.'
: "".concat(episodes.length, " episodes added to queue."));
this.playlistChanged(); this.playlistChanged();
}; };
Playlist.prototype.removeEpisode = function (episode, ignoreChanged) { Playlist.prototype.removeEpisode = function (episode, ignoreChanged, notify) {
if (ignoreChanged === void 0) { ignoreChanged = false; } if (ignoreChanged === void 0) { ignoreChanged = false; }
if (notify === void 0) { notify = true; }
if (this.isQueued(episode)) { if (this.isQueued(episode)) {
var idx = this.episodes.findIndex(function (e) { return e[0].id == episode.id; }); var idx = this.episodes.findIndex(function (e) { return e[0].id == episode.id; });
var deleted = this.episodes.splice(idx, 1); var deleted = this.episodes.splice(idx, 1);
@ -381,6 +395,8 @@ var Playlist = /** @class */ (function () {
} }
if (!ignoreChanged) if (!ignoreChanged)
this.playlistChanged(); this.playlistChanged();
if (notify)
this.notify('Episode removed from queue.');
}; };
Playlist.prototype.isQueued = function (episode) { Playlist.prototype.isQueued = function (episode) {
return this.episodeHash.has(episode.id); return this.episodeHash.has(episode.id);
@ -389,10 +405,14 @@ var Playlist = /** @class */ (function () {
this.playlistChanged(); this.playlistChanged();
}; };
Playlist.prototype.clearPlaylist = function () { Playlist.prototype.clearPlaylist = function () {
var removed = this.episodes.length;
this.episodes.length = 0; this.episodes.length = 0;
this.episodeHash.clear(); this.episodeHash.clear();
this.queueList.innerHTML = ''; this.queueList.innerHTML = '';
this.playlistChanged(); this.playlistChanged();
if (removed > 0) {
this.notify('Playlist cleared.');
}
}; };
Playlist.prototype.playlistChanged = function () { Playlist.prototype.playlistChanged = function () {
for (var _i = 0, _a = this.changedHandlers; _i < _a.length; _i++) { for (var _i = 0, _a = this.changedHandlers; _i < _a.length; _i++) {
@ -449,6 +469,18 @@ var Playlist = /** @class */ (function () {
item.appendChild(controls); item.appendChild(controls);
return item; return item;
}; };
Playlist.prototype.notify = function (message) {
Toastify({
text: message,
gravity: 'bottom',
position: 'right',
style: {
marginBottom: '10ex',
background: '#090',
color: '#fff',
},
}).showToast();
};
return Playlist; return Playlist;
}()); }());
var Radiostasis = /** @class */ (function () { var Radiostasis = /** @class */ (function () {

15
site/toastify.min.css vendored Normal file
View File

@ -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 */

15
site/toastify.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -111,13 +111,13 @@ class Player {
this.stopPlaybackAndResetUi(); this.stopPlaybackAndResetUi();
this.setPauseButtonUI(); this.setPauseButtonUI();
this.episode = episode; this.episode = episode;
this.playlist.removeEpisode(episode); this.playlist.removeEpisode(episode, false, false);
this.updateNowPlayingUI(true); this.updateNowPlayingUI(true);
void fetch(`/api/r/${episode.file}`) void fetch(`/api/r/${episode.file}`)
.then(async (res) => { .then(async (res) => {
if (!res.ok) { if (!res.ok) {
this.setErrorUI(`API returned ${res.status}`); this.setErrorUI(`Error fetching episode (${res.status})`);
return; return;
} }
const link = <UrlResponse>await res.json(); const link = <UrlResponse>await res.json();
@ -142,11 +142,11 @@ class Player {
this.stopTicker(); this.stopTicker();
}, },
onend: () => this.nextEpisode(), onend: () => this.nextEpisode(),
onloaderror: () => this.setErrorUI('Playback error'), onloaderror: () => this.setErrorUI('Error playing episode.'),
}); });
}) })
.catch(() => { .catch(() => {
this.setErrorUI('Fetch episode error'); this.setErrorUI('Error downloading episode.');
}); });
} }
@ -159,9 +159,16 @@ class Player {
private setErrorUI(message: string): void { private setErrorUI(message: string): void {
this.stopPlaybackAndResetUi(); this.stopPlaybackAndResetUi();
this.seriesName.innerHTML = 'Error playing episode'; Toastify({
this.episodeName.innerHTML = message; text: message,
this.nowPlaying.classList.add('error'); gravity: 'bottom',
position: 'right',
style: {
marginBottom: '10ex',
background: '#a00',
color: '#fff',
},
}).showToast();
} }
private playPause(): void { private playPause(): void {

View File

@ -63,28 +63,42 @@ class Playlist {
public pushEpisodes(episodes: Array<Episode>): void { public pushEpisodes(episodes: Array<Episode>): void {
for (const episode of episodes) { 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); const litem = this.createQueueListItem(episode);
this.episodes.push([episode, litem]); this.episodes.push([episode, litem]);
this.episodeHash.add(episode.id); this.episodeHash.add(episode.id);
this.queueList.appendChild(litem); this.queueList.appendChild(litem);
} }
this.notify(
episodes.length == 1
? 'Episode added to queue.'
: `${episodes.length} episodes added to queue.`
);
this.playlistChanged(); this.playlistChanged();
} }
public unshiftEpisodes(episodes: Array<Episode>): void { public unshiftEpisodes(episodes: Array<Episode>): void {
for (let i = episodes.length - 1; i >= 0; i--) { for (let i = episodes.length - 1; i >= 0; i--) {
const episode = episodes[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); const litem = this.createQueueListItem(episode);
this.episodes.unshift([episode, litem]); this.episodes.unshift([episode, litem]);
this.episodeHash.add(episode.id); this.episodeHash.add(episode.id);
this.queueList.prepend(litem); this.queueList.prepend(litem);
} }
this.notify(
episodes.length == 1
? 'Episode added to queue.'
: `${episodes.length} episodes added to queue.`
);
this.playlistChanged(); this.playlistChanged();
} }
public removeEpisode(episode: Episode, ignoreChanged = false): void { public removeEpisode(
episode: Episode,
ignoreChanged = false,
notify = true
): void {
if (this.isQueued(episode)) { if (this.isQueued(episode)) {
const idx = this.episodes.findIndex((e) => e[0].id == episode.id); const idx = this.episodes.findIndex((e) => e[0].id == episode.id);
const deleted = this.episodes.splice(idx, 1); const deleted = this.episodes.splice(idx, 1);
@ -92,6 +106,7 @@ class Playlist {
deleted[0][1].remove(); deleted[0][1].remove();
} }
if (!ignoreChanged) this.playlistChanged(); if (!ignoreChanged) this.playlistChanged();
if (notify) this.notify('Episode removed from queue.');
} }
public isQueued(episode: Episode): boolean { public isQueued(episode: Episode): boolean {
@ -103,10 +118,14 @@ class Playlist {
} }
public clearPlaylist(): void { public clearPlaylist(): void {
const removed = this.episodes.length;
this.episodes.length = 0; this.episodes.length = 0;
this.episodeHash.clear(); this.episodeHash.clear();
this.queueList.innerHTML = ''; this.queueList.innerHTML = '';
this.playlistChanged(); this.playlistChanged();
if (removed > 0) {
this.notify('Playlist cleared.');
}
} }
private playlistChanged(): void { private playlistChanged(): void {
@ -162,4 +181,17 @@ class Playlist {
item.appendChild(controls); item.appendChild(controls);
return item; return item;
} }
private notify(message: string): void {
Toastify({
text: message,
gravity: 'bottom',
position: 'right',
style: {
marginBottom: '10ex',
background: '#090',
color: '#fff',
},
}).showToast();
}
} }

36
src/typings/toastify.d.ts vendored Normal file
View File

@ -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;