@fleetbase/ember-ui
Version:
Fleetbase UI provides all the interface components, helpers, services and utilities for building a Fleetbase extension into the Console.
281 lines (244 loc) • 8.64 kB
JavaScript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { isArray } from '@ember/array';
import { action } from '@ember/object';
import calculatePosition from 'ember-basic-dropdown/utils/calculate-position';
function uniqBy(arr, key) {
return arr.reduce((unique, item) => {
if (!unique.some((existingItem) => existingItem[key] === item[key])) {
unique.push(item);
}
return unique;
}, []);
}
/**
* NotificationTrayComponent is a Glimmer component for handling notifications.
*
* @class NotificationTrayComponent
* @extends Component
*/
export default class NotificationTrayComponent extends Component {
socket;
store;
fetch;
currentUser;
universe;
notification;
media;
/**
* An array to store notifications.
*
* @memberof NotificationTrayComponent
* @type {Array}
*/
notifications = [];
/**
* A reference to the notification sound.
*
* @memberof NotificationTrayComponent
* @type {Audio}
*/
notificationSound = new Audio('/sounds/notification-sound.mp3');
/**
* Creates an instance of the NotificationTrayComponent
*/
constructor() {
super(...arguments);
this.listenForNotificationFrom(`user.${this.currentUser.id}`);
this.listenForNotificationFrom(`company.${this.currentUser.companyId}`);
this.fetchNotificationsFromStore();
if (typeof this.args.onInitialize === 'function') {
this.args.onInitialize(this.context);
}
// listen for notifications deleted
this.universe.on('notifications.deleted', (notifications) => {
this.removeNotifications(notifications);
});
// listen for notifications read
this.universe.on('notifications.read', () => {
this.fetchNotificationsFromStore();
});
// listen if all notifications read
this.universe.on('notifications.all_read', () => {
this.fetchNotificationsFromStore();
});
}
/**
* Calculate dropdown content position.
*
* @param {HTMLElement} trigger
* @param {HTMLElement} content
* @return {Object}
* @memberof NotificationTrayComponent
*/
calculatePosition(trigger, content) {
if (this.media.isMobile) {
content.classList.add('is-mobile');
const triggerRect = trigger.getBoundingClientRect();
const top = triggerRect.height + triggerRect.top;
return { style: { left: '0px', right: '0px', top, padding: '0 0.5rem', width: '100%' } };
}
return calculatePosition(...arguments);
}
/**
* Handles the click event on a notification.
*
* @param {NotificationModel} notification - The clicked notification.
* @returns {Promise} A promise that resolves after marking the notification as read.
* @memberof NotificationTrayComponent
*/
onClickNotification(notification) {
notification.setProperties({
read_at: new Date(),
_isRemoving: true,
});
return notification.save().then(() => {
this.removeNotifications([notification]);
});
}
/**
* Registers the dropdown API.
*
* @param {DropdownApi} dropdownApi - The dropdown API instance.
* @memberof NotificationTrayComponent
*/
registerAPI(dropdownApi) {
this.dropdownApi = dropdownApi;
if (typeof this.args.registerAPI === 'function') {
this.args.registerAPI(...arguments);
}
}
/**
* Handler for when "View all notifications" link is pressed in footer
*
* @returns {void}
* @memberof NotificationTrayComponent
*/
onPressViewAllNotifications() {
if (typeof this.args.onPressViewAllNotifications === 'function') {
this.args.onPressViewAllNotifications();
}
}
/**
* Listens for notifications from a specific channel.
*
* @param {string} channelId - The channel to listen to.
* @memberof NotificationTrayComponent
*/
async listenForNotificationFrom(channelId) {
// setup socket
const socket = this.socket.instance();
// listen on company channel
const channel = socket.subscribe(channelId);
// listen to channel for events
await channel.listener('subscribe').once();
// get incoming data and console out
(async () => {
for await (let incomingNotification of channel) {
if (typeof incomingNotification === 'object' && typeof incomingNotification.notification_id === 'string') {
this.onReceivedNotification(incomingNotification);
}
}
})();
}
/**
* Handles a received notification by fetching the notification record and processing it.
*
* @param {Object} notificationData - The received notification data.
* @param {string} notificationData.id - The unique identifier of the notification.
* @returns {Promise} A promise that resolves after processing the notification.
* @memberof NotificationTrayComponent
*/
onReceivedNotification({ id }) {
return this.getNotificationRecordUsingId(id).then((notification) => {
// add to notifications array
this.insertNotifications(notification);
// trigger notification sound
this.ping();
// handle callback
if (typeof this.args.onReceivedNotification === 'function') {
this.args.onReceivedNotification(notification);
}
// trigger universe event
this.universe.trigger('notification.received', notification);
});
}
/**
* Inserts one or more notifications into the notifications array, ensuring uniqueness.
*
* @param {Array|Object} notifications - The notification(s) to insert into the array.
* @memberof NotificationTrayComponent
*/
insertNotifications(notifications) {
let _notifications = [...this.notifications];
if (isArray(notifications)) {
_notifications.pushObjects(notifications);
} else {
_notifications.pushObject(notifications);
}
this.mutateNotifications(_notifications);
}
/**
* Removes notifications from the tracked notifications array.
*
* @param {Array} notifications
* @memberof NotificationTrayComponent
*/
removeNotifications(notifications) {
let _notifications = [...this.notifications];
if (isArray(notifications)) {
_notifications.removeObjects(notifications);
} else {
_notifications.removeObject(notifications);
}
this.mutateNotifications(_notifications);
}
/**
* mutates the tracked property with a filtered notifications array.
*
* @param {Array} notifications
* @memberof NotificationTrayComponent
*/
mutateNotifications(notifications) {
this.notifications = uniqBy(
notifications.filter(({ read_at }) => !read_at),
'id'
);
}
/**
* Fetches a notification record using its unique identifier.
*
* @param {string} id - The unique identifier of the notification.
* @returns {Promise} A promise that resolves with the notification record.
* @memberof NotificationTrayComponent
*/
getNotificationRecordUsingId(id) {
return this.store.findRecord('notification', id);
}
/**
* Fetches notifications from the store.
*
* @memberof NotificationTrayComponent
*/
fetchNotificationsFromStore() {
this.store.query('notification', { sort: '-created_at', limit: 20, unread: true }).then((notifications) => {
// this.insertNotifications(notifications);
this.notifications = uniqBy(
notifications.filter(({ read_at }) => !read_at),
'id'
);
if (typeof this.args.onNotificationsLoaded === 'function') {
this.args.onNotificationsLoaded(this.notifications);
}
});
}
/**
* Plays the notification sound.
*
* @memberof NotificationTrayComponent
*/
ping() {
this.notificationSound.play();
}
}