DEV: Auto expand active sections and scroll active link into view
This commit is contained in:
parent
11369018b6
commit
b7cce1a0dc
|
@ -11,7 +11,11 @@ export default class SidebarApiPanels extends Component {
|
|||
|
||||
<template>
|
||||
<div class="sidebar-sections {{this.panelCssClass}}">
|
||||
<ApiSections @collapsable={{@collapsableSections}} />
|
||||
<ApiSections
|
||||
@collapsable={{@collapsableSections}}
|
||||
@expandActiveSection={{this.sidebarState.currentPanel.expandActiveSection}}
|
||||
@scrollActiveLinkIntoView={{this.sidebarState.currentPanel.scrollActiveLinkIntoView}}
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { and, eq } from "truth-helpers";
|
||||
import Section from "./section";
|
||||
import SectionLink from "./section-link";
|
||||
|
||||
|
@ -14,6 +15,9 @@ const SidebarApiSection = <template>
|
|||
@displaySection={{@section.displaySection}}
|
||||
@hideSectionHeader={{@section.hideSectionHeader}}
|
||||
@collapsedByDefault={{@section.collapsedByDefault}}
|
||||
@activeLink={{@section.activeLink}}
|
||||
@expandWhenActive={{@expandWhenActive}}
|
||||
@scrollActiveLinkIntoView={{@scrollActiveLinkIntoView}}
|
||||
>
|
||||
{{#each @section.filteredLinks key="name" as |link|}}
|
||||
<SectionLink
|
||||
|
@ -23,6 +27,7 @@ const SidebarApiSection = <template>
|
|||
@model={{link.model}}
|
||||
@query={{link.query}}
|
||||
@models={{link.models}}
|
||||
@currentWhen={{link.currentWhen}}
|
||||
@href={{link.href}}
|
||||
@title={{link.title}}
|
||||
@contentCSSClass={{link.contentCSSClass}}
|
||||
|
@ -38,7 +43,6 @@ const SidebarApiSection = <template>
|
|||
@hoverValue={{link.hoverValue}}
|
||||
@hoverAction={{link.hoverAction}}
|
||||
@hoverTitle={{link.hoverTitle}}
|
||||
@currentWhen={{link.currentWhen}}
|
||||
@didInsert={{link.didInsert}}
|
||||
@willDestroy={{link.willDestroy}}
|
||||
@content={{link.text}}
|
||||
|
@ -46,6 +50,10 @@ const SidebarApiSection = <template>
|
|||
link.contentComponent
|
||||
status=link.contentComponentArgs
|
||||
}}
|
||||
@scrollIntoView={{and
|
||||
@scrollActiveLinkIntoView
|
||||
(eq link.name @section.activeLink.name)
|
||||
}}
|
||||
/>
|
||||
{{/each}}
|
||||
</Section>
|
||||
|
|
|
@ -6,6 +6,7 @@ import ApiSection from "./api-section";
|
|||
import PanelHeader from "./panel-header";
|
||||
|
||||
export default class SidebarApiSections extends Component {
|
||||
@service router;
|
||||
@service sidebarState;
|
||||
|
||||
get sections() {
|
||||
|
@ -20,7 +21,7 @@ export default class SidebarApiSections extends Component {
|
|||
}
|
||||
|
||||
return sectionConfigs.map((Section) => {
|
||||
const SidebarSection = prepareSidebarSectionClass(Section);
|
||||
const SidebarSection = prepareSidebarSectionClass(Section, this.router);
|
||||
|
||||
const sectionInstance = new SidebarSection({
|
||||
filterable:
|
||||
|
@ -43,14 +44,19 @@ export default class SidebarApiSections extends Component {
|
|||
<PanelHeader @sections={{this.filteredSections}} />
|
||||
|
||||
{{#each this.filteredSections as |section|}}
|
||||
<ApiSection @section={{section}} @collapsable={{@collapsable}} />
|
||||
<ApiSection
|
||||
@section={{section}}
|
||||
@collapsable={{@collapsable}}
|
||||
@expandWhenActive={{@expandActiveSection}}
|
||||
@scrollActiveLinkIntoView={{@scrollActiveLinkIntoView}}
|
||||
/>
|
||||
{{/each}}
|
||||
</template>
|
||||
}
|
||||
|
||||
// extends the class provided for the section to add functionality we don't want to be overridable when defining custom
|
||||
// sections using the plugin API, like for example the filtering capabilities
|
||||
function prepareSidebarSectionClass(Section) {
|
||||
function prepareSidebarSectionClass(Section, routerService) {
|
||||
return class extends Section {
|
||||
constructor({ filterable, sidebarState }) {
|
||||
super();
|
||||
|
@ -82,6 +88,46 @@ function prepareSidebarSectionClass(Section) {
|
|||
});
|
||||
}
|
||||
|
||||
get activeLink() {
|
||||
return this.filteredLinks.find((link) => {
|
||||
try {
|
||||
const currentWhen = link.currentWhen;
|
||||
|
||||
if (typeof currentWhen === "boolean") {
|
||||
return currentWhen;
|
||||
}
|
||||
|
||||
// TODO detect active links using the href field
|
||||
|
||||
const queryParams = link.queryParams || {};
|
||||
let models;
|
||||
|
||||
if (link.model) {
|
||||
models = [link.model];
|
||||
} else if (link.models) {
|
||||
models = link.models;
|
||||
} else {
|
||||
models = [];
|
||||
}
|
||||
|
||||
if (typeof currentWhen === "string") {
|
||||
return currentWhen.split(" ").some((route) =>
|
||||
routerService.isActive(route, ...models, {
|
||||
queryParams,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return routerService.isActive(link.route, ...models, {
|
||||
queryParams,
|
||||
});
|
||||
} catch (e) {
|
||||
// false if ember throws an exception while checking the routes
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get filtered() {
|
||||
return !this.filterable || this.filteredLinks?.length > 0;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { hash } from "@ember/helper";
|
||||
import { on } from "@ember/modifier";
|
||||
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
|
||||
import { LinkTo } from "@ember/routing";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import { service } from "@ember/service";
|
||||
import { eq, or } from "truth-helpers";
|
||||
import concatClass from "discourse/helpers/concat-class";
|
||||
import icon from "discourse-common/helpers/d-icon";
|
||||
import deprecated from "discourse-common/lib/deprecated";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import SectionLinkPrefix from "./section-link-prefix";
|
||||
|
||||
/**
|
||||
|
@ -61,6 +65,14 @@ export default class SectionLink extends Component {
|
|||
classNames.push(this.args.class);
|
||||
}
|
||||
|
||||
if (
|
||||
this.args.href &&
|
||||
typeof this.args.currentWhen === "boolean" &&
|
||||
this.args.currentWhen
|
||||
) {
|
||||
classNames.push("active");
|
||||
}
|
||||
|
||||
return classNames.join(" ");
|
||||
}
|
||||
|
||||
|
@ -101,9 +113,22 @@ export default class SectionLink extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
@bind
|
||||
scrollIntoView(element) {
|
||||
if (this.args.scrollIntoView) {
|
||||
schedule("afterRender", () => {
|
||||
element.scrollIntoView({
|
||||
block: "center",
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if this.shouldDisplay}}
|
||||
<li
|
||||
{{didInsert this.scrollIntoView}}
|
||||
{{didUpdate this.scrollIntoView @scrollIntoView}}
|
||||
data-list-item-name={{@linkName}}
|
||||
class="sidebar-section-link-wrapper"
|
||||
...attributes
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { hash } from "@ember/helper";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
|
@ -19,15 +20,27 @@ import SectionHeader from "./section-header";
|
|||
|
||||
export default class SidebarSection extends Component {
|
||||
@service keyValueStore;
|
||||
@service router;
|
||||
@service sidebarState;
|
||||
|
||||
@tracked activeExpanded = false;
|
||||
|
||||
sidebarSectionContentId = getSidebarSectionContentId(this.args.sectionName);
|
||||
collapsedSidebarSectionKey = getCollapsedSidebarSectionKey(
|
||||
this.args.sectionName
|
||||
);
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
this.router.on("routeDidChange", this, this.expandIfActive);
|
||||
}
|
||||
|
||||
willDestroy() {
|
||||
super.willDestroy(...arguments);
|
||||
|
||||
this.router.off("routeDidChange", this, this.expandIfActive);
|
||||
|
||||
this.args.willDestroy?.();
|
||||
}
|
||||
|
||||
|
@ -47,12 +60,20 @@ export default class SidebarSection extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
get isActive() {
|
||||
return !!this.args.activeLink;
|
||||
}
|
||||
|
||||
@bind
|
||||
setExpandedState() {
|
||||
if (!isEmpty(this.sidebarState.filter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.expandIfActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isCollapsed) {
|
||||
this.sidebarState.collapseSection(this.args.sectionName);
|
||||
} else {
|
||||
|
@ -60,11 +81,26 @@ export default class SidebarSection extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
@bind
|
||||
expandIfActive(transition) {
|
||||
if (transition?.isAborted) {
|
||||
return this.activeExpanded;
|
||||
}
|
||||
|
||||
this.activeExpanded = this.args.expandWhenActive && this.isActive;
|
||||
|
||||
return this.activeExpanded;
|
||||
}
|
||||
|
||||
get displaySectionContent() {
|
||||
if (this.args.hideSectionHeader || !isEmpty(this.sidebarState.filter)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.activeExpanded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !this.sidebarState.collapsedSections.has(
|
||||
this.collapsedSidebarSectionKey
|
||||
);
|
||||
|
@ -72,6 +108,8 @@ export default class SidebarSection extends Component {
|
|||
|
||||
@action
|
||||
toggleSectionDisplay(_, event) {
|
||||
this.activeExpanded = false;
|
||||
|
||||
if (this.displaySectionContent) {
|
||||
this.sidebarState.collapseSection(this.args.sectionName);
|
||||
} else {
|
||||
|
@ -134,6 +172,7 @@ export default class SidebarSection extends Component {
|
|||
@sidebarSectionContentId={{this.sidebarSectionContentId}}
|
||||
@toggleSectionDisplay={{this.toggleSectionDisplay}}
|
||||
@isExpanded={{this.displaySectionContent}}
|
||||
@isActive={{this.isActive}}
|
||||
>
|
||||
{{#if @collapsable}}
|
||||
<span class="sidebar-section-header-caret">
|
||||
|
|
|
@ -57,6 +57,14 @@ export default class BaseCustomSidebarPanel {
|
|||
return false;
|
||||
}
|
||||
|
||||
get expandActiveSection() {
|
||||
return false;
|
||||
}
|
||||
|
||||
get scrollActiveLinkIntoView() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} filter filter applied
|
||||
*
|
||||
|
|
|
@ -36,6 +36,11 @@ export default class BaseCustomSidebarSectionLink {
|
|||
*/
|
||||
get models() {}
|
||||
|
||||
/**
|
||||
* @returns {boolean} `query` argument for the <LinkTo> component. See See https://api.emberjs.com/ember/release/classes/Ember.Templates.components/methods/LinkTo?anchor=LinkTo.
|
||||
*/
|
||||
get query() {}
|
||||
|
||||
/**
|
||||
* @returns {boolean} `current-when` argument for the <LinkTo> component. See See https://api.emberjs.com/ember/release/classes/Ember.Templates.components/methods/LinkTo?anchor=LinkTo.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue