DEV: migrates footer-nav from widget to gjs (#28024)

This commit also attempts to promote more declarative patterns. The route history logic has been replaced by using the history-store service.

---------

Co-authored-by: Jarek Radosz <jarek@cvx.dev>
Co-authored-by: David Taylor <david@taylorhq.com>
This commit is contained in:
Joffrey JAFFEUX 2024-07-24 07:54:15 +02:00 committed by GitHub
parent db8c1f20ed
commit 0c13c91f84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 164 additions and 205 deletions

View File

@ -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;
}
<template>
{{this.setDiscourseHubHeaderBg this.modal.activeModal}}
{{#if this.capabilities.isIpadOS}}
{{htmlClass "footer-nav-ipad"}}
{{else if this.isVisible}}
{{htmlClass "footer-nav-visible"}}
{{/if}}
<div class={{concatClass "footer-nav" (if this.isVisible "visible")}}>
<div class="footer-nav-widget">
<DButton
@action={{this.goBack}}
@icon="chevron-left"
class="btn-flat btn-large"
@disabled={{not this.canGoBack}}
@title="footer_nav.back"
@forwardEvent={{true}}
/>
<DButton
@action={{this.goForward}}
@icon="chevron-right"
class="btn-flat btn-large"
@disabled={{not this.canGoForward}}
@title="footer_nav.forward"
@forwardEvent={{true}}
/>
{{#if this.capabilities.isAppWebview}}
<DButton
@action={{this.share}}
@icon="link"
class="btn-flat btn-large"
@title="footer_nav.share"
/>
<DButton
@action={{this.dismiss}}
@icon="chevron-down"
class="btn-flat btn-large"
@title="footer_nav.dismiss"
/>
{{/if}}
</div>
</div>
</template>
}

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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);
},
});