From 7df28b13f4ae28c416ba4396f9a6142cc4cd8b6c Mon Sep 17 00:00:00 2001 From: Phillip Date: Sun, 11 Jun 2023 05:06:54 +0300 Subject: [PATCH] add notification for q-mail --- core/src/components/app-view.js | 52 ++-- .../notification-view/notification-bell.js | 265 ++++++++++++++++++ core/src/components/show-plugin.js | 43 ++- core/src/notifications/controller.js | 16 +- core/src/notifications/dispatcher.js | 7 +- .../notification-actions/index.js | 2 +- .../notification-actions/new-message.js | 72 ++++- core/src/notifications/types.js | 1 + core/src/redux/app/actions/app-core.js | 9 +- core/src/redux/app/app-action-types.js | 1 + core/src/redux/app/app-reducer.js | 46 +-- electron.js | 32 ++- lib/preload.js | 3 +- .../plugins/core/qdn/browser/browser.src.js | 117 ++++---- 14 files changed, 535 insertions(+), 131 deletions(-) create mode 100644 core/src/components/notification-view/notification-bell.js diff --git a/core/src/components/app-view.js b/core/src/components/app-view.js index 649404a1..435c735a 100644 --- a/core/src/components/app-view.js +++ b/core/src/components/app-view.js @@ -34,9 +34,10 @@ import './user-info-view/user-info-view.js' import '../functional-components/side-menu.js' import '../functional-components/side-menu-item.js' import './start-minting.js' +import './notification-view/notification-bell.js' import { setChatLastSeen } from '../redux/app/app-actions.js' -const parentEpml = new Epml({type: 'WINDOW', source: window.parent}) +const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) class AppView extends connect(store)(LitElement) { static get properties() { @@ -473,6 +474,7 @@ class AppView extends connect(store)(LitElement) { +
@@ -1393,16 +1395,16 @@ class AppView extends connect(store)(LitElement) { } } - const getChatLastSeen=async() => { + const getChatLastSeen = async () => { let items = []; - - await chatLastSeen.iterate(function(value, key, iterationNumber) { - - items.push({key, timestamp: value}); - }) - store.dispatch(setChatLastSeen(items)) + + await chatLastSeen.iterate(function (value, key, iterationNumber) { + + items.push({ key, timestamp: value }); + }) + store.dispatch(setChatLastSeen(items)) return items; - } + } await getOpenTradesBTC() await appDelay(1000) @@ -2153,7 +2155,7 @@ class AppView extends connect(store)(LitElement) { this.addressInfo = state.app.accountInfo.addressInfo if (sideurl === "minting") { - this.shadowRoot.getElementById('qminter').setAttribute('selected','selected') + this.shadowRoot.getElementById('qminter').setAttribute('selected', 'selected') if (this.shadowRoot.getElementById('qbminter').hasAttribute('selected')) { this.shadowRoot.getElementById('qbminter').removeAttribute('selected') } else if (this.shadowRoot.getElementById('qiminter').hasAttribute('selected')) { @@ -2186,7 +2188,7 @@ class AppView extends connect(store)(LitElement) { this.shadowRoot.getElementById('qnode').removeAttribute('selected') } } else if (sideurl === "become-minter") { - this.shadowRoot.getElementById('qbminter').setAttribute('selected','selected') + this.shadowRoot.getElementById('qbminter').setAttribute('selected', 'selected') if (this.shadowRoot.getElementById('qminter').hasAttribute('selected')) { this.shadowRoot.getElementById('qminter').removeAttribute('selected') } else if (this.shadowRoot.getElementById('qiminter').hasAttribute('selected')) { @@ -2219,7 +2221,7 @@ class AppView extends connect(store)(LitElement) { this.shadowRoot.getElementById('qnode').removeAttribute('selected') } } else if (sideurl === "sponsorship-list") { - this.shadowRoot.getElementById('qiminter').setAttribute('selected','selected') + this.shadowRoot.getElementById('qiminter').setAttribute('selected', 'selected') if (this.shadowRoot.getElementById('qminter').hasAttribute('selected')) { this.shadowRoot.getElementById('qminter').removeAttribute('selected') } else if (this.shadowRoot.getElementById('qbminter').hasAttribute('selected')) { @@ -2252,7 +2254,7 @@ class AppView extends connect(store)(LitElement) { this.shadowRoot.getElementById('qnode').removeAttribute('selected') } } else if (sideurl === "wallet") { - this.shadowRoot.getElementById('qwallet').setAttribute('selected','selected') + this.shadowRoot.getElementById('qwallet').setAttribute('selected', 'selected') if (this.shadowRoot.getElementById('qminter').hasAttribute('selected')) { this.shadowRoot.getElementById('qminter').removeAttribute('selected') } else if (this.shadowRoot.getElementById('qbminter').hasAttribute('selected')) { @@ -2285,7 +2287,7 @@ class AppView extends connect(store)(LitElement) { this.shadowRoot.getElementById('qnode').removeAttribute('selected') } } else if (sideurl === "trade-portal") { - this.shadowRoot.getElementById('qtrade').setAttribute('selected','selected') + this.shadowRoot.getElementById('qtrade').setAttribute('selected', 'selected') if (this.shadowRoot.getElementById('qminter').hasAttribute('selected')) { this.shadowRoot.getElementById('qminter').removeAttribute('selected') } else if (this.shadowRoot.getElementById('qbminter').hasAttribute('selected')) { @@ -2318,7 +2320,7 @@ class AppView extends connect(store)(LitElement) { this.shadowRoot.getElementById('qnode').removeAttribute('selected') } } else if (sideurl === "trade-bot-portal") { - this.shadowRoot.getElementById('qbot').setAttribute('selected','selected') + this.shadowRoot.getElementById('qbot').setAttribute('selected', 'selected') if (this.shadowRoot.getElementById('qminter').hasAttribute('selected')) { this.shadowRoot.getElementById('qminter').removeAttribute('selected') } else if (this.shadowRoot.getElementById('qbminter').hasAttribute('selected')) { @@ -2351,7 +2353,7 @@ class AppView extends connect(store)(LitElement) { this.shadowRoot.getElementById('qnode').removeAttribute('selected') } } else if (sideurl === "reward-share") { - this.shadowRoot.getElementById('qrewardshare').setAttribute('selected','selected') + this.shadowRoot.getElementById('qrewardshare').setAttribute('selected', 'selected') if (this.shadowRoot.getElementById('qminter').hasAttribute('selected')) { this.shadowRoot.getElementById('qminter').removeAttribute('selected') } else if (this.shadowRoot.getElementById('qbminter').hasAttribute('selected')) { @@ -2384,7 +2386,7 @@ class AppView extends connect(store)(LitElement) { this.shadowRoot.getElementById('qnode').removeAttribute('selected') } } else if (sideurl === "q-chat") { - this.shadowRoot.getElementById('qchat').setAttribute('selected','selected') + this.shadowRoot.getElementById('qchat').setAttribute('selected', 'selected') if (this.shadowRoot.getElementById('qminter').hasAttribute('selected')) { this.shadowRoot.getElementById('qminter').removeAttribute('selected') } else if (this.shadowRoot.getElementById('qbminter').hasAttribute('selected')) { @@ -2417,7 +2419,7 @@ class AppView extends connect(store)(LitElement) { this.shadowRoot.getElementById('qnode').removeAttribute('selected') } } else if (sideurl === "name-registration") { - this.shadowRoot.getElementById('qnamereg').setAttribute('selected','selected') + this.shadowRoot.getElementById('qnamereg').setAttribute('selected', 'selected') if (this.shadowRoot.getElementById('qminter').hasAttribute('selected')) { this.shadowRoot.getElementById('qminter').removeAttribute('selected') } else if (this.shadowRoot.getElementById('qbminter').hasAttribute('selected')) { @@ -2450,7 +2452,7 @@ class AppView extends connect(store)(LitElement) { this.shadowRoot.getElementById('qnode').removeAttribute('selected') } } else if (sideurl === "names-market") { - this.shadowRoot.getElementById('qnamemarket').setAttribute('selected','selected') + this.shadowRoot.getElementById('qnamemarket').setAttribute('selected', 'selected') if (this.shadowRoot.getElementById('qminter').hasAttribute('selected')) { this.shadowRoot.getElementById('qminter').removeAttribute('selected') } else if (this.shadowRoot.getElementById('qbminter').hasAttribute('selected')) { @@ -2483,7 +2485,7 @@ class AppView extends connect(store)(LitElement) { this.shadowRoot.getElementById('qnode').removeAttribute('selected') } } else if (sideurl === "websites") { - this.shadowRoot.getElementById('qweb').setAttribute('selected','selected') + this.shadowRoot.getElementById('qweb').setAttribute('selected', 'selected') if (this.shadowRoot.getElementById('qminter').hasAttribute('selected')) { this.shadowRoot.getElementById('qminter').removeAttribute('selected') } else if (this.shadowRoot.getElementById('qbminter').hasAttribute('selected')) { @@ -2516,7 +2518,7 @@ class AppView extends connect(store)(LitElement) { this.shadowRoot.getElementById('qnode').removeAttribute('selected') } } else if (sideurl === "qapps") { - this.shadowRoot.getElementById('qapp').setAttribute('selected','selected') + this.shadowRoot.getElementById('qapp').setAttribute('selected', 'selected') if (this.shadowRoot.getElementById('qminter').hasAttribute('selected')) { this.shadowRoot.getElementById('qminter').removeAttribute('selected') } else if (this.shadowRoot.getElementById('qbminter').hasAttribute('selected')) { @@ -2549,7 +2551,7 @@ class AppView extends connect(store)(LitElement) { this.shadowRoot.getElementById('qnode').removeAttribute('selected') } } else if (sideurl === "group-management") { - this.shadowRoot.getElementById('qgroupmange').setAttribute('selected','selected') + this.shadowRoot.getElementById('qgroupmange').setAttribute('selected', 'selected') if (this.shadowRoot.getElementById('qminter').hasAttribute('selected')) { this.shadowRoot.getElementById('qminter').removeAttribute('selected') } else if (this.shadowRoot.getElementById('qbminter').hasAttribute('selected')) { @@ -2582,7 +2584,7 @@ class AppView extends connect(store)(LitElement) { this.shadowRoot.getElementById('qnode').removeAttribute('selected') } } else if (sideurl === "puzzles") { - this.shadowRoot.getElementById('qpuzzles').setAttribute('selected','selected') + this.shadowRoot.getElementById('qpuzzles').setAttribute('selected', 'selected') if (this.shadowRoot.getElementById('qminter').hasAttribute('selected')) { this.shadowRoot.getElementById('qminter').removeAttribute('selected') } else if (this.shadowRoot.getElementById('qbminter').hasAttribute('selected')) { @@ -2615,7 +2617,7 @@ class AppView extends connect(store)(LitElement) { this.shadowRoot.getElementById('qnode').removeAttribute('selected') } } else if (sideurl === "data-management") { - this.shadowRoot.getElementById('qdata').setAttribute('selected','selected') + this.shadowRoot.getElementById('qdata').setAttribute('selected', 'selected') if (this.shadowRoot.getElementById('qminter').hasAttribute('selected')) { this.shadowRoot.getElementById('qminter').removeAttribute('selected') } else if (this.shadowRoot.getElementById('qbminter').hasAttribute('selected')) { @@ -2648,7 +2650,7 @@ class AppView extends connect(store)(LitElement) { this.shadowRoot.getElementById('qnode').removeAttribute('selected') } } else if (sideurl === "node-management") { - this.shadowRoot.getElementById('qnode').setAttribute('selected','selected') + this.shadowRoot.getElementById('qnode').setAttribute('selected', 'selected') if (this.shadowRoot.getElementById('qminter').hasAttribute('selected')) { this.shadowRoot.getElementById('qminter').removeAttribute('selected') } else if (this.shadowRoot.getElementById('qbminter').hasAttribute('selected')) { diff --git a/core/src/components/notification-view/notification-bell.js b/core/src/components/notification-view/notification-bell.js new file mode 100644 index 00000000..9dac3e7f --- /dev/null +++ b/core/src/components/notification-view/notification-bell.js @@ -0,0 +1,265 @@ +import { LitElement, html, css } from 'lit'; +import { connect } from 'pwa-helpers'; + +import '@vaadin/button'; +import '@vaadin/item'; +import '@vaadin/list-box'; +import '@vaadin/icon'; +import '@vaadin/icons'; +import { store } from '../../store.js'; +import { setNewTab } from '../../redux/app/app-actions.js'; +import { routes } from '../../plugins/routes.js'; +import config from '../../notifications/config.js'; +import '../../../../plugins/plugins/core/components/TimeAgo.js' +class NotificationBell extends connect(store)(LitElement) { + + + + + static properties = { + notifications: { type: Array }, + showNotifications: { type: Boolean }, + }; + + constructor() { + super(); + this.notifications = []; + this.showNotifications = false; + this.initialFetch = false + } + + firstUpdated() { + this.getNotifications(); + document.addEventListener('click', (event) => { + const path = event.composedPath(); + if (!path.includes(this)) { + this.showNotifications = false; + } + }); + } + + getApiKey() { + const apiNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]; + let apiKey = apiNode.apiKey; + return apiKey; + } + + async getNotifications() { + const myNode = + store.getState().app.nodeConfig.knownNodes[ + store.getState().app.nodeConfig.node + ]; + const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + + let interval = null + let stop = false + + const getNewMail = async () => { + + const getMail = async (recipientName, recipientAddress) => { + const query = `qortal_qmail_${recipientName.slice( + 0, + 20 + )}_${recipientAddress.slice(-6)}_mail_` + const url = `${nodeUrl}/arbitrary/resources/search?service=MAIL_PRIVATE&query=${query}&limit=10&includemetadata=true&offset=0&reverse=true&excludeblocked=true` + const response = await fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }) + + const data = await response.json(); + return data; + }; + + if (!stop && !this.showNotifications) { + stop = true; + try { + const address = window.parent.reduxStore.getState().app?.selectedAddress?.address; + const name = window.parent.reduxStore.getState().app?.accountInfo?.names[0]?.name + + if (!name || !address) return + const mailArray = await getMail(name, address); + let notificationsToShow = [] + if (mailArray.length > 0) { + const lastVisited = localStorage.getItem("Q-Mail-last-visited") + + if (lastVisited) { + mailArray.forEach((mail) => { + if (mail.created > lastVisited) notificationsToShow.push(mail) + }) + } else { + notificationsToShow = mailArray + } + + } + if (!this.initialFetch && notificationsToShow.length > 0) { + const mail = notificationsToShow[0] + const urlPic = `${nodeUrl}/arbitrary/THUMBNAIL/${mail.name}/qortal_avatar?async=true&apiKey=${this.getApiKey()}` + routes.showNotification({ + data: { title: "New Q-Mail", type: "qapp", sound: config.messageAlert, url: "", options: { body: `You have an unread mail from ${mail.name}`, icon: urlPic, badge: urlPic } } + }) + } else if (notificationsToShow.length > 0) { + if (notificationsToShow[0].created > (this.notifications[0]?.created || 0)) { + const mail = notificationsToShow[0] + const urlPic = `${nodeUrl}/arbitrary/THUMBNAIL/${mail.name}/qortal_avatar?async=true&apiKey=${this.getApiKey()}` + routes.showNotification({ + data: { title: "New Q-Mail", type: "qapp", sound: config.messageAlert, url: "", options: { body: `You have an unread mail from ${mail.name}`, icon: urlPic, badge: urlPic } } + }) + } + } + this.notifications = notificationsToShow + if (!this.initialFetch) this.initialFetch = true + } catch (error) { + console.error(error) + } + stop = false + } + }; + try { + + setTimeout(() => { + getNewMail() + + }, 5000) + + + interval = setInterval(getNewMail, 30000); + } catch (error) { + console.error(error) + } + + } + render() { + + return html` +
+ + + ${this.notifications.length} + +
+
+ ${this.notifications.map(notification => html` +
{ + + const query = `?service=APP&name=Q-Mail` + store.dispatch(setNewTab({ + url: `qdn/browser/index.html${query}`, + id: 'q-mail-notification', + myPlugObj: { + "url": "qapps", + "domain": "core", + "page": `qdn/browser/index.html${query}`, + "title": "Q-Mail", + "icon": "vaadin:desktop", + "menus": [], + "parent": false + } + })) + this.showNotifications = false + this.notifications = [] + }}> +
+

Q-Mail

+ +
+
+

${notification.name}

+
+ +
+ `)} +
+
+
+ `; + } + + _toggleNotifications() { + if (this.notifications.length === 0) return + this.showNotifications = !this.showNotifications; + } + + static styles = css` + .layout { + width: 100px; + display: flex; + flex-direction: column; + align-items: center; + position: relative; + } + .count { + position: absolute; + top: 0; + right: 0; + background-color: red; + color: white; + border-radius: 50%; + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + } + .popover-panel { + position: absolute; + width: 200px; + padding: 10px; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + top: 40px; + max-height: 350px; + overflow: auto; + scrollbar-width: thin; + scrollbar-color: #6a6c75 #a1a1a1; + } + + .popover-panel::-webkit-scrollbar { + width: 11px; + } + + + + .popover-panel::-webkit-scrollbar-track { + background: #a1a1a1; + } + + .popover-panel::-webkit-scrollbar-thumb { + background-color: #6a6c75; + border-radius: 6px; + border: 3px solid #a1a1a1; + } + + .notifications-list { + display: flex; + flex-direction: column; + } + .notification-item { + padding: 5px; + border-bottom: 1px solid; + display: flex; + justify-content: space-between; + cursor: pointer; + transition: 0.2s all; + } + .notification-item:hover { + background: #c9d2d9; + } + p { + font-size: 14px; + color: #444444; + margin: 0px; + padding: 0px; + + } + `; +} + +customElements.define('notification-bell', NotificationBell); + + + diff --git a/core/src/components/show-plugin.js b/core/src/components/show-plugin.js index 72829ec5..e85131b8 100644 --- a/core/src/components/show-plugin.js +++ b/core/src/components/show-plugin.js @@ -5,6 +5,7 @@ import { Epml } from '../epml.js' import { addPluginRoutes } from '../plugins/addPluginRoutes.js' import { repeat } from 'lit/directives/repeat.js'; import ShortUniqueId from 'short-unique-id'; +import { setNewTab } from '../redux/app/app-actions.js' class ShowPlugin extends connect(store)(LitElement) { static get properties() { @@ -61,13 +62,12 @@ class ShowPlugin extends connect(store)(LitElement) { .tabs { display: flex; justify-content: flex-start; - height: 35px - max-height: 35px - gap: 1em; padding-top: 0.5em; padding-left: 0.5em; background: var(--sidetopbar); border-bottom: 1px solid var(--black); + height: 48px; + box-sizing: border-box; } .tab { @@ -84,7 +84,9 @@ class ShowPlugin extends connect(store)(LitElement) { position: relative; min-width: 120px; max-width: 200px; - + overflow: hidden; + text-wrap: nowrap; + text-overflow: ellipsis; text-overflow: ellipsis; } @@ -165,6 +167,7 @@ class ShowPlugin extends connect(store)(LitElement) { return myPlug === undefined ? 'about:blank' : `${window.location.origin}/plugin/${myPlug.domain}/${myPlug.page}${this.linkParam}` } + return html`
${this.tabs.map((tab, index) => html` @@ -172,7 +175,7 @@ class ShowPlugin extends connect(store)(LitElement) { class="tab ${this.currentTab === index ? 'active' : ''}" @click=${() => this.currentTab = index} > - ${tab.url}      + ${tab.myPlugObj && tab.myPlugObj.title}     
{ this.removeTab(index) }}>x
`)}    @@ -180,9 +183,9 @@ class ShowPlugin extends connect(store)(LitElement) { class="add-tab-button" title="Add Tab" @click=${() => this.addTab({ - url: "", - id: this.uid() - })} + url: "", + id: this.uid() + })} > + @@ -191,7 +194,7 @@ class ShowPlugin extends connect(store)(LitElement) { ${repeat(this.tabs, (tab) => tab.id, (tab, index) => html`