Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 2 additions & 0 deletions packages/components/src/button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,5 @@ export const Button = React.memo(
);
},
);

export default Button;
Comment thread
hacke2 marked this conversation as resolved.
Outdated
102 changes: 102 additions & 0 deletions packages/components/src/dropdown/dropdown-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import EllipsisOutlined from '@ant-design/icons/EllipsisOutlined';
Comment thread
hacke2 marked this conversation as resolved.
import classNames from 'classnames';
import * as React from 'react';

import Button from '../button';
import type { ButtonSize, ButtonType } from '../button';
import type { ButtonHTMLType } from '../button';

import type { DropDownProps } from './dropdown';
import Dropdown from './dropdown';

export interface DropdownButtonProps extends DropDownProps {
type?: ButtonType;
size?: ButtonSize;
htmlType?: ButtonHTMLType;
danger?: boolean;
disabled?: boolean;
loading?: boolean;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
icon?: React.ReactNode;
href?: string;
children?: React.ReactNode;
title?: string;
buttonsRender?: (buttons: React.ReactNode[]) => React.ReactNode[];
}

const DropdownButton: React.FC<DropdownButtonProps> = (props) => {
const {
prefixCls: customizePrefixCls,
type = 'primary',
size = 'default',
danger,
disabled,
loading,
onClick,
htmlType,
children,
className,
overlay,
trigger,
align,
visible,
onVisibleChange,
placement,
getPopupContainer,
href,
icon = <EllipsisOutlined />,
title,
buttonsRender = (buttons: React.ReactNode[]) => buttons,
mouseEnterDelay,
mouseLeaveDelay,
overlayClassName,
overlayStyle,
...restProps
} = props;

const prefixCls = customizePrefixCls || 'kt-dropdown-button';
const dropdownProps: DropDownProps = {
align,
overlay,
disabled,
trigger: disabled ? [] : trigger,
getPopupContainer,
mouseEnterDelay,
mouseLeaveDelay,
overlayClassName,
overlayStyle,
};

dropdownProps.placement = props?.placement ?? 'bottomRight';

const leftButton = (
<Button
size={size}
type={type}
disabled={disabled}
loading={loading}
onClick={onClick}
htmlType={htmlType}
title={title}
>
{children}
</Button>
);

const rightButton = (
<Button size={size} type={type}>
{icon ?? <EllipsisOutlined />}
</Button>
);

const [leftButtonToRender, rightButtonToRender] = buttonsRender([leftButton, rightButton]);

return (
<div {...restProps} className={classNames(prefixCls, className)}>
{leftButtonToRender}
<Dropdown {...dropdownProps}>{rightButtonToRender}</Dropdown>
</div>
);
};

export default DropdownButton;
3 changes: 2 additions & 1 deletion packages/components/src/dropdown/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Dropdown from './dropdown';
import DropdownButton from './dropdown-button';
import './style.less';

export { DropDownProps } from './dropdown';

export { Dropdown };
export { Dropdown, DropdownButton };
18 changes: 18 additions & 0 deletions packages/components/src/dropdown/style.less
Original file line number Diff line number Diff line change
Expand Up @@ -252,3 +252,21 @@
.iconfont-size-under-12px(10px);
}
}

@dropdown-button-prefix-cls: ~'@{prefix}-dropdown-button';

.@{dropdown-button-prefix-cls} {
.reset-component;
display: flex;
border-radius: 2px;
overflow: hidden;
.@{prefix}-button {
border-radius: 0;
&:first-child {
border-right: 1px solid var(--kt-hintBackground);
}
&:last-child {
padding: 0 6px;
}
}
}
1 change: 1 addition & 0 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ export * from './loading';
export * from './click-outside';

export * from './locale-context-provider';
export * from './dropdown';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.menu {
min-width: 100px !important;
}
60 changes: 60 additions & 0 deletions packages/core-browser/src/toolbar/components/dropdown-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import classnames from 'classnames';
import React from 'react';

import { DropdownButton, DropDownProps } from '@opensumi/ide-components';
import { Menu } from '@opensumi/ide-components/lib/menu';
import { Emitter } from '@opensumi/ide-core-common';

import { IToolbarActionElementProps, IToolbarActionReactElement, IToolbarActionDropdownButtonProps } from '../types';

import style from './dropdown-button.module.less';

export function ToolbarActionDropdownButton<T>(
Comment thread
hacke2 marked this conversation as resolved.
Outdated
props: IToolbarActionDropdownButtonProps<T> & IToolbarActionElementProps,
) {
const selectEmitter = React.useRef(new Emitter<T>());
const [firstOption, ...otherOptions] = props.options;
React.useEffect(() => {
const _onChangeState = new Emitter<{ from: string; to: string }>();
const delegate = {
onSelect: selectEmitter.current.event,
};
props.delegate && props.delegate(delegate);
return () => {
props.delegate && props.delegate(undefined);
_onChangeState.dispose();
};
}, []);

const trigger = React.useMemo(() => props.trigger ?? (['click'] as DropDownProps['trigger']), [props.trigger]);

const handleClick = React.useCallback((value) => {
selectEmitter.current.fire(value);
}, []);

const menu = React.useMemo(() => (
<Menu
className={classnames('kt-menu', style.menu)}
selectable={false}
motion={{ motionLeave: false, motionEnter: false }}
>
{otherOptions.map((option) => (
<Menu.Item key={option.label} onClick={() => handleClick(option.value)}>
{option.label}
</Menu.Item>
))}
</Menu>
), []);

return (
<DropdownButton size='small' onClick={() => handleClick(firstOption.value)} overlay={menu} trigger={trigger}>
{firstOption.label}
</DropdownButton>
);
}

export function createToolbarActionDropdownButton<T = string>(
props: IToolbarActionDropdownButtonProps<T>,
): IToolbarActionReactElement {
return (actionProps) => <ToolbarActionDropdownButton {...props} {...actionProps} />;
}
1 change: 1 addition & 0 deletions packages/core-browser/src/toolbar/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './button';
export * from './select';
export * from './dropdown-button';
14 changes: 13 additions & 1 deletion packages/core-browser/src/toolbar/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IDataOption, IDataOptionGroup } from '@opensumi/ide-components';
import { DropDownProps, IDataOption, IDataOptionGroup } from '@opensumi/ide-components';
import { IDisposable, BasicEvent, Event } from '@opensumi/ide-core-common';

export const IToolbarRegistry = Symbol('IToolbarRegistry');
Expand Down Expand Up @@ -308,6 +308,18 @@ export interface IToolbarActionSelectProps<T> {
delegate?: (delegate: IToolbarActionSelectDelegate<T> | undefined) => void;
}

export interface IToolbarActionDropdownButtonDelegate<T> {
onSelect: Event<T>;
}

// DropdownButton
export interface IToolbarActionDropdownButtonProps<T> {
options: IDataOption<T>[];
trigger?: DropDownProps['trigger'];
onSelect?: (value: T) => void;
delegate?: (delegate: IToolbarActionDropdownButtonDelegate<T> | undefined) => void;
}

export interface IToolbarActionSelectDelegate<T> {
setState(state: string): void;
setSelect(value: T): void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import {
import { createBrowserInjector } from '../../../../../../tools/dev-tool/src/injector-helper';
import { mockExtensions } from '../../../../__mocks__/extensions';
import { ExtHostSumiAPIIdentifier, MainThreadSumiAPIIdentifier } from '../../../../src/common/sumi';
import {
BUTTON_SET_STATE_ID,
BUTTON_STATE_CHANGE_ID,
SELECT_SET_STATE_ID,
SELECT_STATE_CHANGE_ID,
} from '../../../../src/common/sumi/toolbar';
import { MainThreadAPIIdentifier } from '../../../../src/common/vscode';
import { ExtHostCommands } from '../../../../src/hosted/api/vscode/ext.host.command';

Expand All @@ -19,6 +25,10 @@ const mockMainThreadToolbarProxy = {
actionMaps.set(contribution.id, contribution);
}),

$registerDropdownButtonAction: jest.fn((extensionId: string, extensionPath: string, contribution: any) => {
actionMaps.set(contribution.id, contribution);
}),

$registerToolbarSelectAction: jest.fn((extensionId: string, extensionPath: string, contribution: any) => {
actionMaps.set(contribution.id, contribution);
}),
Expand All @@ -34,8 +44,8 @@ const emitter = new Emitter();
const mockMainthreadCommand = {
$executeCommand(id, ...args) {
switch (id) {
case 'sumi-extension.toolbar.btn.setState':
case 'sumi-extension.toolbar.select.setState': {
case BUTTON_SET_STATE_ID:
case SELECT_SET_STATE_ID: {
const [actionId, state] = args;
emitter.fire({
id,
Expand Down Expand Up @@ -74,12 +84,12 @@ describe('packages/extension/__tests__/hosted/api/sumi/ext.host.toolbar.test.ts'
let eventName;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (e.id === 'sumi-extension.toolbar.select.setState') {
eventName = 'sumi-extension.toolbar.select.stateChange';
if (e.id === SELECT_SET_STATE_ID) {
eventName = SELECT_STATE_CHANGE_ID;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
} else if (e.id === 'sumi-extension.toolbar.btn.setState') {
eventName = 'sumi-extension.toolbar.btn.stateChange';
} else if (e.id === BUTTON_SET_STATE_ID) {
eventName = BUTTON_STATE_CHANGE_ID;
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
Expand Down Expand Up @@ -120,6 +130,29 @@ describe('packages/extension/__tests__/hosted/api/sumi/ext.host.toolbar.test.ts'
expect(hostAction).toBeDefined();
});

it('toolbarAPI#registerToolbarAction dropdownButton should be work', async () => {
const id = `${extension.id}-toolbar`;
await toolbarAPI.registerToolbarAction({
id,
type: 'dropdownButton',
description: 'test',
command: 'common-start.select',
options: [
{
label: '运行',
value: 'run',
},
{
label: '调试',
value: 'debug',
},
],
});

const hostAction = await toolbarAPI.getToolbarActionDropdownButtonHandle(id);
expect(hostAction).toBeDefined();
});

it('toolbarAPI#registerToolbarAction button setState should be work', async () => {
const defered = new Deferred();

Expand Down
55 changes: 36 additions & 19 deletions packages/extension/src/browser/sumi/contributes/toolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ import { LifeCyclePhase } from '@opensumi/ide-core-common';
import { VSCodeContributePoint, Contributes, LifeCycle } from '../../../common';
import { AbstractExtInstanceManagementService } from '../../types';
import { KaitianExtensionToolbarService } from '../main.thread.toolbar';
import { IToolbarButtonContribution, IToolbarSelectContribution, IToolbarActionBasicContribution } from '../types';
import {
IToolbarButtonContribution,
IToolbarSelectContribution,
IToolbarActionBasicContribution,
IToolbarDropdownButtonContribution,
} from '../types';

export interface KtToolbarSchema {
actions?: Array<IToolbarButtonContribution | IToolbarSelectContribution>;
actions?: Array<IToolbarButtonContribution | IToolbarSelectContribution | IToolbarDropdownButtonContribution>;
groups?: Array<{
id: string;
preferredLocation?: string;
Expand Down Expand Up @@ -62,23 +67,35 @@ export class ToolbarContributionPoint extends VSCodeContributePoint<KtToolbarSch
}
if (contributes.actions) {
for (const toolbarAction of contributes.actions) {
if (toolbarAction.type === 'button') {
this.addDispose(
this.kaitianExtToolbarService.registerToolbarButton(
extensionId,
extension.path,
this.toLocalized(toolbarAction, ['title'], extensionId),
),
);
} else if (toolbarAction.type === 'select') {
this.addDispose(
this.kaitianExtToolbarService.registerToolbarSelect(
extensionId,
extension.path,
this.toLocalized(toolbarAction, ['description'], extensionId),
),
);
}
switch (toolbarAction.type) {
case 'button':
this.addDispose(
this.kaitianExtToolbarService.registerToolbarButton(
extensionId,
extension.path,
this.toLocalized(toolbarAction, ['title'], extensionId),
),
);
break;
case 'select':
this.addDispose(
this.kaitianExtToolbarService.registerToolbarSelect(
extensionId,
extension.path,
this.toLocalized(toolbarAction, ['description'], extensionId),
),
);
break;
case 'dropdownButton':
this.addDispose(
this.kaitianExtToolbarService.registerToolbarDropdownButton(
extensionId,
extension.path,
this.toLocalized(toolbarAction, ['description'], extensionId),
),
);
break;
}
}
}
}
Expand Down
Loading