fix(control-ui): link dashboard breadcrumb · openclaw/openclaw@1390ead
BunsDev
·
2026-04-29
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -160,6 +160,23 @@
|
160 | 160 | color: var(--muted); |
161 | 161 | } |
162 | 162 | |
| 163 | +.topnav-shell .dashboard-header__breadcrumb-link { |
| 164 | +display: inline-flex; |
| 165 | +align-items: center; |
| 166 | +margin: -2px -4px; |
| 167 | +padding: 2px 4px; |
| 168 | +border-radius: var(--radius-sm); |
| 169 | +text-decoration: none; |
| 170 | +white-space: nowrap; |
| 171 | +} |
| 172 | + |
| 173 | +.topnav-shell .dashboard-header__breadcrumb-link:hover, |
| 174 | +.topnav-shell .dashboard-header__breadcrumb-link:focus-visible { |
| 175 | +color: var(--text); |
| 176 | +text-decoration: underline; |
| 177 | +text-underline-offset: 3px; |
| 178 | +} |
| 179 | + |
163 | 180 | .topnav-shell .dashboard-header__breadcrumb-current { |
164 | 181 | color: var(--accent); |
165 | 182 | font-weight: 650; |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -117,7 +117,13 @@ import {
|
117 | 117 | import { buildExternalLinkRel, EXTERNAL_LINK_TARGET } from "./external-link.ts"; |
118 | 118 | import { icons } from "./icons.ts"; |
119 | 119 | import { createLazyView, renderLazyView } from "./lazy-view.ts"; |
120 | | -import { normalizeBasePath, TAB_GROUPS, subtitleForTab, titleForTab } from "./navigation.ts"; |
| 120 | +import { |
| 121 | +normalizeBasePath, |
| 122 | +TAB_GROUPS, |
| 123 | +subtitleForTab, |
| 124 | +titleForTab, |
| 125 | +type Tab, |
| 126 | +} from "./navigation.ts"; |
121 | 127 | import { isPluginEnabledInConfigSnapshot } from "./plugin-activation.ts"; |
122 | 128 | import "./components/dashboard-header.ts"; |
123 | 129 | import { |
@@ -1346,7 +1352,13 @@ export function renderApp(state: AppViewState) {
|
1346 | 1352 | <span class="nav-collapse-toggle__icon" aria-hidden="true">${icons.menu}</span> |
1347 | 1353 | </button> |
1348 | 1354 | <div class="topnav-shell__content"> |
1349 | | - <dashboard-header .tab=${state.tab}></dashboard-header> |
| 1355 | + <dashboard-header |
| 1356 | + .tab=${state.tab} |
| 1357 | + .basePath=${state.basePath} |
| 1358 | + @navigate=${(event: CustomEvent<Tab>) => { |
| 1359 | + state.setTab(event.detail); |
| 1360 | + }} |
| 1361 | + ></dashboard-header> |
1350 | 1362 | </div> |
1351 | 1363 | <div class="topnav-shell__actions"> |
1352 | 1364 | <button |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
1 | 1 | import { LitElement, html } from "lit"; |
2 | 2 | import { property } from "lit/decorators.js"; |
3 | | -import { titleForTab, type Tab } from "../navigation.js"; |
| 3 | +import { pathForTab, titleForTab, type Tab } from "../navigation.js"; |
4 | 4 | |
5 | 5 | export class DashboardHeader extends LitElement { |
6 | 6 | override createRenderRoot() { |
7 | 7 | return this; |
8 | 8 | } |
9 | 9 | |
10 | 10 | @property() tab: Tab = "overview"; |
| 11 | + @property() basePath = ""; |
| 12 | + |
| 13 | +private handleOverviewClick(event: MouseEvent) { |
| 14 | +if ( |
| 15 | +event.defaultPrevented || |
| 16 | +event.button !== 0 || |
| 17 | +event.metaKey || |
| 18 | +event.ctrlKey || |
| 19 | +event.shiftKey || |
| 20 | +event.altKey |
| 21 | +) { |
| 22 | +return; |
| 23 | +} |
| 24 | +event.preventDefault(); |
| 25 | +this.dispatchEvent( |
| 26 | +new CustomEvent("navigate", { detail: "overview", bubbles: true, composed: true }), |
| 27 | +); |
| 28 | +} |
11 | 29 | |
12 | 30 | override render() { |
13 | 31 | const label = titleForTab(this.tab); |
14 | 32 | |
15 | 33 | return html` |
16 | 34 | <div class="dashboard-header"> |
17 | 35 | <div class="dashboard-header__breadcrumb"> |
18 | | - <span |
| 36 | + <a |
19 | 37 | class="dashboard-header__breadcrumb-link" |
20 | | - @click=${() => |
21 | | - this.dispatchEvent( |
22 | | - new CustomEvent("navigate", { detail: "overview", bubbles: true, composed: true }), |
23 | | - )} |
| 38 | + href=${pathForTab("overview", this.basePath)} |
| 39 | + @click=${this.handleOverviewClick} |
24 | 40 | > |
25 | 41 | OpenClaw |
26 | | - </span> |
| 42 | + </a> |
27 | 43 | <span class="dashboard-header__breadcrumb-sep">›</span> |
28 | 44 | <span class="dashboard-header__breadcrumb-current">${label}</span> |
29 | 45 | </div> |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -48,6 +48,34 @@ describe("control UI routing", () => {
|
48 | 48 | expect(dreamsLink).not.toBeNull(); |
49 | 49 | }); |
50 | 50 | |
| 51 | +it("renders the dashboard breadcrumb as an overview link", async () => { |
| 52 | +const app = mountApp("/channels"); |
| 53 | +await app.updateComplete; |
| 54 | + |
| 55 | +const breadcrumb = app.querySelector<HTMLAnchorElement>( |
| 56 | +"dashboard-header .dashboard-header__breadcrumb-link", |
| 57 | +); |
| 58 | +expect(breadcrumb).toBeInstanceOf(HTMLAnchorElement); |
| 59 | +expect(breadcrumb?.getAttribute("href")).toBe("/overview"); |
| 60 | + |
| 61 | +breadcrumb?.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true })); |
| 62 | +await app.updateComplete; |
| 63 | + |
| 64 | +expect(app.tab).toBe("overview"); |
| 65 | +expect(window.location.pathname).toBe("/overview"); |
| 66 | +}); |
| 67 | + |
| 68 | +it("keeps the dashboard breadcrumb link inside the configured base path", async () => { |
| 69 | +const app = mountApp("/ui/channels"); |
| 70 | +await app.updateComplete; |
| 71 | + |
| 72 | +const breadcrumb = app.querySelector<HTMLAnchorElement>( |
| 73 | +"dashboard-header .dashboard-header__breadcrumb-link", |
| 74 | +); |
| 75 | +expect(breadcrumb).toBeInstanceOf(HTMLAnchorElement); |
| 76 | +expect(breadcrumb?.getAttribute("href")).toBe("/ui/overview"); |
| 77 | +}); |
| 78 | + |
51 | 79 | it("renders the dreaming view on the /dreaming route", async () => { |
52 | 80 | const app = mountApp("/dreaming"); |
53 | 81 | app.dreamingStatus = { |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。