From 62e128a9fe6de0e03b4cd0eba707d72dd91d64d4 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 17 May 2019 16:19:41 +0200 Subject: [PATCH] Store and display new notifications --- customize.dist/src/less2/include/toolbar.less | 22 +++- www/common/common-ui-elements.js | 2 +- www/common/outer/mailbox.js | 99 ++++++++++++---- www/common/sframe-common-mailbox.js | 108 ++++++++++++------ www/common/sframe-common.js | 2 + www/common/toolbar3.js | 28 ++++- 6 files changed, 195 insertions(+), 66 deletions(-) diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index 15ef59d62..ec0e3aa60 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -9,6 +9,7 @@ @import (reference) "./icons.less"; @import (reference) "./modal.less"; @import (reference) "./help.less"; +@import (reference) "./notifications.less"; .toolbar_vars ( @color: @colortheme_default-color, // Color of the text for the toolbar @@ -66,6 +67,7 @@ } .help_main(@color, @bg-color); + .notifications_main(); .dropdown_main(); .history_main(); .iconColors_main(); @@ -567,6 +569,17 @@ } .cp-toolbar-user { height: @toolbar_line-height; + .cp-toolbar-notifications { + height: @toolbar_line-height; + width: @toolbar_line-height; + margin-left: 0; + button { + height: @toolbar_line-height; + width: @toolbar_line-height; + font-size: 20px; + margin-top: -1px; + } + } .cp-toolbar-new { height: @toolbar_line-height; width: @toolbar_line-height; @@ -834,7 +847,7 @@ line-height: 28px; // padding + border } } - .cp-toolbar-link, .cp-toolbar-new { + .cp-toolbar-link, .cp-toolbar-new, .cp-toolbar-notifications { font-size: 48px; line-height: 64px; width: @toolbar_top-height; @@ -849,14 +862,13 @@ } transition: all 0.15s; } - .cp-toolbar-new { + .cp-toolbar-notifications, .cp-toolbar-new { background-color: rgba(0,0,0,0.2); &:hover { background-color: rgba(0,0,0,0.3); } text-align: center; font-size: 32px; - margin-left: 10px; &> button { display: flex; align-items: center; @@ -884,6 +896,9 @@ } } } + .cp-toolbar-notifications { + margin-left: 10px; + } .cp-toolbar-link { display: inline-flex; align-items: center; @@ -917,6 +932,7 @@ order: 6; line-height: @toolbar_top-height; color: white; + .cp-toolbar-notifications { order: 1; } .cp-toolbar-new { order: 2; } .cp-toolbar-user-dropdown { order: 3; } .cp-toolbar-backup { order: 4; } // TODO drive migration to secure iframe diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 9418c2e79..4949b76c0 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1425,7 +1425,7 @@ define([ return /HTML/.test(Object.prototype.toString.call(o)) && typeof(o.tagName) === 'string'; }; - var allowedTags = ['a', 'p', 'hr']; + var allowedTags = ['a', 'p', 'hr', 'div']; var isValidOption = function (o) { if (typeof o !== "object") { return false; } if (isElement(o)) { return true; } diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index ba509d8e0..a2f40f74f 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -1,26 +1,33 @@ -// jshint ignore: start define([ '/common/common-util.js', - '/common/common-constants.js', + '/common/common-hash.js', '/common/common-realtime.js', - '/customize/messages.js', '/bower_components/chainpad-netflux/chainpad-netflux.js', '/bower_components/chainpad-crypto/crypto.js', -], function (Util, Constants, Realtime, Messages, CpNetflux, Crypto) { +], function (Util, Hash, Realtime, CpNetflux, Crypto) { var Mailbox = {}; var TYPES = [ - 'notifications' + 'notifications', + 'test' ]; var BLOCKING_TYPES = [ ]; + var initializeMailboxes = function (mailboxes) { + if (!mailboxes['notifications']) { + mailboxes.notifications = { + channel: Hash.createChannelId(), + lastKnownHash: '', + viewed: [] + }; + } + }; + /* proxy.mailboxes = { friends: { - keys: '', channel: '', - hash: '', lastKnownHash: '', viewed: [] } @@ -41,8 +48,9 @@ proxy.mailboxes = { var openChannel = function (ctx, type, m, onReady) { var box = ctx.boxes[type] = { - queue: [], - history: [], + queue: [], // Store the messages to send when the channel is ready + history: [], // All the hashes loaded from the server in corretc order + content: {}, // Content of the messages that should be displayed sendMessage: function (msg) { // To send a message to our box try { msg = JSON.stringify(msg); @@ -52,6 +60,7 @@ proxy.mailboxes = { box.queue.push(msg); } }; + Crypto = Crypto; /* // XXX if (!Crypto.Mailbox) { @@ -90,38 +99,62 @@ proxy.mailboxes = { }); box.queue = []; }; - cfg.onMessage = function (msg, user, vKey, isCpi, hash) { - // TODO + cfg.onMessage = function (msg, user, vKey, isCp, hash) { try { msg = JSON.parse(msg); } catch (e) { console.error(e); } + box.history.push(hash); if (isMessageNew(hash, m)) { // Message should be displayed var message = { msg: msg, hash: hash }; - box.history.push(message); + box.content[hash] = msg; showMessage(ctx, type, message); } else { - // Message has already been viewer by the user - if (history.length === 0) { + // Message has already been viewed by the user + if (Object.keys(box.content).length === 0) { + // If nothing is displayed yet, we can bump our lastKnownHash and remove this hash + // from our "viewed" array m.lastKnownHash = hash; + box.history = []; + var idxViewed = m.viewed.indexOf(hash); + if (idxViewed !== -1) { m.viewed.splice(idxViewed, 1); } } - console.log(hash + ' is not new'); } }; cfg.onReady = function () { + // Clean the "viewed" array: make sure all the "viewed" hashes are + // in history + var toClean = []; + m.viewed.forEach(function (h, i) { + if (box.history.indexOf(h) === -1) { + toClean.push(i); + } + }); + for (var i = toClean.length-1; i>=0; i--) { + m.viewed.splice(toClean[i], 1); + } + // Continue onReady(); }; CpNetflux.start(cfg); }; // Send a message to someone else - var sendTo = function () { + /*var sendTo = function () { + };*/ + + var updateLastKnownHash = function (ctx, type) { + var m = Util.find(ctx, ['store', 'proxy', 'mailboxes', type]); + if (!m) { return; } + var box = ctx.boxes[type]; + if (!box) { return; } + }; // Mark a message as read @@ -138,33 +171,47 @@ proxy.mailboxes = { // - otherwise, just push to view var idx; if (box.history.some(function (el, i) { - if (hash === el.hash) { + if (hash === el) { idx = i; return true; } })) { if (idx === 0) { m.lastKnownHash = hash; + box.history.shift(); + delete box.content[hash]; } else if (m.viewed.indexOf(hash) === -1) { m.viewed.push(hash); } } + // Clear data in memory if needed // Check the "viewed" array to see if we're able to bump lastKnownhash more var sliceIdx; - box.history.some(function (el, i) { - var isViewed = m.viewed.indexOf(el.hash); + var lastKnownHash; + box.history.some(function (hash, i) { + var isViewed = m.viewed.indexOf(hash); if (isViewed !== -1) { sliceIdx = i + 1; m.viewed.splice(isViewed, 1); + lastKnownHash = hash; return false; } return true; }); + if (sliceIdx) { box.history = box.history.slice(sliceIdx); + m.lastKnownHash = lastKnownHash; } + // Make sure we remove data about dismissed messages + Object.keys(box.content).forEach(function (h) { + if (box.history.indexOf(h) === -1 || m.viewed.indexOf(h) !== -1) { + delete box.content[h]; + } + }); + Realtime.whenRealtimeSyncs(ctx.store.realtime, function () { cb(); }); @@ -173,8 +220,12 @@ proxy.mailboxes = { var subscribe = function (ctx, data, cId, cb) { // Get existing notifications Object.keys(ctx.boxes).forEach(function (type) { - ctx.boxes[type].history.forEach(function (obj) { - showMessage(ctx, type, obj, cId); + Object.keys(ctx.boxes[type].content).forEach(function (h) { + var message = { + msg: ctx.boxes[type].content[h], + hash: h + }; + showMessage(ctx, type, message, cId); }); }); // Subscribe to new notifications @@ -199,13 +250,17 @@ proxy.mailboxes = { boxes: {} }; - var mailboxes = store.proxy.mailboxes || {}; + var mailboxes = store.proxy.mailboxes = store.proxy.mailboxes || {}; + + initializeMailboxes(mailboxes); + Object.keys(mailboxes).forEach(function (key) { if (TYPES.indexOf(key) === -1) { return; } var m = mailboxes[key]; if (BLOCKING_TYPES.indexOf(key) === -1) { openChannel(ctx, key, m, function () { + updateLastKnownHash(ctx, key); console.log(key + ' mailbox is ready'); }); } else { diff --git a/www/common/sframe-common-mailbox.js b/www/common/sframe-common-mailbox.js index dd1f93172..c9f0625cf 100644 --- a/www/common/sframe-common-mailbox.js +++ b/www/common/sframe-common-mailbox.js @@ -3,13 +3,14 @@ define([ '/common/common-util.js', '/common/common-interface.js', '/common/common-ui-elements.js', + '/common/hyperscript.js', '/customize/messages.js' -], function ($, Util, UI, UIElements, Messages) { +], function ($, Util, UI, UIElements, h, Messages) { var Mailbox = {}; + Messages = Messages; // XXX Mailbox.create = function (Common) { - var mailbox = {}; - var metadataMgr = Common.getMetadataMgr(); + var mailbox = Common.mailbox; var sframeChan = Common.getSframeChannel(); var execCommand = function (cmd, data, cb) { @@ -30,53 +31,57 @@ define([ }); }; - mailbox.dismiss = function (type, hash, cb) { - execCommand('DISMISS', { - hash: hash, - type: type - }, function (obj) { - if (obj && obj.error) { return void cb(obj.error); } - removeFromHistory(type, hash); - cb(); - }); - }; - mailbox.sendTo = function (user, type, content) { - + console.log(user, type, content); }; // UI + var formatData = function (data) { + return JSON.stringify(data.content.msg.content); + }; + var createElement = function (data) { + var notif; + var dismiss = h('span.fa.fa-times'); + dismiss.addEventListener('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + mailbox.dismiss(data, function (err) { + if (err) { return void console.error(err); } + if (notif && notif.parentNode) { + try { + notif.parentNode.removeChild(notif); + } catch (e) { console.error(e); } + } + }); + }); + notif = h('div.cp-notification', { + 'data-hash': data.content.hash + }, [ + h('div.cp-notification-content', h('p', formatData(data))), + h('div.cp-notification-dismiss', dismiss) + ]); + return notif; + }; + + var onViewedHandlers = []; var onMessageHandlers = []; // Call the onMessage handlers - var pushMessage = function (data) { - onMessageHandlers.forEach(function (f) { + var pushMessage = function (data, handler) { + var todo = function (f) { try { - f(data); + var el = createElement(data); + f(data, el); } catch (e) { console.error(e); } - }); - }; - - // Get all existing notifications + the new ones when they come - mailbox.subscribe = function (cfg) { - if (typeof(cfg.onViewed) === "function") { - onViewedHandlers.push(cfg.onViewed); + }; + if (typeof (handler) === "function") { + return void todo(handler); } - if (typeof(cfg.onMessage) === "function") { - onMessageHandlers.push(cfg.onMessage); - } - Object.keys(history).forEach(function (type) { - history[type].forEach(function (data) { - pushMessage({ - type: type, - content: data - }); - }); - }); + onMessageHandlers.forEach(todo); }; var onViewed = function (data) { @@ -99,6 +104,37 @@ define([ history[data.type].push(data.content); }; + mailbox.dismiss = function (data, cb) { + var dataObj = { + hash: data.content.hash, + type: data.type + }; + execCommand('DISMISS', dataObj, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + onViewed(dataObj); + cb(); + }); + }; + + + // Get all existing notifications + the new ones when they come + mailbox.subscribe = function (cfg) { + if (typeof(cfg.onViewed) === "function") { + onViewedHandlers.push(cfg.onViewed); + } + if (typeof(cfg.onMessage) === "function") { + onMessageHandlers.push(cfg.onMessage); + } + Object.keys(history).forEach(function (type) { + history[type].forEach(function (data) { + pushMessage({ + type: type, + content: data + }, cfg.onMessage); + }); + }); + }; + // CHANNEL WITH WORKER diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 3959be66e..76dcaa029 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -470,6 +470,8 @@ define([ }); }; + funcs.mailbox = {}; + Object.freeze(funcs); return { create: function (cb) { diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 4c53df130..8eb3b6c2d 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -7,9 +7,10 @@ define([ '/common/common-hash.js', '/common/common-util.js', '/common/common-feedback.js', + '/common/hyperscript.js', '/common/messenger-ui.js', '/customize/messages.js', -], function ($, Config, ApiConfig, UIElements, UI, Hash, Util, Feedback, +], function ($, Config, ApiConfig, UIElements, UI, Hash, Util, Feedback, h, MessengerUI, Messages) { var Common; @@ -929,10 +930,29 @@ MessengerUI, Messages) { return $userAdmin; }; - var createNotifications = function (toolbar, config) { + var createNotifications = function (toolbar) { console.log(Common.mailbox); - var $userAdmin = toolbar.$userAdmin.find('.'+NOTIFICATIONS_CLS).show(); - return $userAdmin; + var $notif = toolbar.$top.find('.'+NOTIFICATIONS_CLS).show(); + var div = h('div.cp-notifications-container'); + var pads_options = [div]; + var dropdownConfig = { + text: '', // Button initial text + options: pads_options, // Entries displayed in the menu + container: $notif, + left: true, + common: Common + }; + var $newPadBlock = UIElements.createDropdown(dropdownConfig); + $newPadBlock.find('button').attr('title', Messages.mailbox_title); // XXX + $newPadBlock.find('button').addClass('fa fa-bell-o'); + + Common.mailbox.subscribe({ + onMessage: function (data, el) { + if (el) { div.appendChild(el); } + } + }); + + return $newPadBlock; }; // Events