import $ from "jquery"; import _ from "lodash"; import render_hotspot_icon from "../templates/hotspot_icon.hbs"; import render_hotspot_overlay from "../templates/hotspot_overlay.hbs"; import * as blueslip from "./blueslip"; import * as channel from "./channel"; import {page_params} from "./page_params"; import * as popovers from "./popovers"; // popover orientations const TOP = "top"; const LEFT = "left"; const RIGHT = "right"; const BOTTOM = "bottom"; const LEFT_BOTTOM = "left_bottom"; const VIEWPORT_CENTER = "viewport_center"; // popover orientation can optionally be fixed here (property: popover), // otherwise popovers.compute_placement is used to compute orientation const HOTSPOT_LOCATIONS = new Map([ [ "intro_streams", { element: "#streams_header .sidebar-title", offset_x: 1.35, offset_y: 0.39, }, ], [ "intro_topics", { element: ".topic-name", offset_x: 0.8, offset_y: 0.39, }, ], [ "intro_gear", { element: "#settings-dropdown", offset_x: -0.4, offset_y: 1.2, popover: LEFT_BOTTOM, }, ], [ "intro_compose", { element: "#left_bar_compose_stream_button_big", offset_x: 0, offset_y: 0, }, ], ]); // popover illustration url(s) const WHALE = "/static/images/hotspots/whale.svg"; export function post_hotspot_as_read(hotspot_name) { channel.post({ url: "/json/users/me/hotspots", data: {hotspot: hotspot_name}, error(err) { blueslip.error(err.responseText); }, }); } function place_icon(hotspot) { const element = $(hotspot.location.element); const icon = $(`#hotspot_${CSS.escape(hotspot.name)}_icon`); if ( element.length === 0 || element.css("display") === "none" || !element.is(":visible") || element.is(":hidden") ) { icon.css("display", "none"); return false; } const offset = { top: element.outerHeight() * hotspot.location.offset_y, left: element.outerWidth() * hotspot.location.offset_x, }; const client_rect = element.get(0).getBoundingClientRect(); const placement = { top: client_rect.top + offset.top, left: client_rect.left + offset.left, }; icon.css("display", "block"); icon.css(placement); return true; } function place_popover(hotspot) { if (!hotspot.location.element) { return; } const popover_width = $( `#hotspot_${CSS.escape(hotspot.name)}_overlay .hotspot-popover`, ).outerWidth(); const popover_height = $( `#hotspot_${CSS.escape(hotspot.name)}_overlay .hotspot-popover`, ).outerHeight(); const el_width = $(hotspot.location.element).outerWidth(); const el_height = $(hotspot.location.element).outerHeight(); const arrow_offset = 20; let popover_offset; let arrow_placement; const orientation = hotspot.location.popover || popovers.compute_placement( $(hotspot.location.element), popover_height, popover_width, false, ); switch (orientation) { case TOP: popover_offset = { top: -(popover_height + arrow_offset), left: el_width / 2 - popover_width / 2, }; arrow_placement = "bottom"; break; case LEFT: popover_offset = { top: el_height / 2 - popover_height / 2, left: -(popover_width + arrow_offset), }; arrow_placement = "right"; break; case BOTTOM: popover_offset = { top: el_height + arrow_offset, left: el_width / 2 - popover_width / 2, }; arrow_placement = "top"; break; case RIGHT: popover_offset = { top: el_height / 2 - popover_height / 2, left: el_width + arrow_offset, }; arrow_placement = "left"; break; case LEFT_BOTTOM: popover_offset = { top: 0, left: -(popover_width + arrow_offset / 2), }; arrow_placement = ""; break; case VIEWPORT_CENTER: popover_offset = { top: el_height / 2, left: el_width / 2, }; arrow_placement = ""; break; default: blueslip.error("Invalid popover placement value for hotspot '" + hotspot.name + "'"); break; } // position arrow arrow_placement = "arrow-" + arrow_placement; $(`#hotspot_${CSS.escape(hotspot.name)}_overlay .hotspot-popover`) .removeClass("arrow-top arrow-left arrow-bottom arrow-right") .addClass(arrow_placement); // position popover let popover_placement; if (orientation === VIEWPORT_CENTER) { popover_placement = { top: "45%", left: "50%", transform: "translate(-50%, -50%)", }; } else { const client_rect = $(hotspot.location.element).get(0).getBoundingClientRect(); popover_placement = { top: client_rect.top + popover_offset.top, left: client_rect.left + popover_offset.left, transform: "", }; } $(`#hotspot_${CSS.escape(hotspot.name)}_overlay .hotspot-popover`).css(popover_placement); } function insert_hotspot_into_DOM(hotspot) { const hotspot_overlay_HTML = render_hotspot_overlay({ name: hotspot.name, title: hotspot.title, description: hotspot.description, img: WHALE, }); const hotspot_icon_HTML = render_hotspot_icon({ name: hotspot.name, }); setTimeout(() => { $("body").prepend(hotspot_icon_HTML); $("body").prepend(hotspot_overlay_HTML); if (place_icon(hotspot)) { place_popover(hotspot); } // reposition on any event that might update the UI for (const event_name of ["resize", "scroll", "onkeydown", "click"]) { window.addEventListener( event_name, _.debounce(() => { if (place_icon(hotspot)) { place_popover(hotspot); } }, 10), true, ); } }, hotspot.delay * 1000); } export function is_open() { return $(".hotspot.overlay").hasClass("show"); } export function close_hotspot_icon(elem) { $(elem).animate( {opacity: 0}, { duration: 300, done: function () { $(elem).css({display: "none"}); }.bind(elem), }, ); } function close_read_hotspots(new_hotspots) { const unwanted_hotspots = _.difference( Array.from(HOTSPOT_LOCATIONS.keys()), new_hotspots.map((hotspot) => hotspot.name), ); for (const hotspot_name of unwanted_hotspots) { close_hotspot_icon($(`#hotspot_${CSS.escape(hotspot_name)}_icon`)); } } export function load_new(new_hotspots) { close_read_hotspots(new_hotspots); for (const hotspot of new_hotspots) { hotspot.location = HOTSPOT_LOCATIONS.get(hotspot.name); insert_hotspot_into_DOM(hotspot); } } export function initialize() { load_new(page_params.hotspots); }