Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(context-menu): add context-menu css theme, help config, ts define #2836

Merged
merged 1 commit into from
Jan 12, 2024
Merged
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
5 changes: 5 additions & 0 deletions docs/docs/guide/expand/editor/theme.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ sidebar_position: 9
- `--color-text-reverse`: 反色情况下,文字颜色
- `--color-text-disabled`: 禁用态文字颜色

#### 菜单颜色
- `--color-context-menu-text`: 菜单项颜色
- `--color-context-menu-text-hover`: 菜单项 hover 颜色
- `--color-context-menu-text-disabled`: 菜单项 disabled 颜色

#### 字段和边框颜色

- `--color-field-label`: field 标签颜色
Expand Down
8 changes: 7 additions & 1 deletion packages/designer/src/builtin-simulator/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -832,16 +832,22 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
doc.addEventListener('contextmenu', (e: MouseEvent) => {
const targetElement = e.target as HTMLElement;
const nodeInst = this.getNodeInstanceFromElement(targetElement);
const editor = this.designer?.editor;
if (!nodeInst) {
editor?.eventBus.emit('designer.builtinSimulator.contextmenu', {
originalEvent: e,
});
return;
}
const node = nodeInst.node || this.project.currentDocument?.focusNode;
if (!node) {
editor?.eventBus.emit('designer.builtinSimulator.contextmenu', {
originalEvent: e,
});
return;
}

// dirty code should refector
const editor = this.designer?.editor;
const npm = node?.componentMeta?.npm;
const selected =
[npm?.package, npm?.componentName].filter((item) => !!item).join('-') ||
Expand Down
2 changes: 2 additions & 0 deletions packages/designer/src/context-menu-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ export class ContextMenuActions implements IContextMenuActions {
node: INode;
originalEvent: MouseEvent;
}) => {
originalEvent.stopPropagation();
originalEvent.preventDefault();
// 如果右键的节点不在 当前选中的节点中,选中该节点
if (!designer.currentSelection.has(node.id)) {
designer.currentSelection.select(node.id);
Expand Down
36 changes: 21 additions & 15 deletions packages/engine/src/inner-plugins/default-context-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
material.addContextMenuOption({
name: 'selectComponent',
title: intl('SelectComponents'),
condition: (nodes) => {
condition: (nodes = []) => {
return nodes.length === 1;
},
items: [
Expand All @@ -74,14 +74,17 @@ export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
material.addContextMenuOption({
name: 'copyAndPaste',
title: intl('CopyAndPaste'),
disabled: (nodes) => {
disabled: (nodes = []) => {
return nodes?.filter((node) => !node?.canPerformAction('copy')).length > 0;
},
condition: (nodes) => {
return nodes.length === 1;
return nodes?.length === 1;
},
action(nodes) {
const node = nodes[0];
const node = nodes?.[0];
if (!node) {
return;
}
const { document: doc, parent, index } = node;
const data = getNodesSchema(nodes);
clipboard.setData(data);
Expand All @@ -96,11 +99,11 @@ export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
material.addContextMenuOption({
name: 'copy',
title: intl('Copy'),
disabled: (nodes) => {
disabled: (nodes = []) => {
return nodes?.filter((node) => !node?.canPerformAction('copy')).length > 0;
},
condition(nodes) {
return nodes.length > 0;
condition(nodes = []) {
return nodes?.length > 0;
},
action(nodes) {
if (!nodes || nodes.length < 1) {
Expand All @@ -116,7 +119,7 @@ export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
name: 'pasteToBottom',
title: intl('PasteToTheBottom'),
condition: (nodes) => {
return nodes.length === 1;
return nodes?.length === 1;
},
async action(nodes) {
if (!nodes || nodes.length < 1) {
Expand Down Expand Up @@ -163,15 +166,18 @@ export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
name: 'pasteToInner',
title: intl('PasteToTheInside'),
condition: (nodes) => {
return nodes.length === 1;
return nodes?.length === 1;
},
disabled: (nodes) => {
disabled: (nodes = []) => {
// 获取粘贴数据
const node = nodes[0];
const node = nodes?.[0];
return !node.isContainerNode;
},
async action(nodes) {
const node = nodes[0];
const node = nodes?.[0];
if (!node) {
return;
}
const { document: doc } = node;

try {
Expand Down Expand Up @@ -210,14 +216,14 @@ export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
material.addContextMenuOption({
name: 'delete',
title: intl('Delete'),
disabled(nodes) {
disabled(nodes = []) {
return nodes?.filter((node) => !node?.canPerformAction('remove')).length > 0;
},
condition(nodes) {
condition(nodes = []) {
return nodes.length > 0;
},
action(nodes) {
nodes.forEach((node) => {
nodes?.forEach((node) => {
node.remove();
});
},
Expand Down
8 changes: 2 additions & 6 deletions packages/shell/src/components/context-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,13 @@ export function ContextMenu({ children, menus, pluginContext }: {
const children: React.ReactNode[] = parseContextMenuAsReactNode(parseContextMenuProperties(menus, {
destroy,
pluginContext,
}), {
pluginContext,
});
}), { pluginContext });

if (!children?.length) {
return;
}

destroyFn = createContextMenu(children, {
event,
});
destroyFn = createContextMenu(children, { event });
};

// 克隆 children 并添加 onContextMenu 事件处理器
Expand Down
14 changes: 10 additions & 4 deletions packages/types/src/shell/type/context-menu.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IPublicEnumContextMenuType } from '../enum';
import { IPublicModelNode } from '../model';
import { IPublicTypeI18nData } from './i8n-data';
import { IPublicTypeHelpTipConfig } from './widget-base-config';

export interface IPublicTypeContextMenuItem extends Omit<IPublicTypeContextMenuAction, 'condition' | 'disabled' | 'items'> {
disabled?: boolean;
Expand Down Expand Up @@ -34,24 +35,29 @@ export interface IPublicTypeContextMenuAction {
* 点击时执行的动作,可选
* Action to execute on click, optional
*/
action?: (nodes: IPublicModelNode[], event?: MouseEvent) => void;
action?: (nodes?: IPublicModelNode[], event?: MouseEvent) => void;

/**
* 子菜单项或生成子节点的函数,可选,仅支持两级
* Sub-menu items or function to generate child node, optional
*/
items?: Omit<IPublicTypeContextMenuAction, 'items'>[] | ((nodes: IPublicModelNode[]) => Omit<IPublicTypeContextMenuAction, 'items'>[]);
items?: Omit<IPublicTypeContextMenuAction, 'items'>[] | ((nodes?: IPublicModelNode[]) => Omit<IPublicTypeContextMenuAction, 'items'>[]);

/**
* 显示条件函数
* Function to determine display condition
*/
condition?: (nodes: IPublicModelNode[]) => boolean;
condition?: (nodes?: IPublicModelNode[]) => boolean;

/**
* 禁用条件函数,可选
* Function to determine disabled condition, optional
*/
disabled?: (nodes: IPublicModelNode[]) => boolean;
disabled?: (nodes?: IPublicModelNode[]) => boolean;

/**
* 帮助提示,可选
*/
help?: IPublicTypeHelpTipConfig;
}

21 changes: 14 additions & 7 deletions packages/utils/src/context-menu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,31 @@

.engine-context-menu-item {
.engine-context-menu-text {
color: var(--color-text);
color: var(--color-context-menu-text, var(--color-text));
display: flex;
align-items: center;

.lc-help-tip {
margin-left: 4px;
opacity: 0.8;
}
}

&:hover {
.engine-context-menu-text {
color: var(--color-title);
&.disabled {
&:hover .engine-context-menu-text, .engine-context-menu-text {
color: var(--color-context-menu-text-disabled, var(--color-text-disabled));
}
}

&.disbale {
&:hover {
.engine-context-menu-text {
color: var(--color-text-disabled);
color: var(--color-context-menu-text-hover, var(--color-title));
}
}
}

.engine-context-menu-title {
color: var(--color-text);
color: var(--color-context-menu-text, var(--color-text));
cursor: pointer;

&:hover {
Expand Down
33 changes: 20 additions & 13 deletions packages/utils/src/context-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ const Tree = (props: {
let destroyFn: Function | undefined;

export function parseContextMenuAsReactNode(menus: IPublicTypeContextMenuItem[], options: IOptions): React.ReactNode[] {
const { common } = options.pluginContext || {};
const { common, commonUI } = options.pluginContext || {};
const { intl = (title: any) => title } = common?.utils || {};
const { HelpTip } = commonUI || {};

const children: React.ReactNode[] = [];
menus.forEach((menu, index) => {
Expand All @@ -79,7 +80,7 @@ export function parseContextMenuAsReactNode(menus: IPublicTypeContextMenuItem[],
children.push((
<PopupItem
className={classNames('engine-context-menu-item', {
disbale: menu.disabled,
disabled: menu.disabled,
})}
key={menu.name}
label={<div className="engine-context-menu-text">{intl(menu.title)}</div>}
Expand All @@ -93,14 +94,17 @@ export function parseContextMenuAsReactNode(menus: IPublicTypeContextMenuItem[],
children.push((
<Item
className={classNames('engine-context-menu-item', {
disbale: menu.disabled,
disabled: menu.disabled,
})}
disabled={menu.disabled}
onClick={menu.action}
onClick={() => {
menu.action?.();
}}
key={menu.name}
>
<div className="engine-context-menu-text">
{intl(menu.title)}
{ menu.title ? intl(menu.title) : null }
{ menu.help ? <HelpTip size="xs" help={menu.help} direction="right" /> : null }
</div>
</Item>
));
Expand Down Expand Up @@ -135,12 +139,14 @@ export function parseContextMenuProperties(menus: (IPublicTypeContextMenuAction
name,
title,
type = IPublicEnumContextMenuType.MENU_ITEM,
help,
} = menu;

const result: IPublicTypeContextMenuItem = {
name,
title,
type,
help,
action: () => {
destroy?.();
menu.action?.(nodes || [], options.event);
Expand Down Expand Up @@ -193,26 +199,27 @@ export function createContextMenu(children: React.ReactNode[], {
event: MouseEvent | React.MouseEvent;
offset?: [number, number];
}) {
event.preventDefault();
event.stopPropagation();

const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const dividerCount = React.Children.count(children.filter(child => React.isValidElement(child) && child.type === Divider));
const popupItemCount = React.Children.count(children.filter(child => React.isValidElement(child) && (child.type === PopupItem || child.type === Item)));
const menuHeight = popupItemCount * parseInt(getMenuItemHeight(), 10) + dividerCount * 8 + 16;
const menuWidthLimit = 200;
const target = event.target;
const { top, left } = (target as any)?.getBoundingClientRect();
let x = event.clientX - left + offset[0];
let y = event.clientY - top + offset[1];
if (x + menuWidthLimit + left > viewportWidth) {
let x = event.clientX + offset[0];
let y = event.clientY + offset[1];
if (x + menuWidthLimit > viewportWidth) {
x = x - menuWidthLimit;
}
if (y + menuHeight + top > viewportHeight) {
if (y + menuHeight > viewportHeight) {
y = y - menuHeight;
}

const menuInstance = Menu.create({
target,
offset: [x, y, 0, 0],
target: document.body,
offset: [x, y],
children,
className: 'engine-context-menu',
});
Expand Down
Loading