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:
parent
db8c1f20ed
commit
0c13c91f84
|
@ -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>
|
||||
}
|
|
@ -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;
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
});
|
Loading…
Reference in New Issue