amis
Version:
一种MIS页面生成工具
530 lines (529 loc) • 25.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var react_1 = tslib_1.__importDefault(require("react"));
var qs_1 = tslib_1.__importDefault(require("qs"));
var index_1 = require("./store/index");
var mobx_state_tree_1 = require("mobx-state-tree");
var api_1 = require("./utils/api");
var helper_1 = require("./utils/helper");
var mobx_react_1 = require("mobx-react");
var filter_schema_1 = tslib_1.__importDefault(require("./utils/filter-schema"));
var hoistNonReactStatic = require("hoist-non-react-statics");
var omit = require("lodash/omit");
var difference = require("lodash/difference");
var isPlainObject = require("lodash/isPlainObject");
var Scoped_1 = tslib_1.__importDefault(require("./Scoped"));
var theme_1 = require("./theme");
var find = require("lodash/find");
var Alert2_1 = tslib_1.__importDefault(require("./components/Alert2"));
var components_1 = require("./components");
var renderers = [];
var rendererNames = [];
var schemaFilters = [];
var anonymousIndex = 1;
function addSchemaFilter(fn) {
schemaFilters.push(fn);
}
exports.addSchemaFilter = addSchemaFilter;
function filterSchema(schema, render, props) {
return schemaFilters.reduce(function (schema, filter) { return filter(schema, render, props); }, schema);
}
exports.filterSchema = filterSchema;
function Renderer(config) {
return function (component) {
var renderer = registerRenderer(tslib_1.__assign(tslib_1.__assign({}, config), { component: component }));
return renderer.component;
};
}
exports.Renderer = Renderer;
function registerRenderer(config) {
if (!config.test) {
throw new TypeError('config.test is required');
}
else if (!config.component) {
throw new TypeError('config.component is required');
}
config.weight = config.weight || 0;
config.Renderer = config.component;
config.name = config.name || "anonymous-" + anonymousIndex++;
if (~rendererNames.indexOf(config.name)) {
throw new Error("The renderer with name \"" + config.name + "\" has already exists, please try another name!");
}
if (config.storeType && config.component) {
config.component = HocStoreFactory({
storeType: config.storeType,
extendsData: config.storeExtendsData
})(mobx_react_1.observer(config.component));
}
if (config.isolateScope) {
config.component = Scoped_1.default(config.component);
}
var idx = helper_1.findIndex(renderers, function (item) { return config.weight < item.weight; });
~idx ? renderers.splice(idx, 0, config) : renderers.push(config);
rendererNames.push(config.name);
return config;
}
exports.registerRenderer = registerRenderer;
function unRegisterRenderer(config) {
var idx = typeof config === 'string' ? helper_1.findIndex(renderers, function (item) { return item.name === config; }) : renderers.indexOf(config);
~idx && renderers.splice(idx, 1);
// 清空渲染器定位缓存
cache = {};
}
exports.unRegisterRenderer = unRegisterRenderer;
function renderChildren(prefix, node, props) {
if (Array.isArray(node)) {
return node.map(function (node, index) {
return renderChild(prefix + "/" + index, node, tslib_1.__assign(tslib_1.__assign({}, props), { key: "" + (props.key ? props.key + "-" : '') + index }));
});
}
return renderChild(prefix, node, props);
}
exports.renderChildren = renderChildren;
function renderChild(prefix, node, props) {
if (Array.isArray(node)) {
return renderChildren(prefix, node, props);
}
var typeofnode = typeof node;
var schema = typeofnode === 'string' || typeofnode === 'number' ? { type: 'tpl', tpl: String(node) } : node;
var detectData = schema.detectField === '&' ? props : props[schema.detectField || 'data'];
var exprProps = detectData ? filter_schema_1.default(schema, detectData) : null;
if (exprProps &&
(exprProps.hidden ||
exprProps.visible === false ||
schema.hidden ||
schema.visible === false ||
props.hidden ||
props.visible === false)) {
return null;
}
var transform = props.propsTransform;
if (transform) {
delete props.propsTransform;
props = transform(props);
}
return (react_1.default.createElement(SchemaRenderer, tslib_1.__assign({}, props, exprProps, { schema: schema, "$path": "" + (prefix ? prefix + "/" : '') + ((schema && schema.type) || '') })));
}
exports.renderChild = renderChild;
var RootStoreContext = react_1.default.createContext(undefined);
var RootRenderer = /** @class */ (function (_super) {
tslib_1.__extends(RootRenderer, _super);
function RootRenderer() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.state = {
error: null,
errorInfo: null
};
return _this;
}
RootRenderer.prototype.componentDidCatch = function (error, errorInfo) {
console.error(error);
this.setState({
error: error,
errorInfo: errorInfo
});
};
RootRenderer.prototype.resolveDefinitions = function (name) {
var definitions = this.props.schema.definitions;
if (!name || helper_1.isEmpty(definitions)) {
return {};
}
return definitions && definitions[name];
};
RootRenderer.prototype.render = function () {
var _a = this.state, error = _a.error, errorInfo = _a.errorInfo;
if (errorInfo) {
return errorRenderer(error, errorInfo);
}
var _b = this.props, schema = _b.schema, rootStore = _b.rootStore, env = _b.env, pathPrefix = _b.pathPrefix, location = _b.location, data = _b.data, rest = tslib_1.__rest(_b, ["schema", "rootStore", "env", "pathPrefix", "location", "data"]);
var theme = env.theme;
var query = (location && location.query) ||
(location && location.search && qs_1.default.parse(location.search.substring(1))) ||
(window.location.search && qs_1.default.parse(window.location.search.substring(1)));
var finalData = query
? helper_1.createObject(tslib_1.__assign(tslib_1.__assign(tslib_1.__assign({}, (data && data.__super ? data.__super : null)), query), { query: query }), data)
: data;
return (react_1.default.createElement(RootStoreContext.Provider, { value: rootStore },
react_1.default.createElement(theme_1.ThemeContext.Provider, { value: this.props.theme || 'default' }, renderChild(pathPrefix || '', isPlainObject(schema)
? tslib_1.__assign({ type: 'page' }, schema) : schema, tslib_1.__assign(tslib_1.__assign({}, rest), { resolveDefinitions: this.resolveDefinitions, location: location, data: finalData, env: env, classnames: theme.classnames, classPrefix: theme.classPrefix })))));
};
tslib_1.__decorate([
helper_1.autobind,
tslib_1.__metadata("design:type", Function),
tslib_1.__metadata("design:paramtypes", [String]),
tslib_1.__metadata("design:returntype", void 0)
], RootRenderer.prototype, "resolveDefinitions", null);
return RootRenderer;
}(react_1.default.Component));
exports.RootRenderer = RootRenderer;
exports.ScopedRootRenderer = Scoped_1.default(RootRenderer);
var defaultOmitList = [
'type',
'name',
'$ref',
'className',
'data',
'children',
'ref',
'visible',
'visibleOn',
'hidden',
'hiddenOn',
'disabled',
'disabledOn',
'component',
'detectField'
];
var SchemaRenderer = /** @class */ (function (_super) {
tslib_1.__extends(SchemaRenderer, _super);
function SchemaRenderer(props) {
var _this = _super.call(this, props) || this;
_this.refFn = _this.refFn.bind(_this);
_this.renderChild = _this.renderChild.bind(_this);
_this.reRender = _this.reRender.bind(_this);
return _this;
}
SchemaRenderer.prototype.componentWillMount = function () {
this.resolveRenderer(this.props);
};
SchemaRenderer.prototype.componentWillReceiveProps = function (nextProps) {
var props = this.props;
if (props.schema.type !== nextProps.schema.type || props.schema.$$id !== nextProps.schema.$$id) {
this.resolveRenderer(nextProps);
}
};
// 限制:只有 schema 除外的 props 变化,或者 schema 里面的某个成员值发生变化才更新。
SchemaRenderer.prototype.shouldComponentUpdate = function (nextProps) {
var props = this.props;
var list = difference(Object.keys(nextProps), ['schema']);
if (difference(Object.keys(props), ['schema']).length !== list.length ||
helper_1.anyChanged(list, this.props, nextProps)) {
return true;
}
else {
var list_1 = Object.keys(nextProps.schema);
if (Object.keys(props.schema).length !== list_1.length || helper_1.anyChanged(list_1, props.schema, nextProps.schema)) {
return true;
}
}
return false;
};
SchemaRenderer.prototype.resolveRenderer = function (props) {
var schema = props.schema;
var path = props.$path;
var rendererResolver = props.env.rendererResolver || resolveRenderer;
if (schema.$ref) {
schema = tslib_1.__assign(tslib_1.__assign({}, props.resolveDefinitions(schema.$ref)), schema);
delete schema.$ref;
path = path.replace(/(?!.*\/).*/, schema.type);
}
// value 会提前从 control 中获取到,所有需要把control中的属性也补充完整
// if (schema.control && schema.control.$ref) {
// schema.control = {
// ...props.resolveDefinitions(schema.control.$ref),
// ...schema.control
// }
// delete schema.control.$ref;
// }
this.renderer = rendererResolver(path, schema, props);
return schema;
};
SchemaRenderer.prototype.getWrappedInstance = function () {
return this.ref;
};
SchemaRenderer.prototype.refFn = function (ref) {
this.ref = ref;
};
SchemaRenderer.prototype.renderChild = function (region, node, subProps) {
if (subProps === void 0) { subProps = {}; }
var _a = this.props, schema = _a.schema, $path = _a.$path, env = _a.env, rest = tslib_1.__rest(_a, ["schema", "$path", "env"]);
var omitList = defaultOmitList.concat();
if (this.renderer) {
var Component = this.renderer.component;
Component.propsList && omitList.push.apply(omitList, Component.propsList);
}
return renderChild("" + $path + (region ? "/" + region : ''), node || '', tslib_1.__assign(tslib_1.__assign(tslib_1.__assign({}, omit(rest, omitList)), subProps), { data: subProps.data || rest.data, env: env }));
};
SchemaRenderer.prototype.reRender = function () {
this.resolveRenderer(this.props);
this.forceUpdate();
};
SchemaRenderer.prototype.render = function () {
var _this = this;
var _a = this.props, $path = _a.$path, schema = _a.schema, rest = tslib_1.__rest(_a, ["$path", "schema"]);
if (schema.$ref) {
schema = this.resolveRenderer(this.props);
}
var theme = this.props.env.theme;
if (Array.isArray(schema)) {
return renderChildren($path, schema, rest);
}
else if (schema.children) {
return react_1.default.isValidElement(schema.children)
? schema.children
: schema.children(tslib_1.__assign(tslib_1.__assign({}, rest), { $path: $path, render: this.renderChild }));
}
else if (typeof schema.component === 'function') {
return react_1.default.createElement(schema.component, tslib_1.__assign(tslib_1.__assign({}, rest), { $path: $path, render: this.renderChild }));
}
else if (!this.renderer) {
return (react_1.default.createElement(components_1.LazyComponent, tslib_1.__assign({}, rest, { getComponent: function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
var result;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, rest.env.loadRenderer(schema, $path, this.reRender)];
case 1:
result = _a.sent();
if (result && typeof result === 'function') {
return [2 /*return*/, result];
}
else if (result && react_1.default.isValidElement(result)) {
return [2 /*return*/, function () { return result; }];
}
this.reRender();
return [2 /*return*/, function () { return loadRenderer(schema, $path); }];
}
});
}); }, "$path": $path, retry: this.reRender })));
}
var renderer = this.renderer;
schema = filterSchema(schema, renderer, rest);
var defaultData = schema.data, restSchema = tslib_1.__rest(schema, ["data"]);
var Component = renderer.component;
return (react_1.default.createElement(Component, tslib_1.__assign({}, theme.getRendererConfig(renderer.name), restSchema, rest, { defaultData: defaultData, "$path": $path, ref: this.refFn, render: this.renderChild })));
};
SchemaRenderer.displayName = 'Renderer';
return SchemaRenderer;
}(react_1.default.Component));
function HocStoreFactory(renderer) {
return function (Component) {
var StoreFactory = /** @class */ (function (_super) {
tslib_1.__extends(StoreFactory, _super);
function StoreFactory() {
return _super !== null && _super.apply(this, arguments) || this;
}
StoreFactory.prototype.getWrappedInstance = function () {
return this.ref;
};
StoreFactory.prototype.refFn = function (ref) {
this.ref = ref;
};
StoreFactory.prototype.formatData = function (data) {
if (Array.isArray(data)) {
return {
items: data
};
}
return data;
};
StoreFactory.prototype.componentWillMount = function () {
var rootStore = this.context;
this.renderChild = this.renderChild.bind(this);
this.refFn = this.refFn.bind(this);
var store = (this.store = rootStore.addStore({
id: helper_1.guid(),
path: this.props.$path,
storeType: renderer.storeType,
parentId: this.props.store ? this.props.store.id : ''
}));
if (renderer.extendsData === false) {
store.initData(helper_1.createObject(this.props.data ? this.props.data.__super : null, tslib_1.__assign(tslib_1.__assign({}, this.formatData(this.props.defaultData)), this.formatData(this.props.data))));
}
else if (this.props.scope || (this.props.data && this.props.data.__super)) {
if (this.props.store && this.props.data === this.props.store.data) {
store.initData(helper_1.createObject(this.props.store.data, tslib_1.__assign({}, this.formatData(this.props.defaultData))));
}
else {
store.initData(helper_1.createObject(this.props.data.__super || this.props.scope, tslib_1.__assign(tslib_1.__assign({}, this.formatData(this.props.defaultData)), this.formatData(this.props.data))));
}
}
else {
store.initData(tslib_1.__assign(tslib_1.__assign({}, this.formatData(this.props.defaultData)), this.formatData(this.props.data)));
}
};
StoreFactory.prototype.componentWillReceiveProps = function (nextProps) {
var props = this.props;
var store = this.store;
if (renderer.extendsData === false) {
(props.defaultData !== nextProps.defaultData ||
helper_1.isObjectShallowModified(props.data, nextProps.data) ||
// CRUD 中 toolbar 里面的 data 是空对象,但是 __super 会不一样
(nextProps.data && props.data && nextProps.data.__super !== props.data.__super)) &&
store.initData(helper_1.extendObject(nextProps.data, tslib_1.__assign(tslib_1.__assign(tslib_1.__assign({}, (store.hasRemoteData ? store.data : null)), this.formatData(nextProps.defaultData)), this.formatData(nextProps.data))));
}
else if (helper_1.isObjectShallowModified(props.data, nextProps.data)) {
if (nextProps.store && nextProps.store.data === nextProps.data) {
var newData = helper_1.createObject(nextProps.store.data, helper_1.syncDataFromSuper(store.data, nextProps.store.data, props.scope, nextProps.dataUpdatedAt !== props.dataUpdatedAt, store));
// todo fix: dialog 种数据从孩子 form 同步过来后,会走这个逻辑让 form 更新 data,会导致里面的 __prev 丢失。
store.initData(newData);
}
else if (nextProps.data && nextProps.data.__super) {
store.initData(helper_1.extendObject(nextProps.data));
}
else {
store.initData(helper_1.createObject(nextProps.scope, nextProps.data));
}
}
else if ((!nextProps.store || nextProps.data !== nextProps.store.data) &&
nextProps.data &&
nextProps.data.__super) {
// 这个用法很少,当 data.__super 值发生变化时,更新 store.data
(!props.data || helper_1.isObjectShallowModified(nextProps.data.__super, props.data.__super, false)) &&
store.initData(helper_1.createObject(nextProps.data.__super, tslib_1.__assign(tslib_1.__assign({}, nextProps.data), store.data)));
}
else if (helper_1.isObjectShallowModified(props.scope, nextProps.scope)) {
store.initData(helper_1.createObject(nextProps.scope, tslib_1.__assign(tslib_1.__assign({}, nextProps.data), store.data)));
}
};
StoreFactory.prototype.componentWillUnmount = function () {
var rootStore = this.context;
var store = this.store;
rootStore.removeStore(store);
delete this.store;
};
StoreFactory.prototype.renderChild = function (region, node, subProps) {
if (subProps === void 0) { subProps = {}; }
var render = this.props.render;
return render(region, node, tslib_1.__assign(tslib_1.__assign({ data: this.store.data, dataUpdatedAt: this.store.updatedAt }, subProps), { scope: this.store.data, store: this.store }));
};
StoreFactory.prototype.render = function () {
var _a = this.props, detectField = _a.detectField, rest = tslib_1.__rest(_a, ["detectField"]);
var exprProps = {};
if (!detectField || detectField === 'data') {
exprProps = filter_schema_1.default(rest, this.store.data);
if (exprProps.hidden || exprProps.visible === false) {
return null;
}
}
return (react_1.default.createElement(Component, tslib_1.__assign({}, rest /* todo */, exprProps, { ref: this.refFn, data: this.store.data, dataUpdatedAt: this.store.updatedAt, store: this.store, scope: this.store.data, render: this.renderChild })));
};
StoreFactory.displayName = "WithStore(" + (Component.displayName || Component.name) + ")";
StoreFactory.ComposedComponent = Component;
StoreFactory.contextType = RootStoreContext;
StoreFactory = tslib_1.__decorate([
mobx_react_1.observer
], StoreFactory);
return StoreFactory;
}(react_1.default.Component));
hoistNonReactStatic(StoreFactory, Component);
return StoreFactory;
};
}
exports.HocStoreFactory = HocStoreFactory;
function loadRenderer(schema, path) {
return (react_1.default.createElement(Alert2_1.default, { level: "danger" },
react_1.default.createElement("p", null, "Error: \u627E\u4E0D\u5230\u5BF9\u5E94\u7684\u6E32\u67D3\u5668"),
react_1.default.createElement("p", null,
"Path: ",
path),
react_1.default.createElement("pre", null,
react_1.default.createElement("code", null, JSON.stringify(schema, null, 2)))));
}
function errorRenderer(error, errorInfo) {
return (react_1.default.createElement(Alert2_1.default, { level: "danger" },
react_1.default.createElement("p", null, error && error.toString()),
react_1.default.createElement("pre", null,
react_1.default.createElement("code", null, errorInfo.componentStack))));
}
var defaultOptions = {
session: 'global',
affixOffsetTop: 50,
affixOffsetBottom: 0,
richTextToken: '',
loadRenderer: loadRenderer,
fetcher: function () {
return Promise.reject('fetcher is required');
},
isCancel: function () {
console.error('Please implements this. see https://baidu.github.io/amis/docs/getting-started#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8');
return false;
},
alert: function (msg) {
alert(msg);
},
updateLocation: function () {
console.error('Please implements this. see https://baidu.github.io/amis/docs/getting-started#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8');
},
confirm: function (msg) {
return confirm(msg);
},
notify: function (msg) {
alert(msg);
},
jumpTo: function () {
console.error('Please implements this. see https://baidu.github.io/amis/docs/getting-started#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8');
},
isCurrentUrl: function () {
return false;
},
copy: function (contents) {
console.error('copy contents', contents);
},
rendererResolver: resolveRenderer
};
var stores = {};
function render(schema, props, options, pathPrefix) {
if (props === void 0) { props = {}; }
if (options === void 0) { options = {}; }
if (pathPrefix === void 0) { pathPrefix = ''; }
options = tslib_1.__assign(tslib_1.__assign({}, defaultOptions), options);
var store = stores[options.session || 'global'] ||
(stores[options.session || 'global'] = index_1.RendererStore.create({}, tslib_1.__assign(tslib_1.__assign({}, options), { fetcher: options.fetcher ? api_1.wrapFetcher(options.fetcher) : defaultOptions.fetcher, confirm: options.confirm ? helper_1.promisify(options.confirm) : defaultOptions.confirm })));
window.amisStore = store; // 为了方便 debug.
var env = mobx_state_tree_1.getEnv(store);
var theme = props.theme || options.theme || 'default';
env.theme = theme_1.getTheme(theme);
return (react_1.default.createElement(exports.ScopedRootRenderer, tslib_1.__assign({}, props, { schema: schema, pathPrefix: pathPrefix, rootStore: store, env: env, theme: theme })));
}
exports.render = render;
function clearStoresCache(sessions) {
if (Array.isArray(sessions) && sessions.length) {
sessions.forEach(function (key) { return delete stores[key]; });
}
else {
stores = {};
}
}
exports.clearStoresCache = clearStoresCache;
var cache = {};
function resolveRenderer(path, schema, props) {
if (cache[path]) {
return cache[path];
}
else if (path && path.length > 1024) {
throw new Error('Path太长是不是死循环了?');
}
var renderer = null;
renderers.some(function (item) {
var matched = false;
if (typeof item.test === 'function') {
matched = item.test(path, schema, resolveRenderer);
}
else if (item.test instanceof RegExp) {
matched = item.test.test(path);
}
if (matched) {
renderer = item;
}
return matched;
});
// 只能缓存纯正则表达式的后者方法中没有用到第二个参数的,因为自定义 test 函数的有可能依赖 schema 的结果
if (renderer !== null &&
(renderer.test instanceof RegExp ||
(typeof renderer.test === 'function' &&
renderer.test.length < 2))) {
cache[path] = renderer;
}
return renderer;
}
exports.resolveRenderer = resolveRenderer;
function getRenderers() {
return renderers.concat();
}
exports.getRenderers = getRenderers;
function getRendererByName(name) {
return find(renderers, function (item) { return item.name === name; });
}
exports.getRendererByName = getRendererByName;
//# sourceMappingURL=./factory.js.map