DEV: Move rerender on 'do not disturb' change to mixin (#11529)

This commit is contained in:
Mark VanLandingham 2020-12-18 10:35:43 -06:00 committed by GitHub
parent 649ed24bb4
commit d8e2b497f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 379 additions and 360 deletions

View File

@ -6,404 +6,387 @@ import PanEvents, {
import { cancel, later, schedule } from "@ember/runloop"; import { cancel, later, schedule } from "@ember/runloop";
import Docking from "discourse/mixins/docking"; import Docking from "discourse/mixins/docking";
import MountWidget from "discourse/components/mount-widget"; import MountWidget from "discourse/components/mount-widget";
import { isTesting } from "discourse-common/config/environment"; import RerenderOnDoNotDisturbChange from "discourse/mixins/rerender-on-do-not-disturb-change";
import { observes } from "discourse-common/utils/decorators"; import { observes } from "discourse-common/utils/decorators";
import { topicTitleDecorators } from "discourse/components/topic-title"; import { topicTitleDecorators } from "discourse/components/topic-title";
const SiteHeaderComponent = MountWidget.extend(Docking, PanEvents, { const SiteHeaderComponent = MountWidget.extend(
widget: "header", Docking,
docAt: null, PanEvents,
dockedHeader: null, RerenderOnDoNotDisturbChange,
_listenToDoNotDisturbLoop: null, {
_animate: false, widget: "header",
_isPanning: false, docAt: null,
_panMenuOrigin: "right", dockedHeader: null,
_panMenuOffset: 0, _animate: false,
_scheduledMovingAnimation: null, _isPanning: false,
_scheduledRemoveAnimate: null, _panMenuOrigin: "right",
_topic: null, _panMenuOffset: 0,
_scheduledMovingAnimation: null,
_scheduledRemoveAnimate: null,
_topic: null,
@observes( @observes(
"currentUser.unread_notifications", "currentUser.unread_notifications",
"currentUser.unread_high_priority_notifications", "currentUser.unread_high_priority_notifications",
"currentUser.reviewable_count" "currentUser.reviewable_count"
) )
notificationsChanged() { notificationsChanged() {
this.queueRerender();
},
_animateOpening($panel) {
$panel.css({ right: "", left: "" });
this._panMenuOffset = 0;
},
_animateClosing($panel, menuOrigin, windowWidth) {
$panel.css(menuOrigin, -windowWidth);
this._animate = true;
schedule("afterRender", () => {
this.eventDispatched("dom:clean", "header");
this._panMenuOffset = 0;
});
},
_isRTL() {
return $("html").css("direction") === "rtl";
},
_leftMenuClass() {
return this._isRTL() ? ".user-menu" : ".hamburger-panel";
},
_leftMenuAction() {
return this._isRTL() ? "toggleUserMenu" : "toggleHamburger";
},
_rightMenuAction() {
return this._isRTL() ? "toggleHamburger" : "toggleUserMenu";
},
_handlePanDone(offset, event) {
const $window = $(window);
const windowWidth = $window.width();
const $menuPanels = $(".menu-panel");
const menuOrigin = this._panMenuOrigin;
this._shouldMenuClose(event, menuOrigin)
? (offset += SWIPE_VELOCITY)
: (offset -= SWIPE_VELOCITY);
$menuPanels.each((idx, panel) => {
const $panel = $(panel);
const $headerCloak = $(".header-cloak");
$panel.css(menuOrigin, -offset);
$headerCloak.css("opacity", Math.min(0.5, (300 - offset) / 600));
if (offset > windowWidth) {
this._animateClosing($panel, menuOrigin, windowWidth);
} else if (offset <= 0) {
this._animateOpening($panel);
} else {
//continue to open or close menu
this._scheduledMovingAnimation = window.requestAnimationFrame(() =>
this._handlePanDone(offset, event)
);
}
});
},
_shouldMenuClose(e, menuOrigin) {
// menu should close after a pan either:
// if a user moved the panel closed past a threshold and away and is NOT swiping back open
// if a user swiped to close fast enough regardless of distance
if (menuOrigin === "right") {
return (
(e.deltaX > SWIPE_DISTANCE_THRESHOLD &&
e.velocityX > -SWIPE_VELOCITY_THRESHOLD) ||
e.velocityX > 0
);
} else {
return (
(e.deltaX < -SWIPE_DISTANCE_THRESHOLD &&
e.velocityX < SWIPE_VELOCITY_THRESHOLD) ||
e.velocityX < 0
);
}
},
panStart(e) {
const center = e.center;
const $centeredElement = $(document.elementFromPoint(center.x, center.y));
if (
($centeredElement.hasClass("panel-body") ||
$centeredElement.hasClass("header-cloak") ||
$centeredElement.parents(".panel-body").length) &&
(e.direction === "left" || e.direction === "right")
) {
e.originalEvent.preventDefault();
this._isPanning = true;
} else {
this._isPanning = false;
}
},
panEnd(e) {
if (!this._isPanning) {
return;
}
this._isPanning = false;
$(".menu-panel").each((idx, panel) => {
const $panel = $(panel);
let offset = $panel.css("right");
if (this._panMenuOrigin === "left") {
offset = $panel.css("left");
}
offset = Math.abs(parseInt(offset, 10));
this._handlePanDone(offset, e);
});
},
panMove(e) {
if (!this._isPanning) {
return;
}
const $menuPanels = $(".menu-panel");
$menuPanels.each((idx, panel) => {
const $panel = $(panel);
const $headerCloak = $(".header-cloak");
if (this._panMenuOrigin === "right") {
const pxClosed = Math.min(0, -e.deltaX + this._panMenuOffset);
$panel.css("right", pxClosed);
$headerCloak.css("opacity", Math.min(0.5, (300 + pxClosed) / 600));
} else {
const pxClosed = Math.min(0, e.deltaX + this._panMenuOffset);
$panel.css("left", pxClosed);
$headerCloak.css("opacity", Math.min(0.5, (300 + pxClosed) / 600));
}
});
},
dockCheck(info) {
const $header = $("header.d-header");
if (this.docAt === null) {
if (!($header && $header.length === 1)) {
return;
}
this.docAt = $header.offset().top;
}
const $body = $("body");
const offset = info.offset();
if (offset >= this.docAt) {
if (!this.dockedHeader) {
$body.addClass("docked");
this.dockedHeader = true;
}
} else {
if (this.dockedHeader) {
$body.removeClass("docked");
this.dockedHeader = false;
}
}
},
setTopic(topic) {
this.eventDispatched("dom:clean", "header");
this._topic = topic;
this.queueRerender();
},
willRender() {
if (this.get("currentUser.staff")) {
$("body").addClass("staff");
}
},
listenForDoNotDisturbChanges() {
if (this.currentUser && !this.currentUser.isInDoNotDisturb()) {
this.queueRerender(); this.queueRerender();
} else { },
cancel(this._listenToDoNotDisturbLoop);
this._listenToDoNotDisturbLoop = later(
this,
() => {
this.listenForDoNotDisturbChanges();
},
10000
);
}
},
didInsertElement() { _animateOpening($panel) {
this._super(...arguments); $panel.css({ right: "", left: "" });
$(window).on("resize.discourse-menu-panel", () => this.afterRender()); this._panMenuOffset = 0;
},
this.appEvents.on("header:show-topic", this, "setTopic"); _animateClosing($panel, menuOrigin, windowWidth) {
this.appEvents.on("header:hide-topic", this, "setTopic"); $panel.css(menuOrigin, -windowWidth);
this.appEvents.on("do-not-disturb:changed", () => this.queueRerender()); this._animate = true;
schedule("afterRender", () => {
this.eventDispatched("dom:clean", "header");
this._panMenuOffset = 0;
});
},
if (!isTesting()) { _isRTL() {
this.listenForDoNotDisturbChanges(); return $("html").css("direction") === "rtl";
} },
this.dispatch("notifications:changed", "user-notifications"); _leftMenuClass() {
this.dispatch("header:keyboard-trigger", "header"); return this._isRTL() ? ".user-menu" : ".hamburger-panel";
this.dispatch("search-autocomplete:after-complete", "search-term"); },
this.appEvents.on("dom:clean", this, "_cleanDom"); _leftMenuAction() {
return this._isRTL() ? "toggleUserMenu" : "toggleHamburger";
},
// Allow first notification to be dismissed on a click anywhere _rightMenuAction() {
if ( return this._isRTL() ? "toggleHamburger" : "toggleUserMenu";
this.currentUser && },
!this.get("currentUser.read_first_notification") &&
!this.get("currentUser.enforcedSecondFactor") _handlePanDone(offset, event) {
) { const $window = $(window);
this._dismissFirstNotification = (e) => { const windowWidth = $window.width();
if ( const $menuPanels = $(".menu-panel");
!e.target.closest("#current-user") && const menuOrigin = this._panMenuOrigin;
!e.target.closest(".ring-backdrop") && this._shouldMenuClose(event, menuOrigin)
this.currentUser && ? (offset += SWIPE_VELOCITY)
!this.get("currentUser.read_first_notification") && : (offset -= SWIPE_VELOCITY);
!this.get("currentUser.enforcedSecondFactor") $menuPanels.each((idx, panel) => {
) { const $panel = $(panel);
this.eventDispatched( const $headerCloak = $(".header-cloak");
"header:dismiss-first-notification-mask", $panel.css(menuOrigin, -offset);
"header" $headerCloak.css("opacity", Math.min(0.5, (300 - offset) / 600));
if (offset > windowWidth) {
this._animateClosing($panel, menuOrigin, windowWidth);
} else if (offset <= 0) {
this._animateOpening($panel);
} else {
//continue to open or close menu
this._scheduledMovingAnimation = window.requestAnimationFrame(() =>
this._handlePanDone(offset, event)
); );
} }
};
document.addEventListener("click", this._dismissFirstNotification, {
once: true,
}); });
} },
},
_cleanDom() { _shouldMenuClose(e, menuOrigin) {
// For performance, only trigger a re-render if any menu panels are visible // menu should close after a pan either:
if (this.element.querySelector(".menu-panel")) { // if a user moved the panel closed past a threshold and away and is NOT swiping back open
this.eventDispatched("dom:clean", "header"); // if a user swiped to close fast enough regardless of distance
} if (menuOrigin === "right") {
}, return (
(e.deltaX > SWIPE_DISTANCE_THRESHOLD &&
willDestroyElement() { e.velocityX > -SWIPE_VELOCITY_THRESHOLD) ||
this._super(...arguments); e.velocityX > 0
$("body").off("keydown.header"); );
$(window).off("resize.discourse-menu-panel"); } else {
return (
this.appEvents.off("header:show-topic", this, "setTopic"); (e.deltaX < -SWIPE_DISTANCE_THRESHOLD &&
this.appEvents.off("header:hide-topic", this, "setTopic"); e.velocityX < SWIPE_VELOCITY_THRESHOLD) ||
this.appEvents.off("dom:clean", this, "_cleanDom"); e.velocityX < 0
);
cancel(this._scheduledRemoveAnimate);
cancel(this._listenToDoNotDisturbLoop);
window.cancelAnimationFrame(this._scheduledMovingAnimation);
document.removeEventListener("click", this._dismissFirstNotification);
},
buildArgs() {
return {
topic: this._topic,
canSignUp: this.canSignUp,
};
},
afterRender() {
const headerTitle = document.querySelector(".header-title .topic-link");
if (headerTitle && this._topic) {
topicTitleDecorators.forEach((cb) =>
cb(this._topic, headerTitle, "header-title")
);
}
const $menuPanels = $(".menu-panel");
if ($menuPanels.length === 0) {
if (this.site.mobileView) {
this._animate = true;
} }
return; },
}
const $window = $(window); panStart(e) {
const windowWidth = $window.width(); const center = e.center;
const $centeredElement = $(document.elementFromPoint(center.x, center.y));
const headerWidth = $("#main-outlet .container").width() || 1100; if (
const remaining = (windowWidth - headerWidth) / 2; ($centeredElement.hasClass("panel-body") ||
const viewMode = remaining < 50 ? "slide-in" : "drop-down"; $centeredElement.hasClass("header-cloak") ||
$centeredElement.parents(".panel-body").length) &&
$menuPanels.each((idx, panel) => { (e.direction === "left" || e.direction === "right")
const $panel = $(panel); ) {
const $headerCloak = $(".header-cloak"); e.originalEvent.preventDefault();
let width = parseInt($panel.attr("data-max-width"), 10) || 300; this._isPanning = true;
if (windowWidth - width < 50) { } else {
width = windowWidth - 50; this._isPanning = false;
}
if (this._panMenuOffset) {
this._panMenuOffset = -width;
} }
},
$panel.removeClass("drop-down slide-in").addClass(viewMode); panEnd(e) {
if (this._animate || this._panMenuOffset !== 0) { if (!this._isPanning) {
$headerCloak.css("opacity", 0); return;
if ( }
this.site.mobileView && this._isPanning = false;
$panel.parent(this._leftMenuClass()).length > 0 $(".menu-panel").each((idx, panel) => {
) { const $panel = $(panel);
this._panMenuOrigin = "left"; let offset = $panel.css("right");
$panel.css("left", -windowWidth); if (this._panMenuOrigin === "left") {
} else { offset = $panel.css("left");
this._panMenuOrigin = "right";
$panel.css("right", -windowWidth);
} }
offset = Math.abs(parseInt(offset, 10));
this._handlePanDone(offset, e);
});
},
panMove(e) {
if (!this._isPanning) {
return;
} }
const $menuPanels = $(".menu-panel");
$menuPanels.each((idx, panel) => {
const $panel = $(panel);
const $headerCloak = $(".header-cloak");
if (this._panMenuOrigin === "right") {
const pxClosed = Math.min(0, -e.deltaX + this._panMenuOffset);
$panel.css("right", pxClosed);
$headerCloak.css("opacity", Math.min(0.5, (300 + pxClosed) / 600));
} else {
const pxClosed = Math.min(0, e.deltaX + this._panMenuOffset);
$panel.css("left", pxClosed);
$headerCloak.css("opacity", Math.min(0.5, (300 + pxClosed) / 600));
}
});
},
const $panelBody = $(".panel-body", $panel); dockCheck(info) {
const $header = $("header.d-header");
// We use a mutationObserver to check for style changes, so it's important if (this.docAt === null) {
// we don't set it if it doesn't change. Same goes for the $panelBody! if (!($header && $header.length === 1)) {
const style = $panel.prop("style");
if (viewMode === "drop-down") {
const $buttonPanel = $("header ul.icons");
if ($buttonPanel.length === 0) {
return; return;
} }
this.docAt = $header.offset().top;
}
// These values need to be set here, not in the css file - this is to deal with the const $body = $("body");
// possibility of the window being resized and the menu changing from .slide-in to .drop-down. const offset = info.offset();
if (style.top !== "100%" || style.height !== "auto") { if (offset >= this.docAt) {
$panel.css({ top: "100%", height: "auto" }); if (!this.dockedHeader) {
$body.addClass("docked");
this.dockedHeader = true;
} }
$("body").addClass("drop-down-mode");
} else { } else {
if (this.site.mobileView) { if (this.dockedHeader) {
$headerCloak.show(); $body.removeClass("docked");
this.dockedHeader = false;
} }
}
},
const menuTop = this.site.mobileView ? headerTop() : headerHeight(); setTopic(topic) {
this.eventDispatched("dom:clean", "header");
this._topic = topic;
this.queueRerender();
},
const winHeightOffset = 16; willRender() {
let initialWinHeight = window.innerHeight if (this.get("currentUser.staff")) {
? window.innerHeight $("body").addClass("staff");
: $(window).height(); }
const winHeight = initialWinHeight - winHeightOffset; },
let height; didInsertElement() {
if (this.site.mobileView) { this._super(...arguments);
height = winHeight - menuTop; $(window).on("resize.discourse-menu-panel", () => this.afterRender());
}
const isIPadApp = document.body.classList.contains("footer-nav-ipad"), this.appEvents.on("header:show-topic", this, "setTopic");
heightProp = isIPadApp ? "max-height" : "height", this.appEvents.on("header:hide-topic", this, "setTopic");
iPadOffset = 10;
if (isIPadApp) { this.dispatch("notifications:changed", "user-notifications");
height = winHeight - menuTop - iPadOffset; this.dispatch("header:keyboard-trigger", "header");
} this.dispatch("search-autocomplete:after-complete", "search-term");
if ($panelBody.prop("style").height !== "100%") { this.appEvents.on("dom:clean", this, "_cleanDom");
$panelBody.height("100%");
} // Allow first notification to be dismissed on a click anywhere
if (style.top !== menuTop + "px" || style[heightProp] !== height) { if (
$panel.css({ top: menuTop + "px", [heightProp]: height }); this.currentUser &&
$(".header-cloak").css({ top: menuTop + "px" }); !this.get("currentUser.read_first_notification") &&
} !this.get("currentUser.enforcedSecondFactor")
$("body").removeClass("drop-down-mode"); ) {
this._dismissFirstNotification = (e) => {
if (
!e.target.closest("#current-user") &&
!e.target.closest(".ring-backdrop") &&
this.currentUser &&
!this.get("currentUser.read_first_notification") &&
!this.get("currentUser.enforcedSecondFactor")
) {
this.eventDispatched(
"header:dismiss-first-notification-mask",
"header"
);
}
};
document.addEventListener("click", this._dismissFirstNotification, {
once: true,
});
}
},
_cleanDom() {
// For performance, only trigger a re-render if any menu panels are visible
if (this.element.querySelector(".menu-panel")) {
this.eventDispatched("dom:clean", "header");
}
},
willDestroyElement() {
this._super(...arguments);
$("body").off("keydown.header");
$(window).off("resize.discourse-menu-panel");
this.appEvents.off("header:show-topic", this, "setTopic");
this.appEvents.off("header:hide-topic", this, "setTopic");
this.appEvents.off("dom:clean", this, "_cleanDom");
cancel(this._scheduledRemoveAnimate);
window.cancelAnimationFrame(this._scheduledMovingAnimation);
document.removeEventListener("click", this._dismissFirstNotification);
},
buildArgs() {
return {
topic: this._topic,
canSignUp: this.canSignUp,
};
},
afterRender() {
const headerTitle = document.querySelector(".header-title .topic-link");
if (headerTitle && this._topic) {
topicTitleDecorators.forEach((cb) =>
cb(this._topic, headerTitle, "header-title")
);
} }
$panel.width(width); const $menuPanels = $(".menu-panel");
if (this._animate) { if ($menuPanels.length === 0) {
$panel.addClass("animate"); if (this.site.mobileView) {
$headerCloak.addClass("animate"); this._animate = true;
this._scheduledRemoveAnimate = later(() => { }
$panel.removeClass("animate"); return;
$headerCloak.removeClass("animate");
}, 200);
} }
$panel.css({ right: "", left: "" });
$headerCloak.css("opacity", 0.5); const $window = $(window);
this._animate = false; const windowWidth = $window.width();
});
}, const headerWidth = $("#main-outlet .container").width() || 1100;
}); const remaining = (windowWidth - headerWidth) / 2;
const viewMode = remaining < 50 ? "slide-in" : "drop-down";
$menuPanels.each((idx, panel) => {
const $panel = $(panel);
const $headerCloak = $(".header-cloak");
let width = parseInt($panel.attr("data-max-width"), 10) || 300;
if (windowWidth - width < 50) {
width = windowWidth - 50;
}
if (this._panMenuOffset) {
this._panMenuOffset = -width;
}
$panel.removeClass("drop-down slide-in").addClass(viewMode);
if (this._animate || this._panMenuOffset !== 0) {
$headerCloak.css("opacity", 0);
if (
this.site.mobileView &&
$panel.parent(this._leftMenuClass()).length > 0
) {
this._panMenuOrigin = "left";
$panel.css("left", -windowWidth);
} else {
this._panMenuOrigin = "right";
$panel.css("right", -windowWidth);
}
}
const $panelBody = $(".panel-body", $panel);
// We use a mutationObserver to check for style changes, so it's important
// we don't set it if it doesn't change. Same goes for the $panelBody!
const style = $panel.prop("style");
if (viewMode === "drop-down") {
const $buttonPanel = $("header ul.icons");
if ($buttonPanel.length === 0) {
return;
}
// These values need to be set here, not in the css file - this is to deal with the
// possibility of the window being resized and the menu changing from .slide-in to .drop-down.
if (style.top !== "100%" || style.height !== "auto") {
$panel.css({ top: "100%", height: "auto" });
}
$("body").addClass("drop-down-mode");
} else {
if (this.site.mobileView) {
$headerCloak.show();
}
const menuTop = this.site.mobileView ? headerTop() : headerHeight();
const winHeightOffset = 16;
let initialWinHeight = window.innerHeight
? window.innerHeight
: $(window).height();
const winHeight = initialWinHeight - winHeightOffset;
let height;
if (this.site.mobileView) {
height = winHeight - menuTop;
}
const isIPadApp = document.body.classList.contains("footer-nav-ipad"),
heightProp = isIPadApp ? "max-height" : "height",
iPadOffset = 10;
if (isIPadApp) {
height = winHeight - menuTop - iPadOffset;
}
if ($panelBody.prop("style").height !== "100%") {
$panelBody.height("100%");
}
if (style.top !== menuTop + "px" || style[heightProp] !== height) {
$panel.css({ top: menuTop + "px", [heightProp]: height });
$(".header-cloak").css({ top: menuTop + "px" });
}
$("body").removeClass("drop-down-mode");
}
$panel.width(width);
if (this._animate) {
$panel.addClass("animate");
$headerCloak.addClass("animate");
this._scheduledRemoveAnimate = later(() => {
$panel.removeClass("animate");
$headerCloak.removeClass("animate");
}, 200);
}
$panel.css({ right: "", left: "" });
$headerCloak.css("opacity", 0.5);
this._animate = false;
});
},
}
);
export default SiteHeaderComponent.extend({ export default SiteHeaderComponent.extend({
classNames: ["d-header-wrap"], classNames: ["d-header-wrap"],

View File

@ -0,0 +1,36 @@
import { cancel, later } from "@ember/runloop";
import Mixin from "@ember/object/mixin";
import { isTesting } from "discourse-common/config/environment";
export default Mixin.create({
_listenToDoNotDisturbLoop: null,
listenForDoNotDisturbChanges() {
if (this.currentUser && !this.currentUser.isInDoNotDisturb()) {
this.queueRerender();
} else {
cancel(this._listenToDoNotDisturbLoop);
this._listenToDoNotDisturbLoop = later(
this,
() => {
this.listenForDoNotDisturbChanges();
},
10000
);
}
},
didInsertElement() {
this._super(...arguments);
this.appEvents.on("do-not-disturb:changed", () => this.queueRerender());
if (!isTesting()) {
this.listenForDoNotDisturbChanges();
}
},
willDestroyElement() {
this._super(...arguments);
cancel(this._listenToDoNotDisturbLoop);
},
});