DEV: Auto expand active sections and scroll active link into view

This commit is contained in:
Sérgio Saquetim 2024-08-05 22:28:38 -03:00
parent 11369018b6
commit b7cce1a0dc
No known key found for this signature in database
GPG Key ID: B4E3D7F11E793062
7 changed files with 140 additions and 5 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -57,6 +57,14 @@ export default class BaseCustomSidebarPanel {
return false;
}
get expandActiveSection() {
return false;
}
get scrollActiveLinkIntoView() {
return false;
}
/**
* @param {string} filter filter applied
*

View File

@ -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.
*/