Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ import { ICommandService } from '../../../../../platform/commands/common/command
import { IAICustomizationListItem } from './aiCustomizationItemSource.js';
import { IAICustomizationItemsModel, ItemsModelSection } from './aiCustomizationItemsModel.js';
import { createCustomizationCardPrimaryAction, CustomizationCardListController } from './customizationCardList.js';
import { DomScrollableElement } from '../../../../../base/browser/ui/scrollbar/scrollableElement.js';
import { ScrollbarVisibility } from '../../../../../base/common/scrollable.js';

export { truncateToFirstLine } from './aiCustomizationListWidgetUtils.js';

Expand Down Expand Up @@ -615,7 +617,8 @@ export class AICustomizationListWidget extends Disposable {
private listContainer!: HTMLElement;
private list!: WorkbenchList<IListEntry>;
private cardContainer!: HTMLElement;
private cardScrollElement: HTMLElement | undefined;
private cardScrollable!: DomScrollableElement;
private cardScrollableNode!: HTMLElement;
private firstCardFocusElement: HTMLElement | undefined;
private readonly cardRowsByUri = new Map<string, HTMLElement>();
private readonly cardRowsById = new Map<string, HTMLElement>();
Expand Down Expand Up @@ -781,7 +784,23 @@ export class AICustomizationListWidget extends Disposable {
this._register(this.addButton.onDidClick(() => this.executePrimaryCreateAction()));

this.cardContainer = DOM.append(this.element, $('.plugin-card-container.customization-card-container'));
this.cardContainer.style.display = 'none';
this.cardScrollable = this._register(new DomScrollableElement(this.cardContainer, {
horizontal: ScrollbarVisibility.Hidden,
vertical: ScrollbarVisibility.Auto,
useShadows: false,
}));
Comment thread
houghj16 marked this conversation as resolved.
this._register(DOM.addDisposableListener(this.cardContainer, DOM.EventType.SCROLL, () => {
this.cardScrollable.setScrollPosition({ scrollTop: this.cardContainer.scrollTop });
}));
this.cardScrollableNode = this.cardScrollable.getDomNode();
this.cardScrollableNode.classList.add('plugin-card-scrollable');
this.cardScrollableNode.style.display = 'none';
this.element.appendChild(this.cardScrollableNode);
const cardResizeObserver = this._register(new DOM.DisposableResizeObserver(
'AICustomizationListWidget.cardScrollable',
() => this.cardScrollable.scanDomNode(),
));
this._register(cardResizeObserver.observe(this.cardScrollableNode));

// List container
this.listContainer = DOM.append(this.element, $('.list-container'));
Expand Down Expand Up @@ -1572,10 +1591,9 @@ export class AICustomizationListWidget extends Disposable {
this.cardRowsByUri.clear();
this.cardRowsById.clear();
this.cardMenuButtonsById.clear();
this.cardScrollElement = undefined;
this.firstCardFocusElement = undefined;
DOM.clearNode(this.cardContainer);
this.cardContainer.style.display = 'none';
this.cardScrollableNode.style.display = 'none';
this.updateEmptyState();
return;
}
Expand All @@ -1594,8 +1612,8 @@ export class AICustomizationListWidget extends Disposable {
DOM.clearNode(this.cardContainer);
this.listContainer.style.display = 'none';
this.emptyStateContainer.style.display = 'none';
this.cardContainer.style.display = '';
const content = this.cardScrollElement = DOM.append(this.cardContainer, $('.plugin-card-scroll.customization-card-scroll'));
this.cardScrollableNode.style.display = '';
const content = DOM.append(this.cardContainer, $('.plugin-card-scroll.plugin-card-scroll-content.customization-card-scroll'));

for (const group of visibleGroups) {
const section = DOM.append(content, $('.plugin-card-section.customization-card-section'));
Expand Down Expand Up @@ -1628,6 +1646,7 @@ export class AICustomizationListWidget extends Disposable {
}
cardList.finalize();
}
this.cardScrollable.scanDomNode();
if (shouldRestoreFocus) {
DOM.getWindow(this.element).requestAnimationFrame(() => {
(this.cardMenuButtonsById.get(focusItemId ?? '') ?? this.cardRowsById.get(focusItemId ?? '') ?? this.firstCardFocusElement)?.focus();
Expand Down Expand Up @@ -1884,7 +1903,7 @@ export class AICustomizationListWidget extends Disposable {
private updateEmptyState(): void {
const hasItems = this.displayEntries.length > 0;
if (!hasItems) {
this.cardContainer.style.display = 'none';
this.cardScrollableNode.style.display = 'none';
this.emptyStateContainer.style.display = 'flex';
this.listContainer.style.display = 'none';

Expand All @@ -1901,7 +1920,7 @@ export class AICustomizationListWidget extends Disposable {
} else {
this.emptyStateContainer.style.display = 'none';
this.listContainer.style.display = this.usesCardLayout() ? 'none' : '';
this.cardContainer.style.display = this.usesCardLayout() ? '' : 'none';
this.cardScrollableNode.style.display = this.usesCardLayout() ? '' : 'none';
}
}

Expand Down Expand Up @@ -1976,9 +1995,7 @@ export class AICustomizationListWidget extends Disposable {
*/
revealLastItem(): void {
if (this.usesCardLayout()) {
if (this.cardScrollElement) {
this.cardScrollElement.scrollTop = this.cardScrollElement.scrollHeight;
}
this.cardScrollable.setScrollPosition({ scrollTop: this.cardContainer.scrollHeight });
return;
}
if (this.displayEntries.length > 0) {
Expand Down Expand Up @@ -2049,9 +2066,11 @@ export class AICustomizationListWidget extends Disposable {
const availableHeight = this.element.clientHeight || height;
const listHeight = Math.max(0, availableHeight - searchBarHeight - headerHeight);

this.cardContainer.style.height = `${listHeight}px`;
this.cardScrollableNode.style.height = `${listHeight}px`;
this.listContainer.style.height = `${listHeight}px`;
if (!this.usesCardLayout()) {
if (this.usesCardLayout()) {
this.cardScrollable.scanDomNode();
} else {
this.list.layout(listHeight, width);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ import { Range } from '../../../../../editor/common/core/range.js';
import { IMcpServerConfiguration, McpServerType } from '../../../../../platform/mcp/common/mcpPlatformTypes.js';
import { createWorkbenchMcpServerDetailInput, IMcpServerDetailInput } from './embeddedMcpServerDetail.js';
import { createCustomizationCardPrimaryAction, CustomizationCardListController } from './customizationCardList.js';
import { DomScrollableElement } from '../../../../../base/browser/ui/scrollbar/scrollableElement.js';
import { ScrollbarVisibility } from '../../../../../base/common/scrollable.js';

const $ = DOM.$;

Expand Down Expand Up @@ -1060,6 +1062,8 @@ export class McpListWidget extends Disposable {
private searchAndButtonContainer!: HTMLElement;
private searchInput!: InputBox;
private cardContainer!: HTMLElement;
private cardScrollable!: DomScrollableElement;
private cardScrollableNode!: HTMLElement;
private emptyContainer!: HTMLElement;
private emptyText!: HTMLElement;
private emptySubtext!: HTMLElement;
Expand All @@ -1082,7 +1086,6 @@ export class McpListWidget extends Disposable {
private visible = false;
private mcpAccessEnabled = false;
private firstCardFocusElement: HTMLElement | undefined;
private cardScrollElement: HTMLElement | undefined;
private availableSection: HTMLElement | undefined;
private narrowLayout = false;
private wideLayout = false;
Expand Down Expand Up @@ -1234,8 +1237,24 @@ export class McpListWidget extends Disposable {
disabledText.textContent = localize('mcpAccessDisabledTitle', "MCP servers are disabled");
this.disabledMessage = DOM.append(this.disabledContainer, $('.empty-subtext'));

this.cardContainer = DOM.append(this.element, $('.plugin-card-container'));
this.cardContainer.style.display = 'none';
this.cardContainer = $('.plugin-card-container');
this.cardScrollable = this._register(new DomScrollableElement(this.cardContainer, {
horizontal: ScrollbarVisibility.Hidden,
vertical: ScrollbarVisibility.Auto,
useShadows: false,
}));
this._register(DOM.addDisposableListener(this.cardContainer, DOM.EventType.SCROLL, () => {
this.cardScrollable.setScrollPosition({ scrollTop: this.cardContainer.scrollTop });
}));
this.cardScrollableNode = this.cardScrollable.getDomNode();
this.cardScrollableNode.classList.add('plugin-card-scrollable');
this.cardScrollableNode.style.display = 'none';
this.element.appendChild(this.cardScrollableNode);
const cardResizeObserver = this._register(new DOM.DisposableResizeObserver(
'McpListWidget.cardScrollable',
() => this.cardScrollable.scanDomNode(),
));
this._register(cardResizeObserver.observe(this.cardScrollableNode));

// Listen to MCP service changes
this._register(this.mcpWorkbenchService.onChange(() => {
Expand Down Expand Up @@ -1403,16 +1422,27 @@ export class McpListWidget extends Disposable {

private showCardSurface(): void {
this.emptyContainer.style.display = 'none';
this.cardContainer.style.display = '';
this.cardScrollableNode.style.display = '';
}

private showEmptySurface(message: string, detail: string): void {
this.cardContainer.style.display = 'none';
this.cardScrollableNode.style.display = 'none';
this.emptyContainer.style.display = 'flex';
this.emptyText.textContent = message;
this.emptySubtext.textContent = detail;
}

private createCardScrollContent(...classNames: string[]): HTMLElement {
const content = DOM.append(this.cardContainer, $('.plugin-card-scroll.plugin-card-scroll-content'));
content.classList.add(...classNames);
const resizeObserver = this.cardDisposables.add(new DOM.DisposableResizeObserver(
'McpListWidget.cardScrollContent',
() => this.cardScrollable.scanDomNode(),
));
this.cardDisposables.add(resizeObserver.observe(content));
return content;
}

private addSurfaceActivation(surface: HTMLElement, label: string, callback: () => void, ...classNames: string[]): HTMLButtonElement {
const primaryAction = createCustomizationCardPrimaryAction(surface, label, ...classNames);
this.firstCardFocusElement ??= primaryAction;
Expand Down Expand Up @@ -1454,7 +1484,7 @@ export class McpListWidget extends Disposable {
DOM.clearNode(this.cardContainer);
this.showCardSurface();

const content = this.cardScrollElement = DOM.append(this.cardContainer, $('.plugin-card-scroll'));
const content = this.createCardScrollContent();
this.renderFeaturedServers(content);

const installedList = this.renderCardSection(
Expand Down Expand Up @@ -1821,7 +1851,7 @@ export class McpListWidget extends Disposable {
this.availableSection = undefined;
DOM.clearNode(this.cardContainer);
this.showCardSurface();
const content = this.cardScrollElement = DOM.append(this.cardContainer, $('.plugin-card-scroll.plugin-search-results'));
const content = this.createCardScrollContent('plugin-search-results');
if (this.installedEntries.length > 0) {
const installedList = this.renderCardSection(content, localize('installedSearchHeader', "Installed"), undefined, 'installed-mcp-servers-section', this.installedEntries.length);
installedList.classList.add('plugin-inventory-list');
Expand Down Expand Up @@ -1986,7 +2016,8 @@ export class McpListWidget extends Disposable {
this.lastHeaderHeight = headerHeight;
const listHeight = Math.max(0, availableHeight - searchBarHeight - headerHeight);

this.cardContainer.style.height = `${listHeight}px`;
this.cardScrollableNode.style.height = `${listHeight}px`;
this.cardScrollable.scanDomNode();
}

/**
Expand All @@ -2000,16 +2031,14 @@ export class McpListWidget extends Disposable {
* Scrolls the list so the last item is visible.
*/
revealLastItem(): void {
if (this.cardScrollElement) {
this.cardScrollElement.scrollTop = this.cardScrollElement.scrollHeight;
}
this.cardScrollable.setScrollPosition({ scrollTop: this.cardContainer.scrollHeight });
}

/**
* Focuses the list.
*/
focus(): void {
if (this.cardContainer.style.display !== 'none') {
if (this.cardScrollableNode.style.display !== 'none') {
this.firstCardFocusElement?.focus();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@
.ai-customization-list-widget .list-container {
flex: 1;
min-height: 0;
overflow: auto;
overflow: hidden;
}

.ai-customization-list-widget .list-empty-message {
Expand Down Expand Up @@ -1336,6 +1336,9 @@ per-word capitalization does not survive translation. */
position: relative;
flex: 1;
min-height: 0;
width: 100%;
max-width: calc(840px + var(--vscode-spacing-size160));
margin: 0 auto;
}

/* The scrolled element: bound it to the wrapper so clientHeight < scrollHeight when content overflows. */
Expand All @@ -1346,7 +1349,7 @@ per-word capitalization does not survive translation. */
display: flex;
flex-direction: column;
gap: var(--vscode-spacing-size240);
padding: 0 max(var(--vscode-spacing-size20), calc((100% - 840px) / 2)) var(--vscode-spacing-size200);
padding: 0 var(--vscode-spacing-size160) var(--vscode-spacing-size200) 0;
box-sizing: border-box;
}

Expand Down Expand Up @@ -2752,13 +2755,33 @@ per-word capitalization does not survive translation. */
overflow: hidden;
}

.plugin-list-widget .plugin-card-scrollable {
position: relative;
flex: 1;
min-height: 0;
width: 100%;
max-width: calc(840px + var(--vscode-spacing-size160));
margin: 0 auto;
}

.plugin-list-widget .plugin-card-scrollable > .plugin-card-container {
position: absolute;
inset: 0;
}

.plugin-list-widget .plugin-card-scroll {
height: 100%;
overflow: auto;
padding: 0 max(var(--vscode-spacing-size20), calc((100% - 840px) / 2)) var(--vscode-spacing-size200);
box-sizing: border-box;
}

.plugin-list-widget .plugin-card-scroll-content {
height: auto;
overflow: visible;
padding: 0 var(--vscode-spacing-size160) var(--vscode-spacing-size200) 0;
}

.plugin-list-widget .plugin-marketplace-back-container {
flex-shrink: 0;
display: flex;
Expand Down
Loading
Loading