Skip to content

Commit

Permalink
feat(context-menu): add context-menu css theme, help config, ts define
Browse files Browse the repository at this point in the history
  • Loading branch information
liujuping committed Jan 11, 2024
1 parent 6f9359e commit c2e943f
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 46 deletions.
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

0 comments on commit c2e943f

Please sign in to comment.