diff --git a/templates/zephyr/index.html b/templates/zephyr/index.html index 4257ea3025..60578c612b 100644 --- a/templates/zephyr/index.html +++ b/templates/zephyr/index.html @@ -70,6 +70,7 @@ var page_params = {{ page_params }}; {% include "zephyr/invite_user.html" %} {% include "zephyr/bankruptcy.html" %} {% include "zephyr/logout.html" %} + {% include "zephyr/tutorial_finale.html" %}
{% endblock %} diff --git a/templates/zephyr/tutorial_finale.html b/templates/zephyr/tutorial_finale.html new file mode 100644 index 0000000000..65e14581d5 --- /dev/null +++ b/templates/zephyr/tutorial_finale.html @@ -0,0 +1,24 @@ + diff --git a/zephyr/static/js/setup.js b/zephyr/static/js/setup.js index bff5e1a70f..f770497468 100644 --- a/zephyr/static/js/setup.js +++ b/zephyr/static/js/setup.js @@ -6,7 +6,7 @@ $(function () { // get_updates completes. if (page_params.have_initial_messages) { util.make_loading_indicator($('#page_loading_indicator'), 'Loading...'); - } else { + } else if (!page_params.needs_tutorial) { util.show_first_run_message(); } diff --git a/zephyr/static/js/tutorial.js b/zephyr/static/js/tutorial.js index 7f6e5bcdb4..2082c3b9ac 100644 --- a/zephyr/static/js/tutorial.js +++ b/zephyr/static/js/tutorial.js @@ -127,6 +127,197 @@ exports.is_running = function() { return is_running; }; +function box(x, y, width, height) { + // Blanket everything ouside the box defined by the parameters in a + // translucent black screen, and cover the box itself with a clear screen so + // nothing in it is clickable. + // + // x and y are the coordinates for hte upper-left corner of the box. + + var doc_width = $(document).width(); + var doc_height = $(document).height(); + + $("#top-screen").css({opacity: 0.7, width: doc_width, height: y}); + $("#bottom-screen").offset({top: y + height, left: 0}); + $("#bottom-screen").css({opacity: 0.7, width: doc_width, height: doc_height}); + $("#left-screen").offset({top: y, left: 0}); + $("#left-screen").css({opacity: 0.7, width: x, height: height}); + $("#right-screen").offset({top: y, left: x + width}); + $("#right-screen").css({opacity: 0.7, width: x, height: height}); + $("#clear-screen").css({opacity: 0.0, width: doc_width, height: doc_height}); +} + +function messages_in_viewport() { + var vp = viewport.message_viewport_info(); + var top = vp.visible_top; + var height = vp.visible_height; + var last_row = rows.last_visible(); + + return $.merge(last_row, last_row.prevAll()).filter(function (idx, row) { + var row_offset = $(row).offset(); + return (row_offset.top > top && row_offset.top < top + height); + }); +} + +function create_and_show_popover(target_div, placement, title, content_template) { + target_div.popover({ + placement: placement, + title: templates.render("tutorial_title", {title: title}), + content: templates.render(content_template), + trigger: "manual" + }); + target_div.popover("show"); + + $(".popover").css("z-index", 20001); +} + +function finale() { + var finale_modal = $("#tutorial-finale"); + $(".screen").css({opacity: 0.0}); + finale_modal.css("z-index", 20001); + finale_modal.modal("show"); + + $("#tutorial-get-started").click(function () { + finale_modal.modal("hide"); + $(".screen").css({opacity: 0.0, width: 0, height: 0}); + }); + + // Restore your actual stream colors and rerender to display any + // messages received during the tutorial. + set_tutorial_status("finished"); + is_running = false; + current_msg_list.clear(); + // Force a check on new events before we re-render the message list. + force_get_updates(); + subs.stream_info(real_stream_info); + util.show_first_run_message(); + current_msg_list.rerender(); +} + +function reply() { + var spotlight_message = rows.first_visible().prev(".recipient_row"); + create_and_show_popover(spotlight_message, "left", "Replying", "tutorial_reply"); + + var my_popover = $("#tutorial-reply").closest(".popover"); + my_popover.offset({left: my_popover.offset().left - 10}); + + $("#tutorial-reply-next").click(function () { + spotlight_message.popover("destroy"); + finale(); + }); +} + +function home() { + var spotlight_message = rows.first_visible().prev(".recipient_row"); + var x = spotlight_message.offset().left; + var y = spotlight_message.offset().top; + var height = 0; + $.each(messages_in_viewport(), function (idx, row) { + height += $(row).height(); + }); + + box(x, y, spotlight_message.width(), height); + create_and_show_popover(spotlight_message, "left", "Home view", "tutorial_home"); + + var my_popover = $("#tutorial-home").closest(".popover"); + my_popover.offset({left: my_popover.offset().left - 10}); + + $("#tutorial-home-next").click(function () { + spotlight_message.popover("destroy"); + reply(); + }); +} + +function private_message() { + var bar = rows.first_visible().nextUntil(".private_message").first().next(); + var spotlight_message = bar.next(); + var x = bar.offset().left; + var y = bar.offset().top; + // In the current example we have back-to-back pms. + var message_width = bar.width(); + var message_height = bar.height() + spotlight_message.height() + + spotlight_message.next().height(); + + box(x, y, message_width, message_height); + create_and_show_popover(bar, "top", "Private messages", "tutorial_private"); + + var my_popover = $("#tutorial-private").closest(".popover"); + my_popover.offset({top: my_popover.offset().top - 10, + left: bar.offset().left + 76 - my_popover.width() / 2}); + + $("#tutorial-private-next").click(function () { + bar.popover("destroy"); + home(); + }); +} + +function subject() { + var spotlight_message = rows.first_visible(); + var bar = spotlight_message.prev(".recipient_row"); + create_and_show_popover(bar, "bottom", "Subjects", "tutorial_subject"); + + var my_popover = $("#tutorial-subject").closest(".popover"); + my_popover.offset({left: bar.offset().left + 94 - my_popover.width() / 2}); + + $("#tutorial-subject-next").click(function () { + bar.popover("destroy"); + private_message(); + }); +} + +function stream() { + var bar = rows.first_visible().prev(".recipient_row"); + create_and_show_popover(bar, "bottom", "Streams", "tutorial_stream"); + + var my_popover = $("#tutorial-stream").closest(".popover"); + my_popover.offset({left: bar.offset().left + 24 - my_popover.width() / 2}); + + $("#tutorial-stream-next").click(function () { + bar.popover("destroy"); + subject(); + }); +} + +function message() { + var spotlight_message = rows.first_visible(); + var bar = spotlight_message.prev(".recipient_row"); + var x = bar.offset().left; + var y = bar.offset().top; + var message_height = bar.height() + spotlight_message.height(); + var message_width = bar.width(); + + box(x, y, message_width, message_height); + create_and_show_popover(bar, "left", "Welcome " + page_params.fullname + "!", + "tutorial_message"); + + var my_popover = $("#tutorial-message").closest(".popover"); + my_popover.offset({left: my_popover.offset().left - 10}); + + $("#tutorial-message-next").click(function () { + bar.popover("destroy"); + stream(); + }); +} + +function welcome() { + // Grey out everything. + $('#top-screen').css({opacity: 0.7, width: $(document).width(), + height: $(document).height()}); + + // Highlight the first recipient row. + var bar = rows.first_visible().prev(".recipient_row"); + create_and_show_popover(bar, "left", "Welcome " + page_params.fullname + "!", + "tutorial_welcome"); + + var my_popover = $("#tutorial-welcome").closest(".popover"); + my_popover.offset({left: my_popover.offset().left - 10}); + + $("#tutorial-welcome-next").click(function () { + bar.popover("destroy"); + message(); + }); +} + exports.start = function () { // If you somehow have messages, temporarily remove them from the visible // feed. @@ -138,6 +329,7 @@ exports.start = function () { current_msg_list.add_and_rerender(fake_messages); is_running = true; set_tutorial_status("started"); + welcome(); }; exports.initialize = function () { diff --git a/zephyr/static/js/zephyr.js b/zephyr/static/js/zephyr.js index 5d696dbc65..1210bbac76 100644 --- a/zephyr/static/js/zephyr.js +++ b/zephyr/static/js/zephyr.js @@ -40,6 +40,8 @@ var furthest_read = -1; var server_furthest_read = -1; var pointer_update_in_flight = false; +var events_stored_during_tutorial = []; + function add_person(person) { page_params.people_list.push(person); people_dict[person.email] = person; @@ -812,6 +814,16 @@ function get_updates_success(data) { var messages_to_update = []; var new_pointer; + if (tutorial.is_running()) { + events_stored_during_tutorial = events_stored_during_tutorial.concat(data.events); + return; + } + + if (events_stored_during_tutorial.length > 0) { + data.events = events_stored_during_tutorial.concat(data.events); + events_stored_during_tutorial = []; + } + $.each(data.events, function (idx, event) { get_updates_params.last_event_id = Math.max(get_updates_params.last_event_id, event.id); @@ -955,7 +967,12 @@ function get_updates(options) { $('#connection-error').hide(); get_updates_success(data); - get_updates_timeout = setTimeout(get_updates, 0); + + if (tutorial.is_running()) { + get_updates_timeout = setTimeout(get_updates, 5000); + } else { + get_updates_timeout = setTimeout(get_updates, 0); + } }, error: function (xhr, error_type, exn) { // If we are old enough to have messages outside of the diff --git a/zephyr/static/styles/zephyr.css b/zephyr/static/styles/zephyr.css index e0b06ee3ec..20c82be9d0 100644 --- a/zephyr/static/styles/zephyr.css +++ b/zephyr/static/styles/zephyr.css @@ -1973,3 +1973,18 @@ li.expanded_subject { background: #000; z-index: 20000; } + +.tutorial-popover { + width: 200px; +} + +#tutorial-stream { + width: 250px; +} + +#tutorial-subject { + width: 250px; +} +.tutorial-done-button { + text-align: right; +} diff --git a/zephyr/static/templates/tutorial_home.handlebars b/zephyr/static/templates/tutorial_home.handlebars new file mode 100644 index 0000000000..5b70f0362e --- /dev/null +++ b/zephyr/static/templates/tutorial_home.handlebars @@ -0,0 +1,13 @@ +{{! Contents of the "tutorial home" onboarding popup }} + ++ We show stream and private messages together, and time flows down. You + can "narrow" to a particular stream or subject by clicking on it. +
+ + ++ Messages in Humbug go to a stream and have a subject. +
+ + ++ You can also send private messages to one or more people. +
+ + ++ You can reply to any message by clicking on it. The stream and subject + will automatically be filled in. +
+ + ++ Streams are like chat rooms or mailing lists. You can easily join, leave, and make them. +
+ + ++ A subject is one word that describes what this message is about. Just + like an e-mail subject line, only much shorter. Don't overthink it. +
+ + ++ Humbug isn't like other chat tools. Here are some things you need to know. +
+ + +