mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-24 12:19:20 +00:00
Optional TreeItem Checkbox (#158250)
This commit is contained in:
committed by
GitHub
parent
daf5eb2262
commit
f51258b93b
@@ -42,6 +42,7 @@
|
||||
"textSearchProvider",
|
||||
"timeline",
|
||||
"tokenInformation",
|
||||
"treeItemCheckbox",
|
||||
"treeViewReveal",
|
||||
"workspaceTrust",
|
||||
"telemetry"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext, CheckboxUpdate } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeView, IViewsRegistry, ITreeViewDescriptor, IRevealOptions, Extensions, ResolvableTreeItem, ITreeViewDragAndDropController, IViewBadge } from 'vs/workbench/common/views';
|
||||
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
@@ -170,6 +170,11 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
|
||||
this._register(treeView.onDidChangeSelection(items => this._proxy.$setSelection(treeViewId, items.map(({ handle }) => handle))));
|
||||
this._register(treeView.onDidChangeFocus(item => this._proxy.$setFocus(treeViewId, item.handle)));
|
||||
this._register(treeView.onDidChangeVisibility(isVisible => this._proxy.$setVisible(treeViewId, isVisible)));
|
||||
this._register(treeView.onDidChangeCheckboxState(items => {
|
||||
this._proxy.$changeCheckboxState(treeViewId, <CheckboxUpdate[]>items.map(item => {
|
||||
return { treeItemHandle: item.handle, newState: item.checkboxChecked ?? false };
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
private getTreeView(treeViewId: string): ITreeView | null {
|
||||
|
||||
@@ -1295,6 +1295,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
ThemeColor: extHostTypes.ThemeColor,
|
||||
ThemeIcon: extHostTypes.ThemeIcon,
|
||||
TreeItem: extHostTypes.TreeItem,
|
||||
TreeItem2: extHostTypes.TreeItem,
|
||||
TreeItemCheckboxState: extHostTypes.TreeItemCheckboxState,
|
||||
TreeItemCollapsibleState: extHostTypes.TreeItemCollapsibleState,
|
||||
TypeHierarchyItem: extHostTypes.TypeHierarchyItem,
|
||||
UIKind: UIKind,
|
||||
|
||||
@@ -1399,6 +1399,11 @@ export interface DataTransferDTO {
|
||||
readonly items: Array<[/* type */string, DataTransferItemDTO]>;
|
||||
}
|
||||
|
||||
export interface CheckboxUpdate {
|
||||
treeItemHandle: string;
|
||||
newState: boolean;
|
||||
}
|
||||
|
||||
export interface ExtHostTreeViewsShape {
|
||||
$getChildren(treeViewId: string, treeItemHandle?: string): Promise<ITreeItem[] | undefined>;
|
||||
$handleDrop(destinationViewId: string, requestId: number, treeDataTransfer: DataTransferDTO, targetHandle: string | undefined, token: CancellationToken, operationUuid?: string, sourceViewId?: string, sourceTreeItemHandles?: string[]): Promise<void>;
|
||||
@@ -1407,6 +1412,7 @@ export interface ExtHostTreeViewsShape {
|
||||
$setSelection(treeViewId: string, treeItemHandles: string[]): void;
|
||||
$setFocus(treeViewId: string, treeItemHandle: string): void;
|
||||
$setVisible(treeViewId: string, visible: boolean): void;
|
||||
$changeCheckboxState(treeViewId: string, checkboxUpdates: CheckboxUpdate[]): void;
|
||||
$hasResolve(treeViewId: string): Promise<boolean>;
|
||||
$resolve(treeViewId: string, treeItemHandle: string, token: CancellationToken): Promise<ITreeItem | undefined>;
|
||||
}
|
||||
|
||||
@@ -10,11 +10,11 @@ import { basename } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DataTransferDTO, ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol';
|
||||
import { CheckboxUpdate, DataTransferDTO, ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol';
|
||||
import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions, TreeCommand, TreeViewPaneHandleArg } from 'vs/workbench/common/views';
|
||||
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { asPromise } from 'vs/base/common/async';
|
||||
import { TreeItemCollapsibleState, ThemeIcon, MarkdownString as MarkdownStringType, TreeItem } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { TreeItemCollapsibleState, TreeItemCheckboxState, ThemeIcon, MarkdownString as MarkdownStringType, TreeItem } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { isUndefinedOrNull, isString } from 'vs/base/common/types';
|
||||
import { equals, coalesce } from 'vs/base/common/arrays';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
@@ -23,6 +23,7 @@ import { MarkdownString, ViewBadge, DataTransfer } from 'vs/workbench/api/common
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { ITreeViewsService, TreeviewsService } from 'vs/workbench/services/views/common/treeViewsService';
|
||||
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
||||
type TreeItemHandle = string;
|
||||
|
||||
@@ -99,6 +100,7 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
|
||||
get onDidChangeSelection() { return treeView.onDidChangeSelection; },
|
||||
get visible() { return treeView.visible; },
|
||||
get onDidChangeVisibility() { return treeView.onDidChangeVisibility; },
|
||||
get onDidChangeTreeCheckbox() { checkProposedApiEnabled(extension, 'treeItemCheckbox'); return treeView.onDidChangeTreeCheckbox; },
|
||||
get message() { return treeView.message; },
|
||||
set message(message: string) {
|
||||
treeView.message = message;
|
||||
@@ -234,6 +236,14 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
|
||||
treeView.setVisible(isVisible);
|
||||
}
|
||||
|
||||
$changeCheckboxState(treeViewId: string, checkboxUpdate: CheckboxUpdate[]): void {
|
||||
const treeView = this.treeViews.get(treeViewId);
|
||||
if (!treeView) {
|
||||
throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId));
|
||||
}
|
||||
treeView.setCheckboxState(checkboxUpdate);
|
||||
}
|
||||
|
||||
private createExtHostTreeView<T>(id: string, options: vscode.TreeViewOptions<T>, extension: IExtensionDescription): ExtHostTreeView<T> {
|
||||
const treeView = new ExtHostTreeView<T>(id, options, this._proxy, this.commands.converter, this.logService, extension);
|
||||
this.treeViews.set(id, treeView);
|
||||
@@ -296,6 +306,9 @@ class ExtHostTreeView<T> extends Disposable {
|
||||
private _onDidChangeVisibility: Emitter<vscode.TreeViewVisibilityChangeEvent> = this._register(new Emitter<vscode.TreeViewVisibilityChangeEvent>());
|
||||
readonly onDidChangeVisibility: Event<vscode.TreeViewVisibilityChangeEvent> = this._onDidChangeVisibility.event;
|
||||
|
||||
private _onDidChangeTreeCheckbox = this._register(new Emitter<vscode.TreeCheckboxChangeEvent<T>>());
|
||||
readonly onDidChangeTreeCheckbox: Event<vscode.TreeCheckboxChangeEvent<T>> = this._onDidChangeTreeCheckbox.event;
|
||||
|
||||
private _onDidChangeData: Emitter<TreeData<T>> = this._register(new Emitter<TreeData<T>>());
|
||||
|
||||
private refreshPromise: Promise<void> = Promise.resolve();
|
||||
@@ -474,6 +487,26 @@ class ExtHostTreeView<T> extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
async setCheckboxState(checkboxUpdates: CheckboxUpdate[]) {
|
||||
const items = (await Promise.all(checkboxUpdates.map(async checkboxUpdate => {
|
||||
const extensionItem = this.getExtensionElement(checkboxUpdate.treeItemHandle);
|
||||
if (extensionItem) {
|
||||
return {
|
||||
extensionItem: extensionItem,
|
||||
treeItem: await this.dataProvider.getTreeItem(extensionItem),
|
||||
newState: checkboxUpdate.newState ? TreeItemCheckboxState.Checked : TreeItemCheckboxState.Unchecked
|
||||
};
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}))).filter((item) => item !== undefined) as { extensionItem: T; treeItem: vscode.TreeItem2; newState: TreeItemCheckboxState }[];
|
||||
|
||||
items.forEach(item => {
|
||||
item.treeItem.checkboxState = item.newState ? TreeItemCheckboxState.Checked : TreeItemCheckboxState.Unchecked;
|
||||
});
|
||||
|
||||
this._onDidChangeTreeCheckbox.fire({ items: items.map(item => [item.extensionItem, item.newState]) });
|
||||
}
|
||||
|
||||
async handleDrag(sourceTreeItemHandles: TreeItemHandle[], treeDataTransfer: vscode.DataTransfer, token: CancellationToken): Promise<vscode.DataTransfer | undefined> {
|
||||
const extensionTreeItems: T[] = [];
|
||||
for (const sourceHandle of sourceTreeItemHandles) {
|
||||
@@ -717,8 +750,13 @@ class ExtHostTreeView<T> extends Disposable {
|
||||
return command ? { ...this.commands.toInternal(command, disposable), originalId: command.command } : undefined;
|
||||
}
|
||||
|
||||
private getCheckbox(extensionTreeItem: vscode.TreeItem2): boolean | undefined {
|
||||
return (extensionTreeItem.checkboxState !== undefined) ?
|
||||
extensionTreeItem.checkboxState === TreeItemCheckboxState.Checked : undefined;
|
||||
}
|
||||
|
||||
private validateTreeItem(extensionTreeItem: vscode.TreeItem) {
|
||||
if (!TreeItem.isTreeItem(extensionTreeItem)) {
|
||||
if (!TreeItem.isTreeItem(extensionTreeItem, this.extension)) {
|
||||
throw new Error(`Extension ${this.extension.identifier.value} has provided an invalid tree item.`);
|
||||
}
|
||||
}
|
||||
@@ -741,7 +779,8 @@ class ExtHostTreeView<T> extends Disposable {
|
||||
iconDark: this.getDarkIconPath(extensionTreeItem) || icon,
|
||||
themeIcon: this.getThemeIcon(extensionTreeItem),
|
||||
collapsibleState: isUndefinedOrNull(extensionTreeItem.collapsibleState) ? TreeItemCollapsibleState.None : extensionTreeItem.collapsibleState,
|
||||
accessibilityInformation: extensionTreeItem.accessibilityInformation
|
||||
accessibilityInformation: extensionTreeItem.accessibilityInformation,
|
||||
checkboxChecked: this.getCheckbox(extensionTreeItem)
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -13,10 +13,12 @@ import { nextCharLength } from 'vs/base/common/strings';
|
||||
import { isString, isStringArray } from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files';
|
||||
import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IRelativePatternDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { CellEditType, ICellPartialMetadataEdit, IDocumentMetadataEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
/**
|
||||
@@ -2413,12 +2415,13 @@ export class TreeItem {
|
||||
command?: vscode.Command;
|
||||
contextValue?: string;
|
||||
tooltip?: string | vscode.MarkdownString;
|
||||
checkboxState?: vscode.TreeItemCheckboxState;
|
||||
|
||||
static isTreeItem(thing: any): thing is TreeItem {
|
||||
static isTreeItem(thing: any, extension: IExtensionDescription): thing is TreeItem {
|
||||
if (thing instanceof TreeItem) {
|
||||
return true;
|
||||
}
|
||||
const treeItemThing = thing as vscode.TreeItem;
|
||||
const treeItemThing = thing as vscode.TreeItem2;
|
||||
if (treeItemThing.label !== undefined && !isString(treeItemThing.label) && !(treeItemThing.label?.label)) {
|
||||
console.log('INVALID tree item, invalid label', treeItemThing.label);
|
||||
return false;
|
||||
@@ -2462,6 +2465,13 @@ export class TreeItem {
|
||||
console.log('INVALID tree item, invalid accessibilityInformation', treeItemThing.accessibilityInformation);
|
||||
return false;
|
||||
}
|
||||
if (treeItemThing.checkboxState !== undefined) {
|
||||
checkProposedApiEnabled(extension, 'treeItemCheckbox');
|
||||
if (treeItemThing.checkboxState !== TreeItemCheckboxState.Checked && treeItemThing.checkboxState !== TreeItemCheckboxState.Unchecked) {
|
||||
console.log('INVALID tree item, invalid checkboxState', treeItemThing.checkboxState);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -2484,6 +2494,11 @@ export enum TreeItemCollapsibleState {
|
||||
Expanded = 2
|
||||
}
|
||||
|
||||
export enum TreeItemCheckboxState {
|
||||
Unchecked = 0,
|
||||
Checked = 1
|
||||
}
|
||||
|
||||
@es5ClassCompat
|
||||
export class DataTransferItem {
|
||||
|
||||
|
||||
92
src/vs/workbench/browser/checkbox.ts
Normal file
92
src/vs/workbench/browser/checkbox.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Toggle } from 'vs/base/browser/ui/toggle/toggle';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { localize } from 'vs/nls';
|
||||
import { attachToggleStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ITreeItem } from 'vs/workbench/common/views';
|
||||
|
||||
export class CheckboxStateHandler extends Disposable {
|
||||
private readonly _onDidChangeCheckboxState = this._register(new Emitter<ITreeItem[]>());
|
||||
readonly onDidChangeCheckboxState: Event<ITreeItem[]> = this._onDidChangeCheckboxState.event;
|
||||
|
||||
public setCheckboxState(node: ITreeItem) {
|
||||
this._onDidChangeCheckboxState.fire([node]);
|
||||
}
|
||||
}
|
||||
|
||||
export class TreeItemCheckbox extends Disposable {
|
||||
public toggle: Toggle | undefined;
|
||||
private checkboxContainer: HTMLDivElement;
|
||||
public isDisposed = false;
|
||||
|
||||
public static readonly checkboxClass = 'custom-view-tree-node-item-checkbox';
|
||||
|
||||
private readonly _onDidChangeState = new Emitter<boolean>();
|
||||
readonly onDidChangeState: Event<boolean> = this._onDidChangeState.event;
|
||||
|
||||
constructor(container: HTMLElement, private checkboxStateHandler: CheckboxStateHandler, private themeService: IThemeService) {
|
||||
super();
|
||||
this.checkboxContainer = <HTMLDivElement>container;
|
||||
}
|
||||
|
||||
public render(node: ITreeItem) {
|
||||
if (node.checkboxChecked !== undefined) {
|
||||
if (!this.toggle) {
|
||||
this.createCheckbox(node);
|
||||
}
|
||||
else {
|
||||
this.toggle.checked = node.checkboxChecked;
|
||||
this.toggle.setIcon(this.toggle.checked ? Codicon.check : undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private createCheckbox(node: ITreeItem) {
|
||||
if (node.checkboxChecked !== undefined) {
|
||||
this.toggle = new Toggle({
|
||||
isChecked: node.checkboxChecked,
|
||||
title: localize('check', "Check"),
|
||||
icon: node.checkboxChecked ? Codicon.check : undefined
|
||||
});
|
||||
|
||||
this.toggle.domNode.classList.add(TreeItemCheckbox.checkboxClass);
|
||||
DOM.append(this.checkboxContainer, this.toggle.domNode);
|
||||
this.registerListener(node);
|
||||
}
|
||||
}
|
||||
|
||||
private registerListener(node: ITreeItem) {
|
||||
if (this.toggle) {
|
||||
this._register({ dispose: () => this.removeCheckbox() });
|
||||
this._register(this.toggle);
|
||||
this._register(this.toggle.onChange(() => {
|
||||
this.setCheckbox(node);
|
||||
}));
|
||||
this._register(attachToggleStyler(this.toggle, this.themeService));
|
||||
}
|
||||
}
|
||||
|
||||
private setCheckbox(node: ITreeItem) {
|
||||
if (this.toggle && node.checkboxChecked !== undefined) {
|
||||
node.checkboxChecked = this.toggle.checked;
|
||||
this.toggle.setIcon(this.toggle.checked ? Codicon.check : undefined);
|
||||
this.toggle.checked = this.toggle.checked;
|
||||
this.checkboxStateHandler.setCheckboxState(node);
|
||||
}
|
||||
}
|
||||
|
||||
private removeCheckbox() {
|
||||
const children = this.checkboxContainer.children;
|
||||
for (const child of children) {
|
||||
this.checkboxContainer.removeChild(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,6 +120,20 @@
|
||||
padding-left: 3px;
|
||||
}
|
||||
|
||||
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .custom-view-tree-node-item-checkbox {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 3px 6px 3px 0px;
|
||||
padding: 0px;
|
||||
border: 1px solid var(--vscode-checkbox-border);
|
||||
opacity: 1;
|
||||
background-color: var(--vscode-checkbox-background);
|
||||
}
|
||||
|
||||
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .custom-view-tree-node-item-checkbox.codicon {
|
||||
font-size: 13px;
|
||||
line-height: 15px;
|
||||
}
|
||||
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .monaco-inputbox {
|
||||
line-height: normal;
|
||||
flex: 1;
|
||||
|
||||
@@ -66,6 +66,7 @@ import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
|
||||
import { ITreeViewsService } from 'vs/workbench/services/views/browser/treeViewsService';
|
||||
import { CodeDataTransfers } from 'vs/platform/dnd/browser/dnd';
|
||||
import { addExternalEditorsDropData, toVSDataTransfer } from 'vs/editor/browser/dnd';
|
||||
import { CheckboxStateHandler, TreeItemCheckbox } from 'vs/workbench/browser/checkbox';
|
||||
|
||||
export class TreeViewPane extends ViewPane {
|
||||
|
||||
@@ -236,6 +237,9 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
|
||||
private readonly _onDidChangeDescription: Emitter<string | undefined> = this._register(new Emitter<string | undefined>());
|
||||
readonly onDidChangeDescription: Event<string | undefined> = this._onDidChangeDescription.event;
|
||||
|
||||
private readonly _onDidChangeCheckboxState: Emitter<ITreeItem[]> = this._register(new Emitter<ITreeItem[]>());
|
||||
readonly onDidChangeCheckboxState: Event<ITreeItem[]> = this._onDidChangeCheckboxState.event;
|
||||
|
||||
private readonly _onDidCompleteRefresh: Emitter<void> = this._register(new Emitter<void>());
|
||||
|
||||
constructor(
|
||||
@@ -598,7 +602,12 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
|
||||
this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this));
|
||||
const dataSource = this.instantiationService.createInstance(TreeDataSource, this, <T>(task: Promise<T>) => this.progressService.withProgress({ location: this.id }, () => task));
|
||||
const aligner = new Aligner(this.themeService);
|
||||
const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner);
|
||||
const checkboxStateHandler = this._register(new CheckboxStateHandler());
|
||||
this._register(checkboxStateHandler.onDidChangeCheckboxState(items => {
|
||||
items.forEach(item => this.tree?.rerender(item));
|
||||
this._onDidChangeCheckboxState.fire(items);
|
||||
}));
|
||||
const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner, checkboxStateHandler);
|
||||
const widgetAriaLabel = this._title;
|
||||
|
||||
this.tree = this._register(this.instantiationService.createInstance(Tree, this.id, this.treeContainer!, new TreeViewDelegate(), [renderer],
|
||||
@@ -641,7 +650,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
|
||||
}
|
||||
},
|
||||
expandOnlyOnTwistieClick: (e: ITreeItem) => {
|
||||
return !!e.command || this.configurationService.getValue<'singleClick' | 'doubleClick'>('workbench.tree.expandMode') === 'doubleClick';
|
||||
return !!e.command || e.checkboxChecked !== undefined || this.configurationService.getValue<'singleClick' | 'doubleClick'>('workbench.tree.expandMode') === 'doubleClick';
|
||||
},
|
||||
collapseByDefault: (e: ITreeItem): boolean => {
|
||||
return e.collapsibleState !== TreeItemCollapsibleState.Expanded;
|
||||
@@ -685,6 +694,9 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
|
||||
if (!e.browserEvent) {
|
||||
return;
|
||||
}
|
||||
if ((e.browserEvent.target as HTMLElement).classList.contains(TreeItemCheckbox.checkboxClass)) {
|
||||
return;
|
||||
}
|
||||
const selection = this.tree!.getSelection();
|
||||
const command = await this.resolveCommand(selection.length === 1 ? selection[0] : undefined);
|
||||
|
||||
@@ -1009,6 +1021,8 @@ interface ITreeExplorerTemplateData {
|
||||
container: HTMLElement;
|
||||
resourceLabel: IResourceLabel;
|
||||
icon: HTMLElement;
|
||||
checkboxContainer: HTMLElement;
|
||||
checkbox?: TreeItemCheckbox;
|
||||
actionBar: ActionBar;
|
||||
}
|
||||
|
||||
@@ -1025,6 +1039,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
private labels: ResourceLabels,
|
||||
private actionViewItemProvider: IActionViewItemProvider,
|
||||
private aligner: Aligner,
|
||||
private checkboxStateHandler: CheckboxStateHandler,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
@@ -1050,6 +1065,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
renderTemplate(container: HTMLElement): ITreeExplorerTemplateData {
|
||||
container.classList.add('custom-view-tree-node-item');
|
||||
|
||||
const checkboxContainer = DOM.append(container, DOM.$(''));
|
||||
const resourceLabel = this.labels.create(container, { supportHighlights: true, hoverDelegate: this._hoverDelegate });
|
||||
const icon = DOM.prepend(resourceLabel.element, DOM.$('.custom-view-tree-node-item-icon'));
|
||||
const actionsContainer = DOM.append(resourceLabel.element, DOM.$('.actions'));
|
||||
@@ -1057,7 +1073,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
actionViewItemProvider: this.actionViewItemProvider
|
||||
});
|
||||
|
||||
return { resourceLabel, icon, actionBar, container, elementDisposable: Disposable.None };
|
||||
return { resourceLabel, icon, checkboxContainer, actionBar, container, elementDisposable: Disposable.None };
|
||||
}
|
||||
|
||||
private getHover(label: string | undefined, resource: URI | null, node: ITreeItem): string | ITooltipMarkdownString | undefined {
|
||||
@@ -1123,6 +1139,11 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
commandEnabled = isTreeCommandEnabled(node.command, this.contextKeyService);
|
||||
}
|
||||
|
||||
const disposableStore = new DisposableStore();
|
||||
templateData.elementDisposable = disposableStore;
|
||||
|
||||
this.renderCheckbox(node, templateData, disposableStore);
|
||||
|
||||
if (resource) {
|
||||
const fileDecorations = this.configurationService.getValue<{ colors: boolean; badges: boolean }>('explorer.decorations');
|
||||
const labelResource = resource ? resource : URI.parse('missing:_icon_resource');
|
||||
@@ -1171,8 +1192,6 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
|
||||
templateData.actionBar.context = <TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle };
|
||||
|
||||
const disposableStore = new DisposableStore();
|
||||
templateData.elementDisposable = disposableStore;
|
||||
const menuActions = this.menus.getResourceActions(node);
|
||||
if (menuActions.menu) {
|
||||
disposableStore.add(menuActions.menu);
|
||||
@@ -1187,6 +1206,21 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
disposableStore.add(toDisposable(() => this.treeViewsService.removeRenderedTreeItemElement(node)));
|
||||
}
|
||||
|
||||
private renderCheckbox(node: ITreeItem, templateData: ITreeExplorerTemplateData, disposableStore: DisposableStore) {
|
||||
if (node.checkboxChecked !== undefined) {
|
||||
if (!templateData.checkbox) {
|
||||
const checkbox = new TreeItemCheckbox(templateData.checkboxContainer, this.checkboxStateHandler, this.themeService);
|
||||
templateData.checkbox = checkbox;
|
||||
disposableStore.add({ dispose: () => { checkbox.dispose(); templateData.checkbox = undefined; } });
|
||||
}
|
||||
templateData.checkbox.render(node);
|
||||
}
|
||||
else if (templateData.checkbox) {
|
||||
templateData.checkbox.dispose();
|
||||
templateData.checkbox = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private setAlignment(container: HTMLElement, treeItem: ITreeItem) {
|
||||
container.parentElement!.classList.toggle('align-icon-with-twisty', this.aligner.alignIconWithTwisty(treeItem));
|
||||
}
|
||||
@@ -1244,9 +1278,11 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
|
||||
class Aligner extends Disposable {
|
||||
private _tree: WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore> | undefined;
|
||||
private hasCheckboxes: boolean;
|
||||
|
||||
constructor(private themeService: IThemeService) {
|
||||
super();
|
||||
this.hasCheckboxes = false;
|
||||
}
|
||||
|
||||
set tree(tree: WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore>) {
|
||||
@@ -1254,6 +1290,9 @@ class Aligner extends Disposable {
|
||||
}
|
||||
|
||||
public alignIconWithTwisty(treeItem: ITreeItem): boolean {
|
||||
if (this.hasCheckboxes) {
|
||||
return false;
|
||||
}
|
||||
if (treeItem.collapsibleState !== TreeItemCollapsibleState.None) {
|
||||
return false;
|
||||
}
|
||||
@@ -1262,6 +1301,12 @@ class Aligner extends Disposable {
|
||||
}
|
||||
|
||||
if (this._tree) {
|
||||
if (!this.hasCheckboxes && treeItem.checkboxChecked !== undefined) {
|
||||
this.hasCheckboxes = true;
|
||||
// TODO: rerender already rendered elements
|
||||
this._tree.rerender();
|
||||
return false;
|
||||
}
|
||||
const parent: ITreeItem = this._tree.getParentElement(treeItem) || this._tree.getInput();
|
||||
if (this.hasIcon(parent)) {
|
||||
return !!parent.children && parent.children.some(c => c.collapsibleState !== TreeItemCollapsibleState.None && !this.hasIcon(c));
|
||||
|
||||
@@ -675,6 +675,8 @@ export interface ITreeView extends IDisposable {
|
||||
|
||||
readonly onDidChangeWelcomeState: Event<void>;
|
||||
|
||||
readonly onDidChangeCheckboxState: Event<ITreeItem[]>;
|
||||
|
||||
readonly container: any | undefined;
|
||||
|
||||
refresh(treeItems?: ITreeItem[]): Promise<void>;
|
||||
@@ -772,6 +774,8 @@ export interface ITreeItem {
|
||||
children?: ITreeItem[];
|
||||
|
||||
accessibilityInformation?: IAccessibilityInformation;
|
||||
|
||||
checkboxChecked?: boolean;
|
||||
}
|
||||
|
||||
export class ResolvableTreeItem implements ITreeItem {
|
||||
|
||||
@@ -65,6 +65,7 @@ export const allApiProposals = Object.freeze({
|
||||
textSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts',
|
||||
timeline: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.timeline.d.ts',
|
||||
tokenInformation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tokenInformation.d.ts',
|
||||
treeItemCheckbox: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeItemCheckbox.d.ts',
|
||||
treeViewReveal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewReveal.d.ts',
|
||||
tunnels: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tunnels.d.ts',
|
||||
workspaceTrust: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.workspaceTrust.d.ts'
|
||||
|
||||
45
src/vscode-dts/vscode.proposed.treeItemCheckbox.d.ts
vendored
Normal file
45
src/vscode-dts/vscode.proposed.treeItemCheckbox.d.ts
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
declare module 'vscode' {
|
||||
|
||||
export class TreeItem2 extends TreeItem {
|
||||
/**
|
||||
* [TreeItemCheckboxState](#TreeItemCheckboxState) of the tree item.
|
||||
*/
|
||||
checkboxState?: TreeItemCheckboxState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checkbox state of the tree item
|
||||
*/
|
||||
export enum TreeItemCheckboxState {
|
||||
/**
|
||||
* Determines an item is unchecked
|
||||
*/
|
||||
Unchecked = 0,
|
||||
/**
|
||||
* Determines an item is checked
|
||||
*/
|
||||
Checked = 1
|
||||
}
|
||||
|
||||
/**
|
||||
* A data provider that provides tree data
|
||||
*/
|
||||
export interface TreeView<T> {
|
||||
/**
|
||||
* An event to signal that an element or root has either been checked or unchecked.
|
||||
*/
|
||||
onDidChangeTreeCheckbox: Event<TreeCheckboxChangeEvent<T>>;
|
||||
}
|
||||
|
||||
export interface TreeCheckboxChangeEvent<T> {
|
||||
/**
|
||||
* The item that was checked or unchecked.
|
||||
*/
|
||||
readonly items: [T, TreeItemCheckboxState][];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user