mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:40:44 +00:00
fix(control-ui): link dashboard breadcrumb
Make the topbar OpenClaw breadcrumb a semantic Overview link, wire the existing navigate event at the app shell, and preserve prefixed Control UI base paths.\n\nValidation:\n- pnpm test ui/src/ui/navigation.browser.test.ts\n- pnpm exec oxfmt --check --threads=1 ui/src/ui/components/dashboard-header.ts ui/src/ui/app-render.ts ui/src/ui/navigation.browser.test.ts\n- git diff --check origin/main...HEAD
This commit is contained in:
@@ -160,6 +160,23 @@
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.topnav-shell .dashboard-header__breadcrumb-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin: -2px -4px;
|
||||
padding: 2px 4px;
|
||||
border-radius: var(--radius-sm);
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.topnav-shell .dashboard-header__breadcrumb-link:hover,
|
||||
.topnav-shell .dashboard-header__breadcrumb-link:focus-visible {
|
||||
color: var(--text);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.topnav-shell .dashboard-header__breadcrumb-current {
|
||||
color: var(--accent);
|
||||
font-weight: 650;
|
||||
|
||||
@@ -117,7 +117,13 @@ import {
|
||||
import { buildExternalLinkRel, EXTERNAL_LINK_TARGET } from "./external-link.ts";
|
||||
import { icons } from "./icons.ts";
|
||||
import { createLazyView, renderLazyView } from "./lazy-view.ts";
|
||||
import { normalizeBasePath, TAB_GROUPS, subtitleForTab, titleForTab } from "./navigation.ts";
|
||||
import {
|
||||
normalizeBasePath,
|
||||
TAB_GROUPS,
|
||||
subtitleForTab,
|
||||
titleForTab,
|
||||
type Tab,
|
||||
} from "./navigation.ts";
|
||||
import { isPluginEnabledInConfigSnapshot } from "./plugin-activation.ts";
|
||||
import "./components/dashboard-header.ts";
|
||||
import {
|
||||
@@ -1346,7 +1352,13 @@ export function renderApp(state: AppViewState) {
|
||||
<span class="nav-collapse-toggle__icon" aria-hidden="true">${icons.menu}</span>
|
||||
</button>
|
||||
<div class="topnav-shell__content">
|
||||
<dashboard-header .tab=${state.tab}></dashboard-header>
|
||||
<dashboard-header
|
||||
.tab=${state.tab}
|
||||
.basePath=${state.basePath}
|
||||
@navigate=${(event: CustomEvent<Tab>) => {
|
||||
state.setTab(event.detail);
|
||||
}}
|
||||
></dashboard-header>
|
||||
</div>
|
||||
<div class="topnav-shell__actions">
|
||||
<button
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
import { titleForTab, type Tab } from "../navigation.js";
|
||||
import { pathForTab, titleForTab, type Tab } from "../navigation.js";
|
||||
|
||||
export class DashboardHeader extends LitElement {
|
||||
override createRenderRoot() {
|
||||
@@ -8,6 +8,24 @@ export class DashboardHeader extends LitElement {
|
||||
}
|
||||
|
||||
@property() tab: Tab = "overview";
|
||||
@property() basePath = "";
|
||||
|
||||
private handleOverviewClick(event: MouseEvent) {
|
||||
if (
|
||||
event.defaultPrevented ||
|
||||
event.button !== 0 ||
|
||||
event.metaKey ||
|
||||
event.ctrlKey ||
|
||||
event.shiftKey ||
|
||||
event.altKey
|
||||
) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("navigate", { detail: "overview", bubbles: true, composed: true }),
|
||||
);
|
||||
}
|
||||
|
||||
override render() {
|
||||
const label = titleForTab(this.tab);
|
||||
@@ -15,15 +33,13 @@ export class DashboardHeader extends LitElement {
|
||||
return html`
|
||||
<div class="dashboard-header">
|
||||
<div class="dashboard-header__breadcrumb">
|
||||
<span
|
||||
<a
|
||||
class="dashboard-header__breadcrumb-link"
|
||||
@click=${() =>
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("navigate", { detail: "overview", bubbles: true, composed: true }),
|
||||
)}
|
||||
href=${pathForTab("overview", this.basePath)}
|
||||
@click=${this.handleOverviewClick}
|
||||
>
|
||||
OpenClaw
|
||||
</span>
|
||||
</a>
|
||||
<span class="dashboard-header__breadcrumb-sep">›</span>
|
||||
<span class="dashboard-header__breadcrumb-current">${label}</span>
|
||||
</div>
|
||||
|
||||
@@ -48,6 +48,34 @@ describe("control UI routing", () => {
|
||||
expect(dreamsLink).not.toBeNull();
|
||||
});
|
||||
|
||||
it("renders the dashboard breadcrumb as an overview link", async () => {
|
||||
const app = mountApp("/channels");
|
||||
await app.updateComplete;
|
||||
|
||||
const breadcrumb = app.querySelector<HTMLAnchorElement>(
|
||||
"dashboard-header .dashboard-header__breadcrumb-link",
|
||||
);
|
||||
expect(breadcrumb).toBeInstanceOf(HTMLAnchorElement);
|
||||
expect(breadcrumb?.getAttribute("href")).toBe("/overview");
|
||||
|
||||
breadcrumb?.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
|
||||
await app.updateComplete;
|
||||
|
||||
expect(app.tab).toBe("overview");
|
||||
expect(window.location.pathname).toBe("/overview");
|
||||
});
|
||||
|
||||
it("keeps the dashboard breadcrumb link inside the configured base path", async () => {
|
||||
const app = mountApp("/ui/channels");
|
||||
await app.updateComplete;
|
||||
|
||||
const breadcrumb = app.querySelector<HTMLAnchorElement>(
|
||||
"dashboard-header .dashboard-header__breadcrumb-link",
|
||||
);
|
||||
expect(breadcrumb).toBeInstanceOf(HTMLAnchorElement);
|
||||
expect(breadcrumb?.getAttribute("href")).toBe("/ui/overview");
|
||||
});
|
||||
|
||||
it("renders the dreaming view on the /dreaming route", async () => {
|
||||
const app = mountApp("/dreaming");
|
||||
app.dreamingStatus = {
|
||||
|
||||
Reference in New Issue
Block a user