From c833265faed51a916214becbfadc25731d07a048 Mon Sep 17 00:00:00 2001 From: Brock Whittaker Date: Fri, 26 Aug 2016 16:47:30 -0700 Subject: [PATCH] Add notification for muting with unmute option. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a support a notification at the top of the screen that alerts a user they’ve muted a stream and gives them the option to unmute if it was an accident. The notification disappears automatically after 4s, but if a user moves their mouse over the notification, the timer resets to 2s after the user moves their mouse off the notification, to make it easy for users to read the full message and decide what to do. --- static/js/muting_ui.js | 70 +++++++++++++++++++++ static/js/popovers.js | 29 ++++++--- static/styles/zulip.css | 93 ++++++++++++++++++++++++++++ templates/zerver/index.html | 2 + templates/zerver/topic_is_muted.html | 9 +++ 5 files changed, 194 insertions(+), 9 deletions(-) create mode 100644 templates/zerver/topic_is_muted.html diff --git a/static/js/muting_ui.js b/static/js/muting_ui.js index 9975eeb039..276e904849 100644 --- a/static/js/muting_ui.js +++ b/static/js/muting_ui.js @@ -16,6 +16,76 @@ exports.rerender = function () { } }; +exports.notify_with_undo_option = (function () { + var event_added = false; + var meta = { + stream: null, + topic: null, + hide_me_time: null, + alert_hover_state: false, + $mute: null + }; + var animate = { + fadeOut: function ($mute) { + if (meta.$mute) { + meta.$mute.fadeOut(500).removeClass("show"); + } + }, + fadeIn: function ($mute) { + if (meta.$mute) { + meta.$mute.fadeIn(500).addClass("show"); + } + } + }; + var interval = setInterval(function () { + if (meta.hide_me_time < new Date().getTime() && !meta.alert_hover_state) { + animate.fadeOut(); + } + }, 100); + + return function (stream, topic) { + var $exit = $("#unmute_muted_topic_notification .exit-me"); + + if (!meta.$mute) { + meta.$mute = $("#unmute_muted_topic_notification"); + + $exit.click(function () { + animate.fadeOut(); + }); + + meta.$mute.find("#unmute").click(function () { + // it should reference the meta variable and not get stuck with + // a pass-by-value of stream, topic. + popovers.topic_ops.unmute(meta.stream, meta.topic); + animate.fadeOut(); + }); + } + + meta.stream = stream; + meta.topic = topic; + // add a four second delay before closing up. + meta.hide_me_time = new Date().getTime() + 4000; + + meta.$mute.find(".topic").html(topic); + meta.$mute.find(".stream").html(stream); + + animate.fadeIn(); + + // if the user mouses over the notification, don't hide it. + meta.$mute.mouseenter(function () { + meta.alert_hover_state = true; + }); + + // once the user's mouse leaves the notification, restart the countdown. + meta.$mute.mouseleave(function () { + meta.alert_hover_state = false; + // add at least 2000ms but if more than that exists just keep the + // current amount. + meta.hide_me_time = Math.max(meta.hide_me_time, new Date().getTime() + 2000); + }); + }; +}()); + exports.persist_and_rerender = function () { // Optimistically rerender our new muting preferences. The back // end should eventually save it, and if it doesn't, it's a recoverable diff --git a/static/js/popovers.js b/static/js/popovers.js index 9fe96c8e47..64e5529775 100644 --- a/static/js/popovers.js +++ b/static/js/popovers.js @@ -136,6 +136,23 @@ exports.hide_actions_popover = function () { } }; +exports.topic_ops = { + mute: function (stream, topic) { + popovers.hide_topic_sidebar_popover(); + muting.mute_topic(stream, topic); + muting_ui.persist_and_rerender(); + muting_ui.notify_with_undo_option(stream, topic); + }, + // we don't run a unmute_notif function because it isn't an issue as much + // if someone accidentally unmutes a stream rather than if they mute it + // and miss out on info. + unmute: function (stream, topic) { + popovers.hide_topic_sidebar_popover(); + muting.unmute_topic(stream, topic); + muting_ui.persist_and_rerender(); + } +}; + function message_info_popped() { return current_message_info_popover_elem !== undefined; } @@ -364,9 +381,7 @@ exports.register_click_handlers = function () { $('body').on('click', '.sidebar-popover-mute-topic', function (e) { var stream = $(e.currentTarget).attr('data-stream-name'); var topic = $(e.currentTarget).attr('data-topic-name'); - popovers.hide_topic_sidebar_popover(); - muting.mute_topic(stream, topic); - muting_ui.persist_and_rerender(); + exports.topic_ops.mute(stream, topic); e.stopPropagation(); e.preventDefault(); }); @@ -374,9 +389,7 @@ exports.register_click_handlers = function () { $('body').on('click', '.sidebar-popover-unmute-topic', function (e) { var stream = $(e.currentTarget).attr('data-stream-name'); var topic = $(e.currentTarget).attr('data-topic-name'); - popovers.hide_topic_sidebar_popover(); - muting.unmute_topic(stream, topic); - muting_ui.persist_and_rerender(); + exports.topic_ops.unmute(stream, topic); e.stopPropagation(); e.preventDefault(); }); @@ -514,9 +527,7 @@ exports.register_click_handlers = function () { $('body').on('click', '.popover_mute_topic', function (e) { var stream = $(e.currentTarget).data('msg-stream'); var topic = $(e.currentTarget).data('msg-topic'); - popovers.hide_actions_popover(); - muting.mute_topic(stream, topic); - muting_ui.persist_and_rerender(); + exports.topic_ops.mute(stream, topic); e.stopPropagation(); e.preventDefault(); }); diff --git a/static/styles/zulip.css b/static/styles/zulip.css index 99d43275ef..3605702fee 100644 --- a/static/styles/zulip.css +++ b/static/styles/zulip.css @@ -42,6 +42,99 @@ a { cursor: pointer; } +p.n-margin { + margin: 10px 0px 0px 0px; +} + +.small-line-height { + line-height: 1.1; +} + +.float-left { + float: left; +} + +.float-right { + float: right; +} + +.float-clear { + clear: both; +} + +.light { + font-weight: 300; +} + +.no-margin { + margin: 0; +} + +.border-radius { + border-radius: 4px; +} + +#unmute_muted_topic_notification { + display: none; + position: absolute; + width: 400px; + top: 0px; + left: calc(50vw - 220px); + padding: 15px; + + background-color: #FAFAFA; + border-radius: 5px; + box-shadow: 0px 0px 30px rgba(0,0,0,0.25); + z-index: 101; + + animation-name: pulse; + animation-iteration-count: infinite; + animation-duration: 2s; + + transition-property: top, bottom; + transition-duration: 0.5s; +} + +#unmute_muted_topic_notification.show { + top: 50px; +} + +#unmute_muted_topic_notification h3 { + font-size: 16pt; + margin-top: 2px; +} + +@keyframes pulse { + 0% { + box-shadow: 0px 0px 30px rgba(0,0,0,0.35); + } + 50% { + box-shadow: 0px 0px 30px rgba(0,0,0,0.15); + } + 100% { + box-shadow: 0px 0px 30px rgba(0,0,0,0.35); + } +} + +#unmute_muted_topic_notification .btn { + background-color: transparent; + border: 1px solid #444; + outline: none; + transition: all 0.2s ease; +} + +#unmute_muted_topic_notification .btn:hover { + background-color: #444; + color: #FAFAFA; +} + +#unmute_muted_topic_notification .exit-me { + font-size: 30pt; + font-weight: 200; + margin: 5px 0px 0px 10px; + cursor: pointer; +} + .ztable_comp_col1 { width: 10px; } diff --git a/templates/zerver/index.html b/templates/zerver/index.html index c8ffb0afbc..2ae4e6b936 100644 --- a/templates/zerver/index.html +++ b/templates/zerver/index.html @@ -44,6 +44,8 @@ var page_params = {{ page_params }}; {{ minified_js('app_debug')|safe }} {% endif %} +{% include "zerver/topic_is_muted.html" %} + {% endblock %} {% block content %}
diff --git a/templates/zerver/topic_is_muted.html b/templates/zerver/topic_is_muted.html new file mode 100644 index 0000000000..4ae18e63c8 --- /dev/null +++ b/templates/zerver/topic_is_muted.html @@ -0,0 +1,9 @@ +
+
+

Topic Muted

+
×
+ +
+
+

You have muted the topic under the stream.

+