DEV: Move desktop notifications logic to service (#24466)

This commit is contained in:
Mark VanLandingham 2023-11-29 08:20:48 -06:00 committed by GitHub
parent 8cf13977a1
commit c4767158df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 268 additions and 244 deletions

View File

@ -0,0 +1,46 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import i18n from "discourse-common/helpers/i18n";
export default class DesktopNotificationsConfig extends Component {
@service desktopNotifications;
<template>
<div class="controls">
{{#if this.desktopNotifications.isNotSupported}}
<DButton
@icon="bell-slash"
@label="user.desktop_notifications.not_supported"
@disabled="true"
class="btn-default"
/>
{{/if}}
{{#if this.desktopNotifications.isDeniedPermission}}
<DButton
@icon="bell-slash"
@label="user.desktop_notifications.perm_denied_btn"
@disabled="true"
class="btn-default"
/>
{{i18n "user.desktop_notifications.perm_denied_expl"}}
{{else}}
{{#if this.desktopNotifications.isSubscribed}}
<DButton
@icon="far-bell-slash"
@label="user.desktop_notifications.disable"
@action={{this.desktopNotifications.disable}}
class="btn-default"
/>
{{else}}
<DButton
@icon="far-bell"
@label="user.desktop_notifications.enable"
@action={{this.desktopNotifications.enable}}
class="btn-default"
/>
{{/if}}
{{/if}}
</div>
</template>
}

View File

@ -1,34 +0,0 @@
{{#if this.isNotSupported}}
<DButton
@icon="bell-slash"
@label="user.desktop_notifications.not_supported"
@disabled="true"
class="btn-default"
/>
{{/if}}
{{#if this.isDeniedPermission}}
<DButton
@icon="bell-slash"
@label="user.desktop_notifications.perm_denied_btn"
@action={{action "recheckPermission"}}
@disabled="true"
class="btn-default"
/>
{{i18n "user.desktop_notifications.perm_denied_expl"}}
{{else}}
{{#if this.isSubscribed}}
<DButton
@icon="far-bell-slash"
@label="user.desktop_notifications.disable"
@action={{action "turnoff"}}
class="btn-default"
/>
{{else}}
<DButton
@icon="far-bell"
@label="user.desktop_notifications.enable"
@action={{action "turnon"}}
class="btn-default"
/>
{{/if}}
{{/if}}

View File

@ -1,138 +0,0 @@
import Component from "@ember/component";
import { or } from "@ember/object/computed";
import {
confirmNotification,
context,
} from "discourse/lib/desktop-notifications";
import KeyValueStore from "discourse/lib/key-value-store";
import {
isPushNotificationsSupported,
keyValueStore as pushNotificationKeyValueStore,
subscribe as subscribePushNotification,
unsubscribe as unsubscribePushNotification,
userSubscriptionKey as pushNotificationUserSubscriptionKey,
} from "discourse/lib/push-notifications";
import discourseComputed from "discourse-common/utils/decorators";
const keyValueStore = new KeyValueStore(context);
export default Component.extend({
classNames: ["controls"],
@discourseComputed("isNotSupported")
notificationsPermission(isNotSupported) {
return isNotSupported ? "" : Notification.permission;
},
@discourseComputed
notificationsDisabled: {
set(value) {
keyValueStore.setItem("notifications-disabled", value);
return keyValueStore.getItem("notifications-disabled");
},
get() {
return keyValueStore.getItem("notifications-disabled");
},
},
@discourseComputed
isNotSupported() {
return typeof window.Notification === "undefined";
},
@discourseComputed("isNotSupported", "notificationsPermission")
isDeniedPermission(isNotSupported, notificationsPermission) {
return isNotSupported ? false : notificationsPermission === "denied";
},
@discourseComputed("isNotSupported", "notificationsPermission")
isGrantedPermission(isNotSupported, notificationsPermission) {
return isNotSupported ? false : notificationsPermission === "granted";
},
@discourseComputed("isGrantedPermission", "notificationsDisabled")
isEnabledDesktop(isGrantedPermission, notificationsDisabled) {
return isGrantedPermission ? !notificationsDisabled : false;
},
// TODO: (selase) getter should consistently return a boolean
@discourseComputed
isEnabledPush: {
set(value) {
const user = this.currentUser;
if (!user) {
return false;
}
pushNotificationKeyValueStore.setItem(
pushNotificationUserSubscriptionKey(user),
value
);
return pushNotificationKeyValueStore.getItem(
pushNotificationUserSubscriptionKey(user)
);
},
get() {
const user = this.currentUser;
return user
? pushNotificationKeyValueStore.getItem(
pushNotificationUserSubscriptionKey(user)
)
: false;
},
},
isEnabled: or("isEnabledDesktop", "isEnabledPush"),
@discourseComputed("isEnabled", "isEnabledPush", "notificationsDisabled")
isSubscribed(isEnabled, isEnabledPush, notificationsDisabled) {
if (!isEnabled) {
return false;
}
if (this.isPushNotificationsPreferred()) {
return isEnabledPush === "subscribed";
} else {
return notificationsDisabled === "";
}
},
isPushNotificationsPreferred() {
return (
(this.site.mobileView ||
this.siteSettings.enable_desktop_push_notifications) &&
isPushNotificationsSupported()
);
},
actions: {
recheckPermission() {
this.notifyPropertyChange("notificationsPermission");
},
turnoff() {
if (this.isEnabledDesktop) {
this.set("notificationsDisabled", "disabled");
this.notifyPropertyChange("notificationsPermission");
}
if (this.isEnabledPush) {
unsubscribePushNotification(this.currentUser, () => {
this.set("isEnabledPush", "");
});
}
},
turnon() {
if (this.isPushNotificationsPreferred()) {
subscribePushNotification(() => {
this.set("isEnabledPush", "subscribed");
}, this.siteSettings.vapid_public_key_bytes);
} else {
this.set("notificationsDisabled", "");
Notification.requestPermission(() => {
confirmNotification(this.siteSettings);
this.notifyPropertyChange("notificationsPermission");
});
}
},
},
});

View File

@ -0,0 +1,79 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import { keyValueStore as pushNotificationKeyValueStore } from "discourse/lib/push-notifications";
import i18n from "discourse-common/helpers/i18n";
const userDismissedPromptKey = "dismissed-prompt";
export default class NotificationConsentBanner extends Component {
@service capabilities;
@service currentUser;
@service desktopNotifications;
@service siteSettings;
@tracked bannerDismissed;
constructor() {
super(...arguments);
this.bannerDismissed = pushNotificationKeyValueStore.getItem(
userDismissedPromptKey
);
}
setBannerDismissed(value) {
pushNotificationKeyValueStore.setItem(userDismissedPromptKey, value);
this.bannerDismissed = pushNotificationKeyValueStore.getItem(
userDismissedPromptKey
);
}
get showNotificationPromptBanner() {
return (
this.siteSettings.push_notifications_prompt &&
!this.desktopNotifications.isNotSupported &&
this.currentUser &&
this.capabilities.isPwa &&
Notification.permission !== "denied" &&
Notification.permission !== "granted" &&
!this.desktopNotifications.isEnabled &&
!this.bannerDismissed
);
}
@action
turnon() {
this.desktopNotifications.enable();
this.setBannerDismissed(true);
}
@action
dismiss() {
this.setBannerDismissed(false);
}
<template>
{{#if this.showNotificationPromptBanner}}
<div class="row">
<div class="consent_banner alert alert-info">
<span>
{{i18n "user.desktop_notifications.consent_prompt"}}
<DButton
@display="link"
@action={{this.turnon}}
@label="user.desktop_notifications.enable"
/>
</span>
<DButton
@icon="times"
@action={{this.dismiss}}
@title="banner.close"
class="btn-flat close"
/>
</div>
</div>
{{/if}}
</template>
}

View File

@ -1,20 +0,0 @@
{{#if this.showNotificationPromptBanner}}
<div class="row">
<div class="consent_banner alert alert-info">
<span>
{{i18n "user.desktop_notifications.consent_prompt"}}
<DButton
@display="link"
@action={{action "turnon"}}
@label="user.desktop_notifications.enable"
/>
</span>
<DButton
@icon="times"
@action={{action "dismiss"}}
@title="banner.close"
class="btn-flat close"
/>
</div>
</div>
{{/if}}

View File

@ -1,52 +0,0 @@
import DesktopNotificationConfig from "discourse/components/desktop-notification-config";
import { keyValueStore as pushNotificationKeyValueStore } from "discourse/lib/push-notifications";
import discourseComputed from "discourse-common/utils/decorators";
const userDismissedPromptKey = "dismissed-prompt";
export default DesktopNotificationConfig.extend({
@discourseComputed
bannerDismissed: {
set(value) {
pushNotificationKeyValueStore.setItem(userDismissedPromptKey, value);
return pushNotificationKeyValueStore.getItem(userDismissedPromptKey);
},
get() {
return pushNotificationKeyValueStore.getItem(userDismissedPromptKey);
},
},
@discourseComputed(
"isNotSupported",
"isEnabled",
"bannerDismissed",
"currentUser.any_posts"
)
showNotificationPromptBanner(
isNotSupported,
isEnabled,
bannerDismissed,
anyPosts
) {
return (
this.siteSettings.push_notifications_prompt &&
!isNotSupported &&
this.currentUser &&
(this.capabilities.isPwa || anyPosts) &&
Notification.permission !== "denied" &&
Notification.permission !== "granted" &&
!isEnabled &&
!bannerDismissed
);
},
actions: {
turnon() {
this._super(...arguments);
this.set("bannerDismissed", true);
},
dismiss() {
this.set("bannerDismissed", true);
},
},
});

View File

@ -0,0 +1,143 @@
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import Service, { inject as service } from "@ember/service";
import {
confirmNotification,
context,
} from "discourse/lib/desktop-notifications";
import { disableImplicitInjections } from "discourse/lib/implicit-injections";
import KeyValueStore from "discourse/lib/key-value-store";
import {
isPushNotificationsSupported,
keyValueStore as pushNotificationKeyValueStore,
subscribe as subscribePushNotification,
unsubscribe as unsubscribePushNotification,
userSubscriptionKey as pushNotificationUserSubscriptionKey,
} from "discourse/lib/push-notifications";
const keyValueStore = new KeyValueStore(context);
@disableImplicitInjections
export default class DesktopNotificationsService extends Service {
@service currentUser;
@service site;
@service siteSettings;
@tracked notificationsDisabled;
@tracked isEnabledPush;
constructor() {
super(...arguments);
this.notificationsDisabled = keyValueStore.getItem(
"notifications-disabled"
);
this.isEnabledPush = this.currentUser
? pushNotificationKeyValueStore.getItem(
pushNotificationUserSubscriptionKey(this.currentUser)
)
: false;
}
get isNotSupported() {
return typeof window.Notification === "undefined";
}
get notificationsPermission() {
return this.isNotSupported ? "" : Notification.permission;
}
setNotificationsDisabled(value) {
keyValueStore.setItem("notifications-disabled", value);
this.notificationsDisabled = keyValueStore.getItem(
"notifications-disabled"
);
}
get isDeniedPermission() {
if (this.isNotSupported) {
return false;
}
return this.notificationsPermission === "denied";
}
get isGrantedPermission() {
if (this.isNotSupported) {
return false;
}
return this.notificationsPermission === "granted";
}
get isEnabledDesktop() {
if (this.isGrantedPermission) {
return this.notificationsDisabled;
}
return false;
}
setIsEnabledPush(value) {
const user = this.currentUser;
if (!user) {
return false;
}
pushNotificationKeyValueStore.setItem(
pushNotificationUserSubscriptionKey(user),
value
);
this.isEnabledPush = pushNotificationKeyValueStore.getItem(
pushNotificationUserSubscriptionKey(user)
);
}
get isEnabled() {
return this.isEnabledDesktop || this.isEnabledPush;
}
get isSubscribed() {
if (!this.isEnabled) {
return false;
}
if (this.isPushNotificationsPreferred) {
return this.isEnabledPush === "subscribed";
} else {
return this.notificationsDisabled === "";
}
}
get isPushNotificationsPreferred() {
return (
(this.site.mobileView ||
this.siteSettings.enable_desktop_push_notifications) &&
isPushNotificationsSupported()
);
}
@action
disable() {
if (this.isEnabledDesktop) {
this.setNotificationsDisabled("disabled");
}
if (this.isEnabledPush) {
unsubscribePushNotification(this.currentUser, () => {
this.setIsEnabledPush("");
});
}
}
@action
enable() {
if (this.isPushNotificationsPreferred) {
subscribePushNotification(() => {
this.setIsEnabledPush("subscribed");
}, this.siteSettings.vapid_public_key_bytes);
} else {
this.setNotificationsDisabled("");
Notification.requestPermission(() => {
confirmNotification(this.siteSettings);
});
}
}
}