Compare commits

...

12 Commits

Author SHA1 Message Date
wwsun 994db5db55 chore(release): publish
- @music163/tango-context@0.1.3
 - @music163/tango-core@0.1.3
 - @music163/tango-designer@0.2.0
 - @music163/tango-sandbox@0.1.3
 - @music163/tango-setting-form@0.2.0
 - @music163/tango-ui@0.1.3
2023-09-13 12:39:47 +08:00
wwsun 15542d9eb2 fix: refactor types 2023-09-13 12:37:21 +08:00
wwsun 4d809e9afd fix: update sidebar 2023-09-13 12:36:23 +08:00
wwsun b786de2f1a fix: refactor setting formItem 2023-09-13 12:36:23 +08:00
wwsun 8d396ff134 feat: refactor setting-form 2023-09-13 12:36:23 +08:00
wwsun 7e9ab12503 fix: remove some useless setters 2023-09-13 12:36:23 +08:00
wwsun 443ae589f6 fix: update exports 2023-09-13 12:36:23 +08:00
BoBoooooo 12de10b0bc fix: nodeTree defaultExpandAll rerender bug (#24)
* fix: fix: nodeTree defaultExpandAll rerender bug
* chore: tsconfig add tango-ui path
2023-09-13 10:20:09 +08:00
BoBoooooo 19d0a4523b fix(core): optimize find jsxElement parent node way (#22) 2023-09-11 19:09:44 +08:00
wwsun b80f5e4c10 chore(release): publish
- @music163/tango-designer@0.1.3
 - @music163/tango-setting-form@0.1.3
2023-09-07 16:42:45 +08:00
wwsun f9e6537dd6 fix: fix lint errors 2023-09-07 16:26:09 +08:00
wwsun 5ecf444fc3 fix: add WorkspaceView 2023-09-07 16:02:26 +08:00
82 changed files with 906 additions and 1604 deletions
+3 -1
View File
@@ -6,6 +6,8 @@
"@typescript-eslint/no-unused-vars": "warn",
"import/no-cycle": "off",
"no-nested-ternary": "off",
"no-useless-return":"off"
"no-useless-return":"off",
"no-param-reassign": "off",
"prefer-destructuring": "off"
}
}
+6 -6
View File
@@ -2,7 +2,7 @@ const packageJson = {
name: 'demo',
private: true,
dependencies: {
'@music163/antd': '0.1.5',
'@music163/antd': '0.1.6',
'@music163/tango-boot': '0.1.3',
react: '17.0.2',
'react-dom': '17.0.2',
@@ -11,7 +11,7 @@ const packageJson = {
},
};
const tangoJson = {
const tangoConfigJson = {
packages: {
react: {
version: '17.0.2',
@@ -52,7 +52,7 @@ const tangoJson = {
description: '云音乐低代码运行时框架',
},
'@music163/antd': {
version: '0.1.5',
version: '0.1.7',
library: 'TangoAntd',
type: 'baseDependency',
resources: [
@@ -148,7 +148,7 @@ import {
class App extends React.Component {
render() {
return (
<Page title="页面标题">
<Page title={tango.stores.app.title}>
<Section>
</Section>
<Section>
@@ -167,7 +167,7 @@ import { defineStore } from '@music163/tango-boot';
export default defineStore({
title: 'hello world',
title: 'Page Title',
array: [1, 2, 3],
}, 'app');
@@ -221,7 +221,7 @@ p {
export const sampleFiles = [
{ filename: '/package.json', code: JSON.stringify(packageJson) },
{ filename: '/appJson.json', code: JSON.stringify(appJson) },
{ filename: '/tango.config.json', code: JSON.stringify(tangoJson) },
{ filename: '/tango.config.json', code: JSON.stringify(tangoConfigJson) },
{ filename: '/README.md', code: '# readme' },
{ filename: '/src/index.less', code: lessCode },
{ filename: '/src/style.css', code: cssCode },
+7 -7
View File
@@ -1,4 +1,6 @@
import React, { useState } from 'react';
import { Box } from 'coral-system';
import { Button, Space } from 'antd';
import {
Designer,
DesignerPanel,
@@ -6,8 +8,8 @@ import {
Sidebar,
Toolbar,
WorkspacePanel,
WorkspaceView,
ComponentsPanel,
ViewPanel,
CodeEditor,
Sandbox,
DndQuery,
@@ -16,8 +18,6 @@ import { createEngine, Workspace } from '@music163/tango-core';
import { Logo, ProjectDetail } from './share';
import { sampleFiles } from '../mock/project';
import './index.less';
import { Box } from 'coral-system';
import { Button, Space } from 'antd';
// 1. 实例化工作区
const workspace = new Workspace({
@@ -74,7 +74,7 @@ export default function App() {
<Sidebar.Item key="dependency" isFloat width={800} />
</Sidebar>
<WorkspacePanel>
<ViewPanel mode="design">
<WorkspaceView mode="design">
<Sandbox
onMessage={(e) => {
if (e.type === 'done') {
@@ -91,10 +91,10 @@ export default function App() {
}
}}
/>
</ViewPanel>
<ViewPanel mode="code">
</WorkspaceView>
<WorkspaceView mode="code">
<CodeEditor />
</ViewPanel>
</WorkspaceView>
</WorkspacePanel>
<SettingPanel />
</DesignerPanel>
+1 -7
View File
@@ -53,19 +53,13 @@
"jest": "^29.1.1",
"jest-environment-jsdom": "^29.1.1",
"lerna": "^6.6.1",
"less": "^4.1.2",
"less-loader": "^7.3.0",
"lint-staged": "^13.1.0",
"mini-css-extract-plugin": "^1.6.2",
"prettier": "^2.8.7",
"progress-bar-webpack-plugin": "^2.1.0",
"react": "^17.0.0",
"react-dom": "^17.0.0",
"styled-components": "^5.3.6",
"typedoc": "^0.25.0",
"typescript": "^5.0.4",
"webpack": "^4.46.0",
"webpack-cli": "^4.9.2"
"typescript": "^5.0.4"
},
"resolutions": {
"@yarnpkg/parsers": "3.0.0-rc.48.1"
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.1.3](https://github.com/netease/tango/compare/@music163/tango-context@0.1.2...@music163/tango-context@0.1.3) (2023-09-13)
**Note:** Version bump only for package @music163/tango-context
## 0.1.2 (2023-09-06)
**Note:** Version bump only for package @music163/tango-context
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-context",
"version": "0.1.2",
"version": "0.1.3",
"description": "react context for tango-apps",
"keywords": [
"react",
@@ -31,7 +31,7 @@
"react": ">= 16.8.0"
},
"dependencies": {
"@music163/tango-core": "^0.1.2",
"@music163/tango-core": "^0.1.3",
"@music163/tango-helpers": "^0.1.2",
"mobx-react-lite": "4.0.2"
},
+9
View File
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.1.3](https://github.com/netease/tango/compare/@music163/tango-core@0.1.2...@music163/tango-core@0.1.3) (2023-09-13)
### Bug Fixes
- **core:** optimize find jsxElement parent node way ([#22](https://github.com/netease/tango/issues/22)) ([19d0a45](https://github.com/netease/tango/commit/19d0a4523bb31ea9286714737a0f4f1883e1c801))
- refactor setting formItem ([b786de2](https://github.com/netease/tango/commit/b786de2f1a0e4e9141eb09fce696e45df633b232))
- refactor types ([15542d9](https://github.com/netease/tango/commit/15542d9eb2f8959597b81cae457091ee71710c83))
- update sidebar ([4d809e9](https://github.com/netease/tango/commit/4d809e9afd0d6d525850708722736847b510638e))
## 0.1.2 (2023-09-06)
### Bug Fixes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-core",
"version": "0.1.2",
"version": "0.1.3",
"description": "tango core",
"author": "wwsun <ww.sun@outlook.com>",
"homepage": "",
+22 -21
View File
@@ -32,12 +32,12 @@ import {
import { isValidComponentName } from '../string';
import { isDefineService, isDefineStore } from '../assert';
import type {
RouteDataType,
StorePropertyType,
ClassPropertyNodeType,
TangoViewNodeDataType,
ImportDeclarationPayloadType,
ServiceFunctionPayloadType,
IRouteData,
IStorePropertyData,
IClassPropertyNodeData,
ITangoViewNodeData,
IImportDeclarationPayload,
IServiceFunctionPayload,
InsertChildPositionType,
} from '../../types';
import { IdGenerator } from '../id-generator';
@@ -459,7 +459,7 @@ function getLastExportDeclarationIndex(node: t.Program) {
* @param importedSourcePath
* @returns
*/
export function makeImportDeclaration(importedModule: ImportDeclarationPayloadType) {
export function makeImportDeclaration(importedModule: IImportDeclarationPayload) {
const specifierNodes =
importedModule.specifiers?.map((localName) =>
t.importSpecifier(t.identifier(localName), t.identifier(localName)),
@@ -474,7 +474,7 @@ export function makeImportDeclaration(importedModule: ImportDeclarationPayloadTy
return t.importDeclaration(newSpecifierNodes, t.stringLiteral(importedModule.sourcePath));
}
function getImportDeclarationData(node: t.ImportDeclaration): ImportDeclarationPayloadType {
function getImportDeclarationData(node: t.ImportDeclaration): IImportDeclarationPayload {
const sourcePath = node2value(node.source);
let defaultSpecifier;
const specifiers: string[] = [];
@@ -499,7 +499,7 @@ function getImportDeclarationData(node: t.ImportDeclaration): ImportDeclarationP
* @param importedSourcePath
* @returns
*/
export function addImportDeclaration(ast: t.File, importedModule: ImportDeclarationPayloadType) {
export function addImportDeclaration(ast: t.File, importedModule: IImportDeclarationPayload) {
traverse(ast, {
Program(path) {
const lastIndex = getLastImportDeclarationIndex(path.node);
@@ -518,7 +518,7 @@ export function addImportDeclaration(ast: t.File, importedModule: ImportDeclarat
* @param importedSourcePath
* @returns
*/
export function updateImportDeclaration(ast: t.File, importedModule: ImportDeclarationPayloadType) {
export function updateImportDeclaration(ast: t.File, importedModule: IImportDeclarationPayload) {
traverse(ast, {
ImportDeclaration(path) {
const currentSourcePath = node2value(path.node.source);
@@ -656,7 +656,7 @@ export function removeRouteFromRouteFile(ast: t.File, routePath: string, importP
}
export function traverseRouteFile(ast: t.File) {
const routes: RouteDataType[] = [];
const routes: IRouteData[] = [];
const importMap: Dict<string> = {};
traverse(ast, {
@@ -819,8 +819,8 @@ export function updateStoreState(ast: t.File, stateName: string, code: string) {
export function traverseStoreFile(ast: t.File) {
let namespace: string;
const actions: StorePropertyType[] = [];
const states: StorePropertyType[] = [];
const actions: IStorePropertyData[] = [];
const states: IStorePropertyData[] = [];
traverse(ast, {
CallExpression(path) {
@@ -909,7 +909,7 @@ export function deleteServiceConfigFromServiceFile(ast: t.File, serviceFunctionN
* @param payload
* @returns
*/
export function serviceConfig2Node(payload: ServiceFunctionPayloadType) {
export function serviceConfig2Node(payload: IServiceFunctionPayload) {
return object2node(payload, (value, key) => {
if (key === 'formatter' && value) {
return code2expression(value);
@@ -920,7 +920,7 @@ export function serviceConfig2Node(payload: ServiceFunctionPayloadType) {
export function updateServiceConfigToServiceFile(
ast: t.File,
config: { [key: string]: ServiceFunctionPayloadType },
config: { [key: string]: IServiceFunctionPayload },
) {
traverse(ast, {
CallExpression(path) {
@@ -1133,11 +1133,11 @@ export function cloneJSXElementWithoutTrackingData(node: t.JSXElement) {
}
export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {
const importedModules: Dict<ImportDeclarationPayloadType | ImportDeclarationPayloadType[]> = {};
const classProperties: Dict<ClassPropertyNodeType> = {};
const importedModules: Dict<IImportDeclarationPayload | IImportDeclarationPayload[]> = {};
const classProperties: Dict<IClassPropertyNodeData> = {};
let stateProperties: string[] = [];
let stateCode = '';
const nodes: Array<TangoViewNodeDataType<t.JSXElement>> = [];
const nodes: Array<ITangoViewNodeData<t.JSXElement>> = [];
const cloneAst = t.cloneNode(ast, true, true);
const cleanAst = removeTrackingDataFromViewAst(cloneAst);
const variables: string[] = []; // 使用的 tango 变量
@@ -1216,9 +1216,10 @@ export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {
// parentId 用于追溯上下游关系
let parentId;
const parentNode = path.parentPath.node;
if (t.isJSXElement(parentNode)) {
const parentAttributes = getJSXElementAttributes(parentNode);
const parentNode = path.findParent((p) => p.isJSXElement());
if (t.isJSXElement(parentNode?.node)) {
const parentAttributes = getJSXElementAttributes(parentNode.node);
parentId = parentAttributes[SLOT.dnd];
}
+2 -2
View File
@@ -1,4 +1,4 @@
import { ImportDeclarationPayloadType } from '../types';
import { IImportDeclarationPayload } from '../types';
/**
* 导入列表解析为导入声明对象
@@ -20,7 +20,7 @@ export function namesToImportDeclarations(
return Object.keys(map).map((sourcePath) => ({
sourcePath,
...map[sourcePath],
})) as ImportDeclarationPayloadType[];
})) as IImportDeclarationPayload[];
}
function updateMod(
+2 -2
View File
@@ -8,7 +8,7 @@ import {
uuid,
} from '@music163/tango-helpers';
import { getRelativePath, isFilepath } from './string';
import type { ImportDeclarationPayloadType } from '../types';
import type { IImportDeclarationPayload } from '../types';
import { code2expression } from './ast';
/**
@@ -17,7 +17,7 @@ import { code2expression } from './ast';
export function getImportDeclarationPayloadByPrototype(
prototype: ComponentPrototypeType,
relativeFilepath?: string,
): ImportDeclarationPayloadType {
): IImportDeclarationPayload {
let defaultSpecifier;
let specifiers;
+1 -1
View File
@@ -1,4 +1,4 @@
export * from './models';
export * from './factory';
export * from './types';
export * from './helpers';
export * from './types';
+5 -5
View File
@@ -1,8 +1,8 @@
import { action, computed, makeObservable, observable, toJS } from 'mobx';
import { getValue, isNil, logger, setValue } from '@music163/tango-helpers';
import type { FileType, ModulePropsType } from '../types';
import { formatCode } from '../helpers';
import type { FileType, IFileConfig } from '../types';
import { IWorkspace } from './interfaces';
import { formatCode } from '../helpers';
/**
* 普通文件,不进行 AST 解析
@@ -35,7 +35,7 @@ export class TangoFile {
return this._cleanCode;
}
constructor(workspace: IWorkspace, props: ModulePropsType, isSyncCode = true) {
constructor(workspace: IWorkspace, props: IFileConfig, isSyncCode = true) {
this.workspace = workspace;
this.filename = props.filename;
this.type = props.type;
@@ -60,7 +60,7 @@ export class TangoFile {
}
export class TangoLessFile extends TangoFile {
constructor(workspace: IWorkspace, props: ModulePropsType) {
constructor(workspace: IWorkspace, props: IFileConfig) {
super(workspace, props, false);
this.update(props.code);
makeObservable(this, {
@@ -87,7 +87,7 @@ export class TangoJsonFile extends TangoFile {
return toJS(this._object);
}
constructor(workspace: IWorkspace, props: ModulePropsType) {
constructor(workspace: IWorkspace, props: IFileConfig) {
super(workspace, props, false);
this.update(props.code);
makeObservable(this, {
+56 -23
View File
@@ -3,15 +3,15 @@ import { TangoHistory } from './history';
import { SelectSource } from './select-source';
import { DragSource } from './drag-source';
import {
FileItemType,
IFileConfig,
FileType,
ImportDeclarationPayloadType,
IImportDeclarationPayload,
InsertChildPositionType,
PackageConfigType,
PageConfigType,
ProjectDataType,
ServiceFunctionPayloadType,
} from '../types/types';
ITangoConfigPackages,
IPageConfigData,
IProjectData,
IServiceFunctionPayload,
} from '../types';
import { TangoFile, TangoJsonFile } from './file';
import { TangoRouteModule, TangoServiceModule, TangoStoreModule } from './module';
@@ -38,17 +38,41 @@ export interface IViewFile {
sourcePrototype?: string | ComponentPrototypeType,
) => IViewFile;
insertAfter: (targetNodeId: string, newNode: any, sourcePrototype?: string | ComponentPrototypeType) => IViewFile;
insertAfter: (
targetNodeId: string,
newNode: any,
sourcePrototype?: string | ComponentPrototypeType,
) => IViewFile;
insertBefore: (targetNodeId: string, newNode: any, sourcePrototype?: string | ComponentPrototypeType) => IViewFile;
insertBefore: (
targetNodeId: string,
newNode: any,
sourcePrototype?: string | ComponentPrototypeType,
) => IViewFile;
replaceNode: (targetNodeId: string, newNode: any, sourcePrototype?: string | ComponentPrototypeType) => IViewFile;
replaceNode: (
targetNodeId: string,
newNode: any,
sourcePrototype?: string | ComponentPrototypeType,
) => IViewFile;
replaceViewChildren: (rawNodes: any[], importDeclarations?: ImportDeclarationPayloadType[]) => IViewFile;
replaceViewChildren: (
rawNodes: any[],
importDeclarations?: IImportDeclarationPayload[],
) => IViewFile;
updateNodeAttribute: (nodeId: string, attrName: string, attrValue?: any, relatedImports?: string[]) => IViewFile;
updateNodeAttribute: (
nodeId: string,
attrName: string,
attrValue?: any,
relatedImports?: string[],
) => IViewFile;
updateNodeAttributes: (nodeId: string, config: Record<string, any>, relatedImports?: string[]) => IViewFile;
updateNodeAttributes: (
nodeId: string,
config: Record<string, any>,
relatedImports?: string[],
) => IViewFile;
get code(): string;
get nodes(): Map<string, IViewNode>;
@@ -129,7 +153,7 @@ export interface IWorkspace {
/**
* 获取项目数据
*/
getProjectData?: () => ProjectDataType;
getProjectData?: () => IProjectData;
/**
* 查询节点
@@ -142,7 +166,7 @@ export interface IWorkspace {
listForms?: () => Record<string, string[]>;
addFile: (filename: string, code: string, fileType?: FileType) => void;
addFiles: (files: FileItemType[]) => void;
addFiles: (files: IFileConfig[]) => void;
updateFile: (filename: string, code: string, shouldFormatCode?: boolean) => void;
removeFile: (filename: string) => void;
renameFile: (oldFilename: string, newFilename: string) => void;
@@ -163,11 +187,20 @@ export interface IWorkspace {
dropNode: () => void;
insertToNode: (targetNodeId: string, sourceNameOrPrototype: string | ComponentPrototypeType) => void;
insertToNode: (
targetNodeId: string,
sourceNameOrPrototype: string | ComponentPrototypeType,
) => void;
replaceNode: (targetNodeId: string, sourceNameOrPrototype: string | ComponentPrototypeType) => void;
replaceNode: (
targetNodeId: string,
sourceNameOrPrototype: string | ComponentPrototypeType,
) => void;
updateSelectedNodeAttributes: (attributes: Record<string, any>, relatedImports?: string[]) => void;
updateSelectedNodeAttributes: (
attributes: Record<string, any>,
relatedImports?: string[],
) => void;
addBlock?: (files: object, name: string) => void;
@@ -175,7 +208,7 @@ export interface IWorkspace {
removeServiceFunction?: (serviceName: string) => void;
addServiceFunction?: (payload: ServiceFunctionPayloadType | ServiceFunctionPayloadType[]) => void;
addServiceFunction?: (payload: IServiceFunctionPayload | IServiceFunctionPayload[]) => void;
updateServiceFunction?: (payload: any) => void;
@@ -195,9 +228,9 @@ export interface IWorkspace {
removeViewModule: (routePath: string) => void;
copyViewPage: (sourceRoutePath: string, targetPageData: PageConfigType) => void;
copyViewPage: (sourceRoutePath: string, targetPageData: IPageConfigData) => void;
updateRoute: (sourceRoutePath: string, targetPageData: PageConfigType) => void;
updateRoute: (sourceRoutePath: string, targetPageData: IPageConfigData) => void;
listDependencies?: () => any;
@@ -205,7 +238,7 @@ export interface IWorkspace {
name: string,
version: string,
options?: {
package?: PackageConfigType;
package?: ITangoConfigPackages;
[x: string]: any;
},
) => void;
@@ -216,7 +249,7 @@ export interface IWorkspace {
name: string,
version: string,
options?: {
package?: PackageConfigType;
package?: ITangoConfigPackages;
[x: string]: any;
},
) => void;
+28 -28
View File
@@ -38,13 +38,13 @@ import {
import { TangoNode } from './node';
import { TangoFile } from './file';
import {
RouteDataType,
ModulePropsType,
ClassPropertyNodeType,
ServiceFunctionPayloadType,
TangoViewNodeDataType,
StorePropertyType,
ImportDeclarationPayloadType,
IRouteData,
IFileConfig,
IClassPropertyNodeData,
IServiceFunctionPayload,
ITangoViewNodeData,
IStorePropertyData,
IImportDeclarationPayload,
InsertChildPositionType,
} from '../types';
import { IViewFile, IWorkspace } from './interfaces';
@@ -57,7 +57,7 @@ import { IViewFile, IWorkspace } from './interfaces';
class TangoModule extends TangoFile {
ast: t.File;
constructor(workspace: IWorkspace, props: ModulePropsType, isSyncCode = true) {
constructor(workspace: IWorkspace, props: IFileConfig, isSyncCode = true) {
super(workspace, props, isSyncCode);
}
@@ -119,7 +119,7 @@ class TangoModule extends TangoFile {
* 普通 JS 文件
*/
export class TangoJsModule extends TangoModule {
constructor(workspace: IWorkspace, props: ModulePropsType) {
constructor(workspace: IWorkspace, props: IFileConfig) {
super(workspace, props, false);
this.update(props.code, false, false);
@@ -143,7 +143,7 @@ export class TangoStoreEntryModule extends TangoModule {
return toJS(this._stores);
}
constructor(workspace: IWorkspace, props: ModulePropsType) {
constructor(workspace: IWorkspace, props: IFileConfig) {
super(workspace, props, false);
this.update(props.code, true, false);
@@ -185,13 +185,13 @@ export class TangoStoreEntryModule extends TangoModule {
* 路由配置模块
*/
export class TangoRouteModule extends TangoModule {
_routes: RouteDataType[];
_routes: IRouteData[];
get routes() {
return toJS(this._routes);
}
constructor(workspace: IWorkspace, props: ModulePropsType) {
constructor(workspace: IWorkspace, props: IFileConfig) {
super(workspace, props, false);
this.update(props.code, true, false);
@@ -264,7 +264,7 @@ export class TangoRouteModule extends TangoModule {
*/
export class TangoViewModule extends TangoModule implements IViewFile {
// 解析为树结构的 jsxNodes 数组
_nodesTree: TangoViewNodeDataType[];
_nodesTree: ITangoViewNodeData[];
/**
* 通过导入组件名查找组件来自的包
*/
@@ -282,12 +282,12 @@ export class TangoViewModule extends TangoModule implements IViewFile {
/**
* 导入的模块
*/
private _importedModules: Dict<ImportDeclarationPayloadType | ImportDeclarationPayloadType[]>;
private _importedModules: Dict<IImportDeclarationPayload | IImportDeclarationPayload[]>;
/**
* 类属性
* @deprecated
*/
private _classProperties: Dict<ClassPropertyNodeType>;
private _classProperties: Dict<IClassPropertyNodeData>;
/**
* 状态属性
*/
@@ -325,7 +325,7 @@ export class TangoViewModule extends TangoModule implements IViewFile {
return this.ast;
}
constructor(workspace: IWorkspace, props: ModulePropsType) {
constructor(workspace: IWorkspace, props: IFileConfig) {
super(workspace, props, false);
this._nodes = new Map();
this._idGenerator = new IdGenerator({ prefix: props.filename });
@@ -401,7 +401,7 @@ export class TangoViewModule extends TangoModule implements IViewFile {
/**
* 更新导入的变量(新版)
*/
updateImportSpecifiers(importDeclaration: ImportDeclarationPayloadType) {
updateImportSpecifiers(importDeclaration: IImportDeclarationPayload) {
const mods = this._importedModules[importDeclaration.sourcePath];
let ast;
// 如果模块已存在,需要去重
@@ -540,7 +540,7 @@ export class TangoViewModule extends TangoModule implements IViewFile {
*/
replaceViewChildren(
childrenNodes: t.JSXElement[],
importDeclarations?: ImportDeclarationPayloadType[],
importDeclarations?: IImportDeclarationPayload[],
) {
if (childrenNodes.length) {
this.ast = replaceRootJSXElementChildren(this.ast, childrenNodes);
@@ -556,7 +556,7 @@ export class TangoViewModule extends TangoModule implements IViewFile {
}
private buildImportMap(
importedModules: Dict<ImportDeclarationPayloadType | ImportDeclarationPayloadType[]>,
importedModules: Dict<IImportDeclarationPayload | IImportDeclarationPayload[]>,
) {
const map = {};
Object.keys(importedModules).forEach((modName) => {
@@ -583,8 +583,8 @@ export class TangoViewModule extends TangoModule implements IViewFile {
* 将节点列表转换为 tree data 嵌套数组
* @param list
*/
export function nodeListToTreeData(list: TangoViewNodeDataType[]) {
const map: Record<string, TangoViewNodeDataType> = {};
export function nodeListToTreeData(list: ITangoViewNodeData[]) {
const map: Record<string, ITangoViewNodeData> = {};
list.forEach((item) => {
// 如果不存在,则初始化
@@ -627,7 +627,7 @@ export class TangoServiceModule extends TangoModule {
return toJS(this._baseConfig);
}
constructor(workspace: IWorkspace, props: ModulePropsType) {
constructor(workspace: IWorkspace, props: IFileConfig) {
super(workspace, props, false);
this.name = getModuleNameByFilename(props.filename);
this.update(props.code, true, false);
@@ -651,13 +651,13 @@ export class TangoServiceModule extends TangoModule {
this._baseConfig = baseConfig;
}
addServiceFunction(payload: ServiceFunctionPayloadType) {
addServiceFunction(payload: IServiceFunctionPayload) {
const { name, ...rest } = payload;
this.ast = updateServiceConfigToServiceFile(this.ast, { [name]: clone(rest, false) });
return this;
}
addServiceFunctions(payloads: ServiceFunctionPayloadType[]) {
addServiceFunctions(payloads: IServiceFunctionPayload[]) {
const config = payloads.reduce((acc, cur) => {
const { name, ...rest } = cur;
acc[name] = clone(rest, false);
@@ -667,7 +667,7 @@ export class TangoServiceModule extends TangoModule {
return this;
}
updateServiceFunction(payload: ServiceFunctionPayloadType) {
updateServiceFunction(payload: IServiceFunctionPayload) {
const { name, ...rest } = payload;
this.ast = updateServiceConfigToServiceFile(this.ast, { [name]: clone(rest, false) });
return this;
@@ -702,11 +702,11 @@ export class TangoStoreModule extends TangoModule {
namespace: string;
states: StorePropertyType[];
states: IStorePropertyData[];
actions: StorePropertyType[];
actions: IStorePropertyData[];
constructor(workspace: IWorkspace, props: ModulePropsType) {
constructor(workspace: IWorkspace, props: IFileConfig) {
super(workspace, props, false);
this.name = getModuleNameByFilename(props.filename);
this.update(props.code, true, false);
+2 -2
View File
@@ -1,10 +1,10 @@
import { JSXElement, SourceLocation } from '@babel/types';
import { cloneJSXElementWithoutTrackingData, getJSXElementAttributes } from '../helpers';
import { TangoViewNodeDataType } from '../types';
import { ITangoViewNodeData } from '../types';
import { TangoViewModule } from './module';
import { IViewNode } from './interfaces';
type TangoNodeConstructorPropsType = TangoViewNodeDataType & {
type TangoNodeConstructorPropsType = ITangoViewNodeData & {
file: TangoViewModule;
};
+25 -25
View File
@@ -31,19 +31,19 @@ import {
TangoJsModule,
} from './module';
import { TangoFile, TangoJsonFile, TangoLessFile } from './file';
import {
FileItemType,
FileType,
PackageConfigType,
PageConfigType,
ProjectDataType,
ServiceFunctionPayloadType,
} from '../types';
import { IWorkspace } from './interfaces';
import {
IFileConfig,
FileType,
ITangoConfigPackages,
IPageConfigData,
IProjectData,
IServiceFunctionPayload,
} from '../types';
import { SelectSource } from './select-source';
import { DragSource } from './drag-source';
export type WorkspaceOptionsType = {
export interface IWorkspaceOptions {
/**
* 入口文件
*/
@@ -51,7 +51,7 @@ export type WorkspaceOptionsType = {
/**
* 初始化文件列表
*/
files?: FileItemType[];
files?: IFileConfig[];
/**
* 默认的激活的路由
*/
@@ -60,7 +60,7 @@ export type WorkspaceOptionsType = {
* 组件描述列表
*/
prototypes?: Record<string, ComponentPrototypeType>;
};
}
/**
* 工作区
@@ -180,7 +180,7 @@ export class Workspace extends EventTarget implements IWorkspace {
get pages() {
const appJsonPages = this.appJson?.getValue('pages');
const pagesMap = array2object(appJsonPages || [], (item) => item.path);
const ret: PageConfigType[] = [];
const ret: IPageConfigData[] = [];
this.routeModule?.routes.forEach((item) => {
const data = pagesMap[item.path];
if (item.path !== '*') {
@@ -225,7 +225,7 @@ export class Workspace extends EventTarget implements IWorkspace {
return Object.keys(this.localBlocks);
}
constructor(options?: WorkspaceOptionsType) {
constructor(options?: IWorkspaceOptions) {
super();
this.history = new TangoHistory(this);
this.selectSource = new SelectSource(this);
@@ -292,7 +292,7 @@ export class Workspace extends EventTarget implements IWorkspace {
functions: Object.keys(this.serviceModule.serviceFunctions),
},
};
const data: ProjectDataType = {
const data: IProjectData = {
pages,
stores,
services,
@@ -353,7 +353,7 @@ export class Workspace extends EventTarget implements IWorkspace {
* 添加一组文件到工作区
* @param files
*/
addFiles(files: FileItemType[] = []) {
addFiles(files: IFileConfig[] = []) {
files.forEach((file) => {
this.addFile(file.filename, file.code, file.type);
});
@@ -540,7 +540,7 @@ export class Workspace extends EventTarget implements IWorkspace {
// remove appJson page
this.appJson
?.setValue('pages', (pages) => {
return (pages as PageConfigType[]).filter((page) => page.path !== routePath);
return (pages as IPageConfigData[]).filter((page) => page.path !== routePath);
})
.update();
this.removeFile(filename);
@@ -552,7 +552,7 @@ export class Workspace extends EventTarget implements IWorkspace {
* @param route 视图名
* @param code 视图代码
*/
addViewPage(routeConfig: string | PageConfigType, code: string) {
addViewPage(routeConfig: string | IPageConfigData, code: string) {
const config =
typeof routeConfig === 'string'
? {
@@ -569,11 +569,11 @@ export class Workspace extends EventTarget implements IWorkspace {
/**
* 添加新的路由
*/
addRoute(routeData: PageConfigType, importFilePath: string) {
addRoute(routeData: IPageConfigData, importFilePath: string) {
this.routeModule?.addRoute(routeData.path, importFilePath).update();
this.appJson
?.setValue('pages', (pages) => {
(pages as PageConfigType[]).push({
(pages as IPageConfigData[]).push({
name: routeData.name || routeData.path,
path: routeData.path,
privilegeCode: getPrivilegeCode(this.packageJson?.getValue('name'), routeData.path),
@@ -588,13 +588,13 @@ export class Workspace extends EventTarget implements IWorkspace {
* @param sourceRoutePath
* @param targetPageData
*/
updateRoute(sourceRoutePath: string, targetPageData: PageConfigType) {
updateRoute(sourceRoutePath: string, targetPageData: IPageConfigData) {
if (sourceRoutePath !== targetPageData.path) {
this.routeModule?.updateRoute(sourceRoutePath, targetPageData.path).update();
}
this.appJson
?.setValue('pages', (pages) => {
for (const page of pages as PageConfigType[]) {
for (const page of pages as IPageConfigData[]) {
if (page.path === sourceRoutePath) {
page.path = targetPageData.path;
page.name = targetPageData.name;
@@ -611,7 +611,7 @@ export class Workspace extends EventTarget implements IWorkspace {
* @param sourceRoute
* @param targetRouteConfig
*/
copyViewPage(sourceRoutePath: string, targetPageData: PageConfigType) {
copyViewPage(sourceRoutePath: string, targetPageData: IPageConfigData) {
const sourceFilePath = this.getRealViewFilePath(this.getFilenameByRoutePath(sourceRoutePath));
const targetFilePath = getFilepath(targetPageData.path, '/src/pages');
this.copyFiles(sourceFilePath, targetFilePath);
@@ -771,7 +771,7 @@ export class Workspace extends EventTarget implements IWorkspace {
/**
* 新增服务函数,支持批量添加
*/
addServiceFunction(payload: ServiceFunctionPayloadType | ServiceFunctionPayloadType[]) {
addServiceFunction(payload: IServiceFunctionPayload | IServiceFunctionPayload[]) {
if (Array.isArray(payload)) {
this.serviceModule.addServiceFunctions(payload).update();
} else {
@@ -811,7 +811,7 @@ export class Workspace extends EventTarget implements IWorkspace {
name: string,
version: string,
options?: {
package?: PackageConfigType;
package?: ITangoConfigPackages;
[x: string]: any;
},
) {
@@ -906,7 +906,7 @@ export class Workspace extends EventTarget implements IWorkspace {
name: string,
version: string,
options?: {
package?: PackageConfigType;
package?: ITangoConfigPackages;
[x: string]: any;
},
) {
@@ -27,7 +27,7 @@ export enum FileType {
Scss = 'scss',
}
export type FileItemType = {
export interface IFileConfig {
/**
*
*/
@@ -40,14 +40,12 @@ export type FileItemType = {
*
*/
type?: FileType;
};
export type ModulePropsType = FileItemType;
}
/**
*
*/
export type TangoViewNodeDataType<T = JSXElement> = {
export interface ITangoViewNodeData<T = JSXElement> {
/**
* ID
*/
@@ -71,39 +69,39 @@ export type TangoViewNodeDataType<T = JSXElement> = {
/**
*
*/
children?: Array<TangoViewNodeDataType<T>>;
};
children?: Array<ITangoViewNodeData<T>>;
}
/**
*
*/
export type ImportDeclarationPayloadType = {
export interface IImportDeclarationPayload {
defaultSpecifier?: string;
specifiers?: string[];
sourcePath: string;
};
}
export type ClassPropertyNodeType = {
export interface IClassPropertyNodeData {
reference: string;
propertyName: string;
propertyBody: string;
};
}
/**
*
*/
export type ServiceFunctionPayloadType = {
export interface IServiceFunctionPayload {
[key: string]: any;
/**
*
*/
name: string;
};
}
/**
* Store
*/
export type StorePropertyType = {
export interface IStorePropertyData {
/**
*
*/
@@ -130,50 +128,12 @@ export type StorePropertyType = {
| 'type'
| 'variable'
| 'object';
};
/*
*
*/
export enum ESFOperationType {
ADD = 'add',
UPDATE = 'update',
DELETE = 'delete',
}
/**
*
*/
export enum ESFDataSourceType {
OvermindX = 'ox',
Custom = 'custom',
}
/**
* HTTP Type
* get post
*/
export enum ESFHTTPMethodType {
GET = 'GET',
// PUT = 'PUT',
POST = 'POST',
// PATCH = 'PATCH',
// DELETE = 'DELETE',
}
/**
* requestType
* headers: Content-Type
*/
export enum ESFHTTRequestType {
'application/json' = 'json',
'application/x-www-form-urlencoded' = 'x-www-form-urlencoded',
}
/**
*
*/
export type RouteDataType = {
export interface IRouteData {
/**
*
*/
@@ -187,12 +147,12 @@ export type RouteDataType = {
*/
importPath?: string;
[key: string]: any;
};
}
/**
*
*/
export type PageConfigType = {
export interface IPageConfigData {
/**
*
*/
@@ -209,12 +169,12 @@ export type PageConfigType = {
* PMS
*/
privilegeCode?: string;
};
}
/**
*
*/
export type ProjectDataType = {
export interface IProjectData {
/**
*
*/
@@ -241,14 +201,14 @@ export type ProjectDataType = {
functions: string[];
};
};
};
}
export type InsertChildPositionType = 'first' | 'last';
/**
* tango.config.json packages
*/
export type PackageConfigType = {
export interface ITangoConfigPackages {
/**
*
*/
@@ -273,4 +233,4 @@ export type PackageConfigType = {
* umd
*/
designerResources?: string[];
};
}
-1
View File
@@ -1 +0,0 @@
export * from './types';
+21
View File
@@ -3,6 +3,27 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.2.0](https://github.com/netease/tango/compare/@music163/tango-designer@0.1.3...@music163/tango-designer@0.2.0) (2023-09-13)
### Bug Fixes
- nodeTree defaultExpandAll rerender bug ([#24](https://github.com/netease/tango/issues/24)) ([12de10b](https://github.com/netease/tango/commit/12de10b0bc89a27d80b1b3bbb4d4b5aed4b73b35))
- refactor setting formItem ([b786de2](https://github.com/netease/tango/commit/b786de2f1a0e4e9141eb09fce696e45df633b232))
- refactor types ([15542d9](https://github.com/netease/tango/commit/15542d9eb2f8959597b81cae457091ee71710c83))
- remove some useless setters ([7e9ab12](https://github.com/netease/tango/commit/7e9ab12503ed33c1e6acb8a1fa5fd89fc82d35fd))
- update exports ([443ae58](https://github.com/netease/tango/commit/443ae589f67255f3815b587fd8f928c2b8742e9a))
- update sidebar ([4d809e9](https://github.com/netease/tango/commit/4d809e9afd0d6d525850708722736847b510638e))
### Features
- refactor setting-form ([8d396ff](https://github.com/netease/tango/commit/8d396ff13459beeb57b6b3c48f7e8fe1765041ae))
## [0.1.3](https://github.com/netease/tango/compare/@music163/tango-designer@0.1.2...@music163/tango-designer@0.1.3) (2023-09-07)
### Bug Fixes
- add WorkspaceView ([5ecf444](https://github.com/netease/tango/commit/5ecf444fc3563a1bf8e533136e93c1a1355d39dc))
## 0.1.2 (2023-09-06)
### Bug Fixes
+12 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-designer",
"version": "0.1.2",
"version": "0.2.0",
"description": "lowcode designer",
"keywords": [
"react"
@@ -33,22 +33,28 @@
"dependencies": {
"@ant-design/icons": "^4.8.0",
"@music163/request": "^0.1.0",
"@music163/tango-context": "^0.1.2",
"@music163/tango-core": "^0.1.2",
"@music163/tango-context": "^0.1.3",
"@music163/tango-core": "^0.1.3",
"@music163/tango-helpers": "^0.1.2",
"@music163/tango-sandbox": "^0.1.2",
"@music163/tango-setting-form": "^0.1.2",
"@music163/tango-ui": "^0.1.2",
"@music163/tango-sandbox": "^0.1.3",
"@music163/tango-setting-form": "^0.2.0",
"@music163/tango-ui": "^0.1.3",
"antd": "^4.24.2",
"cash-dom": "^8.1.2",
"classnames": "^2.3.2",
"color": "^4.2.3",
"coral-system": "^1.0.5",
"cssjson": "^2.1.3",
"date-fns": "^2.29.2",
"lodash-es": "^4.17.21",
"moment": "^2.29.4",
"react-color": "^2.19.3",
"react-resizable": "^3.0.5",
"semver": "^7.3.8"
},
"devDependencies": {
"@types/color": "^3.0.4",
"@types/react-color": "^3.0.6",
"@types/react-resizable": "^3.0.4"
},
"publishConfig": {
@@ -3,7 +3,6 @@ import { Input } from 'antd';
import { css } from 'styled-components';
import { Box } from 'coral-system';
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import { formatValue, compareValue } from './util';
const rowCss = css`
display: flex;
@@ -99,25 +98,25 @@ const OneInputKV = (props: any) => {
!newData.length && setValidate(false);
};
const handleInput = (name: string, index: number, value: string) => {
const handleInput = (name: string, index: number, val: string) => {
const newData = [...data];
if (!newData[index]) return;
// 更新值
newData[index][name] = value;
newData[index][name] = val;
_updateData(newData);
};
const _updateData = (values: Array<Record<string, string>>) => {
const omit = ({ index, ...rest }: any) => rest;
let value = formatValue({ data: values, format });
let nextValue = formatValue({ data: values, format });
Array.isArray(value) && (value = value.map((item) => omit(item)));
Array.isArray(nextValue) && (nextValue = nextValue.map((item) => omit(item)));
setData(values);
onChange?.(value);
onChange?.(nextValue);
};
// render
@@ -166,3 +165,57 @@ const OneInputKV = (props: any) => {
};
export const InputKV = memo(OneInputKV, (prev, next) => prev.value === next.value);
interface IFormatValueConfig {
format: 'simple' | 'original';
data?: Record<string, string> | Array<Record<string, string>>;
}
const formatData = (data?: Array<Record<string, string>>) => {
const result: Record<string, string> = {};
// 遍历数据
data?.forEach((item) => {
item.key && !(item.key in result) && (result[item.key] = item.value);
});
return result;
};
// 格式化value
const formatValue = ({ format, data }: IFormatValueConfig) => {
if (format === 'original') {
if (!data) return [{ index: 0 }];
const list = Array.isArray(data)
? data
: Object.keys(data).map((key) => ({ key, value: data[key] }));
return list.map((item, idx) => ({ ...item, index: idx - list.length - 1 }));
} else {
if (!data) return {};
return Array.isArray(data) ? formatData(data) : data;
}
};
const compareValue = (origin: Array<any>, data: Array<any>) => {
const resolve = (list: Array<any>) => {
let keyStr = '';
let valueStr = '';
list.forEach((item) => {
const { key, value } = item;
keyStr += key || '$';
valueStr += value || '$';
});
return { keyStr, valueStr };
};
const oldRes = resolve(origin);
const newRes = resolve(data);
return oldRes.keyStr === newRes.keyStr && oldRes.valueStr === newRes.valueStr;
};
@@ -33,7 +33,7 @@ import {
} from '@music163/tango-ui';
import { isValidExpressionCode } from '@music163/tango-core';
export type VariableTreeNodeType = {
export interface IVariableTreeNode {
/**
*
*/
@@ -65,9 +65,9 @@ export type VariableTreeNodeType = {
/**
*
*/
children?: VariableTreeNodeType[];
children?: IVariableTreeNode[];
[key: string]: any;
};
}
type VariableModelType = 'preview' | 'define' | 'add';
@@ -91,15 +91,15 @@ export interface EditableVariableTreeProps {
/**
*
*/
getPreviewValue?: (node: VariableTreeNodeType) => unknown;
getPreviewValue?: (node: IVariableTreeNode) => unknown;
/**
*
*/
onSelect?: (data: VariableTreeNodeType) => void;
onSelect?: (data: IVariableTreeNode) => void;
/**
* 使
*/
onUse?: (data: VariableTreeNodeType) => void;
onUse?: (data: IVariableTreeNode) => void;
/**
*
*/
@@ -152,7 +152,7 @@ export function EditableVariableTree({
showPreviewAlert = true,
}: EditableVariableTreeProps) {
const [keyword, setKeyword] = useState('');
const [node, setNode] = useState<VariableTreeNodeType>();
const [node, setNode] = useState<IVariableTreeNode>();
const [mode, setMode] = useState<EditableVariableTreeProps['defaultMode']>(defaultMode);
const hasPanelSwitch = modes.includes('define') && modes.includes('preview');
@@ -184,17 +184,17 @@ export function EditableVariableTree({
</Box>
<VariableTree
dataSource={treeData}
onSelect={(node) => {
setNode(node);
onSelect={(item) => {
setNode(item);
mode === 'add' && setMode(defaultMode);
onSelect(node);
onSelect(item);
}}
onAdd={(node) => {
setNode(node);
onAdd={(item) => {
setNode(item);
setMode('add');
}}
onRemove={(node) => {
const [type, storeName] = node.key.split('.');
onRemove={(item) => {
const [type, storeName] = item.key.split('.');
setNode(null);
onDeleteStore(storeName);
}}
@@ -257,8 +257,8 @@ export function EditableVariableTree({
<NodeDefineForm
node={node}
onSave={onSave}
onDelete={(node) => {
const [type, storeName, stateName] = node.key.split('.');
onDelete={(item) => {
const [type, storeName, stateName] = item.key.split('.');
onDeleteVariable(storeName, stateName);
setNode(null);
}}
@@ -301,7 +301,7 @@ export function EditableVariableTreeModal({
onSelect = noop,
...rest
}: EditableVariableTreeModalProps) {
const [node, setNode] = useState<VariableTreeNodeType>();
const [node, setNode] = useState<IVariableTreeNode>();
const [visible, { on, off }] = useBoolean(false);
return (
<Box>
@@ -348,10 +348,10 @@ const varTreeStyle = css`
`;
interface VariableTreeProps {
dataSource?: VariableTreeNodeType[];
onSelect?: (data: VariableTreeNodeType) => void;
onAdd?: (data: VariableTreeNodeType) => void;
onRemove?: (data: VariableTreeNodeType) => void;
dataSource?: IVariableTreeNode[];
onSelect?: (data: IVariableTreeNode) => void;
onAdd?: (data: IVariableTreeNode) => void;
onRemove?: (data: IVariableTreeNode) => void;
}
function VariableTree({
@@ -450,9 +450,9 @@ export function ValuePreview({ value, onSelect }: NodePreviewProps) {
}
interface NodeDefineProps {
node: VariableTreeNodeType;
onSave?: (code: string, node: VariableTreeNodeType) => void;
onDelete?: (node: VariableTreeNodeType) => void;
node: IVariableTreeNode;
onSave?: (code: string, node: IVariableTreeNode) => void;
onDelete?: (node: IVariableTreeNode) => void;
}
/**
@@ -470,7 +470,7 @@ function NodeDefineForm({ node, onSave = noop, onDelete = noop }: NodeDefineProp
<InputCode
shape="inset"
value={node.raw}
onChange={(value) => setValue(value)}
onChange={(nextValue) => setValue(nextValue)}
editable={editable}
onBlur={() => {
if (!isValidExpressionCode(value)) {
@@ -527,7 +527,7 @@ function NodeDefineForm({ node, onSave = noop, onDelete = noop }: NodeDefineProp
}
interface AddNodeFormProps {
parentNode: VariableTreeNodeType;
parentNode: IVariableTreeNode;
onCancel?: React.MouseEventHandler<HTMLButtonElement>;
onSubmit?: (storeName: string, data: { name: string; initialValue: string }) => void;
}
+3 -6
View File
@@ -1,10 +1,7 @@
import { createContext, TangoRemoteServicesType } from '@music163/tango-helpers';
import { createContext } from '@music163/tango-helpers';
import type { DndQuery } from './dnd';
/**
* TODO: 是不是直接合并到 Context 中
*/
export interface IDesignerContext {
export interface IDesignerContext<S = any> {
/**
* 沙箱查询实例
*/
@@ -12,7 +9,7 @@ export interface IDesignerContext {
/**
* 远程服务
*/
remoteServices?: TangoRemoteServicesType;
remoteServices?: Record<string, S>;
}
const [DesignerProvider, useDesigner] = createContext<IDesignerContext>({
+5 -5
View File
@@ -1,11 +1,11 @@
import React, { useEffect } from 'react';
import { SystemProvider } from 'coral-system';
import React, { useEffect, useMemo } from 'react';
import { SystemProvider, extendTheme } from 'coral-system';
import { ConfigProvider } from 'antd';
import zhCN from 'antd/lib/locale/zh_CN';
import { TangoEngineProvider, ITangoEngineContext } from '@music163/tango-context';
import { createFromIconfontCN } from '@ant-design/icons';
import { DesignerProvider, IDesignerContext } from './context';
import { theme as baseTheme } from './theme';
import { createFromIconfontCN } from '@ant-design/icons';
import zhCN from 'antd/lib/locale/zh_CN';
export interface DesignerProps extends IDesignerContext, ITangoEngineContext {
/**
@@ -33,7 +33,7 @@ export function Designer(props: DesignerProps) {
iconfontScriptUrl = '//at.alicdn.com/t/c/font_2891794_lzc7rtwuzf.js',
children,
} = props;
const theme = themeProp ?? baseTheme;
const theme = useMemo(() => extendTheme(themeProp, baseTheme), [themeProp]);
useEffect(() => {
createFromIconfontCN({
scriptUrl: iconfontScriptUrl,
+6 -5
View File
@@ -3,14 +3,15 @@ export * from './designer';
export * from './designer-panel';
export * from './workspace-panel';
export * from './setting-panel';
export * from './view-panel';
export * from './workspace-view';
export * from './simulator';
export * from './dnd';
export * from './editor';
export * from './sandbox';
export * from './sidebar';
export * from './toolbar';
export * from './dnd';
export * from './selection-menu';
export { INTERNAL_SELECTION_TOOLS } from './simulator/selection';
export { useWorkspace, useDesigner, observer } from '@music163/tango-context';
export { register as registerSetter } from '@music163/tango-setting-form';
export type { FormItemComponentProps, IFormItemCreateOptions } from '@music163/tango-setting-form';
+35 -30
View File
@@ -1,7 +1,7 @@
import React, { useImperativeHandle, ForwardedRef, useRef, useState, useEffect } from 'react';
import { Box } from 'coral-system';
import { CodeSandbox, CodeSandboxProps } from '@music163/tango-sandbox';
import { getValue, logger, pick, setValue } from '@music163/tango-helpers';
import { getValue, isFunction, logger, pick, setValue } from '@music163/tango-helpers';
import { observer, useWorkspace, useDesigner } from '@music163/tango-context';
import { Simulator, Viewport } from '../simulator';
import { useSandboxQuery } from '../context';
@@ -9,22 +9,27 @@ import { DndQuery, useDnd } from '../dnd';
import { Navigator } from './navigator';
import { SelectionToolsProps } from '../simulator/selection';
type SandboxEventHandlerConfig = {
interface ISandboxEventHandlerConfig {
sandboxQuery?: DndQuery;
sandboxType?: 'design' | 'preview';
isActive: boolean;
[x: string]: any;
};
}
export type SandboxProps = Omit<CodeSandboxProps, 'files' | 'eventHandlers' | 'onMessage'> & {
isPreview?: boolean;
selectionTools?: SelectionToolsProps['actions'];
builtinSelectionMenuMap?: SelectionToolsProps['builtinActionMap'];
/**
* tangoConfigJson 处理器
*/
configFormatter?: IMergeTangoConfigJsonConfig['formatter'];
sandboxType?: 'design' | 'preview';
mode?: 'single' | 'combined';
injectScript?: string;
onViewChange?: (data: any, config?: SandboxEventHandlerConfig) => void;
onMessage?: (data: any, config?: SandboxEventHandlerConfig) => void;
onLoad?: (config?: SandboxEventHandlerConfig) => void;
onViewChange?: (data: any, config?: ISandboxEventHandlerConfig) => void;
onMessage?: (data: any, config?: ISandboxEventHandlerConfig) => void;
onLoad?: (config?: ISandboxEventHandlerConfig) => void;
};
export type CombinedSandboxRef = {
@@ -36,6 +41,7 @@ const LANDING_PAGE_PATH = '/__background_landing_page__';
function useSandbox({
isPreview: isPreviewProp,
configFormatter,
onViewChange,
onMessage: onMessageProp,
onLoad: onLoadProp,
@@ -69,12 +75,12 @@ function useSandbox({
let files = Array.from(workspace.files.keys()).reduce((prev, filename) => {
let code = workspace.getFile(filename).code;
if (filename === '/tango.config.json') {
code = mergeTangoConfigJson(code, isPreview, { injectScript });
code = mergeTangoConfigJson(code, { isPreview, injectScript, formatter: configFormatter });
}
prev[filename] = { code };
return prev;
}, {});
files = normalizeFiles(files, workspace.entry);
files = fixSandboxFiles(files, workspace.entry);
const onMessage = (data: any) => onMessageProp && onMessageProp(data, getSandboxConfig());
const onLoad = () => onLoadProp && onLoadProp(getSandboxConfig());
@@ -187,7 +193,7 @@ export const CombinedSandbox = observer(
const activeSandbox = useRef<string>();
const [startRoute, setStartRoute] = useState(workspace.activeRoute);
const onViewChange = (data: any, config: SandboxEventHandlerConfig) => {
const onViewChange = (data: any, config: ISandboxEventHandlerConfig) => {
if (config.isActive) {
const curPath = data?.pathname + data?.search;
const isSandboxChanged = config.sandboxType !== activeSandbox.current;
@@ -275,6 +281,7 @@ export const Sandbox = observer(
({
isPreview: isPreviewProp,
selectionTools,
builtinSelectionMenuMap,
bundlerURL,
mode = 'combined',
...props
@@ -294,7 +301,7 @@ export const Sandbox = observer(
: combinedSandboxRef.current?.designSandbox;
}
const onViewChange = (data: any, { isActive }: SandboxEventHandlerConfig) => {
const onViewChange = (data: any, { isActive }: ISandboxEventHandlerConfig) => {
if (isActive) {
navigatorRef.current.changeRelativeUrl(data?.pathname + data?.search);
}
@@ -345,7 +352,7 @@ export const Sandbox = observer(
}}
/>
<Simulator>
<Viewport selectionTools={selectionTools}>
<Viewport selectionTools={selectionTools} builtinActionMap={builtinSelectionMenuMap}>
{mode === 'single' && (
<DesignSandbox
ref={sandboxRef}
@@ -378,7 +385,7 @@ export const Sandbox = observer(
);
// 兼容 tango.config.json,转成 sandbox.config.json
function normalizeFiles(files: object, entry = '/src/index.js') {
function fixSandboxFiles(files: Record<string, { code: string }>, entry = '/src/index.js') {
if (files['/tango.config.json']) {
const tangConfigJsonStr = files['/tango.config.json'].code;
const tangConfigJson = JSON.parse(tangConfigJsonStr);
@@ -386,6 +393,7 @@ function normalizeFiles(files: object, entry = '/src/index.js') {
code: JSON.stringify(tangConfigJson.sandbox, null, 2),
};
}
if (!files['/index.html']) {
files['/index.html'] = {
code: `
@@ -405,28 +413,30 @@ function normalizeFiles(files: object, entry = '/src/index.js') {
`,
};
}
return files;
}
function mergeTangoConfigJson(code: string, isPreview: boolean, config?: { [x: string]: any }) {
interface IMergeTangoConfigJsonConfig {
isPreview?: boolean;
injectScript?: string;
formatter?: (json: object) => object;
}
function mergeTangoConfigJson(
code: string,
{ isPreview, injectScript, formatter }: IMergeTangoConfigJsonConfig = {},
) {
let json;
try {
json = JSON.parse(code);
} catch (err) {
logger.error(err);
logger.error('Json parse failed!', err);
return code;
}
const ox = getValue(json, 'dataSource.ox');
const userJs = getValue(json, 'sandbox.evaluateJavaScript') || '';
let mergedUserJs = userJs;
const { injectScript } = config || {};
if (ox) {
// TIP: 自动拼装 __tango_ox__ 注入到沙箱中
mergedUserJs = `window.__tango_ox__=${JSON.stringify(ox)};${mergedUserJs}`;
}
if (injectScript) {
mergedUserJs = `${mergedUserJs};${injectScript}`;
}
@@ -435,15 +445,6 @@ function mergeTangoConfigJson(code: string, isPreview: boolean, config?: { [x: s
setValue(json, 'sandbox.evaluateJavaScript', mergedUserJs);
}
const i18n = getValue(json, 'i18n');
if (i18n) {
// TIP: 合并 i18n 配置到沙箱配置中
setValue(json, 'sandbox.i18n', {
id: i18n.appId,
preModule: i18n.preModule,
});
}
// 合并 packages 内的信息至 sandbox
const packages = getValue(json, 'packages');
const externals = getValue(json, 'sandbox.externals') || {};
@@ -490,6 +491,10 @@ function mergeTangoConfigJson(code: string, isPreview: boolean, config?: { [x: s
setValue(json, 'sandbox.externals', externals);
setValue(json, 'sandbox.externalResources', [...new Set(externalResources)]);
if (isFunction(formatter)) {
json = formatter?.(json);
}
return JSON.stringify(json);
}
@@ -1,6 +1,6 @@
import React from 'react';
import { Select } from 'antd';
import { FormItemComponentProps } from '../form-item';
import { FormItemComponentProps } from '@music163/tango-setting-form';
import { ListSetter, NewOptionFormFieldType } from './list-setter';
const defaultNewOptionValue = {
@@ -28,7 +28,11 @@ const optionFormFields: NewOptionFormFieldType[] = [
{ label: 'key', name: 'key', required: true },
{ label: '文本', name: 'label', required: true },
{ label: '外观', name: 'shape', component: <Select placeholder="请选择" options={shapes} /> },
{ label: '按钮类型', name: 'buttonType', component: <Select placeholder="请选择" options={buttonTypes} /> },
{
label: '按钮类型',
name: 'buttonType',
component: <Select placeholder="请选择" options={buttonTypes} />,
},
{ label: '类型', name: 'actionType', component: <Select placeholder="请选择" options={types} /> },
{ label: '确认气泡内容', name: 'confirmContent' },
];
@@ -2,7 +2,7 @@ import React from 'react';
import { Switch } from 'antd';
import { Box } from 'coral-system';
import { FieldStringOutlined } from '@ant-design/icons';
import { FormItemComponentProps } from '../form-item';
import { FormItemComponentProps } from '@music163/tango-setting-form';
import { ListSetter, NewOptionFormFieldType } from './list-setter';
const defaultNewOptionValue = {
@@ -37,7 +37,7 @@ const renderSetterItem = (item: any) => {
export function ColumnSetter(props: FormItemComponentProps<any[]>) {
return (
<ListSetter
getListItemKey={(item) => item.key || item.dataIndex}
getListItemKey={(item: any) => item.key || item.dataIndex}
addBtnText="添加新列"
listItemFormFields={optionFormFields}
newItemDefaultValues={defaultNewOptionValue}
@@ -3,8 +3,7 @@ import { Box } from 'coral-system';
// @ts-ignore
import { toJSON } from 'cssjson';
import { InputNumber, Space } from 'antd';
import { SliderSetter } from './number-setter';
import { FormItemComponentProps } from '../form-item';
import { FormItemComponentProps, SliderSetter } from '@music163/tango-setting-form';
import {
BgSetter,
BorderSetter,
@@ -1,7 +1,7 @@
import React from 'react';
import moment from 'moment';
import { DatePicker, TimePicker } from 'antd';
import { FormItemComponentProps } from '../form-item';
import { FormItemComponentProps } from '@music163/tango-setting-form';
function toMoment(value: any, format: string) {
let ret: moment.Moment;
@@ -1,5 +1,5 @@
import React from 'react';
import { FormItemComponentProps } from '../form-item';
import { FormItemComponentProps } from '@music163/tango-setting-form';
import { InputKV } from '../components';
export function EnumSetter(props: FormItemComponentProps<string>) {
@@ -1,11 +1,10 @@
import React, { useCallback, useState } from 'react';
import { css } from 'styled-components';
import { Box } from 'coral-system';
import { css, Box } from 'coral-system';
import { AutoComplete } from 'antd';
import { ActionSelect } from '@music163/tango-ui';
import { FormItemComponentProps } from '@music163/tango-setting-form';
import { useWorkspace, useWorkspaceData } from '@music163/tango-context';
import { ExpressionModal, getInputValue } from './expression-setter';
import { FormItemComponentProps } from '../form-item';
import { useFormVariable } from '../context';
enum EventAction {
NoAction = 'noAction',
@@ -42,7 +41,9 @@ export function EventSetter(props: EventSetterProps) {
const [temp, setTemp] = useState(''); // 二级暂存值
const [expModalVisible, setExpModalVisible] = useState(false); // 弹窗是否显示
const { actionVariables, routeOptions, modalOptions } = useFormVariable();
const { actionVariables, routeOptions } = useWorkspaceData();
const workspace = useWorkspace();
const modalOptions = workspace.listModals() || [];
const handleChange = useCallback<FormItemComponentProps['onChange']>(
(nextValue: any, ...args) => {
@@ -80,9 +81,9 @@ export function EventSetter(props: EventSetterProps) {
defaultValue={inputValue}
visible={expModalVisible}
onCancel={() => setExpModalVisible(false)}
onOk={(value) => {
onOk={(nextValue) => {
setExpModalVisible(false);
handleChange(value);
handleChange(nextValue);
}}
dataSource={actionVariables}
/>
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import { Box, css } from 'coral-system';
import { Alert, Modal, Tabs } from 'antd';
import { value2code, isValidExpressionCode } from '@music163/tango-core';
@@ -11,9 +11,10 @@ import {
} from '@music163/tango-helpers';
import { CloseCircleFilled, ExpandAltOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import { IconButton, Panel, InputCode, ChatInput } from '@music163/tango-ui';
import { FormItemComponentProps } from '../form-item';
import { EditableVariableTree, VariableTreeNodeType } from '../components';
import { useFormVariable } from '../context';
import { FormItemComponentProps } from '@music163/tango-setting-form';
import { useWorkspace, useWorkspaceData } from '@music163/tango-context';
import { EditableVariableTree, IVariableTreeNode } from '../components';
import { useRemoteServices, useSandboxQuery } from '../context';
export const expressionValueValidate = (value: string) => {
if (isVariableString(value)) {
@@ -91,7 +92,8 @@ export function ExpressionSetter(props: ExpressionSetterProps) {
const [inputValue, setInputValue] = useState(() => {
return getInputValue(valueProp);
});
const { evaluateContext } = useFormVariable();
const sandbox = useSandboxQuery();
const evaluateContext = sandbox.window;
// when receive new value, sync state
useEffect(() => {
@@ -122,7 +124,11 @@ export function ExpressionSetter(props: ExpressionSetterProps) {
}}
/>
)}
<IconButton tooltip="打开表达式变量选择面板" icon={<ExpandAltOutlined />} onClick={on} />
<IconButton
tooltip="打开表达式变量选择面板"
icon={<ExpandAltOutlined />}
onClick={on}
/>
</Box>
}
value={inputValue}
@@ -160,7 +166,7 @@ export interface ExpressionModalProps {
defaultValue?: string;
onCancel?: () => void;
onOk?: (value: string) => void;
dataSource?: VariableTreeNodeType[];
dataSource?: IVariableTreeNode[];
autoCompleteOptions?: string[];
}
@@ -176,8 +182,19 @@ export function ExpressionModal({
autoCompleteOptions,
}: ExpressionModalProps) {
const [exp, setExp] = useState(defaultValue);
const { expressionVariables, evaluateContext, onAction, remoteServices } = useFormVariable();
const workspace = useWorkspace();
const onAction = useCallback(
(action: string, args: unknown[]) => {
workspace[action]?.(...args);
},
[workspace],
);
const sandbox = useSandboxQuery();
const remoteServices = useRemoteServices();
const { expressionVariables } = useWorkspaceData();
const evaluateContext = sandbox.window;
const gptService = remoteServices?.GptService;
const handleExpInputChange = (val: string) => {
setExp(val?.trim());
};
@@ -231,13 +248,13 @@ export function ExpressionModal({
// height={400}
dataSource={dataSource || expressionVariables}
onUse={(node) => {
let exp;
let str;
if (/^(stores|services)\./.test(node.key)) {
exp = `{tango.${node.key.replaceAll('.', '?.')}}`;
str = `{tango.${node.key.replaceAll('.', '?.')}}`;
} else {
exp = `{${node.key}}`;
str = `{${node.key}}`;
}
setExp(exp);
setExp(str);
}}
onAddVariable={(storeName, data) => {
onAction('addStoreState', [storeName, data.name, data.initialValue]);
@@ -278,7 +295,7 @@ export function ExpressionModal({
frequencyPenalty: 0,
mode: 'chat',
})
.then((res) => {
.then((res: any) => {
return res?.detail?.choices?.[0]?.text;
})
}
+141
View File
@@ -0,0 +1,141 @@
import { register, IFormItemCreateOptions } from '@music163/tango-setting-form';
import { ActionListSetter } from './action-list-setter';
import { ColumnSetter } from './column-setter';
import { CssSetter } from './css-setter';
import { DateRangeSetter, DateSetter, TimeRangeSetter } from './date-setter';
import { EnumSetter } from './enum-setter';
import { EventSetter } from './event-setter';
import { ExpressionSetter } from './expression-setter';
import { JSONSetter } from './json-setter';
import { JsxSetter } from './jsx-setter';
import { ListSetter } from './list-setter';
import { ModelSetter } from './model-setter';
import { OptionSetter } from './option-setter';
import { PickerSetter } from './picker-setter';
import { RenderSetter, TableCellSetter, TableExpandableSetter } from './render-props-setter';
import { RouterSetter } from './router-setter';
import {
SpacingSetter,
ColorSetter,
BgSetter,
BorderSetter,
FlexGapSetter,
DisplaySetter,
FlexJustifyContentSetter,
FlexAlignItemsSetter,
FlexDirectionSetter,
} from './style-setter';
const setters: IFormItemCreateOptions[] = [
{
name: 'actionListSetter',
component: ActionListSetter,
},
{
name: 'columnSetter',
component: ColumnSetter,
},
{
name: 'cssSetter',
component: CssSetter,
},
{
name: 'dateSetter',
component: DateSetter,
},
{
name: 'dateRangeSetter',
component: DateRangeSetter,
},
{
name: 'timeSetter',
component: DateRangeSetter,
},
{
name: 'timeRangeSetter',
component: TimeRangeSetter,
},
{
name: 'enumSetter',
component: EnumSetter,
},
{
name: 'eventSetter',
alias: ['actionSetter', 'functionSetter', 'callbackSetter'],
component: EventSetter,
},
{
name: 'expressionSetter',
alias: ['expSetter'],
component: ExpressionSetter,
},
{
name: 'jsonSetter',
component: JSONSetter,
},
{
name: 'jsxSetter',
component: JsxSetter,
},
{
name: 'listSetter',
component: ListSetter as any,
},
{
name: 'modelSetter',
component: ModelSetter,
},
{
name: 'optionSetter',
component: OptionSetter,
},
{
name: 'pickerSetter',
component: PickerSetter,
},
{
name: 'renderPropsSetter',
component: RenderSetter,
},
{
name: 'tableCellSetter',
component: TableCellSetter,
},
{
name: 'tableExpandableSetter',
component: TableExpandableSetter,
},
{
name: 'routerSetter',
component: RouterSetter,
},
{
name: 'displaySetter',
component: DisplaySetter,
},
{ name: 'flexDirectionSetter', component: FlexDirectionSetter },
{
name: 'flexGapSetter',
component: FlexGapSetter,
},
{
name: 'flexJustifyContentSetter',
component: FlexJustifyContentSetter,
},
{
name: 'flexAlignItemsSetter',
component: FlexAlignItemsSetter,
},
{
name: 'spacingSetter',
component: SpacingSetter,
},
{
name: 'colorSetter',
component: ColorSetter,
},
{ name: 'bgSetter', component: BgSetter },
{ name: 'borderSetter', component: BorderSetter },
];
setters.forEach(register);
@@ -1,11 +1,10 @@
import React from 'react';
import { SingleMonacoEditor } from '@music163/tango-ui';
import { Box } from 'coral-system';
import { FormItemComponentProps } from '../form-item';
import { SingleMonacoEditor } from '@music163/tango-ui';
import { FormItemComponentProps } from '@music163/tango-setting-form';
/**
* 使 ExpressionSetter
* @deprecated
* JSON Setter
*/
export function JSONSetter({ value, onChange }: FormItemComponentProps) {
return (
@@ -1,7 +1,7 @@
import React from 'react';
import { isPlainString } from '@music163/tango-helpers';
import { ActionSelect } from '@music163/tango-ui';
import { FormItemComponentProps } from '../form-item';
import { FormItemComponentProps } from '@music163/tango-setting-form';
const options = [
{ label: '取消自定义', value: '' },
@@ -1,10 +1,9 @@
import React, { useState } from 'react';
import { css, Box } from 'coral-system';
import { Form, Input, Button, Popover } from 'antd';
import { DeleteOutlined, EditOutlined, HolderOutlined } from '@ant-design/icons';
import { Box } from 'coral-system';
import { FormItemComponentProps } from '../form-item';
import { css } from 'styled-components';
import { DragBox } from '../components/index';
import { FormItemComponentProps } from '@music163/tango-setting-form';
import { DragBox } from '../components';
function copyWithoutUndefined(source: Record<string, any>) {
const ret = {};
@@ -254,7 +253,7 @@ function NewOptionForm({ fields = [], initialValues = {}, onSubmit }: NewOptionF
);
}
type ListSetterProps = FormItemComponentProps<any[]> & {
interface ListSetterProps extends FormItemComponentProps<any[]> {
listItemFormFields: ListSetterItemProps['formFields'];
newItemDefaultValues?: AddListItemButtonProps['formInitialValues'];
getNewItemDefaultValues?: AddListItemButtonProps['getFormInitialValues'];
@@ -2,10 +2,11 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Box } from 'coral-system';
import { Input, Tooltip } from 'antd';
import { getValue, isFunction } from '@music163/tango-helpers';
import { useFormVariable } from '../context';
import { FormItemComponentProps } from '../form-item';
import { EditableVariableTreeModal } from '../components';
import { FormItemComponentProps } from '@music163/tango-setting-form';
import { MenuOutlined } from '@ant-design/icons';
import { useWorkspace, useWorkspaceData } from '@music163/tango-context';
import { EditableVariableTreeModal } from '../components';
import { useSandboxQuery } from '../context';
function object2treeData(
val: any,
@@ -53,7 +54,15 @@ function traverseTreeData(val: any, callback: (val: any) => void) {
export function ModelSetter({ value, onChange }: FormItemComponentProps) {
const [inputValue, setInputValue] = useState(value);
const { onAction, modelVariables, evaluateContext = {} } = useFormVariable();
const { modelVariables } = useWorkspaceData();
const evaluateContext = useSandboxQuery().window || {};
const workspace = useWorkspace();
const onAction = useCallback(
(action: string, args: unknown[]) => {
workspace[action]?.(...args);
},
[workspace],
);
const definedVariables = useMemo(() => {
const map = new Map();
traverseTreeData(modelVariables, (node) => {
@@ -1,6 +1,6 @@
import { uuid } from '@music163/tango-helpers';
import React from 'react';
import { FormItemComponentProps } from '../form-item';
import { FormItemComponentProps } from '@music163/tango-setting-form';
import { ListSetter, NewOptionFormFieldType } from './list-setter';
const optionFormFields: NewOptionFormFieldType[] = [
@@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import { Select } from 'antd';
import { FormItemComponentProps } from '../form-item';
import { FormItemComponentProps } from '@music163/tango-setting-form';
import { ListStore, OptionType } from '@music163/tango-helpers';
const style = {
@@ -1,18 +1,18 @@
import React, { useCallback, useMemo } from 'react';
import { ActionSelect } from '@music163/tango-ui';
import { FormItemComponentProps } from '../form-item';
import { FormItemComponentProps } from '@music163/tango-setting-form';
type RenderOptionType = {
interface IRenderOption {
label: string;
value: string;
render?: string;
relatedImports?: string[];
};
}
export interface RenderSetterProps {
text?: string;
options?: RenderOptionType[];
fallbackOption?: RenderOptionType;
options?: IRenderOption[];
fallbackOption?: IRenderOption;
}
/**
@@ -97,16 +97,6 @@ const tableExpandableOptions: RenderSetterProps['options'] = [
{ label: '取消可展开行', value: '' },
];
const xtoolbarOptions: RenderSetterProps['options'] = [
{
label: '配置左侧区域',
value: 'Box',
render: getRender('<Box display="flex"></Box>'),
relatedImports: ['Box'],
},
{ label: '取消左侧区域', value: undefined },
];
export function TableCellSetter(props: FormItemComponentProps) {
return <RenderSetter options={tableCellOptions} {...props} />;
}
@@ -114,7 +104,3 @@ export function TableCellSetter(props: FormItemComponentProps) {
export function TableExpandableSetter(props: FormItemComponentProps) {
return <RenderSetter options={tableExpandableOptions} text="配置表格可展开行" {...props} />;
}
export function XToolbarSetter(props: FormItemComponentProps) {
return <RenderSetter options={xtoolbarOptions} text="配置工具栏左侧区域" {...props} />;
}
@@ -2,8 +2,8 @@ import React, { useState } from 'react';
import { Box } from 'coral-system';
import { Input, Radio } from 'antd';
import { isValidUrl } from '@music163/tango-helpers';
import { useFormVariable } from '../context';
import { FormItemComponentProps } from '../form-item';
import { FormItemComponentProps } from '@music163/tango-setting-form';
import { useWorkspaceData } from '@music163/tango-context';
import { PickerSetter } from './picker-setter';
export function RouterSetter(props: FormItemComponentProps) {
@@ -11,7 +11,7 @@ export function RouterSetter(props: FormItemComponentProps) {
return isValidUrl(props.value) ? 'input' : 'select';
});
const [input, setInput] = useState<string>(props.value);
const { routeOptions } = useFormVariable();
const { routeOptions } = useWorkspaceData();
return (
<Box>
<Box mb="m">
@@ -5,9 +5,8 @@ import { SketchPicker } from 'react-color';
import Color from 'color';
import { BgColorsOutlined, FileImageOutlined } from '@ant-design/icons';
import { SingleMonacoEditor, IconFont } from '@music163/tango-ui';
import { FormItemComponentProps } from '../form-item';
import { ImageSetter } from './image-setter';
import { ChoiceSetter } from './choice-setter';
import { FormItemComponentProps, ChoiceSetter, TextSetter } from '@music163/tango-setting-form';
// import { ImageSetter } from './image-setter';
function getRawCssValue(value: string) {
if (value && value.startsWith('{css`')) {
@@ -175,8 +174,8 @@ export function ColorSetter(props: FormItemComponentProps<string>) {
const colorPanel = (
<SketchPicker
color={color}
onChangeComplete={(color: any) => {
const nextValue = color.rgb.a === 1 ? color.hex : rgbaObjectToString(color.rgb);
onChangeComplete={(newColor: any) => {
const nextValue = newColor.rgb.a === 1 ? newColor.hex : rgbaObjectToString(newColor.rgb);
if (typeof onChange === 'function') {
onChange(nextValue);
}
@@ -238,7 +237,7 @@ export function BgSetter({ value, onChange }: FormItemComponentProps<string>) {
<Box mt="m">
{mode === 'color' && <ColorSetter value={value} onChange={onChange} />}
{mode === 'image' && (
<ImageSetter
<TextSetter
value={getImageUrl(value)}
onChange={(imgUrl) => {
onChange(imgUrl ? `url(${imgUrl})` : undefined);
@@ -353,19 +352,19 @@ export function BorderSetter({ value, onChange }: FormItemComponentProps<string>
const [width, setWidth] = useState<number>(Number(getPxValue(valueParts[0])) || 0);
const [color, setColor] = useState<string>(valueParts[2] || '#000');
const handleChange = (key: string, value: any) => {
const handleChange = (key: string, val: any) => {
let ret: any[];
switch (key) {
case 'style': {
ret = [width, value, color];
ret = [width, val, color];
break;
}
case 'width': {
ret = [`${value}px`, style, color];
ret = [`${val}px`, style, color];
break;
}
case 'color': {
ret = [width, style, value];
ret = [width, style, val];
break;
}
default:
+7 -20
View File
@@ -1,10 +1,10 @@
import React, { useCallback } from 'react';
import React from 'react';
import { Box } from 'coral-system';
import { SettingForm, FormModel, SettingFormProps } from '@music163/tango-setting-form';
import { Panel } from '@music163/tango-ui';
import { clone } from '@music163/tango-helpers';
import { observer, useDesigner, useWorkspace, useWorkspaceData } from '@music163/tango-context';
import { useSandboxQuery } from './context';
import { observer, useDesigner, useWorkspace } from '@music163/tango-context';
import './setters';
export interface SettingPanelProps extends SettingFormProps {
title?: React.ReactNode;
@@ -15,17 +15,8 @@ const headerProps = {
};
export const SettingPanel = observer(({ title = '设置面板', ...props }: SettingPanelProps) => {
const sandbox = useSandboxQuery();
const workspace = useWorkspace();
const designer = useDesigner();
const { modelVariables, actionVariables, expressionVariables, routeOptions } = useWorkspaceData();
const onAction = useCallback(
(action: string, args: unknown[]) => {
workspace[action]?.(...args);
},
[workspace],
);
if (!designer.showRightPanel) {
return <Box className="SettingPanel" />;
@@ -74,7 +65,10 @@ export const SettingPanel = observer(({ title = '设置面板', ...props }: Sett
});
} else if (!field.error) {
// 针对 Form.Item 变化的情况
workspace.updateSelectedNodeAttributes({ [firstName]: realValue }, field.detail?.relatedImports);
workspace.updateSelectedNodeAttributes(
{ [firstName]: realValue },
field.detail?.relatedImports,
);
}
},
});
@@ -99,13 +93,6 @@ export const SettingPanel = observer(({ title = '设置面板', ...props }: Sett
key={workspace.selectSource.first.id}
model={formModel}
prototype={prototype}
modelVariables={modelVariables}
actionVariables={actionVariables}
expressionVariables={expressionVariables}
modalOptions={workspace.listModals() || []}
routeOptions={routeOptions}
onAction={onAction}
evaluateContext={sandbox.window}
{...props}
/>
) : (
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useMemo } from 'react';
import { observer, useWorkspace } from '@music163/tango-context';
import { Box, Link, css } from 'coral-system';
import { Box, css } from 'coral-system';
import { Button, FormProps, Empty, Space, Dropdown } from 'antd';
import { PlayCircleOutlined } from '@ant-design/icons';
import { Form, InputCode, Menu, Panel, JsonView, Search } from '@music163/tango-ui';
@@ -12,14 +12,38 @@ import {
logger,
code2object,
} from '@music163/tango-helpers';
import {
ESFDataSourceType,
ESFOperationType,
ESFHTTPMethodType,
ESFHTTRequestType,
} from '@music163/tango-core';
import { useSandboxQuery } from '../../context';
/*
* 服务函数的操作类型
*/
enum ServiceFunctionOperationModeType {
ADD = 'add',
UPDATE = 'update',
DELETE = 'delete',
}
/**
* 服务函数 HTTP Type
* 云音乐网关只支持 get 和 post
*/
enum ServiceFunctionMethodType {
GET = 'GET',
// PUT = 'PUT',
POST = 'POST',
// PATCH = 'PATCH',
// DELETE = 'DELETE',
}
/**
* requestType
* headers: Content-Type
*/
enum ESFHTTRequestType {
'application/json' = 'json',
'application/x-www-form-urlencoded' = 'x-www-form-urlencoded',
}
/**
* 将 api 路径转换为默认的驼峰方法名
*/
@@ -47,28 +71,8 @@ type DataServiceViewProps = {
onDelete?: (values: Record<string, string>) => void;
};
// 判断是不是一个完整的 url 请求
const VALID_REQUEST_URL_REG = /((http|https):)?\/\/([\w.]+\/?)\S*/;
/**
* 根据 url 类型推断方法类型
*/
export function inferServiceType(url?: string) {
// 删除的时候,也会走一下这个逻辑,兼容
if (!url) return ESFDataSourceType.Custom;
// 如果是完整的,是 custom
if (VALID_REQUEST_URL_REG.test(url)) return ESFDataSourceType.Custom;
// 如果是 / 开头的,认为是 ox 的
if (url.startsWith('/')) return ESFDataSourceType.OvermindX;
/**
* 这里预留出数据平台 fastX 的
*/
// 默认是 ox 的
return ESFDataSourceType.OvermindX;
}
// http 方法类型
const httpMethods = Object.keys(ESFHTTPMethodType).map((key) => ({
const httpMethods = Object.keys(ServiceFunctionMethodType).map((key) => ({
label: key,
value: key,
}));
@@ -113,13 +117,7 @@ const DataSourceView = observer(({ onAdd, onUpdate, onDelete }: DataServiceViewP
const isAddMode = !serviceKey;
return (
<Box
className="DataSourceView"
display="flex"
borderTopColor="line.normal"
height="100%"
overflow="hidden"
>
<Box className="ServiceFunctionList" display="flex" borderTopColor="line.normal">
<Box width="35%" overflow="auto" borderRight="solid" borderColor="line.normal">
<Panel
height="100%"
@@ -174,11 +172,10 @@ const DataSourceView = observer(({ onAdd, onUpdate, onDelete }: DataServiceViewP
serviceKeys={Object.keys(serviceMap)}
initialValues={
isAddMode
? { type: ESFDataSourceType.OvermindX }
? {}
: {
name: serviceKey,
method: 'get',
type: inferServiceType(serviceData?.url),
...serviceData,
}
}
@@ -186,7 +183,7 @@ const DataSourceView = observer(({ onAdd, onUpdate, onDelete }: DataServiceViewP
setServiceKey(undefined);
setVisible(null);
}}
onSubmit={(values, mode: ESFOperationType) => {
onSubmit={(values, mode: ServiceFunctionOperationModeType) => {
function shapeServiceValues(val: any) {
const shapeValues = { ...val };
delete shapeValues.type;
@@ -198,10 +195,10 @@ const DataSourceView = observer(({ onAdd, onUpdate, onDelete }: DataServiceViewP
}
// 移除掉不必要的属性
const data = shapeServiceValues(values);
if (mode === ESFOperationType.ADD) {
if (mode === ServiceFunctionOperationModeType.ADD) {
workspace.addServiceFunction(data);
onAdd && onAdd(values);
} else if (mode === ESFOperationType.UPDATE) {
} else if (mode === ServiceFunctionOperationModeType.UPDATE) {
workspace.updateServiceFunction(data);
onUpdate && onUpdate(values);
}
@@ -231,7 +228,7 @@ const DataSourceView = observer(({ onAdd, onUpdate, onDelete }: DataServiceViewP
interface ServiceDetailFormProps extends FormProps {
serviceKeys?: string[];
onCancel?: () => void;
onSubmit?: (values: any, mode: ESFOperationType) => void;
onSubmit?: (values: any, mode: ServiceFunctionOperationModeType) => void;
}
function ServiceDetailForm({
@@ -267,7 +264,12 @@ function ServiceDetailForm({
layout="horizontal"
initialValues={initialValues}
onFinish={(values) => {
onSubmit(values, isModifyMode ? ESFOperationType.UPDATE : ESFOperationType.ADD);
onSubmit(
values,
isModifyMode
? ServiceFunctionOperationModeType.UPDATE
: ServiceFunctionOperationModeType.ADD,
);
setDisabled(true);
}}
{...formProps}
@@ -345,18 +347,7 @@ function ServiceDetailForm({
<Form.Item
label="格式化响应"
name="formatter"
tooltip={
<Box>
<Link
href="https://music-doc.st.netease.com/st/tango-docs/docs/guide/basic/services#%E6%8E%A5%E5%8F%A3%E8%BF%94%E5%9B%9E%E5%80%BC%E8%A7%84%E8%8C%83"
target="_blank"
>
</Link>
</Box>
}
tooltip="提供格式化函数,对结果进行格式化"
validateTrigger="onBlur"
rules={[
{
@@ -372,7 +372,7 @@ function RenderItem({
</Popconfirm>,
]
: [
<Text color={'rgba(0, 0, 0, 0.45)'}>
<Text key="latest" color={'rgba(0, 0, 0, 0.45)'}>
{!isUndefined(baseNeedUpgrade) && '已是最新'}
</Text>,
]
+3 -1
View File
@@ -1,5 +1,7 @@
export * from './components-panel';
export * from './datasource-panel';
export * from './outline-panel';
export * from './components-panel';
export * from './dependency-panel';
export * from './history-panel';
export * from './variable-panel';
export * from './sidebar';
@@ -4,7 +4,7 @@ import { Box, css } from 'coral-system';
import { IconFont } from '@music163/tango-ui';
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
import { observer, useWorkspace } from '@music163/tango-context';
import { DropMethod, TangoViewNodeDataType } from '@music163/tango-core';
import { DropMethod, ITangoViewNodeData } from '@music163/tango-core';
import { parseDndId } from '@music163/tango-helpers';
import { useSandboxQuery } from '../../context';
import { buildQueryBySlotId } from '../../helpers';
@@ -40,7 +40,18 @@ const filedNames = {
children: 'children',
};
const OutlineTreeNode: React.FC<any> = observer(({ node }: { node: TangoViewNodeDataType }) => {
const getNodeKeys = (data: ITangoViewNodeData[]) => {
const ids: string[] = [];
data?.forEach((node) => {
ids.push(node.id);
if (node.children) {
ids.push(...getNodeKeys(node.children));
}
});
return ids;
};
const OutlineTreeNode: React.FC<any> = observer(({ node }: { node: ITangoViewNodeData }) => {
const workspace = useWorkspace();
const sandboxQuery = useSandboxQuery();
const [visible, setVisible] = useState(true);
@@ -87,7 +98,7 @@ export const ComponentsTree = observer(() => {
workspace.selectSource.selected.map((item) => item.id),
);
const file = workspace.activeViewModule;
const nodesTree = file.nodesTree as TangoViewNodeDataType[];
const nodesTree = file.nodesTree as ITangoViewNodeData[];
useEffect(() => {
setSelectedKeys(workspace.selectSource.selected.map((item) => item.id));
@@ -124,7 +135,7 @@ export const ComponentsTree = observer(() => {
}}
blockNode
autoExpandParent
defaultExpandAll
expandedKeys={getNodeKeys(nodesTree)}
draggable
onDragStart={(data) => {
const prototype = workspace.componentPrototypes.get(data.node.component);
+9 -4
View File
@@ -51,6 +51,7 @@ export interface SidebarProps extends ReactComponentProps {
* 底部附加内容
*/
footer?: React.ReactNode;
builtinPanelMap?: Record<string, SidebarPanelItemProps>;
}
export interface SidebarPanelItemProps
@@ -114,13 +115,18 @@ const INTERNAL_SIDEBAR_PANEL_MAP: Record<string, SidebarPanelItemProps> = {
},
};
function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 280, footer, children }: SidebarProps) {
function BaseSidebarPanel({
panelWidth: defaultPanelWidth = 280,
builtinPanelMap = INTERNAL_SIDEBAR_PANEL_MAP,
footer,
children,
}: SidebarProps) {
const items = useMemo(() => {
const ret: Record<React.Key, SidebarPanelItemProps> = {};
React.Children.forEach(children, (child) => {
if (child && React.isValidElement(child)) {
ret[child.key] = {
...INTERNAL_SIDEBAR_PANEL_MAP[child.key],
...builtinPanelMap[child.key],
...child.props,
};
}
@@ -272,11 +278,10 @@ function SidebarPanelBarItem({
const expandPanelStyle = css`
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
background-color: var(--tango-colors-white);
border-left: 1px solid var(--tango-colors-line-normal);
position: relative;
overflow-y: auto;
&.isFloat {
position: absolute;
@@ -1,10 +1,10 @@
import React from 'react';
import { Button, Space } from 'antd';
import { EditableVariableTree, EditableVariableTreeProps } from '@music163/tango-setting-form';
import { useBoolean } from '@music163/tango-helpers';
import { Panel, Form, IconButton } from '@music163/tango-ui';
import { FileAddOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import { observer, useWorkspace, useWorkspaceData } from '@music163/tango-context';
import { EditableVariableTree, EditableVariableTreeProps } from '../components';
export interface VariablePanelProps extends EditableVariableTreeProps {
wrapperHeight?: number | string;
@@ -15,7 +15,7 @@ type SelectionHelperAlignType = 'top-right' | 'top-left' | 'inner-top-right';
const boundingOffset = 100; // 检测选中模块的工具栏是否溢出的偏移值
const internalSelectionTools = {
const internalSelectionToolsMap = {
source: <ViewSourceAction />,
copy: <CopyNodeAction />,
delete: <DeleteNodeAction />,
@@ -28,16 +28,20 @@ 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 }: SelectionToolsProps) => {
({
actions: actionsProp = INTERNAL_SELECTION_TOOLS,
builtinActionMap = internalSelectionToolsMap,
}: SelectionToolsProps) => {
const workspace = useWorkspace();
const selectSource = workspace.selectSource;
const actions = actionsProp.map((item) => {
if (isString(item)) {
return internalSelectionTools[item]
? React.cloneElement(internalSelectionTools[item], { key: item })
return builtinActionMap[item]
? React.cloneElement(builtinActionMap[item], { key: item })
: null;
}
return item;
+9 -2
View File
@@ -10,14 +10,21 @@ import { DraggingMask } from './mask';
export interface ViewportProps extends HTMLCoralProps<'div'> {
selectionTools?: SelectionToolsProps['actions'];
builtinActionMap?: SelectionToolsProps['builtinActionMap'];
}
export function Viewport({ selectionTools, children, className, ...rest }: ViewportProps) {
export function Viewport({
selectionTools,
builtinActionMap,
children,
className,
...rest
}: ViewportProps) {
return (
<Box position="relative" height="100%" className={cx('Viewport', className)} {...rest}>
<ViewportBody>{children}</ViewportBody>
<div className="AuxTools">
<SelectionTools actions={selectionTools} />
<SelectionTools actions={selectionTools} builtinActionMap={builtinActionMap} />
<InsertionPrompt />
<DraggingMask />
</div>
@@ -3,7 +3,7 @@ import { Button, Popconfirm, Modal, ModalProps } from 'antd';
import { Box, css, Group, Text } from 'coral-system';
import { FileOutlined, DeleteOutlined, CopyOutlined, SettingOutlined } from '@ant-design/icons';
import { array2object, callAll } from '@music163/tango-helpers';
import { PageConfigType } from '@music163/tango-core';
import { IPageConfigData } from '@music163/tango-core';
import { Form, ToggleButton } from '@music163/tango-ui';
import { observer, useWorkspace } from '@music163/tango-context';
@@ -108,12 +108,12 @@ const pageListStyle = css`
interface PageListProps {
activeKey?: string;
items?: PageConfigType[];
items?: IPageConfigData[];
onAction?: (key: 'delete' | 'copy' | 'update', e: React.MouseEvent) => void;
onSelect?: (key: string) => void;
onRemove?: (key: string) => void;
onCopy?: (sourcePath: string, newPageData: PageConfigType) => void;
onUpdate?: (sourcePath: string, newPageData: PageConfigType) => void;
onCopy?: (sourcePath: string, newPageData: IPageConfigData) => void;
onUpdate?: (sourcePath: string, newPageData: IPageConfigData) => void;
}
// 表单编辑模式
@@ -128,7 +128,7 @@ function PageList({
onCopy,
onAction,
}: PageListProps) {
const [current, setCurrent] = useState<PageConfigType>(null);
const [current, setCurrent] = useState<IPageConfigData>(null);
const [type, setType] = useState<FormEditType>();
return (
<Box as="ul" bg="white" boxShadow="lowDown" borderRadius="m" css={pageListStyle}>
@@ -219,10 +219,10 @@ function PageList({
interface CopyViewModalFormProps {
title?: string;
visible?: boolean;
onOk?: (sourceRoute: string, targetRouteConfig: PageConfigType) => void;
onOk?: (sourceRoute: string, targetRouteConfig: IPageConfigData) => void;
onCancel?: ModalProps['onCancel'];
current?: PageConfigType;
items?: PageConfigType[];
current?: IPageConfigData;
items?: IPageConfigData[];
editType?: FormEditType;
}
+8 -7
View File
@@ -7,6 +7,11 @@ import { PreviewTool } from './preview';
import { RouteSwitchTool } from './route-switch';
import { TogglePanelTool } from './toggle-panel';
export interface ToolbarProps {
children?: React.ReactElement | React.ReactElement[];
builtinToolMap?: Record<string, React.ReactNode>;
}
const builtinToolsMap = {
history: <HistoryTool />,
modeSwitch: <ModeSwitchTool />,
@@ -15,11 +20,7 @@ const builtinToolsMap = {
togglePanel: <TogglePanelTool />,
};
interface ToolbarProps {
children?: React.ReactElement | React.ReactElement[];
}
export function Toolbar({ children }: ToolbarProps) {
export function Toolbar({ children, builtinToolMap = builtinToolsMap }: ToolbarProps) {
const [leftTools, centerTools, rightTools] = useMemo(() => {
const left: React.ReactNode[] = [];
const center: React.ReactNode[] = [];
@@ -27,7 +28,7 @@ export function Toolbar({ children }: ToolbarProps) {
let prevPlacement: string;
React.Children.forEach(children, (child: React.ReactElement, index) => {
let node = child.props.children || builtinToolsMap[child.key];
let node = child.props.children || builtinToolMap[child.key];
if (!node) {
node = child; // separator
}
@@ -71,7 +72,7 @@ export function Toolbar({ children }: ToolbarProps) {
);
}
interface ToolbarItemProps extends ReactComponentProps {
export interface ToolbarItemProps extends ReactComponentProps {
placement?: 'left' | 'center' | 'right';
children?: React.ReactElement;
}
@@ -3,7 +3,7 @@ import { Box } from 'coral-system';
import { observer, useDesigner } from '@music163/tango-context';
import { DesignerViewType } from '@music163/tango-core';
export interface ViewPanelProps {
export interface WorkspaceViewProps {
/**
* Workspace
*/
@@ -11,7 +11,7 @@ export interface ViewPanelProps {
children: React.ReactNode;
}
export const ViewPanel = observer((props: ViewPanelProps) => {
export const WorkspaceView = observer((props: WorkspaceViewProps) => {
const { mode = 'design', children } = props;
const designer = useDesigner();
const display = mode !== designer.activeView ? 'none' : 'block';
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.1.3](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.2...@music163/tango-sandbox@0.1.3) (2023-09-13)
**Note:** Version bump only for package @music163/tango-sandbox
## 0.1.2 (2023-09-06)
### Bug Fixes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-sandbox",
"version": "0.1.2",
"version": "0.1.3",
"description": "sandbox of tango apps",
"author": "wwsun <ww.sun@outlook.com>",
"homepage": "",
@@ -29,7 +29,7 @@
},
"dependencies": {
"@ant-design/icons": "^4.8.0",
"@music163/tango-core": "^0.1.2",
"@music163/tango-core": "^0.1.3",
"@music163/tango-helpers": "^0.1.2",
"crypto-js": "^4.1.1",
"lodash.isequal": "4.5.0",
+17
View File
@@ -3,6 +3,23 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.2.0](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.1.3...@music163/tango-setting-form@0.2.0) (2023-09-13)
### Bug Fixes
- refactor setting formItem ([b786de2](https://github.com/netease/tango/commit/b786de2f1a0e4e9141eb09fce696e45df633b232))
- remove some useless setters ([7e9ab12](https://github.com/netease/tango/commit/7e9ab12503ed33c1e6acb8a1fa5fd89fc82d35fd))
### Features
- refactor setting-form ([8d396ff](https://github.com/netease/tango/commit/8d396ff13459beeb57b6b3c48f7e8fe1765041ae))
## [0.1.3](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.1.2...@music163/tango-setting-form@0.1.3) (2023-09-07)
### Bug Fixes
- fix lint errors ([f9e6537](https://github.com/netease/tango/commit/f9e6537dd6569f4196b2799d51dbaab9838fd7f2))
## 0.1.2 (2023-09-06)
**Note:** Version bump only for package @music163/tango-setting-form
+4 -12
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-setting-form",
"version": "0.1.2",
"version": "0.2.0",
"description": "setting form of tango-apps",
"author": "wwsun <ww.sun@outlook.com>",
"homepage": "",
@@ -28,21 +28,13 @@
},
"dependencies": {
"@ant-design/icons": "^4.8.0",
"@music163/tango-core": "^0.1.2",
"@music163/tango-core": "^0.1.3",
"@music163/tango-helpers": "^0.1.2",
"@music163/tango-ui": "^0.1.2",
"@music163/tango-ui": "^0.1.3",
"antd": "^4.24.2",
"color": "^4.2.3",
"coral-system": "^1.0.5",
"cssjson": "^2.1.3",
"mobx": "6.9.0",
"mobx-react-lite": "4.0.2",
"moment": "^2.29.3",
"react-color": "^2.19.3"
},
"devDependencies": {
"@types/color": "^3.0.3",
"@types/react-color": "^3.0.6"
"mobx-react-lite": "4.0.2"
},
"publishConfig": {
"access": "public",
@@ -1,51 +0,0 @@
interface FormatProps {
format: 'simple' | 'original';
data?: Record<string, string> | Array<Record<string, string>>;
}
const formatData = (data?: Array<Record<string, string>>) => {
const result: Record<string, string> = {};
// 遍历数据
data?.forEach((item) => {
item.key && !(item.key in result) && (result[item.key] = item.value);
});
return result;
};
// 格式化value
export const formatValue = ({ format, data }: FormatProps) => {
if (format === 'original') {
if (!data) return [{ index: 0 }];
const list = Array.isArray(data) ? data : Object.keys(data).map((key) => ({ key, value: data[key] }));
return list.map((item, idx) => ({ ...item, index: idx - list.length - 1 }));
} else {
if (!data) return {};
return Array.isArray(data) ? formatData(data) : data;
}
};
export const compareValue = (origin: Array<any>, data: Array<any>) => {
const resolve = (list: Array<any>) => {
let keyStr = '';
let valueStr = '';
list.forEach((item) => {
const { key, value } = item;
keyStr += key || '$';
valueStr += value || '$';
});
return { keyStr, valueStr };
};
const oldRes = resolve(origin);
const newRes = resolve(data);
return oldRes.keyStr === newRes.keyStr && oldRes.valueStr === newRes.valueStr;
};
+37 -44
View File
@@ -1,4 +1,3 @@
import { TangoRemoteServicesType } from '@music163/tango-helpers';
import { createContext, useContext } from 'react';
import { IFormModel } from './form-model';
@@ -11,54 +10,48 @@ export const useFormModel = () => {
return useContext(FormModelContext);
};
export type VariableNode = {
label?: string;
value?: string;
type?: string;
children?: VariableNode[];
};
// TODO: 删掉不需要的
export interface FormVariableContextType {
/**
* 是否允许表单项切换到表达式设置器
*/
disableSwitchExpressionSetter?: boolean;
/**
* 探测输入上下文的对象
*/
evaluateContext?: object;
/**
* 表单动作发生时的回调
*/
onAction?: (fn: string, args: unknown[]) => void;
/**
* 状态变量列表
*/
modelVariables?: any[];
/**
* 动作变量列表
*/
actionVariables?: any[];
/**
* 表达式变量列表(含状态变量和动作变量)
*/
expressionVariables?: any[];
/**
* 路由选项
*/
routeOptions?: any[];
/**
* 可选弹窗列表
*/
modalOptions?: any[];
/**
* 关联的表单域列表
*/
formFieldsOptions?: any[];
/**
* 远程服务
*/
remoteServices?: TangoRemoteServicesType;
// /**
// * 探测输入上下文的对象
// */
// evaluateContext?: object;
// /**
// * 表单动作发生时的回调
// */
// onAction?: (fn: string, args: unknown[]) => void;
// /**
// * 状态变量列表
// */
// modelVariables?: any[];
// /**
// * 动作变量列表
// */
// actionVariables?: any[];
// /**
// * 表达式变量列表(含状态变量和动作变量)
// */
// expressionVariables?: any[];
// /**
// * 路由选项
// */
// routeOptions?: any[];
// /**
// * 可选弹窗列表
// */
// modalOptions?: any[];
// /**
// * 关联的表单域列表
// */
// formFieldsOptions?: any[];
// /**
// * 远程服务
// */
// remoteServices?: TangoRemoteServicesType;
}
const FormVariableContext = createContext<FormVariableContextType>(null);
+36 -63
View File
@@ -5,6 +5,7 @@ import {
clone,
ComponentPropType,
isVariableString,
logger,
SetterOnChangeDetailType,
useBoolean,
} from '@music163/tango-helpers';
@@ -12,9 +13,8 @@ import { IconFont, ToggleButton } from '@music163/tango-ui';
import { QuestionCircleOutlined } from '@ant-design/icons';
import { InputProps, Tooltip } from 'antd';
import { INTERNAL_SETTERS } from './setters';
import { ExpressionSetter } from './setters/expression-setter';
import { useFormModel, useFormVariable } from './context';
import { FormControl, FormControlProps } from './form-ui';
import { FormControl } from './form-ui';
export interface FormItemProps extends ComponentPropType {
extra?: React.ReactNode;
@@ -29,15 +29,15 @@ export interface FormItemComponentProps<T = any> {
[prop: string]: any;
}
export interface FormItemCreateOptionsType {
export interface IFormItemCreateOptions {
/**
* 设置器调用名
*/
name: string;
/**
* 别名
* 别名列表
*/
alias?: string;
alias?: string[];
/**
* 渲染的组件
*/
@@ -50,10 +50,6 @@ export interface FormItemCreateOptionsType {
* 是否禁用变量设置器
*/
disableVariableSetter?: boolean;
/**
* 表单项类型
*/
type?: FormControlProps['type'];
/**
* 子表单是否默认折叠
*/
@@ -64,25 +60,12 @@ export interface FormItemCreateOptionsType {
validate?: (value: string) => string | Promise<any>;
}
function normalizeCreateOptions(
options: FormItemCreateOptionsType,
): Required<Omit<FormItemCreateOptionsType, 'component' | 'alias'>> {
const render = options.render ?? ((props: any) => React.createElement(options.component, props));
return {
name: options.name,
render,
disableVariableSetter: options.disableVariableSetter ?? false,
type: options.type || 'formItem',
defaultCollapsed: options.defaultCollapsed ?? true,
validate: options.validate,
};
}
const defaultGetSetterProps = () => ({});
const defaultGetVisible = () => true;
export function createFormItem(options: FormItemCreateOptionsType) {
const _options = normalizeCreateOptions(options);
export function createFormItem(options: IFormItemCreateOptions) {
const renderSetter =
options.render ?? ((props: any) => React.createElement(options.component, props));
function FormItem({
name,
@@ -95,7 +78,7 @@ export function createFormItem(options: FormItemCreateOptionsType) {
setterProps,
defaultValue,
options: setterOptions,
disableVariableSetter: disableVariableSetterProp,
disableVariableSetter: disableVariableSetterProp = options.disableVariableSetter,
getVisible: getVisibleProp,
getSetterProps: getSetterPropsProp,
extra,
@@ -104,16 +87,15 @@ export function createFormItem(options: FormItemCreateOptionsType) {
const model = useFormModel();
const field = model.getField(name);
const value = toJS(field.value ?? defaultValue);
const disableVariableSetter =
disableSwitchExpressionSetter || disableVariableSetterProp || _options.disableVariableSetter;
const disableVariableSetter = disableSwitchExpressionSetter ?? disableVariableSetterProp;
const [isVariable, { toggle: toggleIsVariable }] = useBoolean(
() => !disableVariableSetter && isVariableString(value),
);
const setterName = isVariable ? 'expressionSetter' : setter;
const validate = SETTERS_VALIDATE_DICT[setterName];
field.setConfig({
validate,
validate: options.validate,
});
const baseComponentProps = clone(
@@ -129,6 +111,8 @@ export function createFormItem(options: FormItemCreateOptionsType) {
) as FormItemComponentProps;
let expProps = {};
// FIXME: 重新考虑这段代码的位置
if (['expressionSetter', 'actionSetter', 'eventSetter'].includes(setter) || isVariable) {
expProps = {
modalTitle: title,
@@ -138,11 +122,13 @@ export function createFormItem(options: FormItemCreateOptionsType) {
}
const getSetterProps = getSetterPropsProp || defaultGetSetterProps;
// 从注册表中获取 expSetter
const ExpressionSetter = REGISTERED_FORM_ITEM_MAP['expressionSetter']?.config?.component;
const setterNode = isVariable ? (
<ExpressionSetter {...expProps} {...baseComponentProps} />
) : (
_options.render({
renderSetter({
...expProps,
...baseComponentProps,
...setterProps, // setterProps 优先级大于快捷属性
@@ -155,8 +141,7 @@ export function createFormItem(options: FormItemCreateOptionsType) {
return (
<FormControl
visible={getVisible(model)}
type={_options.type}
defaultCollapsed={_options.defaultCollapsed}
defaultCollapsed={options.defaultCollapsed}
label={title}
note={name}
tip={tip}
@@ -189,28 +174,27 @@ export function createFormItem(options: FormItemCreateOptionsType) {
);
}
FormItem.displayName = `FormItem_${_options.name}`;
FormItem.displayName = `FormItem_${options.name}`;
FormItem.config = options;
return observer(FormItem);
}
// setter 查找表
const SETTERS_DICT: Record<string, React.FunctionComponent<any>> = {};
// setter 校验函数查找表
const SETTERS_VALIDATE_DICT: Record<string, FormItemCreateOptionsType['validate']> = {};
// 已注册的 setter 查找表
const REGISTERED_FORM_ITEM_MAP: Record<string, ReturnType<typeof createFormItem>> = {};
INTERNAL_SETTERS.forEach((item) => {
SETTERS_DICT[item.name] = createFormItem(item);
if (item.alias) {
SETTERS_DICT[item.alias] = SETTERS_DICT[item.name];
}
if (item.validate) {
SETTERS_VALIDATE_DICT[item.name] = item.validate;
if (item.alias) {
SETTERS_VALIDATE_DICT[item.alias] = SETTERS_VALIDATE_DICT[item.name];
}
}
});
/**
* Setter 注册
* @param config 注册选项
*/
export function register(config: IFormItemCreateOptions) {
const names = [config.name, ...(config.alias ?? [])];
names.forEach((name) => {
REGISTERED_FORM_ITEM_MAP[name] = createFormItem(config);
});
}
INTERNAL_SETTERS.forEach(register);
const iconStyle = {
color: 'red',
@@ -218,9 +202,9 @@ const iconStyle = {
export function SettingFormItem(props: FormItemProps) {
const { setter } = props;
const Comp = SETTERS_DICT[setter];
const Comp = REGISTERED_FORM_ITEM_MAP[setter];
if (Comp == null) {
const Fallback = SETTERS_DICT.expressionSetter;
const Fallback = REGISTERED_FORM_ITEM_MAP.expressionSetter;
return (
<Fallback
{...props}
@@ -234,14 +218,3 @@ export function SettingFormItem(props: FormItemProps) {
}
return React.createElement(Comp, props);
}
/**
* Setter 注册
* @param options
*/
export function register(options: FormItemCreateOptionsType) {
if (SETTERS_DICT[options.name]) {
throw Error(`Duplicate setter name: <${options.name}>`);
}
SETTERS_DICT[options.name] = createFormItem(options);
}
+11 -29
View File
@@ -10,7 +10,7 @@ import {
} from '@music163/tango-helpers';
import { Search, Tabs } from '@music163/tango-ui';
import { SettingFormItem, register } from './form-item';
import { FormModelProvider, FormVariableProvider, FormVariableContextType } from './context';
import { FormModelProvider, FormVariableProvider } from './context';
import { FormModel, FormModelOptionsType } from './form-model';
import { SettingFormObject } from './form-object';
import { isValidNestProps } from './helpers';
@@ -53,19 +53,19 @@ const formStyle = css`
}
`;
type GroupOptionType = {
interface IFormTabsGroupOption {
label: string;
value: string;
};
}
const internalGroups: GroupOptionType[] = [
const internalGroups: IFormTabsGroupOption[] = [
{ label: '常用', value: 'basic' },
{ label: '事件', value: 'event' },
{ label: '样式', value: 'style' },
{ label: '高级', value: 'advanced' },
];
export interface SettingFormProps extends FormVariableContextType {
export interface SettingFormProps {
/**
* 表单状态管理模型实例
*/
@@ -85,24 +85,19 @@ export interface SettingFormProps extends FormVariableContextType {
/**
* 选项分组信息
*/
groupOptions?: GroupOptionType[];
groupOptions?: IFormTabsGroupOption[];
/**
* 是否允许表单项切换到表达式设置器
*/
disableSwitchExpressionSetter?: boolean;
}
export function SettingForm({
prototype,
disableSwitchExpressionSetter,
modelVariables = [],
actionVariables = [],
expressionVariables = [],
routeOptions = [],
modalOptions = [],
formFieldsOptions = [],
evaluateContext,
model: modelProp,
defaultValue,
onChange = noop,
onAction = noop,
remoteServices,
groupOptions = internalGroups,
}: SettingFormProps) {
const [keyword, setKeyword] = useState('');
@@ -155,21 +150,8 @@ export function SettingForm({
}
}, [modelProp]);
const formVariable = {
disableSwitchExpressionSetter,
onAction,
modelVariables,
actionVariables,
expressionVariables,
routeOptions,
modalOptions,
formFieldsOptions,
evaluateContext,
remoteServices,
};
return (
<FormVariableProvider value={formVariable}>
<FormVariableProvider value={{ disableSwitchExpressionSetter }}>
<FormModelProvider value={model}>
<Box className="SettingForm" mb="xl" css={formStyle}>
<Box className="SettingFormHeader" position="sticky" top="0" bg="white" zIndex="2">
+1 -3
View File
@@ -3,6 +3,4 @@ export * from './form-item';
export * from './form-object';
export * from './form-model';
export * from './context';
export { EditableVariableTree } from './components';
export type { EditableVariableTreeProps, VariableTreeNodeType } from './components';
export * from './setters';
@@ -9,14 +9,6 @@ interface ChoiceSetterProps {
options?: OptionType[];
}
function RadioButton({ tip, ...rest }: RadioProps & { tip?: string }) {
let ret = <Radio.Button {...rest} />;
if (tip) {
ret = <Tooltip title={tip}>{ret}</Tooltip>;
}
return ret;
}
export function ChoiceSetter({
options,
mode = 'text',
@@ -45,3 +37,11 @@ export function ChoiceSetter({
</Radio.Group>
);
}
function RadioButton({ tip, ...rest }: RadioProps & { tip?: string }) {
let ret = <Radio.Button {...rest} />;
if (tip) {
ret = <Tooltip title={tip}>{ret}</Tooltip>;
}
return ret;
}
@@ -0,0 +1,26 @@
import React from 'react';
import { SingleMonacoEditor } from '@music163/tango-ui';
import { Box } from 'coral-system';
import { FormItemComponentProps } from '@music163/tango-setting-form/src/form-item';
export function ExpressionSetter({ value, onChange }: FormItemComponentProps) {
return (
<Box height="120px" border="solid" borderColor="line.normal" borderRadius="s">
<SingleMonacoEditor
defaultValue={value}
onBlur={(newValue) => {
if (newValue !== value) {
onChange(`{${newValue}}`);
}
}}
language="javascript"
options={{
lineNumbers: 'off',
minimap: {
enabled: false,
},
}}
/>
</Box>
);
}
@@ -1,44 +0,0 @@
import React from 'react';
import { css } from 'styled-components';
import { Box } from 'coral-system';
import { Select } from 'antd';
// import { IconPro } from '@ant-design/icons';
import { FormItemComponentProps } from '../form-item';
const acStyle = css`
.ant-select {
width: 100%;
}
`;
const ALL_ICON_NAMES: string[] = [];
/**
* Icon 组件设置器
* onChange 返回 `{<Icon type="iconName" />}`
*/
export function IconSetter({ onChange, ...rest }: FormItemComponentProps) {
const handleChange = (val: string) => {
onChange &&
onChange(val, {
relatedImports: ['Icon'],
});
};
return (
<Box css={acStyle}>
<Select showSearch allowClear placeholder="请选择图标" onChange={handleChange} {...rest}>
{ALL_ICON_NAMES.map((icon) => {
const key = `{<Icon type="${icon}" />}`;
return (
<Select.Option key={key}>
<Box display="inline-block" mr="m">
{/* <IconPro type={icon as any} size="14px" /> */}
</Box>
{icon}
</Select.Option>
);
})}
</Select>
</Box>
);
}
@@ -1,148 +0,0 @@
import React, { useEffect, useState } from 'react';
import { Button, Empty, Modal, Pagination, Radio } from 'antd';
import { Box, css, Grid, GridItem, Text } from 'coral-system';
import { isFunction, useBoolean } from '@music163/tango-helpers';
import { FormItemComponentProps } from '../form-item';
import { useFormVariable } from '../context';
const wrapperStyle = css`
img {
max-width: 100%;
max-height: 150px;
}
`;
export function ImageSetter({ value, onChange }: FormItemComponentProps) {
const [visible, { on, off }] = useBoolean();
const label = value ? '更新图片' : '上传图片';
const { remoteServices } = useFormVariable();
return (
<Box css={wrapperStyle}>
<Box border="solid" borderColor="line.normal" textAlign="center" mb="m">
{value ? <img src={value} alt="preview image" /> : null}
</Box>
<Button block onClick={on}>
{label}
</Button>
<Modal title={label} visible={visible} onCancel={off} width="60%">
<ImageCenter
imageService={remoteServices?.ImageService}
onSelect={(url) => {
onChange(url);
off();
}}
/>
</Modal>
</Box>
);
}
const categories = [
{ label: '我的素材', value: 'listMy' },
{ label: '我的收藏', value: 'listFav' },
{ label: '公共素材', value: 'listPub' },
];
interface ImageCenterProps {
onSelect?: ImageListProps['onSelect'];
imageService?: Record<string, (...args: any[]) => Promise<any>>;
}
const pageSize = 20;
function ImageCenter({ imageService, onSelect }: ImageCenterProps) {
const [cate, setCate] = useState('listMy');
const [page, setPage] = useState(1);
const [data, setData] = useState<any>({});
useEffect(() => {
imageService?.[cate]?.({ limit: pageSize, offset: (page - 1) * pageSize }).then((res: any) => {
setData(res || {});
});
}, [imageService, cate, page]);
return (
<Box>
<Box display="flex" justifyContent="space-between">
<Radio.Group
value={cate}
onChange={(e) => {
setCate(e.target.value);
}}
options={categories}
optionType="button"
buttonStyle="solid"
/>
<Button
type="primary"
href="https://music-fn.hz.netease.com/s/music-deer-web/user/image/myupload"
target="_blank"
>
</Button>
</Box>
{data.list?.length ? (
<ImageList dataSource={data.list} onSelect={onSelect} />
) : (
<Empty description="没有数据或数据服务请求出错" />
)}
{data.count > pageSize ? (
<Pagination current={page} total={data.count} pageSize={pageSize} onChange={setPage} />
) : null}
</Box>
);
}
const imageListWrapper = css`
img {
max-width: 100%;
max-height: 120px;
}
button {
border-color: transparent;
outline: none;
padding: 0;
margin: 0;
&:hover {
border-color: var(--tango-colors-brand);
}
}
`;
interface ImageListProps {
dataSource?: any[];
onSelect?: (url: string) => void;
}
function ImageList({ dataSource = [], onSelect }: ImageListProps) {
return (
<Grid my="m" columns={4} gap="12px" height="400px" overflow="auto" css={imageListWrapper}>
{dataSource.map((item) => (
<GridItem
key={item.id}
display="flex"
alignItems="center"
justifyContent="center"
bg="gray.20"
>
<Box
as="button"
display="inline-flex"
flexDirection="column"
alignItems="center"
gap="m"
bg="transparent"
onClick={() => {
isFunction(onSelect) && onSelect(item.url);
}}
>
<img src={item.url} />
<Text fontSize="12px" color="text.note">
{item.name}
</Text>
</Box>
</GridItem>
))}
</Grid>
);
}
+16 -192
View File
@@ -1,216 +1,40 @@
import { ActionListSetter } from './action-list-setter';
import { EnumSetter } from './enum-setter';
import { TextAreaSetter, TextSetter } from './text-setter';
import { PickerSetter } from './picker-setter';
import { ChoiceSetter } from './choice-setter';
import { FormItemCreateOptionsType } from '../form-item';
import { ListSetter } from './list-setter';
import { IconSetter } from './icon-setter';
import { ColumnSetter } from './column-setter';
import { NumberSetter, SliderSetter } from './number-setter';
import { ExpressionSetter, expressionValueValidate, jsonValueValidate } from './expression-setter';
import { OptionSetter } from './option-setter';
import { DateSetter, DateRangeSetter, TimeSetter, TimeRangeSetter } from './date-setter';
import { JsxSetter } from './jsx-setter';
import { EventSetter } from './event-setter';
import {
SpacingSetter,
ColorSetter,
BgSetter,
BorderSetter,
FlexGapSetter,
DisplaySetter,
FlexJustifyContentSetter,
FlexAlignItemsSetter,
FlexDirectionSetter,
} from './style-setter';
import { CssSetter } from './css-setter';
import { ListenerSetter } from './listener-setter';
import { RuleSetter } from './rule-setter';
import { ModelSetter } from './model-setter';
import {
RenderSetter,
TableCellSetter,
TableExpandableSetter,
XToolbarSetter,
} from './render-props-setter';
import { ImageSetter } from './image-setter';
import { RouterSetter } from './router-setter';
import { BoolSetter } from './bool-setter';
import { IFormItemCreateOptions } from '../form-item';
import { ExpressionSetter } from './exp-setter';
export const INTERNAL_SETTERS: FormItemCreateOptionsType[] = [
{
name: 'actionListSetter',
component: ActionListSetter,
},
{
name: 'jsxSetter',
component: JsxSetter,
},
{
name: 'modelSetter',
component: ModelSetter,
disableVariableSetter: true,
validate(value: string) {
if (value.split('.').length < 2) {
return '请输入合法的模型变量路径,如:modelName.variableName';
}
},
},
{
name: 'eventSetter',
alias: 'actionSetter',
component: EventSetter,
disableVariableSetter: true,
},
export const INTERNAL_SETTERS: IFormItemCreateOptions[] = [
{
name: 'boolSetter',
component: BoolSetter,
},
{
name: 'expressionSetter',
alias: 'expSetter',
name: 'choiceSetter',
component: ChoiceSetter,
},
{
name: 'expSetter',
alias: ['expressionSetter'],
component: ExpressionSetter,
disableVariableSetter: true,
validate: expressionValueValidate,
},
{
name: 'jsonSetter',
component: ExpressionSetter,
disableVariableSetter: true,
validate: jsonValueValidate,
},
{
name: 'dateSetter',
component: DateSetter,
},
{
name: 'enumSetter',
component: EnumSetter,
},
{
name: 'dateRangeSetter',
component: DateRangeSetter,
},
{
name: 'timeSetter',
component: TimeSetter,
},
{
name: 'timeRangeSetter',
component: TimeRangeSetter,
name: 'numberSetter',
component: NumberSetter,
},
{
name: 'textSetter',
component: TextSetter,
},
{ name: 'textAreaSetter', component: TextAreaSetter },
{
name: 'numberSetter',
component: NumberSetter,
},
{
name: 'sliderSetter',
component: SliderSetter,
},
{
name: 'pickerSetter',
alias: 'selectSetter',
component: PickerSetter,
},
{
name: 'choiceSetter',
component: ChoiceSetter,
},
{
name: 'iconSetter',
component: IconSetter,
},
{
name: 'optionSetter',
component: OptionSetter,
},
// {
// name: 'jsonSetter',
// alias: 'styleSetter',
// component: JSONSetter,
// },
{
name: 'listSetter',
component: ListSetter as any,
},
// {
// name: 'stateSetter',
// component: StateSetter,
// disableVariableSetter: true,
// },
{
name: 'cssSetter',
component: CssSetter,
type: 'subForm',
defaultCollapsed: false,
},
{
name: 'displaySetter',
component: DisplaySetter,
},
{ name: 'flexDirectionSetter', component: FlexDirectionSetter },
{
name: 'flexGapSetter',
component: FlexGapSetter,
},
{
name: 'flexJustifyContentSetter',
component: FlexJustifyContentSetter,
},
{
name: 'flexAlignItemsSetter',
component: FlexAlignItemsSetter,
},
{
name: 'spacingSetter',
component: SpacingSetter,
},
{
name: 'colorSetter',
component: ColorSetter,
},
{ name: 'bgSetter', component: BgSetter },
{ name: 'borderSetter', component: BorderSetter },
{
name: 'columnSetter',
component: ColumnSetter,
},
{
name: 'listenerSetter',
component: ListenerSetter,
},
{
name: 'ruleSetter',
component: RuleSetter,
},
{
name: 'renderPropsSetter',
component: RenderSetter,
},
{
name: 'tableCellSetter',
component: TableCellSetter,
},
{
name: 'tableExpandableSetter',
component: TableExpandableSetter,
},
{
name: 'xtoolbarSetter',
component: XToolbarSetter,
},
{
name: 'imageSetter',
component: ImageSetter,
},
{
name: 'routerSetter',
component: RouterSetter,
},
];
export * from './bool-setter';
export * from './choice-setter';
export * from './number-setter';
export * from './text-setter';
@@ -1,203 +0,0 @@
import React, { FunctionComponent, useState } from 'react';
import { Box } from 'coral-system';
import { Select, Input, Tabs, Button } from 'antd';
import { FieldStringOutlined } from '@ant-design/icons';
import { SingleMonacoEditor } from '@music163/tango-ui';
import { FormItemComponentProps } from '../form-item';
import { useFormVariable } from '../context';
import { ListSetter } from './list-setter';
const { TabPane } = Tabs;
// TODO: 需考虑 option选项联动 & 组件属性联动等面板
const CustomComponent: FunctionComponent<any> = ({ value = {}, onChange }) => {
// FIXME: 这里之前判断逻辑有问题,导致每次 value 变更都执行 parseExpression
const [tabIndex, setTabIndex] = useState(typeof value === 'string' ? '2' : '1');
const triggerChange = (changedValue: any) => {
onChange({
...value,
...changedValue,
});
};
const customListenerDefaultValue = `/* {(valid, field, form) => {
form.fieldMap.input1.setValue("联动改变值");
}}*/`;
return (
<Tabs type="card" activeKey={tabIndex} onChange={setTabIndex}>
<TabPane tab="常用配置" key="1">
<Box display="flex" flexDirection="column" gap="20px">
<Select
value={value?.status}
onChange={(v) => {
triggerChange({
status: v,
});
}}
placeholder="UI状态"
options={[
{
label: '编辑',
value: 'edit',
},
{
label: '禁用',
value: 'disabled',
},
{
label: '隐藏',
value: 'hidden',
},
{
label: '预览',
value: 'preview',
},
]}
/>
<Select
value={value?.ui?.required}
onChange={(v) => {
triggerChange({
ui: {
...value?.ui,
required: v,
},
});
}}
placeholder="必填"
options={[
{
label: '必填',
value: true,
},
{
label: '不必填',
value: false,
},
]}
/>
<Input
placeholder="设置label标题"
value={value?.ui?.label}
onChange={(e) => {
triggerChange({
ui: {
...value?.ui,
label: e.target.value,
},
});
}}
/>
<Input
placeholder="设置字段值"
value={value?.value}
onChange={(e) => {
triggerChange({
value: e.target.value,
});
}}
/>
</Box>
</TabPane>
<TabPane tab="自定义配置" key="2">
<SingleMonacoEditor
defaultValue={customListenerDefaultValue}
value={
Object.prototype.toString.call(value) === '[object Object]'
? customListenerDefaultValue
: value
}
onChange={(v) => onChange(v.trim())}
hasBorder
height="150px"
options={{
lineNumbers: 'off',
fontSize: 12,
wordWrap: 'on',
minimap: {
enabled: false,
},
}}
/>
</TabPane>
</Tabs>
);
};
const renderSetterItem = (item: any) => {
return (
<>
<FieldStringOutlined />
<Box display="inline-block" ml="m">
{item.title}
</Box>
</>
);
};
/**
* XFormItem响应器
*/
export const ListenerSetter = (props: FormItemComponentProps<any[]>) => {
const { formFieldsOptions } = useFormVariable();
return (
<ListSetter
getListItemKey={(item) => item.key || item.dataIndex}
addBtnText="配置响应器"
listItemFormFields={[
{
label: '来源字段',
name: 'watch',
required: true,
component: <Select mode="tags" options={formFieldsOptions} />,
extra: <span></span>,
},
{
label: '触发条件',
name: 'condition',
required: true,
component: (
<SingleMonacoEditor
hasBorder
height="50px"
defaultValue="// input1.value === '123'"
options={{
lineNumbers: 'off',
fontSize: 12,
wordWrap: 'on',
minimap: {
enabled: false,
},
}}
/>
),
extra: <span></span>,
},
{
label: '响应行为',
name: 'set',
required: true,
component: <CustomComponent />,
width: '400px',
extra: (
<Button
type="link"
onClick={() =>
window.open(
'https://music-cms.hz.netease.com/xform-docs/docs/tutorial-basics/listeners#set-%E4%B8%BA-function',
)
}
>
</Button>
),
},
]}
newItemDefaultValues={[]}
renderItem={renderSetterItem}
{...props}
/>
);
};
@@ -1,175 +0,0 @@
import React from 'react';
import { Box } from 'coral-system';
import { AutoComplete, Button, Select, Switch } from 'antd';
import { FieldStringOutlined } from '@ant-design/icons';
import { SingleMonacoEditor } from '@music163/tango-ui';
import { FormItemComponentProps } from '../form-item';
import { ListSetter, NewOptionFormFieldType } from './list-setter';
// 常用正则
const enumPattern = [
{
label: '纯数字',
value: '^[0-9]*$',
},
{
label: '纯汉字',
value: '^[\u4e00-\u9fa5]{0,}$',
},
{
label: '手机号',
value: '^[1][3-8][0-9]{9}$',
},
{
label: 'IP地址',
value: 'd+.d+.d+.d+',
},
{
label: '身份证号',
value: '^d{15}|d{18}$',
},
{
label: '邮政编码',
value: '[1-9]d{5}(?!d)',
},
{
label: '图片URL',
value: '(https?:[^:<>"]*/)([^:<>"]*)(.((png!thumbnail)|(png)|(jpg)|(webp)|(gif)))',
},
];
// async-validator type
// https://github.com/yiminghe/async-validator#type
const type = [
'string', // // Must be of type string. This is the default type.
// 'number', Input TextArea出来的value都是string / InputNumber是number。不在async-validator做数字类型的校验
'boolean', // Must be of type boolean.
'regexp', // Must be an instance of RegExp or a string that does not generate an exception when creating a new RegExp.
'integer', // Must be of type number and an integer.
'float', // Must be of type number and a floating point number.
'array', // Must be an array as determined by Array.isArray.
'object', // Must be of type object and not Array.isArray.
'date', // Value // Must be valid as determined by Date
'url', // Must be of type url.
'email', // Must be of type email.
];
const trigger = ['change', 'submit'];
const status = ['error', 'warning'];
/**
* 正则输入框
*/
const PatternInput: React.FunctionComponent<any> = (props) => {
return (
<>
<AutoComplete options={enumPattern} onSelect={props.onChange} {...props} />
</>
);
};
/**
* 必填选择
*/
const RequiredInput: React.FunctionComponent<any> = (props) => {
return (
<>
<Switch onChange={props.onChange} checked={props.value} {...props} />
</>
);
};
const optionFormFields: NewOptionFormFieldType[] = [
{
label: '字段类型',
name: 'type',
required: true,
component: <Select options={type.map((t) => ({ label: t, value: t }))} />,
},
{
label: '是否必填',
name: 'required',
component: <RequiredInput />,
},
{
label: '触发方式',
name: 'trigger',
component: <Select options={trigger.map((t) => ({ label: t, value: t }))} />,
},
{
label: '触发状态',
name: 'status',
component: <Select options={status.map((t) => ({ label: t, value: t }))} />,
},
{
label: '错误提示语',
name: 'message',
extra: '自定义错误提示',
},
{
label: '正则表达式',
name: 'pattern',
extra: '复杂场景,可以自定义正则。',
component: <PatternInput placeholder="例如: ^[0-9]*$" />,
},
{
label: '自定义校验',
name: 'validator',
width: '400px',
extra: (
<Button
type="link"
onClick={() => window.open('https://github.com/yiminghe/async-validator#validator')}
>
</Button>
),
component: (
<SingleMonacoEditor
hasBorder
height="108px"
defaultValue="/** {(rule, value, callback) => { return value === 'test';}}*/"
options={{
lineNumbers: 'off',
fontSize: 12,
wordWrap: 'on',
minimap: {
enabled: false,
},
}}
/>
),
},
];
const renderSetterItem = (item: any) => {
return (
<>
<FieldStringOutlined />
<Box display="inline-block" ml="m">
{item.title}
</Box>
</>
);
};
/**
* 校验属性配置
*/
export function RuleSetter(props: FormItemComponentProps<any[]>) {
return (
<ListSetter
getListItemKey={(item) => item.key || item.dataIndex}
addBtnText="配置校验器"
listItemFormFields={optionFormFields}
newItemDefaultValues={{
type: 'string',
trigger: 'change',
status: 'error',
}}
renderItem={renderSetterItem}
{...props}
/>
);
}
@@ -4,10 +4,10 @@ import { FormItemComponentProps } from '../form-item';
const noop = () => {};
type TextSetterProps = {
interface TextSetterProps extends Omit<InputProps, 'onChange' | 'value'> {
value?: string;
onChange?: (value: string) => void;
} & Omit<InputProps, 'onChange' | 'value'>;
}
export function TextSetter({
value: valueProp,
-1
View File
@@ -1 +0,0 @@
declare module 'cssjson';
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.1.3](https://github.com/netease/tango/compare/@music163/tango-ui@0.1.2...@music163/tango-ui@0.1.3) (2023-09-13)
### Bug Fixes
- update exports ([443ae58](https://github.com/netease/tango/commit/443ae589f67255f3815b587fd8f928c2b8742e9a))
## 0.1.2 (2023-09-06)
**Note:** Version bump only for package @music163/tango-ui
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-ui",
"version": "0.1.2",
"version": "0.1.3",
"description": "ui widgets of tango",
"keywords": [
"react",
+3 -8
View File
@@ -5,7 +5,7 @@ import type { IconFontProps } from '@ant-design/icons/es/components/IconFont';
/**
* 只能在 Designer 里面用,其他地方用不了,依赖 iconfont 脚本提前载入
*/
export const IconFont = React.forwardRef<HTMLSpanElement, IconFontProps<string>>((props, ref) => {
export function IconFont(props: IconFontProps<string>) {
const { type, children, ...restProps } = props;
// children > type
@@ -16,12 +16,7 @@ export const IconFont = React.forwardRef<HTMLSpanElement, IconFontProps<string>>
if (children) {
content = children;
}
return (
// @ts-ignore
<Icon {...restProps} ref={ref}>
{content}
</Icon>
);
});
return <Icon {...(restProps as any)}>{content}</Icon>;
}
IconFont.displayName = 'IconFont';
+1
View File
@@ -11,6 +11,7 @@
"@music163/tango-setting-form": ["packages/setting-form/src/index.ts"],
"@music163/tango-helpers": ["packages/helpers/src/index.ts"],
"@music163/tango": ["packages/designer/src/index.ts"],
"@music163/tango-ui": ["packages/ui/src/index.ts"],
}
}
}
+22 -156
View File
@@ -1765,7 +1765,7 @@
resolved "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.0.tgz"
integrity sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ==
"@discoveryjs/json-ext@0.5.7", "@discoveryjs/json-ext@^0.5.0", "@discoveryjs/json-ext@^0.5.3":
"@discoveryjs/json-ext@0.5.7", "@discoveryjs/json-ext@^0.5.3":
version "0.5.7"
resolved "https://registry.npmmirror.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz"
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
@@ -4379,10 +4379,10 @@
resolved "https://registry.npmmirror.com/@types/color-name/-/color-name-1.1.1.tgz"
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
"@types/color@^3.0.3":
version "3.0.3"
resolved "https://registry.npmmirror.com/@types/color/-/color-3.0.3.tgz"
integrity sha512-X//qzJ3d3Zj82J9sC/C18ZY5f43utPbAJ6PhYt/M7uG6etcF6MRpKdN880KBy43B0BMzSfeT96MzrsNjFI3GbA==
"@types/color@^3.0.4":
version "3.0.4"
resolved "https://registry.npmmirror.com/@types/color/-/color-3.0.4.tgz#53ceca0946660c7779442948878172c6ace13777"
integrity sha512-OpisS4bqJJwbkkQRrMvURf3DOxBoAg9mysHYI7WgrWpSYHqHGKYBULHdz4ih77SILcLDo/zyHGFyfIl9yb8NZQ==
dependencies:
"@types/color-convert" "*"
@@ -4705,7 +4705,7 @@
"@types/react-color@^3.0.6":
version "3.0.6"
resolved "https://registry.npmmirror.com/@types/react-color/-/react-color-3.0.6.tgz"
resolved "https://registry.npmmirror.com/@types/react-color/-/react-color-3.0.6.tgz#602fed023802b2424e7cd6ff3594ccd3d5055f9a"
integrity sha512-OzPIO5AyRmLA7PlOyISlgabpYUa3En74LP8mTMa0veCA719SvYQov4WLMsHvCgXP+L+KI9yGhYnqZafVGG0P4w==
dependencies:
"@types/react" "*"
@@ -5524,23 +5524,6 @@
"@webassemblyjs/wast-parser" "1.9.0"
"@xtuc/long" "4.2.2"
"@webpack-cli/configtest@^1.2.0":
version "1.2.0"
resolved "https://registry.npmmirror.com/@webpack-cli/configtest/-/configtest-1.2.0.tgz"
integrity sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==
"@webpack-cli/info@^1.5.0":
version "1.5.0"
resolved "https://registry.npmmirror.com/@webpack-cli/info/-/info-1.5.0.tgz"
integrity sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==
dependencies:
envinfo "^7.7.3"
"@webpack-cli/serve@^1.7.0":
version "1.7.0"
resolved "https://registry.npmmirror.com/@webpack-cli/serve/-/serve-1.7.0.tgz"
integrity sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://registry.npmmirror.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz"
@@ -7122,14 +7105,6 @@ chalk@^2.4.1, chalk@^2.4.2:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^3.0.0:
version "3.0.0"
resolved "https://registry.npmmirror.com/chalk/-/chalk-3.0.0.tgz"
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz"
@@ -7505,7 +7480,7 @@ color-support@^1.1.2, color-support@^1.1.3:
color@^4.2.3:
version "4.2.3"
resolved "https://registry.npmmirror.com/color/-/color-4.2.3.tgz"
resolved "https://registry.npmmirror.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a"
integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==
dependencies:
color-convert "^2.0.1"
@@ -7516,7 +7491,7 @@ colord@^2.9.1:
resolved "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz"
integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
colorette@^2.0.10, colorette@^2.0.14, colorette@^2.0.20:
colorette@^2.0.10, colorette@^2.0.20:
version "2.0.20"
resolved "https://registry.npmmirror.com/colorette/-/colorette-2.0.20.tgz"
integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
@@ -7571,7 +7546,7 @@ commander@^6.2.1:
resolved "https://registry.npmmirror.com/commander/-/commander-6.2.1.tgz"
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
commander@^7.0.0, commander@^7.2.0:
commander@^7.2.0:
version "7.2.0"
resolved "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz"
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
@@ -7942,13 +7917,6 @@ cookie@0.5.0:
resolved "https://registry.npmmirror.com/cookie/-/cookie-0.5.0.tgz"
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
copy-anything@^2.0.1:
version "2.0.6"
resolved "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz"
integrity sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==
dependencies:
is-what "^3.14.1"
copy-concurrently@^1.0.0:
version "1.0.5"
resolved "https://registry.npmmirror.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz"
@@ -8373,7 +8341,7 @@ cssesc@^3.0.0:
cssjson@^2.1.3:
version "2.1.3"
resolved "https://registry.npmmirror.com/cssjson/-/cssjson-2.1.3.tgz"
resolved "https://registry.npmmirror.com/cssjson/-/cssjson-2.1.3.tgz#b855aa06c5979df16bf6375a14a455e415660169"
integrity sha512-VKzsSbYW4gwfS6Fg+z1mEII+cvurP/Vr7G3cDLEkvR0tcQD20LpF/ljOOFVVT9XYkOFo4TQWRcB/mSmbrKsXxA==
cssnano-preset-advanced@^5.3.8:
@@ -8523,7 +8491,7 @@ debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, de
dependencies:
ms "2.1.2"
debug@^3.0.0, debug@^3.2.6, debug@^3.2.7:
debug@^3.0.0, debug@^3.2.7:
version "3.2.7"
resolved "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz"
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
@@ -9082,7 +9050,7 @@ env-paths@^2.2.0:
resolved "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz"
integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
envinfo@^7.7.3, envinfo@^7.7.4:
envinfo@^7.7.4:
version "7.10.0"
resolved "https://registry.npmmirror.com/envinfo/-/envinfo-7.10.0.tgz"
integrity sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==
@@ -9092,7 +9060,7 @@ err-code@^2.0.2:
resolved "https://registry.npmmirror.com/err-code/-/err-code-2.0.3.tgz"
integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==
errno@^0.1.1, errno@^0.1.3, errno@~0.1.7:
errno@^0.1.3, errno@~0.1.7:
version "0.1.8"
resolved "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz"
integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==
@@ -9808,11 +9776,6 @@ fast-url-parser@1.1.3:
dependencies:
punycode "^1.3.2"
fastest-levenshtein@^1.0.12:
version "1.0.16"
resolved "https://registry.npmmirror.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz"
integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==
fastq@^1.6.0:
version "1.15.0"
resolved "https://registry.npmmirror.com/fastq/-/fastq-1.15.0.tgz"
@@ -11234,7 +11197,7 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite@0.6.3, iconv-lite@^0.6.2, iconv-lite@^0.6.3:
iconv-lite@0.6.3, iconv-lite@^0.6.2:
version "0.6.3"
resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz"
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
@@ -11294,11 +11257,6 @@ image-size@^1.0.1:
dependencies:
queue "6.0.2"
image-size@~0.5.0:
version "0.5.5"
resolved "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz"
integrity sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==
immer@^9.0.7:
version "9.0.21"
resolved "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz"
@@ -12003,11 +11961,6 @@ is-weakset@^2.0.1:
call-bind "^1.0.2"
get-intrinsic "^1.1.1"
is-what@^3.14.1:
version "3.14.1"
resolved "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz"
integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==
is-whitespace-character@^1.0.0:
version "1.0.4"
resolved "https://registry.npmmirror.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz"
@@ -13038,32 +12991,6 @@ lerna@^6.6.1:
yargs "16.2.0"
yargs-parser "20.2.4"
less-loader@^7.3.0:
version "7.3.0"
resolved "https://registry.npmmirror.com/less-loader/-/less-loader-7.3.0.tgz"
integrity sha512-Mi8915g7NMaLlgi77mgTTQvK022xKRQBIVDSyfl3ErTuBhmZBQab0mjeJjNNqGbdR+qrfTleKXqbGI4uEFavxg==
dependencies:
klona "^2.0.4"
loader-utils "^2.0.0"
schema-utils "^3.0.0"
less@^4.1.2:
version "4.2.0"
resolved "https://registry.npmmirror.com/less/-/less-4.2.0.tgz"
integrity sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==
dependencies:
copy-anything "^2.0.1"
parse-node-version "^1.0.1"
tslib "^2.3.0"
optionalDependencies:
errno "^0.1.1"
graceful-fs "^4.1.2"
image-size "~0.5.0"
make-dir "^2.1.0"
mime "^1.4.1"
needle "^3.1.0"
source-map "~0.6.0"
leven@^3.1.0:
version "3.1.0"
resolved "https://registry.npmmirror.com/leven/-/leven-3.1.0.tgz"
@@ -13751,7 +13678,7 @@ mime@1.4.1:
resolved "https://registry.npmmirror.com/mime/-/mime-1.4.1.tgz"
integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==
mime@1.6.0, mime@^1.4.1, mime@^1.6.0:
mime@1.6.0, mime@^1.6.0:
version "1.6.0"
resolved "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
@@ -13796,15 +13723,6 @@ mini-create-react-context@^0.4.0:
"@babel/runtime" "^7.12.1"
tiny-warning "^1.0.3"
mini-css-extract-plugin@^1.6.2:
version "1.6.2"
resolved "https://registry.npmmirror.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz"
integrity sha512-WhDvO3SjGm40oV5y26GjMJYjd2UMqrLAGKy5YS2/3QKJy2F7jgynuHTir/tgUUOiNQu5saXHdc8reo7YuhhT4Q==
dependencies:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
webpack-sources "^1.1.0"
mini-css-extract-plugin@^2.6.1:
version "2.7.6"
resolved "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz"
@@ -14028,9 +13946,9 @@ modify-values@^1.0.0:
resolved "https://registry.npmmirror.com/modify-values/-/modify-values-1.0.1.tgz"
integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==
moment@^2.24.0, moment@^2.29.2, moment@^2.29.3:
moment@^2.24.0, moment@^2.29.2, moment@^2.29.4:
version "2.29.4"
resolved "https://registry.npmmirror.com/moment/-/moment-2.29.4.tgz"
resolved "https://registry.npmmirror.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
monaco-editor-textmate@^4.0.0:
@@ -14143,15 +14061,6 @@ natural-compare@^1.4.0:
resolved "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
needle@^3.1.0:
version "3.2.0"
resolved "https://registry.npmmirror.com/needle/-/needle-3.2.0.tgz"
integrity sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==
dependencies:
debug "^3.2.6"
iconv-lite "^0.6.3"
sax "^1.2.4"
negotiator@0.6.3, negotiator@^0.6.3:
version "0.6.3"
resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz"
@@ -15156,11 +15065,6 @@ parse-json@^5.0.0, parse-json@^5.2.0:
json-parse-even-better-errors "^2.3.0"
lines-and-columns "^1.1.6"
parse-node-version@^1.0.1:
version "1.0.1"
resolved "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz"
integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==
parse-numeric-range@^1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz"
@@ -16230,19 +16134,6 @@ process@^0.11.10:
resolved "https://registry.npmmirror.com/process/-/process-0.11.10.tgz"
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
progress-bar-webpack-plugin@^2.1.0:
version "2.1.0"
resolved "https://registry.npmmirror.com/progress-bar-webpack-plugin/-/progress-bar-webpack-plugin-2.1.0.tgz"
integrity sha512-UtlZbnxpYk1wufEWfhIjRn2U52zlY38uvnzFhs8rRxJxC1hSqw88JNR2Mbpqq9Kix8L1nGb3uQ+/1BiUWbigAg==
dependencies:
chalk "^3.0.0"
progress "^2.0.3"
progress@^2.0.3:
version "2.0.3"
resolved "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
promise-all-reject-late@^1.0.0:
version "1.0.1"
resolved "https://registry.npmmirror.com/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz"
@@ -16928,7 +16819,7 @@ react-base16-styling@^0.6.0:
react-color@^2.19.3:
version "2.19.3"
resolved "https://registry.npmmirror.com/react-color/-/react-color-2.19.3.tgz"
resolved "https://registry.npmmirror.com/react-color/-/react-color-2.19.3.tgz#ec6c6b4568312a3c6a18420ab0472e146aa5683d"
integrity sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==
dependencies:
"@icons/material" "^0.2.4"
@@ -17417,13 +17308,6 @@ rechoir@^0.6.2:
dependencies:
resolve "^1.1.6"
rechoir@^0.7.0:
version "0.7.1"
resolved "https://registry.npmmirror.com/rechoir/-/rechoir-0.7.1.tgz"
integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==
dependencies:
resolve "^1.9.0"
recursive-readdir@^2.2.2:
version "2.2.3"
resolved "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz"
@@ -17750,7 +17634,7 @@ resolve.exports@^2.0.0:
resolved "https://registry.npmmirror.com/resolve.exports/-/resolve.exports-2.0.2.tgz"
integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==
resolve@^1.1.6, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.4, resolve@^1.3.2, resolve@^1.9.0:
resolve@^1.1.6, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.4, resolve@^1.3.2:
version "1.22.4"
resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.4.tgz"
integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==
@@ -20281,24 +20165,6 @@ webpack-chain@6.5.1:
deepmerge "^1.5.2"
javascript-stringify "^2.0.1"
webpack-cli@^4.9.2:
version "4.10.0"
resolved "https://registry.npmmirror.com/webpack-cli/-/webpack-cli-4.10.0.tgz"
integrity sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==
dependencies:
"@discoveryjs/json-ext" "^0.5.0"
"@webpack-cli/configtest" "^1.2.0"
"@webpack-cli/info" "^1.5.0"
"@webpack-cli/serve" "^1.7.0"
colorette "^2.0.14"
commander "^7.0.0"
cross-spawn "^7.0.3"
fastest-levenshtein "^1.0.12"
import-local "^3.0.2"
interpret "^2.2.0"
rechoir "^0.7.0"
webpack-merge "^5.7.3"
webpack-dev-middleware@^3.7.3:
version "3.7.3"
resolved "https://registry.npmmirror.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz"
@@ -20379,7 +20245,7 @@ webpack-log@^2.0.0:
ansi-colors "^3.0.0"
uuid "^3.3.2"
webpack-merge@^5.7.3, webpack-merge@^5.8.0:
webpack-merge@^5.8.0:
version "5.9.0"
resolved "https://registry.npmmirror.com/webpack-merge/-/webpack-merge-5.9.0.tgz"
integrity sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==
@@ -20387,7 +20253,7 @@ webpack-merge@^5.7.3, webpack-merge@^5.8.0:
clone-deep "^4.0.1"
wildcard "^2.0.0"
webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3:
webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3:
version "1.4.3"
resolved "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-1.4.3.tgz"
integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==
@@ -20407,7 +20273,7 @@ webpack-virtual-modules@^0.2.2:
dependencies:
debug "^3.0.0"
webpack@4, webpack@^4.46.0:
webpack@4:
version "4.46.0"
resolved "https://registry.npmmirror.com/webpack/-/webpack-4.46.0.tgz"
integrity sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==