canner
Version:
Build CMS in few lines of code for different data sources
152 lines (140 loc) • 4.2 kB
Flow
// @flow
import * as React from 'react';
import {generateAction} from '../action';
import {createEmptyData} from 'canner-helpers';
import RefId from 'canner-ref-id';
import type {HOCProps} from './types';
import {update, merge, isArray, isPlainObject} from 'lodash';
type changeQueue = Array<{RefId: RefId | {firstRefId: RefId, secondRefId: RefId}, type: any, value: any}>;
export default function withRequest(Com: React.ComponentType<*>) {
// this hoc will update data;
return class ComponentWithRequest extends React.Component<HOCProps> {
onChange = (refId: RefId | {firstRefId: RefId, secondRefId: RefId} | changeQueue, type: any, delta: any, config: any, transformGqlPayload?: Function): Promise<*> => {
let id;
if (isArray(refId)) { // changeQueue
const changeQueue = refId;
// $FlowFixMe
return Promise.all(changeQueue.map(args => {
const {refId, type, value, config, transformGqlPayload} = args;
return this.onChange(refId, type, value, config, transformGqlPayload);
}));
} else if (refId instanceof RefId) {
id = refId.toString();
} else {
id = {
// $FlowFixMe: refId should be object here
firstId: refId.firstRefId.toString(),
// $FlowFixMe: refId should be object here
secondId: refId.secondRefId.toString()
};
}
const {schema, rootValue, request} = this.props;
const itemSchema = findSchemaByRefId(schema, refId);
const {relation, items, pattern} = itemSchema;
const action = createAction({
relation,
id,
type,
value: delta,
config,
rootValue: {...rootValue},
items,
pattern,
transformGqlPayload
});
if (!action) {
throw new Error('invalid change');
}
if (action.type === 'NOOP') {
return Promise.resolve();
}
// $FlowFixMe
return request(action);
}
render() {
return <Com {...this.props} onChange={this.onChange} />;
}
};
}
export function createAction({
relation,
id,
type,
value,
config,
rootValue,
items,
transformGqlPayload
}: {
relation: any,
id: {firstId: string, secondId: string} | string,
type: 'create' | 'update' | 'delete' | 'swap',
value: any,
config: any,
rootValue: any,
value: any,
items: any,
transformGqlPayload?: Function
}) {
if (type === 'create') {
if (!config) {
const emptyData = createEmptyData(items);
if (isPlainObject(emptyData)) {
value = merge(emptyData, value);
} else {
value = emptyData;
}
}
if (typeof id === 'string' && id.split('/').length === 1) {
update(value, 'id', id => id || randomId());
}
}
return generateAction({
id,
updateType: type,
value,
rootValue,
relation,
transformGqlPayload
});
}
function randomId() {
return Math.random().toString(36).substr(2, 12);
}
export function findSchemaByRefId(schema: Object, refId: any) {
let paths = [];
if (isPlainObject(refId)) {
paths = refId.firstRefId.getPathArr();
} else {
paths = refId.getPathArr();
}
let pattern = '';
return loop(schema, paths, pattern);
}
export function loop(schema: Object, paths: Array<string>, pattern: string): any {
if (paths.length === 0) {
return {
...schema,
pattern: removeFirstSlash(pattern)
};
}
if (!schema.type) { // in items of object
return loop(schema[paths[0]], paths.slice(1), `${pattern}/${schema[paths[0]].type}`);
}
if (schema.type === 'json') {
return {};
}
if (schema.type === 'array') {
if (paths.length === 1) {
// paths = [index], so just return
return loop(schema, [], pattern);
} else if (schema.items.type === 'object') {
// path[0] is index, so we take the paths[1]
return loop(schema.items.items[paths[1]], paths.slice(2), `${pattern}/${schema.items.items[paths[1]].type}`);
}
}
return loop(schema.items[paths[0]], paths.slice(1), `${pattern}/${schema.items[paths[0]].type}`);
}
function removeFirstSlash(pattern: string) {
return pattern.split('/').slice(1).join('/');
}