From e7795e1ffd5d8477291365f67a173adae6b50097 Mon Sep 17 00:00:00 2001 From: xxw Date: Fri, 6 Dec 2024 10:09:07 +0800 Subject: [PATCH 1/6] feat: impl treeItem.checkboxstate --- .../src/recycle-tree/tree/TreeNode.ts | 9 +++ .../src/recycle-tree/types/tree-node.ts | 10 ++++ .../components/extension-tree-view-node.tsx | 21 ++++++- .../components/extension-tree-view.tsx | 9 +++ .../vscode/api/main.thread.treeview.ts | 2 + .../api/tree-view/tree-view.model.service.ts | 10 ++++ .../api/tree-view/tree-view.node.defined.ts | 20 ++++++- .../extension/src/common/vscode/treeview.ts | 25 +++++++++ .../hosted/api/vscode/ext.host.treeview.ts | 55 ++++++++++++++++++- 9 files changed, 156 insertions(+), 5 deletions(-) diff --git a/packages/components/src/recycle-tree/tree/TreeNode.ts b/packages/components/src/recycle-tree/tree/TreeNode.ts index 648249baa6..02dfe3a706 100644 --- a/packages/components/src/recycle-tree/tree/TreeNode.ts +++ b/packages/components/src/recycle-tree/tree/TreeNode.ts @@ -25,6 +25,7 @@ import { MetadataChangeType, TreeNodeEvent, TreeNodeType, + TreeViewItemCheckboxInfo, WatchEvent, } from '../types'; @@ -243,6 +244,14 @@ export class TreeNode implements ITreeNode { return this._path; } + get checkboxInfo(): TreeViewItemCheckboxInfo { + return { + checked: false, + tooltip: '', + accessibilityInformation: this.accessibilityInformation, + }; + } + get accessibilityInformation(): IAccessibilityInformation { return { label: this.name, diff --git a/packages/components/src/recycle-tree/types/tree-node.ts b/packages/components/src/recycle-tree/types/tree-node.ts index 67e9059ba8..0f4339bf70 100644 --- a/packages/components/src/recycle-tree/types/tree-node.ts +++ b/packages/components/src/recycle-tree/types/tree-node.ts @@ -3,6 +3,12 @@ export interface IAccessibilityInformation { role?: string; } +export interface TreeViewItemCheckboxInfo { + checked: boolean | undefined; + tooltip?: string; + accessibilityInformation?: IAccessibilityInformation; +} + export interface ITreeNode { /** * 唯一标识id @@ -41,6 +47,10 @@ export interface ITreeNode { * 父节点,但为undefined时,表示该节点为根节点 */ readonly parent: ICompositeTreeNode | undefined; + /** + * 节点的选择信息 + */ + readonly checkboxInfo?: TreeViewItemCheckboxInfo; /** * 节点无障碍信息 */ diff --git a/packages/extension/src/browser/components/extension-tree-view-node.tsx b/packages/extension/src/browser/components/extension-tree-view-node.tsx index 1891900efa..71408df85a 100644 --- a/packages/extension/src/browser/components/extension-tree-view-node.tsx +++ b/packages/extension/src/browser/components/extension-tree-view-node.tsx @@ -1,7 +1,14 @@ import cls from 'classnames'; import React, { CSSProperties, DragEvent, FC, MouseEvent, ReactNode, useCallback, useEffect, useState } from 'react'; -import { ClasslistComposite, INodeRendererProps, Loading, PromptHandle, TreeNodeType } from '@opensumi/ide-components'; +import { + CheckBox, + ClasslistComposite, + INodeRendererProps, + Loading, + PromptHandle, + TreeNodeType, +} from '@opensumi/ide-components'; import { getIcon, useDesignStyles } from '@opensumi/ide-core-browser'; import { TitleActionList } from '@opensumi/ide-core-browser/lib/components/actions'; import { MenuId } from '@opensumi/ide-core-browser/lib/menu/next'; @@ -21,6 +28,7 @@ export interface ITreeViewNodeProps { decorations?: ClasslistComposite; onTwistierClick?: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType) => void; onClick: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType) => void; + onCheck: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType) => void; onContextMenu?: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType) => void; onDragStart?: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode) => void; onDragEnter?: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode) => void; @@ -36,6 +44,7 @@ export type TreeViewNodeRenderedProps = ITreeViewNodeProps & INodeRendererProps; export const TreeViewNode: FC = ({ item, onClick, + onCheck, onContextMenu, itemType, leftPadding = 8, @@ -138,6 +147,13 @@ export const TreeViewNode: FC = ({ [item, onDrop], ); + const hadnleCheck = useCallback( + (event: MouseEvent) => { + onCheck(event, item, itemType); + }, + [item, itemType, onCheck], + ); + const isDirectory = itemType === TreeNodeType.CompositeTreeNode; const paddingLeft = isDirectory ? `${defaultLeftPadding + (item.depth || 0) * (leftPadding || 0)}px` @@ -167,6 +183,8 @@ export const TreeViewNode: FC = ({
); + const renderCheckbox = (node: ExtensionCompositeTreeNode | ExtensionTreeNode) =>
; + const renderDisplayName = (node: ExtensionCompositeTreeNode | ExtensionTreeNode) => { const displayName = () => { if (node.highlights) { @@ -277,6 +295,7 @@ export const TreeViewNode: FC = ({ >
{renderTwice(item)} + {renderCheckbox(item)} {renderIcon(item)}
{renderDisplayName(item)} diff --git a/packages/extension/src/browser/components/extension-tree-view.tsx b/packages/extension/src/browser/components/extension-tree-view.tsx index cd1dab2cd0..0979e095e5 100644 --- a/packages/extension/src/browser/components/extension-tree-view.tsx +++ b/packages/extension/src/browser/components/extension-tree-view.tsx @@ -117,6 +117,11 @@ export const ExtensionTabBarTreeView = ({ [canSelectMany, model], ); + const handleItemChecked = useCallback( + (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType) => {}, + [canSelectMany, model], + ); + const handleContextMenu = useCallback( (ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode) => { const { handleContextMenu } = model; @@ -201,6 +206,7 @@ export const ExtensionTabBarTreeView = ({ isVisible={isVisible} handleTreeReady={handleTreeReady} handleItemClicked={handleItemClicked} + handleItemChecked={handleItemChecked} handleTwistierClick={handleTwistierClick} handleContextMenu={handleContextMenu} handleDragStart={handleDragStart} @@ -226,6 +232,7 @@ interface TreeViewProps { model: ExtensionTreeViewModel; handleTreeReady(handle: IRecycleTreeHandle): void; handleItemClicked(ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType): void; + handleItemChecked(ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType): void; handleTwistierClick(ev: MouseEvent, item: ExtensionCompositeTreeNode): void; handleContextMenu(ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode): void; handleDragStart(ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode): void; @@ -255,6 +262,7 @@ const TreeView = memo( dataProvider, handleTreeReady, handleItemClicked, + handleItemChecked, handleTwistierClick, handleContextMenu, handleDragStart, @@ -301,6 +309,7 @@ const TreeView = memo( itemType={props.itemType} decorations={model.decorations.getDecorations(props.item as any)} onClick={handleItemClicked} + onCheck={handleItemChecked} onTwistierClick={handleTwistierClick} onContextMenu={handleContextMenu} onDragStart={handleDragStart} diff --git a/packages/extension/src/browser/vscode/api/main.thread.treeview.ts b/packages/extension/src/browser/vscode/api/main.thread.treeview.ts index 8ae386f400..6dd4416a05 100644 --- a/packages/extension/src/browser/vscode/api/main.thread.treeview.ts +++ b/packages/extension/src/browser/vscode/api/main.thread.treeview.ts @@ -414,6 +414,7 @@ export class TreeViewDataProvider extends Tree { item.contextValue || '', item.id, actions, + item.checkboxInfo, item.accessibilityInformation, expanded, item.resourceUri, @@ -437,6 +438,7 @@ export class TreeViewDataProvider extends Tree { item.contextValue || '', item.id, actions, + item.checkboxInfo, item.accessibilityInformation, item.resourceUri, ); diff --git a/packages/extension/src/browser/vscode/api/tree-view/tree-view.model.service.ts b/packages/extension/src/browser/vscode/api/tree-view/tree-view.model.service.ts index bd30c3b7b6..dc28d4a6fc 100644 --- a/packages/extension/src/browser/vscode/api/tree-view/tree-view.model.service.ts +++ b/packages/extension/src/browser/vscode/api/tree-view/tree-view.model.service.ts @@ -230,6 +230,7 @@ export class ExtensionTreeViewModel { treeItemId: string; expanded: boolean; }> = new Emitter(); + private readonly onDidUpdateEmitter = new Emitter(); private _isMultiSelected = false; private revealDelayer = new ThrottledDelayer(ExtensionTreeViewModel.DEFAULT_REVEAL_DELAY); @@ -265,6 +266,10 @@ export class ExtensionTreeViewModel { return this.onDidSelectedNodeChangeEmitter.event; } + get onDidUpdate() { + return this.onDidUpdateEmitter.event; + } + get extensionTreeHandle() { return this._extensionTreeHandle; } @@ -942,6 +947,11 @@ export class ExtensionTreeViewModel { } }; + handleItemCheck = async (item: ExtensionCompositeTreeNode | ExtensionTreeNode, type: TreeNodeType) => { + item.checkboxInfo.checked = !item.checkboxInfo.checked; + this.onDidUpdateEmitter.fire([item]); + }; + handleContextMenu = (ev: MouseEvent, item?: ExtensionCompositeTreeNode | ExtensionTreeNode) => { ev.stopPropagation(); ev.preventDefault(); diff --git a/packages/extension/src/browser/vscode/api/tree-view/tree-view.node.defined.ts b/packages/extension/src/browser/vscode/api/tree-view/tree-view.node.defined.ts index eb1c05de07..2796ab035d 100644 --- a/packages/extension/src/browser/vscode/api/tree-view/tree-view.node.defined.ts +++ b/packages/extension/src/browser/vscode/api/tree-view/tree-view.node.defined.ts @@ -2,7 +2,7 @@ import { CompositeTreeNode, ITree, TreeNode } from '@opensumi/ide-components'; import { MenuNode } from '@opensumi/ide-core-browser/lib/menu/next'; import { IAccessibilityInformation, Uri, UriComponents, isObject, isString } from '@opensumi/ide-core-common'; -import { ITreeItemLabel } from '../../../../common/vscode'; +import { ITreeItemLabel, TreeViewItemCheckboxInfo } from '../../../../common/vscode'; import { ICommand } from '../../../../common/vscode/models'; import { TreeViewDataProvider } from '../main.thread.treeview'; @@ -58,6 +58,7 @@ export class ExtensionCompositeTreeNode extends CompositeTreeNode { public contextValue: string = '', public treeItemId: string = '', public actions: MenuNode[], + private _checkboxInfo?: TreeViewItemCheckboxInfo, private _accessibilityInformation?: IAccessibilityInformation, expanded?: boolean, sourceUri?: UriComponents, @@ -92,6 +93,14 @@ export class ExtensionCompositeTreeNode extends CompositeTreeNode { return this._displayName; } + get checkboxInfo() { + return { + checked: this._checkboxInfo?.checked, + tooltip: this._checkboxInfo?.tooltip, + accessibilityInformation: this._checkboxInfo?.accessibilityInformation, + }; + } + get accessibilityInformation() { return { role: this._accessibilityInformation?.role || 'treeitem', @@ -147,6 +156,7 @@ export class ExtensionTreeNode extends TreeNode { public contextValue: string = '', public treeItemId: string = '', public actions: MenuNode[], + private _checkboxInfo?: TreeViewItemCheckboxInfo, private _accessibilityInformation?: IAccessibilityInformation, private sourceUri?: UriComponents, ) { @@ -180,6 +190,14 @@ export class ExtensionTreeNode extends TreeNode { return this._displayName; } + get checkboxInfo() { + return { + checked: this._checkboxInfo?.checked, + tooltip: this._checkboxInfo?.tooltip, + accessibilityInformation: this._checkboxInfo?.accessibilityInformation, + }; + } + get accessibilityInformation() { return { role: this._accessibilityInformation?.role || 'treeitem', diff --git a/packages/extension/src/common/vscode/treeview.ts b/packages/extension/src/common/vscode/treeview.ts index c916eb7d95..b35c519d28 100644 --- a/packages/extension/src/common/vscode/treeview.ts +++ b/packages/extension/src/common/vscode/treeview.ts @@ -39,6 +39,7 @@ export interface IExtHostTreeView { $setExpanded(treeViewId: string, treeItemId: string, expanded: boolean): Promise; $setSelection(treeViewId: string, treeItemHandles: string[]): void; $setVisible(treeViewId: string, visible: boolean): void; + $checkStateChanged(treeViewId: string, itemIds: { id: string; checked: boolean }[]): Promise; $resolveTreeItem(treeViewId: string, treeItemId: string, token: CancellationToken): Promise; $handleDrop( destinationViewId: string, @@ -77,6 +78,17 @@ export interface ITreeItemLabel { strikethrough?: boolean; } +export interface TreeViewItemCheckboxInfo { + checked: boolean | undefined; + tooltip?: string; + accessibilityInformation?: IAccessibilityInformation; +} + +export enum TreeItemCheckboxState { + Unchecked = 0, + Checked = 1, +} + export class TreeViewItem { id: string; @@ -98,6 +110,8 @@ export class TreeViewItem { contextValue?: string; + checkboxInfo?: TreeViewItemCheckboxInfo; + command?: ICommand; accessibilityInformation?: IAccessibilityInformation; @@ -116,6 +130,12 @@ export interface TreeView extends vscode.TreeView { * 当节点可见性变化时触发的事件 */ readonly onDidChangeVisibility: Event; + + /** + * 表示元素已被选中或未选中的事件。 + */ + readonly onDidChangeCheckboxState: Event>; + /** * 当节点选中时触发的事件 */ @@ -174,6 +194,11 @@ export interface ViewBadge { } export interface TreeViewBaseOptions { + /** + * 管理复选框状态 + */ + manageCheckboxStateManually?: boolean; + /** * 是否展示折叠所有功能(panel上功能) */ diff --git a/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts b/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts index ff63552691..98a037bfd7 100644 --- a/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts +++ b/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts @@ -21,6 +21,7 @@ import { ITreeViewRevealOptions, ITreeViewsService, MainThreadAPIIdentifier, + TreeItemCheckboxState, TreeView, TreeViewItem, TreeViewSelection, @@ -217,6 +218,20 @@ export class ExtHostTreeViews implements IExtHostTreeView { treeView.setVisible(isVisible); } + /** + * 设置节点的选择状态 + * @param treeViewId + * @param itemIds + * @returns + */ + $checkStateChanged(treeViewId: string, itemIds: { id: string; checked: boolean }[]): Promise { + const treeView = this.treeViews.get(treeViewId); + if (!treeView) { + throw new Error('No tree view with id ' + treeViewId); + } + return treeView.checkStateChanged(itemIds); + } + async $handleDrop( destinationViewId: string, requestId: number, @@ -308,6 +323,9 @@ class ExtHostTreeView implements IDisposable { private readonly onDidChangeVisibilityEmitter = new Emitter(); readonly onDidChangeVisibility = this.onDidChangeVisibilityEmitter.event; + private readonly onDidChangeCheckboxStateEmitter = new Emitter>(); + readonly onDidChangeCheckboxState = this.onDidChangeCheckboxStateEmitter.event; + private _visible = false; private selectedItemIds = new Set(); @@ -321,9 +339,6 @@ class ExtHostTreeView implements IDisposable { private readonly dataProvider: vscode.TreeDataProvider; private readonly dndController: vscode.TreeDragAndDropController | undefined; - private readonly onDidChangeCheckboxStateEmitter = new Emitter>(); - readonly onDidChangeCheckboxState = this.onDidChangeCheckboxStateEmitter.event; - private _onDidChangeData: Emitter> = new Emitter>(); private _title: string; @@ -353,6 +368,7 @@ class ExtHostTreeView implements IDisposable { this.disposable.add(this._onDidChangeData); // 将 options 直接取值,避免循环引用导致序列化异常 proxy.$registerTreeDataProvider(treeViewId, { + manageCheckboxStateManually: options.manageCheckboxStateManually, showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, dropMimeTypes, @@ -637,6 +653,23 @@ class ExtHostTreeView implements IDisposable { ); } + async checkStateChanged(items: readonly { id: string; checked: boolean }[]): Promise { + const transformed: [T, TreeItemCheckboxState][] = []; + items.forEach((item) => { + const node = this.getTreeItem(item.id); + if (node) { + transformed.push([node, item.checked ? TreeItemCheckboxState.Checked : TreeItemCheckboxState.Unchecked]); + const treeViewItem = this.element2TreeViewItem.get(node); + if (treeViewItem) { + treeViewItem.checkboxInfo!.checked = item.checked; + } + } + }); + this.onDidChangeCheckboxStateEmitter.fire({ + items: transformed, + }); + } + /** * 在节点被点击或者打开时,获取原有的 command 为 undefined 时被调用 * 在节点被 Hover 时,获取原有的 tooltip 为 undefined 时被调用 @@ -765,6 +798,21 @@ class ExtHostTreeView implements IDisposable { }; } } + let checkboxInfo; + if (treeItem.checkboxState === undefined) { + checkboxInfo = undefined; + } else if (typeof treeItem.checkboxState === 'object') { + checkboxInfo = { + checked: treeItem.checkboxState.state === TreeItemCheckboxState.Checked, + tooltip: treeItem.checkboxState.tooltip, + accessibilityInformation: treeItem.accessibilityInformation, + }; + } else { + checkboxInfo = { + checked: treeItem.checkboxState === TreeItemCheckboxState.Checked, + }; + } + const treeViewItem = { id, label, @@ -776,6 +824,7 @@ class ExtHostTreeView implements IDisposable { tooltip: treeItem.tooltip, collapsibleState: treeItem.collapsibleState, contextValue: treeItem.contextValue, + checkboxInfo, accessibilityInformation: treeItem.accessibilityInformation, command: treeItem.command ? this.commands.converter.toInternal(treeItem.command, this.disposable) : undefined, ...props, From 0088d0ff1bc6daa86f85c460272b0f6583b67dfc Mon Sep 17 00:00:00 2001 From: xxw Date: Tue, 10 Dec 2024 17:41:12 +0800 Subject: [PATCH 2/6] feat: treeview support treeItem.checkboxstate --- .../src/recycle-tree/tree/TreeNode.ts | 2 +- .../src/recycle-tree/types/tree-node.ts | 2 +- .../components/extension-tree-view-node.tsx | 43 +++++++++++++++---- .../components/extension-tree-view.tsx | 17 +++++--- .../vscode/api/main.thread.treeview.ts | 39 ++++++++++++++++- .../api/tree-view/tree-view.model.service.ts | 18 ++++---- .../api/tree-view/tree-view.node.defined.ts | 12 +----- .../extension/src/common/vscode/treeview.ts | 6 +-- .../hosted/api/vscode/ext.host.treeview.ts | 7 +-- 9 files changed, 103 insertions(+), 43 deletions(-) diff --git a/packages/components/src/recycle-tree/tree/TreeNode.ts b/packages/components/src/recycle-tree/tree/TreeNode.ts index 02dfe3a706..94261c5671 100644 --- a/packages/components/src/recycle-tree/tree/TreeNode.ts +++ b/packages/components/src/recycle-tree/tree/TreeNode.ts @@ -244,7 +244,7 @@ export class TreeNode implements ITreeNode { return this._path; } - get checkboxInfo(): TreeViewItemCheckboxInfo { + get checkboxInfo(): TreeViewItemCheckboxInfo | undefined { return { checked: false, tooltip: '', diff --git a/packages/components/src/recycle-tree/types/tree-node.ts b/packages/components/src/recycle-tree/types/tree-node.ts index 0f4339bf70..88ca087e47 100644 --- a/packages/components/src/recycle-tree/types/tree-node.ts +++ b/packages/components/src/recycle-tree/types/tree-node.ts @@ -4,7 +4,7 @@ export interface IAccessibilityInformation { } export interface TreeViewItemCheckboxInfo { - checked: boolean | undefined; + checked: boolean; tooltip?: string; accessibilityInformation?: IAccessibilityInformation; } diff --git a/packages/extension/src/browser/components/extension-tree-view-node.tsx b/packages/extension/src/browser/components/extension-tree-view-node.tsx index 71408df85a..2731b5f8ef 100644 --- a/packages/extension/src/browser/components/extension-tree-view-node.tsx +++ b/packages/extension/src/browser/components/extension-tree-view-node.tsx @@ -1,5 +1,15 @@ import cls from 'classnames'; -import React, { CSSProperties, DragEvent, FC, MouseEvent, ReactNode, useCallback, useEffect, useState } from 'react'; +import React, { + CSSProperties, + DragEvent, + FC, + FormEvent, + MouseEvent, + ReactNode, + useCallback, + useEffect, + useState, +} from 'react'; import { CheckBox, @@ -28,7 +38,7 @@ export interface ITreeViewNodeProps { decorations?: ClasslistComposite; onTwistierClick?: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType) => void; onClick: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType) => void; - onCheck: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType) => void; + onChange: (item: ExtensionTreeNode | ExtensionCompositeTreeNode) => void; onContextMenu?: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType) => void; onDragStart?: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode) => void; onDragEnter?: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode) => void; @@ -44,7 +54,7 @@ export type TreeViewNodeRenderedProps = ITreeViewNodeProps & INodeRendererProps; export const TreeViewNode: FC = ({ item, onClick, - onCheck, + onChange, onContextMenu, itemType, leftPadding = 8, @@ -147,11 +157,12 @@ export const TreeViewNode: FC = ({ [item, onDrop], ); - const hadnleCheck = useCallback( - (event: MouseEvent) => { - onCheck(event, item, itemType); + const handleCheckBoxChange = useCallback( + (event: FormEvent) => { + onChange(item); + event.stopPropagation(); }, - [item, itemType, onCheck], + [item, onChange], ); const isDirectory = itemType === TreeNodeType.CompositeTreeNode; @@ -183,7 +194,23 @@ export const TreeViewNode: FC = ({
); - const renderCheckbox = (node: ExtensionCompositeTreeNode | ExtensionTreeNode) =>
; + const renderCheckbox = (node: ExtensionCompositeTreeNode | ExtensionTreeNode) => { + if (node.checkboxInfo === undefined) { + return null; + } + + return ( + handleCheckBoxChange(event)} + checked={!!node.checkboxInfo.checked} + id={node.treeItemId} + role={node.checkboxInfo.accessibilityInformation?.role} + title={node.checkboxInfo.tooltip} + /> + ); + }; const renderDisplayName = (node: ExtensionCompositeTreeNode | ExtensionTreeNode) => { const displayName = () => { diff --git a/packages/extension/src/browser/components/extension-tree-view.tsx b/packages/extension/src/browser/components/extension-tree-view.tsx index 0979e095e5..7302200b57 100644 --- a/packages/extension/src/browser/components/extension-tree-view.tsx +++ b/packages/extension/src/browser/components/extension-tree-view.tsx @@ -117,8 +117,13 @@ export const ExtensionTabBarTreeView = ({ [canSelectMany, model], ); - const handleItemChecked = useCallback( - (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType) => {}, + const handleCheckBoxChanged = useCallback( + (item: ExtensionTreeNode | ExtensionCompositeTreeNode) => { + const { handleCheckBoxChange } = model; + if (item) { + handleCheckBoxChange(item); + } + }, [canSelectMany, model], ); @@ -206,7 +211,7 @@ export const ExtensionTabBarTreeView = ({ isVisible={isVisible} handleTreeReady={handleTreeReady} handleItemClicked={handleItemClicked} - handleItemChecked={handleItemChecked} + handleCheckBoxChanged={handleCheckBoxChanged} handleTwistierClick={handleTwistierClick} handleContextMenu={handleContextMenu} handleDragStart={handleDragStart} @@ -232,7 +237,7 @@ interface TreeViewProps { model: ExtensionTreeViewModel; handleTreeReady(handle: IRecycleTreeHandle): void; handleItemClicked(ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType): void; - handleItemChecked(ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType): void; + handleCheckBoxChanged(item: ExtensionTreeNode | ExtensionCompositeTreeNode): void; handleTwistierClick(ev: MouseEvent, item: ExtensionCompositeTreeNode): void; handleContextMenu(ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode): void; handleDragStart(ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode): void; @@ -262,7 +267,7 @@ const TreeView = memo( dataProvider, handleTreeReady, handleItemClicked, - handleItemChecked, + handleCheckBoxChanged, handleTwistierClick, handleContextMenu, handleDragStart, @@ -309,7 +314,7 @@ const TreeView = memo( itemType={props.itemType} decorations={model.decorations.getDecorations(props.item as any)} onClick={handleItemClicked} - onCheck={handleItemChecked} + onChange={handleCheckBoxChanged} onTwistierClick={handleTwistierClick} onContextMenu={handleContextMenu} onDragStart={handleDragStart} diff --git a/packages/extension/src/browser/vscode/api/main.thread.treeview.ts b/packages/extension/src/browser/vscode/api/main.thread.treeview.ts index 6dd4416a05..a947a1f744 100644 --- a/packages/extension/src/browser/vscode/api/main.thread.treeview.ts +++ b/packages/extension/src/browser/vscode/api/main.thread.treeview.ts @@ -1,5 +1,5 @@ import { Autowired, INJECTOR_TOKEN, Injectable, Injector, Optional } from '@opensumi/di'; -import { ITreeNodeOrCompositeTreeNode, Tree } from '@opensumi/ide-components'; +import { CompositeTreeNode, ITreeNodeOrCompositeTreeNode, Tree } from '@opensumi/ide-components'; import { IRPCProtocol } from '@opensumi/ide-connection'; import { BinaryBuffer, @@ -588,6 +588,43 @@ export class TreeViewDataProvider extends Tree { super.dispose(); this.treeItemId2TreeNode.clear(); } + + markAsChecked( + node: ExtensionTreeNode | ExtensionCompositeTreeNode, + checked: boolean, + manageCheckboxStateManually?: boolean, + ): void { + function findParentsToChange(child: ITreeNodeOrCompositeTreeNode, nodes: ITreeNodeOrCompositeTreeNode[]): void { + if ( + child.parent?.checkboxInfo !== undefined && + child.parent.checkboxInfo.checked !== checked && + (!checked || + !child.parent.children?.some((candidate) => candidate !== child && candidate.checkboxInfo?.checked === false)) + ) { + nodes.push(child.parent); + findParentsToChange(child.parent, nodes); + } + } + + function findChildrenToChange(parent: ITreeNodeOrCompositeTreeNode, nodes: ITreeNodeOrCompositeTreeNode[]): void { + if (CompositeTreeNode.is(parent)) { + parent.children?.forEach((child) => { + if (child.checkboxInfo !== undefined && child.checkboxInfo.checked !== checked) { + nodes.push(child); + } + findChildrenToChange(child, nodes); + }); + } + } + + const nodesToChange = [node]; + if (!manageCheckboxStateManually) { + findParentsToChange(node, nodesToChange); + findChildrenToChange(node, nodesToChange); + } + nodesToChange.forEach((n) => (n.checkboxInfo!.checked = checked)); + this.proxy?.$checkStateChanged(this.treeViewId, [{ treeItemId: node.treeItemId, checked }]); + } } export class TreeViewDragAndDropController extends Disposable implements ITreeViewDragAndDropController { diff --git a/packages/extension/src/browser/vscode/api/tree-view/tree-view.model.service.ts b/packages/extension/src/browser/vscode/api/tree-view/tree-view.model.service.ts index dc28d4a6fc..f7da8bbcfa 100644 --- a/packages/extension/src/browser/vscode/api/tree-view/tree-view.model.service.ts +++ b/packages/extension/src/browser/vscode/api/tree-view/tree-view.model.service.ts @@ -230,7 +230,6 @@ export class ExtensionTreeViewModel { treeItemId: string; expanded: boolean; }> = new Emitter(); - private readonly onDidUpdateEmitter = new Emitter(); private _isMultiSelected = false; private revealDelayer = new ThrottledDelayer(ExtensionTreeViewModel.DEFAULT_REVEAL_DELAY); @@ -266,10 +265,6 @@ export class ExtensionTreeViewModel { return this.onDidSelectedNodeChangeEmitter.event; } - get onDidUpdate() { - return this.onDidUpdateEmitter.event; - } - get extensionTreeHandle() { return this._extensionTreeHandle; } @@ -947,11 +942,6 @@ export class ExtensionTreeViewModel { } }; - handleItemCheck = async (item: ExtensionCompositeTreeNode | ExtensionTreeNode, type: TreeNodeType) => { - item.checkboxInfo.checked = !item.checkboxInfo.checked; - this.onDidUpdateEmitter.fire([item]); - }; - handleContextMenu = (ev: MouseEvent, item?: ExtensionCompositeTreeNode | ExtensionTreeNode) => { ev.stopPropagation(); ev.preventDefault(); @@ -1093,4 +1083,12 @@ export class ExtensionTreeViewModel { } }); } + + handleCheckBoxChange = async (item: ExtensionCompositeTreeNode | ExtensionTreeNode) => { + this.treeViewDataProvider.markAsChecked( + item, + !item.checkboxInfo!.checked, + this.treeViewOptions.manageCheckboxStateManually, + ); + }; } diff --git a/packages/extension/src/browser/vscode/api/tree-view/tree-view.node.defined.ts b/packages/extension/src/browser/vscode/api/tree-view/tree-view.node.defined.ts index 2796ab035d..0e1ba5e48d 100644 --- a/packages/extension/src/browser/vscode/api/tree-view/tree-view.node.defined.ts +++ b/packages/extension/src/browser/vscode/api/tree-view/tree-view.node.defined.ts @@ -94,11 +94,7 @@ export class ExtensionCompositeTreeNode extends CompositeTreeNode { } get checkboxInfo() { - return { - checked: this._checkboxInfo?.checked, - tooltip: this._checkboxInfo?.tooltip, - accessibilityInformation: this._checkboxInfo?.accessibilityInformation, - }; + return this._checkboxInfo; } get accessibilityInformation() { @@ -191,11 +187,7 @@ export class ExtensionTreeNode extends TreeNode { } get checkboxInfo() { - return { - checked: this._checkboxInfo?.checked, - tooltip: this._checkboxInfo?.tooltip, - accessibilityInformation: this._checkboxInfo?.accessibilityInformation, - }; + return this._checkboxInfo; } get accessibilityInformation() { diff --git a/packages/extension/src/common/vscode/treeview.ts b/packages/extension/src/common/vscode/treeview.ts index b35c519d28..c9f4d2cea6 100644 --- a/packages/extension/src/common/vscode/treeview.ts +++ b/packages/extension/src/common/vscode/treeview.ts @@ -39,7 +39,7 @@ export interface IExtHostTreeView { $setExpanded(treeViewId: string, treeItemId: string, expanded: boolean): Promise; $setSelection(treeViewId: string, treeItemHandles: string[]): void; $setVisible(treeViewId: string, visible: boolean): void; - $checkStateChanged(treeViewId: string, itemIds: { id: string; checked: boolean }[]): Promise; + $checkStateChanged(treeViewId: string, itemIds: { treeItemId: string; checked: boolean }[]): Promise; $resolveTreeItem(treeViewId: string, treeItemId: string, token: CancellationToken): Promise; $handleDrop( destinationViewId: string, @@ -79,7 +79,7 @@ export interface ITreeItemLabel { } export interface TreeViewItemCheckboxInfo { - checked: boolean | undefined; + checked: boolean; tooltip?: string; accessibilityInformation?: IAccessibilityInformation; } @@ -195,7 +195,7 @@ export interface ViewBadge { export interface TreeViewBaseOptions { /** - * 管理复选框状态 + * 手动管理复选框状态 */ manageCheckboxStateManually?: boolean; diff --git a/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts b/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts index 98a037bfd7..d38e4483a9 100644 --- a/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts +++ b/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts @@ -224,7 +224,7 @@ export class ExtHostTreeViews implements IExtHostTreeView { * @param itemIds * @returns */ - $checkStateChanged(treeViewId: string, itemIds: { id: string; checked: boolean }[]): Promise { + $checkStateChanged(treeViewId: string, itemIds: { treeItemId: string; checked: boolean }[]): Promise { const treeView = this.treeViews.get(treeViewId); if (!treeView) { throw new Error('No tree view with id ' + treeViewId); @@ -653,10 +653,10 @@ class ExtHostTreeView implements IDisposable { ); } - async checkStateChanged(items: readonly { id: string; checked: boolean }[]): Promise { + async checkStateChanged(items: readonly { treeItemId: string; checked: boolean }[]): Promise { const transformed: [T, TreeItemCheckboxState][] = []; items.forEach((item) => { - const node = this.getTreeItem(item.id); + const node = this.getTreeItem(item.treeItemId); if (node) { transformed.push([node, item.checked ? TreeItemCheckboxState.Checked : TreeItemCheckboxState.Unchecked]); const treeViewItem = this.element2TreeViewItem.get(node); @@ -798,6 +798,7 @@ class ExtHostTreeView implements IDisposable { }; } } + let checkboxInfo; if (treeItem.checkboxState === undefined) { checkboxInfo = undefined; From c94db8563dd0b9040483d899aaf306c5ad82e7bb Mon Sep 17 00:00:00 2001 From: xxw Date: Tue, 10 Dec 2024 19:28:05 +0800 Subject: [PATCH 3/6] fix: optimization code --- .../src/browser/components/extension-tree-view.tsx | 10 +++++----- .../vscode/api/tree-view/tree-view.model.service.ts | 12 +++++++----- packages/extension/src/common/vscode/treeview.ts | 2 +- .../src/hosted/api/vscode/ext.host.treeview.ts | 12 ++++++------ 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/packages/extension/src/browser/components/extension-tree-view.tsx b/packages/extension/src/browser/components/extension-tree-view.tsx index 7302200b57..a427e96016 100644 --- a/packages/extension/src/browser/components/extension-tree-view.tsx +++ b/packages/extension/src/browser/components/extension-tree-view.tsx @@ -117,7 +117,7 @@ export const ExtensionTabBarTreeView = ({ [canSelectMany, model], ); - const handleCheckBoxChanged = useCallback( + const handleCheckBoxChange = useCallback( (item: ExtensionTreeNode | ExtensionCompositeTreeNode) => { const { handleCheckBoxChange } = model; if (item) { @@ -211,7 +211,7 @@ export const ExtensionTabBarTreeView = ({ isVisible={isVisible} handleTreeReady={handleTreeReady} handleItemClicked={handleItemClicked} - handleCheckBoxChanged={handleCheckBoxChanged} + handleCheckBoxChange={handleCheckBoxChange} handleTwistierClick={handleTwistierClick} handleContextMenu={handleContextMenu} handleDragStart={handleDragStart} @@ -237,7 +237,7 @@ interface TreeViewProps { model: ExtensionTreeViewModel; handleTreeReady(handle: IRecycleTreeHandle): void; handleItemClicked(ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType): void; - handleCheckBoxChanged(item: ExtensionTreeNode | ExtensionCompositeTreeNode): void; + handleCheckBoxChange(item: ExtensionTreeNode | ExtensionCompositeTreeNode): void; handleTwistierClick(ev: MouseEvent, item: ExtensionCompositeTreeNode): void; handleContextMenu(ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode): void; handleDragStart(ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode): void; @@ -267,7 +267,7 @@ const TreeView = memo( dataProvider, handleTreeReady, handleItemClicked, - handleCheckBoxChanged, + handleCheckBoxChange, handleTwistierClick, handleContextMenu, handleDragStart, @@ -314,7 +314,7 @@ const TreeView = memo( itemType={props.itemType} decorations={model.decorations.getDecorations(props.item as any)} onClick={handleItemClicked} - onChange={handleCheckBoxChanged} + onChange={handleCheckBoxChange} onTwistierClick={handleTwistierClick} onContextMenu={handleContextMenu} onDragStart={handleDragStart} diff --git a/packages/extension/src/browser/vscode/api/tree-view/tree-view.model.service.ts b/packages/extension/src/browser/vscode/api/tree-view/tree-view.model.service.ts index f7da8bbcfa..59e363ac9f 100644 --- a/packages/extension/src/browser/vscode/api/tree-view/tree-view.model.service.ts +++ b/packages/extension/src/browser/vscode/api/tree-view/tree-view.model.service.ts @@ -1085,10 +1085,12 @@ export class ExtensionTreeViewModel { } handleCheckBoxChange = async (item: ExtensionCompositeTreeNode | ExtensionTreeNode) => { - this.treeViewDataProvider.markAsChecked( - item, - !item.checkboxInfo!.checked, - this.treeViewOptions.manageCheckboxStateManually, - ); + if (item) { + this.treeViewDataProvider.markAsChecked( + item, + !item.checkboxInfo!.checked, + this.treeViewOptions.manageCheckboxStateManually, + ); + } }; } diff --git a/packages/extension/src/common/vscode/treeview.ts b/packages/extension/src/common/vscode/treeview.ts index c9f4d2cea6..9e937fb7ad 100644 --- a/packages/extension/src/common/vscode/treeview.ts +++ b/packages/extension/src/common/vscode/treeview.ts @@ -39,7 +39,7 @@ export interface IExtHostTreeView { $setExpanded(treeViewId: string, treeItemId: string, expanded: boolean): Promise; $setSelection(treeViewId: string, treeItemHandles: string[]): void; $setVisible(treeViewId: string, visible: boolean): void; - $checkStateChanged(treeViewId: string, itemIds: { treeItemId: string; checked: boolean }[]): Promise; + $checkStateChanged(treeViewId: string, items: { treeItemId: string; checked: boolean }[]): Promise; $resolveTreeItem(treeViewId: string, treeItemId: string, token: CancellationToken): Promise; $handleDrop( destinationViewId: string, diff --git a/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts b/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts index d38e4483a9..031c7273fd 100644 --- a/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts +++ b/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts @@ -221,15 +221,15 @@ export class ExtHostTreeViews implements IExtHostTreeView { /** * 设置节点的选择状态 * @param treeViewId - * @param itemIds + * @param items * @returns */ - $checkStateChanged(treeViewId: string, itemIds: { treeItemId: string; checked: boolean }[]): Promise { + $checkStateChanged(treeViewId: string, items: { treeItemId: string; checked: boolean }[]): Promise { const treeView = this.treeViews.get(treeViewId); if (!treeView) { throw new Error('No tree view with id ' + treeViewId); } - return treeView.checkStateChanged(itemIds); + return treeView.checkStateChanged(items); } async $handleDrop( @@ -660,8 +660,8 @@ class ExtHostTreeView implements IDisposable { if (node) { transformed.push([node, item.checked ? TreeItemCheckboxState.Checked : TreeItemCheckboxState.Unchecked]); const treeViewItem = this.element2TreeViewItem.get(node); - if (treeViewItem) { - treeViewItem.checkboxInfo!.checked = item.checked; + if (treeViewItem && treeViewItem.checkboxInfo) { + treeViewItem.checkboxInfo.checked = item.checked; } } }); @@ -806,7 +806,7 @@ class ExtHostTreeView implements IDisposable { checkboxInfo = { checked: treeItem.checkboxState.state === TreeItemCheckboxState.Checked, tooltip: treeItem.checkboxState.tooltip, - accessibilityInformation: treeItem.accessibilityInformation, + accessibilityInformation: treeItem.checkboxState.accessibilityInformation, }; } else { checkboxInfo = { From d3f03b9eb854c66e141d9a30e4d96d95532e4d62 Mon Sep 17 00:00:00 2001 From: xxw Date: Wed, 11 Dec 2024 11:20:01 +0800 Subject: [PATCH 4/6] fix: optimization code --- .../extension/src/browser/components/extension-tree-view.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/extension/src/browser/components/extension-tree-view.tsx b/packages/extension/src/browser/components/extension-tree-view.tsx index a427e96016..2b85a2806d 100644 --- a/packages/extension/src/browser/components/extension-tree-view.tsx +++ b/packages/extension/src/browser/components/extension-tree-view.tsx @@ -124,7 +124,7 @@ export const ExtensionTabBarTreeView = ({ handleCheckBoxChange(item); } }, - [canSelectMany, model], + [model], ); const handleContextMenu = useCallback( From 8edc5674180c4fc13f53756764fb3a04037bb214 Mon Sep 17 00:00:00 2001 From: xxw Date: Wed, 11 Dec 2024 14:13:35 +0800 Subject: [PATCH 5/6] fix: optimization code --- .../extension/src/hosted/api/vscode/ext.host.treeview.ts | 6 ++++-- packages/types/vscode/typings/vscode.d.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts b/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts index 031c7273fd..2f447ac0d7 100644 --- a/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts +++ b/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts @@ -8,7 +8,9 @@ import { IDisposable, Uri, asPromise, + isObject, isString, + isUndefined, randomString, toDisposable, } from '@opensumi/ide-core-common'; @@ -800,9 +802,9 @@ class ExtHostTreeView implements IDisposable { } let checkboxInfo; - if (treeItem.checkboxState === undefined) { + if (isUndefined(treeItem.checkboxState)) { checkboxInfo = undefined; - } else if (typeof treeItem.checkboxState === 'object') { + } else if (isObject(treeItem.checkboxState)) { checkboxInfo = { checked: treeItem.checkboxState.state === TreeItemCheckboxState.Checked, tooltip: treeItem.checkboxState.tooltip, diff --git a/packages/types/vscode/typings/vscode.d.ts b/packages/types/vscode/typings/vscode.d.ts index 247f460f07..71a9192373 100644 --- a/packages/types/vscode/typings/vscode.d.ts +++ b/packages/types/vscode/typings/vscode.d.ts @@ -2156,7 +2156,7 @@ declare module 'vscode' { * {@link TreeItemCheckboxState TreeItemCheckboxState} of the tree item. * {@link TreeDataProvider.onDidChangeTreeData onDidChangeTreeData} should be fired when {@link TreeItem.checkboxState checkboxState} changes. */ - checkboxState?: TreeItemCheckboxState | { + checkboxState?: { /** * The {@link TreeItemCheckboxState} of the tree item */ From 7a22c3e9eb1deadc2ec1533a87d18d551275ac5e Mon Sep 17 00:00:00 2001 From: xxw Date: Wed, 11 Dec 2024 15:40:41 +0800 Subject: [PATCH 6/6] fix: optimization code --- packages/extension/src/hosted/api/vscode/ext.host.treeview.ts | 4 ++-- packages/types/vscode/typings/vscode.d.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts b/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts index 2f447ac0d7..2c2079f4e6 100644 --- a/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts +++ b/packages/extension/src/hosted/api/vscode/ext.host.treeview.ts @@ -8,7 +8,7 @@ import { IDisposable, Uri, asPromise, - isObject, + isNumber, isString, isUndefined, randomString, @@ -804,7 +804,7 @@ class ExtHostTreeView implements IDisposable { let checkboxInfo; if (isUndefined(treeItem.checkboxState)) { checkboxInfo = undefined; - } else if (isObject(treeItem.checkboxState)) { + } else if (!isNumber(treeItem.checkboxState)) { checkboxInfo = { checked: treeItem.checkboxState.state === TreeItemCheckboxState.Checked, tooltip: treeItem.checkboxState.tooltip, diff --git a/packages/types/vscode/typings/vscode.d.ts b/packages/types/vscode/typings/vscode.d.ts index 71a9192373..247f460f07 100644 --- a/packages/types/vscode/typings/vscode.d.ts +++ b/packages/types/vscode/typings/vscode.d.ts @@ -2156,7 +2156,7 @@ declare module 'vscode' { * {@link TreeItemCheckboxState TreeItemCheckboxState} of the tree item. * {@link TreeDataProvider.onDidChangeTreeData onDidChangeTreeData} should be fired when {@link TreeItem.checkboxState checkboxState} changes. */ - checkboxState?: { + checkboxState?: TreeItemCheckboxState | { /** * The {@link TreeItemCheckboxState} of the tree item */