diff --git a/app/assets/javascripts/discourse/app/components/footer-nav.gjs b/app/assets/javascripts/discourse/app/components/footer-nav.gjs new file mode 100644 index 00000000000..ee18e60cab1 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/footer-nav.gjs @@ -0,0 +1,128 @@ +import Component from "@glimmer/component"; +import { action } from "@ember/object"; +import { service } from "@ember/service"; +import DButton from "discourse/components/d-button"; +import concatClass from "discourse/helpers/concat-class"; +import htmlClass from "discourse/helpers/html-class"; +import { postRNWebviewMessage } from "discourse/lib/utilities"; +import { SCROLLED_UP, UNSCROLLED } from "discourse/services/scroll-direction"; +import not from "truth-helpers/helpers/not"; + +export default class FooterNav extends Component { + @service appEvents; + @service capabilities; + @service scrollDirection; + @service composer; + @service modal; + @service historyStore; + + _modalOn() { + postRNWebviewMessage("headerBg", "rgb(0, 0, 0)"); + } + + _modalOff() { + postRNWebviewMessage( + "headerBg", + document.documentElement.style.getPropertyValue("--header_background") + ); + } + + @action + setDiscourseHubHeaderBg(hasAnActiveModal) { + if (!this.capabilities.isAppWebview) { + return; + } + + if (hasAnActiveModal) { + this._modalOn(); + } else { + this._modalOff(); + } + } + + @action + dismiss() { + postRNWebviewMessage("dismiss", true); + } + + @action + share() { + postRNWebviewMessage("shareUrl", window.location.href); + } + + @action + goBack(_, event) { + window.history.back(); + event.preventDefault(); + } + + @action + goForward(_, event) { + window.history.forward(); + event.preventDefault(); + } + + get isVisible() { + return ( + [UNSCROLLED, SCROLLED_UP].includes( + this.scrollDirection.lastScrollDirection + ) && !this.composer.isOpen + ); + } + + get canGoBack() { + return this.historyStore.hasPastEntries || !!document.referrer; + } + + get canGoForward() { + return this.historyStore.hasFutureEntries; + } + + +} diff --git a/app/assets/javascripts/discourse/app/components/footer-nav.js b/app/assets/javascripts/discourse/app/components/footer-nav.js deleted file mode 100644 index 7cf400d4fa8..00000000000 --- a/app/assets/javascripts/discourse/app/components/footer-nav.js +++ /dev/null @@ -1,144 +0,0 @@ -import { service } from "@ember/service"; -import MountWidget from "discourse/components/mount-widget"; -import { postRNWebviewMessage } from "discourse/lib/utilities"; -import { SCROLLED_UP, UNSCROLLED } from "discourse/services/scroll-direction"; -import { bind, observes } from "discourse-common/utils/decorators"; - -const FooterNavComponent = MountWidget.extend({ - widget: "footer-nav", - classNames: ["footer-nav", "visible"], - scrollDirection: service(), - routeHistory: [], - currentRouteIndex: 0, - canGoBack: false, - canGoForward: false, - backForwardClicked: null, - - buildArgs() { - return { - canGoBack: this.canGoBack, - canGoForward: this.canGoForward, - }; - }, - - didInsertElement() { - this._super(...arguments); - this.appEvents.on("page:changed", this, "_routeChanged"); - - if (this.capabilities.isAppWebview) { - this.appEvents.on("modal:body-shown", this, "_modalOn"); - this.appEvents.on("modal:body-dismissed", this, "_modalOff"); - } - - if (this.capabilities.isIpadOS) { - document.documentElement.classList.add("footer-nav-ipad"); - } else { - this.appEvents.on("composer:opened", this, "_composerOpened"); - this.appEvents.on("composer:closed", this, "_composerClosed"); - document.documentElement.classList.add("footer-nav-visible"); - } - - this.scrollDirection.addObserver( - "lastScrollDirection", - this.toggleMobileFooter - ); - }, - - willDestroyElement() { - this._super(...arguments); - this.appEvents.off("page:changed", this, "_routeChanged"); - - if (this.capabilities.isAppWebview) { - this.appEvents.off("modal:body-shown", this, "_modalOn"); - this.appEvents.off("modal:body-removed", this, "_modalOff"); - } - - if (this.capabilities.isIpadOS) { - document.documentElement.classList.remove("footer-nav-ipad"); - } else { - this.unbindScrolling(); - window.removeEventListener("resize", this.scrolled); - this.appEvents.off("composer:opened", this, "_composerOpened"); - this.appEvents.off("composer:closed", this, "_composerClosed"); - } - - this.scrollDirection.removeObserver( - "lastScrollDirection", - this.toggleMobileFooter - ); - }, - - @bind - toggleMobileFooter() { - const visible = [UNSCROLLED, SCROLLED_UP].includes( - this.scrollDirection.lastScrollDirection - ); - this.element.classList.toggle("visible", visible); - document.documentElement.classList.toggle("footer-nav-visible", visible); - }, - - _routeChanged(route) { - // only update route history if not using back/forward nav - if (this.backForwardClicked) { - this.backForwardClicked = null; - return; - } - - this.routeHistory.push(route.url); - this.set("currentRouteIndex", this.routeHistory.length); - - this.queueRerender(); - }, - - _composerOpened() { - this.set("mobileScrollDirection", "down"); - this.set("scrollEventDisabled", true); - }, - - _composerClosed() { - this.set("mobileScrollDirection", null); - this.set("scrollEventDisabled", false); - }, - - _modalOn() { - const backdrop = document.querySelector(".modal-backdrop"); - if (backdrop) { - postRNWebviewMessage( - "headerBg", - getComputedStyle(backdrop)["background-color"] - ); - } - }, - - _modalOff() { - const dheader = document.querySelector(".d-header"); - if (dheader) { - postRNWebviewMessage( - "headerBg", - getComputedStyle(dheader)["background-color"] - ); - } - }, - - goBack() { - this.set("currentRouteIndex", this.currentRouteIndex - 1); - this.backForwardClicked = true; - window.history.back(); - }, - - goForward() { - this.set("currentRouteIndex", this.currentRouteIndex + 1); - this.backForwardClicked = true; - window.history.forward(); - }, - - @observes("currentRouteIndex") - setBackForward() { - let index = this.currentRouteIndex; - - this.set("canGoBack", index > 1 || document.referrer ? true : false); - this.set("canGoForward", index < this.routeHistory.length ? true : false); - }, -}); - -export default FooterNavComponent; diff --git a/app/assets/javascripts/discourse/app/services/composer.js b/app/assets/javascripts/discourse/app/services/composer.js index 81d234caa75..22d23aa42a2 100644 --- a/app/assets/javascripts/discourse/app/services/composer.js +++ b/app/assets/javascripts/discourse/app/services/composer.js @@ -139,6 +139,10 @@ export default class ComposerService extends Service { return getOwnerWithFallback(this).lookup("controller:topic"); } + get isOpen() { + return this.model?.composeState === Composer.OPEN; + } + @on("init") _setupPreview() { const val = this.site.mobileView diff --git a/app/assets/javascripts/discourse/app/services/history-store.js b/app/assets/javascripts/discourse/app/services/history-store.js index 7e8a1ea294c..a19051f215f 100644 --- a/app/assets/javascripts/discourse/app/services/history-store.js +++ b/app/assets/javascripts/discourse/app/services/history-store.js @@ -1,4 +1,5 @@ import { DEBUG } from "@glimmer/env"; +import { cached } from "@glimmer/tracking"; import Service, { service } from "@ember/service"; import { TrackedMap } from "@ember-compat/tracked-built-ins"; import { disableImplicitInjections } from "discourse/lib/implicit-injections"; @@ -18,7 +19,7 @@ const HANDLED_TRANSITIONS = new WeakSet(); export default class HistoryStore extends Service { @service router; - #routeData = new Map(); + #routeData = new TrackedMap(); #uuid; #pendingStore; @@ -60,6 +61,36 @@ export default class HistoryStore extends Service { return this.#currentStore.delete(key); } + @cached + get hasFutureEntries() { + // Keys will be returned in insertion order. Return true if there is any key **after** the current one + let foundCurrent = false; + for (const key of this.#routeData.keys()) { + if (foundCurrent) { + return true; + } + + if (key === this.#uuid) { + foundCurrent = true; + } + } + return false; + } + + @cached + get hasPastEntries() { + // Keys will be returned in insertion order. Return false if we find the current uuid before any other + for (const key of this.#routeData.keys()) { + if (key === undefined) { + continue; + } + if (key === this.#uuid) { + return false; + } + return true; + } + } + #pruneOldData() { while (this.#routeData.size > HISTORY_SIZE) { // JS Map guarantees keys will be returned in insertion order diff --git a/app/assets/javascripts/discourse/app/widgets/footer-nav.js b/app/assets/javascripts/discourse/app/widgets/footer-nav.js deleted file mode 100644 index 7a18e06ad5d..00000000000 --- a/app/assets/javascripts/discourse/app/widgets/footer-nav.js +++ /dev/null @@ -1,60 +0,0 @@ -import { postRNWebviewMessage } from "discourse/lib/utilities"; -import { createWidget } from "discourse/widgets/widget"; - -createWidget("footer-nav", { - tagName: "div.footer-nav-widget", - - html(attrs) { - const buttons = []; - - buttons.push( - this.attach("flat-button", { - action: "goBack", - icon: "chevron-left", - className: "btn-large", - disabled: !attrs.canGoBack, - title: "footer_nav.back", - }) - ); - - buttons.push( - this.attach("flat-button", { - action: "goForward", - icon: "chevron-right", - className: "btn-large", - disabled: !attrs.canGoForward, - title: "footer_nav.forward", - }) - ); - - if (this.capabilities.isAppWebview) { - buttons.push( - this.attach("flat-button", { - action: "share", - icon: "link", - className: "btn-large", - title: "footer_nav.share", - }) - ); - - buttons.push( - this.attach("flat-button", { - action: "dismiss", - icon: "chevron-down", - className: "btn-large", - title: "footer_nav.dismiss", - }) - ); - } - - return buttons; - }, - - dismiss() { - postRNWebviewMessage("dismiss", true); - }, - - share() { - postRNWebviewMessage("shareUrl", window.location.href); - }, -});