mirror of
https://github.com/NetEase/tango.git
synced 2026-08-29 02:01:34 +08:00
feat: add global widget map
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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 <></>;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user