mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
improve(ui): move shell controls into side rails
This commit is contained in:
parent
64015e71dd
commit
8295a12a83
55 changed files with 755 additions and 548 deletions
|
|
@ -15,7 +15,7 @@ import "../components/terminal/terminal-panel.ts";
|
|||
import "../components/tooltip.ts";
|
||||
import "../components/update-banner.ts";
|
||||
import type { SidebarNavRoute } from "../app-navigation.ts";
|
||||
import { APP_ROUTE_IDS, isRouteId, pathForRoute, type RouteId } from "../app-routes.ts";
|
||||
import { APP_ROUTE_IDS, isRouteId, type RouteId } from "../app-routes.ts";
|
||||
import {
|
||||
COMMAND_PALETTE_TARGET_EVENT,
|
||||
type CommandPalette,
|
||||
|
|
@ -27,8 +27,6 @@ import { copyToClipboard } from "../lib/clipboard.ts";
|
|||
import { isGatewayMethodAdvertised } from "../lib/gateway-methods.ts";
|
||||
import { isWorkboardEnabledInConfigSnapshot } from "../lib/plugin-activation.ts";
|
||||
import { searchForSession } from "../lib/sessions/index.ts";
|
||||
import { resolveAgentIdFromSessionKey } from "../lib/sessions/session-key.ts";
|
||||
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "../lib/string-coerce.ts";
|
||||
import { renderDevicePairSetup } from "../pages/nodes/view-pairing.ts";
|
||||
import { pluginTabKey, pluginTabRefFromSearch } from "../pages/plugin/route.ts";
|
||||
import { bootstrapApplication, type ApplicationRuntime } from "./bootstrap.ts";
|
||||
|
|
@ -70,18 +68,6 @@ function equalShellRouteState(previous: ShellRouteState, next: ShellRouteState):
|
|||
);
|
||||
}
|
||||
|
||||
function resolveAgentLabel(sessionKey: string, agentsList: AgentsListResult | null): string {
|
||||
const agentId = resolveAgentIdFromSessionKey(sessionKey);
|
||||
const agent = agentsList?.agents.find(
|
||||
(entry) => normalizeLowercaseStringOrEmpty(entry.id) === agentId,
|
||||
);
|
||||
return (
|
||||
normalizeOptionalString(agent?.identity?.name) ??
|
||||
normalizeOptionalString(agent?.name) ??
|
||||
agentId
|
||||
);
|
||||
}
|
||||
|
||||
function resolveOnboardingMode(): boolean {
|
||||
const raw = new URLSearchParams(globalThis.location?.search ?? "").get("onboarding");
|
||||
return raw !== null && /^(?:1|true|yes|on)$/iu.test(raw.trim());
|
||||
|
|
@ -123,10 +109,14 @@ function isTerminalAvailable(
|
|||
}
|
||||
return (
|
||||
hasOperatorAdminAccess(snapshot.hello?.auth ?? null) &&
|
||||
isGatewayMethodAdvertised(snapshot, "terminal.open") === true
|
||||
isGatewayMethodAdvertised(snapshot, "terminal.open")
|
||||
);
|
||||
}
|
||||
|
||||
function isMobileNavLayout(): boolean {
|
||||
return globalThis.matchMedia?.("(max-width: 1100px)").matches ?? false;
|
||||
}
|
||||
|
||||
class OpenClawApp extends LitElement {
|
||||
@state() private gatewayConnected = false;
|
||||
@state() private gatewayReconnecting = false;
|
||||
|
|
@ -376,7 +366,6 @@ class OpenClawShell extends LitElement {
|
|||
@state() private terminalAvailable = false;
|
||||
@state() private terminalClient: GatewayBrowserClient | null = null;
|
||||
@state() private activeSessionKey = "";
|
||||
@state() private agentLabel = "";
|
||||
@state() private routeState: ShellRouteState = {};
|
||||
@state() private overlaySnapshot: ApplicationOverlaySnapshot = {
|
||||
updateAvailable: null,
|
||||
|
|
@ -396,7 +385,6 @@ class OpenClawShell extends LitElement {
|
|||
private navDrawerTrigger: HTMLElement | null = null;
|
||||
private agentsListClient: GatewayBrowserClient | null = null;
|
||||
private sessionKeyClient: GatewayBrowserClient | null = null;
|
||||
private stopAgentsSubscription: (() => void) | undefined;
|
||||
private stopConfigSubscription: (() => void) | undefined;
|
||||
private stopGatewaySubscription: (() => void) | undefined;
|
||||
private stopNavigationSubscription: (() => void) | undefined;
|
||||
|
|
@ -413,6 +401,7 @@ class OpenClawShell extends LitElement {
|
|||
super.connectedCallback();
|
||||
this.startSubscriptions();
|
||||
this.addEventListener(COMMAND_PALETTE_TARGET_EVENT, this.handleCommandPaletteTarget);
|
||||
document.addEventListener("keydown", this.handleDocumentKeydown);
|
||||
}
|
||||
|
||||
override updated() {
|
||||
|
|
@ -425,7 +414,6 @@ class OpenClawShell extends LitElement {
|
|||
if (
|
||||
!runtime ||
|
||||
!context ||
|
||||
this.stopAgentsSubscription ||
|
||||
this.stopConfigSubscription ||
|
||||
this.stopGatewaySubscription ||
|
||||
this.stopNavigationSubscription ||
|
||||
|
|
@ -443,13 +431,11 @@ class OpenClawShell extends LitElement {
|
|||
this.updateGatewaySessionKey(context.gateway.snapshot);
|
||||
this.updateGatewayStatus(context.gateway.snapshot);
|
||||
this.updateTerminalSurface(context.gateway.snapshot);
|
||||
this.updateAgentLabel();
|
||||
this.ensureRuntimeConfig(context.gateway.snapshot);
|
||||
this.stopGatewaySubscription = context.gateway.subscribe((snapshot) => {
|
||||
this.updateGatewaySessionKey(snapshot);
|
||||
this.updateGatewayStatus(snapshot);
|
||||
this.updateTerminalSurface(snapshot);
|
||||
this.updateAgentLabel();
|
||||
this.ensureAgentsList(snapshot);
|
||||
this.ensureRuntimeConfig(snapshot);
|
||||
});
|
||||
|
|
@ -457,9 +443,6 @@ class OpenClawShell extends LitElement {
|
|||
this.updateTerminalSurface(context.gateway.snapshot);
|
||||
});
|
||||
this.stopThemeSubscription = context.theme.subscribe(() => this.requestUpdate());
|
||||
this.stopAgentsSubscription = context.agents.subscribe(() => {
|
||||
this.updateAgentLabel();
|
||||
});
|
||||
this.updateRouteState(selectShellRouteState(runtime.router.getState()));
|
||||
this.stopRouteSubscription = runtime.router.subscribeSelector(
|
||||
selectShellRouteState,
|
||||
|
|
@ -480,8 +463,7 @@ class OpenClawShell extends LitElement {
|
|||
|
||||
override disconnectedCallback() {
|
||||
this.removeEventListener(COMMAND_PALETTE_TARGET_EVENT, this.handleCommandPaletteTarget);
|
||||
this.stopAgentsSubscription?.();
|
||||
this.stopAgentsSubscription = undefined;
|
||||
document.removeEventListener("keydown", this.handleDocumentKeydown);
|
||||
this.stopConfigSubscription?.();
|
||||
this.stopConfigSubscription = undefined;
|
||||
this.stopGatewaySubscription?.();
|
||||
|
|
@ -533,13 +515,23 @@ class OpenClawShell extends LitElement {
|
|||
this.context?.replace("chat", this.chatNavigationOptions());
|
||||
}
|
||||
|
||||
private toggleNavDrawer(trigger: HTMLElement) {
|
||||
if (this.navDrawerOpen) {
|
||||
this.closeNavDrawer({ restoreFocus: true });
|
||||
private toggleNavigationSurface(trigger?: HTMLElement) {
|
||||
const context = this.context;
|
||||
if (!context || this.onboarding) {
|
||||
return;
|
||||
}
|
||||
this.navDrawerTrigger = trigger;
|
||||
this.navDrawerOpen = true;
|
||||
if (isMobileNavLayout()) {
|
||||
if (this.navDrawerOpen) {
|
||||
this.closeNavDrawer({ restoreFocus: Boolean(trigger) });
|
||||
return;
|
||||
}
|
||||
this.navDrawerTrigger = trigger ?? null;
|
||||
this.navDrawerOpen = true;
|
||||
return;
|
||||
}
|
||||
context.navigation.update({
|
||||
navCollapsed: !this.navCollapsed,
|
||||
});
|
||||
}
|
||||
|
||||
private closeNavDrawer(options: { restoreFocus?: boolean } = {}) {
|
||||
|
|
@ -564,6 +556,21 @@ class OpenClawShell extends LitElement {
|
|||
this.closeNavDrawer({ restoreFocus: true });
|
||||
};
|
||||
|
||||
private readonly handleDocumentKeydown = (event: KeyboardEvent) => {
|
||||
if (
|
||||
event.defaultPrevented ||
|
||||
event.altKey ||
|
||||
event.shiftKey ||
|
||||
!event.metaKey ||
|
||||
event.ctrlKey ||
|
||||
event.key.toLowerCase() !== "b"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
this.toggleNavigationSurface();
|
||||
};
|
||||
|
||||
private readonly openPalette = () => {
|
||||
this.commandPalette?.openPalette();
|
||||
};
|
||||
|
|
@ -676,21 +683,9 @@ class OpenClawShell extends LitElement {
|
|||
const sessionKey = new URLSearchParams(routeState.location?.search).get("session")?.trim();
|
||||
if (sessionKey) {
|
||||
this.activeSessionKey = sessionKey;
|
||||
this.updateAgentLabel();
|
||||
}
|
||||
}
|
||||
|
||||
private updateAgentLabel() {
|
||||
const context = this.context;
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
this.agentLabel = resolveAgentLabel(
|
||||
this.activeSessionKey || context.gateway.snapshot.sessionKey,
|
||||
context.agents.state.agentsList,
|
||||
);
|
||||
}
|
||||
|
||||
private readonly updateNavigationPreferences = (
|
||||
snapshot: ApplicationRuntime["context"]["navigation"]["snapshot"],
|
||||
) => {
|
||||
|
|
@ -740,23 +735,12 @@ class OpenClawShell extends LitElement {
|
|||
@click=${() => this.closeNavDrawer({ restoreFocus: true })}
|
||||
></button>
|
||||
<openclaw-app-topbar
|
||||
.routeId=${activeRoute}
|
||||
.basePath=${context.basePath}
|
||||
.agentLabel=${this.agentLabel}
|
||||
.overviewHref=${pathForRoute("overview", context.basePath)}
|
||||
.searchDisabled=${false}
|
||||
.navDrawerOpen=${navDrawerOpen}
|
||||
.navCollapsed=${navCollapsed}
|
||||
.onboarding=${this.onboarding}
|
||||
.terminalAvailable=${this.terminalAvailable}
|
||||
.onToggleTerminal=${() =>
|
||||
window.dispatchEvent(new CustomEvent("openclaw:terminal-toggle"))}
|
||||
.onToggleDrawer=${(trigger: HTMLElement) => this.toggleNavDrawer(trigger)}
|
||||
.onToggleCollapse=${() =>
|
||||
context.navigation.update({
|
||||
navCollapsed: !navCollapsed,
|
||||
})}
|
||||
.onNavigate=${(routeId: string, options?: ApplicationNavigationOptions) =>
|
||||
this.navigate(routeId, options)}
|
||||
.onOpenPalette=${this.openPalette}
|
||||
.onToggleDrawer=${(trigger: HTMLElement) => this.toggleNavigationSurface(trigger)}
|
||||
></openclaw-app-topbar>
|
||||
<div class="shell-nav">
|
||||
<openclaw-app-sidebar
|
||||
|
|
@ -773,6 +757,7 @@ class OpenClawShell extends LitElement {
|
|||
.sidebarMoreExpanded=${this.sidebarMoreExpanded}
|
||||
.themeMode=${context.theme.mode}
|
||||
.onOpenPalette=${this.openPalette}
|
||||
.onToggleSidebar=${() => this.toggleNavigationSurface()}
|
||||
.onToggleMore=${() =>
|
||||
context.navigation.update({
|
||||
sidebarMoreExpanded: !context.navigation.snapshot.sidebarMoreExpanded,
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ type SidebarRecentSession = {
|
|||
meta: string;
|
||||
href: string;
|
||||
active: boolean;
|
||||
visuallyActive: boolean;
|
||||
hasActiveRun: boolean;
|
||||
kind?: string;
|
||||
pinned: boolean;
|
||||
|
|
@ -113,6 +114,14 @@ const SIDEBAR_SESSION_SORT_OPTIONS = [
|
|||
labelKey: "chat.sidebar.sortCreated" | "chat.sidebar.sortUpdated";
|
||||
}>;
|
||||
|
||||
function formatSidebarTimestamp(timestampMs: number | null | undefined): string {
|
||||
const value = formatRelativeTimestamp(timestampMs, { fallback: "" });
|
||||
if (value === "just now") {
|
||||
return "now";
|
||||
}
|
||||
return value.endsWith(" ago") ? value.slice(0, -" ago".length) : value;
|
||||
}
|
||||
|
||||
function shouldHandleNavigationClick(event: MouseEvent): boolean {
|
||||
return (
|
||||
!event.defaultPrevented &&
|
||||
|
|
@ -142,6 +151,7 @@ class AppSidebar extends LitElement {
|
|||
@property({ attribute: false }) sidebarMoreExpanded = false;
|
||||
@property({ attribute: false }) themeMode: ThemeMode = "system";
|
||||
@property({ attribute: false }) onOpenPalette?: () => void;
|
||||
@property({ attribute: false }) onToggleSidebar?: () => void;
|
||||
@property({ attribute: false }) onToggleMore?: () => void;
|
||||
@property({ attribute: false }) onUpdatePinnedRoutes?: (routes: SidebarNavRoute[]) => void;
|
||||
@property({ attribute: false }) onPairMobile?: () => void;
|
||||
|
|
@ -278,6 +288,37 @@ class AppSidebar extends LitElement {
|
|||
this.gatewayClient = client;
|
||||
}
|
||||
|
||||
private renderBrand() {
|
||||
const collapseLabel = this.collapsed ? t("nav.expand") : t("nav.collapse");
|
||||
const collapseTooltip = `${collapseLabel} (⌘B)`;
|
||||
return html`
|
||||
<div class="sidebar-brand">
|
||||
<div class="sidebar-brand__identity">
|
||||
<img
|
||||
class="sidebar-brand__logo"
|
||||
src=${controlUiPublicAssetPath("apple-touch-icon.png", this.basePath)}
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
/>
|
||||
${this.collapsed ? nothing : html`<span class="sidebar-brand__title">OpenClaw</span>`}
|
||||
</div>
|
||||
<div class="sidebar-brand__actions">
|
||||
<openclaw-tooltip .content=${collapseTooltip}>
|
||||
<button
|
||||
class="sidebar-brand__icon"
|
||||
type="button"
|
||||
@click=${() => this.onToggleSidebar?.()}
|
||||
aria-label=${collapseLabel}
|
||||
aria-expanded=${String(!this.collapsed)}
|
||||
>
|
||||
${this.collapsed ? icons.panelLeftOpen : icons.panelLeftClose}
|
||||
</button>
|
||||
</openclaw-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private getRouteSessionKey(): string {
|
||||
return this.sessionKey.trim() || this.context?.gateway.snapshot.sessionKey.trim() || "";
|
||||
}
|
||||
|
|
@ -321,12 +362,14 @@ class AppSidebar extends LitElement {
|
|||
hello: context?.gateway.snapshot.hello,
|
||||
compareSessions: this.compareSidebarSessionRows,
|
||||
});
|
||||
const highlightCurrentSession = this.activeRouteId === "chat";
|
||||
const toSidebarSession = (row: SessionsListResult["sessions"][number]) => ({
|
||||
key: row.key,
|
||||
label: resolveSessionDisplayName(row.key, row),
|
||||
meta: row.updatedAt ? formatRelativeTimestamp(row.updatedAt) : "",
|
||||
meta: formatSidebarTimestamp(row.updatedAt),
|
||||
href: `${pathForRoute("chat", context?.basePath ?? "")}${searchForSession(row.key)}`,
|
||||
active: row.key === navigation.activeRowKey,
|
||||
visuallyActive: highlightCurrentSession && row.key === navigation.currentSessionKey,
|
||||
hasActiveRun: Boolean(row.hasActiveRun),
|
||||
kind: row.kind,
|
||||
pinned: row.pinned === true,
|
||||
|
|
@ -1211,7 +1254,7 @@ class AppSidebar extends LitElement {
|
|||
const rowClass = [
|
||||
"sidebar-recent-session",
|
||||
"session-row-host",
|
||||
session.active ? "sidebar-recent-session--active" : "",
|
||||
session.visuallyActive ? "sidebar-recent-session--active" : "",
|
||||
session.pinned ? "session-row-host--pinned" : "",
|
||||
session.hasActiveRun ? "session-row-host--running" : "",
|
||||
this.draggingSessionKey === session.key ? "sidebar-recent-session--dragging" : "",
|
||||
|
|
@ -1454,7 +1497,7 @@ class AppSidebar extends LitElement {
|
|||
@click=${(event: MouseEvent) => {
|
||||
const trigger = event.currentTarget as HTMLElement;
|
||||
const rect = trigger.getBoundingClientRect();
|
||||
this.openSessionSortMenu(rect.right - 180, rect.bottom + 4, trigger);
|
||||
this.openSessionSortMenu(rect.right, rect.bottom + 4, trigger);
|
||||
}}
|
||||
>
|
||||
${icons.listFilter}
|
||||
|
|
@ -1621,6 +1664,7 @@ class AppSidebar extends LitElement {
|
|||
alt="OpenClaw"
|
||||
/>
|
||||
<div class="sidebar-shell">
|
||||
${this.renderBrand()}
|
||||
<div class="sidebar-shell__body">
|
||||
${this.renderSearch()}
|
||||
<nav class="sidebar-nav" @contextmenu=${this.openCustomizeMenuFromContext}>
|
||||
|
|
@ -1667,9 +1711,7 @@ class AppSidebar extends LitElement {
|
|||
${icons.settings}
|
||||
</a>
|
||||
</openclaw-tooltip>
|
||||
<openclaw-tooltip
|
||||
.content=${t("chat.docsOpensInNewTab", { label: t("common.docs") })}
|
||||
>
|
||||
<openclaw-tooltip .content=${t("chat.docsTooltip")}>
|
||||
<a
|
||||
class="sidebar-footer-icon"
|
||||
href="https://docs.openclaw.ai"
|
||||
|
|
|
|||
|
|
@ -1,87 +1,29 @@
|
|||
import { LitElement, html, nothing } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
import type { NavigationRouteId } from "../app-navigation.ts";
|
||||
import "./dashboard-header.ts";
|
||||
import "./tooltip.ts";
|
||||
import { controlUiPublicAssetPath } from "../app/public-assets.ts";
|
||||
import { t } from "../i18n/index.ts";
|
||||
import { icons } from "./icons.ts";
|
||||
|
||||
// Mirrors the layout.mobile.css breakpoint where the sidebar becomes a
|
||||
// slide-over drawer; the one topbar toggle switches behavior there.
|
||||
const NAV_DRAWER_MEDIA_QUERY = "(max-width: 1100px)";
|
||||
import "./tooltip.ts";
|
||||
|
||||
class AppTopbar extends LitElement {
|
||||
override createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@property({ attribute: false }) routeId?: NavigationRouteId;
|
||||
@property({ attribute: false }) basePath = "";
|
||||
@property({ attribute: false }) agentLabel = "";
|
||||
@property({ attribute: false }) navDrawerOpen = false;
|
||||
@property({ attribute: false }) navCollapsed = false;
|
||||
@property({ attribute: false }) onboarding = false;
|
||||
@property({ attribute: false }) basePath = "";
|
||||
@property({ attribute: false }) onToggleDrawer?: (trigger: HTMLElement) => void;
|
||||
@property({ attribute: false }) onToggleCollapse?: () => void;
|
||||
@property({ attribute: false }) onToggleTerminal?: () => void;
|
||||
@property({ attribute: false }) onNavigate?: (routeId: NavigationRouteId) => void;
|
||||
@property({ attribute: false }) overviewHref = "";
|
||||
@property({ attribute: false }) terminalAvailable = false;
|
||||
|
||||
private drawerMedia?: MediaQueryList;
|
||||
private drawerMediaCleanup?: () => void;
|
||||
@property({ attribute: false }) onOpenPalette?: () => void;
|
||||
@property({ attribute: false }) searchDisabled = false;
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.style.display = "contents";
|
||||
if (typeof globalThis.matchMedia === "function") {
|
||||
const media = globalThis.matchMedia(NAV_DRAWER_MEDIA_QUERY);
|
||||
this.drawerMedia = media;
|
||||
// Older WebViews expose only the legacy addListener API (see the same
|
||||
// pattern in app/bootstrap.ts).
|
||||
if (typeof media.addEventListener === "function") {
|
||||
media.addEventListener("change", this.handleDrawerMediaChange);
|
||||
this.drawerMediaCleanup = () =>
|
||||
media.removeEventListener("change", this.handleDrawerMediaChange);
|
||||
} else if (typeof media.addListener === "function") {
|
||||
media.addListener(this.handleDrawerMediaChange);
|
||||
this.drawerMediaCleanup = () => media.removeListener(this.handleDrawerMediaChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override disconnectedCallback() {
|
||||
this.drawerMediaCleanup?.();
|
||||
this.drawerMediaCleanup = undefined;
|
||||
this.drawerMedia = undefined;
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
private readonly handleDrawerMediaChange = () => {
|
||||
this.requestUpdate();
|
||||
};
|
||||
|
||||
private get drawerMode(): boolean {
|
||||
return this.drawerMedia?.matches ?? false;
|
||||
}
|
||||
|
||||
private readonly handleNavigate = (event: CustomEvent<NavigationRouteId>) => {
|
||||
this.onNavigate?.(event.detail);
|
||||
};
|
||||
|
||||
private readonly handleToggleSidebar = (event: MouseEvent) => {
|
||||
if (this.drawerMode) {
|
||||
this.onToggleDrawer?.(event.currentTarget as HTMLElement);
|
||||
} else {
|
||||
this.onToggleCollapse?.();
|
||||
}
|
||||
};
|
||||
|
||||
override render() {
|
||||
// One toggle, viewport-dependent behavior: it drives the persistent rail
|
||||
// collapse on desktop and the slide-over drawer at drawer breakpoints.
|
||||
const sidebarOpen = this.drawerMode ? this.navDrawerOpen : !this.navCollapsed;
|
||||
const toggleLabel = sidebarOpen ? t("nav.collapse") : t("nav.expand");
|
||||
const drawerLabel = this.navDrawerOpen ? t("nav.collapse") : t("nav.expand");
|
||||
return html`
|
||||
<header
|
||||
class="topbar"
|
||||
|
|
@ -89,41 +31,40 @@ class AppTopbar extends LitElement {
|
|||
aria-hidden=${this.onboarding ? "true" : nothing}
|
||||
>
|
||||
<div class="topnav-shell">
|
||||
<openclaw-tooltip .content=${toggleLabel}>
|
||||
<openclaw-tooltip .content=${drawerLabel}>
|
||||
<button
|
||||
type="button"
|
||||
class="topbar-icon-btn topbar-nav-toggle"
|
||||
@click=${this.handleToggleSidebar}
|
||||
aria-label=${toggleLabel}
|
||||
aria-expanded=${String(sidebarOpen)}
|
||||
class="sidebar-menu-trigger topbar-nav-toggle"
|
||||
@click=${(event: MouseEvent) =>
|
||||
this.onToggleDrawer?.(event.currentTarget as HTMLElement)}
|
||||
aria-label=${drawerLabel}
|
||||
aria-expanded=${String(this.navDrawerOpen)}
|
||||
>
|
||||
${icons.panelLeft}
|
||||
<span class="nav-collapse-toggle__icon" aria-hidden="true">${icons.menu}</span>
|
||||
</button>
|
||||
</openclaw-tooltip>
|
||||
<div class="topnav-shell__content">
|
||||
<dashboard-header
|
||||
.routeId=${this.routeId}
|
||||
.basePath=${this.basePath}
|
||||
.agentLabel=${this.agentLabel}
|
||||
.overviewHref=${this.overviewHref}
|
||||
@navigate=${this.handleNavigate}
|
||||
></dashboard-header>
|
||||
<div class="topbar-brand" aria-label="OpenClaw">
|
||||
<img
|
||||
class="topbar-brand__logo"
|
||||
src=${controlUiPublicAssetPath("apple-touch-icon.png", this.basePath)}
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span class="topbar-brand__title">OpenClaw</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="topnav-shell__actions">
|
||||
${this.terminalAvailable
|
||||
? html`
|
||||
<openclaw-tooltip .content=${t("terminal.toggle")}>
|
||||
<button
|
||||
class="topbar-icon-btn"
|
||||
type="button"
|
||||
@click=${() => this.onToggleTerminal?.()}
|
||||
aria-label=${t("terminal.toggle")}
|
||||
>
|
||||
${icons.terminal}
|
||||
</button>
|
||||
</openclaw-tooltip>
|
||||
`
|
||||
: nothing}
|
||||
<openclaw-tooltip .content=${t("chat.commandPaletteTitle")}>
|
||||
<button
|
||||
class="topbar-search"
|
||||
?disabled=${this.searchDisabled || !this.onOpenPalette}
|
||||
@click=${() => this.onOpenPalette?.()}
|
||||
aria-label=${t("chat.openCommandPalette")}
|
||||
>
|
||||
${icons.search}
|
||||
</button>
|
||||
</openclaw-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
|||
|
|
@ -1,76 +0,0 @@
|
|||
// Control UI component implements the dashboard header element.
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
import { titleForRoute, type NavigationRouteId } from "../app-navigation.ts";
|
||||
|
||||
class DashboardHeader extends LitElement {
|
||||
override createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@property() routeId?: NavigationRouteId;
|
||||
@property() basePath = "";
|
||||
@property() agentLabel = "";
|
||||
@property() overviewHref = "";
|
||||
|
||||
private readonly 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 = this.routeId ? titleForRoute(this.routeId) : "";
|
||||
const rawAgentLabel = this.agentLabel.trim();
|
||||
// Skip the agent crumb when it repeats the brand crumb ("OpenClaw › OpenClaw › …").
|
||||
const agentLabel = rawAgentLabel.toLowerCase() === "openclaw" ? "" : rawAgentLabel;
|
||||
|
||||
return html`
|
||||
<div class="dashboard-header">
|
||||
<div class="dashboard-header__breadcrumb">
|
||||
${this.overviewHref
|
||||
? html`
|
||||
<a
|
||||
class="dashboard-header__breadcrumb-link"
|
||||
href=${this.overviewHref}
|
||||
@click=${this.handleOverviewClick}
|
||||
>
|
||||
OpenClaw
|
||||
</a>
|
||||
`
|
||||
: html`<span class="dashboard-header__breadcrumb-link">OpenClaw</span>`}
|
||||
${agentLabel
|
||||
? html`
|
||||
<span class="dashboard-header__breadcrumb-segment">
|
||||
<span class="dashboard-header__breadcrumb-sep">›</span>
|
||||
<span class="dashboard-header__breadcrumb-context" title=${agentLabel}>
|
||||
${agentLabel}
|
||||
</span>
|
||||
</span>
|
||||
`
|
||||
: nothing}
|
||||
<span class="dashboard-header__breadcrumb-sep">›</span>
|
||||
<span class="dashboard-header__breadcrumb-current">${label}</span>
|
||||
</div>
|
||||
<div class="dashboard-header__actions">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!customElements.get("dashboard-header")) {
|
||||
customElements.define("dashboard-header", DashboardHeader);
|
||||
}
|
||||
|
|
@ -20,6 +20,10 @@ const DOCK_BOTTOM_GLYPH = svg`<svg viewBox="0 0 16 16" width="13" height="13" fi
|
|||
const DOCK_RIGHT_GLYPH = svg`<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.3"><rect x="2" y="2.5" width="12" height="11" rx="1.5" /><path d="M10 2.5v11" /></svg>`;
|
||||
|
||||
type TerminalDock = "bottom" | "right";
|
||||
type TerminalToggleDetail = {
|
||||
dock?: TerminalDock;
|
||||
open?: boolean;
|
||||
};
|
||||
|
||||
type PanelLayout = {
|
||||
open: boolean;
|
||||
|
|
@ -148,7 +152,7 @@ export class OpenClawTerminalPanel extends LitElement {
|
|||
private connection: TerminalConnection | null = null;
|
||||
private tabSeq = 0;
|
||||
private readonly onGlobalKeyDown = (event: KeyboardEvent) => this.handleGlobalKey(event);
|
||||
private readonly onToggleRequest = () => this.toggle();
|
||||
private readonly onToggleRequest = (event: Event) => this.handleToggleRequest(event);
|
||||
// Re-clamp a dock sized on a larger window so the header/resizer never end
|
||||
// up off-screen after the viewport shrinks (e.g. rotate, window resize).
|
||||
private readonly onViewportResize = () => {
|
||||
|
|
@ -275,13 +279,37 @@ export class OpenClawTerminalPanel extends LitElement {
|
|||
this.closePanel();
|
||||
} else {
|
||||
this.open = true;
|
||||
this.syncLayoutReservation();
|
||||
this.persistLayout();
|
||||
void this.restoreSessions();
|
||||
}
|
||||
}
|
||||
|
||||
private handleToggleRequest(event: Event): void {
|
||||
const detail =
|
||||
event instanceof CustomEvent && typeof event.detail === "object" && event.detail !== null
|
||||
? (event.detail as TerminalToggleDetail)
|
||||
: null;
|
||||
const dock = detail?.dock === "right" || detail?.dock === "bottom" ? detail.dock : null;
|
||||
if (dock) {
|
||||
this.dock = dock;
|
||||
}
|
||||
if (detail?.open === true) {
|
||||
if (!this.available) {
|
||||
return;
|
||||
}
|
||||
this.open = true;
|
||||
this.syncLayoutReservation();
|
||||
this.persistLayout();
|
||||
void this.restoreSessions();
|
||||
return;
|
||||
}
|
||||
this.toggle();
|
||||
}
|
||||
|
||||
private closePanel(): void {
|
||||
this.open = false;
|
||||
this.syncLayoutReservation();
|
||||
this.persistLayout();
|
||||
}
|
||||
|
||||
|
|
@ -599,6 +627,7 @@ export class OpenClawTerminalPanel extends LitElement {
|
|||
|
||||
private setDock(dock: TerminalDock): void {
|
||||
this.dock = dock;
|
||||
this.syncLayoutReservation();
|
||||
this.persistLayout();
|
||||
void this.updateComplete.then(() => {
|
||||
for (const tab of this.tabs) {
|
||||
|
|
|
|||
|
|
@ -22,13 +22,21 @@ class ThemeModeToggle extends LitElement {
|
|||
this.style.display = "contents";
|
||||
}
|
||||
|
||||
private readonly handleModeChange = (mode: ThemeMode, event: Event) => {
|
||||
if (mode === this.mode) {
|
||||
return;
|
||||
private readonly nextMode = (): ThemeMode => {
|
||||
switch (this.mode) {
|
||||
case "system":
|
||||
return "light";
|
||||
case "light":
|
||||
return "dark";
|
||||
case "dark":
|
||||
return "system";
|
||||
}
|
||||
};
|
||||
|
||||
private readonly handleModeChange = (event: Event) => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent<ThemeModeChangeDetail>("theme-change", {
|
||||
detail: { mode, element: event.currentTarget as HTMLElement },
|
||||
detail: { mode: this.nextMode(), element: event.currentTarget as HTMLElement },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
|
|
@ -36,38 +44,26 @@ class ThemeModeToggle extends LitElement {
|
|||
};
|
||||
|
||||
override render() {
|
||||
const options: Array<{ id: ThemeMode; labelKey: string }> = [
|
||||
{ id: "system", labelKey: "common.system" },
|
||||
{ id: "light", labelKey: "common.light" },
|
||||
{ id: "dark", labelKey: "common.dark" },
|
||||
];
|
||||
const labelKey =
|
||||
this.mode === "system"
|
||||
? "common.system"
|
||||
: this.mode === "light"
|
||||
? "common.light"
|
||||
: "common.dark";
|
||||
const label = t(labelKey);
|
||||
const tooltip = t("common.colorModeOption", { mode: label });
|
||||
|
||||
return html`
|
||||
<div class="theme-mode-toggle" role="group" aria-label=${t("common.colorMode")}>
|
||||
${options.map((option) => {
|
||||
const label = t(option.labelKey);
|
||||
const tooltip = t("common.colorModeOption", { mode: label });
|
||||
return html`
|
||||
<openclaw-tooltip .content=${tooltip}>
|
||||
<button
|
||||
type="button"
|
||||
class="theme-mode-toggle__btn ${option.id === this.mode
|
||||
? "theme-mode-toggle__btn--active"
|
||||
: ""}"
|
||||
aria-label=${tooltip}
|
||||
aria-pressed=${option.id === this.mode}
|
||||
@click=${(event: Event) => this.handleModeChange(option.id, event)}
|
||||
>
|
||||
${option.id === "system"
|
||||
? icons.monitor
|
||||
: option.id === "light"
|
||||
? icons.sun
|
||||
: icons.moon}
|
||||
</button>
|
||||
</openclaw-tooltip>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
<openclaw-tooltip .content=${tooltip}>
|
||||
<button
|
||||
type="button"
|
||||
class="theme-mode-toggle"
|
||||
aria-label=${tooltip}
|
||||
@click=${this.handleModeChange}
|
||||
>
|
||||
${this.mode === "system" ? icons.monitor : this.mode === "light" ? icons.sun : icons.moon}
|
||||
</button>
|
||||
</openclaw-tooltip>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ describeControlUiE2e("Control UI sidebar customization mocked Gateway E2E", () =
|
|||
const sidebar = page.locator("openclaw-app-sidebar");
|
||||
const pinnedItems = sidebar.locator(".sidebar-nav > .nav-section__items > .nav-item");
|
||||
await expect.poll(() => trimmedTextContents(pinnedItems)).toEqual(["Overview"]);
|
||||
await expect.poll(() => sidebar.locator(".sidebar-brand").count()).toBe(0);
|
||||
await expect.poll(() => sidebar.locator(".sidebar-brand").count()).toBe(1);
|
||||
const settingsLink = sidebar.getByRole("link", { name: "Settings" });
|
||||
await expect.poll(() => settingsLink.isVisible()).toBe(true);
|
||||
await settingsLink.click();
|
||||
|
|
@ -101,9 +101,9 @@ describeControlUiE2e("Control UI sidebar customization mocked Gateway E2E", () =
|
|||
)
|
||||
.not.toContain("Workboard");
|
||||
|
||||
const customizeButton = sidebar.getByRole("button", { name: "Customize sidebar" });
|
||||
const customizeButton = sidebar.getByRole("button", { name: "Edit pinned items" });
|
||||
await customizeButton.click();
|
||||
const menu = sidebar.getByRole("menu", { name: "Customize sidebar" });
|
||||
const menu = sidebar.getByRole("menu", { name: "Edit pinned items" });
|
||||
await expect
|
||||
.poll(() => trimmedTextContents(menu.getByRole("menuitemcheckbox")))
|
||||
.not.toContain("Workboard");
|
||||
|
|
@ -133,7 +133,7 @@ describeControlUiE2e("Control UI sidebar customization mocked Gateway E2E", () =
|
|||
await captureUiProof(page, "03-persisted-customization.png");
|
||||
|
||||
await customizeButton.click();
|
||||
await menu.getByRole("menuitem", { name: "Reset to defaults" }).click();
|
||||
await menu.getByRole("menuitem", { name: "Reset pinned items" }).click();
|
||||
await expect.poll(() => trimmedTextContents(pinnedItems)).toEqual(["Overview"]);
|
||||
|
||||
// The sidebar search field is the command palette entry point.
|
||||
|
|
@ -144,10 +144,12 @@ describeControlUiE2e("Control UI sidebar customization mocked Gateway E2E", () =
|
|||
await page.keyboard.press("Escape");
|
||||
await expect.poll(() => paletteInput.isVisible()).toBe(false);
|
||||
|
||||
// The sidebar toggle lives in the topbar, macOS style.
|
||||
// The sidebar toggle lives in the sidebar brand row on desktop.
|
||||
const collapseButton = page.getByRole("button", { name: "Collapse sidebar" });
|
||||
await expect
|
||||
.poll(() => collapseButton.evaluate((element) => Boolean(element.closest(".topbar"))))
|
||||
.poll(() =>
|
||||
collapseButton.evaluate((element) => Boolean(element.closest(".sidebar-brand"))),
|
||||
)
|
||||
.toBe(true);
|
||||
await collapseButton.click();
|
||||
await expect
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/ar.meta.json
generated
10
ui/src/i18n/.i18n/ar.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:32.656Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:13.827Z",
|
||||
"locale": "ar",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/de.meta.json
generated
10
ui/src/i18n/.i18n/de.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:24.981Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:13.071Z",
|
||||
"locale": "de",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/es.meta.json
generated
10
ui/src/i18n/.i18n/es.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:26.317Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:13.195Z",
|
||||
"locale": "es",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/fa.meta.json
generated
10
ui/src/i18n/.i18n/fa.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:43.221Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:14.942Z",
|
||||
"locale": "fa",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/fr.meta.json
generated
10
ui/src/i18n/.i18n/fr.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:30.068Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:13.564Z",
|
||||
"locale": "fr",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/hi.meta.json
generated
10
ui/src/i18n/.i18n/hi.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:31.678Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:13.695Z",
|
||||
"locale": "hi",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/id.meta.json
generated
10
ui/src/i18n/.i18n/id.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:37.327Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:14.328Z",
|
||||
"locale": "id",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/it.meta.json
generated
10
ui/src/i18n/.i18n/it.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:33.970Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:13.952Z",
|
||||
"locale": "it",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/ja-JP.meta.json
generated
10
ui/src/i18n/.i18n/ja-JP.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:27.648Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:13.321Z",
|
||||
"locale": "ja-JP",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/ko.meta.json
generated
10
ui/src/i18n/.i18n/ko.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:29.117Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:13.440Z",
|
||||
"locale": "ko",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/nl.meta.json
generated
10
ui/src/i18n/.i18n/nl.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:41.995Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:14.823Z",
|
||||
"locale": "nl",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/pl.meta.json
generated
10
ui/src/i18n/.i18n/pl.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:38.593Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:14.446Z",
|
||||
"locale": "pl",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/pt-BR.meta.json
generated
10
ui/src/i18n/.i18n/pt-BR.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:23.799Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:12.950Z",
|
||||
"locale": "pt-BR",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
28
ui/src/i18n/.i18n/raw-copy-baseline.json
generated
28
ui/src/i18n/.i18n/raw-copy-baseline.json
generated
|
|
@ -8,6 +8,27 @@
|
|||
"path": "ui/src/app/app-host.ts",
|
||||
"text": "Close navigation"
|
||||
},
|
||||
{
|
||||
"count": 1,
|
||||
"kind": "html-text",
|
||||
"name": "text",
|
||||
"path": "ui/src/components/app-sidebar.ts",
|
||||
"text": "OpenClaw"
|
||||
},
|
||||
{
|
||||
"count": 1,
|
||||
"kind": "html-attribute",
|
||||
"name": "aria-label",
|
||||
"path": "ui/src/components/app-topbar.ts",
|
||||
"text": "OpenClaw"
|
||||
},
|
||||
{
|
||||
"count": 1,
|
||||
"kind": "html-text",
|
||||
"name": "text",
|
||||
"path": "ui/src/components/app-topbar.ts",
|
||||
"text": "OpenClaw"
|
||||
},
|
||||
{
|
||||
"count": 1,
|
||||
"kind": "html-text",
|
||||
|
|
@ -575,13 +596,6 @@
|
|||
"path": "ui/src/components/config-form.render.ts",
|
||||
"text": "Web"
|
||||
},
|
||||
{
|
||||
"count": 2,
|
||||
"kind": "html-text",
|
||||
"name": "text",
|
||||
"path": "ui/src/components/dashboard-header.ts",
|
||||
"text": "OpenClaw"
|
||||
},
|
||||
{
|
||||
"count": 1,
|
||||
"kind": "html-text",
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/ru.meta.json
generated
10
ui/src/i18n/.i18n/ru.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:46.145Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:15.074Z",
|
||||
"locale": "ru",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/th.meta.json
generated
10
ui/src/i18n/.i18n/th.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:39.502Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:14.574Z",
|
||||
"locale": "th",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/tr.meta.json
generated
10
ui/src/i18n/.i18n/tr.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:34.970Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:14.078Z",
|
||||
"locale": "tr",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/uk.meta.json
generated
10
ui/src/i18n/.i18n/uk.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:35.919Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:14.198Z",
|
||||
"locale": "uk",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/vi.meta.json
generated
10
ui/src/i18n/.i18n/vi.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:40.747Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:14.699Z",
|
||||
"locale": "vi",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/zh-CN.meta.json
generated
10
ui/src/i18n/.i18n/zh-CN.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:21.379Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:12.672Z",
|
||||
"locale": "zh-CN",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
10
ui/src/i18n/.i18n/zh-TW.meta.json
generated
10
ui/src/i18n/.i18n/zh-TW.meta.json
generated
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-07T08:47:22.571Z",
|
||||
"fallbackKeys": [
|
||||
"chat.docsTooltip"
|
||||
],
|
||||
"generatedAt": "2026-07-07T14:06:12.828Z",
|
||||
"locale": "zh-TW",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "d012bf7dea64cb5e4858358eb65d806374d86512260f7e16949b761b8bc0627b",
|
||||
"sourceHash": "6bda04d5bd3fed6bddc012598826e7d0c785534d870fc78820873063c896606c",
|
||||
"totalKeys": 1669,
|
||||
"translatedKeys": 1669,
|
||||
"translatedKeys": 1668,
|
||||
"workflow": 1
|
||||
}
|
||||
|
|
|
|||
2
ui/src/i18n/locales/ar.ts
generated
2
ui/src/i18n/locales/ar.ts
generated
|
|
@ -1561,7 +1561,7 @@ export const ar: TranslationMap = {
|
|||
gatewayStatus: "حالة Gateway: {status}",
|
||||
commandPaletteTitle: "ابحث أو انتقل إلى… (⌘K)",
|
||||
openCommandPalette: "فتح لوحة الأوامر",
|
||||
docsOpensInNewTab: "{label} (يفتح في علامة تبويب جديدة)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "يتوفر تحديث:",
|
||||
runningVersion: "الإصدار قيد التشغيل v{version}",
|
||||
updating: "جارٍ التحديث…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/de.ts
generated
2
ui/src/i18n/locales/de.ts
generated
|
|
@ -1591,7 +1591,7 @@ export const de: TranslationMap = {
|
|||
gatewayStatus: "Gateway-Status: {status}",
|
||||
commandPaletteTitle: "Suchen oder springen zu… (⌘K)",
|
||||
openCommandPalette: "Befehlspalette öffnen",
|
||||
docsOpensInNewTab: "{label} (wird in neuem Tab geöffnet)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "Update verfügbar:",
|
||||
runningVersion: "v{version} wird ausgeführt",
|
||||
updating: "Wird aktualisiert…",
|
||||
|
|
|
|||
|
|
@ -472,8 +472,8 @@ export const en: TranslationMap = {
|
|||
collapse: "Collapse sidebar",
|
||||
resize: "Resize sidebar",
|
||||
more: "More",
|
||||
customize: "Customize sidebar",
|
||||
customizeReset: "Reset to defaults",
|
||||
customize: "Edit pinned items",
|
||||
customizeReset: "Reset pinned items",
|
||||
},
|
||||
terminal: {
|
||||
title: "Terminal",
|
||||
|
|
@ -1567,7 +1567,7 @@ export const en: TranslationMap = {
|
|||
gatewayStatus: "Gateway status: {status}",
|
||||
commandPaletteTitle: "Search or jump to… (⌘K)",
|
||||
openCommandPalette: "Open command palette",
|
||||
docsOpensInNewTab: "{label} (opens in new tab)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "Update available:",
|
||||
runningVersion: "running v{version}",
|
||||
updating: "Updating…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/es.ts
generated
2
ui/src/i18n/locales/es.ts
generated
|
|
@ -1588,7 +1588,7 @@ export const es: TranslationMap = {
|
|||
gatewayStatus: "Estado del Gateway: {status}",
|
||||
commandPaletteTitle: "Buscar o ir a… (⌘K)",
|
||||
openCommandPalette: "Abrir paleta de comandos",
|
||||
docsOpensInNewTab: "{label} (se abre en una pestaña nueva)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "Actualización disponible:",
|
||||
runningVersion: "ejecutando v{version}",
|
||||
updating: "Actualizando…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/fa.ts
generated
2
ui/src/i18n/locales/fa.ts
generated
|
|
@ -1579,7 +1579,7 @@ export const fa: TranslationMap = {
|
|||
gatewayStatus: "وضعیت Gateway: {status}",
|
||||
commandPaletteTitle: "جستوجو یا رفتن به… (⌘K)",
|
||||
openCommandPalette: "باز کردن پالت فرمان",
|
||||
docsOpensInNewTab: "{label} (در زبانهٔ جدید باز میشود)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "بهروزرسانی در دسترس است:",
|
||||
runningVersion: "در حال اجرای v{version}",
|
||||
updating: "در حال بهروزرسانی…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/fr.ts
generated
2
ui/src/i18n/locales/fr.ts
generated
|
|
@ -1598,7 +1598,7 @@ export const fr: TranslationMap = {
|
|||
gatewayStatus: "État du Gateway : {status}",
|
||||
commandPaletteTitle: "Rechercher ou accéder à… (⌘K)",
|
||||
openCommandPalette: "Ouvrir la palette de commandes",
|
||||
docsOpensInNewTab: "{label} (s’ouvre dans un nouvel onglet)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "Mise à jour disponible :",
|
||||
runningVersion: "version v{version} en cours d’exécution",
|
||||
updating: "Mise à jour…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/hi.ts
generated
2
ui/src/i18n/locales/hi.ts
generated
|
|
@ -1563,7 +1563,7 @@ export const hi: TranslationMap = {
|
|||
gatewayStatus: "Gateway स्थिति: {status}",
|
||||
commandPaletteTitle: "खोजें या यहां जाएं… (⌘K)",
|
||||
openCommandPalette: "कमांड पैलेट खोलें",
|
||||
docsOpensInNewTab: "{label} (नए टैब में खुलता है)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "अपडेट उपलब्ध है:",
|
||||
runningVersion: "v{version} चल रहा है",
|
||||
updating: "अपडेट हो रहा है…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/id.ts
generated
2
ui/src/i18n/locales/id.ts
generated
|
|
@ -1580,7 +1580,7 @@ export const id: TranslationMap = {
|
|||
gatewayStatus: "Status Gateway: {status}",
|
||||
commandPaletteTitle: "Cari atau lompat ke… (⌘K)",
|
||||
openCommandPalette: "Buka palet perintah",
|
||||
docsOpensInNewTab: "{label} (terbuka di tab baru)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "Pembaruan tersedia:",
|
||||
runningVersion: "menjalankan v{version}",
|
||||
updating: "Memperbarui…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/it.ts
generated
2
ui/src/i18n/locales/it.ts
generated
|
|
@ -1590,7 +1590,7 @@ export const it: TranslationMap = {
|
|||
gatewayStatus: "Stato Gateway: {status}",
|
||||
commandPaletteTitle: "Cerca o passa a… (⌘K)",
|
||||
openCommandPalette: "Apri tavolozza comandi",
|
||||
docsOpensInNewTab: "{label} (si apre in una nuova scheda)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "Aggiornamento disponibile:",
|
||||
runningVersion: "in esecuzione v{version}",
|
||||
updating: "Aggiornamento…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/ja-JP.ts
generated
2
ui/src/i18n/locales/ja-JP.ts
generated
|
|
@ -1586,7 +1586,7 @@ export const ja_JP: TranslationMap = {
|
|||
gatewayStatus: "Gateway ステータス: {status}",
|
||||
commandPaletteTitle: "検索または移動… (⌘K)",
|
||||
openCommandPalette: "コマンドパレットを開く",
|
||||
docsOpensInNewTab: "{label} (新しいタブで開きます)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "アップデートがあります:",
|
||||
runningVersion: "実行中 v{version}",
|
||||
updating: "更新中…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/ko.ts
generated
2
ui/src/i18n/locales/ko.ts
generated
|
|
@ -1568,7 +1568,7 @@ export const ko: TranslationMap = {
|
|||
gatewayStatus: "Gateway 상태: {status}",
|
||||
commandPaletteTitle: "검색하거나 이동… (⌘K)",
|
||||
openCommandPalette: "명령 팔레트 열기",
|
||||
docsOpensInNewTab: "{label}(새 탭에서 열림)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "업데이트 사용 가능:",
|
||||
runningVersion: "실행 중 v{version}",
|
||||
updating: "업데이트 중…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/nl.ts
generated
2
ui/src/i18n/locales/nl.ts
generated
|
|
@ -1585,7 +1585,7 @@ export const nl: TranslationMap = {
|
|||
gatewayStatus: "Gateway-status: {status}",
|
||||
commandPaletteTitle: "Zoeken of springen naar… (⌘K)",
|
||||
openCommandPalette: "Opdrachtenpalet openen",
|
||||
docsOpensInNewTab: "{label} (opent in nieuw tabblad)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "Update beschikbaar:",
|
||||
runningVersion: "actieve versie v{version}",
|
||||
updating: "Bijwerken…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/pl.ts
generated
2
ui/src/i18n/locales/pl.ts
generated
|
|
@ -1583,7 +1583,7 @@ export const pl: TranslationMap = {
|
|||
gatewayStatus: "Status Gateway: {status}",
|
||||
commandPaletteTitle: "Wyszukaj lub przejdź do… (⌘K)",
|
||||
openCommandPalette: "Otwórz paletę poleceń",
|
||||
docsOpensInNewTab: "{label} (otwiera się w nowej karcie)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "Dostępna aktualizacja:",
|
||||
runningVersion: "uruchomiona wersja v{version}",
|
||||
updating: "Aktualizowanie…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/pt-BR.ts
generated
2
ui/src/i18n/locales/pt-BR.ts
generated
|
|
@ -1581,7 +1581,7 @@ export const pt_BR: TranslationMap = {
|
|||
gatewayStatus: "Status do Gateway: {status}",
|
||||
commandPaletteTitle: "Pesquisar ou ir para… (⌘K)",
|
||||
openCommandPalette: "Abrir paleta de comandos",
|
||||
docsOpensInNewTab: "{label} (abre em nova aba)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "Atualização disponível:",
|
||||
runningVersion: "executando v{version}",
|
||||
updating: "Atualizando…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/ru.ts
generated
2
ui/src/i18n/locales/ru.ts
generated
|
|
@ -1592,7 +1592,7 @@ export const ru: TranslationMap = {
|
|||
gatewayStatus: "Статус Gateway: {status}",
|
||||
commandPaletteTitle: "Поиск или переход к… (⌘K)",
|
||||
openCommandPalette: "Открыть палитру команд",
|
||||
docsOpensInNewTab: "{label} (откроется в новой вкладке)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "Доступно обновление:",
|
||||
runningVersion: "запущена v{version}",
|
||||
updating: "Обновление…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/th.ts
generated
2
ui/src/i18n/locales/th.ts
generated
|
|
@ -1545,7 +1545,7 @@ export const th: TranslationMap = {
|
|||
gatewayStatus: "สถานะ Gateway: {status}",
|
||||
commandPaletteTitle: "ค้นหาหรือข้ามไปที่… (⌘K)",
|
||||
openCommandPalette: "เปิดแถบคำสั่ง",
|
||||
docsOpensInNewTab: "{label} (เปิดในแท็บใหม่)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "มีอัปเดตพร้อมใช้งาน:",
|
||||
runningVersion: "กำลังใช้ v{version}",
|
||||
updating: "กำลังอัปเดต…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/tr.ts
generated
2
ui/src/i18n/locales/tr.ts
generated
|
|
@ -1585,7 +1585,7 @@ export const tr: TranslationMap = {
|
|||
gatewayStatus: "Gateway durumu: {status}",
|
||||
commandPaletteTitle: "Ara veya şuraya git… (⌘K)",
|
||||
openCommandPalette: "Komut paletini aç",
|
||||
docsOpensInNewTab: "{label} (yeni sekmede açılır)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "Güncelleme mevcut:",
|
||||
runningVersion: "çalışan v{version}",
|
||||
updating: "Güncelleniyor…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/uk.ts
generated
2
ui/src/i18n/locales/uk.ts
generated
|
|
@ -1581,7 +1581,7 @@ export const uk: TranslationMap = {
|
|||
gatewayStatus: "Стан Gateway: {status}",
|
||||
commandPaletteTitle: "Пошук або перехід до… (⌘K)",
|
||||
openCommandPalette: "Відкрити палітру команд",
|
||||
docsOpensInNewTab: "{label} (відкривається в новій вкладці)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "Доступне оновлення:",
|
||||
runningVersion: "працює v{version}",
|
||||
updating: "Оновлення…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/vi.ts
generated
2
ui/src/i18n/locales/vi.ts
generated
|
|
@ -1570,7 +1570,7 @@ export const vi: TranslationMap = {
|
|||
gatewayStatus: "Trạng thái Gateway: {status}",
|
||||
commandPaletteTitle: "Tìm kiếm hoặc chuyển đến… (⌘K)",
|
||||
openCommandPalette: "Mở bảng lệnh",
|
||||
docsOpensInNewTab: "{label} (mở trong tab mới)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "Có bản cập nhật:",
|
||||
runningVersion: "đang chạy v{version}",
|
||||
updating: "Đang cập nhật…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/zh-CN.ts
generated
2
ui/src/i18n/locales/zh-CN.ts
generated
|
|
@ -1540,7 +1540,7 @@ export const zh_CN: TranslationMap = {
|
|||
gatewayStatus: "Gateway 状态:{status}",
|
||||
commandPaletteTitle: "搜索或跳转到… (⌘K)",
|
||||
openCommandPalette: "打开命令面板",
|
||||
docsOpensInNewTab: "{label}(在新标签页中打开)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "有可用更新:",
|
||||
runningVersion: "正在运行 v{version}",
|
||||
updating: "正在更新…",
|
||||
|
|
|
|||
2
ui/src/i18n/locales/zh-TW.ts
generated
2
ui/src/i18n/locales/zh-TW.ts
generated
|
|
@ -1542,7 +1542,7 @@ export const zh_TW: TranslationMap = {
|
|||
gatewayStatus: "Gateway 狀態:{status}",
|
||||
commandPaletteTitle: "搜尋或跳至… (⌘K)",
|
||||
openCommandPalette: "開啟命令面板",
|
||||
docsOpensInNewTab: "{label}(在新分頁開啟)",
|
||||
docsTooltip: "Documentation ↗",
|
||||
updateAvailable: "有可用更新:",
|
||||
runningVersion: "執行中版本 v{version}",
|
||||
updating: "正在更新…",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
import { icons } from "../../components/icons.ts";
|
||||
import "../../components/tooltip.ts";
|
||||
import { t } from "../../i18n/index.ts";
|
||||
import { isGatewayMethodAdvertised } from "../../lib/gateway-methods.ts";
|
||||
import { resolveSessionDisplayName } from "../../lib/session-display.ts";
|
||||
import { resolveSessionKey, scopedAgentParamsForSession } from "../../lib/sessions/index.ts";
|
||||
import {
|
||||
|
|
@ -569,6 +570,12 @@ class ChatPane extends LitElement {
|
|||
if (!state) {
|
||||
return;
|
||||
}
|
||||
const previousTerminalAvailable = state.terminalAvailable;
|
||||
state.terminalAvailable =
|
||||
config.terminalEnabled &&
|
||||
state.connected &&
|
||||
hasOperatorAdminAccess(state.hello?.auth ?? null) &&
|
||||
isGatewayMethodAdvertised(this.context.gateway.snapshot, "terminal.open");
|
||||
const rootsChanged =
|
||||
state.localMediaPreviewRoots.length !== config.localMediaPreviewRoots.length ||
|
||||
state.localMediaPreviewRoots.some(
|
||||
|
|
@ -576,6 +583,7 @@ class ChatPane extends LitElement {
|
|||
);
|
||||
if (
|
||||
!rootsChanged &&
|
||||
state.terminalAvailable === previousTerminalAvailable &&
|
||||
state.embedSandboxMode === config.embedSandboxMode &&
|
||||
state.allowExternalEmbedUrls === config.allowExternalEmbedUrls &&
|
||||
state.chatMessageMaxWidth === config.chatMessageMaxWidth
|
||||
|
|
@ -599,6 +607,11 @@ class ChatPane extends LitElement {
|
|||
state.client = snapshot.client;
|
||||
state.connected = snapshot.connected;
|
||||
state.hello = snapshot.hello;
|
||||
state.terminalAvailable =
|
||||
this.context.config.current.terminalEnabled &&
|
||||
snapshot.connected &&
|
||||
hasOperatorAdminAccess(snapshot.hello?.auth ?? null) &&
|
||||
isGatewayMethodAdvertised(snapshot, "terminal.open");
|
||||
state.assistantAgentId = snapshot.assistantAgentId;
|
||||
const routeSessionKey = this.sessionKey.trim();
|
||||
const canonicalRouteSessionKey = routeSessionKey
|
||||
|
|
|
|||
|
|
@ -980,6 +980,7 @@ export function createPageState(
|
|||
client: null,
|
||||
connected: false,
|
||||
hello: null,
|
||||
terminalAvailable: false,
|
||||
assistantAgentId: context.agentSelection.state.selectedId,
|
||||
sessionKey: settings.sessionKey,
|
||||
chatLoading: false,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export type SessionWorkspaceProps = {
|
|||
onOpenFile: (path: string, origin: "session" | "workspace") => void;
|
||||
onSearch: (search: string) => void;
|
||||
onOpenArtifact: (artifactId: string) => void;
|
||||
onToggleTerminal?: () => void;
|
||||
};
|
||||
|
||||
type SessionWorkspaceState = {
|
||||
|
|
@ -63,6 +64,7 @@ export type SessionWorkspaceHost = {
|
|||
client: GatewayBrowserClient | null;
|
||||
connected: boolean;
|
||||
hello: GatewayHelloOk | null;
|
||||
terminalAvailable?: boolean;
|
||||
assistantAgentId?: string | null;
|
||||
agentsList?: SessionScopeHost["agentsList"];
|
||||
sessionWorkspaceState?: SessionWorkspaceState;
|
||||
|
|
@ -493,6 +495,15 @@ export function createSessionWorkspaceProps(state: SessionWorkspaceHost): Sessio
|
|||
}, 160);
|
||||
},
|
||||
onOpenArtifact: (artifactId) => openArtifact(state, workspace, artifactId),
|
||||
onToggleTerminal: state.terminalAvailable
|
||||
? () => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("openclaw:terminal-toggle", {
|
||||
detail: { dock: "right", open: true },
|
||||
}),
|
||||
);
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -535,6 +546,20 @@ export function renderSessionWorkspaceRail(
|
|||
if (!sessionWorkspace) {
|
||||
return nothing;
|
||||
}
|
||||
const terminalButton = sessionWorkspace.onToggleTerminal
|
||||
? html`
|
||||
<openclaw-tooltip .content=${t("terminal.toggle")}>
|
||||
<button
|
||||
type="button"
|
||||
class="chat-workspace-rail__terminal"
|
||||
aria-label=${t("terminal.toggle")}
|
||||
@click=${sessionWorkspace.onToggleTerminal}
|
||||
>
|
||||
${icons.terminal}
|
||||
</button>
|
||||
</openclaw-tooltip>
|
||||
`
|
||||
: nothing;
|
||||
if (sessionWorkspace.collapsed) {
|
||||
return html`
|
||||
<aside
|
||||
|
|
@ -557,6 +582,7 @@ export function renderSessionWorkspaceRail(
|
|||
<span class="chat-workspace-rail__collapsed-icon" aria-hidden="true"
|
||||
>${icons.fileText}</span
|
||||
>
|
||||
${terminalButton}
|
||||
</aside>
|
||||
`;
|
||||
}
|
||||
|
|
@ -676,38 +702,6 @@ export function renderSessionWorkspaceRail(
|
|||
: t("chat.workspaceFiles.session");
|
||||
return html`<span class="chat-workspace-rail__file-badge">${label}</span>`;
|
||||
};
|
||||
const renderBrowserBreadcrumbs = (): TemplateResult | typeof nothing => {
|
||||
if (!browser || browser.search) {
|
||||
return nothing;
|
||||
}
|
||||
const parts = browser.path ? browser.path.split("/").filter(Boolean) : [];
|
||||
let currentPath = "";
|
||||
return html`
|
||||
<div class="chat-workspace-rail__breadcrumbs" aria-label=${t("chat.workspaceFiles.path")}>
|
||||
<button
|
||||
class="chat-workspace-rail__crumb"
|
||||
type="button"
|
||||
@click=${() => sessionWorkspace.onBrowsePath("")}
|
||||
>
|
||||
${t("chat.workspaceFiles.root")}
|
||||
</button>
|
||||
${parts.map((part) => {
|
||||
currentPath = currentPath ? `${currentPath}/${part}` : part;
|
||||
const pathForPart = currentPath;
|
||||
return html`
|
||||
<span class="chat-workspace-rail__crumb-separator">/</span>
|
||||
<button
|
||||
class="chat-workspace-rail__crumb"
|
||||
type="button"
|
||||
@click=${() => sessionWorkspace.onBrowsePath(pathForPart)}
|
||||
>
|
||||
${part}
|
||||
</button>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
const renderBrowserRows = (): TemplateResult => {
|
||||
const entries = browser?.entries ?? [];
|
||||
const parentPath = browser?.parentPath;
|
||||
|
|
@ -728,7 +722,6 @@ export function renderSessionWorkspaceRail(
|
|||
/>
|
||||
</label>
|
||||
</div>
|
||||
${renderBrowserBreadcrumbs()}
|
||||
${browser?.search
|
||||
? html`<div class="chat-workspace-rail__browser-caption">
|
||||
${t("chat.workspaceFiles.searchResults")}
|
||||
|
|
@ -874,6 +867,7 @@ export function renderSessionWorkspaceRail(
|
|||
<strong>${t("chat.workspaceFiles.files")}</strong>
|
||||
</div>
|
||||
<div class="chat-workspace-rail__actions">
|
||||
${terminalButton}
|
||||
<openclaw-tooltip .content=${t("chat.workspaceFiles.refresh")}>
|
||||
<button
|
||||
class="btn btn--ghost btn--sm chat-workspace-rail__refresh"
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@
|
|||
gap: 6px;
|
||||
}
|
||||
|
||||
.chat-workspace-rail__terminal,
|
||||
.chat-workspace-rail__refresh,
|
||||
.chat-workspace-rail__collapse-toggle {
|
||||
width: 32px;
|
||||
|
|
@ -119,11 +120,41 @@
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
.chat-workspace-rail__terminal {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid color-mix(in srgb, var(--border) 76%, transparent);
|
||||
border-radius: var(--radius-sm);
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chat-workspace-rail__terminal:hover,
|
||||
.chat-workspace-rail__terminal:focus-visible {
|
||||
color: var(--text);
|
||||
border-color: color-mix(in srgb, var(--accent) 34%, var(--border));
|
||||
background: color-mix(in srgb, var(--accent) 8%, transparent);
|
||||
}
|
||||
|
||||
.chat-workspace-rail--collapsed .chat-workspace-rail__terminal {
|
||||
border-color: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.chat-workspace-rail--collapsed .chat-workspace-rail__terminal:hover,
|
||||
.chat-workspace-rail--collapsed .chat-workspace-rail__terminal:focus-visible {
|
||||
border-color: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.chat-workspace-rail__collapse-toggle {
|
||||
flex: 0 0 auto;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.chat-workspace-rail__terminal svg,
|
||||
.chat-workspace-rail__refresh svg,
|
||||
.chat-workspace-rail__collapse-toggle svg,
|
||||
.chat-workspace-rail__file-icon svg {
|
||||
|
|
@ -407,40 +438,6 @@
|
|||
color: var(--muted);
|
||||
}
|
||||
|
||||
.chat-workspace-rail__breadcrumbs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
padding: 0 12px 6px;
|
||||
color: var(--muted);
|
||||
font-size: var(--control-ui-text-xs);
|
||||
line-height: 1.2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-workspace-rail__crumb {
|
||||
min-width: 0;
|
||||
max-width: 88px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chat-workspace-rail__crumb:hover,
|
||||
.chat-workspace-rail__crumb:focus-visible {
|
||||
color: var(--text);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.chat-workspace-rail__crumb-separator,
|
||||
.chat-workspace-rail__browser-caption {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,16 +7,14 @@
|
|||
--shell-gap: 16px;
|
||||
--shell-nav-width: 258px;
|
||||
--shell-nav-rail-width: 78px;
|
||||
--shell-topbar-height: 44px;
|
||||
--shell-topbar-height: 0px;
|
||||
--shell-focus-duration: 200ms;
|
||||
--shell-focus-ease: var(--ease-out);
|
||||
height: 100vh;
|
||||
display: grid;
|
||||
grid-template-columns: var(--shell-nav-width) minmax(0, 1fr);
|
||||
grid-template-rows: var(--shell-topbar-height) 1fr;
|
||||
grid-template-areas:
|
||||
"nav topbar"
|
||||
"nav content";
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-areas: "nav content";
|
||||
gap: 0;
|
||||
animation: dashboard-enter 0.3s var(--ease-out);
|
||||
transition:
|
||||
|
|
@ -35,6 +33,12 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.shell--no-topbar {
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-areas: "nav content";
|
||||
--shell-topbar-height: 0px;
|
||||
}
|
||||
|
||||
.shell--nav-collapsed {
|
||||
grid-template-columns: var(--shell-nav-rail-width) minmax(0, 1fr);
|
||||
/* Keep --shell-nav-width in sync with the real column so fixed-position
|
||||
|
|
@ -48,10 +52,6 @@
|
|||
--shell-nav-width: 0px;
|
||||
}
|
||||
|
||||
.shell--onboarding .topbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.shell--onboarding .shell-nav {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 40;
|
||||
display: flex;
|
||||
display: none;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
min-height: var(--shell-topbar-height);
|
||||
|
|
@ -79,6 +79,10 @@
|
|||
-webkit-backdrop-filter: blur(12px) saturate(1.6);
|
||||
}
|
||||
|
||||
.shell--onboarding .topbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.topnav-shell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -104,77 +108,120 @@
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
.topnav-shell .dashboard-header {
|
||||
.topbar-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
gap: 7px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.topnav-shell .dashboard-header__breadcrumb {
|
||||
.topbar-brand__logo {
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.topbar-brand__title {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: var(--text-strong);
|
||||
font-size: 14px;
|
||||
font-weight: 650;
|
||||
letter-spacing: 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.topbar .nav-collapse-toggle {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.topbar .nav-collapse-toggle__icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.topbar .nav-collapse-toggle__icon svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.topbar-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.topbar-status .pill {
|
||||
padding: 6px 10px;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
height: 32px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.topbar-status .pill .mono {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.topbar-status .statusDot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.topbar-status .theme-orb__trigger {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.topnav-shell .dashboard-header__breadcrumb-link,
|
||||
.topnav-shell .dashboard-header__breadcrumb-sep {
|
||||
.topbar-search {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-height: 38px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid color-mix(in srgb, var(--border) 88%, transparent);
|
||||
border-radius: var(--radius-full);
|
||||
background: color-mix(in srgb, var(--bg-elevated) 84%, transparent);
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color var(--duration-fast) ease,
|
||||
background var(--duration-fast) ease,
|
||||
color var(--duration-fast) ease;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.topnav-shell .dashboard-header__breadcrumb-segment {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.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 {
|
||||
.topbar-search:hover {
|
||||
border-color: color-mix(in srgb, var(--border-strong) 90%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-hover) 84%, transparent);
|
||||
color: var(--text);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.topnav-shell .dashboard-header__breadcrumb-current {
|
||||
color: var(--accent);
|
||||
font-weight: 650;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
.topbar-search:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--focus-ring);
|
||||
}
|
||||
|
||||
.topnav-shell .dashboard-header__breadcrumb-context {
|
||||
max-width: 18ch;
|
||||
overflow: hidden;
|
||||
color: var(--muted);
|
||||
font-weight: 550;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
.topbar-search svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
stroke: currentColor;
|
||||
fill: none;
|
||||
stroke-width: 1.7px;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.topnav-shell .dashboard-header__breadcrumb-context {
|
||||
max-width: 10ch;
|
||||
}
|
||||
}
|
||||
|
||||
/* Quiet ghost buttons: the tiny top bar carries navigation chrome only, so
|
||||
its controls stay borderless and muted until hovered. */
|
||||
.topbar-icon-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
|
@ -261,6 +308,91 @@
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
.sidebar-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 34px;
|
||||
padding: 0 8px 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-brand__identity {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.sidebar-brand__logo {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: var(--radius-md);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.sidebar-brand__title {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: var(--text-strong);
|
||||
font-size: 14px;
|
||||
font-weight: 650;
|
||||
letter-spacing: 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sidebar-brand__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.sidebar-brand__icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background var(--duration-fast) ease,
|
||||
color var(--duration-fast) ease;
|
||||
}
|
||||
|
||||
.sidebar-brand__icon:hover:not(:disabled),
|
||||
.sidebar-brand__icon:focus-visible {
|
||||
background: color-mix(in srgb, var(--bg-hover) 84%, transparent);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.sidebar-brand__icon:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--focus-ring);
|
||||
}
|
||||
|
||||
.sidebar-brand__icon:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.42;
|
||||
}
|
||||
|
||||
.sidebar-brand__icon svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
stroke: currentColor;
|
||||
fill: none;
|
||||
stroke-width: 1.7px;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
.sidebar--collapsed {
|
||||
width: var(--shell-nav-rail-width);
|
||||
min-width: var(--shell-nav-rail-width);
|
||||
|
|
@ -390,6 +522,13 @@
|
|||
min-height: 36px;
|
||||
margin: 0 auto 10px;
|
||||
padding: 0;
|
||||
border-color: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.sidebar--collapsed .sidebar-search:hover:not(:disabled) {
|
||||
border-color: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
|
|
@ -1195,6 +1334,23 @@
|
|||
padding: 12px 8px 10px;
|
||||
}
|
||||
|
||||
.sidebar--collapsed .sidebar-brand {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 5px 0 14px;
|
||||
}
|
||||
|
||||
.sidebar--collapsed .sidebar-brand__identity {
|
||||
flex: 0 0 auto;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sidebar--collapsed .sidebar-brand__actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar--collapsed .sidebar-nav {
|
||||
padding: 0;
|
||||
}
|
||||
|
|
@ -1249,19 +1405,10 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
/* Rail active state stays flat: it inherits the accent-subtle background from
|
||||
.nav-item--active and only adds the accent bar. Gradients/drop shadows made
|
||||
the rail look like a floating card, especially in light mode. */
|
||||
/* Rail active state stays flat and uses only the inherited active background. */
|
||||
.sidebar--collapsed .nav-item--active::before,
|
||||
.sidebar--collapsed .nav-item.active::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 10px;
|
||||
bottom: 10px;
|
||||
width: 3px;
|
||||
border-radius: var(--radius-full);
|
||||
background: color-mix(in srgb, var(--accent) 86%, transparent);
|
||||
content: none;
|
||||
}
|
||||
|
||||
/* Single compact icon row: status dot, then utility icons right-aligned.
|
||||
|
|
@ -1370,20 +1517,12 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
/* Color mode segmented toggle, sidebar footer edition: quiet enough to sit
|
||||
between plain icon buttons (no pill chrome until a segment is active). */
|
||||
.sidebar-mode-switch {
|
||||
display: inline-flex;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.theme-mode-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.theme-mode-toggle__btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
|
@ -1401,25 +1540,19 @@
|
|||
border-color var(--duration-fast) ease;
|
||||
}
|
||||
|
||||
.theme-mode-toggle__btn:hover {
|
||||
.theme-mode-toggle:hover {
|
||||
color: var(--text);
|
||||
background: color-mix(in srgb, var(--bg-hover) 84%, transparent);
|
||||
}
|
||||
|
||||
.theme-mode-toggle__btn:focus-visible {
|
||||
.theme-mode-toggle:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--focus-ring);
|
||||
}
|
||||
|
||||
.theme-mode-toggle__btn--active {
|
||||
color: var(--accent);
|
||||
background: var(--accent-subtle);
|
||||
border-color: color-mix(in srgb, var(--accent) 25%, transparent);
|
||||
}
|
||||
|
||||
.theme-mode-toggle__btn svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
.theme-mode-toggle svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
stroke: currentColor;
|
||||
fill: none;
|
||||
stroke-width: 1.75px;
|
||||
|
|
@ -1427,15 +1560,10 @@
|
|||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
/* Rail mode: the footer stacks vertically, so the segments do too. */
|
||||
.sidebar--collapsed .sidebar-mode-switch {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.sidebar--collapsed .theme-mode-toggle {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.shell--nav-collapsed .shell-nav {
|
||||
width: var(--shell-nav-rail-width);
|
||||
min-width: var(--shell-nav-rail-width);
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@
|
|||
@media (max-width: 1100px) {
|
||||
.shell,
|
||||
.shell--nav-collapsed {
|
||||
--shell-topbar-height: 58px;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-template-rows: var(--shell-topbar-height) minmax(0, 1fr);
|
||||
grid-template-areas:
|
||||
|
|
@ -104,6 +105,13 @@
|
|||
--shell-nav-width: 0px;
|
||||
}
|
||||
|
||||
.shell--no-topbar,
|
||||
.shell--nav-collapsed.shell--no-topbar {
|
||||
--shell-topbar-height: 0px;
|
||||
grid-template-rows: minmax(0, 1fr);
|
||||
grid-template-areas: "content";
|
||||
}
|
||||
|
||||
.shell-nav,
|
||||
.shell--nav-collapsed .shell-nav {
|
||||
position: fixed;
|
||||
|
|
@ -147,6 +155,50 @@
|
|||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* Show the hamburger toggle at the same breakpoint where the drawer takes over;
|
||||
the desktop rail toggle makes no sense once the sidebar is a drawer. */
|
||||
.topbar-nav-toggle {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.topnav-shell {
|
||||
display: grid;
|
||||
grid-template-columns: 40px minmax(0, 1fr) 40px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.topnav-shell__content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.topnav-shell__actions {
|
||||
min-width: 0;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.topbar-search {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
width: 38px;
|
||||
min-width: 38px;
|
||||
height: 38px;
|
||||
min-height: 38px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.topbar-search__kbd {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-brand__actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar,
|
||||
.sidebar--collapsed {
|
||||
width: 100%;
|
||||
|
|
@ -233,26 +285,75 @@
|
|||
|
||||
/* Topbar */
|
||||
.topbar {
|
||||
display: flex;
|
||||
padding: 10px 12px;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.topnav-shell {
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.topnav-shell__actions {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.topnav-shell__content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.topbar-nav-toggle {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.topbar-status {
|
||||
gap: 6px;
|
||||
width: auto;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.topbar-search {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
width: 38px;
|
||||
min-width: 38px;
|
||||
height: 38px;
|
||||
min-height: 38px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.topbar-search__kbd {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.topbar-theme-mode {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-mode-switch {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar-mode-switch .topbar-theme-mode {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.topbar-status .pill {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px; /* was 11px */
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.topbar-status .pill .mono {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.topbar-status .pill span:nth-child(2) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.shell-nav,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue