mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-17 23:35:54 +01:00
Merge pull request #306272 from microsoft/mrleemurray/sessions-toggle-update
Sessions: refactor toggle action and unread badge
This commit is contained in:
@@ -13,22 +13,25 @@ import { Menus } from './menus.js';
|
|||||||
import { ServicesAccessor } from '../../platform/instantiation/common/instantiation.js';
|
import { ServicesAccessor } from '../../platform/instantiation/common/instantiation.js';
|
||||||
import { KeybindingWeight } from '../../platform/keybinding/common/keybindingsRegistry.js';
|
import { KeybindingWeight } from '../../platform/keybinding/common/keybindingsRegistry.js';
|
||||||
import { registerIcon } from '../../platform/theme/common/iconRegistry.js';
|
import { registerIcon } from '../../platform/theme/common/iconRegistry.js';
|
||||||
import { IsAuxiliaryWindowContext, IsWindowAlwaysOnTopContext } from '../../workbench/common/contextkeys.js';
|
import { IsAuxiliaryWindowContext, IsWindowAlwaysOnTopContext, SideBarVisibleContext } from '../../workbench/common/contextkeys.js';
|
||||||
import { IWorkbenchLayoutService, Parts } from '../../workbench/services/layout/browser/layoutService.js';
|
import { IWorkbenchLayoutService, Parts } from '../../workbench/services/layout/browser/layoutService.js';
|
||||||
|
|
||||||
// Register Icons
|
// Register Icons
|
||||||
const panelCloseIcon = registerIcon('agent-panel-close', Codicon.close, localize('agentPanelCloseIcon', "Icon to close the panel."));
|
const panelCloseIcon = registerIcon('agent-panel-close', Codicon.close, localize('agentPanelCloseIcon', "Icon to close the panel."));
|
||||||
|
const sidebarToggleIcon = registerIcon('agent-sidebar-toggle', Codicon.tasklist, localize('agentSidebarToggleIcon', "Icon to toggle the sessions sidebar."));
|
||||||
|
|
||||||
class ToggleSidebarVisibilityAction extends Action2 {
|
class ToggleSidebarVisibilityAction extends Action2 {
|
||||||
|
|
||||||
static readonly ID = 'workbench.action.agentToggleSidebarVisibility';
|
static readonly ID = 'workbench.action.agentToggleSidebarVisibility';
|
||||||
static readonly LABEL = localize('compositePart.hideSideBarLabel', "Hide Primary Side Bar");
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
id: ToggleSidebarVisibilityAction.ID,
|
id: ToggleSidebarVisibilityAction.ID,
|
||||||
title: localize2('toggleSidebar', 'Toggle Primary Side Bar Visibility'),
|
title: localize2('toggleSidebar', 'Toggle Primary Side Bar Visibility'),
|
||||||
icon: panelCloseIcon,
|
icon: sidebarToggleIcon,
|
||||||
|
toggled: {
|
||||||
|
condition: SideBarVisibleContext,
|
||||||
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
description: localize('openAndCloseSidebar', 'Open/Show and Close/Hide Sidebar'),
|
description: localize('openAndCloseSidebar', 'Open/Show and Close/Hide Sidebar'),
|
||||||
},
|
},
|
||||||
@@ -39,6 +42,12 @@ class ToggleSidebarVisibilityAction extends Action2 {
|
|||||||
primary: KeyMod.CtrlCmd | KeyCode.KeyB
|
primary: KeyMod.CtrlCmd | KeyCode.KeyB
|
||||||
},
|
},
|
||||||
menu: [
|
menu: [
|
||||||
|
{
|
||||||
|
id: Menus.TitleBarLeftLayout,
|
||||||
|
group: 'navigation',
|
||||||
|
order: 0,
|
||||||
|
when: IsAuxiliaryWindowContext.toNegated()
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: Menus.TitleBarContext,
|
id: Menus.TitleBarContext,
|
||||||
group: 'navigation',
|
group: 'navigation',
|
||||||
@@ -66,7 +75,6 @@ class ToggleSidebarVisibilityAction extends Action2 {
|
|||||||
class ToggleSecondarySidebarVisibilityAction extends Action2 {
|
class ToggleSecondarySidebarVisibilityAction extends Action2 {
|
||||||
|
|
||||||
static readonly ID = 'workbench.action.agentToggleSecondarySidebarVisibility';
|
static readonly ID = 'workbench.action.agentToggleSecondarySidebarVisibility';
|
||||||
static readonly LABEL = localize('compositePart.hideSecondarySideBarLabel', "Hide Secondary Side Bar");
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
|
|||||||
@@ -24,8 +24,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Interactive elements in the title area must not be draggable */
|
/* Interactive elements in the title area must not be draggable */
|
||||||
.agent-sessions-workbench .part.sidebar > .composite.title .action-item,
|
.agent-sessions-workbench .part.sidebar > .composite.title .action-item {
|
||||||
.agent-sessions-workbench .part.sidebar > .composite.title > .session-status-toggle {
|
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,38 +72,3 @@
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Session status toggle — standalone button in sidebar title area */
|
|
||||||
.agent-sessions-workbench .part.sidebar .session-status-toggle {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
align-self: center;
|
|
||||||
gap: 3px;
|
|
||||||
height: 22px;
|
|
||||||
padding: 0 4px;
|
|
||||||
margin-right: 4px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: inherit;
|
|
||||||
font: inherit;
|
|
||||||
background: var(--vscode-toolbar-activeBackground);
|
|
||||||
outline: none;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-sessions-workbench .part.sidebar .session-status-toggle:hover {
|
|
||||||
background-color: var(--vscode-toolbar-hoverBackground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-sessions-workbench .part.sidebar .session-status-toggle .codicon {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-sessions-workbench .part.sidebar .session-status-toggle-badge {
|
|
||||||
font-size: 12px;
|
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
line-height: 16px;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -32,17 +32,13 @@ import { Separator } from '../../../base/common/actions.js';
|
|||||||
import { IHoverService } from '../../../platform/hover/browser/hover.js';
|
import { IHoverService } from '../../../platform/hover/browser/hover.js';
|
||||||
import { Extensions } from '../../../workbench/browser/panecomposite.js';
|
import { Extensions } from '../../../workbench/browser/panecomposite.js';
|
||||||
import { Menus } from '../menus.js';
|
import { Menus } from '../menus.js';
|
||||||
import { $, addDisposableListener, append, EventType, getWindowId, prepend } from '../../../base/browser/dom.js';
|
import { $, append, getWindowId, prepend } from '../../../base/browser/dom.js';
|
||||||
import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../platform/actions/browser/toolbar.js';
|
import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../platform/actions/browser/toolbar.js';
|
||||||
import { isMacintosh, isNative } from '../../../base/common/platform.js';
|
|
||||||
import { isFullscreen, onDidChangeFullscreen } from '../../../base/browser/browser.js';
|
import { isFullscreen, onDidChangeFullscreen } from '../../../base/browser/browser.js';
|
||||||
import { mainWindow } from '../../../base/browser/window.js';
|
import { mainWindow } from '../../../base/browser/window.js';
|
||||||
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
|
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
|
||||||
import { hasNativeTitlebar, getTitleBarStyle } from '../../../platform/window/common/window.js';
|
import { hasNativeTitlebar, getTitleBarStyle } from '../../../platform/window/common/window.js';
|
||||||
import { ThemeIcon } from '../../../base/common/themables.js';
|
import { isMacintosh, isNative } from '../../../base/common/platform.js';
|
||||||
import { Codicon } from '../../../base/common/codicons.js';
|
|
||||||
import { DisposableStore } from '../../../base/common/lifecycle.js';
|
|
||||||
import { localize } from '../../../nls.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sidebar part specifically for agent sessions workbench.
|
* Sidebar part specifically for agent sessions workbench.
|
||||||
@@ -156,11 +152,6 @@ export class SidebarPart extends AbstractPaneCompositePart {
|
|||||||
prepend(titleArea, $('div.titlebar-drag-region'));
|
prepend(titleArea, $('div.titlebar-drag-region'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session toggle widget (right side of title area)
|
|
||||||
if (titleArea) {
|
|
||||||
this.createSessionsToggle(titleArea);
|
|
||||||
}
|
|
||||||
|
|
||||||
// macOS native: the sidebar spans full height and the traffic lights
|
// macOS native: the sidebar spans full height and the traffic lights
|
||||||
// overlay the top-left corner. Add a fixed-width spacer inside the
|
// overlay the top-left corner. Add a fixed-width spacer inside the
|
||||||
// title area to push content horizontally past the traffic lights.
|
// title area to push content horizontally past the traffic lights.
|
||||||
@@ -187,27 +178,6 @@ export class SidebarPart extends AbstractPaneCompositePart {
|
|||||||
return titleArea;
|
return titleArea;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a standalone session toggle widget appended to the sidebar title area.
|
|
||||||
* Displays a tasklist icon with an optional unread badge. Clicking hides the sidebar.
|
|
||||||
*/
|
|
||||||
private createSessionsToggle(titleArea: HTMLElement): void {
|
|
||||||
const widgetDisposables = this._register(new DisposableStore());
|
|
||||||
|
|
||||||
const widget = append(titleArea, $('button.session-status-toggle')) as HTMLButtonElement;
|
|
||||||
widget.type = 'button';
|
|
||||||
widget.tabIndex = 0;
|
|
||||||
widget.setAttribute('aria-label', localize('hideSidebar', "Hide Side Bar"));
|
|
||||||
append(widget, $(ThemeIcon.asCSSSelector(Codicon.tasklist)));
|
|
||||||
|
|
||||||
// Toggle sidebar on click
|
|
||||||
widgetDisposables.add(addDisposableListener(widget, EventType.CLICK, (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
this.layoutService.setPartHidden(true, Parts.SIDEBAR_PART);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private createFooter(parent: HTMLElement): void {
|
private createFooter(parent: HTMLElement): void {
|
||||||
const footer = append(parent, $('.sidebar-footer.sidebar-action-list'));
|
const footer = append(parent, $('.sidebar-footer.sidebar-action-list'));
|
||||||
this.footerContainer = footer;
|
this.footerContainer = footer;
|
||||||
|
|||||||
@@ -86,42 +86,27 @@
|
|||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Provider label (shown for untitled sessions) */
|
/* Sidebar toggle unread badge */
|
||||||
.command-center .agent-sessions-titlebar-container .agent-sessions-titlebar-provider {
|
.agent-sessions-workbench .action-item.sidebar-toggle-action {
|
||||||
display: flex;
|
position: relative;
|
||||||
align-items: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Session count widget (tasklist icon + unread count, left of pill) */
|
.agent-sessions-workbench .action-item.sidebar-toggle-action .sidebar-toggle-badge {
|
||||||
.command-center .agent-sessions-titlebar-container .agent-sessions-titlebar-count {
|
position: absolute;
|
||||||
display: flex;
|
top: 0;
|
||||||
align-items: center;
|
right: -4px;
|
||||||
gap: 3px;
|
min-width: 14px;
|
||||||
flex-shrink: 0;
|
height: 14px;
|
||||||
cursor: pointer;
|
line-height: 14px;
|
||||||
padding: 0 4px;
|
padding: 0 3px;
|
||||||
height: 22px;
|
box-sizing: border-box;
|
||||||
border: none;
|
font-size: 9px;
|
||||||
border-radius: 4px;
|
font-weight: 600;
|
||||||
background: transparent;
|
|
||||||
color: inherit;
|
|
||||||
font: inherit;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.command-center .agent-sessions-titlebar-container .agent-sessions-titlebar-count:hover {
|
|
||||||
background-color: var(--vscode-toolbar-hoverBackground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.command-center .agent-sessions-titlebar-container .agent-sessions-titlebar-count .codicon {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.command-center .agent-sessions-titlebar-container .agent-sessions-titlebar-count-label {
|
|
||||||
font-size: 12px;
|
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
line-height: 16px;
|
text-align: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--vscode-activityBarBadge-background);
|
||||||
|
color: var(--vscode-activityBarBadge-foreground);
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,14 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import './media/sessionsTitleBarWidget.css';
|
import './media/sessionsTitleBarWidget.css';
|
||||||
import { $, addDisposableListener, EventType, getActiveWindow, reset } from '../../../../base/browser/dom.js';
|
import { $, addDisposableListener, append, EventType, getActiveWindow, reset } from '../../../../base/browser/dom.js';
|
||||||
|
import { IAction, Separator } from '../../../../base/common/actions.js';
|
||||||
import { Separator } from '../../../../base/common/actions.js';
|
|
||||||
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
|
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
|
||||||
import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js';
|
import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js';
|
||||||
import { localize } from '../../../../nls.js';
|
import { localize } from '../../../../nls.js';
|
||||||
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
|
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
|
||||||
import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';
|
import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';
|
||||||
import { BaseActionViewItem, IBaseActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
|
import { ActionViewItem, BaseActionViewItem, IBaseActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
|
||||||
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||||
import { IMenuService, MenuRegistry, SubmenuItemAction } from '../../../../platform/actions/common/actions.js';
|
import { IMenuService, MenuRegistry, SubmenuItemAction } from '../../../../platform/actions/common/actions.js';
|
||||||
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
||||||
@@ -23,13 +22,12 @@ import { Menus } from '../../../browser/menus.js';
|
|||||||
import { IWorkbenchContribution } from '../../../../workbench/common/contributions.js';
|
import { IWorkbenchContribution } from '../../../../workbench/common/contributions.js';
|
||||||
import { IActionViewItemService } from '../../../../platform/actions/browser/actionViewItemService.js';
|
import { IActionViewItemService } from '../../../../platform/actions/browser/actionViewItemService.js';
|
||||||
import { ISessionsManagementService } from './sessionsManagementService.js';
|
import { ISessionsManagementService } from './sessionsManagementService.js';
|
||||||
import { autorun } from '../../../../base/common/observable.js';
|
import { autorun, observableSignalFromEvent } from '../../../../base/common/observable.js';
|
||||||
import { ThemeIcon } from '../../../../base/common/themables.js';
|
import { ThemeIcon } from '../../../../base/common/themables.js';
|
||||||
import { IsAuxiliaryWindowContext } from '../../../../workbench/common/contextkeys.js';
|
import { IsAuxiliaryWindowContext } from '../../../../workbench/common/contextkeys.js';
|
||||||
import { SessionsWelcomeVisibleContext } from '../../../common/contextkeys.js';
|
import { SessionsWelcomeVisibleContext } from '../../../common/contextkeys.js';
|
||||||
import { ISessionsProvidersService } from './sessionsProvidersService.js';
|
import { ISessionsProvidersService } from './sessionsProvidersService.js';
|
||||||
import { SessionStatus } from '../common/sessionData.js';
|
import { SessionStatus } from '../common/sessionData.js';
|
||||||
import { Codicon } from '../../../../base/common/codicons.js';
|
|
||||||
import { SHOW_SESSIONS_PICKER_COMMAND_ID } from './sessionsActions.js';
|
import { SHOW_SESSIONS_PICKER_COMMAND_ID } from './sessionsActions.js';
|
||||||
import { IsSessionArchivedContext, IsSessionPinnedContext, IsSessionReadContext, SessionItemContextMenuId } from './views/sessionsList.js';
|
import { IsSessionArchivedContext, IsSessionPinnedContext, IsSessionReadContext, SessionItemContextMenuId } from './views/sessionsList.js';
|
||||||
|
|
||||||
@@ -67,7 +65,6 @@ export class SessionsTitleBarWidget extends BaseActionViewItem {
|
|||||||
@IMenuService private readonly menuService: IMenuService,
|
@IMenuService private readonly menuService: IMenuService,
|
||||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||||
@ISessionsProvidersService private readonly sessionsProvidersService: ISessionsProvidersService,
|
@ISessionsProvidersService private readonly sessionsProvidersService: ISessionsProvidersService,
|
||||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
|
|
||||||
@ICommandService private readonly commandService: ICommandService,
|
@ICommandService private readonly commandService: ICommandService,
|
||||||
) {
|
) {
|
||||||
super(undefined, action, options);
|
super(undefined, action, options);
|
||||||
@@ -131,9 +128,8 @@ export class SessionsTitleBarWidget extends BaseActionViewItem {
|
|||||||
const label = this._getActiveSessionLabel();
|
const label = this._getActiveSessionLabel();
|
||||||
const icon = this._getActiveSessionIcon();
|
const icon = this._getActiveSessionIcon();
|
||||||
const repoLabel = this._getRepositoryLabel();
|
const repoLabel = this._getRepositoryLabel();
|
||||||
const unreadCount = this._countUnreadSessions();
|
|
||||||
// Build a render-state key from all displayed data
|
// Build a render-state key from all displayed data
|
||||||
const renderState = `${icon?.id ?? ''}|${label}|${repoLabel ?? ''}|${unreadCount}`;
|
const renderState = `${icon?.id ?? ''}|${label}|${repoLabel ?? ''}`;
|
||||||
|
|
||||||
// Skip re-render if state hasn't changed
|
// Skip re-render if state hasn't changed
|
||||||
if (this._lastRenderState === renderState) {
|
if (this._lastRenderState === renderState) {
|
||||||
@@ -198,37 +194,6 @@ export class SessionsTitleBarWidget extends BaseActionViewItem {
|
|||||||
|
|
||||||
this._container.appendChild(sessionPill);
|
this._container.appendChild(sessionPill);
|
||||||
|
|
||||||
// Session count widget (to the left of the pill) — toggles sidebar
|
|
||||||
const countWidget = $('button.agent-sessions-titlebar-count') as HTMLButtonElement;
|
|
||||||
countWidget.type = 'button';
|
|
||||||
countWidget.tabIndex = 0;
|
|
||||||
const countIcon = $(ThemeIcon.asCSSSelector(Codicon.tasklist));
|
|
||||||
countWidget.appendChild(countIcon);
|
|
||||||
if (unreadCount > 0) {
|
|
||||||
const countLabel = $('span.agent-sessions-titlebar-count-label');
|
|
||||||
countLabel.textContent = `${unreadCount}`;
|
|
||||||
countWidget.appendChild(countLabel);
|
|
||||||
countWidget.setAttribute('aria-label', localize('showSidebarUnread', "Show Side Bar, {0} unread session(s)", unreadCount));
|
|
||||||
} else {
|
|
||||||
countWidget.setAttribute('aria-label', localize('showSidebar', "Show Side Bar"));
|
|
||||||
}
|
|
||||||
// Hide when sidebar is visible (only shown when sidebar is hidden)
|
|
||||||
const updateVisibility = () => {
|
|
||||||
countWidget.style.display = this.layoutService.isVisible(Parts.SIDEBAR_PART) ? 'none' : '';
|
|
||||||
};
|
|
||||||
updateVisibility();
|
|
||||||
this._dynamicDisposables.add(this.layoutService.onDidChangePartVisibility(e => {
|
|
||||||
if (e.partId === Parts.SIDEBAR_PART) {
|
|
||||||
updateVisibility();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
this._dynamicDisposables.add(addDisposableListener(countWidget, EventType.CLICK, (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
this.layoutService.setPartHidden(false, Parts.SIDEBAR_PART);
|
|
||||||
}));
|
|
||||||
this._container.insertBefore(countWidget, sessionPill);
|
|
||||||
|
|
||||||
// Hover
|
// Hover
|
||||||
this._dynamicDisposables.add(this.hoverService.setupManagedHover(
|
this._dynamicDisposables.add(this.hoverService.setupManagedHover(
|
||||||
getDefaultHoverDelegate('mouse'),
|
getDefaultHoverDelegate('mouse'),
|
||||||
@@ -285,16 +250,6 @@ export class SessionsTitleBarWidget extends BaseActionViewItem {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _countUnreadSessions(): number {
|
|
||||||
let unread = 0;
|
|
||||||
for (const session of this.sessionsManagementService.getSessions()) {
|
|
||||||
if (!session.isArchived.get() && session.status.get() === SessionStatus.Completed && !session.isRead.get()) {
|
|
||||||
unread++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return unread;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _showContextMenu(e: MouseEvent): void {
|
private _showContextMenu(e: MouseEvent): void {
|
||||||
const sessionData = this.sessionsManagementService.activeSession.get();
|
const sessionData = this.sessionsManagementService.activeSession.get();
|
||||||
if (!sessionData) {
|
if (!sessionData) {
|
||||||
@@ -324,6 +279,93 @@ export class SessionsTitleBarWidget extends BaseActionViewItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom action view item for the sidebar toggle button.
|
||||||
|
* Renders the tasklist icon with an unread session count badge.
|
||||||
|
*/
|
||||||
|
class SidebarToggleActionViewItem extends ActionViewItem {
|
||||||
|
|
||||||
|
private _countBadge: HTMLElement | undefined;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
context: unknown,
|
||||||
|
action: IAction,
|
||||||
|
options: IBaseActionViewItemOptions | undefined,
|
||||||
|
@ISessionsManagementService private readonly sessionsManagementService: ISessionsManagementService,
|
||||||
|
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
|
||||||
|
) {
|
||||||
|
super(context, action, { ...options, icon: true, label: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
override render(container: HTMLElement): void {
|
||||||
|
super.render(container);
|
||||||
|
|
||||||
|
container.classList.add('sidebar-toggle-action');
|
||||||
|
|
||||||
|
// Add badge element for unread session count
|
||||||
|
this._countBadge = append(container, $('span.sidebar-toggle-badge'));
|
||||||
|
this._countBadge.setAttribute('aria-hidden', 'true');
|
||||||
|
this._updateBadge();
|
||||||
|
|
||||||
|
// Single autorun that tracks all badge-relevant state:
|
||||||
|
// - session list changes (add/remove) via observableSignalFromEvent
|
||||||
|
// - individual session observable state (status, isRead, isArchived)
|
||||||
|
// - sidebar visibility changes
|
||||||
|
const sessionsChanged = observableSignalFromEvent(this, this.sessionsManagementService.onDidChangeSessions);
|
||||||
|
const sidebarVisibilityChanged = observableSignalFromEvent(this, handler => this.layoutService.onDidChangePartVisibility(e => {
|
||||||
|
if (e.partId === Parts.SIDEBAR_PART) {
|
||||||
|
handler(e);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
this._register(autorun(reader => {
|
||||||
|
sessionsChanged.read(reader);
|
||||||
|
sidebarVisibilityChanged.read(reader);
|
||||||
|
for (const session of this.sessionsManagementService.getSessions()) {
|
||||||
|
session.isArchived.read(reader);
|
||||||
|
session.status.read(reader);
|
||||||
|
session.isRead.read(reader);
|
||||||
|
}
|
||||||
|
this._updateBadge();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateBadge(): void {
|
||||||
|
if (!this._countBadge) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unreadCount = this._countUnreadSessions();
|
||||||
|
const sidebarVisible = this.layoutService.isVisible(Parts.SIDEBAR_PART);
|
||||||
|
|
||||||
|
if (unreadCount > 0 && !sidebarVisible) {
|
||||||
|
this._countBadge.textContent = `${unreadCount}`;
|
||||||
|
this._countBadge.style.display = '';
|
||||||
|
} else {
|
||||||
|
this._countBadge.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update accessible label to include unread count for screen readers
|
||||||
|
if (this.label) {
|
||||||
|
const baseLabel = this.action.label || localize('toggleSidebarA11y', "Toggle Primary Side Bar");
|
||||||
|
if (unreadCount > 0 && !sidebarVisible) {
|
||||||
|
this.label.setAttribute('aria-label', localize('toggleSidebarUnread', "{0}, {1} unread session(s)", baseLabel, unreadCount));
|
||||||
|
} else {
|
||||||
|
this.label.setAttribute('aria-label', baseLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _countUnreadSessions(): number {
|
||||||
|
let unread = 0;
|
||||||
|
for (const session of this.sessionsManagementService.getSessions()) {
|
||||||
|
if (!session.isArchived.get() && session.status.get() === SessionStatus.Completed && !session.isRead.get()) {
|
||||||
|
unread++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unread;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides custom rendering for the sessions title bar widget
|
* Provides custom rendering for the sessions title bar widget
|
||||||
* in the command center. Uses IActionViewItemService to render a custom widget
|
* in the command center. Uses IActionViewItemService to render a custom widget
|
||||||
@@ -364,5 +406,10 @@ export class SessionsTitleBarContribution extends Disposable implements IWorkben
|
|||||||
}
|
}
|
||||||
return instantiationService.createInstance(SessionsTitleBarWidget, action, options);
|
return instantiationService.createInstance(SessionsTitleBarWidget, action, options);
|
||||||
}, undefined));
|
}, undefined));
|
||||||
|
|
||||||
|
// Register custom view item for sidebar toggle with unread badge
|
||||||
|
this._register(actionViewItemService.register(Menus.TitleBarLeftLayout, 'workbench.action.agentToggleSidebarVisibility', (action, options) => {
|
||||||
|
return instantiationService.createInstance(SidebarToggleActionViewItem, undefined, action, options);
|
||||||
|
}, undefined));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user