├── .dockerignore ├── packages ├── rcre-runtime │ ├── src │ │ ├── parser │ │ │ ├── analyzeCodeDependencies.ts │ │ │ └── analyze │ │ │ │ ├── index.ts │ │ │ │ └── walker.ts │ │ ├── index.ts │ │ ├── util.ts │ │ └── runTime │ │ │ ├── evaluation.ts │ │ │ └── index.ts │ ├── CHANGELOG.md │ ├── package.json │ └── tsconfig.json ├── rcre │ ├── src │ │ ├── core │ │ │ ├── DataProvider │ │ │ │ ├── index.ts │ │ │ │ ├── adaptors │ │ │ │ │ ├── socket.ts │ │ │ │ │ ├── sync.ts │ │ │ │ │ └── async.ts │ │ │ │ └── applications │ │ │ │ │ ├── localstorage.ts │ │ │ │ │ ├── cookie.ts │ │ │ │ │ └── ajax.ts │ │ │ ├── Form │ │ │ │ └── index.ts │ │ │ ├── Connect │ │ │ │ ├── index.ts │ │ │ │ └── Common │ │ │ │ │ └── Common.tsx │ │ │ ├── Trigger │ │ │ │ ├── index.ts │ │ │ │ ├── reducers.ts │ │ │ │ └── actions.ts │ │ │ ├── util │ │ │ │ ├── configure.ts │ │ │ │ ├── compat.ts │ │ │ │ ├── stringToPath.ts │ │ │ │ ├── withAllContext.tsx │ │ │ │ ├── componentLoader.tsx │ │ │ │ └── filter.ts │ │ │ ├── Container │ │ │ │ ├── Container.css │ │ │ │ └── index.ts │ │ │ ├── Hosts │ │ │ │ ├── index.ts │ │ │ │ ├── Button.tsx │ │ │ │ ├── Checkbox.tsx │ │ │ │ ├── Form.tsx │ │ │ │ ├── FormItem.tsx │ │ │ │ ├── Container.tsx │ │ │ │ └── Input.tsx │ │ │ ├── Layout │ │ │ │ ├── Text │ │ │ │ │ ├── demo │ │ │ │ │ │ ├── basic.md │ │ │ │ │ │ ├── html.md │ │ │ │ │ │ ├── thousand.md │ │ │ │ │ │ ├── link.md │ │ │ │ │ │ └── mode.md │ │ │ │ │ ├── Text.css │ │ │ │ │ └── Text.md │ │ │ │ └── Div │ │ │ │ │ └── Div.tsx │ │ │ ├── Events │ │ │ │ ├── Node.tsx │ │ │ │ ├── dataProviderEvent.ts │ │ │ │ └── index.tsx │ │ │ ├── externalApi.ts │ │ │ ├── DataCustomer │ │ │ │ ├── funcCustomer.ts │ │ │ │ └── customers │ │ │ │ │ ├── pass.ts │ │ │ │ │ ├── localStorage.ts │ │ │ │ │ └── location.ts │ │ │ ├── ErrorBoundary.tsx │ │ │ └── Service │ │ │ │ ├── observer.test.ts │ │ │ │ └── observer.ts │ │ ├── index.css │ │ ├── jsx-support │ │ │ ├── index.tsx │ │ │ ├── Container.tsx │ │ │ ├── FormItem.tsx │ │ │ └── Form.tsx │ │ ├── data │ │ │ ├── store.tsx │ │ │ ├── reducers.ts │ │ │ ├── events.ts │ │ │ └── history.ts │ │ └── index.tsx │ ├── global.ts │ ├── tsconfig.json │ ├── package.json │ └── CHANGELOG.md ├── rcre-test-tools │ ├── src │ │ └── index.tsx │ ├── package.json │ ├── tsconfig.json │ └── CHANGELOG.md └── rcre-runtime-syntax-transform │ ├── CHANGELOG.md │ ├── package.json │ └── tsconfig.json ├── docs ├── api │ ├── ES.md │ ├── Task.md │ ├── Container.md │ └── DataProvider.md ├── test │ ├── set-data.md │ ├── simulate-event.md │ ├── use-debug-mode.md │ ├── read-form-state.md │ ├── waiting-for-api.md │ ├── use-test-tools.md │ ├── trigger-form-submit.md │ ├── find-your-components.md │ ├── read-container-state.md │ └── write-automatic-scripts.md ├── recipes │ ├── writing-tests.md │ ├── debug-tools.md │ ├── watch-state-events.md │ ├── keep-state-immutable.md │ ├── usage-with-typescript.md │ ├── use-with-react-router.md │ └── use-with-redux.md ├── json-render │ ├── print-text.md │ ├── layout-system.md │ ├── trigger-event.md │ ├── dynamic-render.md │ ├── render-control.md │ ├── use-dynamic-props.md │ ├── how-to-use-json-render.md │ └── use-external-functions.md ├── automatic-api │ ├── extend-data-provider.md │ ├── api-loading-state.md │ ├── condition.md │ ├── required-params.md │ ├── response-rewrite.md │ └── serial-and-parallel-api.md ├── state-management │ ├── dynamic-components.md │ ├── dynamic-container.md │ ├── debounce.md │ ├── multi-container.md │ ├── use-lodash-path.md │ ├── state-auto-recycle.md │ ├── container-init-value.md │ └── cross-container-assignment.md ├── tasks │ ├── handle-task-error.md │ ├── custom-tasks.md │ └── pass-task.md ├── Installation.md ├── basic-tutorial │ ├── intro.md │ └── automatic-api.md └── form │ ├── read-form-state.md │ ├── dynamic-rules.md │ ├── custom-validation.md │ └── automatic-api-validation.md ├── commitlint.config.js ├── examples ├── form-submit │ ├── src │ │ ├── styles.css │ │ └── components │ │ │ └── Input.js │ ├── package.json │ └── public │ │ └── index.html ├── todos │ ├── src │ │ ├── styles.css │ │ └── components │ │ │ ├── Input.js │ │ │ └── Todo.js │ ├── package.json │ └── public │ │ └── index.html ├── counter │ ├── src │ │ ├── styles.css │ │ ├── components │ │ │ └── Counter.js │ │ └── index.js │ ├── package.json │ └── public │ │ └── index.html ├── immutable │ ├── src │ │ ├── styles.css │ │ ├── components │ │ │ └── Counter.js │ │ └── index.js │ ├── package.json │ └── public │ │ └── index.html ├── simple-search │ ├── src │ │ ├── styles.css │ │ └── index.js │ ├── package.json │ └── public │ │ └── index.html ├── component-auto-clear │ ├── src │ │ ├── styles.css │ │ ├── components │ │ │ └── Input.js │ │ └── index.js │ ├── package.json │ └── public │ │ └── index.html ├── combine-search │ ├── src │ │ ├── styles.css │ │ ├── Components │ │ │ ├── Input.js │ │ │ └── Select.js │ │ └── constant.js │ ├── package.json │ └── public │ │ └── index.html ├── task-group │ ├── src │ │ ├── tasks │ │ │ ├── showNotice.js │ │ │ └── async.js │ │ ├── components │ │ │ └── Input.js │ │ └── styles.css │ ├── package.json │ └── public │ │ └── index.html ├── pass-tasks │ ├── src │ │ ├── components │ │ │ └── Input.js │ │ └── styles.css │ ├── package.json │ └── public │ │ └── index.html ├── multi-container │ ├── src │ │ ├── components │ │ │ └── Input.js │ │ ├── styles.css │ │ └── index.js │ ├── package.json │ └── public │ │ └── index.html └── container-inheritance │ ├── src │ ├── components │ │ └── Input.js │ ├── styles.css │ └── index.js │ ├── package.json │ └── public │ └── index.html ├── website ├── static │ ├── img │ │ └── oss_logo.png │ ├── css │ │ └── code-block-button.css │ └── js │ │ └── code-block-button.js ├── blog │ └── 2019-04-27-helloworld.md ├── package.json └── pages │ └── en │ ├── users.js │ ├── help.js │ └── help-with-translations.js ├── test ├── setupTests.ts ├── rcre │ ├── __snapshots__ │ │ └── event.test.ts.snap │ ├── core │ │ ├── __snapshots__ │ │ │ └── DataCustomer.test.tsx.snap │ │ ├── Form │ │ │ └── Form.test.tsx │ │ ├── ExternalComponent.test.tsx │ │ └── Hosts │ │ │ └── Input.test.tsx │ ├── jsx-syntax │ │ └── components │ │ │ ├── Input.tsx │ │ │ └── Checkbox.tsx │ └── parser │ │ └── walker.test.ts ├── __mock__ │ ├── data │ │ ├── barchart.json │ │ ├── cascader.json │ │ ├── piechart.json │ │ └── linechart.json │ └── server.js └── rcre-test-tools │ └── __snapshots__ │ └── AutomaticRobot.test.tsx.snap ├── .gitignore ├── __mocks__ └── styleMock.js ├── .editorconfig ├── lerna.json ├── scripts └── rollup │ ├── build.js │ └── watch.js ├── LICENSE ├── crowdin.yaml ├── jest.config.json ├── tsconfig.json └── CHANGELOG.md /.dockerignore: -------------------------------------------------------------------------------- 1 | */node_modules 2 | *.log 3 | -------------------------------------------------------------------------------- /packages/rcre-runtime/src/parser/analyzeCodeDependencies.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/api/ES.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: ES 3 | title: 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/api/Task.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: task 3 | title: Task 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /packages/rcre/src/core/DataProvider/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Controller'; -------------------------------------------------------------------------------- /docs/test/set-data.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: set-data 3 | title: Set Data 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /packages/rcre/src/core/DataProvider/adaptors/socket.ts: -------------------------------------------------------------------------------- 1 | export class SocketAdaptor {} -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {extends: ['@commitlint/config-conventional']}; -------------------------------------------------------------------------------- /docs/api/Container.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: container 3 | title: 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/recipes/writing-tests.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: writing-tests 3 | title: Writing Tests 4 | --- -------------------------------------------------------------------------------- /docs/api/DataProvider.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: data-provider 3 | title: DataProvider 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/json-render/print-text.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: print-text 3 | title: Print Text 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/recipes/debug-tools.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: debug-tools 3 | title: Debug Tools 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /packages/rcre-runtime/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './parser'; 2 | export * from './runTime'; -------------------------------------------------------------------------------- /packages/rcre/src/core/Form/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Form'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /docs/test/simulate-event.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: simulate-event 3 | title: Simulate Event 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/test/use-debug-mode.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: use-debug-mode 3 | title: Use Debug Mode 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/json-render/layout-system.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: layout-system 3 | title: Layout System 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/json-render/trigger-event.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: trigger-event 3 | title: Trigger Event 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/test/read-form-state.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: read-form-state 3 | title: Read Form State 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/test/waiting-for-api.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: waiting-for-api 3 | title: Waiting For API 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/json-render/dynamic-render.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: dynamic-render 3 | title: Dynamic Render 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/json-render/render-control.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: render-control 3 | title: Render Control 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/test/use-test-tools.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: trigger-form-submit 3 | title: Trigger Form Submit 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /examples/form-submit/src/styles.css: -------------------------------------------------------------------------------- 1 | .App { 2 | font-family: sans-serif; 3 | padding: 20px; 4 | } 5 | -------------------------------------------------------------------------------- /examples/todos/src/styles.css: -------------------------------------------------------------------------------- 1 | .App { 2 | font-family: sans-serif; 3 | text-align: center; 4 | } 5 | -------------------------------------------------------------------------------- /packages/rcre-test-tools/src/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './RCRETestUtil'; 2 | export * from './AutomaticRobot'; -------------------------------------------------------------------------------- /packages/rcre/src/core/Connect/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Common/Common'; 2 | export * from './basicConnect'; -------------------------------------------------------------------------------- /docs/recipes/watch-state-events.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: watch-state-events 3 | title: Watch State Events 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/test/trigger-form-submit.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: trigger-form-submit 3 | title: Trigger Form Submit 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /examples/counter/src/styles.css: -------------------------------------------------------------------------------- 1 | .App { 2 | font-family: sans-serif; 3 | text-align: center; 4 | } 5 | -------------------------------------------------------------------------------- /examples/immutable/src/styles.css: -------------------------------------------------------------------------------- 1 | .App { 2 | font-family: sans-serif; 3 | text-align: center; 4 | } 5 | -------------------------------------------------------------------------------- /website/static/img/oss_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andycall/RCRE/HEAD/website/static/img/oss_logo.png -------------------------------------------------------------------------------- /docs/json-render/use-dynamic-props.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: use-dynamic-props 3 | title: Use Dynamic Props 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/recipes/keep-state-immutable.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: keep-state-immutable 3 | title: Keep State Immutable 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/test/find-your-components.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: find-your-components 3 | title: Find Your Components 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/test/read-container-state.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: read-container-state 3 | title: Read Container State 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /examples/simple-search/src/styles.css: -------------------------------------------------------------------------------- 1 | .App { 2 | font-family: sans-serif; 3 | text-align: center; 4 | } 5 | -------------------------------------------------------------------------------- /docs/recipes/usage-with-typescript.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: usage-with-typescript 3 | title: Usage With Typescript 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/recipes/use-with-react-router.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: use-with-react-router 3 | title: Use With React Router 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /examples/component-auto-clear/src/styles.css: -------------------------------------------------------------------------------- 1 | .App { 2 | font-family: sans-serif; 3 | text-align: center; 4 | } 5 | -------------------------------------------------------------------------------- /docs/json-render/how-to-use-json-render.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: how-to-use-json-render 3 | title: How To Use JSON Render 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/json-render/use-external-functions.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: use-external-functions 3 | title: Use External Functions 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /docs/test/write-automatic-scripts.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: write automatic scripts 3 | title: Write Automatic Scripts 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /packages/rcre/src/core/Trigger/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Trigger'; 2 | export * from './actions'; 3 | export * from './reducers'; -------------------------------------------------------------------------------- /packages/rcre/src/core/util/configure.ts: -------------------------------------------------------------------------------- 1 | type ConfigureOptions = {}; 2 | 3 | export function configure(options: ConfigureOptions = {}) { 4 | 5 | } -------------------------------------------------------------------------------- /packages/rcre/src/index.css: -------------------------------------------------------------------------------- 1 | .rcre-render { 2 | width: 100%; 3 | background: transparent; 4 | } 5 | 6 | .rcre-spin { 7 | width: 100%; 8 | } -------------------------------------------------------------------------------- /packages/rcre/src/jsx-support/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './Container'; 2 | export * from '../core/ES'; 3 | export * from './Form'; 4 | export * from './FormItem'; -------------------------------------------------------------------------------- /test/setupTests.ts: -------------------------------------------------------------------------------- 1 | import {configure} from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | 4 | configure({ 5 | adapter: new Adapter() 6 | }); 7 | -------------------------------------------------------------------------------- /examples/combine-search/src/styles.css: -------------------------------------------------------------------------------- 1 | .App { 2 | font-family: sans-serif; 3 | text-align: center; 4 | } 5 | 6 | .search-zone { 7 | margin-bottom: 20px; 8 | } -------------------------------------------------------------------------------- /packages/rcre/src/core/Container/Container.css: -------------------------------------------------------------------------------- 1 | .rcre-abstract-container { 2 | overflow: auto; 3 | padding: 0 10px; 4 | } 5 | 6 | .err-text { 7 | color: red; 8 | } -------------------------------------------------------------------------------- /packages/rcre/src/core/Container/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../Hosts/Container'; 2 | export * from './action'; 3 | export * from './Container'; 4 | export * from './reducer'; 5 | -------------------------------------------------------------------------------- /packages/rcre/src/core/Hosts/index.ts: -------------------------------------------------------------------------------- 1 | import './Input'; 2 | import './Button'; 3 | import './Checkbox'; 4 | import './Form'; 5 | import './FormItem'; 6 | import './Container'; -------------------------------------------------------------------------------- /website/blog/2019-04-27-helloworld.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: helloworld 3 | author: helloworld 4 | authorURL: http://github.com/andycall 5 | authorFBID: 100002976521003 6 | --- 7 | 8 | helloworld -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | build 3 | 4 | # dependencies 5 | node_modules 6 | 7 | # testing 8 | /coverage 9 | /coverage/* 10 | 11 | dist 12 | 13 | .DS_Store 14 | 15 | website/i18n/* 16 | website/translated_docs -------------------------------------------------------------------------------- /examples/task-group/src/tasks/showNotice.js: -------------------------------------------------------------------------------- 1 | import {notification} from 'antd'; 2 | 3 | export function showNotice({params}) { 4 | notification.success({ 5 | message: '已成功发送username:' + params.username 6 | }); 7 | } -------------------------------------------------------------------------------- /__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process() { 3 | return 'module.exports = {};'; 4 | }, 5 | getCacheKey() { 6 | // The output is always the same. 7 | return 'cssTransform'; 8 | } 9 | }; -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | 10 | [examples/**.{json,js,jsx,ts,tsx}] 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /packages/rcre/src/core/Layout/Text/demo/basic.md: -------------------------------------------------------------------------------- 1 | ## 基础使用 2 | 3 | 显示一行字 4 | 5 | ```json 6 | { 7 | "body": [ 8 | { 9 | "type": "text", 10 | "text": "helloworld" 11 | } 12 | ] 13 | } 14 | ``` -------------------------------------------------------------------------------- /examples/pass-tasks/src/components/Input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ES} from 'rcre'; 3 | 4 | export const Input = (props) => ( 5 | {({$value, $name}, context) => ( 6 | context.container.$setData($name, event.target.value)} /> 7 | )} 8 | ); -------------------------------------------------------------------------------- /packages/rcre/src/core/Layout/Text/Text.css: -------------------------------------------------------------------------------- 1 | span::selection { 2 | color: inherit; 3 | } 4 | 5 | .rcre-text-disabled { 6 | color: #b8b8b8; 7 | background-color: #eee; 8 | border-color: #dbdbdb; 9 | opacity: 1; 10 | cursor: not-allowed; 11 | } 12 | 13 | .rcre-text { 14 | padding: 0 10px; 15 | } -------------------------------------------------------------------------------- /test/rcre/__snapshots__/event.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Event async middleware 1`] = ` 4 | Object { 5 | "$error": null, 6 | "$loading": false, 7 | "test": Object { 8 | "data": "ok", 9 | "errno": 0, 10 | }, 11 | "username": "helloworld", 12 | } 13 | `; 14 | -------------------------------------------------------------------------------- /examples/multi-container/src/components/Input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ES} from 'rcre'; 3 | 4 | export const Input = (props) => ( 5 | {({$value, $name}, context) => ( 6 | context.container.$setData($name, event.target.value)} /> 7 | )} 8 | ); -------------------------------------------------------------------------------- /packages/rcre/src/core/DataProvider/adaptors/sync.ts: -------------------------------------------------------------------------------- 1 | import {ProviderSourceConfig, runTimeType} from '../../../types'; 2 | 3 | export class SyncAdaptor { 4 | exec(config: any, provider: ProviderSourceConfig, runTime: runTimeType): any { 5 | throw new TypeError(`DataProvider: ${provider.mode} is not defined`); 6 | } 7 | } -------------------------------------------------------------------------------- /examples/component-auto-clear/src/components/Input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ES} from 'rcre'; 3 | 4 | export const Input = (props) => ( 5 | {({$value, $name}, context) => ( 6 | context.container.$setData($name, event.target.value)} /> 7 | )} 8 | ); -------------------------------------------------------------------------------- /examples/container-inheritance/src/components/Input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ES} from 'rcre'; 3 | 4 | export const Input = (props) => ( 5 | {({$value, $name}, context) => ( 6 | context.container.$setData($name, event.target.value)} /> 7 | )} 8 | ); -------------------------------------------------------------------------------- /examples/multi-container/src/styles.css: -------------------------------------------------------------------------------- 1 | .App { 2 | font-family: sans-serif; 3 | } 4 | 5 | h2 { 6 | margin-top: 20px; 7 | margin-bottom: 0; 8 | } 9 | 10 | p { 11 | margin: 5px 0 5px 0; 12 | } 13 | 14 | .demo1, .demo2 { 15 | margin-bottom: 15px; 16 | border: 1px solid #000; 17 | line-height: 25px; 18 | } -------------------------------------------------------------------------------- /packages/rcre/src/core/util/compat.ts: -------------------------------------------------------------------------------- 1 | import warning from 'warning'; 2 | export const $parent = new Proxy({}, { 3 | get(target: {}, p: PropertyKey, receiver: any): any { 4 | warning(process.env.NODE_ENV !== 'production', '在ExpressionString访问$parent已经被废弃了,请使用Container组件继承(props)来获取父级的数据'); 5 | return null; 6 | } 7 | }); -------------------------------------------------------------------------------- /packages/rcre/src/core/Layout/Text/demo/html.md: -------------------------------------------------------------------------------- 1 | ## 渲染原生HTML 2 | 3 | 设置`rawHtml`为true可以使用原生HTML的模式渲染 4 | 5 | ```json 6 | { 7 | "body": [ 8 | { 9 | "type": "text", 10 | "text": " -------------------------------------------------------------------------------- /docs/state-management/dynamic-container.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: dynamic-container 3 | title: Dynamic Container 4 | --- 5 | 6 | Containers can also be dynamically generated and can be used to generate multiple independent data scopes. 7 | 8 | -------------------------------------------------------------------------------- /examples/todos/src/components/Input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ES} from 'rcre'; 3 | 4 | export const Input = props => ( 5 | // Use debounce prop to temporary cache input's value for 500 second 6 | 8 | {(runTime, context) => { 9 | return context.container.$setData(runTime.$name, event.target.value)} 12 | /> 13 | }} 14 | 15 | ); -------------------------------------------------------------------------------- /packages/rcre/src/core/Layout/Text/demo/link.md: -------------------------------------------------------------------------------- 1 | ## 跳转链接 2 | 3 | 可跳转的链接 4 | 5 | ```json 6 | { 7 | "body": [ 8 | { 9 | "type": "text", 10 | "textType": "link", 11 | "href": "http://www.baidu.com", 12 | "text": "Baidu" 13 | }, 14 | { 15 | "type": "text", 16 | "textType": "link", 17 | "href": "/guide/HelloWorld", 18 | "text": "HelloWorld" 19 | } 20 | ] 21 | } 22 | ``` -------------------------------------------------------------------------------- /docs/tasks/handle-task-error.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: handle-task-error 3 | title: Handle Task Error 4 | --- 5 | 6 | Tasks can cause errors, such as code exceptions and data errors. 7 | 8 | RCRE will output these errors to the console by default, but you can use the following code for custom error handling. 9 | 10 | ```javascript 11 | import {Task} from 'rcre'; 12 | 13 | // write your custom error handler 14 | Task.errorHandler = (error) => { 15 | console.error('ops, got an error', error); 16 | }; 17 | ``` -------------------------------------------------------------------------------- /docs/state-management/debounce.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: debounce 3 | title: Debounce 4 | --- 5 | 6 | If you need to delay the synchronization of data to the Container after a certain amount of time, you can add the debounce attribute to the ES component. 7 | 8 | -------------------------------------------------------------------------------- /packages/rcre-runtime/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rcre-runtime", 3 | "version": "0.21.0", 4 | "description": "rcre expression runTime", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "scripts": { 8 | "test": "npm test" 9 | }, 10 | "keywords": [ 11 | "rcre" 12 | ], 13 | "dependencies": { 14 | "acorn": "^5.3.0", 15 | "lru-cache": "5.1.1" 16 | }, 17 | "author": "andycall", 18 | "license": "MIT" 19 | } 20 | -------------------------------------------------------------------------------- /examples/combine-search/src/Components/Input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Input} from 'antd'; 3 | import {ES} from 'rcre'; 4 | 5 | export const ESInput = (props) => ( 6 | {({$data, $name, $value}, context) => ( 7 | context.container.$setData($name, event.target.value)} 12 | /> 13 | )} 14 | ); -------------------------------------------------------------------------------- /packages/rcre-runtime-syntax-transform/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rcre-runtime-syntax-transform", 3 | "version": "0.21.0", 4 | "description": "rcre test transform", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "dependencies": { 11 | "rcre-runtime": "^0.21.0" 12 | }, 13 | "license": "MIT", 14 | "gitHead": "3cccf68d4c841aebe60ca2663fd523952591c859" 15 | } 16 | -------------------------------------------------------------------------------- /examples/todos/src/components/Todo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | const Todo = ({onClick, completed, text}) => ( 5 |
  • 11 | {text} 12 |
  • 13 | ) 14 | 15 | Todo.propTypes = { 16 | onClick: PropTypes.func.isRequired, 17 | completed: PropTypes.bool.isRequired, 18 | text: PropTypes.string.isRequired 19 | } 20 | 21 | export default Todo 22 | -------------------------------------------------------------------------------- /packages/rcre/src/core/DataProvider/adaptors/async.ts: -------------------------------------------------------------------------------- 1 | import {ProviderSourceConfig, runTimeType} from '../../../types'; 2 | 3 | export interface AsyncAdaptorRetValue { 4 | success: boolean; 5 | data?: any; 6 | errmsg?: string; 7 | } 8 | 9 | export class AsyncAdaptor { 10 | constructor() {} 11 | 12 | public async exec(config: any, provider: ProviderSourceConfig, runTime: runTimeType): Promise { 13 | throw new Error('AsyncAdaptor: exec function is not implemented'); 14 | } 15 | } -------------------------------------------------------------------------------- /examples/task-group/src/styles.css: -------------------------------------------------------------------------------- 1 | .App { 2 | font-family: sans-serif; 3 | } 4 | 5 | h2 { 6 | margin-top: 20px; 7 | margin-bottom: 0; 8 | } 9 | 10 | p { 11 | margin: 5px 0 5px 0 !important; 12 | } 13 | 14 | .demo1, .demo2 { 15 | margin-bottom: 15px; 16 | border: 1px solid #000; 17 | line-height: 25px; 18 | } 19 | 20 | .demo1 div, .demo2 div { 21 | margin-bottom: 10px; 22 | margin-top: 10px; 23 | } 24 | 25 | .container-inherit { 26 | border: 1px solid #000; 27 | padding: 0 10px 0 10px; 28 | } -------------------------------------------------------------------------------- /docs/state-management/multi-container.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: multi-container 3 | title: Multi Container 4 | --- 5 | 6 | Multiple Container components are like multiple reducers, and each Container's data synchronization is relatively independent. 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/combine-search/src/constant.js: -------------------------------------------------------------------------------- 1 | export const GET_REPO_LIST = 'getRepoList'; 2 | 3 | export const RepoSortOptions = [{ 4 | label: 'created', 5 | value: 'created' 6 | }, { 7 | label: 'updated', 8 | value: 'updated' 9 | }, { 10 | label: 'pushed', 11 | value: 'pushed' 12 | }, { 13 | label: 'full_name', 14 | value: 'full_name' 15 | }]; 16 | 17 | export const TypeSortOptions = [{ 18 | label: 'all', 19 | value: 'all' 20 | }, { 21 | label: 'owner', 22 | value: 'owner' 23 | }, { 24 | label: 'member', 25 | value: 'member' 26 | }]; -------------------------------------------------------------------------------- /packages/rcre/src/core/Events/Node.tsx: -------------------------------------------------------------------------------- 1 | import {EventEmitter} from 'events'; 2 | 3 | export class EventNode extends EventEmitter { 4 | public parent: EventNode; 5 | public childs: EventNode[]; 6 | public model: string; 7 | 8 | constructor(model: string) { 9 | super(); 10 | this.model = model; 11 | } 12 | 13 | trigger(event: string | symbol, ...args: any[]): boolean { 14 | this.emit('_SECRET_EVENT_', this.model, event, ...args); 15 | this.emit(event, args); 16 | return true; 17 | } 18 | } -------------------------------------------------------------------------------- /docs/Installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: installation 3 | title: Installation 4 | --- 5 | 6 | You can install RCRE with NPM, Yarn. 7 | 8 | ### NPM 9 | 10 | ```sh 11 | npm install rcre --save 12 | ``` 13 | or 14 | ``` 15 | yarn add rcre 16 | ``` 17 | 18 | RCRE is compatible with React v15+ and works with ReactDOM and React Native. 19 | 20 | ### CDN 21 | 22 | // TODO 23 | 24 | ### In-browser Playgrounds 25 | 26 | You can play with RCRE in your web browser with these live online playgrounds. 27 | 28 | * CodeSandbox (ReactDOM) https://codesandbox.io/s/pywlq1vqq0 29 | 30 | -------------------------------------------------------------------------------- /test/rcre/core/__snapshots__/DataCustomer.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`DataCustomer customer exec when add keepWhenError property in groups 1`] = ` 4 | Object { 5 | "container": Object { 6 | "__TMP_MODEL__DO_NO_USE_IT": Object {}, 7 | "customerGroup": Object { 8 | "$error": [Error: Request failed with status code 500], 9 | "$loading": true, 10 | }, 11 | }, 12 | "trigger": Object { 13 | "customerGroup": Object { 14 | "step1": Object {}, 15 | "step2": Object {}, 16 | }, 17 | }, 18 | } 19 | `; 20 | -------------------------------------------------------------------------------- /examples/form-submit/src/components/Input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Input} from 'antd'; 3 | import {ES} from 'rcre'; 4 | 5 | export const ESInput = (props) => ( 6 | {({$data, $name, $value}, context) => ( 7 | context.container.$setData($name, event.target.value)} 14 | /> 15 | )} 16 | ); -------------------------------------------------------------------------------- /docs/basic-tutorial/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: basic-tutorial-info 3 | title: Basics 4 | --- 5 | 6 | RCRE is not designed to mess up your codes. Instead, it's designed to save you from messed up code. 7 | 8 | Most of RCRE's features are smart and automatic, it can help you save a lot of repetitive work, let you focus on the functions you want to achieve 9 | 10 | In this guide, we will introduce the four core functions of RCRE that help you develop complex applications: 11 | 12 | 1. [Automatic State](./automatic-state.md) 13 | 2. [Automatic API](./automatic-api.md) 14 | 3. [Tasks](./tasks.md) 15 | 4. [Form Validation](./form-validation.md) -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "examples": "docusaurus-examples", 4 | "start": "docusaurus-start", 5 | "build": "docusaurus-build", 6 | "publish-gh-pages": "docusaurus-publish", 7 | "write-translations": "docusaurus-write-translations", 8 | "version": "docusaurus-version", 9 | "rename-version": "docusaurus-rename-version", 10 | "crowdin-upload": "crowdin --config ../crowdin.yaml upload sources --auto-update -b master", 11 | "crowdin-download": "crowdin --config ../crowdin.yaml download -b master" 12 | }, 13 | "devDependencies": { 14 | "docusaurus": "^1.8.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/combine-search/src/Components/Select.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Select} from 'antd'; 3 | import {ES} from 'rcre'; 4 | 5 | export const ESSelect = (props) => ( 6 | {({$name, $value}, context) => ( 7 | 17 | )} 18 | ); -------------------------------------------------------------------------------- /docs/form/read-form-state.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: read-form-state 3 | title: Read Form State 4 | --- 5 | 6 | The `$form` variable of the RCREForm component is the state of the current entire form. 7 | 8 | You can use the `$form.valid` variable to get the overall validation status of the current form. When all the RCREFormItem components in the form have been validated, `$form.valid` will become true. 9 | 10 | -------------------------------------------------------------------------------- /packages/rcre/src/core/externalApi.ts: -------------------------------------------------------------------------------- 1 | import {Store} from 'redux'; 2 | import {containerActionCreators} from './Container/action'; 3 | import {undoable, createContainerStateHistory} from '../data/history'; 4 | 5 | /** 6 | * 回滚container设置的state 7 | * @param store 8 | */ 9 | export function undoRCREContainerState(store: Store) { 10 | store.dispatch(containerActionCreators.undoState()); 11 | } 12 | 13 | /** 14 | * 15 | * @param store 16 | */ 17 | export function redoRCREContainerState(store: Store) { 18 | store.dispatch(containerActionCreators.redoState()); 19 | } 20 | 21 | export { 22 | undoable, 23 | createContainerStateHistory 24 | }; -------------------------------------------------------------------------------- /packages/rcre/src/core/Layout/Text/demo/mode.md: -------------------------------------------------------------------------------- 1 | ## 快速颜色 2 | 3 | 可以使用mode属性来快速使用常用的颜色值 4 | 5 | ```json 6 | { 7 | "body": [{ 8 | "type": "text", 9 | "text": "info text", 10 | "mode": "info" 11 | }, { 12 | "type": "text", 13 | "text": "error text", 14 | "mode": "error" 15 | }, { 16 | "type": "text", 17 | "text": "success text", 18 | "mode": "success" 19 | }, { 20 | "type": "text", 21 | "text": "warning text", 22 | "mode": "warning" 23 | }, { 24 | "type": "text", 25 | "text": "warning text", 26 | "mode": "primary" 27 | }] 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/state-management/use-lodash-path.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: use-lodash-path 3 | title: Use Lodash Path 4 | --- 5 | 6 | The component's name attribute can be a string of lodash path type, so that you can control objects or arrays with these strings. 7 | 8 | ```jsx harmony 9 | // scope: { 10 | // name: "" 11 | // } 12 | 13 | 14 | 15 | // list: [{ 16 | // name: "" 17 | // }] 18 | 19 | 20 | ``` 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/todos/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todos", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.js", 7 | "dependencies": { 8 | "rcre": "^0.20.x", 9 | "react": "16.8.6", 10 | "react-dom": "16.8.6", 11 | "react-scripts": "2.1.8" 12 | }, 13 | "devDependencies": { 14 | "typescript": "3.3.3" 15 | }, 16 | "scripts": { 17 | "start": "SKIP_PREFLIGHT_CHECK=true react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test --env=jsdom", 20 | "eject": "react-scripts eject" 21 | }, 22 | "browserslist": [ 23 | ">0.2%", 24 | "not dead", 25 | "not ie <= 11", 26 | "not op_mini all" 27 | ] 28 | } -------------------------------------------------------------------------------- /examples/counter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.js", 7 | "dependencies": { 8 | "rcre": "^0.20.x", 9 | "react": "16.8.6", 10 | "react-dom": "16.8.6", 11 | "react-scripts": "2.1.8" 12 | }, 13 | "devDependencies": { 14 | "typescript": "3.3.3" 15 | }, 16 | "scripts": { 17 | "start": "SKIP_PREFLIGHT_CHECK=true react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test --env=jsdom", 20 | "eject": "react-scripts eject" 21 | }, 22 | "browserslist": [ 23 | ">0.2%", 24 | "not dead", 25 | "not ie <= 11", 26 | "not op_mini all" 27 | ] 28 | } -------------------------------------------------------------------------------- /examples/immutable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "immutable", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.js", 7 | "dependencies": { 8 | "rcre": "^0.20.x", 9 | "react": "16.8.6", 10 | "react-dom": "16.8.6", 11 | "react-scripts": "2.1.8" 12 | }, 13 | "devDependencies": { 14 | "typescript": "3.3.3" 15 | }, 16 | "scripts": { 17 | "start": "SKIP_PREFLIGHT_CHECK=true react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test --env=jsdom", 20 | "eject": "react-scripts eject" 21 | }, 22 | "browserslist": [ 23 | ">0.2%", 24 | "not dead", 25 | "not ie <= 11", 26 | "not op_mini all" 27 | ] 28 | } -------------------------------------------------------------------------------- /test/__mock__/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 测试代理服务器 3 | * @author dongtiancheng@baidu.com 4 | */ 5 | 6 | const express = require('express'); 7 | const cors = require('cors'); 8 | const bodyParser = require('body-parser'); 9 | const path = require('path'); 10 | 11 | const app = express(); 12 | app.use('/static', express.static(path.join(__dirname, './data'))); 13 | 14 | app.use(cors()); 15 | app.use(bodyParser.json()); 16 | 17 | // // catch 404 and forward to error handler 18 | app.use((req, res, next) => { 19 | let err = new Error('Not Found'); 20 | res.status(404); 21 | next(err); 22 | }); 23 | 24 | console.log('server listening at 0.0.0.0:' + (process.env.PORT || 8844)); 25 | app.listen(process.env.PORT || 8844); 26 | -------------------------------------------------------------------------------- /test/rcre/jsx-syntax/components/Input.tsx: -------------------------------------------------------------------------------- 1 | import {ES} from 'rcre'; 2 | import React from 'react'; 3 | 4 | export class Input extends React.Component { 5 | render() { 6 | return ( 7 | 8 | {({$data}, context) => { 9 | return ( 10 | context.container.$setData(this.props.name, event.target.value)} 13 | /> 14 | ); 15 | }} 16 | 17 | ); 18 | } 19 | } -------------------------------------------------------------------------------- /examples/simple-search/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-search", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.js", 7 | "dependencies": { 8 | "rcre": "^0.20.x", 9 | "react": "16.8.6", 10 | "react-dom": "16.8.6", 11 | "react-scripts": "2.1.8" 12 | }, 13 | "devDependencies": { 14 | "typescript": "3.3.3" 15 | }, 16 | "scripts": { 17 | "start": "SKIP_PREFLIGHT_CHECK=true react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test --env=jsdom", 20 | "eject": "react-scripts eject" 21 | }, 22 | "browserslist": [ 23 | ">0.2%", 24 | "not dead", 25 | "not ie <= 11", 26 | "not op_mini all" 27 | ] 28 | } -------------------------------------------------------------------------------- /packages/rcre-test-tools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rcre-test-tools", 3 | "version": "0.21.17", 4 | "description": "rcre test utils", 5 | "main": "./dist/index.js", 6 | "typings": "./dist/index.d.ts", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "andycall", 11 | "license": "MIT", 12 | "dependencies": { 13 | "@types/enzyme": "^2.8.12", 14 | "@types/pretty-format": "^20.0.0", 15 | "chalk": "^2.4.1", 16 | "json-format": "^1.0.1", 17 | "lodash": "^4.17.4", 18 | "pretty-format": "^23.6.0", 19 | "rcre": "^0.21.17" 20 | }, 21 | "gitHead": "3cccf68d4c841aebe60ca2663fd523952591c859" 22 | } 23 | -------------------------------------------------------------------------------- /examples/component-auto-clear/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "component-auto-clear", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.js", 7 | "dependencies": { 8 | "rcre": "^0.20.x", 9 | "react": "16.8.6", 10 | "react-dom": "16.8.6", 11 | "react-scripts": "2.1.8" 12 | }, 13 | "devDependencies": { 14 | "typescript": "3.3.3" 15 | }, 16 | "scripts": { 17 | "start": "SKIP_PREFLIGHT_CHECK=true react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test --env=jsdom", 20 | "eject": "react-scripts eject" 21 | }, 22 | "browserslist": [ 23 | ">0.2%", 24 | "not dead", 25 | "not ie <= 11", 26 | "not op_mini all" 27 | ] 28 | } -------------------------------------------------------------------------------- /packages/rcre/src/core/Layout/Text/Text.md: -------------------------------------------------------------------------------- 1 | # Text 文本 2 | 3 | 用于展示纯文本,不支持显示HTML字符串 4 | 5 | ## 代码演示 6 | 7 | {{demo}} 8 | 9 | ## API 10 | 11 | | 属性 | 说明 | 类型 | 是否必须 | 默认值 | 12 | | --------- | ------ | ---------------- | ----- | ----- | 13 | | text | 显示的文本 | string | true | - | 14 | | textType | 文本类型 | text,link,strong | false | text | 15 | | href | 跳转链接 | string | false | - | 16 | | thousands | 添加千分位符 | boolean | false | false | 17 | | rawHtml | 采用innerHTML的方式, 警告: 会有xss注入风险 | boolean | false | false | 18 | |style | CSS内联属性 | CSSProperties | false |-| 19 | |className | class Class | string | false | -| 20 | | rightAddon | 右侧添加额外的文字 | string | false | - | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/form-submit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "form-submit", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.js", 7 | "dependencies": { 8 | "rcre": "^0.20.x", 9 | "antd": "3.16.6", 10 | "react": "16.8.6", 11 | "react-dom": "16.8.6", 12 | "react-scripts": "2.1.8" 13 | }, 14 | "devDependencies": { 15 | "typescript": "3.3.3" 16 | }, 17 | "scripts": { 18 | "start": "SKIP_PREFLIGHT_CHECK=true react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test --env=jsdom", 21 | "eject": "react-scripts eject" 22 | }, 23 | "browserslist": [ 24 | ">0.2%", 25 | "not dead", 26 | "not ie <= 11", 27 | "not op_mini all" 28 | ] 29 | } -------------------------------------------------------------------------------- /docs/state-management/state-auto-recycle.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: state-auto-recycle 3 | title: State Auto Recycle 4 | --- 5 | 6 | Inside the form, when the component is destroyed, the state of the component can be automatically recycled. 7 | 8 | If your component is not inside the form, you can use the `clearWhenDestroy` property to automatically clean up the data when the component is destroyed. 9 | 10 | In other cases, the destruction of the component does not trigger the clearing of the value. 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/combine-search/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "combine-search", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.js", 7 | "dependencies": { 8 | "rcre": "0.20.x", 9 | "react": "16.8.6", 10 | "react-dom": "16.8.6", 11 | "antd": "^3.16.6", 12 | "react-scripts": "2.1.8" 13 | }, 14 | "devDependencies": { 15 | "typescript": "3.3.3" 16 | }, 17 | "scripts": { 18 | "start": "SKIP_PREFLIGHT_CHECK=true react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test --env=jsdom", 21 | "eject": "react-scripts eject" 22 | }, 23 | "browserslist": [ 24 | ">0.2%", 25 | "not dead", 26 | "not ie <= 11", 27 | "not op_mini all" 28 | ] 29 | } -------------------------------------------------------------------------------- /examples/pass-tasks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pass-task", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.js", 7 | "dependencies": { 8 | "rcre": "^0.20.x", 9 | "react": "16.8.6", 10 | "react-dom": "16.8.6", 11 | "react-scripts": "2.1.8", 12 | "json-format": "^1.0.1" 13 | }, 14 | "devDependencies": { 15 | "typescript": "3.3.3" 16 | }, 17 | "scripts": { 18 | "start": "SKIP_PREFLIGHT_CHECK=true react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test --env=jsdom", 21 | "eject": "react-scripts eject" 22 | }, 23 | "browserslist": [ 24 | ">0.2%", 25 | "not dead", 26 | "not ie <= 11", 27 | "not op_mini all" 28 | ] 29 | } -------------------------------------------------------------------------------- /packages/rcre/src/data/store.tsx: -------------------------------------------------------------------------------- 1 | import {applyMiddleware, createStore, Store, compose, combineReducers} from 'redux'; 2 | import {ContainerStateHistory, undoable} from './history'; 3 | import {rcreReducer, RootState} from './reducers'; 4 | import {triggerEvents} from './events'; 5 | 6 | const composeEnhancers = (( 7 | window && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 8 | ) || compose)(applyMiddleware( 9 | triggerEvents 10 | )); 11 | 12 | export function createReduxStore(history?: ContainerStateHistory): Store { 13 | return createStore( 14 | combineReducers({ 15 | $rcre: undoable(rcreReducer, history) 16 | }), 17 | composeEnhancers 18 | ); 19 | } 20 | 21 | export default createReduxStore; 22 | -------------------------------------------------------------------------------- /examples/multi-container/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multi-container", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.js", 7 | "dependencies": { 8 | "rcre": "^0.20.x", 9 | "react": "16.8.6", 10 | "react-dom": "16.8.6", 11 | "react-scripts": "2.1.8", 12 | "json-format": "^1.0.1" 13 | }, 14 | "devDependencies": { 15 | "typescript": "3.3.3" 16 | }, 17 | "scripts": { 18 | "start": "SKIP_PREFLIGHT_CHECK=true react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test --env=jsdom", 21 | "eject": "react-scripts eject" 22 | }, 23 | "browserslist": [ 24 | ">0.2%", 25 | "not dead", 26 | "not ie <= 11", 27 | "not op_mini all" 28 | ] 29 | } -------------------------------------------------------------------------------- /scripts/rollup/build.js: -------------------------------------------------------------------------------- 1 | const rollup = require('rollup'); 2 | const config = require('./config'); 3 | 4 | let modules = Object.keys(config); 5 | 6 | async function main() { 7 | for (let key of modules) { 8 | let inputOptions = config[key].inputOptions; 9 | let outputOptions = config[key].outputOptions; 10 | 11 | try { 12 | // create a bundle 13 | const bundle = await rollup.rollup(inputOptions); 14 | 15 | // generate code 16 | await bundle.generate(outputOptions); 17 | 18 | await bundle.write(outputOptions); 19 | } catch (err) { 20 | throw err; 21 | } 22 | } 23 | } 24 | 25 | main().catch(err => { 26 | console.error(err); 27 | process.exit(1); 28 | }); -------------------------------------------------------------------------------- /website/static/css/code-block-button.css: -------------------------------------------------------------------------------- 1 | /* "Copy" code block button */ 2 | pre { 3 | position: relative; 4 | } 5 | 6 | pre .btnIcon { 7 | position: absolute; 8 | top: 4px; 9 | z-index: 2; 10 | cursor: pointer; 11 | border: 1px solid transparent; 12 | padding: 0; 13 | color: #000; 14 | background-color: transparent; 15 | height: 30px; 16 | transition: all .25s ease-out; 17 | } 18 | 19 | pre .btnIcon:hover { 20 | text-decoration: none; 21 | } 22 | 23 | .btnIcon__body { 24 | align-items: center; 25 | display: flex; 26 | } 27 | 28 | .btnIcon svg { 29 | fill: currentColor; 30 | margin-right: .4em; 31 | } 32 | 33 | .btnIcon__label { 34 | font-size: 11px; 35 | } 36 | 37 | .btnClipboard { 38 | right: 10px; 39 | } -------------------------------------------------------------------------------- /examples/container-inheritance/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "container-inheritance", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.js", 7 | "dependencies": { 8 | "rcre": "^0.20.x", 9 | "react": "16.8.6", 10 | "react-dom": "16.8.6", 11 | "react-scripts": "2.1.8", 12 | "json-format": "^1.0.1" 13 | }, 14 | "devDependencies": { 15 | "typescript": "3.3.3" 16 | }, 17 | "scripts": { 18 | "start": "SKIP_PREFLIGHT_CHECK=true react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test --env=jsdom", 21 | "eject": "react-scripts eject" 22 | }, 23 | "browserslist": [ 24 | ">0.2%", 25 | "not dead", 26 | "not ie <= 11", 27 | "not op_mini all" 28 | ] 29 | } -------------------------------------------------------------------------------- /examples/task-group/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "task-group", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.js", 7 | "dependencies": { 8 | "rcre": "0.20.x", 9 | "react": "16.8.6", 10 | "react-dom": "16.8.6", 11 | "react-scripts": "2.1.8", 12 | "json-format": "^1.0.1", 13 | "antd": "^3.16.6" 14 | }, 15 | "devDependencies": { 16 | "typescript": "3.3.3" 17 | }, 18 | "scripts": { 19 | "start": "SKIP_PREFLIGHT_CHECK=true react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test --env=jsdom", 22 | "eject": "react-scripts eject" 23 | }, 24 | "browserslist": [ 25 | ">0.2%", 26 | "not dead", 27 | "not ie <= 11", 28 | "not op_mini all" 29 | ] 30 | } -------------------------------------------------------------------------------- /test/rcre/jsx-syntax/components/Checkbox.tsx: -------------------------------------------------------------------------------- 1 | import {ES} from 'rcre'; 2 | import React from 'react'; 3 | 4 | export class Checkbox extends React.Component { 5 | render() { 6 | return ( 7 | 8 | {({$data}, context) => { 9 | return ( 10 | { 14 | context.container.$setData(this.props.name, event.target.checked); 15 | }} 16 | /> 17 | ); 18 | }} 19 | 20 | ); 21 | } 22 | } -------------------------------------------------------------------------------- /test/__mock__/data/cascader.json: -------------------------------------------------------------------------------- 1 | { 2 | "errno": 0, 3 | "data": [ 4 | { 5 | "label": "A", 6 | "value": "a", 7 | "children": [ 8 | { 9 | "label": "B", 10 | "value": "b", 11 | "children": [ 12 | { 13 | "label": "C", 14 | "value": "c" 15 | } 16 | ] 17 | } 18 | ] 19 | }, 20 | { 21 | "label": "123", 22 | "value": "123", 23 | "children": [ 24 | { 25 | "label": "456", 26 | "value": "456" 27 | } 28 | ] 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /packages/rcre/src/jsx-support/Container.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react'; 2 | import {ContainerProps, RCREContainer} from '../core/Container/Container'; 3 | import {ContainerContext, RCREContext, IteratorContext, TriggerContext} from '../core/context'; 4 | 5 | export function Container(props: ContainerProps) { 6 | let rcreContext = useContext(RCREContext); 7 | let containerContext = useContext(ContainerContext); 8 | let iteratorContext = useContext(IteratorContext); 9 | let triggerContext = useContext(TriggerContext); 10 | 11 | return ( 12 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /packages/rcre/src/core/DataProvider/applications/localstorage.ts: -------------------------------------------------------------------------------- 1 | import {ProviderSourceConfig, runTimeType} from '../../../types'; 2 | import {SyncAdaptor} from '../adaptors/sync'; 3 | 4 | interface LocalStorageConfig extends ProviderSourceConfig { 5 | config: string[]; 6 | } 7 | 8 | export class LocalStorageAdaptor extends SyncAdaptor { 9 | exec(config: string[], provider: LocalStorageConfig, runTime: runTimeType) { 10 | let items = config; 11 | let ret = {}; 12 | 13 | items.forEach(key => { 14 | let result = localStorage.getItem(key); 15 | 16 | if (typeof result === 'string') { 17 | try { 18 | result = JSON.parse(result); 19 | } catch (e) {} 20 | } 21 | 22 | ret[key] = result; 23 | }); 24 | 25 | return ret; 26 | } 27 | } -------------------------------------------------------------------------------- /test/__mock__/data/piechart.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": 0, 3 | "msg": "", 4 | "data": { 5 | "series" : [ 6 | { 7 | "name": "访问来源", 8 | "type": "pie", 9 | "radius" : "55%", 10 | "center": ["50%", "60%"], 11 | "data":[ 12 | {"value":335, "name": "直接访问"}, 13 | {"value":310, "name":"邮件营销"}, 14 | {"value":234, "name":"联盟广告"}, 15 | {"value":135, "name":"视频广告"}, 16 | {"value":1548, "name":"搜索引擎"} 17 | ], 18 | "itemStyle": { 19 | "emphasis": { 20 | "shadowBlur": 10, 21 | "shadowOffsetX": 0, 22 | "shadowColor": "rgba(0, 0, 0, 0.5)" 23 | } 24 | } 25 | } 26 | ] 27 | } 28 | } -------------------------------------------------------------------------------- /packages/rcre/src/core/Hosts/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ES} from '../ES'; 3 | import {componentLoader} from '../util/componentLoader'; 4 | 5 | interface ButtonProps { 6 | text: string; 7 | } 8 | 9 | class RCREButton extends React.Component { 10 | render() { 11 | return ( 12 | 13 | {({$data}, context) => { 14 | return ( 15 | 24 | ); 25 | }} 26 | 27 | ); 28 | } 29 | } 30 | 31 | componentLoader.addComponent('button', RCREButton); -------------------------------------------------------------------------------- /test/rcre-test-tools/__snapshots__/AutomaticRobot.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`AutomaticRobot Containers can still be embedded in the Steps 1`] = ` 4 | Object { 5 | "__TMP_MODEL__DO_NO_USE_IT": Object {}, 6 | "child": Object { 7 | "child-username": "1234", 8 | }, 9 | "demo": Object { 10 | "anotherUserName": "1234", 11 | "passUserName": "1234", 12 | "username": "1234", 13 | }, 14 | } 15 | `; 16 | 17 | exports[`AutomaticRobot event steps 1`] = ` 18 | Object { 19 | "container": Object { 20 | "__TMP_MODEL__DO_NO_USE_IT": Object {}, 21 | "demo": Object { 22 | "anotherUserName": "1234", 23 | "passUserName": "1234", 24 | "username": "1234", 25 | }, 26 | }, 27 | "trigger": Object { 28 | "demo": Object { 29 | "$SELF_PASS_CUSTOMER": Object { 30 | "anotherUserName": "1234", 31 | }, 32 | "testCustomer": Object { 33 | "passUserName": "1234", 34 | }, 35 | }, 36 | }, 37 | } 38 | `; 39 | -------------------------------------------------------------------------------- /docs/automatic-api/api-loading-state.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: api-loading-state 3 | title: Use API Loading State 4 | --- 5 | 6 | When the interface is triggered, RCRE will automatically update the current state of the interface. 7 | 8 | You can use the `$data.$loading` variable to determine if the current interface is calling. 9 | 10 | If the interface call fails, you can use `$data.$error` to get the reason for the interface failure. 11 | 12 | ```jsx harmony 13 | {({$data}) => { 23 | if ($data.$loading) { 24 | return
    loading...
    ; 25 | } 26 | 27 | if ($data.$error) { 28 | return
    Request failed: {$data.$error.message}
    29 | } 30 | 31 | return
    loading success
    32 | }}
    33 | /> 34 | ``` -------------------------------------------------------------------------------- /packages/rcre/src/core/Hosts/Checkbox.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {BasicConnectProps} from '../Connect/basicConnect'; 3 | import {commonConnect} from '../Connect/Common/Common'; 4 | import {componentLoader} from '../util/componentLoader'; 5 | 6 | interface RCRECheckboxProps extends BasicConnectProps { 7 | text: string; 8 | } 9 | 10 | class RCRECheckbox extends React.Component { 11 | render() { 12 | return ( 13 | { 17 | this.props.tools.updateNameValue(e.target.checked); 18 | this.props.tools.registerEvent('onChange', { 19 | value: e.target.checked, 20 | event: e 21 | }); 22 | }} 23 | /> 24 | ); 25 | } 26 | } 27 | 28 | componentLoader.addComponent('checkbox', commonConnect()(RCRECheckbox)); -------------------------------------------------------------------------------- /packages/rcre/src/data/reducers.ts: -------------------------------------------------------------------------------- 1 | import {combineReducers, Reducer} from 'redux'; 2 | import {IContainerState, containerReducer, TMP_MODEL} from '../core/Container/reducer'; 3 | import {TriggerState, triggerReducers} from '../core/Trigger/reducers'; 4 | // import {FormState, formReducer} from '../core/Form/reducers'; 5 | 6 | export interface RootState { 7 | $rcre: RCREState; 8 | } 9 | 10 | export interface RCREState { 11 | container: IContainerState; 12 | trigger: TriggerState; 13 | // form: FormState; 14 | } 15 | 16 | const appReducer = combineReducers({ 17 | container: containerReducer, 18 | trigger: triggerReducers, 19 | // form: formReducer 20 | }); 21 | 22 | export const rcreReducer: Reducer = (state, action) => { 23 | if (action.type === '_RESET_STORE_') { 24 | return { 25 | container: { 26 | [TMP_MODEL]: {} 27 | }, 28 | trigger: {} 29 | }; 30 | } 31 | 32 | return appReducer(state, action); 33 | }; 34 | -------------------------------------------------------------------------------- /test/rcre/core/Form/Form.test.tsx: -------------------------------------------------------------------------------- 1 | import {RCRETestUtil} from 'rcre-test-tools'; 2 | 3 | describe('Form', () => { 4 | it('init form', () => { 5 | let config = { 6 | body: [{ 7 | type: 'container', 8 | model: 'demo', 9 | data: { 10 | username: 'helloworld' 11 | }, 12 | children: [ 13 | { 14 | type: 'form', 15 | name: 'test', 16 | children: [ 17 | { 18 | type: 'text', 19 | text: '#ES{$data.username}' 20 | } 21 | ] 22 | } 23 | ] 24 | }] 25 | }; 26 | 27 | let test = new RCRETestUtil(config); 28 | let state = test.getFormState('test'); 29 | expect(state.name).toBe('test'); 30 | expect(state.valid).toBe(false); 31 | }); 32 | }); -------------------------------------------------------------------------------- /docs/recipes/use-with-redux.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | The state management of RCRE is based on Redux, so it can be 100% compatible with other Redux usages. 4 | 5 | ## combine RCRE's reducer 6 | 7 | RCRE has exposed all the built-in reducers, you just need to integrate it with your existing via `combineReducer`. 8 | 9 | Here is an example integrate RCRE's reducer with other reducers. 10 | 11 | ```jsx harmony 12 | import { combineReducers } from "redux"; 13 | import todos from "./todos"; 14 | import visibilityFilter from "./visibilityFilter"; 15 | import { rcreReducer } from "rcre"; 16 | 17 | export default combineReducers({ 18 | todos, 19 | visibilityFilter, 20 | $rcre: rcreReducer 21 | }); 22 | ``` 23 | 24 | ## How this works 25 | 26 | When you combine the RCRE's reducer with the reducer in your current application, all the data and state of the RCRE will only update the object pointed to by the $rcre key, without any impact on your existing data. 27 | 28 | You can safely integrate RCRE with your existing React application without worrying about some conflicts between them. -------------------------------------------------------------------------------- /packages/rcre/src/core/Events/dataProviderEvent.ts: -------------------------------------------------------------------------------- 1 | import {EventEmitter} from 'events'; 2 | 3 | export class DataProviderEvent extends EventEmitter { 4 | public stack: {key: string, done: boolean}[]; 5 | constructor() { 6 | super(); 7 | 8 | this.stack = []; 9 | } 10 | 11 | addToList(key: string) { 12 | this.stack.push({ 13 | key: key, 14 | done: false 15 | }); 16 | } 17 | 18 | setToDone(key: string) { 19 | let isFinished = true; 20 | 21 | for (let i = 0; i < this.stack.length; i ++) { 22 | let item = this.stack[i]; 23 | if (item.key === key) { 24 | this.stack[i].done = true; 25 | } 26 | 27 | if (!this.stack[i].done && isFinished) { 28 | isFinished = false; 29 | } 30 | } 31 | 32 | if (isFinished) { 33 | this.emit('done'); 34 | this.clear(); 35 | } 36 | } 37 | 38 | clear() { 39 | this.stack = []; 40 | this.removeAllListeners('done'); 41 | } 42 | } -------------------------------------------------------------------------------- /packages/rcre/global.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 全局定义文件 3 | * @author dongtiancheng 4 | */ 5 | declare interface Window { 6 | __REDUX_DEVTOOLS_EXTENSION__: any; 7 | __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: any; 8 | RCRE: any; 9 | RCRE019: any; 10 | RCRE_React: any; 11 | RCRE_ReactDOM: any; 12 | RCRE_VERSION: any; 13 | React: any; 14 | ReactDOM: any; 15 | RCRE_BasicContainer: any; 16 | RCRE_providerLoader: any; 17 | RCRE_customerLoader: any; 18 | RCRE_componentDriver: any; 19 | RCRE_AXIOS_REQUEST_COOKIE: string; 20 | RCRE_AXIOS_REQUEST_BASEURI: string; 21 | /** 22 | * 请求缓存,根据URL, method缓存接口请求,如果接口在参数相同的情况下,返回一定相同,则可以在测试环境中使用 23 | */ 24 | __RCRE_TEST_REQUEST_CACHE__: boolean; 25 | RCRE_filter: any; 26 | loadRightBar: () => void; 27 | RCRE_clearStore: any; 28 | } 29 | 30 | declare var __VERSION__: any; 31 | 32 | declare module 'deep-freeze-node' { 33 | function freeze(object: object): void; 34 | export = freeze; 35 | } 36 | 37 | interface System { 38 | import (request: string): Promise; 39 | } 40 | declare var System: System; -------------------------------------------------------------------------------- /docs/automatic-api/condition.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: condition 3 | title: Customize Request Condition 4 | --- 5 | 6 | Similar to requiredParams, condition can use function to customize the interface request, the function returns true to trigger the request, and returns false to stop the request. 7 | 8 | ```jsx harmony 9 | { 20 | if (/* some condition */) { 21 | return true; 22 | } 23 | 24 | return false; 25 | } 26 | } 27 | ]} 28 | /> 29 | ``` 30 | 31 | -------------------------------------------------------------------------------- /packages/rcre/src/core/DataCustomer/funcCustomer.ts: -------------------------------------------------------------------------------- 1 | import {FuncCustomerArgs} from './index'; 2 | 3 | export class FunsCustomerController { 4 | private store: Map) => any | Promise>; 5 | 6 | constructor() { 7 | this.store = new Map(); 8 | } 9 | 10 | public setCustomer(key: string, func: ($args: FuncCustomerArgs) => any) { 11 | if (this.store.has(key)) { 12 | throw new Error('found exist customer: ' + key); 13 | } 14 | 15 | this.store.set(key, func); 16 | } 17 | 18 | public getCustomer(key: string) { 19 | if (!this.store.has(key)) { 20 | console.error('can not find dataCustomer: ' + key); 21 | } 22 | 23 | return this.store.get(key); 24 | } 25 | 26 | public clearCustomer() { 27 | this.store.clear(); 28 | return this.store; 29 | } 30 | 31 | public getAllCustomerName(): Iterator { 32 | return this.store.keys(); 33 | } 34 | 35 | public delCustomer(funcName: string) { 36 | this.store.delete(funcName); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/rcre/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "es6", 5 | "lib": [ 6 | "es6", 7 | "dom", 8 | "es7" 9 | ], 10 | "sourceMap": true, 11 | "allowJs": false, 12 | "moduleResolution": "node", 13 | "forceConsistentCasingInFileNames": false, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "strictNullChecks": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "noUnusedLocals": true, 20 | "experimentalDecorators": true, 21 | "emitDecoratorMetadata": true, 22 | "jsx": "react", 23 | "allowSyntheticDefaultImports": true, 24 | "baseUrl": "./src", 25 | "declaration": true 26 | }, 27 | "exclude": [ 28 | "node_modules", 29 | "build", 30 | "scripts", 31 | "acceptance-tests", 32 | "webpack", 33 | "jest", 34 | "src/setupTests.ts" 35 | ], 36 | "types": [ 37 | "typePatches" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 AndyCALL 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/rcre-runtime/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "es6", 5 | "lib": [ 6 | "es6", 7 | "dom", 8 | "es7" 9 | ], 10 | "sourceMap": true, 11 | "allowJs": false, 12 | "moduleResolution": "node", 13 | "forceConsistentCasingInFileNames": false, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "strictNullChecks": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "noUnusedLocals": true, 20 | "experimentalDecorators": true, 21 | "emitDecoratorMetadata": true, 22 | "jsx": "react", 23 | "allowSyntheticDefaultImports": true, 24 | "baseUrl": "./src", 25 | "declaration": true 26 | }, 27 | "exclude": [ 28 | "node_modules", 29 | "build", 30 | "scripts", 31 | "acceptance-tests", 32 | "webpack", 33 | "jest", 34 | "src/setupTests.ts" 35 | ], 36 | "types": [ 37 | "typePatches" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /packages/rcre-test-tools/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "es6", 5 | "lib": [ 6 | "es6", 7 | "dom", 8 | "es7" 9 | ], 10 | "sourceMap": true, 11 | "allowJs": false, 12 | "moduleResolution": "node", 13 | "forceConsistentCasingInFileNames": false, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "strictNullChecks": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "noUnusedLocals": true, 20 | "experimentalDecorators": true, 21 | "emitDecoratorMetadata": true, 22 | "jsx": "react", 23 | "allowSyntheticDefaultImports": true, 24 | "baseUrl": "./src", 25 | "declaration": true 26 | }, 27 | "exclude": [ 28 | "node_modules", 29 | "build", 30 | "scripts", 31 | "acceptance-tests", 32 | "webpack", 33 | "jest", 34 | "src/setupTests.ts" 35 | ], 36 | "types": [ 37 | "typePatches" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /packages/rcre-runtime-syntax-transform/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "es6", 5 | "lib": [ 6 | "es6", 7 | "dom", 8 | "es7" 9 | ], 10 | "sourceMap": true, 11 | "allowJs": false, 12 | "moduleResolution": "node", 13 | "forceConsistentCasingInFileNames": false, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "strictNullChecks": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "noUnusedLocals": true, 20 | "experimentalDecorators": true, 21 | "emitDecoratorMetadata": true, 22 | "jsx": "react", 23 | "allowSyntheticDefaultImports": true, 24 | "baseUrl": "./src", 25 | "declaration": true 26 | }, 27 | "exclude": [ 28 | "node_modules", 29 | "build", 30 | "scripts", 31 | "acceptance-tests", 32 | "webpack", 33 | "jest", 34 | "src/setupTests.ts" 35 | ], 36 | "types": [ 37 | "typePatches" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /packages/rcre-runtime/src/parser/analyze/index.ts: -------------------------------------------------------------------------------- 1 | // import {parse} from 'acorn'; 2 | // import {walker} from './walker'; 3 | // 4 | // /** 5 | // * 分析调用代码的依赖 6 | // * @param {string} code 7 | // * @param {string} base 8 | // */ 9 | // export function analyzeCodeDependencies(code: string, base: string): string[] | null { 10 | // let ast = parse(code); 11 | // let body = ast.body; 12 | // 13 | // if (body.length === 0) { 14 | // return null; 15 | // } 16 | // 17 | // let firstLine = body[0]; 18 | // 19 | // if (firstLine.type === 'ImportDeclaration' || 20 | // firstLine.type === 'ExportNamedDeclaration' || 21 | // firstLine.type === 'ExportDefaultDeclaration' || 22 | // firstLine.type === 'ExportAllDeclaration') { 23 | // return null; 24 | // } 25 | // 26 | // let dependencies: string[] = []; 27 | // 28 | // switch (firstLine.type) { 29 | // case 'ExpressionStatement': 30 | // walker(firstLine.expression, dependencies, base); 31 | // break; 32 | // default: 33 | // return null; 34 | // } 35 | // 36 | // return dependencies; 37 | // } -------------------------------------------------------------------------------- /packages/rcre/src/core/Trigger/reducers.ts: -------------------------------------------------------------------------------- 1 | import {Reducer} from 'redux'; 2 | import {ITriggerAction, RESET_TRIGGER, TRIGGER_SET_DATA} from './actions'; 3 | import {setWith} from '../util/util'; 4 | 5 | export type TriggerState = any; 6 | 7 | export const triggerInitState: TriggerState = {}; 8 | 9 | export const triggerReducers: Reducer = (state: TriggerState = triggerInitState, actions: ITriggerAction): TriggerState => { 10 | switch (actions.type) { 11 | case TRIGGER_SET_DATA: { 12 | let payload = actions.payload; 13 | payload.forEach(pay => { 14 | let model = pay.model; 15 | 16 | if (!(model in state)) { 17 | state = setWith(state, model, {}); 18 | } 19 | 20 | let customer = pay.customer; 21 | let value = pay.data; 22 | 23 | state = setWith(state, model + '.' + customer, value); 24 | }); 25 | 26 | return state; 27 | } 28 | case RESET_TRIGGER: { 29 | state = {}; 30 | return state; 31 | } 32 | default: 33 | return state; 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /docs/tasks/custom-tasks.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: custom-tasks 3 | title: Custom Tasks 4 | --- 5 | 6 | Any function can be a task, the only need is an simple configuration. 7 | 8 | ```jsx harmony 9 | function sampleTask() { 10 | console.log('helloworld'); 11 | } 12 | 13 | 22 | ``` 23 | 24 | Then, you can use the `trigger.execTask` function from `` to trigger your task. 25 | 26 | ```jsx harmony 27 | {({$data}, {trigger}) => ( 28 | 35 | )} 36 | ``` 37 | 38 | The second param of execTask is the params of your task, if you trigger an taskMap, all tasks from the taskMap will share the same params. 39 | 40 | You can access your params in any task: 41 | 42 | ```jsx harmony 43 | function sampleTask({params}) { 44 | console.log('this is the params of an button click', params); // { username: 'value from $data' } 45 | } 46 | ``` -------------------------------------------------------------------------------- /docs/automatic-api/required-params.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: required-params 3 | title: Required Request Params 4 | --- 5 | 6 | Each interface will only listen for changes to the config attribute by default. 7 | 8 | If you need to depends on some data in the Container state before the sending the request, you can use the requiredParams property. 9 | 10 | The requiredParams property value type is array. Each item in the array is the key from Container State. 11 | 12 | Key also support Lodash path syntax. 13 | 14 | ```jsx harmony 15 | 30 | ``` 31 | 32 | -------------------------------------------------------------------------------- /packages/rcre/src/core/Hosts/Form.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {BasicConfig, BasicProps} from '../../types'; 3 | import {FormProps, RCREForm} from '../Form/Form'; 4 | import {componentLoader} from '../util/componentLoader'; 5 | import {createChild} from '../util/createChild'; 6 | import {FormContext} from '../context'; 7 | 8 | function JSONForm(props: FormProps & BasicProps) { 9 | let children = (props.children || []).map((child: BasicConfig, index: number) => { 10 | return createChild(child, { 11 | key: index 12 | }); 13 | }); 14 | 15 | return ( 16 | 17 | 18 | {context => { 19 | return ( 20 |
    { 22 | context.$handleSubmit(event); 23 | }} 24 | > 25 | {children} 26 |
    27 | ); 28 | }} 29 |
    30 | 31 |
    32 | ); 33 | } 34 | 35 | componentLoader.addComponent('form', JSONForm); -------------------------------------------------------------------------------- /packages/rcre/src/core/Hosts/FormItem.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {BasicConfig} from '../../types'; 3 | import {RCREFormItem, RCREFormItemProps} from '../Form/FormItem'; 4 | import {componentLoader} from '../util/componentLoader'; 5 | import {createChild} from '../util/createChild'; 6 | 7 | class JSONFormItem extends React.PureComponent { 8 | static getComponentParseOptions() { 9 | return { 10 | blackList: ['filterRule', 'filterErrMsg', 'validation'] 11 | }; 12 | } 13 | 14 | render() { 15 | let children = []; 16 | 17 | if (Array.isArray(this.props.control)) { 18 | children = this.props.control; 19 | } else if (this.props.control) { 20 | children = [this.props.control]; 21 | } 22 | 23 | children = children.map((child: BasicConfig, index: number) => { 24 | return createChild(child, { 25 | key: child.type + '_' + index 26 | }); 27 | }); 28 | 29 | return ( 30 | 31 | {children} 32 | 33 | ); 34 | } 35 | } 36 | 37 | componentLoader.addComponent('formItem', JSONFormItem); -------------------------------------------------------------------------------- /packages/rcre/src/core/DataProvider/applications/cookie.ts: -------------------------------------------------------------------------------- 1 | import {ProviderSourceConfig, runTimeType} from '../../../types'; 2 | import {SyncAdaptor} from '../adaptors/sync'; 3 | 4 | interface CookieConfig extends ProviderSourceConfig { 5 | config: string[]; 6 | } 7 | 8 | function getAllCookie() { 9 | let cookies = document.cookie; 10 | let result = {}; 11 | let list = cookies.split(';'); 12 | 13 | list.forEach(item => { 14 | let cookie = item.split('='); 15 | let key = cookie[0].replace(/\s+/, ''); 16 | result[key] = decodeURIComponent(cookie[1]); 17 | }); 18 | 19 | return result; 20 | } 21 | 22 | export class CookieAdaptor extends SyncAdaptor { 23 | exec(config: string[], provider: CookieConfig, runTime: runTimeType) { 24 | let items = config; 25 | let ret = {}; 26 | let cookies = getAllCookie(); 27 | 28 | items.forEach(key => { 29 | let result = cookies[key]; 30 | 31 | if (typeof result === 'string') { 32 | try { 33 | result = JSON.parse(result); 34 | } catch (e) {} 35 | } 36 | 37 | ret[key] = result; 38 | }); 39 | 40 | return ret; 41 | } 42 | } -------------------------------------------------------------------------------- /crowdin.yaml: -------------------------------------------------------------------------------- 1 | project_identifier_env: CROWDIN_DOCUSAURUS_PROJECT_ID 2 | api_key_env: CROWDIN_DOCUSAURUS_API_KEY 3 | base_path: "./" 4 | preserve_hierarchy: true 5 | 6 | files: 7 | - 8 | source: '/docs/**/*.md' 9 | translation: '/website/translated_docs/%locale%/**/%original_file_name%' 10 | languages_mapping: &anchor 11 | locale: 12 | 'af': 'af' 13 | 'ar': 'ar' 14 | 'bs-BA': 'bs-BA' 15 | 'ca': 'ca' 16 | 'cs': 'cs' 17 | 'da': 'da' 18 | 'de': 'de' 19 | 'el': 'el' 20 | 'es-ES': 'es-ES' 21 | 'fa': 'fa-IR' 22 | 'fi': 'fi' 23 | 'fr': 'fr' 24 | 'he': 'he' 25 | 'hu': 'hu' 26 | 'id': 'id-ID' 27 | 'it': 'it' 28 | 'ja': 'ja' 29 | 'ko': 'ko' 30 | 'mr': 'mr-IN' 31 | 'nl': 'nl' 32 | 'no': 'no-NO' 33 | 'pl': 'pl' 34 | 'pt-BR': 'pt-BR' 35 | 'pt-PT': 'pt-PT' 36 | 'ro': 'ro' 37 | 'ru': 'ru' 38 | 'sk': 'sk-SK' 39 | 'sr': 'sr' 40 | 'sv-SE': 'sv-SE' 41 | 'tr': 'tr' 42 | 'uk': 'uk' 43 | 'vi': 'vi' 44 | 'zh-CN': 'zh-CN' 45 | 'zh-TW': 'zh-TW' 46 | - 47 | source: '/website/i18n/en.json' 48 | translation: '/website/i18n/%locale%.json' 49 | languages_mapping: *anchor 50 | -------------------------------------------------------------------------------- /docs/automatic-api/response-rewrite.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: response-rewrite 3 | title: Rewrite Response Data Before SetState 4 | --- 5 | 6 | When you need to process the data returned by the interface and then write it to the Container, you can use the `responseRewrite` property. 7 | 8 | The `responseRewrite` property need an object. 9 | 10 | Each property value is a function that is used to calculate the value of this property. The object of `responseRewrite` will eventually merge with the state in the Container. 11 | 12 | You can use the `$output` variable to read the value of the interface. 13 | 14 | ```jsx harmony 15 | $output.data.username 28 | } 29 | } 30 | ]} 31 | /> 32 | ``` 33 | 34 | -------------------------------------------------------------------------------- /packages/rcre/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rcre", 3 | "description": "Build complex applications without tears", 4 | "version": "0.21.17", 5 | "main": "./dist/index.js", 6 | "typings": "./dist/src/index.d.ts", 7 | "repository": "https://github.com/andycall/RCRE", 8 | "homepage": "/", 9 | "proxy": { 10 | "/proxy": { 11 | "target": "http://localhost:8844" 12 | }, 13 | "/api": { 14 | "target": "http://localhost:8844" 15 | } 16 | }, 17 | "files": [ 18 | "dist" 19 | ], 20 | "dependencies": { 21 | "axios": "^0.16.2", 22 | "bowser": "1.9.4", 23 | "classnames": "^2.2.5", 24 | "create-react-context": "^0.2.3", 25 | "deep-freeze-node": "^1.1.3", 26 | "events": "^3.0.0", 27 | "lodash": "^4.17.4", 28 | "lru-cache": "5.1.1", 29 | "moment": "^2.20.1", 30 | "prop-types": "^15.7.2", 31 | "qs": "^6.5.2", 32 | "rcre-runtime": "^0.21.0", 33 | "react": ">=16", 34 | "react-dom": ">=16", 35 | "react-lifecycles-compat": "^3.0.4", 36 | "react-redux": "^5.0.7", 37 | "redux": "^3.7.2", 38 | "redux-undo": "^0.6.1", 39 | "warning": "^4.0.3" 40 | }, 41 | "devDependencies": { 42 | "chalk": "^2.4.1" 43 | }, 44 | "license": "MIT" 45 | } 46 | -------------------------------------------------------------------------------- /packages/rcre/src/core/Trigger/actions.ts: -------------------------------------------------------------------------------- 1 | export const TRIGGER_SET_DATA = 'RCRE_TRIGGER_SET_DATA'; 2 | export const RESET_TRIGGER = 'RCRE_RESET_TRIGGER'; 3 | 4 | export type TRIGGER_SET_DATA_OPTIONS = { 5 | /** 6 | * 是否在发生错误的时候持续执行 7 | */ 8 | keepWhenError?: boolean; 9 | 10 | /** 11 | * 阻止表单最终的提交 12 | */ 13 | preventSubmit?: boolean; 14 | }; 15 | 16 | export type TRIGGER_SET_DATA_PAYLOAD = { 17 | /** 18 | * 数据模型Key 19 | */ 20 | model: string; 21 | /** 22 | * customer的名称 23 | */ 24 | customer: string; 25 | /** 26 | * customer的数据 27 | */ 28 | data: Object; 29 | 30 | /** 31 | * 额外配置功能 32 | */ 33 | options?: TRIGGER_SET_DATA_OPTIONS 34 | }; 35 | 36 | export type ITriggerActions = { 37 | TRIGGER_SET_DATA: { 38 | type: typeof TRIGGER_SET_DATA, 39 | payload: TRIGGER_SET_DATA_PAYLOAD[]; 40 | }, 41 | RESET_TRIGGER: { 42 | type: typeof RESET_TRIGGER 43 | } 44 | }; 45 | 46 | export type ITriggerAction = ITriggerActions[keyof ITriggerActions]; 47 | 48 | export const formActions = { 49 | triggerSetData: (payload: TRIGGER_SET_DATA_PAYLOAD[]) => ({ 50 | type: TRIGGER_SET_DATA as typeof TRIGGER_SET_DATA, 51 | payload 52 | }), 53 | resetTrigger: () => ({ 54 | type: RESET_TRIGGER as typeof RESET_TRIGGER 55 | }) 56 | }; 57 | -------------------------------------------------------------------------------- /examples/counter/src/components/Counter.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | class Counter extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.incrementAsync = this.incrementAsync.bind(this); 8 | this.incrementIfOdd = this.incrementIfOdd.bind(this); 9 | } 10 | 11 | incrementIfOdd() { 12 | if (this.props.value % 2 !== 0) { 13 | this.props.onIncrement() 14 | } 15 | } 16 | 17 | incrementAsync() { 18 | setTimeout(this.props.onIncrement, 1000) 19 | } 20 | 21 | render() { 22 | const {value, onIncrement, onDecrement} = this.props 23 | return ( 24 |

    25 | Clicked: {value} times 26 | {' '} 27 | 30 | {' '} 31 | 34 | {' '} 35 | 38 | {' '} 39 | 42 |

    43 | ) 44 | } 45 | } 46 | 47 | Counter.propTypes = { 48 | value: PropTypes.number.isRequired, 49 | onIncrement: PropTypes.func.isRequired, 50 | onDecrement: PropTypes.func.isRequired 51 | } 52 | 53 | export default Counter 54 | -------------------------------------------------------------------------------- /examples/immutable/src/components/Counter.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | class Counter extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.incrementAsync = this.incrementAsync.bind(this); 8 | this.incrementIfOdd = this.incrementIfOdd.bind(this); 9 | } 10 | 11 | incrementIfOdd() { 12 | if (this.props.value % 2 !== 0) { 13 | this.props.onIncrement() 14 | } 15 | } 16 | 17 | incrementAsync() { 18 | setTimeout(this.props.onIncrement, 1000) 19 | } 20 | 21 | render() { 22 | const {value, onIncrement, onDecrement} = this.props 23 | return ( 24 |

    25 | Clicked: {value} times 26 | {' '} 27 | 30 | {' '} 31 | 34 | {' '} 35 | 38 | {' '} 39 | 42 |

    43 | ) 44 | } 45 | } 46 | 47 | Counter.propTypes = { 48 | value: PropTypes.number.isRequired, 49 | onIncrement: PropTypes.func.isRequired, 50 | onDecrement: PropTypes.func.isRequired 51 | } 52 | 53 | export default Counter 54 | -------------------------------------------------------------------------------- /packages/rcre-runtime/src/parser/analyze/walker.ts: -------------------------------------------------------------------------------- 1 | // import {Expression} from 'estree'; 2 | // import {memberExpressionWalker} from './memberExpression'; 3 | // 4 | // export function walker(exp: Expression, dependencies: string[], base: string) { 5 | // switch (exp.type) { 6 | // case 'MemberExpression': { 7 | // memberExpressionWalker(exp); 8 | // break; 9 | // } 10 | // // case 'Literal': 11 | // // case 'Identifier': 12 | // // case 'BinaryExpression': 13 | // // case 'ConditionalExpression': 14 | // // case 'ObjectExpression': 15 | // // case 'ThisExpression': 16 | // // case 'ArrayExpression': 17 | // // case 'UnaryExpression': 18 | // // case 'UpdateExpression': 19 | // // case 'LogicalExpression': 20 | // // case 'CallExpression': 21 | // // case 'NewExpression': 22 | // // 23 | // case 'AssignmentExpression': 24 | // case 'TemplateLiteral': 25 | // case 'ClassExpression': 26 | // case 'MetaProperty': 27 | // case 'TaggedTemplateExpression': 28 | // case 'AwaitExpression': 29 | // case 'FunctionExpression': 30 | // case 'ArrowFunctionExpression': 31 | // case 'YieldExpression': 32 | // case 'SequenceExpression': 33 | // default: 34 | // return; 35 | // } 36 | // } -------------------------------------------------------------------------------- /packages/rcre/src/core/util/stringToPath.ts: -------------------------------------------------------------------------------- 1 | import {memoize} from 'lodash'; 2 | 3 | const charCodeOfDot = '.'.charCodeAt(0); 4 | const reEscapeChar = /\\(\\)?/g; 5 | const patten = /[^.[\]]+|\[(?:([^"'].*)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g; 6 | 7 | const MAX_MEMOIZE_SIZE = 500; 8 | function memoizeCapped(func: (...args: any[]) => any[]) { 9 | const result = memoize(func, (key) => { 10 | const { cache } = result; 11 | if (cache['size'] === MAX_MEMOIZE_SIZE && typeof cache.clear === 'function') { 12 | cache.clear(); 13 | } 14 | return key; 15 | }); 16 | 17 | return result; 18 | } 19 | 20 | export const stringToPath: (str: string) => string[] = memoizeCapped((str: string) => { 21 | const result: string[] = []; 22 | if (str.charCodeAt(0) === charCodeOfDot) { 23 | result.push(''); 24 | } 25 | // @ts-ignore 26 | str.replace(patten, (match, expression, quote, subString) => { 27 | let key = match; 28 | if (quote) { 29 | key = subString.replace(reEscapeChar, '$1'); 30 | } 31 | 32 | if (/\]\[/.test(key)) { 33 | let groups = key.match(/\[\d+\]/g); 34 | if (groups) { 35 | groups.forEach(k => result.push(k)); 36 | } 37 | } else { 38 | result.push(key); 39 | } 40 | }); 41 | return result; 42 | }); -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "setupTestFrameworkScriptFile": "/test/setupTests.ts", 3 | "coverageDirectory": "/coverage", 4 | "moduleFileExtensions": [ 5 | "ts", 6 | "tsx", 7 | "js", 8 | "jsx", 9 | "json" 10 | ], 11 | "testMatch": [ 12 | "/test/**/__tests__/**/*.ts?(x)", 13 | "/test/**/?(*.)(spec|test).ts?(x)" 14 | ], 15 | "testEnvironment": "node", 16 | "testURL": "http://localhost", 17 | "transform": { 18 | "\\.(css|less|sass|scss)$": "/__mocks__/styleMock.js", 19 | "\\.tsx?$": "ts-jest" 20 | }, 21 | "transformIgnorePatterns": [ 22 | "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$" 23 | ], 24 | "collectCoverageFrom": [ 25 | "/packages/**/*.{tsx,ts}", 26 | "!/packages/**/*.d.{tsx,ts}" 27 | ], 28 | "moduleNameMapper": { 29 | "^rcre$": "/packages/rcre/src/index.tsx", 30 | "^rcre-runtime$": "/packages/rcre-runtime/src/index.ts", 31 | "^rcre-test-tools$": "/packages/rcre-test-tools/src/index.tsx", 32 | "^rcre-runtime-syntax-transform$": "/packages/rcre-runtime-syntax-transform/src/index.ts", 33 | "^rcre-syntax-jsx$": "/packages/rcre-syntax-jsx/src/index.tsx" 34 | }, 35 | "globals": { 36 | "__VERSION__": "0.1.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/counter/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {RCREProvider, Container, ES, addEventListener} from 'rcre'; 4 | import Counter from './components/Counter'; 5 | 6 | import "./styles.css"; 7 | 8 | // Add redux middleware to log actions 9 | addEventListener('RCRE_SET_DATA', (action) => { 10 | let name = action.payload.name; 11 | let value = action.payload.value; 12 | console.log('Container RCRE_SET_DATA action: name:' + name + ', value: ' + value); 13 | }); 14 | 15 | function App() { 16 | return ( 17 |
    18 | 19 | 25 |

    The Async Example

    26 | 27 | {({$data}, context) => ( 28 | { 31 | context.container.$setData('count', $data.count + 1) 32 | }} 33 | onDecrement={() => { 34 | context.container.$setData('count', $data.count - 1) 35 | }} 36 | /> 37 | )} 38 | 39 |
    40 |
    41 |
    42 | ); 43 | } 44 | 45 | const rootElement = document.getElementById("root"); 46 | ReactDOM.render(, rootElement); 47 | -------------------------------------------------------------------------------- /scripts/rollup/watch.js: -------------------------------------------------------------------------------- 1 | const rollup = require('rollup'); 2 | const config = require('./config'); 3 | const chalk = require('chalk'); 4 | 5 | let modules = Object.keys(config); 6 | let queue = modules; 7 | 8 | 9 | function watch(key) { 10 | let inputOptions = config[key].inputOptions; 11 | let outputOptions = config[key].outputOptions; 12 | 13 | const watchOptions = { 14 | ...inputOptions, 15 | output: [outputOptions], 16 | watch: { 17 | clearScreen: false, 18 | exclude: 'node_modules/**', 19 | include: 'packages/**' 20 | } 21 | }; 22 | 23 | let watcher = rollup.watch(watchOptions); 24 | 25 | watcher.on('event', event => { 26 | switch(event.code) { 27 | case 'START': 28 | console.log(chalk.yellow(key + ' Compiling...')); 29 | break; 30 | case 'FATAL': 31 | case 'ERROR': 32 | console.log(chalk.red(key + ' Compile Error')); 33 | console.log(event.error); 34 | break; 35 | case 'BUNDLE_END': 36 | console.log(chalk.green(key + ' Compiled Success')); 37 | run(); 38 | break; 39 | } 40 | }); 41 | } 42 | 43 | function run() { 44 | if (queue.length === 0) { 45 | return; 46 | } 47 | 48 | let key = modules.shift(); 49 | watch(key); 50 | } 51 | 52 | run(); 53 | -------------------------------------------------------------------------------- /packages/rcre/src/core/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React, {ErrorInfo} from 'react'; 2 | 3 | interface ErrorBoundaryProps {} 4 | 5 | interface ErrorBoundaryState { 6 | error: Error | null; 7 | errorInfo: ErrorInfo | null; 8 | } 9 | 10 | export class ErrorBoundary extends React.Component { 11 | constructor(props: ErrorBoundaryProps) { 12 | super(props); 13 | this.state = { error: null, errorInfo: null }; 14 | } 15 | 16 | componentDidCatch(error: Error, errorInfo: ErrorInfo) { 17 | // Catch errors in any components below and re-render with error message 18 | this.setState({ 19 | error: error, 20 | errorInfo: errorInfo 21 | }); 22 | // You can also log error messages to an error reporting service here 23 | } 24 | 25 | render() { 26 | if (this.state.errorInfo) { 27 | return ( 28 |
    29 |

    Component render Error.

    30 |
    31 | {this.state.error && this.state.error.toString()} 32 |
    33 | {this.state.errorInfo.componentStack} 34 |
    35 |
    36 | ); 37 | } 38 | // Normally, just render children 39 | return this.props.children; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/rcre-test-tools/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [0.21.15](https://github.com/andycall/RCRE/compare/v0.21.14...v0.21.15) (2019-08-01) 7 | 8 | **Note:** Version bump only for package rcre-test-tools 9 | 10 | 11 | 12 | 13 | 14 | ## [0.21.9](https://github.com/andycall/RCRE/compare/v0.21.8...v0.21.9) (2019-05-21) 15 | 16 | **Note:** Version bump only for package rcre-test-tools 17 | 18 | 19 | 20 | 21 | 22 | ## [0.21.8](https://github.com/andycall/RCRE/compare/v0.21.7...v0.21.8) (2019-05-21) 23 | 24 | **Note:** Version bump only for package rcre-test-tools 25 | 26 | 27 | 28 | 29 | 30 | ## [0.21.7](https://github.com/andycall/RCRE/compare/v0.21.6...v0.21.7) (2019-05-21) 31 | 32 | **Note:** Version bump only for package rcre-test-tools 33 | 34 | 35 | 36 | 37 | 38 | ## [0.21.5](https://github.com/andycall/RCRE/compare/v0.21.4...v0.21.5) (2019-05-15) 39 | 40 | **Note:** Version bump only for package rcre-test-tools 41 | 42 | 43 | 44 | 45 | 46 | ## [0.21.4](https://github.com/andycall/RCRE/compare/v0.21.3...v0.21.4) (2019-05-13) 47 | 48 | **Note:** Version bump only for package rcre-test-tools 49 | 50 | 51 | 52 | 53 | 54 | # [0.21.0](https://github.com/andycall/RCRE/compare/v0.20.14...v0.21.0) (2019-05-13) 55 | 56 | **Note:** Version bump only for package rcre-test-tools 57 | -------------------------------------------------------------------------------- /packages/rcre/src/core/Service/observer.test.ts: -------------------------------------------------------------------------------- 1 | import {ObserverTrack} from './observer'; 2 | 3 | describe('Observer', () => { 4 | it('nest arr', () => { 5 | let data = { 6 | name: { 7 | age: '1234', 8 | arr: [0] 9 | } 10 | }; 11 | 12 | let track = new ObserverTrack(data); 13 | let observer = track.getObserver(); 14 | 15 | track.exec(() => observer.name.arr[0]); 16 | expect(track.path[0]).toBe('name.arr[0]'); 17 | }); 18 | 19 | it('nest Object', () => { 20 | let data = { 21 | name: { 22 | age: '1234' 23 | } 24 | }; 25 | let track = new ObserverTrack(data); 26 | let observer = track.getObserver(); 27 | 28 | track.exec(() => observer.name.age); 29 | expect(track.path[0]).toBe('name.age'); 30 | }); 31 | 32 | it('two access in one exec function', () => { 33 | let data = { 34 | name: { 35 | age: '1234', 36 | height: 5555 37 | } 38 | }; 39 | 40 | let track = new ObserverTrack(data); 41 | let observer = track.getObserver(); 42 | 43 | track.exec(() => { 44 | return [ 45 | observer.name.age, 46 | observer.name.height 47 | ]; 48 | }); 49 | 50 | expect(track.path[0]).toBe('name.age'); 51 | expect(track.path[1]).toBe('name.height'); 52 | }); 53 | }); -------------------------------------------------------------------------------- /docs/form/dynamic-rules.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: dynamic-rules 3 | title: Dynamic Rules 4 | --- 5 | 6 | You can use the `` component the do the component validation. 7 | 8 | But form validation is not always static, it may be dynamic and is calculated based on the current state. 9 | 10 | This section will describe how to deal with this situation, 11 | 12 | Here is an example: 13 | 14 | ```jsx harmony 15 | 16 | {({ $data }) => ( 17 | 26 | {({ valid, errmsg }, { $handleBlur }) => ( 27 | 33 | 34 | 35 | )} 36 | 37 | )} 38 | 39 | ``` 40 | 41 | In this example, we use the `` component to read the state in the Container and pass it to the validation rules of the FormItem, which forms a dynamic validation. 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /docs/form/custom-validation.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: custom-validation 3 | title: Custom Validation 4 | --- 5 | 6 | Business logic is changeable, you can't do all the functions with built-in validation, so you can implement custom validation by using a function. 7 | 8 | You can do this using the `validation` property of the RCREFormItem component. 9 | 10 | ```jsx harmony 11 | { 14 | // use $args.value to access 15 | if ($args.value !== "123456") { 16 | return { 17 | isValid: false, 18 | errmsg: "data should equal to 123456" 19 | }; 20 | } 21 | return { 22 | isValid: true, 23 | errmsg: "" 24 | }; 25 | }} 26 | > 27 | {({ valid, errmsg }) => ( 28 | 34 | 35 | 36 | )} 37 | 38 | ``` 39 | 40 | Within the validation function, you can use the `$data` variable to access Container's data. 41 | 42 | Online example: 43 | 44 | 45 | -------------------------------------------------------------------------------- /packages/rcre/src/core/DataCustomer/customers/pass.ts: -------------------------------------------------------------------------------- 1 | import {CustomerParams} from '../index'; 2 | import {containerActionCreators} from '../../Container/action'; 3 | import {compileExpressionString, isExpression, parseExpressionString} from '../../util/vm'; 4 | 5 | export interface PassCustomerExecConfig { 6 | /** 7 | * 目标container组件的key 8 | */ 9 | model: string; 10 | 11 | /** 12 | * 写入的值 13 | */ 14 | assign: Object | string; 15 | } 16 | 17 | export function passCustomer(config: PassCustomerExecConfig, params: CustomerParams) { 18 | let targetContainerModel = config.model; 19 | let assign = config.assign; 20 | let { 21 | runTime 22 | } = params; 23 | let output; 24 | 25 | if (isExpression(targetContainerModel)) { 26 | targetContainerModel = parseExpressionString(targetContainerModel, runTime); 27 | } 28 | 29 | if (isExpression(assign)) { 30 | output = parseExpressionString(assign, runTime); 31 | } else { 32 | output = compileExpressionString(assign, runTime); 33 | } 34 | 35 | if (!output) { 36 | throw new Error('pass output is not valid, please check your ExpressionString'); 37 | } 38 | 39 | params.rcreContext.store.dispatch(containerActionCreators.dataCustomerPass({ 40 | model: targetContainerModel, 41 | data: output 42 | }, { 43 | rcre: params.rcreContext, 44 | container: params.containerContext, 45 | iterator: params.iteratorContext 46 | })); 47 | 48 | return true; 49 | } 50 | -------------------------------------------------------------------------------- /website/pages/en/users.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | const React = require('react'); 9 | 10 | const CompLibrary = require('../../core/CompLibrary.js'); 11 | 12 | const Container = CompLibrary.Container; 13 | 14 | class Users extends React.Component { 15 | render() { 16 | const {config: siteConfig} = this.props; 17 | if ((siteConfig.users || []).length === 0) { 18 | return null; 19 | } 20 | 21 | const editUrl = `${siteConfig.repoUrl}/edit/master/website/siteConfig.js`; 22 | const showcase = siteConfig.users.map(user => ( 23 | 24 | {user.caption} 25 | 26 | )); 27 | 28 | return ( 29 |
    30 | 31 |
    32 |
    33 |

    Who is Using This?

    34 |

    This project is used by many folks

    35 |
    36 |
    {showcase}
    37 |

    Are you using this project?

    38 | 39 | Add your company 40 | 41 |
    42 |
    43 |
    44 | ); 45 | } 46 | } 47 | 48 | module.exports = Users; 49 | -------------------------------------------------------------------------------- /packages/rcre/src/core/util/withAllContext.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react'; 2 | import {ContainerContext, FormItemContext, RCREContext, TriggerContext} from '../context'; 3 | import {RCRETrigger} from '../Trigger/Trigger'; 4 | 5 | export const withAllContext: any = (Component: any) => ( 6 | (props: any) => { 7 | let rcreContext = useContext(RCREContext); 8 | let containerContext = useContext(ContainerContext); 9 | let formItemContext = useContext(FormItemContext); 10 | 11 | let children = ( 12 | 13 | {triggerContext => { 14 | return ; 21 | }} 22 | 23 | ); 24 | 25 | if (rcreContext.mode === 'json') { 26 | return children; 27 | } 28 | 29 | return ( 30 | 37 | {children} 38 | 39 | ); 40 | } 41 | ); 42 | -------------------------------------------------------------------------------- /examples/counter/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 21 | React App 22 | 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/immutable/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 21 | React App 22 | 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/pass-tasks/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 21 | React App 22 | 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/task-group/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 21 | React App 22 | 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/combine-search/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 21 | React App 22 | 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/form-submit/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 21 | React App 22 | 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/multi-container/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 21 | React App 22 | 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/simple-search/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 21 | React App 22 | 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/component-auto-clear/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 21 | React App 22 | 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/todos/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 21 | React App 22 | 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/container-inheritance/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 21 | React App 22 | 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/state-management/container-init-value.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: container-init-value 3 | title: Container Init Value 4 | --- 5 | 6 | The Container component can use the `data` attribute to give the component initial value. 7 | 8 | ```jsx harmony 9 | 15 | ``` 16 | 17 | After all component is initialized, the state: 18 | ```json 19 | { 20 | "demo": { 21 | "name": "helloworld" 22 | } 23 | } 24 | ``` 25 | 26 | If the name value of the component is the same as the key of the initialization value, the component will also get the initial value at initialization time. 27 | 28 | 29 | 30 | ## Dynamic initialization 31 | 32 | Each value in the data property can be an anonymous function. 33 | 34 | This anonymous function is not passed to the component as a normal function. Instead, it is executed after the component is initialized, and the return value of the function is taken as the initial state of the component. 35 | 36 | ```jsx harmony 37 | 'helloworld ' + $data.other 42 | }} 43 | /> 44 | ``` 45 | 46 | After all component is initialized, the state: 47 | ```json 48 | { 49 | "demo": { 50 | "other": "other value", 51 | "name": "helloworld other value" 52 | } 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/state-management/cross-container-assignment.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: cross-container-assignment 3 | title: Cross Container Assignment 4 | --- 5 | 6 | You can use the built-in pass task to pass data to another Container component. 7 | 8 | 9 | 10 | ## Container Build-In Pass Task 11 | 12 | The Container component will have 2 built-in pass tasks. 13 | 14 | 1. $this 15 | 2. $parent 16 | 17 | ### The $this task 18 | 19 | The $this task refers to the Container component attached to the current component, so in the event callback, you can use `trigger.execTask('$this', {})` to pass the value to the current Container component. 20 | 21 | ```jsx harmony 22 | {({$data}, context) => ( 23 | 26 | )} 27 | ``` 28 | 29 | ### The $parent task 30 | 31 | The $parent task refers to the parent Container component of the Container component attached to the current component, so in the event callback, you can use `trigger.execTask('$parent', {})` to pass the value to the parent Container Component. 32 | 33 | ```jsx harmony 34 | {({$data}, context) => ( 35 | 38 | )} 39 | ``` -------------------------------------------------------------------------------- /packages/rcre/src/core/Hosts/Container.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import '../Container/Container.css'; 3 | import {BasicConfig} from '../../types'; 4 | import {ConnectContainerProps, RCREContainer} from '../Container/Container'; 5 | import {componentLoader} from '../util/componentLoader'; 6 | import {createChild} from '../util/createChild'; 7 | 8 | class JSONContainer extends React.PureComponent { 9 | static getComponentParseOptions() { 10 | return { 11 | blackList: ['props', 'export', 'dataProvider', 'dataCustomer'], 12 | }; 13 | } 14 | 15 | render() { 16 | const props = this.props; 17 | let children = (props.children || []).map((child: BasicConfig, index: number) => { 18 | let elements = createChild(child, { 19 | key: `${child.type}_${index}` 20 | }); 21 | 22 | if (props.rcreContext.debug) { 23 | const containerStyle = { 24 | border: props.rcreContext.debug ? '1px dashed #3398FC' : '', 25 | }; 26 | 27 | return ( 28 |
    29 | container: {props.model} 30 | {elements} 31 |
    32 | ); 33 | } 34 | 35 | return elements; 36 | }); 37 | 38 | return ( 39 | 42 | {children} 43 | 44 | ); 45 | } 46 | } 47 | 48 | componentLoader.addComponent('container', JSONContainer, '__BUILDIN__'); 49 | -------------------------------------------------------------------------------- /packages/rcre/src/core/Layout/Div/Div.tsx: -------------------------------------------------------------------------------- 1 | import {CSSProperties} from 'react'; 2 | import * as React from 'react'; 3 | import {BasicConnectProps} from "../../Connect/basicConnect"; 4 | import {commonConnect} from '../../Connect/Common/Common'; 5 | import {createChild} from '../../util/createChild'; 6 | import {componentLoader} from '../../util/componentLoader'; 7 | 8 | export interface DivProps extends BasicConnectProps { 9 | children: any[]; 10 | style?: CSSProperties; 11 | } 12 | 13 | function JSONDiv(props: DivProps) { 14 | let children = props.children; 15 | 16 | if (!(children instanceof Array)) { 17 | children = []; 18 | } 19 | 20 | const buildInStyle = { 21 | width: '100%', 22 | outline: props.tools.rcreContext.debug ? '1px dashed #B8B8B8' : '' 23 | }; 24 | 25 | return ( 26 |
    props.tools.triggerContext && props.tools.triggerContext.eventHandle('onClick', event)} 32 | onMouseDown={(event) => props.tools.triggerContext && props.tools.triggerContext.eventHandle('onMouseDown', event)} 33 | onMouseUp={(event) => props.tools.triggerContext && props.tools.triggerContext.eventHandle('onMouseUp', event)} 34 | {...props} 35 | > 36 | { 37 | children.map((child, key) => { 38 | return createChild(child, { 39 | key: key 40 | }); 41 | }) 42 | } 43 |
    44 | ); 45 | } 46 | 47 | componentLoader.addComponent('div', commonConnect()(JSONDiv), '__BUILDIN__'); -------------------------------------------------------------------------------- /docs/form/automatic-api-validation.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: automatic-api-validation 3 | title: Automatic API Validation 4 | --- 5 | 6 | Verification can also be verified through the interface, using the apiRule attribute to do this. 7 | 8 | ```jsx harmony 9 | $args.value 17 | }, 18 | // an pattern to determine the query is correct 19 | validate: ({ $output }) => $output.errno === 0, 20 | // read errmsg from the API 21 | errmsg: ({ $output }) => $output.errmsg, 22 | // used to set value to Container State from validation API 23 | export: { 24 | user_id: ({ $output }) => $output.data.userId 25 | } 26 | }} 27 | > 28 | {({ valid, errmsg, validating }) => { 29 | return ( 30 | 43 | 44 | 45 | ); 46 | }} 47 | 48 | ``` 49 | 50 | Online example: 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /packages/rcre/src/core/Hosts/Input.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {BasicConnectProps, commonConnect} from '../Connect'; 3 | import {componentLoader} from '../util/componentLoader'; 4 | 5 | export interface RCREInputProps extends BasicConnectProps {} 6 | 7 | class RCREInput extends React.PureComponent { 8 | render() { 9 | let { 10 | tools, 11 | value, 12 | ...props 13 | } = this.props; 14 | 15 | return ( 16 | { 20 | tools.updateNameValue(e.target.value); 21 | tools.registerEvent('onChange', { 22 | event: e, 23 | value: e.target.value 24 | }); 25 | }} 26 | onAbort={e => { 27 | tools.registerEvent('onAbort', { 28 | event: e 29 | }); 30 | }} 31 | onBlur={e => { 32 | tools.registerEvent('onBlur', { 33 | event: e 34 | }); 35 | }} 36 | onFocus={e => { 37 | tools.registerEvent('onFocus', { 38 | event: e 39 | }); 40 | }} 41 | onClick={e => { 42 | tools.registerEvent('onClick', { 43 | event: e 44 | }); 45 | }} 46 | /> 47 | ); 48 | } 49 | } 50 | 51 | componentLoader.addComponent('input', commonConnect({ 52 | })(RCREInput)); -------------------------------------------------------------------------------- /packages/rcre/src/jsx-support/FormItem.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {FormItemContext} from '../core/context'; 3 | import {FormItemProps, RCREFormItem as _RCREFormItem, RCREFormItemProps} from '../core/Form/FormItem'; 4 | import {withAllContext} from '../core/util/withAllContext'; 5 | import {FormItemContextType} from '../types'; 6 | 7 | type FormItemParams = { 8 | valid: boolean; 9 | errmsg: string; 10 | validating: boolean; 11 | }; 12 | type FormItemChildFunc = (params: FormItemParams, context: FormItemContextType) => any; 13 | 14 | interface FormItemComponentProps extends FormItemProps { 15 | children: React.ReactElement | FormItemChildFunc; 16 | } 17 | 18 | const WrappedRCREFormItem = withAllContext(_RCREFormItem); 19 | 20 | class FormItemComponents extends React.PureComponent { 21 | render() { 22 | return ( 23 | 24 | {formItemContext => { 25 | let children; 26 | let $formItem = formItemContext.$formItem; 27 | 28 | if (typeof this.props.children === 'function') { 29 | children = this.props.children($formItem, formItemContext); 30 | } else { 31 | children = this.props.children; 32 | } 33 | 34 | return children; 35 | }} 36 | 37 | 38 | ); 39 | } 40 | } 41 | 42 | class DummyFormItem extends React.PureComponent { 43 | } 44 | 45 | export const RCREFormItem = (FormItemComponents as any) as typeof DummyFormItem; -------------------------------------------------------------------------------- /test/rcre/parser/walker.test.ts: -------------------------------------------------------------------------------- 1 | import {Walker} from 'rcre-runtime'; 2 | 3 | describe('Walker', () => { 4 | const code = `var example = 'helloworld'; 5 | console.log(example); 6 | `; 7 | const walker = new Walker(code); 8 | 9 | it('charCode()', () => { 10 | expect(walker.charCode(0)).toBe(118); 11 | }); 12 | 13 | it('currentNode()', () => { 14 | expect(walker.currentCode()).toBe(118); 15 | walker.go(1); 16 | expect(walker.currentCode()).toBe(97); 17 | }); 18 | 19 | it('cut(0, 1)', () => { 20 | expect(walker.cut(0, 1)).toBe('v'); 21 | }); 22 | 23 | it('go(1)', () => { 24 | walker.reset(); 25 | walker.go(2); 26 | expect(walker.currentCode()).toBe(114); 27 | }); 28 | 29 | it('isEnd()', () => { 30 | walker.go(code.length); 31 | expect(walker.isEnd()).toBe(true); 32 | }); 33 | 34 | it('nextCode()', () => { 35 | walker.reset(); 36 | expect(walker.currentCode()).toBe(118); 37 | expect(walker.nextCode()).toBe(97); 38 | expect(walker.nextCode()).toBe(114); 39 | }); 40 | 41 | it('findCharUtil()', () => { 42 | walker.reset(); 43 | 44 | walker.findCharUtil('='); 45 | expect(walker.currentCode()).toBe(61); 46 | }); 47 | 48 | it('findCharUtil("{", "}")', () => { 49 | const str = '{{{name: 1}}}'; 50 | const walk = new Walker(str); 51 | walk.findCharUtil('}', '{'); 52 | expect(str.substring(0, walk.index + 1)).toBe(str); 53 | }); 54 | 55 | it('findStrUtil()', () => { 56 | walker.reset(); 57 | 58 | walker.findStrUtil('helloworld'); 59 | expect(walker.currentCode()).toBe(39); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/rcre-runtime/src/runTime/evaluation.ts: -------------------------------------------------------------------------------- 1 | import {parse} from 'acorn'; 2 | import * as ESTree from 'estree'; 3 | import {Statement} from 'estree'; 4 | import {execExpression} from './execExpression'; 5 | 6 | const ASTCache: Map = new Map(); 7 | 8 | function execStatement(statement: Statement, context: Object) { 9 | switch (statement.type) { 10 | case 'ExpressionStatement': 11 | return execExpression(statement.expression, context); 12 | default: 13 | throw new Error(`type: ${statement.type} is not supported`); 14 | } 15 | } 16 | 17 | export function evaluation(code: string, context: Object) { 18 | let ast = ASTCache.get(code); 19 | 20 | if (!ast) { 21 | ast = parse(code); 22 | ASTCache.set(code, ast); 23 | } 24 | 25 | let body = ast.body; 26 | 27 | if (body.length === 0) { 28 | return null; 29 | } 30 | 31 | let firstLine = body[0]; 32 | 33 | if (firstLine.type === 'ImportDeclaration' || 34 | firstLine.type === 'ExportNamedDeclaration' || 35 | firstLine.type === 'ExportDefaultDeclaration' || 36 | firstLine.type === 'ExportAllDeclaration') { 37 | throw new Error('module declaration is not supported in expressionString'); 38 | } 39 | 40 | if (firstLine.type === 'ExpressionStatement') { 41 | let exp = firstLine.expression; 42 | 43 | if (exp.type === 'Identifier') { 44 | let name = exp.name; 45 | 46 | if (context && !(name in context)) { 47 | throw new TypeError(name + ' is not defined'); 48 | } 49 | 50 | return context[name]; 51 | } 52 | } 53 | 54 | return execStatement(firstLine, context); 55 | } 56 | -------------------------------------------------------------------------------- /packages/rcre/src/data/events.ts: -------------------------------------------------------------------------------- 1 | import {RootState} from './reducers'; 2 | import {IContainerAction} from '../core/Container'; 3 | import {ITriggerAction} from '../core/Trigger'; 4 | 5 | export interface ListenerFnItem { 6 | (action: IContainerAction & ITriggerAction, state: RootState, prevState: RootState): void; 7 | } 8 | 9 | interface Listener { 10 | [eventName: string]: ListenerFnItem[]; 11 | } 12 | 13 | let listeners: Listener = {}; 14 | 15 | export function addEventListener(eventName: string, fn: ListenerFnItem) { 16 | if (!listeners[eventName]) { 17 | listeners[eventName] = []; 18 | } 19 | 20 | listeners[eventName].push(fn); 21 | } 22 | 23 | export function removeEventListener(eventName: string, fn: ListenerFnItem) { 24 | if (!listeners[eventName]) { 25 | return; 26 | } 27 | 28 | for (let i = 0 ; i < listeners[eventName].length; i ++) { 29 | if (listeners[eventName][i] === fn) { 30 | delete listeners[eventName][i]; 31 | } 32 | } 33 | } 34 | 35 | export function removeAllEventListener() { 36 | listeners = {}; 37 | } 38 | 39 | export function removeAllEventListenerByEventName(eventName: string) { 40 | delete listeners[eventName]; 41 | } 42 | 43 | export const triggerEvents = (store: any) => (next: any) => (action: any) => { 44 | let type = action.type; 45 | 46 | if (Array.isArray(listeners[type]) && listeners[type].length > 0) { 47 | // 确保事件触发完成时,state已经被更新 48 | // reducer 内部都要识同步操作,异步都要在触发action之前完成 49 | let prevState = store.getState(); 50 | Promise.resolve().then(() => { 51 | listeners[type].forEach(fn => fn(action, store.getState(), prevState)); 52 | }); 53 | } 54 | 55 | return next(action); 56 | }; 57 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es6", 4 | "target": "es6", 5 | "lib": [ 6 | "es6", 7 | "dom", 8 | "es7" 9 | ], 10 | "sourceMap": true, 11 | "allowJs": false, 12 | "moduleResolution": "node", 13 | "forceConsistentCasingInFileNames": false, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "strictNullChecks": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "noUnusedLocals": true, 20 | "experimentalDecorators": true, 21 | "emitDecoratorMetadata": true, 22 | "esModuleInterop": true, 23 | "jsx": "react", 24 | "allowSyntheticDefaultImports": true, 25 | "declaration": true, 26 | "baseUrl": "./", 27 | "paths": { 28 | "rcre-test-tools": [ 29 | "packages/rcre-test-tools/src/index.tsx" 30 | ], 31 | "rcre-runtime-syntax-transform": [ 32 | "packages/rcre-runtime-syntax-transform/src/index.ts" 33 | ], 34 | "rcre": [ 35 | "packages/rcre/src/index.tsx" 36 | ], 37 | "rcre-runtime": [ 38 | "packages/rcre-runtime/src/index.ts" 39 | ], 40 | "rcre-syntax-jsx": [ 41 | "packages/rcre-syntax-jsx/src/index.tsx" 42 | ] 43 | } 44 | }, 45 | "exclude": [ 46 | "node_modules", 47 | "build", 48 | "scripts", 49 | "acceptance-tests", 50 | "webpack", 51 | "jest", 52 | "test/setupTests.ts" 53 | ], 54 | "types": [ 55 | "typePatches" 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /website/pages/en/help.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | const React = require('react'); 9 | 10 | const CompLibrary = require('../../core/CompLibrary.js'); 11 | 12 | const Container = CompLibrary.Container; 13 | const GridBlock = CompLibrary.GridBlock; 14 | 15 | function Help(props) { 16 | const {config: siteConfig, language = ''} = props; 17 | const {baseUrl, docsUrl} = siteConfig; 18 | const docsPart = `${docsUrl ? `${docsUrl}/` : ''}`; 19 | const langPart = `${language ? `${language}/` : ''}`; 20 | const docUrl = doc => `${baseUrl}${docsPart}${langPart}${doc}`; 21 | 22 | const supportLinks = [ 23 | { 24 | content: `Learn more using the [documentation on this site.](${docUrl( 25 | 'doc1.html', 26 | )})`, 27 | title: 'Browse Docs', 28 | }, 29 | { 30 | content: 'Ask questions about the documentation and project', 31 | title: 'Join the community', 32 | }, 33 | { 34 | content: "Find out what's new with this project", 35 | title: 'Stay up to date', 36 | }, 37 | ]; 38 | 39 | return ( 40 |
    41 | 42 |
    43 |
    44 |

    Need help?

    45 |
    46 |

    This project is maintained by a dedicated group of people.

    47 | 48 |
    49 |
    50 |
    51 | ); 52 | } 53 | 54 | module.exports = Help; 55 | -------------------------------------------------------------------------------- /docs/tasks/pass-task.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: pass-task 3 | title: The Pass Task 4 | --- 5 | 6 | RCRE give you a build-in task to complete cross-container data assignment. 7 | 8 | ## Define a Pass Task 9 | 10 | It's very easy to define a pass task. 11 | 12 | ```jsx harmony 13 | $trigger.passToDemo2.username 26 | } 27 | } 28 | }] 29 | }} 30 | /> 31 | ``` 32 | 33 | ## Use a Pass Task 34 | 35 | Use the `trigger.execTask` function provided by `` component to call your task. 36 | 37 | ```jsx harmony 38 | {({$data}, {trigger}) => ( 39 | 46 | )} 47 | ``` 48 | 49 | ## Demo 50 | 51 | -------------------------------------------------------------------------------- /examples/multi-container/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {RCREProvider, Container, ES, createReduxStore} from 'rcre'; 4 | 5 | import "./styles.css"; 6 | import {Input} from "./components/Input"; 7 | 8 | const store = createReduxStore(); 9 | 10 | function App() { 11 | return ( 12 |
    13 | 14 |

    Use Multi Container Components as multi reducers

    15 |

    Container has their own namespaces, components with the same name will not affect each other

    16 |
    17 |
    demo 1 Container
    18 | 19 | UserName: 20 | {({$name, $value}, context) => ( 21 | context.container.$setData($name, event.target.value)}/> 22 | )} 23 | 24 |
    Components in the same container will share the same name
    25 | 26 |
    demo1 state value: {({$data}) => {JSON.stringify($data)}}
    27 |
    28 |
    29 |
    30 |
    demo 2 Container
    31 | 32 | UserName: 33 | 34 |
    demo1 state value: {({$data}) => {JSON.stringify($data)}}
    35 |
    36 |
    37 |
    38 |
    39 | ); 40 | } 41 | 42 | const rootElement = document.getElementById("root"); 43 | ReactDOM.render(, rootElement); 44 | -------------------------------------------------------------------------------- /examples/component-auto-clear/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {RCREProvider, Container, ES, addEventListener} from 'rcre'; 4 | 5 | import "./styles.css"; 6 | import {Input} from "./components/Input"; 7 | 8 | function App() { 9 | return ( 10 |
    11 | 12 | 19 |

    Component Will AutoClear When destroy

    20 |
    21 | {({$data}, context) => { 22 | if ($data.hideUserName) { 23 | return null; 24 | } 25 | 26 | return 27 | }} 28 | {({$data}, context) => ( 29 | 32 | )} 33 |
    34 | 35 | {({$data}, context) => ( 36 |
    37 | 38 | auto clear: {$data.clear ? 'true' : 'false'} 39 |
    40 | )}
    41 | 42 |

    State Value

    43 | {({$data}) => ( 44 |
    {JSON.stringify($data)}
    45 | )}
    46 |
    47 |
    48 |
    49 | ); 50 | } 51 | 52 | const rootElement = document.getElementById("root"); 53 | ReactDOM.render(, rootElement); 54 | -------------------------------------------------------------------------------- /test/rcre/core/ExternalComponent.test.tsx: -------------------------------------------------------------------------------- 1 | import {ES, componentLoader} from 'rcre'; 2 | import {RCRETestUtil} from 'rcre-test-tools'; 3 | import React from 'react'; 4 | 5 | describe('External Component', () => { 6 | it('Component with ES Wrapper Can directly render by JSON', () => { 7 | class Input extends React.Component { 8 | render() { 9 | return ( 10 | 11 | {({$data}, context) => { 12 | return ( 13 | context.container.$setData(this.props.name, event.target.value)} 16 | /> 17 | ); 18 | }} 19 | 20 | ); 21 | } 22 | } 23 | 24 | componentLoader.addComponent('ESInput', Input); 25 | 26 | let config = { 27 | body: [{ 28 | type: 'container', 29 | model: 'demo', 30 | children: [ 31 | { 32 | type: 'ESInput', 33 | name: 'username' 34 | }, 35 | { 36 | type: 'text', 37 | text: '#ES{$data.username}' 38 | } 39 | ] 40 | }] 41 | }; 42 | 43 | let test = new RCRETestUtil(config); 44 | test.setContainer('demo'); 45 | let username = test.getComponentByName('username'); 46 | test.setData(username, 'helloworld'); 47 | 48 | let state = test.getContainerState(); 49 | expect(state.username).toBe('helloworld'); 50 | }); 51 | }); -------------------------------------------------------------------------------- /packages/rcre-runtime/src/runTime/index.ts: -------------------------------------------------------------------------------- 1 | import {isPlainObject} from 'lodash'; 2 | import {evaluation} from './evaluation'; 3 | import LRUCache from 'lru-cache'; 4 | import moment from 'moment'; 5 | import * as _ from 'lodash'; 6 | 7 | export * from './evaluation'; 8 | 9 | const runTimeCache: LRUCache> = new LRUCache({ 10 | max: 300 11 | }); 12 | 13 | export const Global = { 14 | Object: Object, 15 | Array: Array, 16 | String: String, 17 | Number: Number, 18 | RegExp: RegExp, 19 | Boolean: Boolean, 20 | Date: Date, 21 | Math: Math, 22 | prompt: prompt, 23 | parseInt: parseInt, 24 | parseFloat: parseFloat, 25 | encodeURI: encodeURI, 26 | decodeURI: decodeURI, 27 | encodeURIComponent: encodeURIComponent, 28 | decodeURIComponent: decodeURIComponent, 29 | JSON: JSON, 30 | $moment: moment, 31 | $now: moment(), 32 | _: _, 33 | console: console 34 | }; 35 | 36 | /** 37 | * 安全运行沙箱 38 | * 39 | * @param {string} code 40 | * @param {Object} context 41 | * @returns {any} 42 | */ 43 | export function runInContext(code: string, context: Object) { 44 | if (!isPlainObject(context)) { 45 | throw new TypeError('context argument must be an object'); 46 | } 47 | 48 | if (!code) { 49 | throw new TypeError('code must be a evaluable string'); 50 | } 51 | 52 | if (!runTimeCache.has(code)) { 53 | runTimeCache.set(code, new LRUCache({ 54 | max: 50 55 | })); 56 | } 57 | 58 | let contextCache = runTimeCache.get(code); 59 | if (!contextCache) { 60 | throw new Error('LRU Cache error'); 61 | } 62 | 63 | if (!contextCache.has(context)) { 64 | let result = evaluation('(' + code + ')', context); 65 | contextCache.set(context, result); 66 | return result; 67 | } else { 68 | return contextCache.get(context); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/rcre/core/Hosts/Input.test.tsx: -------------------------------------------------------------------------------- 1 | import {clearStore} from 'rcre'; 2 | import {RCRETestUtil} from 'rcre-test-tools'; 3 | 4 | describe('Input', () => { 5 | beforeEach(() => { 6 | clearStore(); 7 | }); 8 | 9 | it('name key', () => { 10 | let config = { 11 | body: [{ 12 | type: 'container', 13 | model: 'demo', 14 | children: [{ 15 | type: 'input', 16 | name: 'username' 17 | }] 18 | }] 19 | }; 20 | 21 | let test = new RCRETestUtil(config); 22 | test.setContainer('demo'); 23 | let username = test.getComponentByName('username'); 24 | test.setData(username, 'helloworld'); 25 | let state = test.getContainerState(); 26 | expect(state.username).toBe('helloworld'); 27 | test.unmount(); 28 | }); 29 | 30 | it('onChange event', async () => { 31 | let config = { 32 | body: [{ 33 | type: 'container', 34 | model: 'demo', 35 | children: [{ 36 | type: 'input', 37 | name: 'username', 38 | trigger: [{ 39 | event: 'onChange', 40 | targetCustomer: '$this', 41 | params: { 42 | other: '12345' 43 | } 44 | }] 45 | }] 46 | }] 47 | }; 48 | 49 | let test = new RCRETestUtil(config); 50 | test.setContainer('demo'); 51 | let username = test.getComponentByName('username'); 52 | test.setData(username, 'helloworld'); 53 | await test.simulate(username, 'onChange'); 54 | let state = test.getContainerState(); 55 | expect(state.username).toBe('helloworld'); 56 | expect(state.other).toBe('12345'); 57 | test.unmount(); 58 | }); 59 | }); -------------------------------------------------------------------------------- /packages/rcre/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [0.21.15](https://github.com/andycall/RCRE/compare/v0.21.14...v0.21.15) (2019-08-01) 7 | 8 | 9 | ### Bug Fixes 10 | 11 | * **rcre:** fix foreach try to mutate original state ([ed0857c](https://github.com/andycall/RCRE/commit/ed0857c)) 12 | 13 | 14 | 15 | 16 | 17 | ## [0.21.9](https://github.com/andycall/RCRE/compare/v0.21.8...v0.21.9) (2019-05-21) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * fix failed unit tests ([0768327](https://github.com/andycall/RCRE/commit/0768327)) 23 | 24 | 25 | 26 | 27 | 28 | ## [0.21.8](https://github.com/andycall/RCRE/compare/v0.21.7...v0.21.8) (2019-05-21) 29 | 30 | 31 | ### Bug Fixes 32 | 33 | * fix formItem validating status did not match component life circle ([5230bb6](https://github.com/andycall/RCRE/commit/5230bb6)) 34 | 35 | 36 | 37 | 38 | 39 | ## [0.21.7](https://github.com/andycall/RCRE/compare/v0.21.6...v0.21.7) (2019-05-21) 40 | 41 | 42 | ### Bug Fixes 43 | 44 | * add validation API instead of filterRule, filterErrMsg ([6436576](https://github.com/andycall/RCRE/commit/6436576)) 45 | 46 | 47 | 48 | 49 | 50 | ## [0.21.5](https://github.com/andycall/RCRE/compare/v0.21.4...v0.21.5) (2019-05-15) 51 | 52 | 53 | ### Bug Fixes 54 | 55 | * fix dataProvider deps analyse algorith ([bb588a8](https://github.com/andycall/RCRE/commit/bb588a8)) 56 | 57 | 58 | 59 | 60 | 61 | ## [0.21.4](https://github.com/andycall/RCRE/compare/v0.21.3...v0.21.4) (2019-05-13) 62 | 63 | 64 | ### Bug Fixes 65 | 66 | * fix crash when try to access an deleted formItem ([60e5e7d](https://github.com/andycall/RCRE/commit/60e5e7d)) 67 | 68 | 69 | 70 | 71 | 72 | # [0.21.0](https://github.com/andycall/RCRE/compare/v0.20.14...v0.21.0) (2019-05-13) 73 | 74 | 75 | ### Features 76 | 77 | * fix Container init value ([b3259ae](https://github.com/andycall/RCRE/commit/b3259ae)) 78 | -------------------------------------------------------------------------------- /packages/rcre/src/core/Events/index.tsx: -------------------------------------------------------------------------------- 1 | import {EventEmitter} from 'events'; 2 | import {EventNode} from './Node'; 3 | import {values} from 'lodash'; 4 | 5 | export class Events extends EventEmitter { 6 | public roots: EventNode[]; 7 | private nodes: { 8 | [model: string]: EventNode 9 | }; 10 | private eventTick: { 11 | [event: string]: { 12 | [model: string]: boolean; 13 | } 14 | }; 15 | 16 | constructor() { 17 | super(); 18 | this.roots = []; 19 | this.nodes = {}; 20 | this.eventTick = {}; 21 | this.eventDispatcher = this.eventDispatcher.bind(this); 22 | } 23 | 24 | private getKeyCount(object: object) { 25 | return Object.keys(object).length; 26 | } 27 | 28 | private eventDispatcher(model: string, event: string, ...args: any[]) { 29 | if (!this.eventTick[event]) { 30 | this.eventTick[event] = {}; 31 | } 32 | 33 | this.eventTick[event][model] = true; 34 | this.dispatchMasterEvent(event); 35 | } 36 | 37 | private dispatchMasterEvent(event: string) { 38 | let tick = this.eventTick[event]; 39 | let nodeLength = this.getKeyCount(this.nodes); 40 | let triggedNodes = this.getKeyCount(tick); 41 | 42 | if (nodeLength !== triggedNodes) { 43 | return; 44 | } 45 | 46 | let tickValue = values(tick); 47 | if (tickValue.every(t => t)) { 48 | this.emit(event); 49 | delete this.eventTick[event]; 50 | } 51 | } 52 | 53 | public mountContainer(model: string, parent?: string) { 54 | let node = new EventNode(model); 55 | 56 | node.on('_SECRET_EVENT_', this.eventDispatcher); 57 | 58 | this.nodes[model] = node; 59 | 60 | // if (!parent) { 61 | // this.roots.push(node); 62 | // } else { 63 | // let parentNode = this.nodes[parent]; 64 | // node.parent = parentNode; 65 | // parentNode.childs.push(node); 66 | // } 67 | 68 | return node; 69 | } 70 | } -------------------------------------------------------------------------------- /packages/rcre/src/core/DataCustomer/customers/localStorage.ts: -------------------------------------------------------------------------------- 1 | import {CustomerParams} from '../index'; 2 | import {isObjectLike} from 'lodash'; 3 | import {isExpression, parseExpressionString, safeStringify} from '../../util/vm'; 4 | 5 | export interface PassCustomerExecConfig { 6 | /** 7 | * 设置的值 8 | */ 9 | groups: {key: string; value: string}[]; 10 | 11 | /** 12 | * 动作 13 | */ 14 | action?: 'SET' | 'DELETE'; 15 | } 16 | 17 | export async function localStoreCustomer(config: PassCustomerExecConfig, params: CustomerParams) { 18 | let groups = config.groups; 19 | let { 20 | runTime 21 | } = params; 22 | 23 | if (isExpression(groups)) { 24 | groups = parseExpressionString(groups, runTime); 25 | } 26 | 27 | if (groups instanceof Array) { 28 | return groups.map(group => { 29 | let key = group.key; 30 | let value = group.value; 31 | 32 | if (isExpression(key)) { 33 | key = parseExpressionString(key, runTime); 34 | } 35 | 36 | if (isExpression(value)) { 37 | value = parseExpressionString(value, runTime); 38 | } 39 | 40 | if (isObjectLike(value)) { 41 | value = safeStringify(value); 42 | } 43 | 44 | let action = config.action || 'SET'; 45 | 46 | switch (action) { 47 | default: 48 | case 'SET': 49 | try { 50 | localStorage.setItem(key, value); 51 | } catch (e) { 52 | console.error(e); 53 | } 54 | break; 55 | case 'DELETE': 56 | try { 57 | localStorage.removeItem(key); 58 | } catch (e) { 59 | console.error(e); 60 | } 61 | break; 62 | } 63 | 64 | return {key: key, value: value}; 65 | }); 66 | } else { 67 | throw new Error('LocalStorage的groups需要是一个数组'); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /website/pages/en/help-with-translations.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | const React = require('react'); 9 | 10 | const CompLibrary = require('../../core/CompLibrary.js'); 11 | 12 | const Container = CompLibrary.Container; 13 | const GridBlock = CompLibrary.GridBlock; 14 | 15 | const translate = require('../../server/translate.js').translate; 16 | 17 | class Help extends React.Component { 18 | render() { 19 | const supportLinks = [ 20 | { 21 | content: ( 22 | 23 | Learn more using the [documentation on this 24 | site.](/test-site/docs/en/doc1.html) 25 | 26 | ), 27 | title: Browse Docs, 28 | }, 29 | { 30 | content: ( 31 | 32 | Ask questions about the documentation and project 33 | 34 | ), 35 | title: Join the community, 36 | }, 37 | { 38 | content: Find out what's new with this project, 39 | title: Stay up to date, 40 | }, 41 | ]; 42 | 43 | return ( 44 |
    45 | 46 |
    47 |
    48 |

    49 | Need help? 50 |

    51 |
    52 |

    53 | 54 | This project is maintained by a dedicated group of people. 55 | 56 |

    57 | 58 |
    59 |
    60 |
    61 | ); 62 | } 63 | } 64 | 65 | Help.defaultProps = { 66 | language: 'en', 67 | }; 68 | 69 | module.exports = Help; 70 | -------------------------------------------------------------------------------- /packages/rcre/src/index.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | import react from 'react'; 3 | import reactdom from 'react-dom'; 4 | import {filter} from './core/util/filter'; 5 | import {createChild} from './core/util/createChild'; 6 | import * as vm from './core/util/vm'; 7 | 8 | import './index.css'; 9 | import * as Connect from './core/Connect'; 10 | import * as types from './types'; 11 | import {Render, JSONRender} from './core/JSONRender'; 12 | 13 | import './core/Layout/Div/Div'; 14 | import './core/Layout/Text/Text'; 15 | import './core/Layout/ForEach/Foreach'; 16 | import './core/Hosts'; 17 | import './core/Layout/Row/Row'; 18 | 19 | export * from './types'; 20 | export * from './core/util/util'; 21 | export * from './core/Service/api'; 22 | export * from './core/Events'; 23 | export * from './core/Events/dataProviderEvent'; 24 | export * from './core/DataCustomer/index'; 25 | export * from './core/DataProvider/index'; 26 | export * from './core/util/componentLoader'; 27 | export * from './core/util/stringToPath'; 28 | export * from './data/events'; 29 | export * from './core/RCREProvider'; 30 | export * from './core/context'; 31 | export * from './core/Trigger'; 32 | export * from './core/ErrorBoundary'; 33 | export * from './jsx-support'; 34 | export * from './data/reducers'; 35 | export * from './data/store'; 36 | export * from './core/externalApi'; 37 | 38 | let hasWarn = false; 39 | export function clearStore() { 40 | if (!hasWarn) { 41 | hasWarn = true; 42 | console.warn('clearStore is deprecated, please remove it from your code'); 43 | } 44 | } 45 | 46 | export { 47 | createChild, 48 | filter, 49 | vm, 50 | Render, 51 | JSONRender, 52 | Connect, 53 | types 54 | }; 55 | 56 | if (process.env.REMOTE_DEBUG) { 57 | let script = document.querySelector('script'); 58 | let url; 59 | 60 | if (script) { 61 | url = script.src; 62 | } 63 | 64 | let msg = `you are in remote debug mode, please copy and past into your webpages.`; 65 | console.log(msg); 66 | } 67 | 68 | export const version = __VERSION__; 69 | export const React = react; 70 | export const ReactDOM = reactdom; 71 | -------------------------------------------------------------------------------- /packages/rcre/src/core/DataProvider/applications/ajax.ts: -------------------------------------------------------------------------------- 1 | import {ProviderSourceConfig, runTimeType} from '../../../types'; 2 | import {AsyncAdaptor, AsyncAdaptorRetValue} from '../adaptors/async'; 3 | import {AxiosRequestConfig, AxiosResponse} from 'axios'; 4 | import {isNil, clone} from 'lodash'; 5 | import {request} from '../../Service/api'; 6 | import {isExpression, parseExpressionString} from '../../util/vm'; 7 | 8 | type AjaxConfig = AxiosRequestConfig & { 9 | keepEmptyData?: boolean; 10 | }; 11 | 12 | export class AjaxAdaptor extends AsyncAdaptor { 13 | async exec(config: AjaxConfig, provider: ProviderSourceConfig, runTime: runTimeType): Promise { 14 | if (!config.url) { 15 | throw new Error('AjaxAdaptor: url is required param for ajax call'); 16 | } 17 | 18 | let data = config.data; 19 | if (!config.keepEmptyData) { 20 | data = clone(config.data); 21 | for (let key in data) { 22 | if (isNil(data[key]) || data[key] === '') { 23 | delete data[key]; 24 | } 25 | } 26 | } 27 | 28 | try { 29 | let response: AxiosResponse = await request(config.url!, { 30 | ...config, 31 | data: data 32 | }); 33 | return { 34 | success: true, 35 | errmsg: '', 36 | data: response.data 37 | }; 38 | } catch (e) { 39 | let errResponse = e.response; 40 | let $output; 41 | // axios response data 42 | if (errResponse && errResponse.data) { 43 | $output = errResponse.data; 44 | } 45 | 46 | let errmsg = provider.retErrMsg || e.message; 47 | 48 | if (isExpression(errmsg)) { 49 | errmsg = parseExpressionString(errmsg, { 50 | ...runTime, 51 | $output: $output 52 | }); 53 | } 54 | 55 | return { 56 | success: false, 57 | errmsg: errmsg, 58 | data: null 59 | }; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /examples/simple-search/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {RCREProvider, Container, ES, DataCustomer} from 'rcre'; 4 | 5 | import "./styles.css"; 6 | 7 | DataCustomer.errorHandler = (error) => { 8 | console.error('request failed', error); 9 | }; 10 | 11 | function App() { 12 | return ( 13 |
    14 | 15 | `https://api.github.com/repos/${$data.user_repo}`, 23 | method: 'GET', 24 | export: { 25 | userName: ({$output}) => $output.owner.login, 26 | star: ({$output}) => $output.stargazers_count 27 | } 28 | } 29 | }] 30 | }} 31 | > 32 |

    Github Repo Star

    33 | {({$value, $name}, context) => ( 34 | 38 | context.container.$setData($name, event.target.value) 39 | } 40 | /> 41 | )} 42 | {({$value, $name}, context) => ( 43 | 44 | )} 45 | 46 | {({$data}) => { 47 | if ($data.$loading) { 48 | return
    loading...
    ; 49 | } 50 | 51 | if ($data.$error) { 52 | return
    {$data.$error.message}
    53 | } 54 | 55 | return ( 56 |
      57 |
    • UserName: {$data.userName}
    • 58 |
    • Star: {$data.star}
    • 59 |
    60 | ) 61 | }}
    62 |
    63 |
    64 |
    65 | ); 66 | } 67 | 68 | const rootElement = document.getElementById("root"); 69 | ReactDOM.render(, rootElement); 70 | -------------------------------------------------------------------------------- /examples/immutable/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {RCREProvider, Container, ES, createReduxStore, addEventListener} from 'rcre'; 4 | import Counter from './components/Counter'; 5 | 6 | import "./styles.css"; 7 | 8 | const store = createReduxStore(); 9 | 10 | addEventListener('RCRE_SET_DATA', (action, state, prevState) => { 11 | let name = action.payload.name; 12 | let value = action.payload.value; 13 | console.log('Container RCRE_SET_DATA action: name:' + name + ', value: ' + value); 14 | 15 | if (!prevState) { 16 | return; 17 | } 18 | 19 | console.log('container equals: ', state.$rcre.container.test === prevState.$rcre.container.test); 20 | console.log('root equals: ', state.$rcre.container.test.root === prevState.$rcre.container.test.root); 21 | console.log('A equals: ', state.$rcre.container.test.root.A === prevState.$rcre.container.test.root.A); 22 | console.log('a equals: ', state.$rcre.container.test.root.A.a === prevState.$rcre.container.test.root.A.a); 23 | console.log('count equals: ', state.$rcre.container.test.root.A.a.count === prevState.$rcre.container.test.root.A.a.count); 24 | }); 25 | 26 | function App() { 27 | return ( 28 |
    29 | 30 | 42 |

    The Immutable test

    43 | 44 | {({$value, $name}, context) => ( 45 | { 48 | context.container.$setData($name, $value + 1) 49 | }} 50 | onDecrement={() => { 51 | context.container.$setData($name, $value - 1) 52 | }} 53 | /> 54 | )} 55 | 56 |
    57 |
    58 |
    59 | ); 60 | } 61 | 62 | const rootElement = document.getElementById("root"); 63 | ReactDOM.render(, rootElement); 64 | -------------------------------------------------------------------------------- /docs/automatic-api/serial-and-parallel-api.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: serial-and-parallel-api 3 | title: Serial And Parallel Request 4 | --- 5 | 6 | If you need to call multiple interfaces on a Container component, you can do this by adding multiple interface configurations to the DataProvider property. 7 | 8 | RCRE will automatically help you analyze the dependency of each interface on the data and generate a request queue. 9 | 10 | In the queue, those interfaces that do not have pre-dependencies will be ranked first, while those that depend on other interfaces will be placed behind the interfaces that are dependent. 11 | 12 | So you don't need to know how to call them, just provide the calling conditions for each interface. 13 | 14 | 15 | ```jsx harmony 16 | 52 | ``` 53 | 54 | So if there are 4 interfaces, according to the above configuration, then the order of the interface requests will be 55 | 56 | ```text 57 | --> API 1---> API2 --> API3 58 | | | 59 | |-- API 4 -- 60 | ``` 61 | 62 | API1 and API4 will use Promise.all to call in parallel. Since API2 and API3 have pre-interface dependencies, they will be serial calls. 63 | 64 | -------------------------------------------------------------------------------- /packages/rcre/src/core/util/componentLoader.tsx: -------------------------------------------------------------------------------- 1 | import {isEmpty} from 'lodash'; 2 | 3 | let defaultLoadMode = 'default'; 4 | 5 | export function setDefaultLoadMode(mode: string) { 6 | defaultLoadMode = mode; 7 | } 8 | 9 | const buildInModule = [ 10 | 'container', 11 | 'div', 12 | 'row', 13 | 'text' 14 | ]; 15 | 16 | interface ComponentCache { 17 | [type: string]: { 18 | [mode: string]: any; 19 | }; 20 | } 21 | 22 | export class ComponentLoader { 23 | private cache: ComponentCache; 24 | 25 | constructor() { 26 | this.cache = {}; 27 | } 28 | 29 | getComponent(type: string, mode?: string) { 30 | if (buildInModule.includes(type)) { 31 | return this.cache[type]['__BUILDIN__']; 32 | } 33 | 34 | if (!mode) { 35 | mode = defaultLoadMode; 36 | } 37 | 38 | let item = this.cache[type]; 39 | 40 | if (item && !item[mode]) { 41 | mode = defaultLoadMode; 42 | } 43 | 44 | if (!item || !item[mode]) { 45 | console.log(defaultLoadMode); 46 | throw new Error('can not find module, type:' + type + '; mode: ' + mode); 47 | } 48 | 49 | return item[mode]; 50 | } 51 | 52 | addComponent(type: string, component: any, mode?: string) { 53 | if (!component) { 54 | throw new Error('ComponentLoader: component of type is null type: ' + type); 55 | } 56 | 57 | if (!mode) { 58 | mode = defaultLoadMode; 59 | } 60 | 61 | if (typeof mode !== 'string') { 62 | throw new Error('invalid component mode: ' + mode); 63 | } 64 | 65 | if (!this.cache[type]) { 66 | this.cache[type] = {}; 67 | } 68 | 69 | this.cache[type][mode] = component; 70 | } 71 | 72 | removeComponent(type: string, mode?: string) { 73 | if (!mode) { 74 | mode = defaultLoadMode; 75 | } 76 | 77 | if (!this.cache[type] || !this.cache[type][mode]) { 78 | return; 79 | } 80 | 81 | delete this.cache[type][mode]; 82 | 83 | if (isEmpty(this.cache[type])) { 84 | delete this.cache[type]; 85 | } 86 | } 87 | } 88 | 89 | export const componentLoader = new ComponentLoader(); -------------------------------------------------------------------------------- /packages/rcre/src/core/Service/observer.ts: -------------------------------------------------------------------------------- 1 | import {isPlainObject} from 'lodash'; 2 | 3 | export class ObserverTrack { 4 | private object: Object; 5 | private originalObject: Object; 6 | private pathIndex: number; 7 | public path: string[]; 8 | 9 | constructor(object: Object) { 10 | this.originalObject = object; 11 | this.object = this.applyObserver(object); 12 | this.path = []; 13 | this.pathIndex = -1; 14 | this.applyTracking = this.applyTracking.bind(this); 15 | } 16 | 17 | private applyObserver(object: Object) { 18 | if (!isPlainObject(object) && !Array.isArray(object)) { 19 | return object; 20 | } 21 | 22 | for (let key in object) { 23 | if (object.hasOwnProperty(key)) { 24 | let value = object[key]; 25 | 26 | if (isPlainObject(value)) { 27 | object[key] = this.applyObserver(value); 28 | } 29 | 30 | if (Array.isArray(value)) { 31 | object[key] = this.applyObserver( 32 | value.map(v => this.applyObserver(v)) 33 | ); 34 | } 35 | } 36 | } 37 | 38 | let self = this; 39 | 40 | return new Proxy(object, { 41 | get(obj: any, prop: string) { 42 | self.applyTracking(obj, prop); 43 | return obj[prop]; 44 | } 45 | }); 46 | } 47 | 48 | private applyTracking(original: object, propKey: string) { 49 | if (typeof propKey === 'symbol') { 50 | return; 51 | } 52 | 53 | if (original === this.originalObject) { 54 | this.pathIndex++; 55 | } 56 | 57 | let path = this.path[this.pathIndex] || ''; 58 | 59 | if (/^\d+$/.test(propKey)) { 60 | path += '[' + propKey + ']'; 61 | } else if (path.length === 0) { 62 | path += propKey; 63 | } else { 64 | path += '.' + propKey; 65 | } 66 | 67 | this.path[this.pathIndex] = path; 68 | } 69 | 70 | getObserver(): any { 71 | return this.object; 72 | } 73 | 74 | exec(fn: Function) { 75 | this.path = []; 76 | this.pathIndex = -1; 77 | fn(); 78 | return this.path; 79 | } 80 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [0.21.15](https://github.com/andycall/RCRE/compare/v0.21.14...v0.21.15) (2019-08-01) 7 | 8 | 9 | ### Bug Fixes 10 | 11 | * **rcre:** fix foreach try to mutate original state ([ed0857c](https://github.com/andycall/RCRE/commit/ed0857c)) 12 | 13 | 14 | 15 | 16 | 17 | ## [0.21.9](https://github.com/andycall/RCRE/compare/v0.21.8...v0.21.9) (2019-05-21) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * fix failed unit tests ([0768327](https://github.com/andycall/RCRE/commit/0768327)) 23 | 24 | 25 | 26 | 27 | 28 | ## [0.21.8](https://github.com/andycall/RCRE/compare/v0.21.7...v0.21.8) (2019-05-21) 29 | 30 | 31 | ### Bug Fixes 32 | 33 | * fix formItem validating status did not match component life circle ([5230bb6](https://github.com/andycall/RCRE/commit/5230bb6)) 34 | 35 | 36 | 37 | 38 | 39 | ## [0.21.7](https://github.com/andycall/RCRE/compare/v0.21.6...v0.21.7) (2019-05-21) 40 | 41 | 42 | ### Bug Fixes 43 | 44 | * add validation API instead of filterRule, filterErrMsg ([6436576](https://github.com/andycall/RCRE/commit/6436576)) 45 | 46 | 47 | 48 | 49 | 50 | ## [0.21.5](https://github.com/andycall/RCRE/compare/v0.21.4...v0.21.5) (2019-05-15) 51 | 52 | 53 | ### Bug Fixes 54 | 55 | * fix dataProvider deps analyse algorith ([bb588a8](https://github.com/andycall/RCRE/commit/bb588a8)) 56 | 57 | 58 | 59 | 60 | 61 | ## [0.21.4](https://github.com/andycall/RCRE/compare/v0.21.3...v0.21.4) (2019-05-13) 62 | 63 | 64 | ### Bug Fixes 65 | 66 | * fix crash when try to access an deleted formItem ([60e5e7d](https://github.com/andycall/RCRE/commit/60e5e7d)) 67 | 68 | 69 | 70 | 71 | 72 | # [0.21.0](https://github.com/andycall/RCRE/compare/v0.20.14...v0.21.0) (2019-05-13) 73 | 74 | 75 | ### Bug Fixes 76 | 77 | * fix circleci build failed. ([4cf09b3](https://github.com/andycall/RCRE/commit/4cf09b3)) 78 | * fix circleci command failed. ([3c5b75e](https://github.com/andycall/RCRE/commit/3c5b75e)) 79 | * fix zh-CN translate config ([f4b6c8c](https://github.com/andycall/RCRE/commit/f4b6c8c)) 80 | * recover circleci workflow ([6f302de](https://github.com/andycall/RCRE/commit/6f302de)) 81 | 82 | 83 | ### Features 84 | 85 | * fix Container init value ([b3259ae](https://github.com/andycall/RCRE/commit/b3259ae)) 86 | -------------------------------------------------------------------------------- /packages/rcre/src/jsx-support/Form.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {FormProps, RCREForm as _RCREForm} from '../core/Form/Form'; 3 | import {withAllContext} from '../core/util/withAllContext'; 4 | import {FormContext} from '../core/context'; 5 | import {BasicProps, FormContextType} from '../types'; 6 | 7 | interface FormComponentProps extends FormProps { 8 | onSubmit?: (event: React.FormEvent, data: object) => any; 9 | children: (context: FormContextType) => any; 10 | } 11 | 12 | class FormComponent extends React.PureComponent { 13 | render() { 14 | if (typeof this.props.children !== 'function') { 15 | return
    The children property of RCREForm component should be a function
    ; 16 | } 17 | 18 | const fn = async () => {}; 19 | const onSubmit = this.props.onSubmit || fn; 20 | 21 | return ( 22 | <_RCREForm 23 | {...this.props} 24 | > 25 | 26 | {context => this.props.children({ 27 | ...context, 28 | $handleSubmit: async (event: any) => { 29 | if (event && event.preventDefault) { 30 | event.preventDefault(); 31 | } 32 | event.persist(); 33 | 34 | let valid = await context.$runValidations(); 35 | 36 | if (valid) { 37 | let submitData = {}; 38 | let names = Object.keys(context.$form.control); 39 | for (let itemName of names) { 40 | if (context.$form.control.hasOwnProperty(itemName)) { 41 | submitData[itemName] = this.props.containerContext.$getData(itemName); 42 | } 43 | } 44 | 45 | onSubmit(event, submitData); 46 | } 47 | } 48 | })} 49 | 50 | 51 | ); 52 | } 53 | } 54 | 55 | class DommyForm extends React.PureComponent {} 56 | 57 | export const RCREForm = withAllContext(FormComponent) as typeof DommyForm; -------------------------------------------------------------------------------- /website/static/js/code-block-button.js: -------------------------------------------------------------------------------- 1 | // Copied from Docusaurus websit 2 | // @see https://github.com/facebook/Docusaurus/blob/master/website/static/js/code-blocks-buttons.js 3 | // Turn off ESLint for this file because it's sent down to users as-is. 4 | /* eslint-disable */ 5 | window.addEventListener('load', function() { 6 | function button(label, ariaLabel, icon, className) { 7 | const btn = document.createElement('button'); 8 | btn.classList.add('btnIcon', className); 9 | btn.setAttribute('type', 'button'); 10 | btn.setAttribute('aria-label', ariaLabel); 11 | btn.innerHTML = 12 | '
    ' + 13 | icon + 14 | '' + 15 | label + 16 | '' + 17 | '
    '; 18 | return btn; 19 | } 20 | 21 | function addButtons(codeBlockSelector, btn) { 22 | document.querySelectorAll(codeBlockSelector).forEach(function(code) { 23 | code.parentNode.appendChild(btn.cloneNode(true)); 24 | }); 25 | } 26 | 27 | const copyIcon = 28 | ''; 29 | 30 | addButtons( 31 | '.hljs', 32 | button('Copy', 'Copy code to clipboard', copyIcon, 'btnClipboard') 33 | ); 34 | 35 | const clipboard = new ClipboardJS('.btnClipboard', { 36 | target: function(trigger) { 37 | return trigger.parentNode.querySelector('code'); 38 | }, 39 | }); 40 | 41 | clipboard.on('success', function(event) { 42 | event.clearSelection(); 43 | const textEl = event.trigger.querySelector('.btnIcon__label'); 44 | textEl.textContent = 'Copied'; 45 | setTimeout(function() { 46 | textEl.textContent = 'Copy'; 47 | }, 2000); 48 | }); 49 | }); -------------------------------------------------------------------------------- /packages/rcre/src/core/DataCustomer/customers/location.ts: -------------------------------------------------------------------------------- 1 | import {CustomerParams} from '../index'; 2 | import {stringify, parse} from 'querystring'; 3 | import {compileExpressionString, isExpression, parseExpressionString} from '../../util/vm'; 4 | import * as _ from 'lodash'; 5 | 6 | export interface PassCustomerExecConfig { 7 | /** 8 | * 跳转的方式. href 普通地址的跳转, querystring。更新location.search参数 9 | */ 10 | mode?: 'href' | 'querystring'; 11 | 12 | /** 13 | * 跳转的地址 14 | */ 15 | href?: string; 16 | 17 | /** 18 | * 跳转带的参数 19 | */ 20 | params?: Object | string; 21 | } 22 | 23 | export function locationCustomer(config: PassCustomerExecConfig, customParams: CustomerParams) { 24 | let { 25 | runTime 26 | } = customParams; 27 | let targetHref = config.href; 28 | let locationParams = config.params; 29 | let mode = config.mode || 'href'; 30 | 31 | if (isExpression(locationParams)) { 32 | // 当传入的参数是表达式类型 33 | locationParams = parseExpressionString(locationParams, runTime); 34 | } else if (_.isPlainObject(locationParams)) { 35 | // 当传入的参数是Object 36 | locationParams = compileExpressionString(locationParams, runTime); 37 | } 38 | 39 | switch (mode) { 40 | case 'href': { 41 | // 是跳转方式 42 | if (!targetHref) { 43 | console.error('targetHref is necessary fo r location jumping'); 44 | } else if (isExpression(targetHref)) { 45 | targetHref = parseExpressionString(targetHref, runTime); 46 | } 47 | 48 | let locationParamsString = ''; 49 | locationParamsString = (targetHref!.indexOf('?') === -1 ? '?' : '&') + stringify(locationParams); 50 | location.href = targetHref! + locationParamsString; 51 | return true; 52 | } 53 | case 'querystring': { 54 | // 是更新参数方式 55 | let curParamsString = location.search.slice(1); 56 | let currentParams = parse(curParamsString); 57 | let updatedParamsString = ''; 58 | 59 | let updatedParams = Object.assign(currentParams, locationParams); 60 | updatedParamsString = stringify(updatedParams); 61 | 62 | history.pushState('', '', location.origin + location.pathname + '?' + updatedParamsString); 63 | return true; 64 | } 65 | default: 66 | throw new Error('location mode is not supported'); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /docs/basic-tutorial/automatic-api.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: automatic-api 3 | title: Automatic API 4 | --- 5 | 6 | We use ajax to request the backend interface to get the data. 7 | 8 | There are many application scenarios that require the use of a backend interface. When we want to trigger an API, we wrote such codes below: 9 | 10 | ```javascript 11 | import axios from 'axios'; 12 | 13 | function callAnAPI(data) { 14 | return axios({ 15 | url: '/example', 16 | method: 'GET', 17 | data: data 18 | }); 19 | } 20 | 21 | callAnAPI({ 22 | username: 'helloworld' 23 | }); 24 | ``` 25 | 26 | We just call it when we need it, that's very simple. But when your application is complex, there will be many of API calls. 27 | 28 | Things get bad when you need to trigger an API in parallel or have more than a dozen components, and any component update must trigger the same API repeatedly. 29 | 30 | RCRE provider you an automatic API system that can trigger you API when needs. The only things you need to think is every API's params and trigger conditions. 31 | 32 | ## The dataProvider property 33 | The automatic API system is integrated on the Container component. Just add the dataProvider property and have fun with APIs. 34 | 35 | Let's update the previous example and add dataProvider property. 36 | 37 | 38 | 39 | In this example, we add an dataProvider property to trigger an API call. 40 | 41 | ```javascript 42 | [{ 43 | mode: "ajax", 44 | namespace: GITHUB_USER, 45 | config: { 46 | url: ({ $data }) => 47 | "https://api.github.com/users/" + $data.username, 48 | method: "GET" 49 | } 50 | }] 51 | ``` 52 | 53 | the namespace is the name of this API, RCRE use this name to keep track of every APIS. 54 | 55 | Every property under config property can be dynamic property. Use anonymous function to replace pure value to trigger recalculated when call this API, so you can read container state at this moment and return the newest value. 56 | 57 | Every time when you update the input components, value will be sync to container and trigger dataProvider's API automatically. Every API will use the config property to compare with previous config to filter request which have the same params. 58 | 59 | After the API is finished, the request value will pass to store and update to your `$data` property, so you can access them via `$data[namespace]`. -------------------------------------------------------------------------------- /packages/rcre/src/core/util/filter.ts: -------------------------------------------------------------------------------- 1 | export type pureFunc = typeof Function; 2 | 3 | export class FilterController { 4 | public store: { 5 | [key: string]: pureFunc 6 | }; 7 | 8 | constructor() { 9 | this.store = {}; 10 | } 11 | 12 | private wrapFunc(func: typeof Function) { 13 | let f = func; 14 | let cArgs: any[] | null = null; 15 | let result: any = null; 16 | 17 | return ((...args: any[]) => { 18 | if (cArgs && cArgs.length === args.length) { 19 | let isEqual = cArgs.every((cArg, index) => { 20 | return cArg === args[index]; 21 | }); 22 | 23 | if (isEqual) { 24 | return result; 25 | } 26 | } 27 | try { 28 | result = f(...args); 29 | cArgs = args; 30 | return result; 31 | } catch (e) { 32 | e.message = e.message + '; function arguments: ' + JSON.stringify(args); 33 | throw e; 34 | } 35 | }); 36 | } 37 | 38 | public setFilter(funcName: string, func: any, force?: boolean) { 39 | if (this.store.hasOwnProperty(funcName) && !force) { 40 | throw new Error('found exist func : ' + funcName); 41 | } 42 | 43 | if (typeof func === 'function') { 44 | func = this.wrapFunc(func); 45 | } 46 | 47 | this.store[funcName] = func; 48 | } 49 | 50 | public setFilterFunc(func: typeof Function) { 51 | if (typeof func !== 'function') { 52 | console.error('invalid setFilterFunc params', func); 53 | return; 54 | } 55 | 56 | let name = func.name; 57 | 58 | if (this.store.hasOwnProperty(name)) { 59 | throw new Error('found exist func :' + name); 60 | } 61 | 62 | this.store[name] = func; 63 | } 64 | 65 | public getFilter(funcName: string): Function { 66 | if (!this.store.hasOwnProperty(funcName)) { 67 | console.error('can not find target func :' + funcName); 68 | return (...args: any[]) => args; 69 | } 70 | 71 | return this.store[funcName]; 72 | } 73 | 74 | public hasFilter(funcName: string) { 75 | return this.store.hasOwnProperty(funcName); 76 | } 77 | 78 | public delFilter(funcName: string) { 79 | delete this.store[funcName]; 80 | } 81 | 82 | public clearFilter() { 83 | this.store = {}; 84 | } 85 | 86 | public size() { 87 | return Object.keys(this.store).length; 88 | } 89 | } 90 | 91 | export const filter = new FilterController(); 92 | -------------------------------------------------------------------------------- /examples/container-inheritance/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {RCREProvider, Container, ES, createReduxStore} from 'rcre'; 4 | 5 | import "./styles.css"; 6 | import {Input} from "./components/Input"; 7 | 8 | const store = createReduxStore(); 9 | 10 | function App() { 11 | return ( 12 |
    13 | 14 |

    There are two ways to make cross container assignment

    15 | 16 |
      17 |
    • Use Container Inherit
    • 18 |
    • Use Pass Task
    • 19 |
    20 | 21 |
    22 |

    Use Container Inherit to communicate between parent and child container

    23 | 24 |
    the value of root container {({$data}) => {JSON.stringify($data)}}
    25 | 26 |
    27 |
    demo 1 Container
    28 | $parent.username.toUpperCase() 32 | }} 33 | > 34 |
    Child container can use props property to inherit value from parent container
    35 | UserName: 36 | 37 | 38 |
    Components in the same container will share the same name
    39 | 40 |
    demo1 state value: {({$data}) => {JSON.stringify($data)}}
    41 |
    42 |
    43 |
    44 |
    demo 2 Container
    45 | $parent.username 50 | }} 51 | export={{ 52 | username: ({$data}) => $data.username 53 | }} 54 | > 55 |
    Child container can use export property to send child value to parent
    56 |
    When using both export and props, child will always keep sync with parent
    57 | UserName: 58 | 59 |
    demo1 state value: {({$data}) => {JSON.stringify($data)}}
    60 |
    61 |
    62 |
    63 |
    64 |
    65 |
    66 | ); 67 | } 68 | 69 | const rootElement = document.getElementById("root"); 70 | ReactDOM.render(, rootElement); 71 | -------------------------------------------------------------------------------- /packages/rcre/src/data/history.ts: -------------------------------------------------------------------------------- 1 | import {AnyAction, Reducer} from 'redux'; 2 | import {RCRE_ASYNC_LOAD_DATA_FAIL, RCRE_ASYNC_LOAD_DATA_SUCCESS, RCRE_DATA_CUSTOMER_PASS, RCRE_DELETE_DATA, RCRE_REDO_STATE, RCRE_SET_DATA, RCRE_SET_MULTI_DATA, RCRE_UNDO_STATE} from '../core/Container/action'; 3 | 4 | const validUNDOAction = [ 5 | RCRE_SET_DATA, 6 | RCRE_SET_MULTI_DATA, 7 | RCRE_ASYNC_LOAD_DATA_SUCCESS, 8 | RCRE_ASYNC_LOAD_DATA_FAIL, 9 | RCRE_DELETE_DATA, 10 | RCRE_DATA_CUSTOMER_PASS 11 | ]; 12 | 13 | export class ContainerStateHistory { 14 | private historyIndex: number; 15 | private history: T[]; 16 | 17 | constructor() { 18 | this.history = []; 19 | this.historyIndex = 0; 20 | 21 | this.canRedoContainerState = this.canRedoContainerState.bind(this); 22 | this.canUndoContainerState = this.canUndoContainerState.bind(this); 23 | } 24 | 25 | undo(state: T): T { 26 | this.history[this.historyIndex] = state; 27 | this.historyIndex--; 28 | return this.history[this.historyIndex]; 29 | } 30 | 31 | forward(state: T) { 32 | this.history[this.historyIndex] = state; 33 | this.historyIndex++; 34 | return this.history[this.historyIndex]; 35 | } 36 | 37 | middleware(state: T, action: AnyAction) { 38 | if (validUNDOAction.includes(action.type)) { 39 | if (this.history.length > 100) { 40 | this.history.shift(); 41 | } 42 | 43 | this.history[this.historyIndex++] = state; 44 | } 45 | } 46 | 47 | canUndoContainerState(): boolean { 48 | return this.historyIndex > 0; 49 | } 50 | 51 | canRedoContainerState(): boolean { 52 | return this.historyIndex < this.history.length - 1; 53 | } 54 | } 55 | 56 | export function createContainerStateHistory() { 57 | return new ContainerStateHistory(); 58 | } 59 | 60 | export function undoable(reducer: Reducer, externalHistory?: ContainerStateHistory): Reducer { 61 | let history = externalHistory || new ContainerStateHistory(); 62 | 63 | return (state, action) => { 64 | switch (action.type) { 65 | case RCRE_UNDO_STATE: { 66 | let prevState = history.undo(state); 67 | if (!prevState) { 68 | return state; 69 | } 70 | 71 | return prevState; 72 | } 73 | case RCRE_REDO_STATE: { 74 | let nextState = history.forward(state); 75 | 76 | if (!nextState) { 77 | return state; 78 | } 79 | 80 | return nextState; 81 | } 82 | default: { 83 | history.middleware(state, action); 84 | return reducer(state, action); 85 | } 86 | } 87 | }; 88 | } -------------------------------------------------------------------------------- /packages/rcre/src/core/Connect/Common/Common.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {BasicProps} from '../../../types'; 3 | import {BasicConnect, BasicConnectProps, CommonOptions, WrapperComponentType} from '../basicConnect'; 4 | 5 | const defaultMappingProps = {}; 6 | 7 | export function commonConnect(options: CommonOptions = {}): 8 | (WrapperComponent: WrapperComponentType) => React.ComponentClass { 9 | return (WrapperComponent) => { 10 | class CommonConnect extends BasicConnect { 11 | static displayName: string; 12 | private mapping = defaultMappingProps; 13 | constructor(props: BasicConnectProps & BasicProps) { 14 | super(props, options); 15 | this.mapping = { 16 | ...this.mapping, 17 | ...options.propsMapping 18 | }; 19 | } 20 | 21 | render() { 22 | let { 23 | props, 24 | runTime, 25 | registerEvent, 26 | updateNameValue, 27 | getNameValue 28 | } = this.prepareRender(options); 29 | 30 | let changeCallBack = options.defaultNameCallBack || 'onChange'; 31 | props[changeCallBack] = (value: any) => updateNameValue(value); 32 | 33 | this.applyPropsMapping(props, this.mapping); 34 | 35 | CommonConnect.displayName = `RCREConnect(${this.props.type})`; 36 | WrapperComponent.displayName = `RCRE(${this.props.type})`; 37 | 38 | return ( 39 | 60 | ); 61 | } 62 | } 63 | 64 | return CommonConnect; 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /test/__mock__/data/linechart.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": 0, 3 | "msg": "", 4 | "data": { 5 | "categories": [ 6 | "2015-11-01", 7 | "2015-11-02", 8 | "2015-11-03", 9 | "2015-11-04", 10 | "2015-11-05", 11 | "2015-11-06", 12 | "2015-11-07", 13 | "2015-11-08", 14 | "2015-11-09", 15 | "2015-11-10", 16 | "2015-11-11", 17 | "2015-11-12", 18 | "2015-11-13", 19 | "2015-11-14", 20 | "2015-11-15" 21 | ], 22 | "series": [ 23 | { 24 | "name": "推广用户数", 25 | "icon": "rect", 26 | "areaStyle": { 27 | "normal":{ 28 | "color": "rgba(57,152,252,0.10)" 29 | } 30 | }, 31 | "itemStyle": { 32 | "normal": { 33 | "color": "rgb(57, 152, 252)", 34 | "lineStyle": { 35 | "width": 1 36 | } 37 | }, 38 | "emphasis": { 39 | "borderWidth": 1 40 | } 41 | }, 42 | "data": [ 43 | 198907, 44 | 97761, 45 | 198924, 46 | 66256, 47 | 138748, 48 | 64537, 49 | 77671, 50 | 101897, 51 | 399961, 52 | 197001, 53 | 194126, 54 | 191954, 55 | 199853, 56 | 193973, 57 | 192649 58 | ] 59 | }, 60 | { 61 | "name": "老用户数", 62 | "icon": "rect", 63 | "areaStyle": { 64 | "normal":{ 65 | "color": "rgba(91, 196, 159, 0.10)" 66 | } 67 | }, 68 | "itemStyle": { 69 | "normal": { 70 | "color": "rgb(91, 196, 159)", 71 | "lineStyle": { 72 | "width": 1 73 | } 74 | }, 75 | "emphasis": { 76 | "borderWidth": 1 77 | } 78 | }, 79 | "data": [ 80 | 123787, 81 | 126385, 82 | 123691, 83 | 127096, 84 | 125113, 85 | 126222, 86 | 121228, 87 | 129377, 88 | 122632, 89 | 121349, 90 | 120543, 91 | 124909, 92 | 128427, 93 | 128919, 94 | 28824 95 | ] 96 | } 97 | ] 98 | } 99 | } --------------------------------------------------------------------------------