diff --git a/apps/playground/src/pages/index.tsx b/apps/playground/src/pages/index.tsx
index 81c8fa0..6786c80 100644
--- a/apps/playground/src/pages/index.tsx
+++ b/apps/playground/src/pages/index.tsx
@@ -113,8 +113,8 @@ export default function App() {
actions={
-
-
+
+
setShowNewPageModal(true)}
/>
-
-
+
+
+
-
diff --git a/packages/core/src/helpers/ast/generate.ts b/packages/core/src/helpers/ast/generate.ts
index 8749149..0cab0e0 100644
--- a/packages/core/src/helpers/ast/generate.ts
+++ b/packages/core/src/helpers/ast/generate.ts
@@ -3,7 +3,7 @@
*/
import generator, { GeneratorOptions } from '@babel/generator';
import * as t from '@babel/types';
-import { logger, wrapCode } from '@music163/tango-helpers';
+import { Dict, logger, wrapCode } from '@music163/tango-helpers';
import { formatCode } from '../string';
const defaultGeneratorOptions: GeneratorOptions = {
@@ -25,19 +25,7 @@ export function ast2code(ast: t.Node, options: GeneratorOptions = defaultGenerat
return code;
}
-const bracketPattern = /^\(.+\)$/s;
-
-/**
- * 是否被 () 包裹
- *
- * @example ({ foo: 'foo' }) -> true
- * @example { foo: 'foo' } -> false
- *
- * @param str 目标字符串
- */
-function isWrappingWithBrackets(str: string) {
- return bracketPattern.test(str);
-}
+const bracketPattern = /^\(.+\)$/;
/**
* 将表达式生成为块级代码
@@ -54,7 +42,7 @@ export function expression2code(node: t.Expression) {
const isWrappingExpression = t.isObjectExpression(node) || t.isFunctionExpression(node);
- if (isWrappingExpression && isWrappingWithBrackets(ret)) {
+ if (isWrappingExpression && bracketPattern.test(ret)) {
// 如果是对象,输出包含 ({}),则去掉首尾的括号
ret = ret.slice(1, -1);
}
@@ -195,7 +183,7 @@ export function node2value(node: t.Node, isWrapCode = true): any {
);
if (isSimpleObject) {
// simple object: { key1, key2, key3 }
- ret = node.properties.reduce((prev, propertyNode) => {
+ ret = node.properties.reduce((prev, propertyNode) => {
if (propertyNode.type === 'ObjectProperty') {
const key = keyNode2value(propertyNode.key);
const value = node2value(propertyNode.value, isWrapCode);
diff --git a/packages/core/src/helpers/ast/parse.ts b/packages/core/src/helpers/ast/parse.ts
index 02ec0ad..b847b82 100644
--- a/packages/core/src/helpers/ast/parse.ts
+++ b/packages/core/src/helpers/ast/parse.ts
@@ -68,11 +68,7 @@ export function isValidExpressionCode(code: string) {
* @returns
*/
export function code2ast(code: string): t.File {
- try {
- return parse(code, babelParserConfig);
- } catch (err) {
- logger.error('[code2ast failed!]', err);
- }
+ return parse(code, babelParserConfig);
}
/**
@@ -175,7 +171,7 @@ export function value2node(
* 将 js 普通对象解析为 t.Node
*/
export function object2node(
- obj: object,
+ obj: Dict,
getValueNode: (value: any, key?: string) => t.Expression = value2node,
) {
if (!isPlainObject(obj)) {
diff --git a/packages/core/src/helpers/ast/traverse.ts b/packages/core/src/helpers/ast/traverse.ts
index 8eaadc3..2d73595 100644
--- a/packages/core/src/helpers/ast/traverse.ts
+++ b/packages/core/src/helpers/ast/traverse.ts
@@ -1009,7 +1009,7 @@ export function serviceConfig2Node(payload: object) {
});
}
-export function updateServiceConfigToServiceFile(ast: t.File, config: Dict
);
});
-
-function isJsFile(path: string) {
- return /.jsx?$/.test(path);
-}
diff --git a/packages/designer/src/sidebar/sidebar.tsx b/packages/designer/src/sidebar/sidebar.tsx
index 89e7d7d..5889778 100644
--- a/packages/designer/src/sidebar/sidebar.tsx
+++ b/packages/designer/src/sidebar/sidebar.tsx
@@ -3,7 +3,7 @@ import cx from 'classnames';
import { Box, Text, css, HTMLCoralProps } from 'coral-system';
import { Badge } from 'antd';
import { ReactComponentProps } from '@music163/tango-helpers';
-import { observer, useDesigner } from '@music163/tango-context';
+import { observer, useDesigner, useWorkspace } from '@music163/tango-context';
import { ResizableBox } from './resizable-box';
import { getWidget } from '../widgets';
@@ -19,15 +19,22 @@ const sidebarStyle = css`
list-style: none;
user-select: none;
transition: all 0.3s ease;
+
+ &.disabled {
+ pointer-events: none;
+ }
}
.SidebarPanelBarListItem {
+ color: var(--tango-colors-text2);
+
&.active {
- background-color: var(--tango-colors-custom-sidebarItemActiveBg);
+ color: var(--tango-colors-brand);
+ background-color: var(--tango-colors-custom-sidebarItemActiveBg, --tango-colors-fill2);
}
&:hover {
- background-color: var(--tango-colors-custom-sidebarItemHoverBg);
+ background-color: var(--tango-colors-custom-sidebarItemHoverBg, --tango-colors-fill2);
}
}
`;
@@ -76,6 +83,7 @@ export interface SidebarPanelItemProps
function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 266, footer, children }: SidebarProps) {
const designer = useDesigner();
+ const workspace = useWorkspace();
const items = useMemo(() => {
const ret: Record = {};
@@ -92,10 +100,23 @@ function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 266, footer, childre
return ret;
}, [children]);
+ if (designer.activeView !== 'design') {
+ return null;
+ }
+
return (
-
+ 0,
+ })}
+ >
{Object.keys(items).map((key) => {
const item = items[key];
const isActive = key === designer.activeSidebarPanel;
@@ -112,7 +133,6 @@ function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 266, footer, childre
icon={item.icon}
label={item.label}
showBadge={item.showBadge}
- isActive={isActive}
/>
);
@@ -183,10 +203,6 @@ BaseSidebarPanel.Item = function ({
export const Sidebar = observer(BaseSidebarPanel);
interface SidebarPanelBarItemProps {
- /**
- * 是否选中
- */
- isActive?: boolean;
/**
* 侧边栏图标
*/
@@ -212,15 +228,9 @@ interface SidebarPanelBarItemProps {
};
}
-function SidebarPanelBarItem({
- isActive,
- icon: iconProp,
- label,
- showBadge,
-}: SidebarPanelBarItemProps) {
- const color = isActive ? 'brand' : 'text.body';
+function SidebarPanelBarItem({ icon: iconProp, label, showBadge }: SidebarPanelBarItemProps) {
let icon = (
-
+
{iconProp}
);
@@ -241,7 +251,7 @@ function SidebarPanelBarItem({
py="12px"
>
{icon}
-
+
{label}
diff --git a/packages/designer/src/toolbar/history.tsx b/packages/designer/src/toolbar/history.tsx
index b9f239c..0739861 100644
--- a/packages/designer/src/toolbar/history.tsx
+++ b/packages/designer/src/toolbar/history.tsx
@@ -5,11 +5,12 @@ import { observer, useWorkspace } from '@music163/tango-context';
export const HistoryTool = observer(() => {
const workspace = useWorkspace();
+ const disabled = !workspace.isValid;
return (
{
workspace.history.back();
@@ -20,7 +21,7 @@ export const HistoryTool = observer(() => {
{
workspace.history.forward();
diff --git a/packages/designer/src/toolbar/mode-switch.tsx b/packages/designer/src/toolbar/mode-switch.tsx
index 6c458cc..e92342a 100644
--- a/packages/designer/src/toolbar/mode-switch.tsx
+++ b/packages/designer/src/toolbar/mode-switch.tsx
@@ -1,6 +1,5 @@
-import React, { useCallback } from 'react';
+import React from 'react';
import { Group } from 'coral-system';
-import { Modal } from 'antd';
import { ToggleButton, CodeOutlined, DualOutlined } from '@music163/tango-ui';
import { observer, useDesigner, useWorkspace } from '@music163/tango-context';
import { BorderOutlined } from '@ant-design/icons';
@@ -9,23 +8,13 @@ export const ModeSwitchTool = observer(() => {
const workspace = useWorkspace();
const designer = useDesigner();
- const activeFileCheck = useCallback(() => {
- if (workspace.activeFile !== workspace.activeViewFile) {
- Modal.confirm({
- title: '当前打开的文件与视图不匹配,是否切换到当前视图对应的文件?',
- onOk: () => {
- workspace.setActiveFile(workspace.activeViewFile);
- },
- });
- }
- }, [workspace]);
-
return (
{
+ workspace.syncFiles(); // 保证 ast 与 code 是同步的
designer.setActiveView('design');
}}
tooltip="设计视图"
@@ -38,7 +27,6 @@ export const ModeSwitchTool = observer(() => {
onClick={() => {
designer.setActiveView('code'); // 切换到源码视图
designer.setActiveSidebarPanel(''); // 关闭左侧面板
- activeFileCheck();
}}
tooltip="源码视图"
>
@@ -50,7 +38,6 @@ export const ModeSwitchTool = observer(() => {
onClick={() => {
designer.setActiveView('dual'); // 切换到双屏视图
designer.setActiveSidebarPanel(''); // 关闭左侧面板
- activeFileCheck();
}}
tooltip="双屏视图"
>
diff --git a/packages/designer/src/toolbar/preview.tsx b/packages/designer/src/toolbar/preview.tsx
index 499c4e2..366c8bb 100644
--- a/packages/designer/src/toolbar/preview.tsx
+++ b/packages/designer/src/toolbar/preview.tsx
@@ -1,13 +1,15 @@
import React from 'react';
import { ToggleButton } from '@music163/tango-ui';
import { EyeOutlined } from '@ant-design/icons';
-import { observer, useDesigner } from '@music163/tango-context';
+import { observer, useDesigner, useWorkspace } from '@music163/tango-context';
export const PreviewTool = observer(() => {
const designer = useDesigner();
+ const workspace = useWorkspace();
return (
{
diff --git a/packages/designer/src/toolbar/toolbar.tsx b/packages/designer/src/toolbar/toolbar.tsx
index 114fdd4..8f272c7 100644
--- a/packages/designer/src/toolbar/toolbar.tsx
+++ b/packages/designer/src/toolbar/toolbar.tsx
@@ -1,6 +1,8 @@
import React, { useMemo } from 'react';
import { Box, Group } from 'coral-system';
import { ReactComponentProps } from '@music163/tango-helpers';
+import { DesignerViewType } from '@music163/tango-core';
+import { observer, useDesigner } from '@music163/tango-context';
import { getWidget } from '../widgets';
export interface ToolbarProps {
@@ -15,25 +17,23 @@ export function Toolbar({ children }: ToolbarProps) {
let prevPlacement: string;
React.Children.forEach(children, (child: React.ReactElement, index) => {
- let fallbackNode;
+ const childProps = child.props;
+
+ let fallbackChildren;
if (child.key) {
const Widget = getWidget(['toolbar', child.key].join('.'));
if (Widget) {
- fallbackNode = React.createElement(Widget, child.props.widgetProps);
+ fallbackChildren = ;
}
}
-
- let node = child.props.children ?? fallbackNode ?? null;
- if (!node) {
- node = child; // separator
- }
const key = child.key || index;
- node = (
-
- {node}
-
- );
- const placement = child.props.placement || prevPlacement || 'center';
+ const node = React.cloneElement(child, {
+ key,
+ 'data-key': key,
+ children: childProps.children || fallbackChildren,
+ });
+
+ const placement = childProps.placement || prevPlacement || 'center';
switch (placement) {
case 'left':
left.push(node);
@@ -68,17 +68,47 @@ export function Toolbar({ children }: ToolbarProps) {
}
export interface ToolbarItemProps extends ReactComponentProps {
+ /**
+ * 类型
+ */
+ type?: 'divider';
placement?: 'left' | 'center' | 'right';
children?: React.ReactElement;
/**
* 如果 key 匹配到内置组件的话,传递给子节点的属性
*/
widgetProps?: object;
+ /**
+ * 工具箱展示的视图
+ */
+ activeViews?: DesignerViewType[];
}
-function ToolbarItem({ placement, widgetProps, children }: ToolbarItemProps) {
- return {children}
;
-}
+const ToolbarItem = observer(
+ ({
+ activeViews = ['code', 'design', 'dual'],
+ type,
+ placement,
+ widgetProps,
+ children,
+ ...rest
+ }: ToolbarItemProps) => {
+ const designer = useDesigner();
+
+ if (!activeViews.includes(designer.activeView)) {
+ return null;
+ }
+
+ if (type === 'divider') {
+ return ;
+ }
+ return (
+
+ {children}
+
+ );
+ },
+);
function Separator() {
return ;
diff --git a/packages/designer/src/widgets.ts b/packages/designer/src/widgets.ts
index 109ce45..c468a0d 100644
--- a/packages/designer/src/widgets.ts
+++ b/packages/designer/src/widgets.ts
@@ -30,7 +30,7 @@ import {
const widgets = {};
export function registerWidget(key: string, component: React.ComponentType) {
- if (!/^(toolbar|sidebar|selectionMenu|contextMenu)\.[a-zA-z]+$/.test(key)) {
+ if (!/^(toolbar|sidebar|selectionMenu|contextMenu)\.[a-zA-Z]+$/.test(key)) {
throw new Error(
`Invalid widget key(${key}), should start with toolbar, sidebar, contextMenu or selectionMenu`,
);
diff --git a/packages/designer/src/workspace-view.tsx b/packages/designer/src/workspace-view.tsx
index 0218d0f..7e206c3 100644
--- a/packages/designer/src/workspace-view.tsx
+++ b/packages/designer/src/workspace-view.tsx
@@ -1,35 +1,37 @@
import React from 'react';
-import { Box } from 'coral-system';
+import { Box, HTMLCoralProps } from 'coral-system';
import { observer, useDesigner } from '@music163/tango-context';
import { DesignerViewType } from '@music163/tango-core';
-import { ComponentsPopover } from './components';
+import cx from 'classnames';
+import { ComponentsPopover, FileErrorsOverlay } from './components';
-export interface WorkspaceViewProps {
+export interface WorkspaceViewProps extends HTMLCoralProps<'div'> {
/**
* 视图面板模式,对应 Workspace 的模式
*/
mode?: DesignerViewType;
- children: React.ReactNode;
}
export const WorkspaceView = observer((props: WorkspaceViewProps) => {
- const { mode = 'design', children } = props;
+ const { mode = 'design', children, className, ...rest } = props;
const designer = useDesigner();
const display = mode !== designer.activeView ? 'none' : 'block';
- // 云音乐移动端模式小屏幕适配,可能会溢出屏幕
+ // 移动端模式小屏幕适配,可能会溢出屏幕
const overflow =
designer.simulator.name === 'phone' ? 'auto' : designer.isPreview ? 'auto' : 'hidden';
return (
{children}
{/* 添加组件弹层 */}
{display === 'block' && }
+ {mode === 'design' && }
);
});
diff --git a/packages/ui/src/icons/package-outlined.tsx b/packages/ui/src/icons/package-outlined.tsx
index 181dcac..f840c4d 100644
--- a/packages/ui/src/icons/package-outlined.tsx
+++ b/packages/ui/src/icons/package-outlined.tsx
@@ -8,6 +8,7 @@ const PackageOutlinedSvg = () => (
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
+ fill="currentColor"
>