调整为 monorepo

This commit is contained in:
liaoxuezhi 2022-06-01 15:06:00 +08:00 committed by GitHub
parent 6c99d5fe91
commit ebb798edf6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1047 changed files with 16346 additions and 5036 deletions

4
.gitignore vendored
View File

@ -25,3 +25,7 @@ package-lock.json
/schema.json
/npm
/mock/cfc/cfc.zip
.rollup.cache
dist
tsconfig.tsbuildinfo

View File

@ -1,293 +0,0 @@
import {
registerRenderer,
unRegisterRenderer,
RendererProps
} from '../src/factory';
import '../src/themes/default';
import {render as amisRender} from '../src/index';
import React = require('react');
import {render, fireEvent, cleanup, waitFor} from '@testing-library/react';
import {wait, makeEnv} from './helper';
test('factory unregistered Renderer', async () => {
const {container, getByText} = render(
amisRender({
type: 'my-renderer',
a: 23
})
);
await waitFor(() => {
expect(getByText('Error: 找不到对应的渲染器')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
test('factory custom not found!', async () => {
const {container, getByText} = render(
amisRender(
{
type: 'my-renderer',
a: 23
},
{},
makeEnv({
loadRenderer: () => Promise.resolve(() => <div>Not Found</div>)
})
)
);
await waitFor(() => {
expect(getByText('Not Found')).toBeInTheDocument();
});
expect(container).toMatchSnapshot(); // not found
});
test('factory custom not found 2!', async () => {
const {container, getByText} = render(
amisRender(
{
type: 'my-renderer',
a: 23
},
{},
makeEnv({
loadRenderer: () => () => <div>Not Found</div>
})
)
);
await waitFor(() => {
expect(getByText('Not Found')).toBeInTheDocument();
});
expect(container).toMatchSnapshot(); // not found
});
test('factory custom not found 3!', async () => {
const {container, getByText} = render(
amisRender(
{
type: 'my-renderer',
a: 23
},
{},
makeEnv({
loadRenderer: () => <div>Not Found</div>
})
)
);
await waitFor(() => {
expect(getByText('Not Found')).toBeInTheDocument();
});
expect(container).toMatchSnapshot(); // not found
});
test('factory load Renderer on need', async () => {
const {container, getByText} = render(
amisRender(
{
type: 'my-renderer2',
a: 23
},
{},
makeEnv({
session: 'loadRenderer',
loadRenderer: schema => {
interface MyProps extends RendererProps {
a?: number;
}
class MyComponent extends React.Component<MyProps> {
render() {
return <div>This is Custom Renderer2, a is {this.props.a}</div>;
}
}
registerRenderer({
component: MyComponent,
test: /\bmy-renderer2$/
});
}
})
)
);
await waitFor(() => {
expect(getByText('This is Custom Renderer2, a is 23')).toBeInTheDocument();
});
expect(container).toMatchSnapshot(); // not found
});
test('factory:registerRenderer', () => {
interface MyProps extends RendererProps {
a?: number;
}
class MyComponent extends React.Component<MyProps> {
render() {
return <div>This is Custom Renderer, a is {this.props.a}</div>;
}
}
const renderer = registerRenderer({
component: MyComponent,
test: /\bmy-renderer$/
});
const {container} = render(
amisRender({
type: 'my-renderer',
a: 23
})
);
expect(container).toMatchSnapshot();
unRegisterRenderer(renderer);
});
test('factory:definitions', async () => {
const {container, getByText} = render(
amisRender(
{
definitions: {
aa: {
type: 'text',
name: 'jack',
value: 'ref value',
remark: '通过<code>\\$ref</code>引入的组件'
},
bb: {
type: 'combo',
multiple: true,
multiLine: true,
remark: '<code>combo</code>中的子项引入自身,实现嵌套的效果',
controls: [
{
label: 'combo 1',
type: 'text',
name: 'key'
},
{
label: 'combo 2',
name: 'value',
$ref: 'aa'
},
{
name: 'children',
label: 'children',
$ref: 'bb'
}
]
}
},
type: 'page',
title: '引用',
body: [
{
type: 'form',
api: 'api/xxx',
actions: [],
controls: [
{
label: 'text2',
$ref: 'aa',
name: 'ref1'
},
{
label: 'combo',
$ref: 'bb',
name: 'ref2'
}
]
}
]
},
{},
makeEnv({})
)
);
await waitFor(() => {
expect(getByText('新增')).toBeInTheDocument();
});
fireEvent.click(getByText('新增'));
await waitFor(() => {
expect(getByText('combo 1')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
test('factory:definitions override', async () => {
const {container, getByText} = render(
amisRender(
{
definitions: {
aa: {
type: 'text',
name: 'jack',
remark: '通过<code>\\$ref</code>引入的组件'
},
bb: {
type: 'combo',
multiple: true,
multiLine: true,
remark: '<code>combo</code>中的子项引入自身,实现嵌套的效果',
controls: [
{
label: 'combo 1',
type: 'text',
name: 'key'
},
{
label: 'combo 2',
name: 'value',
$ref: 'aa'
},
{
name: 'children',
label: 'children',
$ref: 'bb'
}
]
}
},
type: 'page',
title: '引用',
body: [
{
type: 'form',
api: 'api/xxx',
actions: [],
controls: [
{
label: 'text2',
$ref: 'aa',
name: 'ref1'
},
{
label: 'combo',
$ref: 'bb',
name: 'ref2',
type: 'checkboxes',
value: 1,
options: [
{
label: 'Option A',
value: 1
},
{
label: 'Option B',
value: 2
}
]
}
]
}
]
},
{},
makeEnv({})
)
);
await waitFor(() => {
expect(getByText('combo')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});

View File

@ -1,715 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Renderer:breadcrumb 1`] = `
<div>
<div
class="cxd-Breadcrumb"
>
<span
class="cxd-Breadcrumb-item"
>
<a
class="cxd-Breadcrumb-item-default"
href="https://baidu.gitee.com/"
>
<i
class="cxd-Icon fa fa-home cxd-Breadcrumb-icon"
/>
<span
class="cxd-TplField"
>
首页
</span>
</a>
</span>
<span
class="cxd-Breadcrumb-separator"
>
&gt;
</span>
<span
class="cxd-Breadcrumb-item"
>
<span
class="cxd-Breadcrumb-item-default"
>
<span
class="cxd-TplField"
>
上级页面
</span>
</span>
</span>
<span
class="cxd-Breadcrumb-separator"
>
&gt;
</span>
<span
class="cxd-Breadcrumb-item cxd-Breadcrumb-item-last"
>
<span
class="cxd-Breadcrumb-item-default"
>
<span
class="cxd-TplField"
>
当前页面
</span>
</span>
</span>
</div>
</div>
`;
exports[`Renderer:breadcrumb className 1`] = `
<div>
<div
class="cxd-Breadcrumb className"
>
<span
class="cxd-Breadcrumb-item"
>
<a
class="cxd-Breadcrumb-item-default itemClassName"
href="https://baidu.gitee.com/"
>
<i
class="cxd-Icon fa fa-home cxd-Breadcrumb-icon"
/>
<span
class="cxd-TplField"
>
首页
</span>
</a>
</span>
<span
class="cxd-Breadcrumb-separator"
>
&gt;
</span>
<span
class="cxd-Breadcrumb-item is-opened"
>
<span
class="cxd-Breadcrumb-item-default itemClassName"
>
<span
class="cxd-TplField"
>
上级页面
</span>
</span>
<span
class="cxd-Breadcrumb-item-caret"
>
<icon-mock
classname="icon icon-caret"
icon="caret"
/>
</span>
<ul
class="cxd-Breadcrumb-dropdown dropdownClassName"
>
<li>
<a
class="cxd-Breadcrumb-item-dropdown dropdownItemClassName"
href="https://baidu.gitee.com/"
>
<span
class="cxd-TplField"
>
选项一
</span>
</a>
</li>
<li>
<span
class="cxd-Breadcrumb-item-dropdown dropdownItemClassName"
>
<span
class="cxd-TplField"
>
选项二
</span>
</span>
</li>
</ul>
</span>
<span
class="cxd-Breadcrumb-separator"
>
&gt;
</span>
<span
class="cxd-Breadcrumb-item cxd-Breadcrumb-item-last"
>
<span
class="cxd-Breadcrumb-item-default itemClassName"
>
<span
class="cxd-TplField"
>
当前页面
</span>
</span>
</span>
</div>
</div>
`;
exports[`Renderer:breadcrumb dropdown 1`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
>
<div
class="cxd-Breadcrumb"
>
<span
class="cxd-Breadcrumb-item"
>
<a
class="cxd-Breadcrumb-item-default"
href="https://baidu.gitee.com/"
>
<i
class="cxd-Icon fa fa-home cxd-Breadcrumb-icon"
/>
<span
class="cxd-TplField"
>
首页
</span>
</a>
</span>
<span
class="cxd-Breadcrumb-separator"
>
&gt;
</span>
<span
class="cxd-Breadcrumb-item"
>
<span
class="cxd-Breadcrumb-item-default"
>
<span
class="cxd-TplField"
>
上级页面
</span>
</span>
<span
class="cxd-Breadcrumb-item-caret"
>
<icon-mock
classname="icon icon-caret"
icon="caret"
/>
</span>
</span>
<span
class="cxd-Breadcrumb-separator"
>
&gt;
</span>
<span
class="cxd-Breadcrumb-item cxd-Breadcrumb-item-last"
>
<span
class="cxd-Breadcrumb-item-default"
>
<span
class="cxd-TplField"
>
当前页面
</span>
</span>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`Renderer:breadcrumb separator 1`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
>
<div
class="cxd-Breadcrumb"
>
<span
class="cxd-Breadcrumb-item"
>
<a
class="cxd-Breadcrumb-item-default"
href="https://baidu.gitee.com/"
>
<i
class="cxd-Icon fa fa-home cxd-Breadcrumb-icon"
/>
<span
class="cxd-TplField"
>
首页
</span>
</a>
</span>
<span
class="cxd-Breadcrumb-separator text-black"
>
&gt;
</span>
<span
class="cxd-Breadcrumb-item"
>
<span
class="cxd-Breadcrumb-item-default"
>
<span
class="cxd-TplField"
>
上级页面
</span>
</span>
</span>
<span
class="cxd-Breadcrumb-separator text-black"
>
&gt;
</span>
<span
class="cxd-Breadcrumb-item cxd-Breadcrumb-item-last"
>
<span
class="cxd-Breadcrumb-item-default"
>
<span
class="cxd-TplField"
>
当前页面
</span>
</span>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`Renderer:breadcrumb tooltip labelMaxLength 1`] = `
<div
className="cxd-Page"
onClick={[Function]}
>
<div
className="cxd-Page-content"
>
<div
className="cxd-Page-main"
>
<div
className="cxd-Page-body"
>
<div
className="cxd-Breadcrumb"
>
<span
className="cxd-Breadcrumb-item"
>
<a
className="cxd-Breadcrumb-item-default"
href="https://baidu.gitee.com/"
>
<i
className="cxd-Icon fa fa-home cxd-Breadcrumb-icon"
/>
<span
className="cxd-TplField"
/>
</a>
</span>
<span
className="cxd-Breadcrumb-separator text-black"
>
&gt;
</span>
<span
className="cxd-Breadcrumb-item"
>
<a
className="cxd-Breadcrumb-item-default"
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
>
<span
className="cxd-TplField"
>
上级页面上级页面上级页面上级页面...
</span>
</a>
</span>
<span
className="cxd-Breadcrumb-separator text-black"
>
&gt;
</span>
<span
className="cxd-Breadcrumb-item cxd-Breadcrumb-item-last"
>
<span
className="cxd-Breadcrumb-item-default"
>
<span
className="cxd-TplField"
>
当前页面
</span>
</span>
</span>
</div>
</div>
</div>
</div>
</div>
`;
exports[`Renderer:breadcrumb tooltip labelMaxLength 2`] = `
<div
className="cxd-Page"
onClick={[Function]}
>
<div
className="cxd-Page-content"
>
<div
className="cxd-Page-main"
>
<div
className="cxd-Page-body"
>
<div
className="cxd-Breadcrumb"
>
<span
className="cxd-Breadcrumb-item"
>
<a
className="cxd-Breadcrumb-item-default"
href="https://baidu.gitee.com/"
>
<i
className="cxd-Icon fa fa-home cxd-Breadcrumb-icon"
/>
<span
className="cxd-TplField"
/>
</a>
</span>
<span
className="cxd-Breadcrumb-separator text-black"
>
&gt;
</span>
<span
className="cxd-Breadcrumb-item"
>
<a
className="cxd-Breadcrumb-item-default"
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
>
<span
className="cxd-TplField"
>
上级页面上级页面上级页面上级页面...
</span>
</a>
</span>
<span
className="cxd-Breadcrumb-separator text-black"
>
&gt;
</span>
<span
className="cxd-Breadcrumb-item cxd-Breadcrumb-item-last"
>
<span
className="cxd-Breadcrumb-item-default"
>
<span
className="cxd-TplField"
>
当前页面
</span>
</span>
</span>
</div>
</div>
</div>
</div>
</div>
`;
exports[`Renderer:breadcrumb tooltip labelMaxLength 3`] = `
<div
className="cxd-Page"
onClick={[Function]}
>
<div
className="cxd-Page-content"
>
<div
className="cxd-Page-main"
>
<div
className="cxd-Page-body"
>
<div
className="cxd-Breadcrumb"
>
<span
className="cxd-Breadcrumb-item"
>
<a
className="cxd-Breadcrumb-item-default"
href="https://baidu.gitee.com/"
>
<i
className="cxd-Icon fa fa-home cxd-Breadcrumb-icon"
/>
<span
className="cxd-TplField"
/>
</a>
</span>
<span
className="cxd-Breadcrumb-separator text-black"
>
&gt;
</span>
<span
className="cxd-Breadcrumb-item"
>
<a
className="cxd-Breadcrumb-item-default"
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
>
<span
className="cxd-TplField"
>
上级页面上级页面上级页面上级页面...
</span>
</a>
</span>
<span
className="cxd-Breadcrumb-separator text-black"
>
&gt;
</span>
<span
className="cxd-Breadcrumb-item cxd-Breadcrumb-item-last"
>
<span
className="cxd-Breadcrumb-item-default"
>
<span
className="cxd-TplField"
>
当前页面
</span>
</span>
</span>
</div>
</div>
</div>
</div>
</div>
`;
exports[`Renderer:breadcrumb tooltip labelMaxLength 4`] = `
<div
className="cxd-Page"
onClick={[Function]}
>
<div
className="cxd-Page-content"
>
<div
className="cxd-Page-main"
>
<div
className="cxd-Page-body"
>
<div
className="cxd-Breadcrumb"
>
<span
className="cxd-Breadcrumb-item"
>
<a
className="cxd-Breadcrumb-item-default"
href="https://baidu.gitee.com/"
>
<i
className="cxd-Icon fa fa-home cxd-Breadcrumb-icon"
/>
<span
className="cxd-TplField"
/>
</a>
</span>
<span
className="cxd-Breadcrumb-separator text-black"
>
&gt;
</span>
<span
className="cxd-Breadcrumb-item"
>
<a
className="cxd-Breadcrumb-item-default"
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
>
<span
className="cxd-TplField"
>
上级页面上级页面上级页面上级页面...
</span>
</a>
</span>
<span
className="cxd-Breadcrumb-separator text-black"
>
&gt;
</span>
<span
className="cxd-Breadcrumb-item cxd-Breadcrumb-item-last"
>
<span
className="cxd-Breadcrumb-item-default"
>
<span
className="cxd-TplField"
>
当前页面
</span>
</span>
</span>
</div>
</div>
</div>
</div>
</div>
`;
exports[`Renderer:breadcrumb var 1`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
>
<div
class="cxd-Breadcrumb"
>
<span
class="cxd-Breadcrumb-item"
>
<a
class="cxd-Breadcrumb-item-default"
href="https://baidu.gitee.com/"
>
<span
class="cxd-TplField"
>
首页
</span>
</a>
</span>
<span
class="cxd-Breadcrumb-separator"
>
&gt;
</span>
<span
class="cxd-Breadcrumb-item"
>
<span
class="cxd-Breadcrumb-item-default"
>
<span
class="cxd-TplField"
>
上级页面
</span>
</span>
</span>
<span
class="cxd-Breadcrumb-separator"
>
&gt;
</span>
<span
class="cxd-Breadcrumb-item cxd-Breadcrumb-item-last"
>
<span
class="cxd-Breadcrumb-item-default"
>
<span
class="cxd-TplField"
>
当前页面
</span>
</span>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
`;

View File

@ -1,31 +0,0 @@
#!/bin/bash
set -e
rm -rf npm
echo "Cloning"
git clone -b npm --depth=1 https://$GH_TOKEN@github.com/baidu/amis.git npm
echo "building"
sh build.sh
cp -rf lib npm
cp package.json npm
cp schema.json npm
cp -rf scss npm
cp -rf examples npm
cp -rf sdk npm
echo "pushing"
cd npm
git config user.email "liaoxuezhi@icloud.com"
git config user.name "liaoxuezhi"
git add .
git commit --allow-empty -m "npm 下一个版本"
git push --tags https://$GH_TOKEN@github.com/baidu/amis.git npm
echo "done"

View File

@ -1,20 +1,19 @@
import React from 'react';
import NotFound from '../../src/components/404';
import Layout from '../../src/components/Layout';
import AsideNav from '../../src/components/AsideNav';
import {
NotFound,
Layout,
AsideNav,
AlertComponent,
Button,
Drawer,
Spinner,
ToastComponent
} from '../../src/components/index';
import {eachTree, mapTree} from '../../src/utils/helper';
import {Icon} from '../../src/components/icons';
import '../../src/locale/en-US';
ToastComponent,
Select,
InputBox
} from 'amis';
import {eachTree, mapTree} from 'amis/lib/utils/helper';
import 'amis/lib/locale/en-US';
import {withRouter} from 'react-router';
import Select from '../../src/components/Select';
import InputBox from '../../src/components/InputBox';
import DocSearch from './DocSearch';
import Doc from './Doc';
import DocNavCN from './DocNavCN';

View File

@ -1,7 +1,7 @@
import React from 'react';
import {Switch} from 'react-router-dom';
import {flattenTree, filterTree, mapTree} from '../../src/utils/helper';
import {flattenTree, filterTree, mapTree} from 'amis/lib/utils/helper';
import {navigations2route} from './App';
import DocNavCN from './DocNavCN';

View File

@ -3,9 +3,7 @@
*/
import React from 'react';
import axios from 'axios';
import SearchBox from '../../src/components/SearchBox';
import Drawer from '../../src/components/Drawer';
import {Icon} from '../../src';
import {Icon, Drawer, SearchBox} from 'amis';
let ContextPath = '';

View File

@ -7,7 +7,7 @@ import example from './EChartsEditor/Example';
import {lazyData} from './LazyData';
import React from 'react';
import Spinner from '../../src/components/Spinner';
import Spinner from 'amis/lib/components/Spinner';
const LazyComponent = lazyData(
async () =>

View File

@ -1,7 +1,7 @@
import React from 'react';
import Editor from '../../src/editor/Editor';
import Switch from '../../src/components/Switch';
import Button from '../../src/components/Button';
import Editor from 'amis/editor/Editor';
import Switch from 'amis/components/Switch';
import Button from 'amis/components/Button';
import schema from './Form/Test';
import Portal from 'react-overlays/Portal';

View File

@ -119,7 +119,7 @@ import Tab3Schema from './Tabs/Tab3';
import TestComponent from './Test';
import {normalizeLink} from '../../src/utils/normalizeLink';
import {normalizeLink} from 'amis/utils/normalizeLink';
import {Switch} from 'react-router-dom';
import {navigations2route} from './App';

View File

@ -1,5 +1,5 @@
import React from 'react';
import {FormItem, Renderer} from '../../../src/index';
import {FormItem, Renderer} from 'amis';
@FormItem({
type: 'my-custom'

View File

@ -1,6 +1,6 @@
import React from 'react';
import TitleBar from '../../../src/components/TitleBar';
import {render} from '../../../src/index';
import TitleBar from 'amis/components/TitleBar';
import {render} from 'amis';
const Schema = {
title: 'Person',

View File

@ -2,13 +2,13 @@
import React from 'react';
import {findDOMNode} from 'react-dom';
import {createRoot} from 'react-dom/client';
import {getTheme, render} from '../../src/index';
import {getTheme, render} from 'amis';
import axios from 'axios';
import TitleBar from '../../src/components/TitleBar';
import LazyComponent from '../../src/components/LazyComponent';
import Overlay from '../../src/components/Overlay';
import PopOver from '../../src/components/PopOver';
import NestedLinks from '../../src/components/AsideNav';
import TitleBar from 'amis/lib/components/TitleBar';
import LazyComponent from 'amis/lib/components/LazyComponent';
import Overlay from 'amis/lib/components/Overlay';
import PopOver from 'amis/lib/components/PopOver';
import NestedLinks from 'amis/lib/components/AsideNav';
import classnames from 'classnames';
import {Link} from 'react-router-dom';
import Play from './Play';

View File

@ -1,16 +1,15 @@
import React from 'react';
import {toast} from '../../src/components/Toast';
import {render, makeTranslator} from '../../src/index';
import {normalizeLink} from '../../src/utils/normalizeLink';
import {isMobile} from '../../src/utils/helper';
import attachmentAdpator from '../../src/utils/attachmentAdpator';
import {alert, confirm} from '../../src/components/Alert';
import {toast, render, makeTranslator} from 'amis';
import {normalizeLink} from 'amis/lib/utils/normalizeLink';
import {isMobile} from 'amis/lib/utils/helper';
import attachmentAdpator from 'amis/lib/utils/attachmentAdpator';
import {alert, confirm} from 'amis/lib/components/Alert';
import axios from 'axios';
import JSON5 from 'json5';
import CodeEditor from '../../src/components/Editor';
import CodeEditor from 'amis/lib/components/Editor';
import copy from 'copy-to-clipboard';
import {matchPath} from 'react-router-dom';
import Drawer from '../../src/components/Drawer';
import Drawer from 'amis/lib/components/Drawer';
const DEFAULT_CONTENT = `{
"$schema": "/schemas/page.json#",

View File

@ -1,21 +1,17 @@
import React from 'react';
import {render} from '../../src/index';
import {render, toast, Button, LazyComponent, Drawer} from 'amis';
import axios from 'axios';
import Portal from 'react-overlays/Portal';
import {toast} from '../../src/components/Toast';
import {normalizeLink} from '../../src/utils/normalizeLink';
import Button from '../../src/components/Button';
import LazyComponent from '../../src/components/LazyComponent';
import {default as DrawerContainer} from '../../src/components/Drawer';
import {toast} from 'amis';
import {normalizeLink} from 'amis/lib/utils/normalizeLink';
import {withRouter} from 'react-router';
import {matchPath} from 'react-router-dom';
import copy from 'copy-to-clipboard';
import {qsparse} from '../../src/utils/helper';
import {qsparse} from 'amis/lib/utils/helper';
function loadEditor() {
return new Promise(resolve =>
require(['../../src/components/Editor'], component =>
require(['amis/lib/components/Editor'], component =>
resolve(component.default))
);
}
@ -266,7 +262,7 @@ export default function (schema, showCode, envOverrides) {
<>
<div className="schema-wrapper">
{finalShowCode !== false ? (
<DrawerContainer
<Drawer
classPrefix={ns}
size="lg"
onHide={this.close}
@ -276,7 +272,7 @@ export default function (schema, showCode, envOverrides) {
position="right"
>
{this.state.open ? this.renderCode() : null}
</DrawerContainer>
</Drawer>
) : null}
{this.renderSchema()}
</div>

View File

@ -1,6 +1,6 @@
import React from 'react';
import TitleBar from '../../../src/components/TitleBar';
import {render} from '../../../src/index';
import {TitleBar} from 'amis';
import {render} from 'amis';
export default class SdkTest extends React.Component {
state = {

View File

@ -1,5 +1,5 @@
import React from 'react';
import Button from '../../src/components/Button';
import {Button} from 'amis';
export default class TestComponent extends React.Component {
render() {

View File

@ -4,7 +4,7 @@ import {createRoot} from 'react-dom/client';
import axios from 'axios';
import {match} from 'path-to-regexp';
import copy from 'copy-to-clipboard';
import {normalizeLink} from '../src/utils/normalizeLink';
import {normalizeLink} from 'amis/lib/utils/normalizeLink';
import qs from 'qs';
import {
@ -15,13 +15,13 @@ import {
AlertComponent,
render as renderAmis,
makeTranslator
} from '../src/index';
} from 'amis';
import '../src/locale/en-US';
import 'amis/lib/locale/en-US';
import 'history';
import attachmentAdpator from '../src/utils/attachmentAdpator';
import attachmentAdpator from 'amis/lib/utils/attachmentAdpator';
import type {ToastLevel, ToastConf} from '../src/components/Toast';
import type {ToastLevel, ToastConf} from 'amis/lib/components/Toast';
export function embed(
container: string | HTMLElement,

View File

@ -46,48 +46,48 @@
document.write(
`<link rel="stylesheet" title="ang" ${
theme !== 'ang' ? 'disabled' : ''
} href="${__uri('../scss/themes/ang.scss')}" />`
} href="${__uri('amis/scss/themes/ang.scss')}" />`
);
document.write(
`<link rel="stylesheet" title="cxd" ${
theme !== 'cxd' ? 'disabled' : ''
} href="${__uri('../scss/themes/cxd.scss')}" />`
} href="${__uri('amis/scss/themes/cxd.scss')}" />`
);
document.write(
`<link rel="stylesheet" title="dark" ${
theme !== 'dark' ? 'disabled' : ''
} href="${__uri('../scss/themes/dark.scss')}" />`
} href="${__uri('amis/scss/themes/dark.scss')}" />`
);
document.write(
`<link rel="stylesheet" title="antd" ${
theme !== 'antd' ? 'disabled' : ''
} href="${__uri('../scss/themes/antd.scss')}" />`
} href="${__uri('amis/scss/themes/antd.scss')}" />`
);
} else {
document.write(
`<link rel="stylesheet" title="ang" ${
theme !== 'ang' ? 'disabled' : ''
} href="${__uri('../scss/themes/ang-ie11.scss')}" />`
} href="${__uri('amis/scss/themes/ang-ie11.scss')}" />`
);
document.write(
`<link rel="stylesheet" title="cxd" ${
theme !== 'cxd' ? 'disabled' : ''
} href="${__uri('../scss/themes/cxd-ie11.scss')}" />`
} href="${__uri('amis/scss/themes/cxd-ie11.scss')}" />`
);
document.write(
`<link rel="stylesheet" title="dark" ${
theme !== 'dark' ? 'disabled' : ''
} href="${__uri('../scss/themes/dark-ie11.scss')}" />`
} href="${__uri('amis/scss/themes/dark-ie11.scss')}" />`
);
document.write(
`<link rel="stylesheet" title="antd" ${
theme !== 'antd' ? 'disabled' : ''
} href="${__uri('../scss/themes/antd-ie11.scss')}" />`
} href="${__uri('amis/scss/themes/antd-ie11.scss')}" />`
);
}
</script>
<!--ignore-->
<link rel="stylesheet" href="../scss/helper.scss" />
<link rel="stylesheet" href="amis/scss/helper.scss" />
<!--ignore-->
</head>

View File

@ -21,8 +21,8 @@
'echarts': __moduleId('echarts'),
'zrender': __moduleId('zrender'),
'sortablejs': __moduleId('sortablejs'),
'amis': __moduleId('../src'),
'amis@@version': __moduleId('../src'),
'amis': __moduleId('amis'),
'amis@@version': __moduleId('amis'),
'amis/embed': __moduleId('./embed.tsx'),
'amis@@version/embed': __moduleId('./embed.tsx'),
'prop-types': __moduleId('prop-types'),

View File

@ -8,10 +8,10 @@ import React from 'react';
import {createRoot} from 'react-dom/client';
import axios from 'axios';
import copy from 'copy-to-clipboard';
import {toast} from '../src/components/Toast';
import '../src/locale/en-US';
import {toast} from 'amis';
import 'amis/lib/locale/en-US';
import {render as renderAmis} from '../src/index';
import {render as renderAmis} from 'amis';
class AMISComponent extends React.Component {
state = {

View File

@ -1,6 +1,6 @@
@import '../scss/mixins';
@import '../scss/functions';
@import '../scss/variables';
@import 'node_modules/amis/scss/mixins';
@import 'node_modules/amis/scss/functions';
@import 'node_modules/amis/scss/variables';
body {
background-color: #fff !important;

View File

@ -3,7 +3,7 @@
*/
const path = require('path');
const fs = require('fs');
const package = require('./package.json');
const package = require('./packages/amis/package.json');
const parserMarkdown = require('./scripts/md-parser');
const convertSCSSIE11 = require('./scripts/scss-ie11');
const parserCodeMarkdown = require('./scripts/code-md-parser');
@ -64,7 +64,6 @@ fis.set('project.files', [
'/examples/static/photo/*.png',
'/examples/static/audio/*.mp3',
'/examples/static/video/*.mp4',
'/src/**.html',
'mock/**'
]);
@ -287,123 +286,11 @@ fis.media('dev').match('/node_modules/**.js', {
packTo: '/pkg/npm.js'
});
fis.match('monaco-editor/**', {
fis.match('{monaco-editor,amis,amis-core}/**', {
packTo: null
});
if (fis.project.currentMedia() === 'publish') {
const publishEnv = fis.media('publish');
publishEnv.get('project.ignore').push('lib/**');
publishEnv.set('project.files', ['/scss/**', '/src/**']);
fis.on('compile:end', function (file) {
if (
file.subpath === '/src/index.tsx' ||
file.subpath === '/examples/mod.js'
) {
file.setContent(file.getContent().replace('@version', package.version));
}
});
publishEnv.match('/scss/(**)', {
release: '/$1',
relative: true
});
publishEnv.match('/src/(**)', {
release: '/$1',
relative: true
});
publishEnv.match('/src/**.{jsx,tsx,js,ts}', {
parser: [
// docsGennerator,
fis.plugin('typescript', {
importHelpers: true,
sourceMap: true,
experimentalDecorators: true,
esModuleInterop: true,
allowUmdGlobalAccess: true
}),
function (contents) {
return contents
.replace(
/(?:\w+\.)?\b__uri\s*\(\s*('|")(.*?)\1\s*\)/g,
function (_, quote, value) {
let str = quote + value + quote;
return (
'(function(){try {return __uri(' +
str +
')} catch(e) {return ' +
str +
'}})()'
);
}
)
.replace(/\(\d+, (tslib_\d+\.__importStar)\)/g, '$1')
.replace(
/return\s+(tslib_\d+)\.__importStar\(require\(('|")(.*?)\2\)\);/g,
function (_, tslib, quto, value) {
return `return new Promise(function(resolve){require(['${value}'], function(ret) {resolve(${tslib}.__importStar(ret));})});`;
}
);
}
],
preprocessor: null
});
publishEnv.match('*', {
deploy: fis.plugin('local-deliver', {
to: fis.get('options.d') || fis.get('options.desc') || './lib'
})
});
publishEnv.match('/src/**.{jsx,tsx,js,ts,svg}', {
isMod: false,
standard: false
});
publishEnv.match('/src/**.{jsx,tsx,js,ts}', {
postprocessor: function (content, file) {
return content
.replace(/^''/gm, '')
.replace(/\/\/# sourceMappingURL=\//g, '//# sourceMappingURL=./');
}
});
publishEnv.match('*.scss', {
postprocessor: function (content, file) {
return content.replace(
/\/\*# sourceMappingURL=\//g,
'/*# sourceMappingURL=./'
);
}
});
publishEnv.match('::package', {
postpackager: function (ret) {
Object.keys(ret.src).forEach(function (subpath) {
var file = ret.src[subpath];
if (!file.isText()) {
return;
}
var content = file.getContent();
if (subpath === '/src/components/icons.tsx') {
content = content.replace(/\.svg/g, '.js');
} else {
content = content.replace(
/@require\s+(?:\.\.\/)?node_modules\//g,
'@require '
);
}
file.setContent(content);
});
}
});
// publishEnv.unhook('node_modules');
publishEnv.hook('relative');
publishEnv.match('_*.scss', {
release: false
});
} else if (fis.project.currentMedia() === 'publish-sdk') {
if (fis.project.currentMedia() === 'publish-sdk') {
const env = fis.media('publish-sdk');
fis.on('compile:end', function (file) {

View File

@ -1,238 +1,13 @@
{
"name": "amis",
"version": "1.10.0",
"description": "一种MIS页面生成工具",
"main": "lib/index.js",
"name": "aisuda",
"workspaces": [
"packages/*"
],
"scripts": {
"test": "jest",
"coverage": "jest --coverage",
"serve": "fis3 server start --www ./public --port 8888 --no-daemon --no-browse",
"start": "concurrently --restart-tries -1 npm:serve npm:dev",
"update-snapshot": "jest --updateSnapshot",
"stop": "fis3 server stop",
"dev": "fis3 release -cwd ./public",
"publish-to-internal": "sh build.sh && sh publish.sh",
"build": "sh build.sh",
"prettier": "prettier --write '{src,scss,examples}/**/**/*.{js,jsx,ts,tsx,scss,json}'",
"deploy-gh-page": "sh ./deploy-gh-pages.sh",
"build-schemas": "ts-node -O '{\"target\":\"es6\"}' scripts/build-schemas.ts"
},
"repository": {
"type": "git",
"url": "https://github.com/baidu/amis.git"
},
"keywords": [
"react",
"amis",
"mis",
"renderer",
"json",
"schema"
],
"author": "baidu",
"license": "Apache-2.0",
"licenses": [
{
"type": "Apache-2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0"
}
],
"lint-staged": {
"{src,examples}/**/**/*.{tsx,jsx,ts}": [
"prettier --write"
]
},
"dependencies": {
"amis-formula": "^1.3.15",
"ansi-to-react": "^6.1.6",
"attr-accept": "2.2.2",
"blueimp-canvastoblob": "2.1.0",
"classnames": "2.3.1",
"codemirror": "^5.63.0",
"downshift": "6.1.7",
"echarts": "5.3.1",
"echarts-stat": "^1.2.0",
"exceljs": "^4.3.0",
"file-saver": "^2.0.2",
"froala-editor": "3.1.1",
"hls.js": "1.1.3",
"hoist-non-react-statics": "^3.3.2",
"hotkeys-js": "^3.8.7",
"immutability-helper": "^3.1.1",
"jsbarcode": "^3.11.5",
"keycode": "^2.2.1",
"lodash": "^4.17.15",
"markdown-it": "^12.0.6",
"markdown-it-html5-media": "^0.7.1",
"match-sorter": "^6.3.1",
"mobx": "^4.5.0",
"mobx-react": "^6.3.1",
"mobx-state-tree": "^3.17.3",
"moment": "^2.19.3",
"monaco-editor": "0.30.1",
"mpegts.js": "^1.6.10",
"papaparse": "^5.3.0",
"prop-types": "^15.6.1",
"punycode": "^2.1.1",
"qrcode.react": "^3.0.0",
"qs": "6.9.7",
"rc-input-number": "^7.3.4",
"rc-overflow": "^1.2.4",
"rc-progress": "^3.1.4",
"react-color": "^2.19.3",
"react-cropper": "^2.1.8",
"react-dropzone": "^11.4.2",
"react-hook-form": "7.30.0",
"react-input-range": "1.3.0",
"react-json-view": "1.21.3",
"react-overlays": "5.1.1",
"react-textarea-autosize": "8.3.3",
"react-transition-group": "4.4.2",
"react-visibility-sensor": "5.1.1",
"sortablejs": "1.14.0",
"tinymce": "^5.10.3",
"tslib": "^2.3.1",
"uncontrollable": "7.2.1",
"video-react": "0.15.0"
},
"devDependencies": {
"@fortawesome/fontawesome-free": "^6.1.1",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.0.0",
"@types/async": "^2.0.45",
"@types/codemirror": "^5.60.3",
"@types/echarts": "^4.9.2",
"@types/file-saver": "^2.0.1",
"@types/history": "^4.6.0",
"@types/hoist-non-react-statics": "^3.3.1",
"@types/jest": "^27.0.2",
"@types/json-schema": "^7.0.11",
"@types/lodash": "^4.14.175",
"@types/markdown-it": "^12.2.1",
"@types/mkdirp": "^1.0.1",
"@types/node": "^12.7.1",
"@types/papaparse": "^5.2.2",
"@types/prop-types": "^15.5.2",
"@types/qs": "^6.5.1",
"@types/react": "^17.0.39",
"@types/react-color": "^3.0.5",
"@types/react-dom": "^17.0.11",
"@types/react-onclickoutside": "^6.0.2",
"@types/react-router-dom": "^5.3.3",
"@types/react-test-renderer": "^17.0.1",
"@types/react-transition-group": "4.4.3",
"@types/sortablejs": "^1.3.32",
"@types/tinymce": "^4.5.24",
"axios": "0.25.0",
"bce-sdk-js": "^0.2.9",
"concurrently": "^7.0.0",
"copy-to-clipboard": "3.3.1",
"core-js": "^3.21.0",
"css": "3.0.0",
"fis-optimizer-terser": "^1.0.1",
"fis-parser-sass": "^1.1.1",
"fis-parser-svgr": "^1.0.0",
"fis3": "^3.4.41",
"fis3-deploy-skip-packed": "0.0.5",
"fis3-hook-commonjs": "^0.1.31",
"fis3-hook-node_modules": "^2.3.1",
"fis3-hook-relative": "^2.0.3",
"fis3-packager-deps-pack": "^0.1.2",
"fis3-parser-typescript": "^1.4.0",
"fis3-postpackager-loader": "^2.1.12",
"fis3-prepackager-stand-alone-pack": "^1.0.0",
"fis3-preprocessor-js-require-css": "^0.1.3",
"fis3-preprocessor-js-require-file": "^0.1.3",
"fs-walk": "0.0.2",
"glob": "^7.2.0",
"history": "^4.7.2",
"husky": "^7.0.4",
"jest": "^27.5.1",
"jest-canvas-mock": "^2.3.0",
"js-yaml": "^4.1.0",
"json5": "^2.2.1",
"lint-staged": "^12.3.3",
"marked": ">=4.0.12",
"mkdirp": "^1.0.4",
"moment-timezone": "^0.5.34",
"path-to-regexp": "^6.2.0",
"postcss": "^8.4.6",
"postcss-cli": "^9.1.0",
"postcss-custom-properties": "^12.1.5",
"prettier": "^2.6.1",
"pretty-quick": "^3.1.1",
"prismjs": "^1.25.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-router": "5.2.1",
"react-router-dom": "5.3.0",
"react-test-renderer": "^18.0.0",
"ts-jest": "^27.1.4",
"ts-json-schema-generator": "0.96.0",
"ts-node": "^10.5.0",
"typescript": "~4.5.5"
},
"jest": {
"testEnvironment": "jsdom",
"collectCoverageFrom": [
"src/**/*"
],
"moduleFileExtensions": [
"ts",
"tsx",
"js"
],
"transform": {
"\\.(ts|tsx)$": "ts-jest"
},
"setupFiles": [
"jest-canvas-mock"
],
"testRegex": "/.*\\.test\\.(ts|tsx|js)$",
"moduleNameMapper": {
"\\.(css|less|sass|scss)$": "<rootDir>/__mocks__/styleMock.js",
"\\.(svg)$": "<rootDir>/__mocks__/svgMock.js"
},
"setupFilesAfterEnv": [
"<rootDir>/__tests__/jest.setup.js"
],
"globals": {
"ts-jest": {
"diagnostics": false,
"tsconfig": {
"module": "commonjs",
"target": "es5",
"lib": [
"es6",
"dom",
"ES2015",
"ES2021"
],
"sourceMap": true,
"jsx": "react",
"moduleResolution": "node",
"rootDir": ".",
"importHelpers": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceRoot": ".",
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": false,
"typeRoots": [
"./node_modules/@types",
"./types"
],
"skipLibCheck": true
}
}
}
},
"peerDependencies": {
"react": ">=16.8.6",
"react-dom": ">=16.8.6"
"deploy-gh-page": "sh ./deploy-gh-pages.sh"
}
}

5
packages/amis-core/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/lib
/node_modules
/esm
/.rollup.cache
/tsconfig.tsbuildinfo

View File

@ -0,0 +1,67 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`factory custom not found 2! 1`] = `
<div>
<div>
Not Found
</div>
</div>
`;
exports[`factory custom not found 3! 1`] = `
<div>
<div>
Not Found
</div>
</div>
`;
exports[`factory custom not found! 1`] = `
<div>
<div>
Not Found
</div>
</div>
`;
exports[`factory load Renderer on need 1`] = `
<div>
<div>
This is Custom Renderer2, a is
23
</div>
</div>
`;
exports[`factory unregistered Renderer 1`] = `
<div>
<div
class="RuntimeError"
>
<p>
Error: 找不到对应的渲染器
</p>
<p>
Path:
my-renderer
</p>
<pre>
<code>
{
"type": "my-renderer",
"a": 23
}
</code>
</pre>
</div>
</div>
`;
exports[`factory:registerRenderer 1`] = `
<div>
<div>
This is Custom Renderer, a is
23
</div>
</div>
`;

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,642 @@
import moment from 'moment';
import {
resolveVariable,
resolveVariableAndFilter
} from '../src/utils/tpl-builtin';
const filters = [
{
type: 'raw1',
data: {
value: 1
},
path: '${value}',
filter: '| raw',
expectValue: 1
},
{
type: 'raw2',
data: {
value: '2'
},
path: '${value}',
filter: '| raw',
expectValue: '2'
},
{
type: 'raw3',
data: {
a: 1,
b: '2',
c: {
'1': 'first',
'2': 'second'
}
},
path: '${c.${a}}',
filter: '| raw',
expectValue: 'first'
},
{
type: 'raw4',
data: {
a: 1,
b: '2',
c: {
'1': 'first',
'2': 'second'
}
},
path: '${c.${b}}',
filter: '| raw',
expectValue: 'second'
},
{
type: 'raw5',
data: {
a: 1
},
path: '',
filter: '| raw',
expectValue: undefined
},
{
type: 'raw6',
data: {
a: 1
},
path: '$$',
filter: 'raw',
expectValue: {a: 1}
},
{
type: 'raw7',
data: {
a: 1
},
path: '$a',
filter: '| raw',
expectValue: 1
},
{
type: 'json',
data: {
value: {
a: 'a',
b: 'b'
}
},
path: '${value | json:0}',
filter: '',
expectValue: '{"a":"a","b":"b"}'
},
{
type: 'toJson',
data: {
value: '{"a":"a","b":"b"}'
},
path: '${value | toJson}',
filter: '',
expectValue: {
a: 'a',
b: 'b'
}
},
{
type: 'date',
data: {
value: 1559649981
},
path: '${value | date}',
filter: '',
expectValue: moment(1559649981, 'X').format('LLL')
},
{
type: 'number',
data: {
value: 9999
},
path: '${value| number}',
filter: '',
expectValue: '9,999'
},
{
type: 'trim',
data: {
value: ' abc '
},
path: '${value| trim}',
filter: '',
expectValue: 'abc'
},
{
type: 'percent',
data: {
value: 0.8232343
},
path: '${value| percent}',
filter: '',
expectValue: '82%'
},
// duration
{
type: 'duration1',
data: {
value: 1
},
path: '${value| duration}',
filter: '',
expectValue: '1秒'
},
{
type: 'duration2',
data: {
value: 61
},
path: '${value| duration}',
filter: '',
expectValue: '1分1秒'
},
{
type: 'duration3',
data: {
value: 233233
},
path: '${value| duration}',
filter: '',
expectValue: '2天16时47分13秒'
},
// bytes
{
type: 'bytes1',
data: {
value: 1024
},
path: '${value| bytes}',
filter: '',
expectValue: '1.02 KB'
},
{
type: 'bytes2',
data: {
value: 1024000
},
path: '${value| bytes}',
filter: '',
expectValue: '1.02 MB'
},
{
type: 'bytes3',
data: {
value: -1024
},
path: '${value| bytes}',
filter: '',
expectValue: '-1.02 KB'
},
{
type: 'bytes4',
data: {
value: 0.5
},
path: '${value| bytes}',
filter: '',
expectValue: '0.5 B'
},
// round
{
type: 'round1',
data: {
value: '啥啊'
},
path: '${value| round}',
filter: '',
expectValue: 0
},
{
type: 'round2',
data: {
value: 1.22
},
path: '${value| round:1}',
filter: '',
expectValue: '1.2'
},
{
type: 'round3',
data: {
value: 1.26
},
path: '${value| round:1}',
filter: '',
expectValue: '1.3'
},
{
type: 'truncate1',
data: {
value: 'this is a very loooooong sentence.'
},
path: '${value| truncate:10}',
filter: '',
expectValue: 'this is a ...'
},
{
type: 'truncate2',
data: {
value: 'this is a very loooooong sentence.'
},
path: '${value| truncate:null}',
filter: '',
expectValue: 'this is a very loooooong sentence.'
},
{
type: 'url_encode',
data: {
value: 'http://www.baidu.com?query=123'
},
path: '${value| url_encode}',
filter: '',
expectValue: 'http%3A%2F%2Fwww.baidu.com%3Fquery%3D123'
},
{
type: 'url_decode',
data: {
value: 'http%3A%2F%2Fwww.baidu.com%3Fquery%3D123'
},
path: '${value| url_decode:10}',
filter: '',
expectValue: 'http://www.baidu.com?query=123'
},
{
type: 'default1',
data: {
value: ''
},
path: '${value| default}',
filter: '',
expectValue: undefined
},
{
type: 'default2',
data: {
value: ''
},
path: '${value| default:-}',
filter: '',
expectValue: '-'
},
{
type: 'join',
data: {
value: ['a', 'b', 'c']
},
path: '${value| join:,}',
filter: '',
expectValue: 'a,b,c'
},
{
type: 'split',
data: {
value: 'a,b,c'
},
path: '${value| split}',
filter: '',
expectValue: ['a', 'b', 'c']
},
{
type: 'first',
data: {
value: ['a', 'b', 'c']
},
path: '${value| first}',
filter: '',
expectValue: 'a'
},
{
type: 'nth',
data: {
value: ['a', 'b', 'c']
},
path: '${value| nth:1}',
filter: '',
expectValue: 'b'
},
{
type: 'last',
data: {
value: ['a', 'b', 'c']
},
path: '${value| last}',
filter: '',
expectValue: 'c'
},
{
type: 'minus',
data: {
value: 5
},
path: '${value| minus:1}',
filter: '',
expectValue: 4
},
{
type: 'plus',
data: {
value: 5
},
path: '${value| plus:1}',
filter: '',
expectValue: 6
},
{
type: 'pick1',
data: {
value: {
a: '1',
b: '2'
}
},
path: '${value| pick:a}',
filter: '',
expectValue: '1'
},
{
type: 'pick2',
data: {
value: [
{
label: 'A',
value: 'a'
},
{
label: 'B',
value: 'b'
},
{
label: 'C',
value: 'c'
}
]
},
path: '${value| pick:value}',
filter: '',
expectValue: ['a', 'b', 'c']
},
{
type: 'pick_if_exist',
data: {
value: [
{
label: 'A',
value: 'a'
},
{
label: 'B',
value: 'b'
},
{
label: 'C',
value: 'c'
}
]
},
path: '${value| pick_if_exist:value}',
filter: '',
expectValue: ['a', 'b', 'c']
},
{
type: 'str2date',
data: {
value: '1559649981'
},
path: '${value| str2date:X:YYYY-MM-DD HH-mm-ss}',
filter: '',
expectValue: moment('1559649981', 'X').format('YYYY-MM-DD HH-mm-ss')
},
{
type: 'asArray',
data: {
value: 'a'
},
path: '${value| asArray}',
filter: '',
expectValue: ['a']
},
{
type: 'base64Encode',
data: {
value: 'I love amis'
},
path: '${value| base64Encode}',
filter: '',
expectValue: 'SSBsb3ZlIGFtaXM='
},
{
type: 'base64Decode',
data: {
value: 'SSBsb3ZlIGFtaXM='
},
path: '${value| base64Decode}',
filter: '',
expectValue: 'I love amis'
},
{
type: 'lowerCase',
data: {
value: 'AbC'
},
path: '${value| lowerCase}',
filter: '',
expectValue: 'abc'
},
{
type: 'upperCase',
data: {
value: 'aBc'
},
path: '${value| upperCase}',
filter: '',
expectValue: 'ABC'
}
];
filters.forEach(f => {
test(`compat:${f.type}`, () => {
const result = resolveVariableAndFilter(f.path, f.data, f.filter);
expect(result).toEqual(f.expectValue);
});
});
test(`compat:filter`, () => {
expect(
resolveVariableAndFilter(
'${rows | filter:engine:match:keywords}',
{
rows: [
{
engine: 'a'
},
{
engine: 'b'
},
{
engine: 'c'
}
]
},
'| raw'
)
).toMatchObject([
{
engine: 'a'
},
{
engine: 'b'
},
{
engine: 'c'
}
]);
expect(
resolveVariableAndFilter(
'${rows | filter:engine:match:keywords}',
{
keywords: 'a',
rows: [
{
engine: 'a'
},
{
engine: 'b'
},
{
engine: 'c'
}
]
},
'| raw'
)
).toMatchObject([
{
engine: 'a'
}
]);
});
test(`compat:&`, () => {
expect(
resolveVariableAndFilter(
'${& | json:0}',
{
a: 1,
b: 2
},
'| raw'
)
).toBe('{"a":1,"b":2}');
});
test(`compat:filter-default`, () => {
expect(
resolveVariableAndFilter(
'${a | default:undefined}',
{
a: 1
},
'| raw'
)
).toBe(1);
expect(
resolveVariableAndFilter(
'${a | default:undefined}',
{
a: [1, 2, 3]
},
'| raw'
)
).toMatchObject([1, 2, 3]);
expect(
resolveVariableAndFilter(
'${b | default:undefined}',
{
a: 1
},
'| raw'
)
).toBe(undefined);
expect(
resolveVariableAndFilter(
'${b | default:-}',
{
a: 1
},
'| raw'
)
).toBe('-');
expect(
resolveVariableAndFilter(
'${b | default:undefined}',
{
a: 1
},
'| raw',
() => ''
)
).toBe(undefined);
expect(
resolveVariableAndFilter(
'${b}',
{
a: 1
},
'| raw',
() => ''
)
).toBe('');
});
test(`compat:numberVariable`, () => {
expect(
resolveVariableAndFilter(
'a $1 ',
{
'1': 233
},
'| raw'
)
).toEqual('a 233 ');
expect(
resolveVariableAndFilter(
'a $1',
{
'1': 233
},
'| raw'
)
).toEqual('a 233');
});
test(`compat:test`, () => {
const result = resolveVariableAndFilter('', {}, '| raw');
expect(result).toEqual(undefined);
});
test(`compat:test2`, () => {
const data = {
'123': 123,
'123.123': 123,
'中文': 123,
'obj': {
x: 123
}
};
expect(resolveVariable('123', data)).toEqual(123);
expect(resolveVariable('123.123', data)).toEqual(123);
expect(resolveVariable('中文', data)).toEqual(123);
expect(resolveVariable('obj.x', data)).toEqual(123);
});

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,292 @@
import {
registerRenderer,
unRegisterRenderer,
RendererProps
} from '../src/factory';
import {render as amisRender} from '../src';
import React from 'react';
import {render, fireEvent, waitFor} from '@testing-library/react';
import {makeEnv} from './helper';
test('factory unregistered Renderer', async () => {
const {container, getByText} = render(
amisRender({
type: 'my-renderer',
a: 23
})
);
await waitFor(() => {
expect(getByText('Error: 找不到对应的渲染器')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
test('factory custom not found!', async () => {
const {container, getByText} = render(
amisRender(
{
type: 'my-renderer',
a: 23
},
{},
makeEnv({
loadRenderer: () => Promise.resolve(() => <div>Not Found</div>)
})
)
);
await waitFor(() => {
expect(getByText('Not Found')).toBeInTheDocument();
});
expect(container).toMatchSnapshot(); // not found
});
test('factory custom not found 2!', async () => {
const {container, getByText} = render(
amisRender(
{
type: 'my-renderer',
a: 23
},
{},
makeEnv({
loadRenderer: () => () => <div>Not Found</div>
})
)
);
await waitFor(() => {
expect(getByText('Not Found')).toBeInTheDocument();
});
expect(container).toMatchSnapshot(); // not found
});
test('factory custom not found 3!', async () => {
const {container, getByText} = render(
amisRender(
{
type: 'my-renderer',
a: 23
},
{},
makeEnv({
loadRenderer: () => <div>Not Found</div>
})
)
);
await waitFor(() => {
expect(getByText('Not Found')).toBeInTheDocument();
});
expect(container).toMatchSnapshot(); // not found
});
test('factory load Renderer on need', async () => {
const {container, getByText} = render(
amisRender(
{
type: 'my-renderer2',
a: 23
},
{},
makeEnv({
session: 'loadRenderer',
loadRenderer: schema => {
interface MyProps extends RendererProps {
a?: number;
}
class MyComponent extends React.Component<MyProps> {
render() {
return <div>This is Custom Renderer2, a is {this.props.a}</div>;
}
}
registerRenderer({
component: MyComponent,
test: /\bmy-renderer2$/
});
}
})
)
);
await waitFor(() => {
expect(getByText('This is Custom Renderer2, a is 23')).toBeInTheDocument();
});
expect(container).toMatchSnapshot(); // not found
});
test('factory:registerRenderer', () => {
interface MyProps extends RendererProps {
a?: number;
}
class MyComponent extends React.Component<MyProps> {
render() {
return <div>This is Custom Renderer, a is {this.props.a}</div>;
}
}
const renderer = registerRenderer({
component: MyComponent,
test: /\bmy-renderer$/
});
const {container} = render(
amisRender({
type: 'my-renderer',
a: 23
})
);
expect(container).toMatchSnapshot();
unRegisterRenderer(renderer);
});
// test('factory:definitions', async () => {
// const {container, getByText} = render(
// amisRender(
// {
// definitions: {
// aa: {
// type: 'text',
// name: 'jack',
// value: 'ref value',
// remark: '通过<code>\\$ref</code>引入的组件'
// },
// bb: {
// type: 'combo',
// multiple: true,
// multiLine: true,
// remark: '<code>combo</code>中的子项引入自身,实现嵌套的效果',
// controls: [
// {
// label: 'combo 1',
// type: 'text',
// name: 'key'
// },
// {
// label: 'combo 2',
// name: 'value',
// $ref: 'aa'
// },
// {
// name: 'children',
// label: 'children',
// $ref: 'bb'
// }
// ]
// }
// },
// type: 'page',
// title: '引用',
// body: [
// {
// type: 'form',
// api: 'api/xxx',
// actions: [],
// controls: [
// {
// label: 'text2',
// $ref: 'aa',
// name: 'ref1'
// },
// {
// label: 'combo',
// $ref: 'bb',
// name: 'ref2'
// }
// ]
// }
// ]
// },
// {},
// makeEnv({})
// )
// );
// await waitFor(() => {
// expect(getByText('新增')).toBeInTheDocument();
// });
// fireEvent.click(getByText('新增'));
// await waitFor(() => {
// expect(getByText('combo 1')).toBeInTheDocument();
// });
// expect(container).toMatchSnapshot();
// });
// test('factory:definitions override', async () => {
// const {container, getByText} = render(
// amisRender(
// {
// definitions: {
// aa: {
// type: 'text',
// name: 'jack',
// remark: '通过<code>\\$ref</code>引入的组件'
// },
// bb: {
// type: 'combo',
// multiple: true,
// multiLine: true,
// remark: '<code>combo</code>中的子项引入自身,实现嵌套的效果',
// controls: [
// {
// label: 'combo 1',
// type: 'text',
// name: 'key'
// },
// {
// label: 'combo 2',
// name: 'value',
// $ref: 'aa'
// },
// {
// name: 'children',
// label: 'children',
// $ref: 'bb'
// }
// ]
// }
// },
// type: 'page',
// title: '引用',
// body: [
// {
// type: 'form',
// api: 'api/xxx',
// actions: [],
// controls: [
// {
// label: 'text2',
// $ref: 'aa',
// name: 'ref1'
// },
// {
// label: 'combo',
// $ref: 'bb',
// name: 'ref2',
// type: 'checkboxes',
// value: 1,
// options: [
// {
// label: 'Option A',
// value: 1
// },
// {
// label: 'Option B',
// value: 2
// }
// ]
// }
// ]
// }
// ]
// },
// {},
// makeEnv({})
// )
// );
// await waitFor(() => {
// expect(getByText('combo')).toBeInTheDocument();
// });
// expect(container).toMatchSnapshot();
// });

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,355 @@
import {resolveVariableAndFilter} from '../src/utils/tpl-builtin';
import {evaluate} from 'amis-formula';
test(`filter:map`, () => {
expect(
resolveVariableAndFilter('${a | map: toInt}', {
a: ['123', '3434']
})
).toMatchObject([123, 3434]);
});
test(`filter:html`, () => {
expect(
resolveVariableAndFilter('${a}', {
a: '<html>'
})
).toEqual('&lt;html&gt;');
});
test(`filter:complex`, () => {
expect(
resolveVariableAndFilter('${`${a}`}', {
a: '<html>'
})
).toEqual('<html>');
expect(
resolveVariableAndFilter('${a ? a : a}', {
a: '<html>'
})
).toEqual('<html>');
expect(
resolveVariableAndFilter('${b.a}', {
a: '<html>',
b: {
a: '<br />'
}
})
).toEqual('&lt;br &#x2F;&gt;');
});
test(`filter:json`, () => {
expect(
resolveVariableAndFilter('${a | json : 0}', {
a: {a: 1}
})
).toEqual('{"a":1}');
expect(
resolveVariableAndFilter('${a | json : 2}', {
a: {a: 1}
})
).toEqual('{\n "a": 1\n}');
});
test(`filter:toJson`, () => {
expect(
resolveVariableAndFilter('${a|toJson}', {
a: '{"a":1}'
})
).toMatchObject({a: 1});
});
test(`filter:toInt`, () => {
expect(
resolveVariableAndFilter('${a|toInt}', {
a: '233'
})
).toBe(233);
});
test(`filter:toFloat`, () => {
expect(
resolveVariableAndFilter('${a|toFloat}', {
a: '233.233'
})
).toBe(233.233);
});
test(`filter:toDate`, () => {
expect(
resolveVariableAndFilter('${a|toDate:x|date: YYYY-MM-DD}', {
a: 1638028267226
})
).toBe('2021-11-27');
});
test(`filter:fromNow`, () => {
expect(
resolveVariableAndFilter('${a|toDate:x|fromNow}', {
a: Date.now() - 2 * 60 * 1000
})
).toBe('2 minutes ago');
});
test(`filter:dateModify`, () => {
expect(
resolveVariableAndFilter('${a|toDate:x|dateModify:subtract:2:m|fromNow}', {
a: Date.now()
})
).toBe('2 minutes ago');
});
test(`filter:number`, () => {
expect(
resolveVariableAndFilter('${a|number}', {
a: 1234
})
).toBe('1,234');
});
test(`filter:trim`, () => {
expect(
resolveVariableAndFilter('${a|trim}', {
a: ' ab '
})
).toBe('ab');
});
test(`filter:duration`, () => {
expect(
resolveVariableAndFilter('${a|duration}', {
a: 234343
})
).toBe('2天17时5分43秒');
});
test(`filter:bytes`, () => {
expect(
resolveVariableAndFilter('${a|bytes}', {
a: 234343
})
).toBe('234 KB');
});
test(`filter:round`, () => {
expect(
resolveVariableAndFilter('${a|round}', {
a: 23.234
})
).toBe('23.23');
});
test(`filter:truncate`, () => {
expect(
resolveVariableAndFilter('${a|truncate:5}', {
a: 'abcdefghijklmnopqrst'
})
).toBe('abcde...');
});
test(`filter:url_encode`, () => {
expect(
resolveVariableAndFilter('${a|url_encode}', {
a: '='
})
).toBe('%3D');
});
test(`filter:url_encode`, () => {
expect(
resolveVariableAndFilter('${a|url_decode}', {
a: '%3D'
})
).toBe('=');
});
test(`filter:url_encode`, () => {
expect(
resolveVariableAndFilter('${a|default:-}', {
a: ''
})
).toBe('-');
});
test(`filter:join`, () => {
expect(
resolveVariableAndFilter('${a|join:-}', {
a: [1, 2, 3]
})
).toBe('1-2-3');
});
test(`filter:split`, () => {
expect(
resolveVariableAndFilter('${a|split:-}', {
a: '1-2-3'
})
).toMatchObject(['1', '2', '3']);
});
test(`filter:sortBy`, () => {
expect(
resolveVariableAndFilter('${a|sortBy:&|join}', {
a: ['b', 'c', 'a']
})
).toBe('a,b,c');
expect(
resolveVariableAndFilter('${a|sortBy:&:numerical|join}', {
a: ['023', '20', '44']
})
).toBe('20,023,44');
});
test(`filter:objectToArray`, () => {
expect(
resolveVariableAndFilter('${a|objectToArray}', {
a: {
a: 1,
b: 2,
done: 'Done'
}
})
).toMatchObject([
{
value: 'a',
label: 1
},
{
value: 'b',
label: 2
},
{
value: 'done',
label: 'Done'
}
]);
});
test(`filter:substring`, () => {
expect(
resolveVariableAndFilter('${a|substring:0:2}', {
a: 'abcdefg'
})
).toBe('ab');
expect(
resolveVariableAndFilter('${a|substring:1:3}', {
a: 'abcdefg'
})
).toBe('bc');
});
test(`filter:variableInVariable`, () => {
expect(
resolveVariableAndFilter('${a}', {
a: 'abc$0defg'
})
).toBe('abc$0defg');
});
test('filter:isMatch', () => {
expect(
resolveVariableAndFilter('${status | isMatch:2:1|isMatch:5:1:4}', {
status: 2
})
).toBe(1);
});
test('filter:filter:isMatch', () => {
expect(
resolveVariableAndFilter('${items|filter:text:match:"ab"}', {
items: [
{
text: 'abc'
},
{
text: 'bcd'
},
{
text: 'cde'
}
]
})
).toMatchObject([
{
text: 'abc'
}
]);
});
test('evalute:conditional', () => {
expect(
evaluate(
'${a | isTrue: true : false}',
{
a: 4
},
{
defaultFilter: 'raw'
}
)
).toBe(true);
expect(
evaluate(
'${a | isTrue: b : false}',
{
a: 4,
b: 5
},
{
defaultFilter: 'raw'
}
)
).toBe(5);
expect(
evaluate(
'${a | isTrue: b : false}',
{
a: null,
b: 5
},
{
defaultFilter: 'raw'
}
)
).toBe(false);
expect(
evaluate(
'${a | isEquals: 1 : "1" |isEquals: 2 : "2" | isEquals: 3 : "3" }',
{
a: 3
},
{
defaultFilter: 'raw'
}
)
).toBe('3');
expect(
evaluate(
'${a | isEquals: 1 : "1" |isEquals: 1 : "2" | isEquals: 1 : "3" }',
{
a: 1
},
{
defaultFilter: 'raw'
}
)
).toBe('1');
expect(
evaluate(
'${a | isEquals: 1 : "1" : "12" |isEquals: 2 : "2" | isEquals: 3 : "3" }',
{
a: 2
},
{
defaultFilter: 'raw'
}
)
).toBe('12');
});

View File

@ -0,0 +1,13 @@
import { RenderOptions } from '../src/factory';
export declare function wait(duration: number, fnOrUseWaitFor?: Function | boolean): Promise<void>;
export declare function makeEnv(env?: Partial<RenderOptions>): RenderOptions;
export declare const createMockMediaMatcher: (matchesOrMapOfMatches: any) => (qs: any) => {
matches: any;
media: string;
addListener: () => void;
addEventListener: () => void;
removeEventListener: () => void;
onchange: () => void;
removeListener: () => void;
dispatchEvent: () => boolean;
};

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,24 @@
const originalWarn = console.warn.bind(console.warn);
require('@testing-library/jest-dom');
require('moment-timezone');
const moment = require('moment');
moment.tz.setDefault('Asia/Shanghai');
const cleanup = require('@testing-library/react').cleanup;
global.beforeAll(() => {
console.warn = msg => {
// warning 先关了,实在太吵。
// const str = msg.toString();
// if (
// str.includes('componentWillMount') ||
// str.includes('componentWillReceiveProps')
// ) {
// return;
// }
// originalWarn(msg);
};
});
global.afterAll(() => {
console.warn = originalWarn;
cleanup();
});

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,26 @@
import {tokenize} from '../src/utils/tpl-builtin';
test(`tokenize:null`, () => {
expect(
tokenize('abc${a}', {
a: ''
})
).toBe('abc');
expect(
tokenize('abc${a}', {
a: null
})
).toBe('abc');
expect(
tokenize('abc${a}', {
a: undefined
})
).toBe('abc');
expect(
tokenize('abc${a}', {
a: 0
})
).toBe('abc0');
});

View File

@ -0,0 +1,122 @@
{
"name": "amis-core",
"version": "1.0.0-beta.8",
"description": "amis-core",
"main": "lib/index.js",
"module": "esm/index.js",
"author": "fex",
"license": "Apache-2.0",
"devDependencies": {
"@rollup/plugin-commonjs": "^22.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.3.0",
"@rollup/plugin-typescript": "^8.3.2",
"@testing-library/jest-dom": "^5.16.4",
"@types/file-saver": "^2.0.1",
"@types/hoist-non-react-statics": "^3.3.1",
"@types/jest": "^27.0.2",
"@types/react": "^17.0.39",
"@types/react-dom": "^17.0.11",
"jest": "^27.2.1",
"moment-timezone": "^0.5.34",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"rimraf": "^3.0.2",
"rollup": "^2.73.0",
"rollup-plugin-auto-external": "^2.0.0",
"rollup-plugin-license": "^2.7.0",
"typescript": "^4.6.4"
},
"scripts": {
"build": "npm run clean-dist && NODE_ENV=production rollup -c ",
"dev": "rollup -c -w",
"test": "jest",
"coverage": "jest --coverage",
"clean-dist": "rimraf lib/* esm/*"
},
"files": [
"lib",
"esm"
],
"dependencies": {
"amis-formula": "^2.0.0-beta.0",
"classnames": "2.3.1",
"file-saver": "^2.0.2",
"hoist-non-react-statics": "^3.3.2",
"lodash": "^4.17.15",
"match-sorter": "^6.3.1",
"mobx": "^4.5.0",
"mobx-react": "^6.3.1",
"mobx-state-tree": "^3.17.3",
"moment": "^2.19.3",
"papaparse": "^5.3.0",
"qs": "6.9.7",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-json-view": "1.21.3",
"react-visibility-sensor": "5.1.1",
"tslib": "^2.3.1"
},
"jest": {
"testEnvironment": "jsdom",
"collectCoverageFrom": [
"src/**/*"
],
"moduleFileExtensions": [
"ts",
"tsx",
"js"
],
"transform": {
"\\.(ts|tsx)$": "ts-jest"
},
"setupFiles": [
"jest-canvas-mock"
],
"testRegex": "/.*\\.test\\.(ts|tsx|js)$",
"moduleNameMapper": {
"\\.(css|less|sass|scss)$": "<rootDir>/../__mocks__/styleMock.js",
"\\.(svg)$": "<rootDir>/../__mocks__/svgMock.js"
},
"setupFilesAfterEnv": [
"<rootDir>/__tests__/jest.setup.js"
],
"testPathIgnorePatterns": [
"/node_modules/",
"/.rollup.cache/"
],
"globals": {
"ts-jest": {
"diagnostics": false,
"tsconfig": {
"module": "commonjs",
"target": "es5",
"lib": [
"es6",
"dom",
"ES2015"
],
"sourceMap": true,
"jsx": "react",
"moduleResolution": "node",
"rootDir": ".",
"importHelpers": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceRoot": ".",
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": false,
"typeRoots": [
"./node_modules/@types",
"./types"
],
"skipLibCheck": true
}
}
}
}
}

View File

@ -0,0 +1,135 @@
// rollup.config.js
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import resolve from '@rollup/plugin-node-resolve';
import typescript from '@rollup/plugin-typescript';
import license from 'rollup-plugin-license';
import autoExternal from 'rollup-plugin-auto-external';
import {
name,
version,
author,
main,
module,
dependencies
} from './package.json';
import path from 'path';
const settings = {
globals: {}
};
const external = id =>
new RegExp(
`^(?:${Object.keys(dependencies)
.concat([
'entities',
'linkify-it',
'markdown-it',
'markdown-it-html5-media',
'mdurl',
'uc.micro'
])
.map(value =>
value.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/-/g, '\\x2d')
)
.join('|')})`
).test(id);
const input = './src/index.tsx';
export default [
{
input,
output: [
{
...settings,
dir: path.dirname(main),
format: 'cjs',
exports: 'named',
preserveModulesRoot: './src',
preserveModules: true // Keep directory structure and files
}
],
external,
plugins: getPlugins('cjs')
},
{
input,
output: [
{
...settings,
dir: path.dirname(module),
format: 'esm',
exports: 'named',
preserveModulesRoot: './src',
preserveModules: true // Keep directory structure and files
}
],
external,
plugins: getPlugins('esm')
}
];
function transpileDynamicImportForCJS(options) {
return {
name: 'transpile-dynamic-import-for-cjs',
renderDynamicImport({format, targetModuleId}) {
if (format !== 'cjs') {
return null;
}
return {
left: 'Promise.resolve().then(function() {return new Promise(function(fullfill) {require.ensure([',
right:
'], function(r) {fullfill(_interopDefaultLegacy(r("' +
targetModuleId +
'")))})})})'
};
}
};
}
function getPlugins(format = 'esm') {
const typeScriptOptions = {
typescript: require('typescript'),
sourceMap: false,
outputToFilesystem: true,
...(format === 'esm'
? {
compilerOptions: {
rootDir: './src',
outDir: path.dirname(module)
}
}
: {
compilerOptions: {
rootDir: './src',
outDir: path.dirname(main)
}
})
};
return [
transpileDynamicImportForCJS(),
autoExternal(),
json(),
resolve({
jsnext: true,
main: true
}),
typescript(typeScriptOptions),
commonjs({
sourceMap: false
}),
license({
banner: `
${name} v${version}
Copyright 2018<%= moment().format('YYYY') > 2018 ? '-' + moment().format('YYYY') : null %> ${author}
`
})
];
}

View File

@ -1,7 +1,5 @@
import isPlainObject from 'lodash/isPlainObject';
import React from 'react';
import Alert from './components/Alert2';
import ImageGallery from './components/ImageGallery';
import {RendererEnv} from './env';
import {RendererProps} from './factory';
import {LocaleContext, TranslateFn} from './locale';
@ -11,8 +9,7 @@ import Scoped from './Scoped';
import {IRendererStore} from './store';
import {ThemeContext} from './theme';
import {Schema, SchemaNode} from './types';
import getExprProperties from './utils/filter-schema';
import {autobind, createObject, isEmpty} from './utils/helper';
import {autobind, isEmpty} from './utils/helper';
import {RootStoreContext} from './WithRootStore';
export interface RootRenderProps {
@ -65,29 +62,28 @@ export class Root extends React.Component<RootProps> {
<RootStoreContext.Provider value={rootStore}>
<ThemeContext.Provider value={themeName}>
<LocaleContext.Provider value={this.props.locale!}>
<ImageGallery modalContainer={env.getModalContainer}>
<RootRenderer
pathPrefix={pathPrefix || ''}
schema={
isPlainObject(schema)
? {
type: 'page',
...(schema as any)
}
: schema
}
{...rest}
rootStore={rootStore}
resolveDefinitions={this.resolveDefinitions}
location={location}
data={data}
env={env}
classnames={theme.classnames}
classPrefix={theme.classPrefix}
locale={locale}
translate={translate}
/>
</ImageGallery>
<RootRenderer
pathPrefix={pathPrefix || ''}
schema={
isPlainObject(schema)
? {
type: 'page',
...(schema as any)
}
: schema
}
{...rest}
render={renderChild}
rootStore={rootStore}
resolveDefinitions={this.resolveDefinitions}
location={location}
data={data}
env={env}
classnames={theme.classnames}
classPrefix={theme.classPrefix}
locale={locale}
translate={translate}
/>
</LocaleContext.Provider>
</ThemeContext.Provider>
</RootStoreContext.Provider>
@ -130,6 +126,8 @@ export function renderChild(
if (typeofnode === 'undefined' || node === null) {
return null;
} else if (React.isValidElement(node)) {
return node;
}
let schema: Schema =

View File

@ -1,12 +1,10 @@
import {observer} from 'mobx-react';
import {getEnv} from 'mobx-state-tree';
import React from 'react';
import Alert from './components/Alert2';
import Spinner from './components/Spinner';
import {renderChild, RootProps} from './Root';
import type {RootProps} from './Root';
import {IScopedContext, ScopedContext} from './Scoped';
import {IRootStore, RootStore} from './store/root';
import {Action} from './types';
import {ActionObject} from './types';
import {bulkBindFunctions, guid, isVisible} from './utils/helper';
import {filter} from './utils/tpl';
import qs from 'qs';
@ -14,10 +12,10 @@ import pick from 'lodash/pick';
import mapValues from 'lodash/mapValues';
import {saveAs} from 'file-saver';
import {normalizeApi} from './utils/api';
import {AjaxActionSchema} from './renderers/Action';
export interface RootRendererProps extends RootProps {
location?: any;
render: (region: string, schema: any, props: any) => React.ReactNode;
}
@observer
@ -94,12 +92,12 @@ export class RootRenderer extends React.Component<RootRendererProps> {
handleAction(
e: React.UIEvent<any> | void,
action: Action,
action: ActionObject,
ctx: object,
throwErrors: boolean = false,
delegate?: IScopedContext
) {
const {env, messages, onAction} = this.props;
const {env, messages, onAction, render} = this.props;
const store = this.store;
if (
@ -164,7 +162,7 @@ export class RootRenderer extends React.Component<RootRendererProps> {
env.notify(
item.level || 'info',
item.body
? renderChild('body', item.body, {
? render('body', item.body, {
...this.props,
data: ctx
})
@ -173,7 +171,7 @@ export class RootRenderer extends React.Component<RootRendererProps> {
...action.toast,
...item,
title: item.title
? renderChild('title', item.title, {
? render('title', item.title, {
...this.props,
data: ctx
})
@ -224,7 +222,7 @@ export class RootRenderer extends React.Component<RootRendererProps> {
} else if (action.actionType === 'saveAs') {
// 使用 saveAs 实现下载
// 不支持 env除非以后将 saveAs 代码拷过来改
const api = normalizeApi((action as AjaxActionSchema).api);
const api = normalizeApi((action as any).api);
if (typeof api.url === 'string') {
let fileName = action.fileName || 'data.txt';
if (api.url.indexOf('.') !== -1) {
@ -235,7 +233,11 @@ export class RootRenderer extends React.Component<RootRendererProps> {
}
}
handleDialogConfirm(values: object[], action: Action, ...args: Array<any>) {
handleDialogConfirm(
values: object[],
action: ActionObject,
...args: Array<any>
) {
const store = this.store;
if (action.mergeData && values.length === 1 && values[0]) {
@ -259,7 +261,11 @@ export class RootRenderer extends React.Component<RootRendererProps> {
store.closeDialog(confirmed);
}
handleDrawerConfirm(values: object[], action: Action, ...args: Array<any>) {
handleDrawerConfirm(
values: object[],
action: ActionObject,
...args: Array<any>
) {
const store = this.store;
if (action.mergeData && values.length === 1 && values[0]) {
@ -302,43 +308,71 @@ export class RootRenderer extends React.Component<RootRendererProps> {
}
render() {
const {pathPrefix, schema, ...rest} = this.props;
const {pathPrefix, schema, render, ...rest} = this.props;
const store = this.store;
if (store.runtimeError) {
return (
<Alert level="danger">
<h3>{this.store.runtimeError?.toString()}</h3>
<pre>
<code>{this.store.runtimeErrorStack.componentStack}</code>
</pre>
</Alert>
return render(
'error',
{
type: 'alert',
level: 'danger'
},
{
...rest,
body: (
<>
<h3>{this.store.runtimeError?.toString()}</h3>
<pre>
<code>{this.store.runtimeErrorStack.componentStack}</code>
</pre>
</>
)
}
);
}
return (
<>
{
renderChild(pathPrefix!, schema, {
render(pathPrefix!, schema, {
...rest,
data: this.store.downStream,
onAction: this.handleAction
}) as JSX.Element
}
<Spinner size="lg" overlay key="info" show={store.loading} />
{render(
'spinner',
{
type: 'spinner'
},
{
...rest,
show: store.loading
}
)}
{store.error ? (
<Alert level="danger" showCloseButton onClose={store.clearMessage}>
{store.msg}
</Alert>
) : null}
{store.error
? render(
'error',
{
type: 'alert'
},
{
...rest,
body: store.msg,
showCloseButton: true,
onClose: store.clearMessage
}
)
: null}
{renderChild(
{render(
'dialog',
{
...((store.action as Action) &&
((store.action as Action).dialog as object)),
...((store.action as ActionObject) &&
((store.action as ActionObject).dialog as object)),
type: 'dialog'
},
{
@ -352,11 +386,11 @@ export class RootRenderer extends React.Component<RootRendererProps> {
}
)}
{renderChild(
{render(
'drawer',
{
...((store.action as Action) &&
((store.action as Action).drawer as object)),
...((store.action as ActionObject) &&
((store.action as ActionObject).drawer as object)),
type: 'drawer'
},
{

View File

@ -11,8 +11,7 @@ import {
RendererProps,
resolveRenderer
} from './factory';
import {asFormItem} from './renderers/Form/Item';
import {renderChild, renderChildren} from './Root';
import {asFormItem} from './renderers/Item';
import {ScopedContext} from './Scoped';
import {Schema, SchemaNode} from './types';
import {DebugWrapper} from './utils/debug';
@ -217,7 +216,7 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
[propName: string]: any;
} = {}
) {
let {schema: _, $path: __, env, ...rest} = this.props;
let {schema: _, $path: __, env, render, ...rest} = this.props;
let {path: $path} = this.resolveRenderer(this.props);
const omitList = defaultOmitList.concat();
@ -227,7 +226,7 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
omitList.push.apply(omitList, Component.propsList as Array<string>);
}
return renderChild(`${$path}${region ? `/${region}` : ''}`, node || '', {
return render!(`${$path}${region ? `/${region}` : ''}`, node || '', {
...omit(rest, omitList),
...subProps,
data: subProps.data || rest.data,
@ -241,7 +240,7 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
}
render(): JSX.Element | null {
let {$path: _, schema: __, rootStore, ...rest} = this.props;
let {$path: _, schema: __, rootStore, render, ...rest} = this.props;
if (__ == null) {
return null;
@ -251,7 +250,7 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
const theme = this.props.env.theme;
if (Array.isArray(schema)) {
return renderChildren($path, schema as any, rest) as JSX.Element;
return render!($path, schema as any, rest) as JSX.Element;
}
const detectData =

View File

@ -17,12 +17,12 @@ import {
findTree,
TreeItem
} from './utils/helper';
import {RendererData, Action} from './types';
import {RendererData, ActionObject} from './types';
export interface ScopedComponentType extends React.Component<RendererProps> {
focus?: () => void;
doAction?: (
action: Action,
action: ActionObject,
data: RendererData,
throwErrors?: boolean
) => void;

View File

@ -39,7 +39,7 @@ export function withRootStore<
{...(this.props as JSX.LibraryManagedAttributes<
T,
React.ComponentProps<T>
>)}
> as any)}
{...injectedProps}
/>
);

View File

@ -1,4 +1,4 @@
import {Action} from '../types';
import {ActionObject} from '../types';
import {RendererEvent} from '../utils/renderer-event';
import {
RendererAction,
@ -14,7 +14,7 @@ export interface ICustomAction extends ListenerAction {
| string
| ((
renderer: any,
doAction: (action: Action, data: Record<string, any>) => void,
doAction: (action: ActionObject, data: Record<string, any>) => void,
event: RendererEvent<any>,
action: ListenerAction
) => void); // 自定义JSactionType: custom
@ -25,7 +25,7 @@ export interface ICustomAction extends ListenerAction {
*
* @export
* @class CustomAction
* @implements {Action}
* @implements {ActionObject}
*/
export class CustomAction implements RendererAction {
async run(

View File

@ -1,8 +1,6 @@
import {FormControlProps} from '../renderers/Item';
import {createObject} from '../utils/helper';
import type {ListenerAction} from './Action';
import type {OptionsControlProps} from '../renderers/Form/Options';
import type {FormControlProps} from '../renderers/Form/Item';
import type {RendererEvent} from '../utils/renderer-event';
/**

View File

@ -20,4 +20,5 @@ import './ToastAction';
import './PageAction';
import './Decorators';
export * from './Decorators';
export * from './Action';

View File

@ -6,7 +6,6 @@
import React from 'react';
import VisibilitySensor from 'react-visibility-sensor';
import Spinner from './Spinner';
export interface LazyComponentProps {
component?: React.ReactType;
@ -28,7 +27,7 @@ export default class LazyComponent extends React.Component<
LazyComponentState
> {
static defaultProps = {
placeholder: <Spinner />,
placeholder: <span>Loading...</span>,
unMountOnHidden: false,
partialVisibility: true
};

View File

@ -4,18 +4,36 @@
import React from 'react';
import {RendererConfig} from './factory';
import {ThemeInstance} from './theme';
import {Action, Api, Payload, Schema} from './types';
import {
ActionObject,
Api,
EventTrack,
Payload,
PlainObject,
Schema,
ToastConf,
ToastLevel
} from './types';
import hoistNonReactStatic from 'hoist-non-react-statics';
import {IScopedContext} from './Scoped';
import {RendererEvent} from './utils/renderer-event';
import type {ToastLevel, ToastConf} from './components/Toast';
export interface wsObject {
url: string;
responseKey?: string;
body?: any;
}
export interface RendererEnv {
fetcher: (api: Api, data?: any, options?: object) => Promise<Payload>;
isCancel: (val: any) => boolean;
wsFetcher: (
ws: wsObject,
onMessage: (data: any) => void,
onError: (error: any) => void
) => void;
notify: (type: ToastLevel, msg: any, conf?: ToastConf) => void;
jumpTo: (to: string, action?: Action, ctx?: object) => void;
jumpTo: (to: string, action?: ActionObject, ctx?: object) => void;
alert: (msg: string) => void;
confirm: (msg: string, title?: string) => Promise<boolean>;
updateLocation: (location: any, replace?: boolean) => void;
@ -33,6 +51,8 @@ export interface RendererEnv {
* jssdk
*/
watchRouteChange?: (fn: () => void) => () => void;
// 用于跟踪用户在界面中的各种操作
tracker: (eventTrack: EventTrack, props?: PlainObject) => void;
rendererResolver?: (
path: string,
schema: Schema,
@ -72,7 +92,20 @@ export interface RendererEnv {
data: any,
broadcast?: RendererEvent<any>
) => void;
[propName: string]: any;
/**
* amis
*/
enableAMISDebug?: boolean;
/**
* URL
*/
replaceText?: {[propName: string]: any};
/**
* fangs
*/
replaceTextIgnoreKeys?: String[];
}
export const EnvContext = React.createContext<RendererEnv | void>(undefined);
@ -115,7 +148,7 @@ export function withRendererEnv<
{...(this.props as JSX.LibraryManagedAttributes<
T,
React.ComponentProps<T>
>)}
> as any)}
{...injectedProps}
/>
);

View File

@ -2,8 +2,6 @@
* @file 使
*/
import {SchemaNode, Schema} from './types';
import {RendererProps, RendererConfig, addSchemaFilter} from './factory';
import {findObjectsWithKey} from './utils/helper';
const isMobile = (window as any).matchMedia?.('(max-width: 768px)').matches
? true

View File

@ -3,39 +3,23 @@ import {RendererStore, IRendererStore, IIRendererStore} from './store/index';
import {getEnv, destroy} from 'mobx-state-tree';
import {wrapFetcher} from './utils/api';
import {normalizeLink} from './utils/normalizeLink';
import {
findIndex,
isObject,
JSONTraverse,
promisify,
qsparse,
string2regExp
} from './utils/helper';
import {findIndex, promisify, qsparse, string2regExp} from './utils/helper';
import {
fetcherResult,
SchemaNode,
Schema,
Action,
EventTrack,
PlainObject
} from './types';
import {observer} from 'mobx-react';
import Scoped from './Scoped';
import {getTheme, ThemeProps} from './theme';
import {ThemeProps} from './theme';
import find from 'lodash/find';
import Alert from './components/Alert2';
import {toast} from './components/Toast';
import {alert, confirm, setRenderSchemaFn} from './components/Alert';
import {getDefaultLocale, makeTranslator, LocaleProps} from './locale';
import ScopedRootRenderer, {RootRenderProps} from './Root';
import {LocaleProps} from './locale';
import {HocStoreFactory} from './WithStore';
import {EnvContext, RendererEnv} from './env';
import {envOverwrite} from './envOverwrite';
import {RendererEnv} from './env';
import {OnEventProps} from './utils/renderer-event';
import {enableDebug} from './utils/debug';
import type {ToastLevel, ToastConf} from './components/Toast';
import {replaceText} from './utils/replaceText';
import {Placeholder} from './renderers/Placeholder';
export interface TestFunc {
(
@ -68,7 +52,11 @@ export interface RendererBasicConfig {
}
export interface RendererProps extends ThemeProps, LocaleProps, OnEventProps {
render: (region: string, node: SchemaNode, props?: any) => JSX.Element;
render: (
region: string,
node: SchemaNode,
props?: PlainObject
) => JSX.Element;
env: RendererEnv;
$path: string; // 当前组件所在的层级信息
$schema: any; // 原始 schema 配置
@ -101,51 +89,11 @@ export interface wsObject {
body?: any;
}
export interface RenderOptions {
export interface RenderOptions
extends Partial<Omit<RendererEnv, 'fetcher' | 'theme'>> {
session?: string;
theme?: string;
fetcher?: (config: fetcherConfig) => Promise<fetcherResult>;
wsFetcher?: (
ws: wsObject,
onMessage: (data: any) => void,
onError: (error: any) => void
) => void;
isCancel?: (value: any) => boolean;
notify?: (type: ToastLevel, msg: string, conf?: ToastConf) => void;
jumpTo?: (to: string, action?: Action, ctx?: object) => void;
alert?: (msg: string) => void;
confirm?: (msg: string, title?: string) => boolean | Promise<boolean>;
rendererResolver?: (
path: string,
schema: Schema,
props: any
) => null | RendererConfig;
copy?: (contents: string, options?: any) => void;
getModalContainer?: () => HTMLElement;
loadRenderer?: (
schema: Schema,
path: string,
reRender: Function
) => Promise<React.ReactType> | React.ReactType | JSX.Element | void;
affixOffsetTop?: number;
affixOffsetBottom?: number;
richTextToken?: string;
/**
* URL
*/
replaceText?: {[propName: string]: any};
/**
* fangs
*/
replaceTextIgnoreKeys?: String[];
/**
* html xss
*/
filterHtml?: (input: string) => string;
/**
* amis
*/
enableAMISDebug?: boolean;
[propName: string]: any;
}
export interface fetcherConfig {
@ -156,7 +104,9 @@ export interface fetcherConfig {
}
const renderers: Array<RendererConfig> = [];
const rendererNames: Array<string> = [];
const renderersMap: {
[propName: string]: boolean;
} = {};
const schemaFilters: Array<RenderSchemaFilter> = [];
let anonymousIndex = 1;
@ -202,7 +152,7 @@ export function registerRenderer(config: RendererConfig): RendererConfig {
config.Renderer = config.component;
config.name = config.name || config.type || `anonymous-${anonymousIndex++}`;
if (~rendererNames.indexOf(config.name)) {
if (renderersMap[config.name]) {
throw new Error(
`The renderer with name "${config.name}" has already exists, please try another name!`
);
@ -225,22 +175,13 @@ export function registerRenderer(config: RendererConfig): RendererConfig {
item => (config.weight as number) < item.weight
);
~idx ? renderers.splice(idx, 0, config) : renderers.push(config);
rendererNames.push(config.name);
renderersMap[config.name] = config.Renderer !== Placeholder;
return config;
}
export function unRegisterRenderer(config: RendererConfig | string) {
let idx =
typeof config === 'string'
? findIndex(renderers, item => item.name === config)
: renderers.indexOf(config);
~idx && renderers.splice(idx, 1);
let idx2 =
typeof config === 'string'
? findIndex(rendererNames, item => item === config)
: rendererNames.indexOf(config.name || '');
~idx2 && rendererNames.splice(idx2, 1);
const name = (typeof config === 'string' ? config : config.name)!;
delete renderersMap[name];
// 清空渲染器定位缓存
cache = {};
@ -248,17 +189,17 @@ export function unRegisterRenderer(config: RendererConfig | string) {
export function loadRenderer(schema: Schema, path: string) {
return (
<Alert level="danger">
<div className="RuntimeError">
<p>Error: </p>
<p>Path: {path}</p>
<pre>
<code>{JSON.stringify(schema, null, 2)}</code>
</pre>
</Alert>
</div>
);
}
const defaultOptions: RenderOptions = {
export const defaultOptions: RenderOptions = {
session: 'global',
affixOffsetTop: 0,
affixOffsetBottom: 0,
@ -273,7 +214,11 @@ const defaultOptions: RenderOptions = {
return Promise.reject('fetcher is required');
},
// 使用 WebSocket 来实时获取数据
wsFetcher(ws, onMessage, onError) {
wsFetcher(
ws: wsObject,
onMessage: (data: any) => void,
onError: (error: any) => void
) {
if (ws) {
const socket = new WebSocket(ws.url);
socket.onopen = event => {
@ -318,10 +263,6 @@ const defaultOptions: RenderOptions = {
'Please implement updateLocation. see https://baidu.gitee.io/amis/docs/start/getting-started#%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97'
);
},
alert,
confirm,
notify: (type, msg, conf) =>
toast[type] ? toast[type](msg, conf) : console.warn('[Notify]', type, msg),
jumpTo: (to: string, action?: any) => {
if (to === 'goBack') {
@ -383,88 +324,9 @@ const defaultOptions: RenderOptions = {
*/
filterHtml: (input: string) => input
};
let stores: {
export const stores: {
[propName: string]: IRendererStore;
} = {};
export function render(
schema: Schema,
props: RootRenderProps = {},
options: RenderOptions = {},
pathPrefix: string = ''
): JSX.Element {
let locale = props.locale || getDefaultLocale();
// 兼容 locale 的不同写法
locale = locale.replace('_', '-');
locale = locale === 'en' ? 'en-US' : locale;
locale = locale === 'zh' ? 'zh-CN' : locale;
locale = locale === 'cn' ? 'zh-CN' : locale;
const translate = props.translate || makeTranslator(locale);
let store = stores[options.session || 'global'];
// 根据环境覆盖 schema这个要在最前面做不然就无法覆盖 validations
envOverwrite(schema, locale);
if (!store) {
options = {
...defaultOptions,
...options,
fetcher: options.fetcher
? wrapFetcher(options.fetcher, options.tracker)
: defaultOptions.fetcher,
confirm: promisify(
options.confirm || defaultOptions.confirm || window.confirm
),
locale,
translate
} as any;
if (options.enableAMISDebug) {
// 因为里面还有 render
setTimeout(() => {
enableDebug();
}, 10);
}
store = RendererStore.create({}, options);
stores[options.session || 'global'] = store;
}
(window as any).amisStore = store; // 为了方便 debug.
const env = getEnv(store);
let theme = props.theme || options.theme || 'cxd';
if (theme === 'default') {
theme = 'cxd';
}
env.theme = getTheme(theme);
if (props.locale !== undefined) {
env.translate = translate;
env.locale = locale;
}
// 默认将开启移动端原生 UI
if (typeof options.useMobileUI) {
props.useMobileUI = true;
}
replaceText(schema, env.replaceText, env.replaceTextIgnoreKeys);
return (
<EnvContext.Provider value={env}>
<ScopedRootRenderer
{...props}
schema={schema}
pathPrefix={pathPrefix}
rootStore={store}
env={env}
theme={theme}
locale={locale}
translate={translate}
/>
</EnvContext.Provider>
);
}
// 默认 env 会被缓存,所以新传入的 env 不会替换旧的。
// 除非先删了旧的,新的才会生效。
@ -576,28 +438,4 @@ export function getRendererByName(name: string) {
return find(renderers, item => item.name === name);
}
setRenderSchemaFn((controls, value, callback, scopeRef, theme) => {
return render(
{
name: 'form',
type: 'form',
wrapWithPanel: false,
mode: 'horizontal',
controls,
messages: {
validateFailed: ''
}
},
{
data: value,
onFinished: callback,
scopeRef,
theme
},
{
session: 'prompt'
}
);
});
export {RendererEnv};

View File

@ -0,0 +1,237 @@
/** @license amis v@version
*
* Copyright Baidu
*
* This source code is licensed under the Apache license found in the
* LICENSE file in the root directory of this source tree.
*/
import {
Renderer,
getRendererByName,
getRenderers,
registerRenderer,
unRegisterRenderer,
resolveRenderer,
filterSchema,
clearStoresCache,
updateEnv,
RenderOptions,
stores,
defaultOptions,
addSchemaFilter,
RendererProps
} from './factory';
import './renderers/builtin';
export * from './utils/index';
export * from './types';
export * from './store';
import * as utils from './utils/helper';
import {getEnv} from 'mobx-state-tree';
import {RegisterStore, RendererStore} from './store';
import {
setDefaultLocale,
getDefaultLocale,
makeTranslator,
register as registerLocale,
extendLocale,
localeable,
LocaleProps,
TranslateFn
} from './locale';
import Scoped, {IScopedContext, ScopedContext} from './Scoped';
import {
classnames,
getClassPrefix,
setDefaultTheme,
theme,
getTheme,
ThemeProps,
themeable,
ClassNamesFn,
makeClassnames
} from './theme';
const classPrefix = getClassPrefix();
export * from './actions';
import FormItem, {
FormBaseControl,
FormControlProps,
FormItemWrap,
registerFormItem
} from './renderers/Item';
import {
FormOptionsControl,
OptionsControl,
OptionsControlProps,
registerOptionsControl
} from './renderers/Options';
import {Schema} from './types';
import ScopedRootRenderer, {RootRenderProps} from './Root';
import {envOverwrite} from './envOverwrite';
import {wrapFetcher} from './utils/api';
import {autobind, promisify} from './utils/helper';
import {enableDebug} from './utils/debug';
import {replaceText} from './utils/replaceText';
import {EnvContext, RendererEnv} from './env';
import React from 'react';
import {
evaluate,
Evaluator,
extendsFilters,
FilterContext,
filters,
getFilters,
lexer,
parse,
registerFilter
} from 'amis-formula';
import LazyComponent from './components/LazyComponent';
import {FormHorizontal, FormRenderer} from './renderers/Form';
export {
clearStoresCache,
updateEnv,
Renderer,
RendererProps,
RendererEnv,
EnvContext,
RegisterStore,
FormItem,
FormItemWrap,
OptionsControl,
FormRenderer,
FormHorizontal,
// 其他功能类方法
utils,
getRendererByName,
registerRenderer,
unRegisterRenderer,
getRenderers,
registerFormItem,
registerOptionsControl,
resolveRenderer,
filterSchema,
Scoped,
ScopedContext,
IScopedContext,
setDefaultTheme,
theme,
themeable,
ThemeProps,
getTheme,
classPrefix,
getClassPrefix,
classnames,
makeClassnames,
// 多语言相关
getDefaultLocale,
setDefaultLocale,
registerLocale,
makeTranslator,
extendLocale,
localeable,
LocaleProps,
TranslateFn,
autobind,
ClassNamesFn,
// amis-formula 相关
parse,
lexer,
Evaluator,
FilterContext,
filters,
getFilters,
registerFilter,
extendsFilters,
evaluate,
// 其他
LazyComponent,
addSchemaFilter,
OptionsControlProps,
FormOptionsControl,
FormControlProps,
FormBaseControl
};
export function render(
schema: Schema,
props: RootRenderProps = {},
options: RenderOptions = {},
pathPrefix: string = ''
): JSX.Element {
let locale = props.locale || getDefaultLocale();
// 兼容 locale 的不同写法
locale = locale.replace('_', '-');
locale = locale === 'en' ? 'en-US' : locale;
locale = locale === 'zh' ? 'zh-CN' : locale;
locale = locale === 'cn' ? 'zh-CN' : locale;
const translate = props.translate || makeTranslator(locale);
let store = stores[options.session || 'global'];
// 根据环境覆盖 schema这个要在最前面做不然就无法覆盖 validations
envOverwrite(schema, locale);
if (!store) {
options = {
...defaultOptions,
...options,
fetcher: options.fetcher
? wrapFetcher(options.fetcher, options.tracker)
: defaultOptions.fetcher,
confirm: promisify(
options.confirm || defaultOptions.confirm || window.confirm
),
locale,
translate
} as any;
if (options.enableAMISDebug) {
// 因为里面还有 render
setTimeout(() => {
enableDebug();
}, 10);
}
store = RendererStore.create({}, options);
stores[options.session || 'global'] = store;
}
(window as any).amisStore = store; // 为了方便 debug.
const env = getEnv(store);
let theme = props.theme || options.theme || 'cxd';
if (theme === 'default') {
theme = 'cxd';
}
env.theme = getTheme(theme);
if (props.locale !== undefined) {
env.translate = translate;
env.locale = locale;
}
// 默认将开启移动端原生 UI
if (typeof options.useMobileUI) {
props.useMobileUI = true;
}
replaceText(schema, env.replaceText, env.replaceTextIgnoreKeys);
return (
<EnvContext.Provider value={env}>
<ScopedRootRenderer
{...props}
schema={schema}
pathPrefix={pathPrefix}
rootStore={store}
env={env}
theme={theme}
locale={locale}
translate={translate}
/>
</EnvContext.Provider>
);
}

View File

@ -19,6 +19,13 @@ export function register(name: string, config: LocaleConfig) {
locales[name] = config;
}
export function extendLocale(name: string, config: LocaleConfig) {
locales[name] = {
...(locales[name] || {}),
...config
};
}
const fns: {
[propName: string]: TranslateFn;
} = {};
@ -126,7 +133,7 @@ export function localeable<
{...(this.props as JSX.LibraryManagedAttributes<
T,
React.ComponentProps<T>
>)}
> as any)}
{...injectedProps}
{...refConfig}
/>

View File

@ -1,12 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Renderer, RendererProps} from '../../factory';
import {observer} from 'mobx-react';
import {FormStore, IFormStore} from '../../store/form';
import {Api, SchemaNode, Schema, Action, ApiObject, Payload} from '../../types';
import {filter, evalExpression} from '../../utils/tpl';
import cx from 'classnames';
import getExprProperties from '../../utils/filter-schema';
import {Renderer, RendererProps} from '../factory';
import {FormStore, IFormStore} from '../store/form';
import {
Api,
SchemaNode,
Schema,
ActionObject,
Payload,
ClassName
} from '../types';
import {filter, evalExpression} from '../utils/tpl';
import getExprProperties from '../utils/filter-schema';
import {
promisify,
difference,
@ -22,61 +26,30 @@ import {
qsparse,
repeatCount,
createObject
} from '../../utils/helper';
} from '../utils/helper';
import debouce from 'lodash/debounce';
import flatten from 'lodash/flatten';
import find from 'lodash/find';
import Scoped, {
ScopedContext,
IScopedContext,
ScopedComponentType
} from '../../Scoped';
import {IComboStore} from '../../store/combo';
import {dataMapping} from '../../utils/tpl-builtin';
import {isApiOutdated, isEffectiveApi} from '../../utils/api';
import Spinner from '../../components/Spinner';
import {LazyComponent} from '../../components';
import {ScopedContext, IScopedContext, ScopedComponentType} from '../Scoped';
import {IComboStore} from '../store/combo';
import {dataMapping} from '../utils/tpl-builtin';
import {isApiOutdated, isEffectiveApi} from '../utils/api';
import LazyComponent from '../components/LazyComponent';
import {isAlive} from 'mobx-state-tree';
import {asFormItem} from './Item';
import {SimpleMap} from '../../utils/SimpleMap';
import {trace} from 'mobx';
import {
BaseSchema,
SchemaApi,
SchemaClassName,
SchemaCollection,
SchemaDefaultData,
SchemaExpression,
SchemaMessage,
SchemaName,
SchemaObject,
SchemaRedirect,
SchemaReload
} from '../../Schema';
import {ActionSchema} from '../Action';
import {DialogSchemaBase} from '../Dialog';
import type {LabelAlign} from './Item';
export interface FormSchemaHorizontal {
export interface FormHorizontal {
left?: number;
right?: number;
leftFixed?: boolean | number | 'xs' | 'sm' | 'md' | 'lg';
justify?: boolean; // 两端对齐
labelAlign?: 'left' | 'right' // label对齐方式
labelAlign?: 'left' | 'right'; // label对齐方式
}
/**
* Form
*
* https://baidu.gitee.io/amis/docs/components/form/index
*/
export interface FormSchema extends BaseSchema {
/**
*
*/
type: 'form';
export interface FormSchemaBase {
/**
*
*/
@ -85,12 +58,12 @@ export interface FormSchema extends BaseSchema {
/**
*
*/
actions?: Array<ActionSchema>;
actions?: Array<any>;
/**
*
*/
body?: SchemaCollection;
body?: any;
/**
* @deprecated tabs
@ -102,7 +75,7 @@ export interface FormSchema extends BaseSchema {
*/
fieldSet?: any;
data?: SchemaDefaultData;
data?: any;
/**
*
@ -112,12 +85,12 @@ export interface FormSchema extends BaseSchema {
/**
*
*/
initApi?: SchemaApi;
initApi?: Api;
/**
* Form api,initApi不同的是 finished true
*/
initAsyncApi?: SchemaApi;
initAsyncApi?: Api;
/**
* initAsyncApi后data.finished来判断是否完成xxxdata.xxx中获取
@ -137,7 +110,7 @@ export interface FormSchema extends BaseSchema {
/**
* api sendOn
*/
initFetchOn?: SchemaExpression;
initFetchOn?: string;
/**
* initApi
@ -169,17 +142,17 @@ export interface FormSchema extends BaseSchema {
*
* https://baidu.gitee.io/amis/docs/components/form/index#%E8%A1%A8%E5%8D%95%E6%8F%90%E4%BA%A4
*/
api?: SchemaApi;
api?: Api;
/**
* Form feedback
*/
feedback?: DialogSchemaBase;
feedback?: any;
/**
* finished true
*/
asyncApi?: SchemaApi;
asyncApi?: Api;
/**
* 3 asyncApi
@ -214,7 +187,7 @@ export interface FormSchema extends BaseSchema {
/**
*
*/
horizontal?: FormSchemaHorizontal;
horizontal?: FormHorizontal;
/**
*
@ -229,14 +202,14 @@ export interface FormSchema extends BaseSchema {
*
*/
validateFailed?: string;
} & SchemaMessage;
};
name?: SchemaName;
name?: string;
/**
* panel className
*/
panelClassName?: SchemaClassName;
panelClassName?: ClassName;
/**
* id, asyncApi
@ -244,9 +217,9 @@ export interface FormSchema extends BaseSchema {
*/
primaryField?: string;
redirect?: SchemaRedirect;
redirect?: string;
reload?: SchemaReload;
reload?: string;
/**
*
@ -310,18 +283,16 @@ export interface FormSchema extends BaseSchema {
labelAlign?: LabelAlign;
}
export type FormGroup = FormSchema & {
export type FormGroup = FormSchemaBase & {
title?: string;
className?: string;
};
export type FormGroupNode = FormGroup | FormGroupArray;
export interface FormGroupArray extends Array<FormGroupNode> {}
export type FormHorizontal = FormSchemaHorizontal;
export interface FormProps
extends RendererProps,
Omit<FormSchema, 'mode' | 'className'> {
Omit<FormSchemaBase, 'mode' | 'className'> {
data: any;
store: IFormStore;
wrapperComponent: React.ElementType;
@ -945,7 +916,7 @@ export default class Form extends React.Component<FormProps, object> {
handleAction(
e: React.UIEvent<any> | void,
action: Action,
action: ActionObject,
data: object,
throwErrors: boolean = false,
delegate?: IScopedContext
@ -1234,7 +1205,7 @@ export default class Form extends React.Component<FormProps, object> {
handleDialogConfirm(
values: object[],
action: Action,
action: ActionObject,
ctx: any,
targets: Array<any>
) {
@ -1259,7 +1230,7 @@ export default class Form extends React.Component<FormProps, object> {
handleDrawerConfirm(
values: object[],
action: Action,
action: ActionObject,
ctx: any,
targets: Array<any>
) {
@ -1327,7 +1298,7 @@ export default class Form extends React.Component<FormProps, object> {
!!~['submit', 'button', 'button-group', 'reset'].indexOf(
(item as any)?.body?.[0]?.type ||
(item as any)?.body?.type ||
(item as SchemaObject).type
(item as any).type
)
))
) {
@ -1344,7 +1315,7 @@ export default class Form extends React.Component<FormProps, object> {
}
renderFormItems(
schema: Partial<FormSchema> & {
schema: Partial<FormSchemaBase> & {
controls?: Array<any>;
},
region: string = '',
@ -1409,8 +1380,10 @@ export default class Form extends React.Component<FormProps, object> {
return null;
}
const {classnames: cx} = this.props;
return (
<div className={`${ns}Form-row`}>
<div className={cx('Form-row')}>
{children.map((control, key) =>
~['hidden', 'formula'].indexOf((control as any).type) ||
(control as any).mode === 'inline' ? (
@ -1418,10 +1391,7 @@ export default class Form extends React.Component<FormProps, object> {
) : (
<div
key={key}
className={cx(
`${ns}Form-col`,
(control as Schema).columnClassName
)}
className={cx(`Form-col`, (control as Schema).columnClassName)}
>
{this.renderChild(control, '', {
...otherProps,
@ -1568,7 +1538,14 @@ export default class Form extends React.Component<FormProps, object> {
</pre>
) : null}
<Spinner show={store.loading} overlay />
{render(
'spinner',
{type: 'spinner'},
{
overlay: true,
show: store.loading
}
)}
{this.renderFormItems({
body
@ -1588,8 +1565,8 @@ export default class Form extends React.Component<FormProps, object> {
{render(
'modal',
{
...((store.action as Action) &&
((store.action as Action).dialog as object)),
...((store.action as ActionObject) &&
((store.action as ActionObject).dialog as object)),
type: 'dialog'
},
{
@ -1604,8 +1581,8 @@ export default class Form extends React.Component<FormProps, object> {
{render(
'modal',
{
...((store.action as Action) &&
((store.action as Action).drawer as object)),
...((store.action as ActionObject) &&
((store.action as ActionObject).drawer as object)),
type: 'drawer'
},
{
@ -1734,7 +1711,7 @@ export class FormRenderer extends Form {
}
doAction(
action: Action,
action: ActionObject,
data: object = this.props.store.data,
throwErrors: boolean = false
) {
@ -1743,7 +1720,7 @@ export class FormRenderer extends Form {
handleAction(
e: React.UIEvent<any> | undefined,
action: Action,
action: ActionObject,
ctx: object,
throwErrors: boolean = false,
delegate?: IScopedContext
@ -1779,7 +1756,7 @@ export class FormRenderer extends Form {
handleDialogConfirm(
values: object[],
action: Action,
action: ActionObject,
ctx: any,
targets: Array<any>
) {

View File

@ -1,14 +1,14 @@
import React from 'react';
import hoistNonReactStatic from 'hoist-non-react-statics';
import {IFormItemStore, IFormStore} from '../../store/form';
import {IMapEntries, reaction} from 'mobx';
import {IFormItemStore, IFormStore} from '../store/form';
import {reaction} from 'mobx';
import {
RendererProps,
registerRenderer,
TestFunc,
RendererConfig
} from '../../factory';
} from '../factory';
import {
anyChanged,
ucFirst,
@ -17,38 +17,25 @@ import {
isMobile,
createObject,
getVariable
} from '../../utils/helper';
} from '../utils/helper';
import {observer} from 'mobx-react';
import {FormHorizontal, FormSchema, FormSchemaHorizontal} from '.';
import {Api, Schema} from '../../types';
import {filter} from '../../utils/tpl';
import {SchemaRemark} from '../Remark';
import {FormHorizontal, FormSchemaBase} from './Form';
import {
BaseSchema,
SchemaApi,
SchemaApiObject,
SchemaClassName,
SchemaExpression,
SchemaObject,
SchemaType
} from '../../Schema';
import {HocStoreFactory} from '../../WithStore';
Api,
ApiObject,
BaseSchemaWithoutType,
ClassName,
Schema
} from '../types';
import {filter} from '../utils/tpl';
import {HocStoreFactory} from '../WithStore';
import {wrapControl} from './wrapControl';
import isEmpty from 'lodash/isEmpty';
import debounce from 'lodash/debounce';
import {isEffectiveApi} from '../../utils/api';
import {dataMapping} from '../../utils/tpl-builtin';
import {isEffectiveApi} from '../utils/api';
export type LabelAlign = 'right' | 'left';
export type FormControlSchemaAlias = SchemaObject;
export interface FormBaseControl extends Omit<BaseSchema, 'type'> {
/**
*
*/
type: SchemaType;
export interface FormBaseControl extends BaseSchemaWithoutType {
/**
*
*/
@ -67,7 +54,7 @@ export interface FormBaseControl extends Omit<BaseSchema, 'type'> {
/**
* label className
*/
labelClassName?: SchemaClassName;
labelClassName?: string;
/**
* key. a.b.c
@ -77,12 +64,12 @@ export interface FormBaseControl extends Omit<BaseSchema, 'type'> {
/**
* ,
*/
remark?: SchemaRemark;
remark?: any;
/**
* , , label
*/
labelRemark?: SchemaRemark;
labelRemark?: any;
/**
*
@ -102,7 +89,7 @@ export interface FormBaseControl extends Omit<BaseSchema, 'type'> {
/**
*
*/
readOnlyOn?: SchemaExpression;
readOnlyOn?: string;
/**
*
@ -123,7 +110,7 @@ export interface FormBaseControl extends Omit<BaseSchema, 'type'> {
/**
* className
*/
descriptionClassName?: SchemaClassName;
descriptionClassName?: ClassName;
/**
*
@ -133,7 +120,7 @@ export interface FormBaseControl extends Omit<BaseSchema, 'type'> {
/**
*
*/
horizontal?: FormSchemaHorizontal;
horizontal?: FormHorizontal;
/**
* control inline
@ -143,7 +130,7 @@ export interface FormBaseControl extends Omit<BaseSchema, 'type'> {
/**
* input className
*/
inputClassName?: SchemaClassName;
inputClassName?: ClassName;
/**
*
@ -296,7 +283,7 @@ export interface FormBaseControl extends Omit<BaseSchema, 'type'> {
/**
*
*/
validateApi?: SchemaApi;
validateApi?: Api;
}
export interface FormItemBasicConfig extends Partial<RendererConfig> {
@ -350,7 +337,7 @@ export interface FormItemProps extends RendererProps {
addHook: (fn: Function, mode?: 'validate' | 'init' | 'flush') => () => void;
removeHook: (fn: Function, mode?: 'validate' | 'init' | 'flush') => void;
renderFormItems: (
schema: Partial<FormSchema>,
schema: Partial<FormSchemaBase>,
region: string,
props: any
) => JSX.Element;
@ -473,7 +460,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
const result = await formItem?.loadAutoUpdateData(
autoFillApi,
ctx,
!!(autoFillApi as SchemaApiObject)?.silent
!!(autoFillApi as ApiObject)?.silent
);
if (!result) return;

View File

@ -2,8 +2,14 @@
* @file SelectRadiosCheckboxes
* ListButtonGroup
*/
import {Api, PlainObject, Schema, Action} from '../../types';
import {isEffectiveApi, isApiOutdated, isValidApi} from '../../utils/api';
import {
Api,
PlainObject,
ActionObject,
OptionProps,
BaseApiObject
} from '../types';
import {isEffectiveApi, isApiOutdated} from '../utils/api';
import {isAlive} from 'mobx-state-tree';
import {
anyChanged,
@ -19,7 +25,7 @@ import {
mapTree,
getTreeDepth,
flattenTree
} from '../../utils/helper';
} from '../utils/helper';
import {reaction} from 'mobx';
import {
FormControlProps,
@ -28,7 +34,7 @@ import {
detectProps as itemDetectProps,
FormBaseControl
} from './Item';
import {IFormItemStore} from '../../store/formItem';
import {IFormItemStore} from '../store/formItem';
export type OptionsControlComponent = React.ComponentType<FormControlProps>;
@ -37,23 +43,16 @@ import {
resolveVariableAndFilter,
isPureVariable,
dataMapping
} from '../../utils/tpl-builtin';
import {
Option,
OptionProps,
normalizeOptions,
optionValueCompare
} from '../../components/Select';
import {filter} from '../../utils/tpl';
} from '../utils/tpl-builtin';
import {filter} from '../utils/tpl';
import findIndex from 'lodash/findIndex';
import {
SchemaApi,
SchemaExpression,
SchemaObject,
SchemaTokenizeableString
} from '../../Schema';
import isPlainObject from 'lodash/isPlainObject';
import merge from 'lodash/merge';
import {normalizeOptions} from '../utils/normalizeOptions';
import {optionValueCompare} from '../utils/optionValueCompare';
import {Option} from '../types';
export {Option};
@ -66,7 +65,7 @@ export interface FormOptionsControl extends FormBaseControl {
/**
* API options
*/
source?: SchemaApi | SchemaTokenizeableString;
source?: BaseApiObject | string;
/**
*
@ -78,7 +77,7 @@ export interface FormOptionsControl extends FormBaseControl {
*
* @deprecated source sendOn
*/
initFetchOn?: SchemaExpression;
initFetchOn?: string;
/**
* source
@ -123,17 +122,17 @@ export interface FormOptionsControl extends FormBaseControl {
/**
* API defer: true
*/
deferApi?: SchemaApi;
deferApi?: BaseApiObject | string;
/**
*
*/
addApi?: SchemaApi;
addApi?: BaseApiObject | string;
/**
*
*/
addControls?: Array<SchemaObject>;
addControls?: Array<PlainObject>;
/**
*
@ -153,12 +152,12 @@ export interface FormOptionsControl extends FormBaseControl {
/**
* API
*/
editApi?: SchemaApi;
editApi?: BaseApiObject | string;
/**
*
*/
editControls?: Array<SchemaObject>;
editControls?: Array<PlainObject>;
/**
*
@ -168,7 +167,7 @@ export interface FormOptionsControl extends FormBaseControl {
/**
* API
*/
deleteApi?: SchemaApi;
deleteApi?: BaseApiObject | string;
/**
*
@ -179,7 +178,7 @@ export interface FormOptionsControl extends FormBaseControl {
*
*/
autoFill?: {
[propName: string]: SchemaTokenizeableString;
[propName: string]: string;
};
}
@ -471,7 +470,7 @@ export function registerOptionsControl(config: OptionsConfig) {
return !!rendererEvent?.prevented;
}
doAction(action: Action, data: object, throwErrors: boolean) {
doAction(action: ActionObject, data: object, throwErrors: boolean) {
const {resetValue, onChange} = this.props;
const actionType = action?.actionType as string;
@ -1300,55 +1299,3 @@ export function OptionsControl(config: OptionsBasicConfig) {
return renderer.component as any;
};
}
export function highlight(
text: string,
input?: string,
hlClassName: string = 'is-matched'
) {
if (!input) {
return text;
}
text = String(text);
const reg = new RegExp(input.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'), 'ig');
if (!reg.test(text)) {
return text;
}
const dom: Array<any> = [];
let start = 0;
let match = null;
reg.lastIndex = 0;
while ((match = reg.exec(text))) {
const prev = text.substring(start, match.index);
prev && dom.push(<span key={dom.length}>{prev}</span>);
match[0] &&
dom.push(
<span className={hlClassName} key={dom.length}>
{match[0]}
</span>
);
start = match.index + match[0].length;
}
const rest = text.substring(start);
rest && dom.push(<span key={dom.length}>{rest}</span>);
// const parts = text.split(reg);
// parts.forEach((text: string, index) => {
// text && dom.push(<span key={index}>{text}</span>);
// dom.push(
// <span className={hlClassName} key={`${index}-hl`}>
// {input}
// </span>
// );
// });
// dom.pop();
return dom;
}

View File

@ -0,0 +1,12 @@
import React from 'react';
import {RendererProps} from '../factory';
export class Placeholder extends React.Component<RendererProps> {
componentDidMount() {
console.warn(`Please implement this renderer(${this.props.type})`);
}
render() {
return null;
}
}

View File

@ -0,0 +1,23 @@
import {Placeholder} from './Placeholder';
import './Form.tsx';
import {registerRenderer} from '../factory';
registerRenderer({
type: 'spinner',
component: Placeholder
});
registerRenderer({
type: 'alert',
component: Placeholder
});
registerRenderer({
type: 'dialog',
component: Placeholder
});
registerRenderer({
type: 'drawer',
component: Placeholder
});

View File

@ -1,10 +1,10 @@
import React from 'react';
import {IFormStore, IFormItemStore} from '../../store/form';
import {IFormStore, IFormItemStore} from '../store/form';
import debouce from 'lodash/debounce';
import isEqual from 'lodash/isEqual';
import {RendererProps, Renderer} from '../../factory';
import {ComboStore, IComboStore, IUniqueGroup} from '../../store/combo';
import {RendererProps, Renderer} from '../factory';
import {ComboStore, IComboStore, IUniqueGroup} from '../store/combo';
import {
anyChanged,
promisify,
@ -13,24 +13,24 @@ import {
autobind,
getVariable,
createObject
} from '../../utils/helper';
} from '../utils/helper';
import {
isNeedFormula,
isExpression,
FormulaExec,
replaceExpression
} from '../../utils/formula';
import {IIRendererStore, IRendererStore} from '../../store';
import {ScopedContext, IScopedContext} from '../../Scoped';
} from '../utils/formula';
import {IIRendererStore, IRendererStore} from '../store';
import {ScopedContext, IScopedContext} from '../Scoped';
import {reaction} from 'mobx';
import {FormItemStore} from '../../store/formItem';
import {FormItemStore} from '../store/formItem';
import {isAlive} from 'mobx-state-tree';
import {observer} from 'mobx-react';
import hoistNonReactStatic from 'hoist-non-react-statics';
import {withRootStore} from '../../WithRootStore';
import {withRootStore} from '../WithRootStore';
import {FormBaseControl, FormItemWrap} from './Item';
import {Api} from '../../types';
import {TableStore} from '../../store/table';
import {Api} from '../types';
import {TableStore} from '../store/table';
export interface ControlOutterProps extends RendererProps {
formStore?: IFormStore;
@ -327,7 +327,10 @@ export function wrapControl<
// 此处需要同时考虑 defaultValue 和 value
if (model && typeof props.value !== 'undefined') {
// 渲染器中的 value 优先
if (!isEqual(props.value, prevProps.value) && !isEqual(props.value, model.tmpValue)) {
if (
!isEqual(props.value, prevProps.value) &&
!isEqual(props.value, model.tmpValue)
) {
// 外部直接传入的 value 无需执行运算器
model.changeTmpValue(props.value);
}
@ -350,7 +353,10 @@ export function wrapControl<
prevProps.defaultValue,
prevProps.data
);
if (!isEqual(curResult, prevResult) && !isEqual(curResult, model.tmpValue)) {
if (
!isEqual(curResult, prevResult) &&
!isEqual(curResult, model.tmpValue)
) {
// 识别上下文变动、自身数值变动、公式运算结果变动
model.changeTmpValue(curResult);
if (props.onChange) {
@ -369,7 +375,8 @@ export function wrapControl<
if (
// 然后才是查看关联的 name 属性值是否变化
!isEqual(props.data, prevProps.data) &&
(!model.emitedValue || isEqual(model.emitedValue, model.tmpValue))
(!model.emitedValue ||
isEqual(model.emitedValue, model.tmpValue))
) {
model.changeEmitedValue(undefined);
const prevValueByName = getVariable(props.data, model.name);
@ -377,7 +384,7 @@ export function wrapControl<
(!isEqual(valueByName, prevValueByName) ||
getVariable(props.data, model.name, false) !==
getVariable(prevProps.data, model.name, false)) &&
!isEqual(valueByName, model.tmpValue)
!isEqual(valueByName, model.tmpValue)
) {
model.changeTmpValue(valueByName);
}

View File

@ -1,6 +1,6 @@
import {Instance, SnapshotIn, types} from 'mobx-state-tree';
import {Navigation} from '../components/AsideNav';
import {RendererEnv} from '../factory';
import {NavigationObject} from '../types';
import {
createObject,
filterTree,
@ -18,7 +18,7 @@ export const AppStore = ServiceStore.named('AppStore')
offScreen: false
})
.views(self => ({
get navigations(): Array<Navigation> {
get navigations(): Array<NavigationObject> {
if (Array.isArray(self.pages)) {
return mapTree(self.pages, item => {
let visible = item.visible;

View File

@ -1,7 +1,6 @@
import {types, SnapshotIn, isAlive, onAction, Instance} from 'mobx-state-tree';
import {IIRendererStore, iRendererStore} from './iRenderer';
import {FormItemStore} from './formItem';
import {FormStore, IFormStore, IFormItemStore} from './form';
import {types, SnapshotIn, Instance} from 'mobx-state-tree';
import {iRendererStore} from './iRenderer';
import type {IFormStore, IFormItemStore} from './form';
import {getStoreById} from './manager';
export const UniqueGroup = types
@ -137,7 +136,7 @@ export const ComboStore = iRendererStore
}
function onChildStoreDispose(child: IFormStore) {
if (child.storeType === FormStore.name) {
if (child.storeType === 'FormStore') {
const idx = self.formsRef.indexOf(child.id);
if (~idx) {
self.formsRef.splice(idx, 1);

View File

@ -1,13 +1,5 @@
import {saveAs} from 'file-saver';
import {
types,
getParent,
flow,
getEnv,
getRoot,
isAlive,
Instance
} from 'mobx-state-tree';
import {types, flow, getEnv, isAlive, Instance} from 'mobx-state-tree';
import {IRendererStore} from './index';
import {ServiceStore} from './service';
import {
@ -19,7 +11,7 @@ import {
qsstringify,
getVariable
} from '../utils/helper';
import {Api, Payload, fetchOptions, Action, ApiObject} from '../types';
import {Api, Payload, fetchOptions, ActionObject, ApiObject} from '../types';
import pick from 'lodash/pick';
import {resolveVariableAndFilter} from '../utils/tpl-builtin';
import {normalizeApiResponseData} from '../utils/api';
@ -399,7 +391,7 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
perPage && (self.perPage = parseInt(perPage as string, 10));
}
function selectAction(action: Action) {
function selectAction(action: ActionObject) {
self.selectedAction = action;
}

View File

@ -1,16 +1,7 @@
import {
types,
getEnv,
flow,
getRoot,
detach,
destroy,
isAlive,
Instance
} from 'mobx-state-tree';
import {types, getEnv, flow, isAlive, Instance} from 'mobx-state-tree';
import debounce from 'lodash/debounce';
import {ServiceStore} from './service';
import {FormItemStore, IFormItemStore, SFormItemStore} from './formItem';
import type {IFormItemStore} from './formItem';
import {Api, ApiObject, fetchOptions, Payload} from '../types';
import {ServerError} from '../utils/errors';
import {
@ -20,14 +11,12 @@ import {
cloneObject,
createObject,
difference,
guid,
isEmpty,
mapObject,
keyToPath
} from '../utils/helper';
import isEqual from 'lodash/isEqual';
import flatten from 'lodash/flatten';
import {getStoreById, removeStore} from './manager';
import {filter} from '../utils/tpl';
import {normalizeApiResponseData} from '../utils/api';
@ -52,7 +41,7 @@ export const FormStore = ServiceStore.named('FormStore')
while (pool.length) {
const current = pool.shift()!;
if (current.storeType === FormItemStore.name) {
if (current.storeType === 'FormItemStore') {
formItems.push(current);
} else {
pool.push(...current.children);
@ -326,7 +315,6 @@ export const FormStore = ServiceStore.named('FormStore')
}
if (!json.ok) {
if (json.status === 422 && json.errors) {
setFormItemErrors(json.errors);
@ -359,22 +347,23 @@ export const FormStore = ServiceStore.named('FormStore')
);
if (!ret?.dispatcher?.prevented) {
self.msg &&
getEnv(self).notify(
'success',
self.msg,
json.msgTimeout !== undefined
? {
closeButton: true,
timeout: json.msgTimeout
}
: undefined
);
getEnv(self).notify(
'success',
self.msg,
json.msgTimeout !== undefined
? {
closeButton: true,
timeout: json.msgTimeout
}
: undefined
);
}
return json.data;
}
} catch (e) {
self.markSaving(false);
let ret = options && options.onFailed && options.onFailed(e.response || {});
let ret =
options && options.onFailed && options.onFailed(e.response || {});
if (ret?.then) {
ret = yield ret;
}
@ -435,7 +424,7 @@ export const FormStore = ServiceStore.named('FormStore')
}
const getItemsByPath = (key: string) => {
const paths = keyToPath(key);
const paths: Array<string> = keyToPath(key);
const len = paths.length;
return paths.reduce(
@ -486,7 +475,7 @@ export const FormStore = ServiceStore.named('FormStore')
if (dispatcher?.then) {
dispatcher = yield dispatcher;
}
if (!dispatcher?.prevented){
if (!dispatcher?.prevented) {
msg && env.notify('error', msg);
}
throw new Error(msg);

View File

@ -1,10 +1,7 @@
import {
types,
getParent,
SnapshotIn,
flow,
getRoot,
hasParent,
isAlive,
getEnv,
Instance
@ -19,27 +16,21 @@ import {isEffectiveApi} from '../utils/api';
import findIndex from 'lodash/findIndex';
import {
isArrayChildrenModified,
isObject,
createObject,
isObjectShallowModified,
findTree,
findTreeIndex,
spliceTree,
isEmpty,
getTreeAncestors,
filterTree
} from '../utils/helper';
import {flattenTree} from '../utils/helper';
import {IRendererStore} from '.';
import {normalizeOptions, optionValueCompare} from '../components/Select';
import find from 'lodash/find';
import isPlainObject from 'lodash/isPlainObject';
import {SimpleMap} from '../utils/SimpleMap';
import memoize from 'lodash/memoize';
import {TranslateFn} from '../locale';
import {StoreNode} from './node';
import {getStoreById} from './manager';
import {toast} from '../components';
import {normalizeOptions} from '../utils/normalizeOptions';
import {optionValueCompare} from '../utils/optionValueCompare';
interface IOption {
value?: string | number | null;
@ -663,7 +654,8 @@ export const FormItemStore = StoreNode.named('FormItemStore')
return json.data;
}
!silent && toast.info(self.__('FormItem.autoUpdateloadFaild'));
!silent &&
getEnv(self).notify('info', self.__('FormItem.autoUpdateloadFaild'));
return;
});

View File

@ -2,29 +2,29 @@ import {
types,
getEnv,
detach,
setLivelynessChecking,
setLivelinessChecking,
isAlive,
Instance
} from 'mobx-state-tree';
import {iRendererStore, IIRendererStore, SIRendererStore} from './iRenderer';
import {ServiceStore} from './service';
import {ComboStore} from './combo';
import {FormStore} from './form';
import {CRUDStore} from './crud';
import {TableStore} from './table';
import {TableStoreV2} from './table-v2';
import {ListStore} from './list';
import {ModalStore} from './modal';
import {IServiceStore, ServiceStore} from './service';
import {ComboStore, IComboStore} from './combo';
import {FormStore, IFormStore} from './form';
import {CRUDStore, ICRUDStore} from './crud';
import {IColumn, IRow, ITableStore, TableStore} from './table';
import {IColumnV2, IRowV2, ITableStoreV2, TableStoreV2} from './table-v2';
import {IListStore, ListStore} from './list';
import {IModalStore, ModalStore} from './modal';
import {TranslateFn} from '../locale';
import find from 'lodash/find';
import {IStoreNode} from './node';
import {FormItemStore} from './formItem';
import {IStoreNode, StoreNode} from './node';
import {FormItemStore, IFormItemStore} from './formItem';
import {addStore, getStoreById, getStores, removeStore} from './manager';
import {PaginationStore} from './pagination';
import {AppStore} from './app';
import {IPaginationStore, PaginationStore} from './pagination';
import {AppStore, IAppStore} from './app';
import {RootStore} from './root';
setLivelynessChecking(
setLivelinessChecking(
process.env.NODE_ENV === 'production' ? 'ignore' : 'error'
);
@ -121,3 +121,34 @@ export {iRendererStore, IIRendererStore};
export const RegisterStore = function (store: any) {
allowedStoreList.push(store as any);
};
export {
ServiceStore,
IServiceStore,
FormStore,
IFormStore,
ComboStore,
IComboStore,
CRUDStore,
ICRUDStore,
TableStore,
IColumn,
IRow,
ITableStore,
TableStoreV2,
ITableStoreV2,
IColumnV2,
IRowV2,
ListStore,
IListStore,
ModalStore,
IModalStore,
FormItemStore,
IFormItemStore,
PaginationStore,
IPaginationStore,
AppStore,
IAppStore,
StoreNode,
IStoreNode
};

View File

@ -52,7 +52,7 @@ export const Column = types
.actions(self => ({
toggleToggle() {
self.toggled = !self.toggled;
const table = getParent(self, 2) as ITableStore;
const table = getParent(self, 2) as ITableStoreV2;
if (!table.activeToggaleColumns.length) {
self.toggled = true;
@ -65,8 +65,8 @@ export const Column = types
}
}));
export type IColumn = Instance<typeof Column>;
export type SColumn = SnapshotIn<typeof Column>;
export type IColumnV2 = Instance<typeof Column>;
export type SColumnV2 = SnapshotIn<typeof Column>;
export const Row = types
.model('Row', {
@ -87,8 +87,8 @@ export const Row = types
})
.views(self => ({
get checked(): boolean {
return (getParent(self, self.depth * 2) as ITableStore).isSelected(
self as IRow
return (getParent(self, self.depth * 2) as ITableStoreV2).isSelected(
self as IRowV2
);
},
@ -112,9 +112,9 @@ export const Row = types
children = self.children.map(item => item.locals);
}
const parent = getParent(self, 2) as ITableStore;
const parent = getParent(self, 2) as ITableStoreV2;
return createObject(
extendObject((getParent(self, self.depth * 2) as ITableStore).data, {
extendObject((getParent(self, self.depth * 2) as ITableStoreV2).data, {
index: self.index,
// todo 以后再支持多层,目前先一层
parent: parent.storeType === Row.name ? parent.data : undefined
@ -188,8 +188,8 @@ export const Row = types
}
}));
export type IRow = Instance<typeof Row>;
export type SRow = SnapshotIn<typeof Row>;
export type IRowV2 = Instance<typeof Row>;
export type SRowV2 = SnapshotIn<typeof Row>;
export const TableStoreV2 = ServiceStore.named('TableStoreV2')
.props({
@ -235,7 +235,7 @@ export const TableStoreV2 = ServiceStore.named('TableStoreV2')
return getToggableColumns().filter(item => item.toggled);
}
function getAllFilteredColumns(columns?: Array<SColumn>): Array<any> {
function getAllFilteredColumns(columns?: Array<SColumnV2>): Array<any> {
if (columns) {
return columns
.filter(
@ -263,7 +263,9 @@ export const TableStoreV2 = ServiceStore.named('TableStoreV2')
}
function getUnSelectedRows() {
return flattenTree<IRow>(self.rows).filter((item: IRow) => !item.checked);
return flattenTree<IRowV2>(self.rows).filter(
(item: IRowV2) => !item.checked
);
}
function getData(superData: any): any {
@ -274,7 +276,7 @@ export const TableStoreV2 = ServiceStore.named('TableStoreV2')
});
}
function getRowByIndex(rowIndex: number, levels?: Array<string>): IRow {
function getRowByIndex(rowIndex: number, levels?: Array<string>): IRowV2 {
if (levels && levels.length > 0) {
const index = +(levels.shift() || 0);
return getRowByIndex(index, levels);
@ -282,12 +284,12 @@ export const TableStoreV2 = ServiceStore.named('TableStoreV2')
return self.rows[rowIndex];
}
function isSelected(row: IRow): boolean {
function isSelected(row: IRowV2): boolean {
return !!~self.selectedRows.indexOf(row);
}
function getMovedRows() {
return flattenTree(self.rows).filter((item: IRow) => item.moved);
return flattenTree(self.rows).filter((item: IRowV2) => item.moved);
}
function getMoved() {
@ -352,13 +354,13 @@ export const TableStoreV2 = ServiceStore.named('TableStoreV2')
get movedRows() {
return getMovedRows();
},
}
};
})
.actions(self => {
function updateColumns(columns: Array<SColumn>) {
function updateColumns(columns: Array<SColumnV2>) {
if (columns && Array.isArray(columns)) {
let cols: Array<SColumn> = columns.filter(column => column).concat();
let cols: Array<SColumnV2> = columns.filter(column => column).concat();
cols = cols.map((item, index) => ({
...item,
@ -375,7 +377,7 @@ export const TableStoreV2 = ServiceStore.named('TableStoreV2')
return;
}
function update(config: Partial<STableStore>) {
function update(config: Partial<STableStoreV2>) {
config.columnsTogglable !== void 0 &&
(self.columnsTogglable = config.columnsTogglable);
@ -391,11 +393,11 @@ export const TableStoreV2 = ServiceStore.named('TableStoreV2')
}
}
function exchange(fromIndex: number, toIndex: number, item?: IRow) {
function exchange(fromIndex: number, toIndex: number, item?: IRowV2) {
item = item || self.rows[fromIndex];
if (item.parentId) {
const parent: IRow = self.getRowById(item.parentId) as any;
const parent: IRowV2 = self.getRowById(item.parentId) as any;
const offset = parent.children.indexOf(item) - fromIndex;
toIndex += offset;
fromIndex += offset;
@ -512,7 +514,7 @@ export const TableStoreV2 = ServiceStore.named('TableStoreV2')
}
// 尽可能的复用 row
function replaceRow(arr: Array<SRow>, reUseRow?: boolean) {
function replaceRow(arr: Array<SRowV2>, reUseRow?: boolean) {
if (reUseRow === false) {
self.rows.replace(arr.map(item => Row.create(item)));
return;
@ -588,7 +590,7 @@ export const TableStoreV2 = ServiceStore.named('TableStoreV2')
const key = keyField || 'children';
let arr: Array<SRow> = rows.map((item, index) => {
let arr: Array<SRowV2> = rows.map((item, index) => {
let id = getEntryId ? getEntryId(item, index) : guid();
return {
@ -736,5 +738,5 @@ export const TableStoreV2 = ServiceStore.named('TableStoreV2')
};
});
export type ITableStore = Instance<typeof TableStoreV2>;
export type STableStore = SnapshotIn<typeof TableStoreV2>;
export type ITableStoreV2 = Instance<typeof TableStoreV2>;
export type STableStoreV2 = SnapshotIn<typeof TableStoreV2>;

View File

@ -2,9 +2,6 @@ import {
types,
getParent,
SnapshotIn,
flow,
getEnv,
getRoot,
IAnyModelType,
isAlive,
Instance
@ -26,15 +23,12 @@ import {
difference,
immutableExtends,
extendObject,
hasVisibleExpression,
filterTree
hasVisibleExpression
} from '../utils/helper';
import {evalExpression} from '../utils/tpl';
import {IFormStore} from './form';
import {getStoreById} from './manager';
import type {SchemaObject} from '../Schema';
/**
* '__checkme' | '__dragme' | '__expandme'
*/

View File

@ -35,11 +35,13 @@ interface ThemeConfig {
const themes: {
[propName: string]: ThemeConfig;
} = {
default: {}
default: {},
cxd: {}
};
export function theme(name: string, config: Partial<ThemeConfig>) {
themes[name] = {
...themes[name],
...config
};
}

View File

@ -0,0 +1,586 @@
// https://json-schema.org/draft-07/json-schema-release-notes.html
import type {JSONSchema7} from 'json-schema';
import {ListenerAction} from './actions/Action';
export interface Option {
/**
*
*/
label?: string;
/**
* Option
*
*
*/
scopeLabel?: string;
/**
*
*/
value?: any;
/**
*
*/
disabled?: boolean;
/**
*
*/
children?: Options;
/**
*
*/
visible?: boolean;
/**
* visible
*
* @deprecated visible
*/
hidden?: boolean;
/**
*
*/
description?: string;
/**
*
*/
defer?: boolean;
/**
* source
*/
deferApi?: BaseApiObject | string;
/**
* defer true
*/
loading?: boolean;
/**
* defer
*/
loaded?: boolean;
[propName: string]: any;
}
export interface Options extends Array<Option> {}
export type OptionValue = string | number | null | undefined | Option;
export interface BaseApiObject {
/**
* API
*/
method?: 'get' | 'post' | 'put' | 'delete' | 'patch' | 'jsonp';
/**
* API
*/
url: string;
/**
* . key `&` `$$` , data . $$ key . $ , key .
*/
data?: {
[propName: string]: any;
};
/**
* key如果带点
*
* {
* 'a.b': '123'
* }
*
*
* {
* a: {
* b: '123
* }
* }
*
* convertKeyToPath false
*/
convertKeyToPath?: boolean;
/**
*
*/
responseData?: {
[propName: string]: any;
};
/**
* method get data
* data query
*
* body false
*
* method-override
*/
attachDataToQuery?: boolean;
/**
*
*/
dataType?: 'json' | 'form-data' | 'form';
/**
*
*/
responseType?: 'blob';
/**
* headers data
*/
headers?: {
[propName: string]: string | number;
};
/**
*
*/
sendOn?: string;
/**
* true
*/
replaceData?: boolean;
/**
* url
*
* @default true
*/
autoRefresh?: boolean;
/**
* api url
* url traceExpression
*/
trackExpression?: string;
/**
* ms
*/
cache?: number;
/**
* query api crud
* query
*
*/
forceAppendDataToQuery?: boolean;
/**
* qs
*/
qsOptions?: {
arrayFormat?: 'indices' | 'brackets' | 'repeat' | 'comma';
indices?: boolean;
allowDots?: boolean;
};
/**
* autoFillApi
*/
silent?: boolean;
}
export type ClassName =
| string
| {
[propName: string]: boolean | undefined | null | string;
};
export interface ApiObject extends BaseApiObject {
config?: {
withCredentials?: boolean;
cancelExecutor?: (cancel: Function) => void;
};
graphql?: string;
operationName?: string;
body?: PlainObject;
query?: PlainObject;
adaptor?: (payload: object, response: fetcherResult, api: ApiObject) => any;
requestAdaptor?: (api: ApiObject) => ApiObject;
}
export type ApiString = string;
export type Api = ApiString | ApiObject;
export interface fetcherResult {
data?: {
data: object;
status: number;
msg: string;
msgTimeout?: number;
errors?: {
[propName: string]: string;
};
type?: string;
[propName: string]: any; // 为了兼容其他返回格式
};
status: number;
headers: object;
}
export interface fetchOptions {
method?: 'get' | 'post' | 'put' | 'patch' | 'delete' | 'jsonp';
successMessage?: string;
errorMessage?: string;
autoAppend?: boolean;
beforeSend?: (data: any) => any;
onSuccess?: (json: Payload) => any;
onFailed?: (json: Payload) => any;
silent?: boolean;
[propName: string]: any;
}
export interface Payload {
ok: boolean;
msg: string;
msgTimeout?: number;
data: any;
status: number;
errors?: {
[propName: string]: string;
};
}
export interface Schema {
type: string;
detectField?: string;
visibleOn?: string;
hiddenOn?: string;
disabledOn?: string;
visible?: boolean;
hidden?: boolean;
disabled?: boolean;
children?: JSX.Element | ((props: any, schema?: any) => JSX.Element) | null;
definitions?: Definitions;
[propName: string]: any;
}
export interface ButtonObject {
type: 'submit' | 'button' | 'reset';
label?: string;
icon?: string;
size?: string;
disabled?: boolean;
className?: string;
}
export type SchemaNode = Schema | string | Array<Schema | string>;
export interface SchemaArray extends Array<SchemaNode> {}
export interface Definitions {
[propName: string]: SchemaNode;
}
export interface ActionObject extends ButtonObject {
actionType?:
| 'submit'
| 'copy'
| 'reload'
| 'ajax'
| 'saveAs'
| 'dialog'
| 'drawer'
| 'jump'
| 'link'
| 'url'
| 'email'
| 'close'
| 'confirm'
| 'add'
| 'remove'
| 'delete'
| 'edit'
| 'cancel'
| 'next'
| 'prev'
| 'reset'
| 'validate'
| 'reset-and-submit'
| 'clear'
| 'clear-and-submit'
| 'toast'
| 'goto-step'
| 'goto-image'
| 'expand'
| 'collapse'
| 'step-submit'
| 'selectAll'
| 'changeTabKey';
api?: BaseApiObject | string;
asyncApi?: BaseApiObject | string;
payload?: any;
dialog?: SchemaNode;
to?: string;
target?: string;
link?: string;
url?: string;
cc?: string;
bcc?: string;
subject?: string;
body?: string;
mergeData?: boolean;
reload?: string;
messages?: {
success?: string;
failed?: string;
};
feedback?: any;
required?: Array<string>;
[propName: string]: any;
}
export interface Location {
pathname: string;
search: string;
state: any;
hash: string;
key?: string;
query?: any;
}
export interface PlainObject {
[propsName: string]: any;
}
export interface RendererData {
[propsName: string]: any;
__prev?: RendererDataAlias;
__super?: RendererData;
}
type RendererDataAlias = RendererData;
export type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
// 先只支持 JSONSchema draft07 好了
export type JSONSchema = JSONSchema7;
// export type Omit<T, K extends keyof T & any> = Pick<T, Exclude<keyof T, K>>;
// export type Override<M, N> = Omit<M, Extract<keyof M, keyof N>> & N;
// export type ExtractProps<
// TComponentOrTProps
// > = TComponentOrTProps extends React.ComponentType<infer P> ? P : never;
/**
*
*/
export interface EventTrack {
/**
*
*
* api: 所有 fetcher
* url: 打开外部链接 action link
* link: 打开内部链接
* dialog: action
* drawer: action
* copy: action
* reload: action reload
* email: action email
* prev: action prev
* next: action next
* cancel: action cancel
* close: action close
* submit: 有可能是 action submit
* confirm: action confirm
* add: action add
* reset: action reset
* reset-and-submit: action reset-and-submit
* formItemChange: 表单项内容变化
* formError: 表单验证失败
* formSubmit: 表单成功提交 api
* tabChange: tab
* netError: api
*/
eventType:
| 'api'
| 'url'
| 'link'
| 'dialog'
| 'drawer'
| 'copy'
| 'reload'
| 'email'
| 'prev'
| 'next'
| 'cancel'
| 'close'
| 'submit'
| 'confirm'
| 'reset'
| 'reset-and-submit'
| 'formItemChange'
| 'tabChange'
| 'pageHidden'
| 'pageVisible';
/**
*
*/
eventData?: PlainObject | Api;
}
export type ToastLevel = 'info' | 'success' | 'error' | 'warning';
export type ToastConf = {
position?:
| 'top-right'
| 'top-center'
| 'top-left'
| 'bottom-center'
| 'bottom-left'
| 'bottom-right'
| 'center';
closeButton: boolean;
showIcon?: boolean;
timeout?: number;
errorTimeout?: number;
className?: string;
items?: Array<any>;
useMobileUI?: boolean;
};
export interface OptionProps {
className?: string;
multi?: boolean;
multiple?: boolean;
valueField?: string;
labelField?: string;
simpleValue?: boolean; // 默认onChange 出去是整个 option 节点,如果配置了 simpleValue 就只包含值。
options: Options;
loading?: boolean;
joinValues?: boolean;
extractValue?: boolean;
delimiter?: string;
clearable?: boolean;
resetValue: any;
placeholder?: string;
disabled?: boolean;
creatable?: boolean;
pathSeparator?: string;
hasError?: boolean;
block?: boolean;
onAdd?: (
idx?: number | Array<number>,
value?: any,
skipForm?: boolean
) => void;
editable?: boolean;
onEdit?: (value: Option, origin?: Option, skipForm?: boolean) => void;
removable?: boolean;
onDelete?: (value: Option) => void;
}
export type LinkItem = LinkItemProps;
interface LinkItemProps {
id?: number;
label: string;
hidden?: boolean;
open?: boolean;
active?: boolean;
className?: string;
children?: Array<LinkItem>;
path?: string;
icon?: string;
component?: React.ReactType;
}
export interface NavigationObject {
label: string;
children?: Array<LinkItem>;
prefix?: JSX.Element;
affix?: JSX.Element;
className?: string;
[propName: string]: any;
}
/**
* `data.xxx > 5`
*/
export type SchemaExpression = string;
/**
* css类名
*
* className: "red"
*
* 使
*
* className: {
* "red": "data.progress > 80",
* "blue": "data.progress > 60"
* }
*/
export type SchemaClassName =
| string
| {
[propName: string]: boolean | undefined | null | SchemaExpression;
};
export interface BaseSchemaWithoutType {
/**
* css
*/
className?: SchemaClassName;
/**
* definitions 使
*/
$ref?: string;
/**
*
*/
disabled?: boolean;
/**
*
*/
disabledOn?: SchemaExpression;
/**
*
* @deprecated visible
*/
hidden?: boolean;
/**
*
* @deprecated visibleOn
*/
hiddenOn?: SchemaExpression;
/**
*
*/
visible?: boolean;
/**
*
*/
visibleOn?: SchemaExpression;
/**
* id
*/
id?: string;
/**
*
*/
onEvent?: {
[propName: string]: {
weight?: number; // 权重
actions: ListenerAction[]; // 执行的动作集
};
};
}

View File

@ -85,7 +85,7 @@ function rgbaToHex(color: Color) {
return `#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`;
}
export default class ColorScale {
export class ColorScale {
private min: number;
private max: number;
private alpha: number;
@ -136,3 +136,5 @@ export default class ColorScale {
return new Color(r, g, b, this.alpha);
}
}
export default ColorScale;

View File

@ -1,5 +1,5 @@
import {JSONSchema} from '../types';
import {DataScope} from './DataScope';
import type {JSONSchema} from './DataScope';
import {guid} from './helper';
/**

View File

@ -1,10 +1,6 @@
import type {JSONSchema7} from 'json-schema';
import {JSONSchema} from '../types';
import {guid, keyToPath, mapTree} from './helper';
// 先只支持 JSONSchema draft07 好了
// https://json-schema.org/draft-07/json-schema-release-notes.html
export type JSONSchema = JSONSchema7;
export class DataScope {
// 指向父级
parent?: DataScope;

Some files were not shown because too many files have changed in this diff Show More