mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-03-21 17:50:29 -05:00
Preact: Support desktop notifications (#2441)
--------- Co-authored-by: Guangcong Luo <guangcongluo@gmail.com>
This commit is contained in:
parent
2a195dac3b
commit
b1fc3b2d9e
|
|
@ -1304,7 +1304,7 @@ export class BattleLog {
|
|||
this.changeUhtml(parts[0], htmlSrc, cmd === 'uhtml');
|
||||
return ['', ''];
|
||||
case 'raw':
|
||||
return ['chat', BattleLog.sanitizeHTML(target)];
|
||||
return ['chat', BattleLog.sanitizeHTML(target), true];
|
||||
case 'nonotify':
|
||||
return ['chat', BattleLog.sanitizeHTML(target), true];
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -899,6 +899,7 @@ interface PSNotificationState {
|
|||
id: string;
|
||||
/** normally: automatically dismiss the notification when viewing the room; set this to require manual dismissing */
|
||||
noAutoDismiss: boolean;
|
||||
notification?: Notification | null;
|
||||
}
|
||||
|
||||
type ClientCommands<RoomT extends PSRoom> = {
|
||||
|
|
@ -1003,7 +1004,24 @@ export class PSRoom extends PSStreamModel<Args | null> implements RoomOptions {
|
|||
return null;
|
||||
}
|
||||
notify(options: { title: string, body?: string, noAutoDismiss?: boolean, id?: string }) {
|
||||
if (PS.isVisible(this)) return;
|
||||
let desktopNotification: Notification | null = null;
|
||||
const roomIsFocused = document.hasFocus?.() && PS.isVisible(this);
|
||||
if (roomIsFocused && !options.noAutoDismiss) return;
|
||||
if (!roomIsFocused) {
|
||||
PS.playNotificationSound();
|
||||
try {
|
||||
desktopNotification = new Notification(options.title, { body: options.body });
|
||||
if (desktopNotification) {
|
||||
desktopNotification.onclick = () => {
|
||||
window.focus();
|
||||
PS.focusRoom(this.id);
|
||||
};
|
||||
if (PS.prefs.temporarynotifications) {
|
||||
setTimeout(() => { desktopNotification?.close(); }, 5000);
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
if (options.noAutoDismiss && !options.id) {
|
||||
throw new Error(`Must specify id for manual dismissing`);
|
||||
}
|
||||
|
|
@ -1015,6 +1033,7 @@ export class PSRoom extends PSStreamModel<Args | null> implements RoomOptions {
|
|||
body: options.body,
|
||||
id: options.id || '',
|
||||
noAutoDismiss: options.noAutoDismiss || false,
|
||||
notification: desktopNotification,
|
||||
});
|
||||
PS.update();
|
||||
}
|
||||
|
|
@ -1024,7 +1043,13 @@ export class PSRoom extends PSStreamModel<Args | null> implements RoomOptions {
|
|||
PS.update();
|
||||
}
|
||||
dismissNotification(id: string) {
|
||||
this.notifications = this.notifications.filter(notification => notification.id !== id);
|
||||
const index = this.notifications.findIndex(n => n.id === id);
|
||||
if (index !== -1) {
|
||||
try {
|
||||
this.notifications[index].notification?.close();
|
||||
} catch {}
|
||||
this.notifications.splice(index, 1);
|
||||
}
|
||||
PS.update();
|
||||
}
|
||||
autoDismissNotifications() {
|
||||
|
|
@ -1866,6 +1891,11 @@ export const PS = new class extends PSModel {
|
|||
}
|
||||
}
|
||||
|
||||
// for old versions of Safari
|
||||
if (window.webkitNotification) {
|
||||
window.Notification ||= window.webkitNotification;
|
||||
}
|
||||
|
||||
this.updateLayout();
|
||||
window.addEventListener('resize', () => {
|
||||
// super.update() skips another updateLayout() call
|
||||
|
|
@ -2331,10 +2361,12 @@ export const PS = new class extends PSModel {
|
|||
addRoom(options: RoomOptions, noFocus = false) {
|
||||
// support hardcoded PM room-IDs
|
||||
if (options.id.startsWith('challenge-')) {
|
||||
this.requestNotifications();
|
||||
options.id = `dm-${options.id.slice(10)}` as RoomID;
|
||||
options.args = { challengeMenuOpen: true };
|
||||
}
|
||||
if (options.id.startsWith('dm-')) {
|
||||
this.requestNotifications();
|
||||
if (options.id.length >= 5 && options.id.endsWith('--')) {
|
||||
options.id = options.id.slice(0, -2) as RoomID;
|
||||
options.args = { initialSlash: true };
|
||||
|
|
@ -2628,4 +2660,23 @@ export const PS = new class extends PSModel {
|
|||
this.prefs.set('autojoin', autojoin);
|
||||
}
|
||||
}
|
||||
requestNotifications() {
|
||||
try {
|
||||
if (window.webkitNotifications?.requestPermission) {
|
||||
// Notification.requestPermission crashes Chrome 23:
|
||||
// https://code.google.com/p/chromium/issues/detail?id=139594
|
||||
// In lieu of a way to detect Chrome 23, we'll just use the old
|
||||
// requestPermission API, which works to request permissions for
|
||||
// the new Notification spec anyway.
|
||||
window.webkitNotifications.requestPermission();
|
||||
} else if (window.Notification) {
|
||||
Notification.requestPermission?.(permission => {});
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
playNotificationSound() {
|
||||
if (window.BattleSound && !this.prefs.mute) {
|
||||
window.BattleSound.playSound('audio/notification.wav', this.prefs.notifvolume);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -366,8 +366,25 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
|
|||
|
||||
room.request = request;
|
||||
room.choices = new BattleChoiceBuilder(request);
|
||||
this.notifyRequest();
|
||||
room.update(null);
|
||||
}
|
||||
notifyRequest() {
|
||||
const room = this.props.room;
|
||||
let oName = room.battle.farSide.name;
|
||||
if (oName) oName = " against " + oName;
|
||||
switch (room.request?.requestType) {
|
||||
case 'move':
|
||||
room.notify({ title: "Your move!", body: "Move in your battle" + oName });
|
||||
break;
|
||||
case 'switch':
|
||||
room.notify({ title: "Your switch!", body: "Switch in your battle" + oName });
|
||||
break;
|
||||
case 'team':
|
||||
room.notify({ title: "Team preview!", body: "Choose your team order in your battle" + oName });
|
||||
break;
|
||||
}
|
||||
}
|
||||
renderControls() {
|
||||
const room = this.props.room;
|
||||
if (!room.battle) return null;
|
||||
|
|
|
|||
|
|
@ -125,7 +125,17 @@ export class ChatRoom extends PSRoom {
|
|||
this.joinLeave = null;
|
||||
this.markUserActive(args[args[0] === 'c:' ? 2 : 1]);
|
||||
if (this.tour) this.tour.joinLeave = null;
|
||||
this.subtleNotify();
|
||||
if (this.id.startsWith("dm-")) {
|
||||
const fromUser = args[args[0] === 'c:' ? 2 : 1];
|
||||
if (toID(fromUser) === PS.user.userid) break;
|
||||
const message = args[args[0] === 'c:' ? 3 : 2];
|
||||
this.notify({
|
||||
title: `${this.title}`,
|
||||
body: message,
|
||||
});
|
||||
} else {
|
||||
this.subtleNotify();
|
||||
}
|
||||
break;
|
||||
case ':':
|
||||
this.timeOffset = Math.trunc(Date.now() / 1000) - (parseInt(args[1], 10) || 0);
|
||||
|
|
@ -1110,6 +1120,7 @@ class ChatPanel extends PSRoomPanel<ChatRoom> {
|
|||
return false;
|
||||
};
|
||||
makeChallenge = (e: Event, format: string, team?: Team) => {
|
||||
PS.requestNotifications();
|
||||
const room = this.props.room;
|
||||
const packedTeam = team ? team.packedTeam : '';
|
||||
const privacy = PS.mainmenu.adjustPrivacy();
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ export class MainMenuRoom extends PSRoom {
|
|||
return '';
|
||||
}
|
||||
startSearch = (format: string, team?: Team) => {
|
||||
PS.requestNotifications();
|
||||
if (this.searchCountdown) {
|
||||
PS.alert("Wait for this countdown to finish first...");
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -324,6 +324,11 @@ export class PSMiniHeader extends preact.Component {
|
|||
override render() {
|
||||
if (PS.leftPanelWidth !== null) return null;
|
||||
|
||||
let notificationsCount = 0;
|
||||
for (const roomid of PS.leftRoomList) {
|
||||
const miniNotifications = PS.rooms[roomid]?.notifications;
|
||||
if (miniNotifications?.length) notificationsCount++;
|
||||
}
|
||||
const { icon, title } = PSHeader.roomInfo(PS.panel);
|
||||
const userColor = window.BattleLog && `color:${BattleLog.usernameColor(PS.user.userid)}`;
|
||||
const showMenuButton = PSView.narrowMode;
|
||||
|
|
@ -334,6 +339,7 @@ export class PSMiniHeader extends preact.Component {
|
|||
null
|
||||
) : window.scrollX ? (
|
||||
<button onClick={PSView.scrollToHeader} class={`mini-header-left ${notifying}`} aria-label="Menu">
|
||||
{!!notificationsCount && <div class="notification-badge">{notificationsCount}</div>}
|
||||
<i class="fa fa-bars" aria-hidden></i>
|
||||
</button>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -1494,7 +1494,7 @@ pre.textbox.textbox-empty[placeholder]:before {
|
|||
}
|
||||
.userlist-hidden {
|
||||
width: auto;
|
||||
border-right: 0;
|
||||
border-right: 0;
|
||||
bottom: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
|
|
@ -2451,3 +2451,18 @@ pre.textbox.textbox-empty[placeholder]:before {
|
|||
.dark .chat.mine {
|
||||
background: rgba(255,255,255,0.05);
|
||||
}
|
||||
|
||||
.notification-badge {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 13px;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
background: #e9790d;
|
||||
color: white;
|
||||
font-size: 9px;
|
||||
font-weight: bold;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user