feat: add global widget map

This commit is contained in:
wwsun
2023-09-22 17:37:26 +08:00
parent 09a1e2135a
commit 1c72ee6ac2
5 changed files with 114 additions and 97 deletions
+33 -9
View File
@@ -9,7 +9,6 @@ import {
Toolbar,
WorkspacePanel,
WorkspaceView,
ComponentsPanel,
CodeEditor,
Sandbox,
DndQuery,
@@ -18,6 +17,13 @@ import { createEngine, Workspace } from '@music163/tango-core';
import { Logo, ProjectDetail } from './share';
import { sampleFiles } from '../mock/project';
import './index.less';
import {
ApiOutlined,
AppstoreAddOutlined,
BuildOutlined,
ClusterOutlined,
FunctionOutlined,
} from '@ant-design/icons';
// 1. 实例化工作区
const workspace = new Workspace({
@@ -55,7 +61,7 @@ export default function App() {
<Toolbar.Item key="modeSwitch" placement="right" />
<Toolbar.Item key="togglePanel" placement="right" />
<Toolbar.Separator />
<Toolbar.Item key="extra" placement="right">
<Toolbar.Item placement="right">
<Space>
<Button type="primary"></Button>
</Space>
@@ -65,13 +71,31 @@ export default function App() {
}
>
<Sidebar>
<Sidebar.Item key="outline" />
<Sidebar.Item key="components">
<ComponentsPanel menuData={menuData as any} loading={menuLoading} />
</Sidebar.Item>
<Sidebar.Item key="variables" isFloat width={800} />
<Sidebar.Item key="dataSource" isFloat width={800} />
<Sidebar.Item key="dependency" isFloat width={800} />
<Sidebar.Item key="outline" label="结构" icon={<BuildOutlined />} />
<Sidebar.Item
key="components"
label="组件"
icon={<AppstoreAddOutlined />}
widgetProps={{
menuData: menuData as any,
loading: menuLoading,
}}
/>
<Sidebar.Item
key="variables"
label="变量"
icon={<FunctionOutlined />}
isFloat
width={800}
/>
<Sidebar.Item key="dataSource" label="接口" icon={<ApiOutlined />} isFloat width={800} />
<Sidebar.Item
key="dependency"
label="依赖"
icon={<ClusterOutlined />}
isFloat
width={800}
/>
</Sidebar>
<WorkspacePanel>
<WorkspaceView mode="design">
+17 -54
View File
@@ -1,21 +1,11 @@
import React, { useMemo } from 'react';
import cx from 'classnames';
import { Box, Text, css, HTMLCoralProps } from 'coral-system';
import {
ClusterOutlined,
FunctionOutlined,
ApiOutlined,
AppstoreOutlined,
BuildOutlined,
} from '@ant-design/icons';
import { Badge } from 'antd';
import { ReactComponentProps } from '@music163/tango-helpers';
import { observer, useDesigner } from '@music163/tango-context';
import { ResizableBox } from './resizable-box';
import { OutlinePanel } from './outline-panel';
import { DependencyPanel } from './dependency-panel';
import { VariablePanel } from './variable-panel';
import { DataSourcePanel } from './datasource-panel';
import { getWidget } from '../widgets';
const sidebarStyle = css`
position: relative;
@@ -51,7 +41,6 @@ export interface SidebarProps extends ReactComponentProps {
* 底部附加内容
*/
footer?: React.ReactNode;
builtinPanelMap?: Record<string, SidebarPanelItemProps>;
}
export interface SidebarPanelItemProps
@@ -79,54 +68,21 @@ export interface SidebarPanelItemProps
* 面板宽度
*/
width?: number;
/**
* 如果 key 匹配到内置组件的话,传递给子节点的属性
*/
widgetProps?: object;
}
const INTERNAL_SIDEBAR_PANEL_MAP: Record<string, SidebarPanelItemProps> = {
components: {
label: '组件',
title: '组件列表',
icon: <AppstoreOutlined />,
},
outline: {
label: '结构',
title: '结构',
icon: <BuildOutlined />,
children: <OutlinePanel />,
},
dependency: {
label: '依赖',
title: '项目依赖',
icon: <ClusterOutlined />,
children: <DependencyPanel />,
},
variables: {
label: '变量',
title: '变量管理',
icon: <FunctionOutlined />,
children: <VariablePanel />,
width: 440,
},
dataSource: {
label: '接口',
title: '数据源与接口',
icon: <ApiOutlined />,
children: <DataSourcePanel />,
width: 600,
},
};
function BaseSidebarPanel({
panelWidth: defaultPanelWidth = 280,
builtinPanelMap = INTERNAL_SIDEBAR_PANEL_MAP,
footer,
children,
}: SidebarProps) {
function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 280, footer, children }: SidebarProps) {
const items = useMemo(() => {
const ret: Record<React.Key, SidebarPanelItemProps> = {};
React.Children.forEach(children, (child) => {
if (child && React.isValidElement(child)) {
const widget = getWidget(['sidebar', child.key].join('.'));
const fallbackNode = widget ? React.createElement(widget, child.props.widgetProps) : null;
ret[child.key] = {
...builtinPanelMap[child.key],
children: fallbackNode,
...child.props,
};
}
@@ -203,7 +159,14 @@ function BaseSidebarPanel({
);
}
BaseSidebarPanel.Item = function ({ key, title, doc, isFloat, width }: SidebarPanelItemProps) {
BaseSidebarPanel.Item = function ({
key,
title,
doc,
isFloat,
width,
widgetProps,
}: SidebarPanelItemProps) {
return <></>;
};
+4 -17
View File
@@ -6,7 +6,7 @@ import { ISelectedItemData, isString, noop } from '@music163/tango-helpers';
import { observer, useDesigner, useWorkspace } from '@music163/tango-context';
import { IconButton } from '@music163/tango-ui';
import { getDragGhostElement } from '../helpers';
import { CopyNodeAction, DeleteNodeAction, ViewSourceAction } from '../selection-menu';
import { getWidget } from '../widgets';
/**
* 选择辅助工具的对齐方式
@@ -15,34 +15,21 @@ type SelectionHelperAlignType = 'top-right' | 'top-left' | 'inner-top-right';
const boundingOffset = 100; // 检测选中模块的工具栏是否溢出的偏移值
const internalSelectionToolsMap = {
source: <ViewSourceAction />,
copy: <CopyNodeAction />,
delete: <DeleteNodeAction />,
};
export const INTERNAL_SELECTION_TOOLS = ['source', 'create', 'copy', 'delete'];
export interface SelectionToolsProps {
/**
* 动作列表,内置的动作列表有 source-定位到源码, block-创建区块, copy-复制节点, delete-删除节点
*/
actions?: Array<string | React.ReactElement>;
builtinActionMap?: Record<string, React.ReactElement>;
}
export const SelectionTools = observer(
({
actions: actionsProp = INTERNAL_SELECTION_TOOLS,
builtinActionMap = internalSelectionToolsMap,
}: SelectionToolsProps) => {
({ actions: actionsProp = ['viewSource', 'copyNode', 'deleteNode'] }: SelectionToolsProps) => {
const workspace = useWorkspace();
const selectSource = workspace.selectSource;
const actions = actionsProp.map((item) => {
if (isString(item)) {
return builtinActionMap[item]
? React.cloneElement(builtinActionMap[item], { key: item })
: null;
const widget = getWidget(['selectionMenu', item].join('.'));
return widget ? React.createElement(widget, { key: item }) : null;
}
return item;
});
+16 -17
View File
@@ -1,26 +1,13 @@
import React, { useMemo } from 'react';
import { Box, Group } from 'coral-system';
import { ReactComponentProps } from '@music163/tango-helpers';
import { HistoryTool } from './history';
import { ModeSwitchTool } from './mode-switch';
import { PreviewTool } from './preview';
import { RouteSwitchTool } from './route-switch';
import { TogglePanelTool } from './toggle-panel';
import { getWidget } from '../widgets';
export interface ToolbarProps {
children?: React.ReactElement | React.ReactElement[];
builtinToolMap?: Record<string, React.ReactNode>;
}
const builtinToolsMap = {
history: <HistoryTool />,
modeSwitch: <ModeSwitchTool />,
preview: <PreviewTool />,
routeSwitch: <RouteSwitchTool />,
togglePanel: <TogglePanelTool />,
};
export function Toolbar({ children, builtinToolMap = builtinToolsMap }: ToolbarProps) {
export function Toolbar({ children }: ToolbarProps) {
const [leftTools, centerTools, rightTools] = useMemo(() => {
const left: React.ReactNode[] = [];
const center: React.ReactNode[] = [];
@@ -28,7 +15,15 @@ export function Toolbar({ children, builtinToolMap = builtinToolsMap }: ToolbarP
let prevPlacement: string;
React.Children.forEach(children, (child: React.ReactElement, index) => {
let node = child.props.children || builtinToolMap[child.key];
let fallbackNode;
if (child.key) {
const Widget = getWidget(['toolbar', child.key].join('.'));
if (Widget) {
fallbackNode = React.createElement(Widget, child.props.widgetProps);
}
}
let node = child.props.children ?? fallbackNode ?? null;
if (!node) {
node = child; // separator
}
@@ -75,9 +70,13 @@ export function Toolbar({ children, builtinToolMap = builtinToolsMap }: ToolbarP
export interface ToolbarItemProps extends ReactComponentProps {
placement?: 'left' | 'center' | 'right';
children?: React.ReactElement;
/**
* 如果 key 匹配到内置组件的话,传递给子节点的属性
*/
widgetProps?: object;
}
function ToolbarItem({ placement, children }: ToolbarItemProps) {
function ToolbarItem({ placement, widgetProps, children }: ToolbarItemProps) {
return <div>{children}</div>;
}
+44
View File
@@ -0,0 +1,44 @@
import { getValue, setValue } from '@music163/tango-helpers';
import { ModeSwitchTool, PreviewTool, RouteSwitchTool, TogglePanelTool } from './toolbar';
import {
ComponentsPanel,
DataSourcePanel,
DependencyPanel,
OutlinePanel,
VariablePanel,
} from './sidebar';
import { CopyNodeAction, DeleteNodeAction, ViewSourceAction } from './selection-menu';
const widgets = {};
export function registerWidget(key: string, component: React.ComponentType<any>) {
if (!/^(toolbar|sidebar|selectionMenu)\.[a-zA-z]+$/.test(key)) {
throw new Error(
`Invalid widget key(${key}), should start with toolbar, sidebar, or selection-menu`,
);
}
setValue(widgets, key, component);
}
export function getWidget(key: string) {
if (!key) {
return;
}
return getValue(widgets, key);
}
registerWidget('toolbar.modeSwitch', ModeSwitchTool);
registerWidget('toolbar.preview', PreviewTool);
registerWidget('toolbar.routeSwitch', RouteSwitchTool);
registerWidget('toolbar.togglePanel', TogglePanelTool);
registerWidget('sidebar.components', ComponentsPanel);
registerWidget('sidebar.outline', OutlinePanel);
registerWidget('sidebar.dependency', DependencyPanel);
registerWidget('sidebar.variables', VariablePanel);
registerWidget('sidebar.dataSource', DataSourcePanel);
registerWidget('selectionMenu.copyNode', CopyNodeAction);
registerWidget('selectionMenu.deleteNode', DeleteNodeAction);
registerWidget('selectionMenu.viewSource', ViewSourceAction);