mirror of
https://github.com/vale981/Vulcan
synced 2025-03-05 09:31:43 -05:00
Merge branch 'devel' into apollo2
This commit is contained in:
commit
19ea8159b3
53 changed files with 923 additions and 492 deletions
|
@ -27,8 +27,9 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"babel/array-bracket-spacing": 0,
|
"babel/array-bracket-spacing": 1,
|
||||||
"babel/object-curly-spacing": 0,
|
"babel/object-curly-spacing": 0,
|
||||||
|
# "babel/object-curly-spacing": [1, "always", { "objectsInObjects": false, "arraysInObjects": false }],
|
||||||
"babel/object-shorthand": 0,
|
"babel/object-shorthand": 0,
|
||||||
"babel/arrow-parens": 0,
|
"babel/arrow-parens": 0,
|
||||||
"no-await-in-loop": 1,
|
"no-await-in-loop": 1,
|
||||||
|
@ -78,4 +79,4 @@
|
||||||
"param": true,
|
"param": true,
|
||||||
"returns": true
|
"returns": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
const {esNextPaths} = require('./.vulcan/shared/pathsByLanguageVersion');
|
const {esNextPaths} = require('./.vulcan/shared/pathsByLanguageVersion');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
bracketSpacing: false,
|
bracketSpacing: true,
|
||||||
singleQuote: true,
|
singleQuote: true,
|
||||||
jsxBracketSameLine: true,
|
jsxBracketSameLine: true,
|
||||||
trailingComma: 'es5',
|
trailingComma: 'es5',
|
||||||
printWidth: 80,
|
printWidth: 100,
|
||||||
parser: 'babylon',
|
parser: 'babylon',
|
||||||
|
|
||||||
overrides: [
|
overrides: [
|
||||||
|
@ -18,4 +18,4 @@ module.exports = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
2
.vulcan/.gitignore
vendored
Normal file
2
.vulcan/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
bkp
|
||||||
|
package.json
|
99
.vulcan/update_package.js
Normal file
99
.vulcan/update_package.js
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
var mergePackages = require('@userfrosting/merge-package-dependencies');
|
||||||
|
var jsdiff = require('diff');
|
||||||
|
require('colors');
|
||||||
|
|
||||||
|
function diffPartReducer(accumulator, part) {
|
||||||
|
// green for additions, red for deletions
|
||||||
|
// grey for common parts
|
||||||
|
var color = part.added ? 'green' : (part.removed ? 'red' : 'grey');
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: (accumulator.text || '') + part.value[color],
|
||||||
|
count: (accumulator.count || 0) + (part.added || part.removed ? 1 : 0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// copied from sort-object-keys package
|
||||||
|
function sortObjectByKeyNameList(object, sortWith) {
|
||||||
|
var keys;
|
||||||
|
var sortFn;
|
||||||
|
|
||||||
|
if (typeof sortWith === 'function') {
|
||||||
|
sortFn = sortWith;
|
||||||
|
} else {
|
||||||
|
keys = sortWith;
|
||||||
|
}
|
||||||
|
return (keys || []).concat(Object.keys(object).sort(sortFn)).reduce(function(total, key) {
|
||||||
|
total[key] = object[key];
|
||||||
|
return total;
|
||||||
|
}, Object.create({}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var appDirPath = './';
|
||||||
|
var vulcanDirPath = './.vulcan/';
|
||||||
|
|
||||||
|
if (!fs.existsSync(vulcanDirPath + 'package.json')) {
|
||||||
|
console.log('Could not find \'' + vulcanDirPath + 'package.json\'');
|
||||||
|
} else if (!fs.existsSync(appDirPath + 'package.json')) {
|
||||||
|
console.log('Could not find \'' + appDirPath + 'package.json\'');
|
||||||
|
} else {
|
||||||
|
var appPackageFile = fs.readFileSync(appDirPath + '/package.json');
|
||||||
|
var appPackageJson = JSON.parse(appPackageFile);
|
||||||
|
var vulcanPackageFile = fs.readFileSync(vulcanDirPath + 'package.json');
|
||||||
|
var vulcanPackageJson = JSON.parse(vulcanPackageFile);
|
||||||
|
|
||||||
|
if (appPackageJson.vulcanVersion) {
|
||||||
|
console.log(appPackageJson.name + '@' + appPackageJson.version +
|
||||||
|
' \'package.json\' will be updated from Vulcan@' + appPackageJson.vulcanVersion +
|
||||||
|
' to Vulcan@' + vulcanPackageJson.version +
|
||||||
|
' dependencies.');
|
||||||
|
} else {
|
||||||
|
console.log(appPackageJson.name + '@' + appPackageJson.version +
|
||||||
|
' \'package.json\' will be updated with Vulcan@' + vulcanPackageJson.version +
|
||||||
|
' dependencies.');
|
||||||
|
}
|
||||||
|
|
||||||
|
var backupDirPath = vulcanDirPath + 'bkp/';
|
||||||
|
if (!fs.existsSync(backupDirPath)) {
|
||||||
|
fs.mkdirSync(backupDirPath);
|
||||||
|
}
|
||||||
|
var backupFilePath = backupDirPath + 'package-' + Date.now() + '.json';
|
||||||
|
console.log('Saving a backup of \'' + appDirPath + 'package.json\' in \'' + backupFilePath + '\'');
|
||||||
|
fs.writeFileSync(backupFilePath, appPackageFile);
|
||||||
|
|
||||||
|
var updatedAppPackageJson = mergePackages.npm(
|
||||||
|
// IMPORTANT: parse again because mergePackages.npm mutates json
|
||||||
|
JSON.parse(appPackageFile),
|
||||||
|
[vulcanDirPath]
|
||||||
|
);
|
||||||
|
|
||||||
|
updatedAppPackageJson.vulcanVersion = vulcanPackageJson.version;
|
||||||
|
|
||||||
|
[
|
||||||
|
'dependencies',
|
||||||
|
'devDependencies',
|
||||||
|
'peerDependencies'
|
||||||
|
].forEach(function(key) {
|
||||||
|
if (updatedAppPackageJson[key]) {
|
||||||
|
updatedAppPackageJson[key] = sortObjectByKeyNameList(updatedAppPackageJson[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const diff = jsdiff.diffJson(
|
||||||
|
sortObjectByKeyNameList(appPackageJson[key] || {}),
|
||||||
|
updatedAppPackageJson[key] || {}
|
||||||
|
).reduce(diffPartReducer, {});
|
||||||
|
if (diff.count) {
|
||||||
|
console.log('Changes in "' + key + '":');
|
||||||
|
console.log(diff.text);
|
||||||
|
} else {
|
||||||
|
console.log('No changes in "' + key + '".');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync(appDirPath + 'package.json', JSON.stringify(updatedAppPackageJson, null, ' '));
|
||||||
|
}
|
|
@ -11,7 +11,8 @@
|
||||||
"test-unit": "TEST_WATCH=1 meteor test-packages ./packages/* --port 3002 --driver-package meteortesting:mocha --raw-logs",
|
"test-unit": "TEST_WATCH=1 meteor test-packages ./packages/* --port 3002 --driver-package meteortesting:mocha --raw-logs",
|
||||||
"test": "npm run test-unit",
|
"test": "npm run test-unit",
|
||||||
"prettier": "node ./.vulcan/prettier/index.js write-changed",
|
"prettier": "node ./.vulcan/prettier/index.js write-changed",
|
||||||
"prettier-all": "node ./.vulcan/prettier/index.js write"
|
"prettier-all": "node ./.vulcan/prettier/index.js write",
|
||||||
|
"update-package-json": "node ./.vulcan/update_package.js"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
|
@ -105,10 +106,13 @@
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@userfrosting/merge-package-dependencies": "^1.2.0",
|
||||||
"autoprefixer": "^6.3.6",
|
"autoprefixer": "^6.3.6",
|
||||||
"babel-eslint": "^7.0.0",
|
"babel-eslint": "^7.0.0",
|
||||||
"babylon": "^6.18.0",
|
"babylon": "^6.18.0",
|
||||||
|
"colors": "^1.3.2",
|
||||||
"chromedriver": "^2.40.0",
|
"chromedriver": "^2.40.0",
|
||||||
|
"diff": "^3.5.0",
|
||||||
"enzyme": "^3.3.0",
|
"enzyme": "^3.3.0",
|
||||||
"enzyme-adapter-react-16.3": "^1.4.0",
|
"enzyme-adapter-react-16.3": "^1.4.0",
|
||||||
"eslint": "^3.19.0",
|
"eslint": "^3.19.0",
|
||||||
|
|
|
@ -6,9 +6,9 @@ export class AccountsFormMessage extends React.Component {
|
||||||
let { message, type, className = 'message', style = {} } = this.props;
|
let { message, type, className = 'message', style = {} } = this.props;
|
||||||
message = _.isObject(message) ? message.message : message; // If message is object, then try to get message from it
|
message = _.isObject(message) ? message.message : message; // If message is object, then try to get message from it
|
||||||
return message ? (
|
return message ? (
|
||||||
<div style={style} className={[ className, type ].join(' ')}>{ message }</div>
|
<div style={style} className={[className, type].join(' ')}>{ message }</div>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerComponent('AccountsFormMessage', AccountsFormMessage);
|
registerComponent('AccountsFormMessage', AccountsFormMessage);
|
||||||
|
|
27
packages/vulcan-admin/lib/components/AdminLayout.jsx
Normal file
27
packages/vulcan-admin/lib/components/AdminLayout.jsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* @Author: Apollinaire Lecocq <apollinaire>
|
||||||
|
* @Date: 08-01-19
|
||||||
|
* @Last modified by: apollinaire
|
||||||
|
* @Last modified time: 10-01-19
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import {registerComponent, Components, withAccess, Dummy} from 'meteor/vulcan:core';
|
||||||
|
|
||||||
|
const RestrictToAdmins = withAccess({groups: ['admins']})(Dummy);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple component that renders the existing layout and checks wether the currentUser is an admin or not.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function AdminLayout({children}) {
|
||||||
|
return (
|
||||||
|
<Components.Layout>
|
||||||
|
<RestrictToAdmins>{children}</RestrictToAdmins>
|
||||||
|
</Components.Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerComponent({
|
||||||
|
name: 'AdminLayout',
|
||||||
|
component: AdminLayout,
|
||||||
|
});
|
|
@ -1,3 +1,4 @@
|
||||||
import './fragments.js';
|
import './fragments.js';
|
||||||
import './routes.js';
|
import './routes.js';
|
||||||
import './i18n.js';
|
import './i18n.js';
|
||||||
|
import '../components/AdminLayout';
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
import { addRoute, getDynamicComponent } from 'meteor/vulcan:core';
|
import {addRoute, getDynamicComponent} from 'meteor/vulcan:core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
addRoute({ name: 'admin', path: '/admin', component: () => getDynamicComponent(import('../components/AdminHome.jsx'))});
|
addRoute({
|
||||||
addRoute({ name: 'admin2', path: '/admin/users', component: () => getDynamicComponent(import('../components/AdminHome.jsx'))});
|
name: 'admin',
|
||||||
|
path: '/admin',
|
||||||
|
component: () => getDynamicComponent(import('../components/AdminHome.jsx')),
|
||||||
|
layoutName: 'AdminLayout',
|
||||||
|
});
|
||||||
|
addRoute({
|
||||||
|
name: 'admin2',
|
||||||
|
path: '/admin/users',
|
||||||
|
component: () => getDynamicComponent(import('../components/AdminHome.jsx')),
|
||||||
|
});
|
||||||
|
|
|
@ -20,13 +20,11 @@ import moment from 'moment';
|
||||||
import { Switch, Route } from 'react-router-dom';
|
import { Switch, Route } from 'react-router-dom';
|
||||||
import { withRouter} from 'react-router';
|
import { withRouter} from 'react-router';
|
||||||
|
|
||||||
const DummyErrorCatcher = ({ children }) => children;
|
|
||||||
|
|
||||||
// see https://stackoverflow.com/questions/42862028/react-router-v4-with-multiple-layouts
|
// see https://stackoverflow.com/questions/42862028/react-router-v4-with-multiple-layouts
|
||||||
const RouteWithLayout = ({ layoutName, component, currentRoute, ...rest }) => {
|
const RouteWithLayout = ({ layoutName, component, currentRoute, ...rest }) => {
|
||||||
|
|
||||||
// if defined, use ErrorCatcher component to wrap layout contents
|
// if defined, use ErrorCatcher component to wrap layout contents
|
||||||
const ErrorCatcher = Components.ErrorCatcher ? Components.ErrorCatcher : DummyErrorCatcher;
|
const ErrorCatcher = Components.ErrorCatcher ? Components.ErrorCatcher : Components.Dummy;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Route
|
<Route
|
||||||
|
@ -38,8 +36,8 @@ const RouteWithLayout = ({ layoutName, component, currentRoute, ...rest }) => {
|
||||||
{...rest}
|
{...rest}
|
||||||
render={props => {
|
render={props => {
|
||||||
|
|
||||||
const layoutProps = {...props, currentRoute}
|
const layoutProps = { ...props, currentRoute };
|
||||||
const childComponentProps = {...props, currentRoute}
|
const childComponentProps = { ...props, currentRoute };
|
||||||
const layout = layoutName ? Components[layoutName] : Components.Layout;
|
const layout = layoutName ? Components[layoutName] : Components.Layout;
|
||||||
return React.createElement(layout, layoutProps, <ErrorCatcher>{React.createElement(component, childComponentProps)}</ErrorCatcher>);
|
return React.createElement(layout, layoutProps, <ErrorCatcher>{React.createElement(component, childComponentProps)}</ErrorCatcher>);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -187,4 +187,4 @@ Card.contextTypes = {
|
||||||
intl: intlShape
|
intl: intlShape
|
||||||
};
|
};
|
||||||
|
|
||||||
registerComponent('Card', Card);
|
registerComponent('Card', Card);
|
||||||
|
|
10
packages/vulcan-core/lib/modules/components/Dummy.jsx
Normal file
10
packages/vulcan-core/lib/modules/components/Dummy.jsx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {registerComponent} from 'meteor/vulcan:lib';
|
||||||
|
|
||||||
|
function Dummy({children}) {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
Dummy.displayName = 'Dummy';
|
||||||
|
|
||||||
|
registerComponent({name: 'Dummy', component: Dummy});
|
||||||
|
export default Dummy;
|
|
@ -47,7 +47,7 @@ class Flash extends PureComponent {
|
||||||
const flashType = type === 'error' ? 'danger' : type; // if flashType is "error", use "danger" instead
|
const flashType = type === 'error' ? 'danger' : type; // if flashType is "error", use "danger" instead
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Components.Alert className="flash-message" variant={flashType} onDismiss={this.dismissFlash}>
|
<Components.Alert className="flash-message" variant={flashType} onClose={this.dismissFlash}>
|
||||||
<span dangerouslySetInnerHTML={{ __html: message }} />
|
<span dangerouslySetInnerHTML={{ __html: message }} />
|
||||||
</Components.Alert>
|
</Components.Alert>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,31 +1,63 @@
|
||||||
import React, { PureComponent } from 'react';
|
import React, {PureComponent} from 'react';
|
||||||
import { withCurrentUser } from 'meteor/vulcan:core';
|
import {Components} from 'meteor/vulcan:lib';
|
||||||
import { withRouter } from 'react-router';
|
import withCurrentUser from './withCurrentUser';
|
||||||
|
import {withRouter} from 'react-router';
|
||||||
import Users from 'meteor/vulcan:users';
|
import Users from 'meteor/vulcan:users';
|
||||||
|
|
||||||
export default function withAccess (options) {
|
/**
|
||||||
|
* withAccess - description
|
||||||
|
*
|
||||||
|
* @param {Object} options the options that define the hoc
|
||||||
|
* @param {string[]} options.groups the groups that have access to this component
|
||||||
|
* @param {string} options.redirect the link to redirect to in case the access is not granted (optional)
|
||||||
|
* @param {string} options.failureComponentName the name of a component to display if access is not granted (optional)
|
||||||
|
* @param {Component} options.failureComponent the component to display if access is not granted (optional)
|
||||||
|
* @return {PureComponent} a React component that will display only if the acces is granted
|
||||||
|
*/
|
||||||
|
|
||||||
const { groups, redirect } = options;
|
export default function withAccess(options) {
|
||||||
|
const {
|
||||||
|
groups = [],
|
||||||
|
redirect = null,
|
||||||
|
failureComponent = null,
|
||||||
|
failureComponentName = null,
|
||||||
|
} = options;
|
||||||
|
|
||||||
// we return a function that takes a component and itself returns a component
|
// we return a function that takes a component and itself returns a component
|
||||||
return WrappedComponent => {
|
return WrappedComponent => {
|
||||||
class AccessComponent extends PureComponent {
|
class AccessComponent extends PureComponent {
|
||||||
|
|
||||||
// if there are any groups defined check if user belongs, else just check if user exists
|
// if there are any groups defined check if user belongs, else just check if user exists
|
||||||
canAccess = currentUser => {
|
canAccess = currentUser => {
|
||||||
return groups ? Users.isMemberOf(currentUser, groups) : currentUser;
|
return groups ? Users.isMemberOf(currentUser, groups) : currentUser;
|
||||||
}
|
};
|
||||||
|
|
||||||
// redirect on constructor if user cannot access
|
// redirect on constructor if user cannot access
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
if(!this.canAccess(props.currentUser) && typeof redirect === 'string') {
|
if (
|
||||||
|
!this.canAccess(props.currentUser) &&
|
||||||
|
typeof redirect === 'string'
|
||||||
|
) {
|
||||||
props.router.push(redirect);
|
props.router.push(redirect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderFailureComponent() {
|
||||||
|
if (failureComponentName) {
|
||||||
|
const FailureComponent = Components[failureComponentName];
|
||||||
|
return <FailureComponent {...this.props} />;
|
||||||
|
} else if (failureComponent) {
|
||||||
|
const FailureComponent = failureComponent; // necesary because jsx components must be uppercase
|
||||||
|
return <FailureComponent {...this.props} />;
|
||||||
|
} else return null;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return this.canAccess(this.props.currentUser) ? <WrappedComponent {...this.props}/> : null;
|
return this.canAccess(this.props.currentUser) ? (
|
||||||
|
<WrappedComponent {...this.props} />
|
||||||
|
) : (
|
||||||
|
this.renderFailureComponent()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -419,14 +419,12 @@ const registerCollectionCallbacks = (typeName, options) => {
|
||||||
if (options.create) {
|
if (options.create) {
|
||||||
registerCallback({
|
registerCallback({
|
||||||
name: `${typeName}.create.validate`,
|
name: `${typeName}.create.validate`,
|
||||||
iterator: {document: 'The document being inserted'},
|
iterator: { validationErrors: 'An array that can be used to accumulate validation errors' },
|
||||||
properties: [
|
properties: [
|
||||||
{document: 'The document being inserted'},
|
{ document: 'The document being inserted' },
|
||||||
{currentUser: 'The current user'},
|
{ currentUser: 'The current user' },
|
||||||
{
|
{ collection: 'The collection the document belongs to' },
|
||||||
validationErrors:
|
{ context: 'The context of the mutation'},
|
||||||
'An object that can be used to accumulate validation errors',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
runs: 'sync',
|
runs: 'sync',
|
||||||
returns: 'document',
|
returns: 'document',
|
||||||
|
@ -467,14 +465,13 @@ const registerCollectionCallbacks = (typeName, options) => {
|
||||||
if (options.update) {
|
if (options.update) {
|
||||||
registerCallback({
|
registerCallback({
|
||||||
name: `${typeName}.update.validate`,
|
name: `${typeName}.update.validate`,
|
||||||
iterator: {data: 'The client data'},
|
iterator: { validationErrors: 'An object that can be used to accumulate validation errors' },
|
||||||
properties: [
|
properties: [
|
||||||
{document: 'The document being edited'},
|
{ document: 'The document being edited' },
|
||||||
{currentUser: 'The current user'},
|
{ data: 'The client data' },
|
||||||
{
|
{ currentUser: 'The current user' },
|
||||||
validationErrors:
|
{ collection: 'The collection the document belongs to' },
|
||||||
'An object that can be used to accumulate validation errors',
|
{ context: 'The context of the mutation'},
|
||||||
},
|
|
||||||
],
|
],
|
||||||
runs: 'sync',
|
runs: 'sync',
|
||||||
returns: 'modifier',
|
returns: 'modifier',
|
||||||
|
@ -522,13 +519,12 @@ const registerCollectionCallbacks = (typeName, options) => {
|
||||||
if (options.delete) {
|
if (options.delete) {
|
||||||
registerCallback({
|
registerCallback({
|
||||||
name: `${typeName}.delete.validate`,
|
name: `${typeName}.delete.validate`,
|
||||||
iterator: {document: 'The document being removed'},
|
iterator: { validationErrors: 'An object that can be used to accumulate validation errors' },
|
||||||
properties: [
|
properties: [
|
||||||
{currentUser: 'The current user'},
|
{ currentUser: 'The current user' },
|
||||||
{
|
{ document: 'The document being removed' },
|
||||||
validationErrors:
|
{ collection: 'The collection the document belongs to'},
|
||||||
'An object that can be used to accumulate validation errors',
|
{ context: 'The context of this mutation'}
|
||||||
},
|
|
||||||
],
|
],
|
||||||
runs: 'sync',
|
runs: 'sync',
|
||||||
returns: 'document',
|
returns: 'document',
|
||||||
|
|
|
@ -25,6 +25,7 @@ export { default as HelloWorld } from './components/HelloWorld.jsx';
|
||||||
export { default as Welcome } from './components/Welcome.jsx';
|
export { default as Welcome } from './components/Welcome.jsx';
|
||||||
export { default as RouterHook } from './components/RouterHook.jsx';
|
export { default as RouterHook } from './components/RouterHook.jsx';
|
||||||
export { default as ScrollToTop } from './components/ScrollToTop.jsx';
|
export { default as ScrollToTop } from './components/ScrollToTop.jsx';
|
||||||
|
export { default as Dummy } from './components/Dummy.jsx';
|
||||||
|
|
||||||
export { default as withAccess } from './containers/withAccess.js';
|
export { default as withAccess } from './containers/withAccess.js';
|
||||||
export { default as withMessages } from './containers/withMessages.js';
|
export { default as withMessages } from './containers/withMessages.js';
|
||||||
|
@ -36,6 +37,7 @@ export { default as withDelete } from './containers/withDelete.js';
|
||||||
export { default as withCurrentUser } from './containers/withCurrentUser.js';
|
export { default as withCurrentUser } from './containers/withCurrentUser.js';
|
||||||
export { default as withMutation } from './containers/withMutation.js';
|
export { default as withMutation } from './containers/withMutation.js';
|
||||||
export { default as withUpsert } from './containers/withUpsert.js';
|
export { default as withUpsert } from './containers/withUpsert.js';
|
||||||
|
export { default as withSiteData } from './containers/withSiteData.js';
|
||||||
|
|
||||||
export { default as withComponents } from './containers/withComponents';
|
export { default as withComponents } from './containers/withComponents';
|
||||||
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Components, registerComponent } from 'meteor/vulcan:lib';
|
|
||||||
|
|
||||||
const adminStyles = {
|
|
||||||
padding: '20px'
|
|
||||||
};
|
|
||||||
|
|
||||||
const AdminLayout = props => <div className="admin-layout" style={adminStyles}>{props.children}</div>;
|
|
||||||
|
|
||||||
registerComponent('AdminLayout', AdminLayout);
|
|
10
packages/vulcan-debug/lib/components/DebugLayout.jsx
Normal file
10
packages/vulcan-debug/lib/components/DebugLayout.jsx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Components, registerComponent } from 'meteor/vulcan:lib';
|
||||||
|
|
||||||
|
const debugStyles = {
|
||||||
|
padding: '20px'
|
||||||
|
};
|
||||||
|
|
||||||
|
const DebugLayout = props => <div className="debug-layout" style={debugStyles}>{props.children}</div>;
|
||||||
|
|
||||||
|
registerComponent('DebugLayout', DebugLayout);
|
|
@ -2,25 +2,30 @@ import React from 'react';
|
||||||
import { registerComponent, Components, Routes } from 'meteor/vulcan:lib';
|
import { registerComponent, Components, Routes } from 'meteor/vulcan:lib';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
const RoutePath = ({ document }) =>
|
const RoutePath = ({document}) => (
|
||||||
<Link to={document.path}>{document.path}</Link>;
|
<Link to={document.path}>{document.path}</Link>
|
||||||
|
);
|
||||||
|
|
||||||
const RoutesDashboard = props =>
|
const RoutesDashboard = props => {
|
||||||
<div className="routes">
|
return (
|
||||||
<Components.Datatable
|
<div className="routes">
|
||||||
showSearch={false}
|
<Components.Datatable
|
||||||
showNew={false}
|
showSearch={false}
|
||||||
showEdit={false}
|
showNew={false}
|
||||||
data={Object.values(Routes)}
|
showEdit={false}
|
||||||
columns={[
|
data={Object.values(Routes)}
|
||||||
'name',
|
columns={[
|
||||||
{
|
'name',
|
||||||
name: 'path',
|
{
|
||||||
component: RoutePath
|
name: 'path',
|
||||||
},
|
component: RoutePath,
|
||||||
'componentName',
|
},
|
||||||
]}
|
'componentName',
|
||||||
/>
|
'layoutName'
|
||||||
</div>;
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
registerComponent('Routes', RoutesDashboard);
|
registerComponent('Routes', RoutesDashboard);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import '../components/AdminLayout.jsx';
|
import '../components/DebugLayout.jsx';
|
||||||
|
|
||||||
import '../components/Emails.jsx';
|
import '../components/Emails.jsx';
|
||||||
import '../components/Groups.jsx';
|
import '../components/Groups.jsx';
|
||||||
|
|
|
@ -1,14 +1,54 @@
|
||||||
import { addRoute, getDynamicComponent } from 'meteor/vulcan:lib';
|
import {addRoute, getDynamicComponent} from 'meteor/vulcan:lib';
|
||||||
|
|
||||||
addRoute([
|
addRoute([
|
||||||
// {name: 'cheatsheet', path: '/cheatsheet', component: import('./components/Cheatsheet.jsx')},
|
// {name: 'cheatsheet', path: '/cheatsheet', component: import('./components/Cheatsheet.jsx')},
|
||||||
{ name: 'debug', path: '/debug', componentName: 'DebugDashboard', layoutName: 'AdminLayout' },
|
{
|
||||||
{ name: 'debugGroups', path: '/debug/groups', component: () => getDynamicComponent(import('../components/Groups.jsx')), layoutName: 'AdminLayout' },
|
name: 'debug',
|
||||||
{ name: 'debugSettings', path: '/debug/settings', componentName: 'Settings', layoutName: 'AdminLayout' },
|
path: '/debug',
|
||||||
{ name: 'debugCallbacks', path: '/debug/callbacks', componentName: 'Callbacks', layoutName: 'AdminLayout' },
|
componentName: 'DebugDashboard',
|
||||||
|
layoutName: 'DebugLayout',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'debugGroups',
|
||||||
|
path: '/debug/groups',
|
||||||
|
component: () => getDynamicComponent(import('../components/Groups.jsx')),
|
||||||
|
layoutName: 'DebugLayout',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'debugSettings',
|
||||||
|
path: '/debug/settings',
|
||||||
|
componentName: 'Settings',
|
||||||
|
layoutName: 'DebugLayout',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'debugCallbacks',
|
||||||
|
path: '/debug/callbacks',
|
||||||
|
componentName: 'Callbacks',
|
||||||
|
layoutName: 'DebugLayout',
|
||||||
|
},
|
||||||
// {name: 'emails', path: '/emails', component: () => getDynamicComponent(import('./components/Emails.jsx'))},
|
// {name: 'emails', path: '/emails', component: () => getDynamicComponent(import('./components/Emails.jsx'))},
|
||||||
{ name: 'debugEmails', path: '/debug/emails', componentName: 'Emails', layoutName: 'AdminLayout' },
|
{
|
||||||
{ name: 'debugRoutes', path: '/debug/routes', componentName: 'Routes', layoutName: 'AdminLayout' },
|
name: 'debugEmails',
|
||||||
{ name: 'debugComponents', path: '/debug/components', componentName: 'Components', layoutName: 'AdminLayout' },
|
path: '/debug/emails',
|
||||||
{ name: 'debugI18n', path: '/debug/i18n', componentName: 'I18n', layoutName: 'AdminLayout' },
|
componentName: 'Emails',
|
||||||
|
layoutName: 'DebugLayout',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'debugRoutes',
|
||||||
|
path: '/debug/routes',
|
||||||
|
componentName: 'Routes',
|
||||||
|
layoutName: 'DebugLayout',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'debugComponents',
|
||||||
|
path: '/debug/components',
|
||||||
|
componentName: 'Components',
|
||||||
|
layoutName: 'DebugLayout',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'debugI18n',
|
||||||
|
path: '/debug/i18n',
|
||||||
|
componentName: 'I18n',
|
||||||
|
layoutName: 'DebugLayout',
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -4,6 +4,7 @@ import Juice from 'juice';
|
||||||
import htmlToText from 'html-to-text';
|
import htmlToText from 'html-to-text';
|
||||||
import Handlebars from 'handlebars';
|
import Handlebars from 'handlebars';
|
||||||
import { Utils, getSetting, registerSetting, runQuery, Strings, getString } from 'meteor/vulcan:lib'; // import from vulcan:lib because vulcan:core is not loaded yet
|
import { Utils, getSetting, registerSetting, runQuery, Strings, getString } from 'meteor/vulcan:lib'; // import from vulcan:lib because vulcan:core is not loaded yet
|
||||||
|
import { Email } from 'meteor/email';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
@ -82,13 +83,13 @@ VulcanEmail.generateTextVersion = html => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
VulcanEmail.send = (to, subject, html, text, throwErrors, cc, bcc, replyTo, headers) => {
|
VulcanEmail.send = (to, subject, html, text, throwErrors, cc, bcc, replyTo, headers, attachments) => {
|
||||||
// TODO: limit who can send emails
|
// TODO: limit who can send emails
|
||||||
// TODO: fix this error: Error: getaddrinfo ENOTFOUND
|
// TODO: fix this error: Error: getaddrinfo ENOTFOUND
|
||||||
|
|
||||||
if (typeof to === 'object') {
|
if (typeof to === 'object') {
|
||||||
// eslint-disable-next-line no-redeclare
|
// eslint-disable-next-line no-redeclare
|
||||||
var { to, cc, bcc, replyTo, subject, html, text, throwErrors, headers } = to;
|
var { to, cc, bcc, replyTo, subject, html, text, throwErrors, headers, attachments } = to;
|
||||||
}
|
}
|
||||||
|
|
||||||
const from = getSetting('defaultEmail', 'noreply@example.com');
|
const from = getSetting('defaultEmail', 'noreply@example.com');
|
||||||
|
@ -102,14 +103,15 @@ VulcanEmail.send = (to, subject, html, text, throwErrors, cc, bcc, replyTo, head
|
||||||
|
|
||||||
const email = {
|
const email = {
|
||||||
from: from,
|
from: from,
|
||||||
to: to,
|
to,
|
||||||
cc: cc,
|
cc,
|
||||||
bcc: bcc,
|
bcc,
|
||||||
replyTo: replyTo,
|
replyTo,
|
||||||
subject: subject,
|
subject,
|
||||||
headers: headers,
|
headers,
|
||||||
text: text,
|
text,
|
||||||
html: html,
|
html,
|
||||||
|
attachments,
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldSendEmail = process.env.NODE_ENV === 'production' || getSetting('enableDevelopmentEmails', false);
|
const shouldSendEmail = process.env.NODE_ENV === 'production' || getSetting('enableDevelopmentEmails', false);
|
||||||
|
@ -153,9 +155,9 @@ VulcanEmail.build = async ({ emailName, variables, locale }) => {
|
||||||
return { data, subject, html };
|
return { data, subject, html };
|
||||||
};
|
};
|
||||||
|
|
||||||
VulcanEmail.buildAndSend = async ({ to, cc, bcc, replyTo, emailName, variables, locale = getSetting('locale'), headers }) => {
|
VulcanEmail.buildAndSend = async ({ to, cc, bcc, replyTo, emailName, variables, locale = getSetting('locale'), headers, attachments }) => {
|
||||||
const email = await VulcanEmail.build({ to, emailName, variables, locale });
|
const email = await VulcanEmail.build({ to, emailName, variables, locale });
|
||||||
return VulcanEmail.send({ to, cc, bcc, replyTo, subject: email.subject, html: email.html, headers });
|
return VulcanEmail.send({ to, cc, bcc, replyTo, subject: email.subject, html: email.html, headers, attachments });
|
||||||
};
|
};
|
||||||
|
|
||||||
VulcanEmail.buildAndSendHTML = (to, subject, html) => VulcanEmail.send(to, subject, VulcanEmail.buildTemplate(html));
|
VulcanEmail.buildAndSendHTML = (to, subject, html) => VulcanEmail.send(to, subject, VulcanEmail.buildTemplate(html));
|
||||||
|
|
|
@ -3,9 +3,7 @@ import { withMutation } from 'meteor/vulcan:core';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage, intlShape } from 'meteor/vulcan:i18n';
|
import { FormattedMessage, intlShape } from 'meteor/vulcan:i18n';
|
||||||
import FRC from 'formsy-react-components';
|
import Form from 'react-bootstrap/lib/Form';
|
||||||
|
|
||||||
const Input = FRC.Input;
|
|
||||||
|
|
||||||
class EmbedURL extends Component {
|
class EmbedURL extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -161,7 +159,7 @@ class EmbedURL extends Component {
|
||||||
<label className="control-label col-sm-3">{this.props.label}</label>
|
<label className="control-label col-sm-3">{this.props.label}</label>
|
||||||
<div className="col-sm-9 embedly-form-control">
|
<div className="col-sm-9 embedly-form-control">
|
||||||
<div className="embedly-url-field">
|
<div className="embedly-url-field">
|
||||||
<Input
|
<Form.Control
|
||||||
{...inputProperties}
|
{...inputProperties}
|
||||||
onBlur={this.handleBlur}
|
onBlur={this.handleBlur}
|
||||||
type="url"
|
type="url"
|
||||||
|
|
|
@ -10,7 +10,8 @@ Usage:
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Components, registerComponent, withCurrentUser } from 'meteor/vulcan:core';
|
import { Components, registerComponent, withCurrentUser, withSiteData } from 'meteor/vulcan:core';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Errors } from '../modules/errors.js';
|
import { Errors } from '../modules/errors.js';
|
||||||
|
|
||||||
|
@ -31,11 +32,26 @@ class ErrorCatcher extends Component {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (
|
||||||
|
this.props.location &&
|
||||||
|
prevProps.location &&
|
||||||
|
this.props.location.pathname &&
|
||||||
|
prevProps.location.pathname &&
|
||||||
|
prevProps.location.pathname !== this.props.location.pathname
|
||||||
|
) {
|
||||||
|
// reset the component state when the route changes to re-render the app and avodi blocking the navigation
|
||||||
|
this.setState({ error: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { error } = this.state;
|
const { error } = this.state;
|
||||||
return error ? (
|
return error ? (
|
||||||
<div className="error-catcher">
|
<div className="error-catcher">
|
||||||
<Components.Flash message={{ id: 'errors.generic_report', properties: { errorMessage: error.message } }} />
|
<Components.Flash
|
||||||
|
message={{ id: 'errors.generic_report', properties: { errorMessage: error.message } }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
this.props.children
|
this.props.children
|
||||||
|
@ -43,4 +59,4 @@ class ErrorCatcher extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerComponent('ErrorCatcher', ErrorCatcher, withCurrentUser);
|
registerComponent('ErrorCatcher', ErrorCatcher, withCurrentUser, withSiteData, withRouter);
|
||||||
|
|
|
@ -36,7 +36,6 @@ import React, { Component } from 'react';
|
||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { intlShape } from 'meteor/vulcan:i18n';
|
import { intlShape } from 'meteor/vulcan:i18n';
|
||||||
import Formsy from 'formsy-react';
|
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import set from 'lodash/set';
|
import set from 'lodash/set';
|
||||||
|
@ -319,13 +318,15 @@ class SmartForm extends Component {
|
||||||
Note: when submitting the form (getData()), do not include any extra fields.
|
Note: when submitting the form (getData()), do not include any extra fields.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
getFieldNames = (args = {}) => {
|
getFieldNames = (args) => {
|
||||||
|
// we do this to avoid having default values in arrow functions, which breaks MS Edge support. See https://github.com/meteor/meteor/issues/10171
|
||||||
|
let args0 = args || {};
|
||||||
const {
|
const {
|
||||||
schema = this.state.schema,
|
schema = this.state.schema,
|
||||||
excludeHiddenFields = true,
|
excludeHiddenFields = true,
|
||||||
replaceIntlFields = false,
|
replaceIntlFields = false,
|
||||||
addExtraFields = true
|
addExtraFields = true
|
||||||
} = args;
|
} = args0;
|
||||||
|
|
||||||
const { fields, addFields } = this.props;
|
const { fields, addFields } = this.props;
|
||||||
|
|
||||||
|
@ -619,7 +620,7 @@ class SmartForm extends Component {
|
||||||
...newValues
|
...newValues
|
||||||
} // Submit form after setState update completed
|
} // Submit form after setState update completed
|
||||||
}),
|
}),
|
||||||
() => this.submitForm(this.form.getModel())
|
() => this.submitForm()
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -856,7 +857,7 @@ class SmartForm extends Component {
|
||||||
*/
|
*/
|
||||||
formKeyDown = event => {
|
formKeyDown = event => {
|
||||||
if ((event.ctrlKey || event.metaKey) && event.keyCode === 13) {
|
if ((event.ctrlKey || event.metaKey) && event.keyCode === 13) {
|
||||||
this.submitForm(this.form.getModel());
|
this.submitForm();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -920,9 +921,10 @@ class SmartForm extends Component {
|
||||||
Submit form handler
|
Submit form handler
|
||||||
|
|
||||||
*/
|
*/
|
||||||
submitForm = data => {
|
submitForm = event => {
|
||||||
// note: we can discard the data collected by Formsy because all the data we need is already available via getDocument()
|
|
||||||
|
|
||||||
|
event && event.preventDefault();
|
||||||
|
|
||||||
// if form is disabled (there is already a submit handler running) don't do anything
|
// if form is disabled (there is already a submit handler running) don't do anything
|
||||||
if (this.state.disabled) {
|
if (this.state.disabled) {
|
||||||
return;
|
return;
|
||||||
|
@ -931,9 +933,9 @@ class SmartForm extends Component {
|
||||||
// clear errors and disable form while it's submitting
|
// clear errors and disable form while it's submitting
|
||||||
this.setState(prevState => ({ errors: [], disabled: true }));
|
this.setState(prevState => ({ errors: [], disabled: true }));
|
||||||
|
|
||||||
// complete the data with values from custom components which are not being catched by Formsy mixin
|
// complete the data with values from custom components
|
||||||
// note: it follows the same logic as SmartForm's getDocument method
|
// note: it follows the same logic as SmartForm's getDocument method
|
||||||
data = this.getData({ replaceIntlFields: true, addExtraFields: false });
|
let data = this.getData({ replaceIntlFields: true, addExtraFields: false });
|
||||||
|
|
||||||
// if there's a submit callback, run it
|
// if there's a submit callback, run it
|
||||||
if (this.props.submitCallback) {
|
if (this.props.submitCallback) {
|
||||||
|
@ -988,67 +990,78 @@ class SmartForm extends Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------- //
|
||||||
|
// ------------------------- Props to Pass ----------------------------- //
|
||||||
|
// --------------------------------------------------------------------- //
|
||||||
|
|
||||||
|
getFormProps = () => ({
|
||||||
|
className: 'document-' + this.getFormType(),
|
||||||
|
id: this.props.id,
|
||||||
|
onSubmit: this.submitForm,
|
||||||
|
onKeyDown: this.formKeyDown,
|
||||||
|
ref: e => {
|
||||||
|
this.form = e;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
getFormErrorsProps = () => ({
|
||||||
|
errors: this.state.errors
|
||||||
|
});
|
||||||
|
|
||||||
|
getFormGroupProps = group => ({
|
||||||
|
key: group.name,
|
||||||
|
...group,
|
||||||
|
errors: this.state.errors,
|
||||||
|
throwError: this.throwError,
|
||||||
|
currentValues: this.state.currentValues,
|
||||||
|
updateCurrentValues: this.updateCurrentValues,
|
||||||
|
deletedValues: this.state.deletedValues,
|
||||||
|
addToDeletedValues: this.addToDeletedValues,
|
||||||
|
clearFieldErrors: this.clearFieldErrors,
|
||||||
|
formType: this.getFormType(),
|
||||||
|
currentUser: this.props.currentUser,
|
||||||
|
disabled: this.state.disabled,
|
||||||
|
formComponents: mergeWithComponents(this.props.formComponents),
|
||||||
|
});
|
||||||
|
|
||||||
|
getFormSubmitProps = () => ({
|
||||||
|
submitLabel: this.props.submitLabel,
|
||||||
|
cancelLabel: this.props.cancelLabel,
|
||||||
|
revertLabel: this.props.revertLabel,
|
||||||
|
cancelCallback: this.props.cancelCallback,
|
||||||
|
revertCallback: this.props.revertCallback,
|
||||||
|
document: this.getDocument(),
|
||||||
|
deleteDocument:
|
||||||
|
(this.getFormType() === 'edit' &&
|
||||||
|
this.props.showRemove &&
|
||||||
|
this.deleteDocument) ||
|
||||||
|
null,
|
||||||
|
collectionName:this.props.collectionName,
|
||||||
|
currentValues:this.state.currentValues,
|
||||||
|
deletedValues:this.state.deletedValues,
|
||||||
|
errors:this.state.errors,
|
||||||
|
});
|
||||||
|
|
||||||
// --------------------------------------------------------------------- //
|
// --------------------------------------------------------------------- //
|
||||||
// ----------------------------- Render -------------------------------- //
|
// ----------------------------- Render -------------------------------- //
|
||||||
// --------------------------------------------------------------------- //
|
// --------------------------------------------------------------------- //
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const fieldGroups = this.getFieldGroups();
|
|
||||||
const collectionName = this.props.collectionName;
|
|
||||||
const FormComponents = mergeWithComponents(this.props.formComponents);
|
const FormComponents = mergeWithComponents(this.props.formComponents);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'document-' + this.getFormType()}>
|
<FormComponents.FormElement {...this.getFormProps()}>
|
||||||
<Formsy.Form
|
<FormComponents.FormErrors {...this.getFormErrorsProps()} />
|
||||||
id={this.props.id}
|
|
||||||
onSubmit={this.submitForm}
|
|
||||||
onKeyDown={this.formKeyDown}
|
|
||||||
ref={e => {
|
|
||||||
this.form = e;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormComponents.FormErrors errors={this.state.errors} />
|
|
||||||
|
|
||||||
{fieldGroups.map(group => (
|
{this.getFieldGroups().map(group => (
|
||||||
<FormComponents.FormGroup
|
<FormComponents.FormGroup {...this.getFormGroupProps(group)} />
|
||||||
key={group.name}
|
))}
|
||||||
{...group}
|
|
||||||
errors={this.state.errors}
|
|
||||||
throwError={this.throwError}
|
|
||||||
currentValues={this.state.currentValues}
|
|
||||||
updateCurrentValues={this.updateCurrentValues}
|
|
||||||
deletedValues={this.state.deletedValues}
|
|
||||||
addToDeletedValues={this.addToDeletedValues}
|
|
||||||
clearFieldErrors={this.clearFieldErrors}
|
|
||||||
formType={this.getFormType()}
|
|
||||||
currentUser={this.props.currentUser}
|
|
||||||
disabled={this.state.disabled}
|
|
||||||
formComponents={FormComponents}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{this.props.repeatErrors && this.renderErrors()}
|
{this.props.repeatErrors && this.renderErrors()}
|
||||||
|
|
||||||
<FormComponents.FormSubmit
|
<FormComponents.FormSubmit {...this.getFormSubmitProps()} />
|
||||||
submitLabel={this.props.submitLabel}
|
</FormComponents.FormElement>
|
||||||
cancelLabel={this.props.cancelLabel}
|
|
||||||
revertLabel={this.props.revertLabel}
|
|
||||||
cancelCallback={this.props.cancelCallback}
|
|
||||||
revertCallback={this.props.revertCallback}
|
|
||||||
document={this.getDocument()}
|
|
||||||
deleteDocument={
|
|
||||||
(this.getFormType() === 'edit' &&
|
|
||||||
this.props.showRemove &&
|
|
||||||
this.deleteDocument) ||
|
|
||||||
null
|
|
||||||
}
|
|
||||||
collectionName={collectionName}
|
|
||||||
currentValues={this.state.currentValues}
|
|
||||||
deletedValues={this.state.deletedValues}
|
|
||||||
errors={this.state.errors}
|
|
||||||
/>
|
|
||||||
</Formsy.Form>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,14 +28,14 @@ class FormComponent extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { currentValues, deletedValues, errors } = nextProps;
|
const { currentValues, deletedValues, errors } = nextProps;
|
||||||
const { path } = this.props;
|
const path = this.getPath(this.props);
|
||||||
|
|
||||||
// when checking for deleted values, both current path ('foo') and child path ('foo.0.bar') should trigger updates
|
// when checking for deleted values, both current path ('foo') and child path ('foo.0.bar') should trigger updates
|
||||||
const includesPathOrChildren = deletedValues =>
|
const includesPathOrChildren = deletedValues =>
|
||||||
deletedValues.some(deletedPath => deletedPath.includes(path));
|
deletedValues.some(deletedPath => deletedPath.includes(path));
|
||||||
|
|
||||||
const valueChanged =
|
const valueChanged =
|
||||||
get(currentValues, path) !== get(this.props.currentValues, path);
|
!isEqual(get(currentValues, path), get(this.props.currentValues, path));
|
||||||
const errorChanged = !isEqual(this.getErrors(errors), this.getErrors());
|
const errorChanged = !isEqual(this.getErrors(errors), this.getErrors());
|
||||||
const deleteChanged =
|
const deleteChanged =
|
||||||
includesPathOrChildren(deletedValues) !==
|
includesPathOrChildren(deletedValues) !==
|
||||||
|
@ -93,7 +93,8 @@ class FormComponent extends Component {
|
||||||
Function passed to form controls (always controlled) to update their value
|
Function passed to form controls (always controlled) to update their value
|
||||||
|
|
||||||
*/
|
*/
|
||||||
handleChange = (name, value) => {
|
handleChange = value => {
|
||||||
|
|
||||||
// if value is an empty string, delete the field
|
// if value is an empty string, delete the field
|
||||||
if (value === '') {
|
if (value === '') {
|
||||||
value = null;
|
value = null;
|
||||||
|
|
|
@ -97,15 +97,12 @@ class FormNestedArray extends PureComponent {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
(!maxCount || arrayLength < maxCount) && (
|
(!maxCount || arrayLength < maxCount) && (
|
||||||
<Components.Button
|
<Components.FormNestedFoot
|
||||||
key="add-button"
|
key="add-button"
|
||||||
size="small"
|
addItem={this.addItem}
|
||||||
variant="success"
|
label={this.props.label}
|
||||||
onClick={this.addItem}
|
className="form-nested-foot"
|
||||||
className="form-nested-button"
|
/>
|
||||||
>
|
|
||||||
<Components.IconAdd height={12} width={12} />
|
|
||||||
</Components.Button>
|
|
||||||
),
|
),
|
||||||
hasErrors ? (
|
hasErrors ? (
|
||||||
<FormComponents.FieldErrors
|
<FormComponents.FieldErrors
|
||||||
|
|
|
@ -2,8 +2,8 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
const FormNestedFoot = ({ label, addItem }) => (
|
const FormNestedFoot = ({ addItem }) => (
|
||||||
<Components.Button size="small" variant="success" iconButton onClick={addItem} className="form-nested-button">
|
<Components.Button size="small" variant="success" onClick={addItem} className="form-nested-button">
|
||||||
<Components.IconAdd height={12} width={12} />
|
<Components.IconAdd height={12} width={12} />
|
||||||
</Components.Button>
|
</Components.Button>
|
||||||
);
|
);
|
||||||
|
|
|
@ -51,6 +51,8 @@ import {
|
||||||
import withCollectionProps from './withCollectionProps';
|
import withCollectionProps from './withCollectionProps';
|
||||||
import { callbackProps } from './propTypes';
|
import { callbackProps } from './propTypes';
|
||||||
|
|
||||||
|
const intlSuffix = '_intl';
|
||||||
|
|
||||||
class FormWrapper extends PureComponent {
|
class FormWrapper extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -94,8 +96,11 @@ class FormWrapper extends PureComponent {
|
||||||
|
|
||||||
// if "fields" prop is specified, restrict list of fields to it
|
// if "fields" prop is specified, restrict list of fields to it
|
||||||
if (typeof fields !== 'undefined' && fields.length > 0) {
|
if (typeof fields !== 'undefined' && fields.length > 0) {
|
||||||
queryFields = _.intersection(queryFields, fields);
|
// add "_intl" suffix to all fields in case some of them are intl fields
|
||||||
mutationFields = _.intersection(mutationFields, fields);
|
const fieldsWithIntlSuffix = fields.map(field => `${field}${intlSuffix}`);
|
||||||
|
const allFields = [...fields, ...fieldsWithIntlSuffix];
|
||||||
|
queryFields = _.intersection(queryFields, allFields);
|
||||||
|
mutationFields = _.intersection(mutationFields, allFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add "addFields" prop contents to list of fields
|
// add "addFields" prop contents to list of fields
|
||||||
|
@ -105,7 +110,7 @@ class FormWrapper extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
const convertFields = field => {
|
const convertFields = field => {
|
||||||
return field.slice(-5) === '_intl' ? `${field}{ locale value }` : field;
|
return field.slice(-5) === intlSuffix ? `${field}{ locale value }` : field;
|
||||||
};
|
};
|
||||||
|
|
||||||
// generate query fragment based on the fields that can be edited. Note: always add _id.
|
// generate query fragment based on the fields that can be edited. Note: always add _id.
|
||||||
|
|
|
@ -49,15 +49,23 @@ export function registerComponent(name, rawComponent, ...hocs) {
|
||||||
* @param {String} name The name of the component to get.
|
* @param {String} name The name of the component to get.
|
||||||
* @returns {Function|React Component} A (wrapped) React component
|
* @returns {Function|React Component} A (wrapped) React component
|
||||||
*/
|
*/
|
||||||
export const getComponent = (name) => {
|
export const getComponent = name => {
|
||||||
const component = ComponentsTable[name];
|
const component = ComponentsTable[name];
|
||||||
if (!component) {
|
if (!component) {
|
||||||
throw new Error(`Component ${name} not registered.`);
|
throw new Error(`Component ${name} not registered.`);
|
||||||
}
|
}
|
||||||
if (component.hocs) {
|
if (component.hocs) {
|
||||||
const hocs = component.hocs.map(hoc => {
|
const hocs = component.hocs.map(hoc => {
|
||||||
if(!Array.isArray(hoc)) return hoc;
|
if (!Array.isArray(hoc)) {
|
||||||
|
if (typeof hoc !== 'function') {
|
||||||
|
throw new Error(`In registered component ${name}, an hoc is of type ${typeof hoc}`);
|
||||||
|
}
|
||||||
|
return hoc;
|
||||||
|
}
|
||||||
const [actualHoc, ...args] = hoc;
|
const [actualHoc, ...args] = hoc;
|
||||||
|
if (typeof actualHoc !== 'function') {
|
||||||
|
throw new Error(`In registered component ${name}, an hoc is of type ${typeof actualHoc}`);
|
||||||
|
}
|
||||||
return actualHoc(...args);
|
return actualHoc(...args);
|
||||||
});
|
});
|
||||||
return compose(...hocs)(component.rawComponent);
|
return compose(...hocs)(component.rawComponent);
|
||||||
|
@ -202,4 +210,3 @@ export const delayedComponent = name => {
|
||||||
// return proxy;
|
// return proxy;
|
||||||
//};
|
//};
|
||||||
export const mergeWithComponents = myComponents => (myComponents ? { ...Components, ...myComponents } : Components);
|
export const mergeWithComponents = myComponents => (myComponents ? { ...Components, ...myComponents } : Components);
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { delayedComponent } from './components';
|
||||||
* `Components.DynamicLoading` in the meantime.
|
* `Components.DynamicLoading` in the meantime.
|
||||||
*
|
*
|
||||||
* @example Register a component with a dynamic import
|
* @example Register a component with a dynamic import
|
||||||
* registerComponent('MyComponent', dynamicComponent(() => import('./path/to/MyComponent')));
|
* registerComponent('MyComponent', dynamicLoader(() => import('./path/to/MyComponent')));
|
||||||
*
|
*
|
||||||
* @example Pass a dynamic component to a route
|
* @example Pass a dynamic component to a route
|
||||||
* import { addRoute, dynamicLoader, getDynamicComponent } from 'meteor/vulcan:core';
|
* import { addRoute, dynamicLoader, getDynamicComponent } from 'meteor/vulcan:core';
|
||||||
|
|
|
@ -203,7 +203,10 @@ const createApolloServer = (givenOptions = {}, givenConfig = {}) => {
|
||||||
const headers = req.renderContext.originalHeaders || req.headers;
|
const headers = req.renderContext.originalHeaders || req.headers;
|
||||||
|
|
||||||
options.context.locale = getHeaderLocale(headers, user && user.locale);
|
options.context.locale = getHeaderLocale(headers, user && user.locale);
|
||||||
|
|
||||||
|
if (headers.apikey && (headers.apikey === getSetting('vulcan.apiKey'))) {
|
||||||
|
options.context.currentUser = { isAdmin: true, isApiUser: true };
|
||||||
|
}
|
||||||
// console.log('// apollo_server.js isSSR?', !!req.renderContext.originalHeaders ? 'yes' : 'no');
|
// console.log('// apollo_server.js isSSR?', !!req.renderContext.originalHeaders ? 'yes' : 'no');
|
||||||
// console.log('// apollo_server.js headers:');
|
// console.log('// apollo_server.js headers:');
|
||||||
// console.log(headers);
|
// console.log(headers);
|
||||||
|
|
|
@ -51,7 +51,7 @@ GraphQL @intl directive resolver
|
||||||
*/
|
*/
|
||||||
class IntlDirective extends SchemaDirectiveVisitor {
|
class IntlDirective extends SchemaDirectiveVisitor {
|
||||||
visitFieldDefinition(field, details) {
|
visitFieldDefinition(field, details) {
|
||||||
const {resolve = defaultFieldResolver, name} = field;
|
const { resolve = defaultFieldResolver, name } = field;
|
||||||
field.resolve = async function(...args) {
|
field.resolve = async function(...args) {
|
||||||
const [doc, graphQLArguments, context] = args;
|
const [doc, graphQLArguments, context] = args;
|
||||||
const fieldValue = await resolve.apply(this, args);
|
const fieldValue = await resolve.apply(this, args);
|
||||||
|
|
|
@ -1,25 +1,29 @@
|
||||||
/*
|
/*
|
||||||
|
|
||||||
Mutations have four steps:
|
Mutations have five steps:
|
||||||
|
|
||||||
1. Validation
|
1. Validation
|
||||||
|
|
||||||
If the mutation call is not trusted (i.e. it comes from a GraphQL mutation),
|
If the mutator call is not trusted (for example, it comes from a GraphQL mutation),
|
||||||
we'll run all validate steps:
|
we'll run all validate steps:
|
||||||
|
|
||||||
- Check that the current user has permission to insert/edit each field.
|
- Check that the current user has permission to insert/edit each field.
|
||||||
- Add userId to document (insert only).
|
- Add userId to document (insert only).
|
||||||
- Run validation callbacks.
|
- Run validation callbacks.
|
||||||
|
|
||||||
2. Sync Callbacks
|
2. Before Callbacks
|
||||||
|
|
||||||
The second step is to run the mutation argument through all the sync callbacks.
|
The second step is to run the mutation argument through all the [before] callbacks.
|
||||||
|
|
||||||
3. Operation
|
3. Operation
|
||||||
|
|
||||||
We then perform the insert/update/remove operation.
|
We then perform the insert/update/remove operation.
|
||||||
|
|
||||||
4. Async Callbacks
|
4. After Callbacks
|
||||||
|
|
||||||
|
We then run the mutation argument through all the [after] callbacks.
|
||||||
|
|
||||||
|
5. Async Callbacks
|
||||||
|
|
||||||
Finally, *after* the operation is performed, we execute any async callbacks.
|
Finally, *after* the operation is performed, we execute any async callbacks.
|
||||||
Being async, they won't hold up the mutation and slow down its response time
|
Being async, they won't hold up the mutation and slow down its response time
|
||||||
|
@ -39,49 +43,63 @@ import isEmpty from 'lodash/isEmpty';
|
||||||
|
|
||||||
registerSetting('database', 'mongo', 'Which database to use for your back-end');
|
registerSetting('database', 'mongo', 'Which database to use for your back-end');
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Create
|
||||||
|
|
||||||
|
*/
|
||||||
export const createMutator = async ({ collection, document, data, currentUser, validate, context }) => {
|
export const createMutator = async ({ collection, document, data, currentUser, validate, context }) => {
|
||||||
|
|
||||||
const { collectionName, typeName } = collection.options;
|
// OpenCRUD backwards compatibility: accept either data or document
|
||||||
|
|
||||||
debug('');
|
|
||||||
debugGroup(`--------------- start \x1b[36m${collectionName} Create Mutator\x1b[0m ---------------`);
|
|
||||||
debug(`validate: ${validate}`);
|
|
||||||
debug(document || data);
|
|
||||||
|
|
||||||
// we don't want to modify the original document
|
// we don't want to modify the original document
|
||||||
let newDocument = Object.assign({}, document || data);
|
document = data || document;
|
||||||
|
|
||||||
|
const { collectionName, typeName } = collection.options;
|
||||||
const schema = collection.simpleSchema()._schema;
|
const schema = collection.simpleSchema()._schema;
|
||||||
|
|
||||||
const callbackProperties = { currentUser, collection, context };
|
startDebugMutator(collectionName, 'Update', { validate, document });
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Properties
|
||||||
|
|
||||||
|
*/
|
||||||
|
const properties = { data, currentUser, collection, context, document: document };
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Validation
|
||||||
|
|
||||||
|
*/
|
||||||
if (validate) {
|
if (validate) {
|
||||||
|
|
||||||
let validationErrors = [];
|
let validationErrors = [];
|
||||||
|
validationErrors = validationErrors.concat(validateDocument(document, collection, context));
|
||||||
validationErrors = validationErrors.concat(validateDocument(newDocument, collection, context));
|
|
||||||
|
|
||||||
// run validation callbacks
|
// run validation callbacks
|
||||||
validationErrors = await runCallbacks({ name: `${typeName.toLowerCase()}.create.validate`, iterator: validationErrors, properties: { ...callbackProperties, document: newDocument } });
|
validationErrors = await runCallbacks({ name: `${typeName.toLowerCase()}.create.validate`, iterator: validationErrors, properties });
|
||||||
validationErrors = await runCallbacks({ name: '*.create.validate', iterator: validationErrors, properties: { ...callbackProperties, document: newDocument } });
|
validationErrors = await runCallbacks({ name: '*.create.validate', iterator: validationErrors, properties });
|
||||||
// OpenCRUD backwards compatibility
|
// OpenCRUD backwards compatibility
|
||||||
newDocument = await runCallbacks(`${collectionName.toLowerCase()}.new.validate`, newDocument, currentUser, validationErrors);
|
document = await runCallbacks(`${collectionName.toLowerCase()}.new.validate`, document, currentUser, validationErrors);
|
||||||
|
|
||||||
if (validationErrors.length) {
|
if (validationErrors.length) {
|
||||||
throwError({ id: 'app.validation_error', data: {break: true, errors: validationErrors}});
|
console.log(validationErrors); // eslint-disable-line no-console
|
||||||
|
throwError({ id: 'app.validation_error', data: { break: true, errors: validationErrors } });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if user is logged in, check if userId field is in the schema and add it to document if needed
|
/*
|
||||||
|
|
||||||
|
userId
|
||||||
|
|
||||||
|
If user is logged in, check if userId field is in the schema and add it to document if needed
|
||||||
|
|
||||||
|
*/
|
||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
const userIdInSchema = Object.keys(schema).find(key => key === 'userId');
|
const userIdInSchema = Object.keys(schema).find(key => key === 'userId');
|
||||||
if (!!userIdInSchema && !newDocument.userId) newDocument.userId = currentUser._id;
|
if (!!userIdInSchema && !document.userId) document.userId = currentUser._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
run onCreate step
|
onCreate
|
||||||
|
|
||||||
note: cannot use forEach with async/await.
|
note: cannot use forEach with async/await.
|
||||||
See https://stackoverflow.com/a/37576787/649299
|
See https://stackoverflow.com/a/37576787/649299
|
||||||
|
@ -89,19 +107,17 @@ export const createMutator = async ({ collection, document, data, currentUser, v
|
||||||
note: clone arguments in case callbacks modify them
|
note: clone arguments in case callbacks modify them
|
||||||
|
|
||||||
*/
|
*/
|
||||||
for(let fieldName of Object.keys(schema)) {
|
for (let fieldName of Object.keys(schema)) {
|
||||||
let autoValue;
|
let autoValue;
|
||||||
if (schema[fieldName].onCreate) {
|
if (schema[fieldName].onCreate) {
|
||||||
// OpenCRUD backwards compatibility: keep both newDocument and data for now, but phase our newDocument eventually
|
// OpenCRUD backwards compatibility: keep both newDocument and data for now, but phase out newDocument eventually
|
||||||
// eslint-disable-next-line no-await-in-loop
|
autoValue = await schema[fieldName].onCreate(properties); // eslint-disable-line no-await-in-loop
|
||||||
autoValue = await schema[fieldName].onCreate({ newDocument: clone(newDocument), data: clone(newDocument), currentUser, context });
|
|
||||||
} else if (schema[fieldName].onInsert) {
|
} else if (schema[fieldName].onInsert) {
|
||||||
// OpenCRUD backwards compatibility
|
// OpenCRUD backwards compatibility
|
||||||
// eslint-disable-next-line no-await-in-loop
|
autoValue = await schema[fieldName].onInsert(clone(document), currentUser); // eslint-disable-line no-await-in-loop
|
||||||
autoValue = await schema[fieldName].onInsert(clone(newDocument), currentUser);
|
|
||||||
}
|
}
|
||||||
if (typeof autoValue !== 'undefined') {
|
if (typeof autoValue !== 'undefined') {
|
||||||
newDocument[fieldName] = autoValue;
|
document[fieldName] = autoValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,137 +127,164 @@ export const createMutator = async ({ collection, document, data, currentUser, v
|
||||||
// post.userAgent = this.connection.httpHeaders['user-agent'];
|
// post.userAgent = this.connection.httpHeaders['user-agent'];
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// run sync callbacks
|
/*
|
||||||
newDocument = await runCallbacks({ name: `${typeName.toLowerCase()}.create.before`, iterator: newDocument, properties: callbackProperties });
|
|
||||||
newDocument = await runCallbacks({ name: '*.create.before', iterator: newDocument, properties: callbackProperties });
|
Before
|
||||||
|
|
||||||
|
*/
|
||||||
|
document = await runCallbacks({ name: `${typeName.toLowerCase()}.create.before`, iterator: document, properties });
|
||||||
|
document = await runCallbacks({ name: '*.create.before', iterator: document, properties });
|
||||||
// OpenCRUD backwards compatibility
|
// OpenCRUD backwards compatibility
|
||||||
newDocument = await runCallbacks(`${collectionName.toLowerCase()}.new.before`, newDocument, currentUser);
|
document = await runCallbacks(`${collectionName.toLowerCase()}.new.before`, document, currentUser);
|
||||||
newDocument = await runCallbacks(`${collectionName.toLowerCase()}.new.sync`, newDocument, currentUser);
|
document = await runCallbacks(`${collectionName.toLowerCase()}.new.sync`, document, currentUser);
|
||||||
|
|
||||||
// add _id to document
|
/*
|
||||||
newDocument._id = await Connectors.create(collection, newDocument);
|
|
||||||
|
|
||||||
|
DB Operation
|
||||||
|
|
||||||
|
*/
|
||||||
|
document._id = await Connectors.create(collection, document);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
After
|
||||||
|
|
||||||
|
*/
|
||||||
// run any post-operation sync callbacks
|
// run any post-operation sync callbacks
|
||||||
newDocument = await runCallbacks({ name: `${typeName.toLowerCase()}.create.after`, iterator: newDocument, properties: callbackProperties });
|
document = await runCallbacks({ name: `${typeName.toLowerCase()}.create.after`, iterator: document, properties });
|
||||||
newDocument = await runCallbacks({ name: '*.create.after', iterator: newDocument, properties: callbackProperties });
|
document = await runCallbacks({ name: '*.create.after', iterator: document, properties });
|
||||||
// OpenCRUD backwards compatibility
|
// OpenCRUD backwards compatibility
|
||||||
newDocument = await runCallbacks(`${collectionName.toLowerCase()}.new.after`, newDocument, currentUser);
|
document = await runCallbacks(`${collectionName.toLowerCase()}.new.after`, document, currentUser);
|
||||||
|
|
||||||
// get fresh copy of document from db
|
|
||||||
// TODO: not needed?
|
|
||||||
const insertedDocument = await Connectors.get(collection, newDocument._id);
|
|
||||||
|
|
||||||
// run async callbacks
|
|
||||||
// note: query for document to get fresh document with collection-hooks effects applied
|
// note: query for document to get fresh document with collection-hooks effects applied
|
||||||
await runCallbacksAsync({ name: `${typeName.toLowerCase()}.create.async`, properties: { insertedDocument, document: insertedDocument, ...callbackProperties }});
|
document = await Connectors.get(collection, document._id);
|
||||||
await runCallbacksAsync({ name: '*.create.async', properties: { insertedDocument, document: insertedDocument, ...callbackProperties }});
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Async
|
||||||
|
|
||||||
|
*/
|
||||||
|
// note: make sure properties.document is up to date
|
||||||
|
await runCallbacksAsync({ name: `${typeName.toLowerCase()}.create.async`, properties: { ...properties, document: document } });
|
||||||
|
await runCallbacksAsync({ name: '*.create.async', properties });
|
||||||
// OpenCRUD backwards compatibility
|
// OpenCRUD backwards compatibility
|
||||||
await runCallbacksAsync(`${collectionName.toLowerCase()}.new.async`, insertedDocument, currentUser, collection);
|
await runCallbacksAsync(`${collectionName.toLowerCase()}.new.async`, document, currentUser, collection);
|
||||||
|
|
||||||
debug('\x1b[33m=> created new document: \x1b[0m');
|
endDebugMutator(collectionName, 'Create', { document });
|
||||||
debug(newDocument);
|
|
||||||
debugGroupEnd();
|
|
||||||
debug(`--------------- end \x1b[36m${collectionName} Create Mutator\x1b[0m ---------------`);
|
|
||||||
debug('');
|
|
||||||
|
|
||||||
return { data: newDocument };
|
return { data: document };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
export const updateMutator = async ({ collection, documentId, selector, data, set = {}, unset = {}, currentUser, validate, context, document }) => {
|
Update
|
||||||
|
|
||||||
|
*/
|
||||||
|
export const updateMutator = async ({ collection, documentId, selector, data, set = {}, unset = {}, currentUser, validate, context, document: oldDocument }) => {
|
||||||
|
|
||||||
const { collectionName, typeName } = collection.options;
|
const { collectionName, typeName } = collection.options;
|
||||||
|
|
||||||
const schema = collection.simpleSchema()._schema;
|
const schema = collection.simpleSchema()._schema;
|
||||||
|
|
||||||
// OpenCRUD backwards compatibility
|
// OpenCRUD backwards compatibility
|
||||||
selector = selector || { _id: documentId };
|
selector = selector || { _id: documentId };
|
||||||
data = data || modifierToData({ $set: set, $unset: unset });
|
data = data || modifierToData({ $set: set, $unset: unset });
|
||||||
|
|
||||||
|
startDebugMutator(collectionName, 'Update', { selector, data });
|
||||||
|
|
||||||
if (isEmpty(selector)) {
|
if (isEmpty(selector)) {
|
||||||
throw new Error('Selector cannot be empty');
|
throw new Error('Selector cannot be empty');
|
||||||
}
|
}
|
||||||
|
|
||||||
// get original document from database or arguments
|
// get original document from database or arguments
|
||||||
document = document || await Connectors.get(collection, selector);
|
oldDocument = oldDocument || (await Connectors.get(collection, selector));
|
||||||
|
|
||||||
if (!document) {
|
if (!oldDocument) {
|
||||||
throw new Error(`Could not find document to update for selector: ${JSON.stringify(selector)}`);
|
throw new Error(`Could not find document to update for selector: ${JSON.stringify(selector)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const callbackProperties = { data, document, currentUser, collection, context };
|
|
||||||
|
|
||||||
debug('');
|
// get a "preview" of the new document
|
||||||
debugGroup(`--------------- start \x1b[36m${collectionName} Update Mutator\x1b[0m ---------------`);
|
let document = { ...oldDocument, ...data };
|
||||||
debug('// collectionName: ', collectionName);
|
document = pickBy(document, f => f !== null);
|
||||||
debug('// selector: ', selector);
|
|
||||||
debug('// data: ', data);
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Properties
|
||||||
|
|
||||||
|
*/
|
||||||
|
const properties = { data, oldDocument, document, currentUser, collection, context };
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Validation
|
||||||
|
|
||||||
|
*/
|
||||||
if (validate) {
|
if (validate) {
|
||||||
|
|
||||||
let validationErrors = [];
|
let validationErrors = [];
|
||||||
|
|
||||||
validationErrors = validationErrors.concat(validateData(data, document, collection, context));
|
validationErrors = validationErrors.concat(validateData(data, document, collection, context));
|
||||||
|
|
||||||
validationErrors = await runCallbacks({ name: `${typeName.toLowerCase()}.update.validate`, iterator: validationErrors, properties: callbackProperties });
|
validationErrors = await runCallbacks({ name: `${typeName.toLowerCase()}.update.validate`, iterator: validationErrors, properties });
|
||||||
validationErrors = await runCallbacks({ name: '*.update.validate', iterator: validationErrors, properties: callbackProperties });
|
validationErrors = await runCallbacks({ name: '*.update.validate', iterator: validationErrors, properties });
|
||||||
// OpenCRUD backwards compatibility
|
// OpenCRUD backwards compatibility
|
||||||
data = modifierToData(await runCallbacks(`${collectionName.toLowerCase()}.edit.validate`, dataToModifier(data), document, currentUser, validationErrors));
|
data = modifierToData(await runCallbacks(`${collectionName.toLowerCase()}.edit.validate`, dataToModifier(data), document, currentUser, validationErrors));
|
||||||
|
|
||||||
if (validationErrors.length) {
|
if (validationErrors.length) {
|
||||||
// eslint-disable-next-line no-console
|
console.log(validationErrors); // eslint-disable-line no-console
|
||||||
console.log('// validationErrors');
|
throwError({ id: 'app.validation_error', data: { break: true, errors: validationErrors } });
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(validationErrors);
|
|
||||||
throwError({ id: 'app.validation_error', data: {break: true, errors: validationErrors}});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get a "preview" of the new document
|
/*
|
||||||
let newDocument = { ...document, ...data};
|
|
||||||
newDocument = pickBy(newDocument, f => f !== null);
|
|
||||||
|
|
||||||
// run onUpdate step
|
onUpdate
|
||||||
for(let fieldName of Object.keys(schema)) {
|
|
||||||
|
*/
|
||||||
|
for (let fieldName of Object.keys(schema)) {
|
||||||
let autoValue;
|
let autoValue;
|
||||||
if (schema[fieldName].onUpdate) {
|
if (schema[fieldName].onUpdate) {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
autoValue = await schema[fieldName].onUpdate(properties); // eslint-disable-line no-await-in-loop
|
||||||
autoValue = await schema[fieldName].onUpdate({ data: clone(data), document, currentUser, newDocument, context });
|
|
||||||
} else if (schema[fieldName].onEdit) {
|
} else if (schema[fieldName].onEdit) {
|
||||||
// OpenCRUD backwards compatibility
|
// OpenCRUD backwards compatibility
|
||||||
// eslint-disable-next-line no-await-in-loop
|
autoValue = await schema[fieldName].onEdit(dataToModifier(clone(data)), document, currentUser, document); // eslint-disable-line no-await-in-loop
|
||||||
autoValue = await schema[fieldName].onEdit(dataToModifier(clone(data)), document, currentUser, newDocument);
|
|
||||||
}
|
}
|
||||||
if (typeof autoValue !== 'undefined') {
|
if (typeof autoValue !== 'undefined') {
|
||||||
data[fieldName] = autoValue;
|
data[fieldName] = autoValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// run sync callbacks
|
/*
|
||||||
data = await runCallbacks({ name: `${typeName.toLowerCase()}.update.before`, iterator: data, properties: { newDocument, ...callbackProperties }});
|
|
||||||
data = await runCallbacks({ name: '*.update.before', iterator: data, properties: { newDocument, ...callbackProperties }});
|
Before
|
||||||
|
|
||||||
|
*/
|
||||||
|
data = await runCallbacks({ name: `${typeName.toLowerCase()}.update.before`, iterator: data, properties });
|
||||||
|
data = await runCallbacks({ name: '*.update.before', iterator: data, properties });
|
||||||
// OpenCRUD backwards compatibility
|
// OpenCRUD backwards compatibility
|
||||||
data = modifierToData(await runCallbacks(`${collectionName.toLowerCase()}.edit.before`, dataToModifier(data), document, currentUser, newDocument));
|
data = modifierToData(await runCallbacks(`${collectionName.toLowerCase()}.edit.before`, dataToModifier(data), document, currentUser, document));
|
||||||
data = modifierToData(await runCallbacks(`${collectionName.toLowerCase()}.edit.sync`, dataToModifier(data), document, currentUser, newDocument));
|
data = modifierToData(await runCallbacks(`${collectionName.toLowerCase()}.edit.sync`, dataToModifier(data), document, currentUser, document));
|
||||||
|
|
||||||
// update connector requires a modifier, so get it from data
|
// update connector requires a modifier, so get it from data
|
||||||
const modifier = dataToModifier(data);
|
const modifier = dataToModifier(data);
|
||||||
|
|
||||||
// remove empty modifiers
|
// remove empty modifiers
|
||||||
if (_.isEmpty(modifier.$set)) {
|
if (isEmpty(modifier.$set)) {
|
||||||
delete modifier.$set;
|
delete modifier.$set;
|
||||||
}
|
}
|
||||||
if (_.isEmpty(modifier.$unset)) {
|
if (isEmpty(modifier.$unset)) {
|
||||||
delete modifier.$unset;
|
delete modifier.$unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_.isEmpty(modifier)) {
|
/*
|
||||||
|
|
||||||
|
DB Operation
|
||||||
|
|
||||||
|
*/
|
||||||
|
if (!isEmpty(modifier)) {
|
||||||
// update document
|
// update document
|
||||||
await Connectors.update(collection, selector, modifier, { removeEmptyStrings: false });
|
await Connectors.update(collection, selector, modifier, { removeEmptyStrings: false });
|
||||||
|
|
||||||
// get fresh copy of document from db
|
// get fresh copy of document from db
|
||||||
newDocument = await Connectors.get(collection, selector);
|
document = await Connectors.get(collection, selector);
|
||||||
|
|
||||||
// TODO: add support for caching by other indexes to Dataloader
|
// TODO: add support for caching by other indexes to Dataloader
|
||||||
// https://github.com/VulcanJS/Vulcan/issues/2000
|
// https://github.com/VulcanJS/Vulcan/issues/2000
|
||||||
|
@ -251,38 +294,40 @@ export const updateMutator = async ({ collection, documentId, selector, data, se
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// run any post-operation sync callbacks
|
/*
|
||||||
newDocument = await runCallbacks({ name: `${typeName.toLowerCase()}.update.after`, iterator: newDocument, properties: callbackProperties });
|
|
||||||
newDocument = await runCallbacks({ name: '*.update.after', iterator: newDocument, properties: callbackProperties });
|
|
||||||
// OpenCRUD backwards compatibility
|
|
||||||
newDocument = await runCallbacks(`${collectionName.toLowerCase()}.edit.after`, newDocument, document, currentUser);
|
|
||||||
|
|
||||||
|
After
|
||||||
|
|
||||||
|
*/
|
||||||
|
document = await runCallbacks({ name: `${typeName.toLowerCase()}.update.after`, iterator: document, properties });
|
||||||
|
document = await runCallbacks({ name: '*.update.after', iterator: document, properties });
|
||||||
|
// OpenCRUD backwards compatibility
|
||||||
|
document = await runCallbacks(`${collectionName.toLowerCase()}.edit.after`, document, oldDocument, currentUser);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Async
|
||||||
|
|
||||||
|
*/
|
||||||
// run async callbacks
|
// run async callbacks
|
||||||
await runCallbacksAsync({ name: `${typeName.toLowerCase()}.update.async`, properties: { ...callbackProperties,newDocument, document: newDocument, oldDocument: document }});
|
await runCallbacksAsync({ name: `${typeName.toLowerCase()}.update.async`, properties });
|
||||||
await runCallbacksAsync({ name: '*.update.async', properties: { ...callbackProperties, newDocument, document: newDocument, oldDocument: document }});
|
await runCallbacksAsync({ name: '*.update.async', properties });
|
||||||
// OpenCRUD backwards compatibility
|
// OpenCRUD backwards compatibility
|
||||||
await runCallbacksAsync(`${collectionName.toLowerCase()}.edit.async`, newDocument, document, currentUser, collection);
|
await runCallbacksAsync(`${collectionName.toLowerCase()}.edit.async`, document, oldDocument, currentUser, collection);
|
||||||
|
|
||||||
debug('\x1b[33m=> updated document with modifier: \x1b[0m');
|
endDebugMutator(collectionName, 'Update', { modifier });
|
||||||
debug('// modifier: ', modifier);
|
|
||||||
debugGroupEnd();
|
|
||||||
debug(`--------------- end \x1b[36m${collectionName} Update Mutator\x1b[0m ---------------`);
|
|
||||||
debug('');
|
|
||||||
|
|
||||||
return { data: newDocument };
|
return { data: document };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Delete
|
||||||
|
|
||||||
|
*/
|
||||||
export const deleteMutator = async ({ collection, documentId, selector, currentUser, validate, context, document }) => {
|
export const deleteMutator = async ({ collection, documentId, selector, currentUser, validate, context, document }) => {
|
||||||
|
|
||||||
const { collectionName, typeName } = collection.options;
|
const { collectionName, typeName } = collection.options;
|
||||||
|
|
||||||
debug('');
|
|
||||||
debugGroup(`--------------- start \x1b[36m${collectionName} Delete Mutator\x1b[0m ---------------`);
|
|
||||||
debug('// collectionName: ', collectionName);
|
|
||||||
debug('// selector: ', selector);
|
|
||||||
|
|
||||||
const schema = collection.simpleSchema()._schema;
|
const schema = collection.simpleSchema()._schema;
|
||||||
|
|
||||||
// OpenCRUD backwards compatibility
|
// OpenCRUD backwards compatibility
|
||||||
selector = selector || { _id: documentId };
|
selector = selector || { _id: documentId };
|
||||||
|
|
||||||
|
@ -290,51 +335,68 @@ export const deleteMutator = async ({ collection, documentId, selector, currentU
|
||||||
throw new Error('Selector cannot be empty');
|
throw new Error('Selector cannot be empty');
|
||||||
}
|
}
|
||||||
|
|
||||||
document = document || await Connectors.get(collection, selector);
|
document = document || (await Connectors.get(collection, selector));
|
||||||
|
|
||||||
if (!document) {
|
if (!document) {
|
||||||
throw new Error(`Could not find document to delete for selector: ${JSON.stringify(selector)}`);
|
throw new Error(`Could not find document to delete for selector: ${JSON.stringify(selector)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const callbackProperties = { document, currentUser, collection, context };
|
/*
|
||||||
|
|
||||||
|
Properties
|
||||||
|
|
||||||
|
*/
|
||||||
|
const properties = { document, currentUser, collection, context };
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Validation
|
||||||
|
|
||||||
|
*/
|
||||||
if (validate) {
|
if (validate) {
|
||||||
|
|
||||||
let validationErrors = [];
|
let validationErrors = [];
|
||||||
|
|
||||||
validationErrors = await runCallbacks({ name: `${typeName.toLowerCase()}.delete.validate`, iterator: validationErrors, properties: callbackProperties });
|
validationErrors = await runCallbacks({ name: `${typeName.toLowerCase()}.delete.validate`, iterator: validationErrors, properties });
|
||||||
validationErrors = await runCallbacks({ name: '*.delete.validate', iterator: validationErrors, properties: callbackProperties });
|
validationErrors = await runCallbacks({ name: '*.delete.validate', iterator: validationErrors, properties });
|
||||||
// OpenCRUD backwards compatibility
|
// OpenCRUD backwards compatibility
|
||||||
document = await runCallbacks(`${collectionName.toLowerCase()}.remove.validate`, document, currentUser);
|
document = await runCallbacks(`${collectionName.toLowerCase()}.remove.validate`, document, currentUser);
|
||||||
|
|
||||||
if (validationErrors.length) {
|
if (validationErrors.length) {
|
||||||
// eslint-disable-next-line no-console
|
console.log(validationErrors); // eslint-disable-line no-console
|
||||||
console.log('// validationErrors');
|
throwError({ id: 'app.validation_error', data: { break: true, errors: validationErrors } });
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(validationErrors);
|
|
||||||
throwError({id: 'app.validation_error', data: {break: true, errors: validationErrors}});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// run onRemove step
|
/*
|
||||||
for(let fieldName of Object.keys(schema)) {
|
|
||||||
|
onDelete
|
||||||
|
|
||||||
|
*/
|
||||||
|
for (let fieldName of Object.keys(schema)) {
|
||||||
if (schema[fieldName].onDelete) {
|
if (schema[fieldName].onDelete) {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
await schema[fieldName].onDelete(properties); // eslint-disable-line no-await-in-loop
|
||||||
await schema[fieldName].onDelete({ document, currentUser, context });
|
|
||||||
} else if (schema[fieldName].onRemove) {
|
} else if (schema[fieldName].onRemove) {
|
||||||
// OpenCRUD backwards compatibility
|
// OpenCRUD backwards compatibility
|
||||||
// eslint-disable-next-line no-await-in-loop
|
await schema[fieldName].onRemove(document, currentUser); // eslint-disable-line no-await-in-loop
|
||||||
await schema[fieldName].onRemove(document, currentUser);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await runCallbacks({ name: `${typeName.toLowerCase()}.delete.before`, iterator: document, properties: callbackProperties });
|
/*
|
||||||
await runCallbacks({ name: '*.delete.before', iterator: document, properties: callbackProperties });
|
|
||||||
|
Before
|
||||||
|
|
||||||
|
*/
|
||||||
|
await runCallbacks({ name: `${typeName.toLowerCase()}.delete.before`, iterator: document, properties });
|
||||||
|
await runCallbacks({ name: '*.delete.before', iterator: document, properties });
|
||||||
// OpenCRUD backwards compatibility
|
// OpenCRUD backwards compatibility
|
||||||
await runCallbacks(`${collectionName.toLowerCase()}.remove.before`, document, currentUser);
|
await runCallbacks(`${collectionName.toLowerCase()}.remove.before`, document, currentUser);
|
||||||
await runCallbacks(`${collectionName.toLowerCase()}.remove.sync`, document, currentUser);
|
await runCallbacks(`${collectionName.toLowerCase()}.remove.sync`, document, currentUser);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
DB Operation
|
||||||
|
|
||||||
|
*/
|
||||||
await Connectors.delete(collection, selector);
|
await Connectors.delete(collection, selector);
|
||||||
|
|
||||||
// TODO: add support for caching by other indexes to Dataloader
|
// TODO: add support for caching by other indexes to Dataloader
|
||||||
|
@ -343,14 +405,17 @@ export const deleteMutator = async ({ collection, documentId, selector, currentU
|
||||||
collection.loader.clear(selector.documentId);
|
collection.loader.clear(selector.documentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await runCallbacksAsync({ name: `${typeName.toLowerCase()}.delete.async`, properties: callbackProperties });
|
/*
|
||||||
await runCallbacksAsync({ name: '*.delete.async', properties: callbackProperties });
|
|
||||||
|
Async
|
||||||
|
|
||||||
|
*/
|
||||||
|
await runCallbacksAsync({ name: `${typeName.toLowerCase()}.delete.async`, properties });
|
||||||
|
await runCallbacksAsync({ name: '*.delete.async', properties });
|
||||||
// OpenCRUD backwards compatibility
|
// OpenCRUD backwards compatibility
|
||||||
await runCallbacksAsync(`${collectionName.toLowerCase()}.remove.async`, document, currentUser, collection);
|
await runCallbacksAsync(`${collectionName.toLowerCase()}.remove.async`, document, currentUser, collection);
|
||||||
|
|
||||||
debugGroupEnd();
|
endDebugMutator(collectionName, 'Delete');
|
||||||
debug(`--------------- end \x1b[36m${collectionName} Delete Mutator\x1b[0m ---------------`);
|
|
||||||
debug('');
|
|
||||||
|
|
||||||
return { data: document };
|
return { data: document };
|
||||||
};
|
};
|
||||||
|
@ -362,3 +427,20 @@ export const removeMutation = deleteMutator;
|
||||||
export const newMutator = createMutator;
|
export const newMutator = createMutator;
|
||||||
export const editMutator = updateMutator;
|
export const editMutator = updateMutator;
|
||||||
export const removeMutator = deleteMutator;
|
export const removeMutator = deleteMutator;
|
||||||
|
|
||||||
|
const startDebugMutator = (name, action, properties) => {
|
||||||
|
debug('');
|
||||||
|
debugGroup(`--------------- start \x1b[36m${name} ${action} Mutator\x1b[0m ---------------`);
|
||||||
|
Object.keys(properties).forEach(p => {
|
||||||
|
debug(`// ${p}: `, properties[p]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const endDebugMutator = (name, action, properties) => {
|
||||||
|
Object.keys(properties).forEach(p => {
|
||||||
|
debug(`// ${p}: `, properties[p]);
|
||||||
|
});
|
||||||
|
debugGroupEnd();
|
||||||
|
debug(`--------------- end \x1b[36m${name} ${action} Mutator\x1b[0m ---------------`);
|
||||||
|
debug('');
|
||||||
|
};
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Checkbox } from 'formsy-react-components';
|
import Form from 'react-bootstrap/lib/Form';
|
||||||
import { registerComponent } from 'meteor/vulcan:core';
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
const CheckboxComponent = ({refFunction, inputProperties}) =>
|
const CheckboxComponent = ({ refFunction, path, inputProperties, itemProperties }) => (
|
||||||
<Checkbox {...inputProperties} ref={refFunction} />;
|
<Components.FormItem {...inputProperties} {...itemProperties}>
|
||||||
|
<Form.Check {...inputProperties} id={path} ref={refFunction} checked={!!inputProperties.value}/>
|
||||||
|
</Components.FormItem>
|
||||||
|
);
|
||||||
|
|
||||||
registerComponent('FormComponentCheckbox', CheckboxComponent);
|
registerComponent('FormComponentCheckbox', CheckboxComponent);
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Checkbox } from 'formsy-react-components';
|
import Form from 'react-bootstrap/lib/Form';
|
||||||
import { registerComponent } from 'meteor/vulcan:core';
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
import without from 'lodash/without';
|
import without from 'lodash/without';
|
||||||
import uniq from 'lodash/uniq';
|
import uniq from 'lodash/uniq';
|
||||||
import intersection from 'lodash/intersection';
|
import intersection from 'lodash/intersection';
|
||||||
|
|
||||||
// note: treat checkbox group the same as a nested component, using `path`
|
// note: treat checkbox group the same as a nested component, using `path`
|
||||||
const CheckboxGroupComponent = ({ refFunction, label, path, value, formType, updateCurrentValues, inputProperties }) => {
|
const CheckboxGroupComponent = ({ refFunction, label, path, value, formType, updateCurrentValues, inputProperties, itemProperties }) => {
|
||||||
|
|
||||||
const { options } = inputProperties;
|
const { options } = inputProperties;
|
||||||
|
|
||||||
|
@ -22,25 +22,28 @@ const CheckboxGroupComponent = ({ refFunction, label, path, value, formType, upd
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="form-group row">
|
<Components.FormItem {...inputProperties} {...itemProperties}>
|
||||||
<label className="control-label col-sm-3">{label}</label>
|
<div>
|
||||||
<div className="col-sm-9">
|
|
||||||
{options.map((option, i) => (
|
{options.map((option, i) => (
|
||||||
<Checkbox
|
<Form.Check
|
||||||
layout="elementOnly"
|
layout="elementOnly"
|
||||||
key={i}
|
key={i}
|
||||||
{...inputProperties}
|
{...inputProperties}
|
||||||
label={option.label}
|
label={option.label}
|
||||||
value={value.includes(option.value)}
|
value={value.includes(option.value)}
|
||||||
|
checked={!!value.includes(option.value)}
|
||||||
|
id={`${path}.${i}`}
|
||||||
|
path={`${path}.${i}`}
|
||||||
ref={refFunction}
|
ref={refFunction}
|
||||||
onChange={(name, isChecked) => {
|
onChange={event => {
|
||||||
|
const isChecked = event.target.checked;
|
||||||
const newValue = isChecked ? [...value, option.value] : without(value, option.value);
|
const newValue = isChecked ? [...value, option.value] : without(value, option.value);
|
||||||
updateCurrentValues({ [path]: newValue });
|
updateCurrentValues({ [path]: newValue });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Components.FormItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,43 +1,34 @@
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import DateTimePicker from 'react-datetime';
|
import DateTimePicker from 'react-datetime';
|
||||||
import { registerComponent } from 'meteor/vulcan:core';
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
class DateComponent extends PureComponent {
|
class DateComponent extends PureComponent {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.updateDate = this.updateDate.bind(this);
|
this.updateDate = this.updateDate.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// when the datetime picker has mounted, SmartForm will catch the date value (no formsy mixin in this component)
|
|
||||||
// componentDidMount() {
|
|
||||||
// if (this.props.value) {
|
|
||||||
// this.updateDate(this.props.value);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
updateDate(date) {
|
updateDate(date) {
|
||||||
this.context.updateCurrentValues({[this.props.path]: date});
|
this.context.updateCurrentValues({ [this.props.path]: date });
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const date = this.props.value
|
||||||
const date = this.props.value ? (typeof this.props.value === 'string' ? new Date(this.props.value) : this.props.value) : null;
|
? typeof this.props.value === 'string'
|
||||||
|
? new Date(this.props.value)
|
||||||
|
: this.props.value
|
||||||
|
: null;
|
||||||
return (
|
return (
|
||||||
<div className="form-group row">
|
<Components.FormItem {...this.props.inputProperties} {...this.props.itemProperties}>
|
||||||
<label className="control-label col-sm-3">{this.props.label}</label>
|
<DateTimePicker
|
||||||
<div className="col-sm-9">
|
value={date}
|
||||||
<DateTimePicker
|
timeFormat={false}
|
||||||
value={date}
|
// newDate argument is a Moment object given by react-datetime
|
||||||
timeFormat={false}
|
onChange={newDate => this.updateDate(newDate)}
|
||||||
// newDate argument is a Moment object given by react-datetime
|
inputProps={{ name: this.props.name }}
|
||||||
onChange={newDate => this.updateDate(newDate)}
|
/>
|
||||||
inputProps={{name: this.props.name}}
|
</Components.FormItem>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,4 +48,4 @@ DateComponent.contextTypes = {
|
||||||
|
|
||||||
export default DateComponent;
|
export default DateComponent;
|
||||||
|
|
||||||
registerComponent('FormComponentDate', DateComponent);
|
registerComponent('FormComponentDate', DateComponent);
|
||||||
|
|
|
@ -2,17 +2,20 @@ import React, { PureComponent } from 'react';
|
||||||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
const isEmptyValue = value => (typeof value === 'undefined' || value === null || value === '' || Array.isArray(value) && value.length === 0);
|
const isEmptyValue = value =>
|
||||||
|
typeof value === 'undefined' ||
|
||||||
|
value === null ||
|
||||||
|
value === '' ||
|
||||||
|
(Array.isArray(value) && value.length === 0);
|
||||||
|
|
||||||
class DateComponent2 extends PureComponent {
|
class DateComponent2 extends PureComponent {
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
year: null,
|
year: null,
|
||||||
month: null,
|
month: null,
|
||||||
day: null,
|
day: null,
|
||||||
}
|
};
|
||||||
|
|
||||||
updateDate = (date) => {
|
updateDate = date => {
|
||||||
const { value, path } = this.props;
|
const { value, path } = this.props;
|
||||||
let newDate;
|
let newDate;
|
||||||
this.setState(date, () => {
|
this.setState(date, () => {
|
||||||
|
@ -20,7 +23,10 @@ class DateComponent2 extends PureComponent {
|
||||||
if (isEmptyValue(value)) {
|
if (isEmptyValue(value)) {
|
||||||
if (year && month && day) {
|
if (year && month && day) {
|
||||||
// wait until we have all three values to update the date
|
// wait until we have all three values to update the date
|
||||||
newDate = moment().year(year).month(month).date(day);
|
newDate = moment()
|
||||||
|
.year(year)
|
||||||
|
.month(month)
|
||||||
|
.date(day);
|
||||||
this.props.updateCurrentValues({ [path]: newDate.toDate() });
|
this.props.updateCurrentValues({ [path]: newDate.toDate() });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -32,10 +38,9 @@ class DateComponent2 extends PureComponent {
|
||||||
this.props.updateCurrentValues({ [path]: newDate.toDate() });
|
this.props.updateCurrentValues({ [path]: newDate.toDate() });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
const { path, value } = this.props;
|
const { path, value } = this.props;
|
||||||
const months = moment.months();
|
const months = moment.months();
|
||||||
const mDate = !isEmptyValue(value) && moment(value);
|
const mDate = !isEmptyValue(value) && moment(value);
|
||||||
|
@ -45,10 +50,10 @@ class DateComponent2 extends PureComponent {
|
||||||
name: `${path}.month`,
|
name: `${path}.month`,
|
||||||
layout: 'vertical',
|
layout: 'vertical',
|
||||||
options: months.map((m, i) => ({ label: m, value: m })),
|
options: months.map((m, i) => ({ label: m, value: m })),
|
||||||
value: mDate && mDate.format('MMMM') || '',
|
value: (mDate && mDate.format('MMMM')) || '',
|
||||||
onChange: (name, value) => {
|
onChange: (name, value) => {
|
||||||
this.updateDate({ month: value });
|
this.updateDate({ month: value });
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const dayProperties = {
|
const dayProperties = {
|
||||||
|
@ -56,10 +61,10 @@ class DateComponent2 extends PureComponent {
|
||||||
name: `${path}.day`,
|
name: `${path}.day`,
|
||||||
layout: 'vertical',
|
layout: 'vertical',
|
||||||
maxLength: 2,
|
maxLength: 2,
|
||||||
value: mDate && mDate.format('DD') || '',
|
value: (mDate && mDate.format('DD')) || '',
|
||||||
onBlur: (e) => {
|
onBlur: e => {
|
||||||
this.updateDate({ day: e.target.value });
|
this.updateDate({ day: e.target.value });
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const yearProperties = {
|
const yearProperties = {
|
||||||
|
@ -67,25 +72,33 @@ class DateComponent2 extends PureComponent {
|
||||||
name: `${path}.year`,
|
name: `${path}.year`,
|
||||||
layout: 'vertical',
|
layout: 'vertical',
|
||||||
maxLength: 4,
|
maxLength: 4,
|
||||||
value: mDate && mDate.format('YYYY') || '',
|
value: (mDate && mDate.format('YYYY')) || '',
|
||||||
onBlur: (e) => {
|
onBlur: e => {
|
||||||
this.updateDate({ year: e.target.value });
|
this.updateDate({ year: e.target.value });
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="form-group row">
|
<Components.FormItem {...this.props.inputProperties} {...this.props.itemProperties}>
|
||||||
<label className="control-label col-sm-3">{this.props.label}</label>
|
<div>
|
||||||
<div className="col-sm-9" style={{ display: 'flex', alignItems: 'center' }}>
|
<div>
|
||||||
<div><Components.FormComponentSelect inputProperties={monthProperties} datatype={[{ type: String }]} /></div>
|
<Components.FormComponentSelect
|
||||||
<div style={{ marginLeft: 10, width: 60 }}><Components.FormComponentText inputProperties={dayProperties} /></div>
|
inputProperties={monthProperties}
|
||||||
<div style={{ marginLeft: 10, width: 80 }}><Components.FormComponentText inputProperties={yearProperties} /></div>
|
datatype={[{ type: String }]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginLeft: 10, width: 60 }}>
|
||||||
|
<Components.FormComponentText inputProperties={dayProperties} />
|
||||||
|
</div>
|
||||||
|
<div style={{ marginLeft: 10, width: 80 }}>
|
||||||
|
<Components.FormComponentText inputProperties={yearProperties} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Components.FormItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DateComponent2;
|
export default DateComponent2;
|
||||||
|
|
||||||
registerComponent('FormComponentDate2', DateComponent2);
|
registerComponent('FormComponentDate2', DateComponent2);
|
||||||
|
|
|
@ -1,43 +1,35 @@
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import DateTimePicker from 'react-datetime';
|
import DateTimePicker from 'react-datetime';
|
||||||
import { registerComponent } from 'meteor/vulcan:core';
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
class DateTime extends PureComponent {
|
class DateTime extends PureComponent {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.updateDate = this.updateDate.bind(this);
|
this.updateDate = this.updateDate.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// when the datetime picker has mounted, SmartForm will catch the date value (no formsy mixin in this component)
|
|
||||||
// componentDidMount() {
|
|
||||||
// if (this.props.value) {
|
|
||||||
// this.updateDate(this.props.value);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
updateDate(date) {
|
updateDate(date) {
|
||||||
this.context.updateCurrentValues({[this.props.path]: date});
|
this.context.updateCurrentValues({ [this.props.path]: date });
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const date = this.props.value
|
||||||
|
? typeof this.props.value === 'string'
|
||||||
|
? new Date(this.props.value)
|
||||||
|
: this.props.value
|
||||||
|
: null;
|
||||||
|
|
||||||
const date = this.props.value ? (typeof this.props.value === 'string' ? new Date(this.props.value) : this.props.value) : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="form-group row">
|
<Components.FormItem {...this.props.inputProperties} {...this.props.itemProperties}>
|
||||||
<label className="control-label col-sm-3">{this.props.label}</label>
|
<DateTimePicker
|
||||||
<div className="col-sm-9">
|
value={date}
|
||||||
<DateTimePicker
|
// newDate argument is a Moment object given by react-datetime
|
||||||
value={date}
|
onChange={newDate => this.updateDate(newDate._d)}
|
||||||
// newDate argument is a Moment object given by react-datetime
|
format={'x'}
|
||||||
onChange={newDate => this.updateDate(newDate._d)}
|
inputProps={{ name: this.props.name }}
|
||||||
format={'x'}
|
/>
|
||||||
inputProps={{name: this.props.name}}
|
</Components.FormItem>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,4 +49,4 @@ DateTime.contextTypes = {
|
||||||
|
|
||||||
export default DateTime;
|
export default DateTime;
|
||||||
|
|
||||||
registerComponent('FormComponentDateTime', DateTime);
|
registerComponent('FormComponentDateTime', DateTime);
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Input } from 'formsy-react-components';
|
import Form from 'react-bootstrap/lib/Form';
|
||||||
import { registerComponent } from 'meteor/vulcan:core';
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
const Default = ({refFunction, inputProperties}) =>
|
const Default = ({ refFunction, inputProperties, itemProperties }) => (
|
||||||
<Input {...inputProperties} ref={refFunction} type="text" />;
|
<Components.FormItem {...inputProperties} {...itemProperties}>
|
||||||
|
<Form.Control {...inputProperties} ref={refFunction} type="text" />
|
||||||
|
</Components.FormItem>
|
||||||
|
);
|
||||||
|
|
||||||
registerComponent('FormComponentDefault', Default);
|
registerComponent('FormComponentDefault', Default);
|
||||||
registerComponent('FormComponentText', Default);
|
registerComponent('FormComponentText', Default);
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Input } from 'formsy-react-components';
|
import Form from 'react-bootstrap/lib/Form';
|
||||||
import { registerComponent } from 'meteor/vulcan:core';
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
const EmailComponent = ({refFunction, inputProperties}) =>
|
const EmailComponent = ({ refFunction, inputProperties, itemProperties }) => (
|
||||||
<Input {...inputProperties} ref={refFunction} type="email" />;
|
<Components.FormItem {...inputProperties} {...itemProperties}>
|
||||||
|
<Form.Control {...inputProperties} ref={refFunction} type="email" />
|
||||||
|
</Components.FormItem>
|
||||||
|
);
|
||||||
|
|
||||||
registerComponent('FormComponentEmail', EmailComponent);
|
registerComponent('FormComponentEmail', EmailComponent);
|
||||||
|
|
|
@ -21,15 +21,20 @@ class FormComponentInner extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
getProperties = () => {
|
getProperties = () => {
|
||||||
const { name, options, label, onChange, value, disabled } = this.props;
|
const { name, path, options, label, onChange, value, disabled, inputType } = this.props;
|
||||||
|
|
||||||
// these properties are whitelisted so that they can be safely passed to the actual form input
|
// these properties are whitelisted so that they can be safely passed to the actual form input
|
||||||
// and avoid https://facebook.github.io/react/warnings/unknown-prop.html warnings
|
// and avoid https://facebook.github.io/react/warnings/unknown-prop.html warnings
|
||||||
const inputProperties = {
|
const inputProperties = {
|
||||||
name,
|
name,
|
||||||
|
path,
|
||||||
options,
|
options,
|
||||||
label,
|
label,
|
||||||
onChange,
|
onChange: event => {
|
||||||
|
// FormComponent's handleChange expects value as argument; look in target.checked or target.value
|
||||||
|
const inputValue = inputType === 'checkbox' ? event.target.checked : event.target.value;
|
||||||
|
onChange(inputValue);
|
||||||
|
},
|
||||||
value,
|
value,
|
||||||
disabled,
|
disabled,
|
||||||
...this.props.inputProperties,
|
...this.props.inputProperties,
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
// import React from 'react';
|
||||||
|
import Form from 'react-bootstrap/lib/Form';
|
||||||
|
import { registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
|
registerComponent('FormElement', Form);
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
Layout for a single form item
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import Form from 'react-bootstrap/lib/Form';
|
||||||
|
import Row from 'react-bootstrap/lib/Row';
|
||||||
|
import Col from 'react-bootstrap/lib/Col';
|
||||||
|
import { registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
|
const FormItem = ({ path, label, children, beforeInput, afterInput }) => {
|
||||||
|
if (label) {
|
||||||
|
return (
|
||||||
|
<Form.Group as={Row} controlId={path}>
|
||||||
|
<Form.Label column sm={3}>
|
||||||
|
{label}
|
||||||
|
</Form.Label>
|
||||||
|
<Col sm={9}>
|
||||||
|
{beforeInput}
|
||||||
|
{children}
|
||||||
|
{afterInput}
|
||||||
|
</Col>
|
||||||
|
</Form.Group>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Form.Group controlId={path}>
|
||||||
|
{beforeInput}
|
||||||
|
{children}
|
||||||
|
{afterInput}
|
||||||
|
</Form.Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
registerComponent('FormItem', FormItem);
|
|
@ -1,8 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Input } from 'formsy-react-components';
|
import Form from 'react-bootstrap/lib/Form';
|
||||||
import { registerComponent } from 'meteor/vulcan:core';
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
const NumberComponent = ({refFunction, inputProperties}) =>
|
const NumberComponent = ({ refFunction, inputProperties, itemProperties }) => (
|
||||||
<Input {...inputProperties} ref={refFunction} type="number" />;
|
<Components.FormItem {...inputProperties} {...itemProperties}>
|
||||||
|
<Form.Control {...inputProperties} ref={refFunction} type="number" />
|
||||||
|
</Components.FormItem>
|
||||||
|
);
|
||||||
|
|
||||||
registerComponent('FormComponentNumber', NumberComponent);
|
registerComponent('FormComponentNumber', NumberComponent);
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { RadioGroup } from 'formsy-react-components';
|
import Form from 'react-bootstrap/lib/Form';
|
||||||
import { registerComponent } from 'meteor/vulcan:core';
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
const RadioGroupComponent = ({refFunction, inputProperties, ...properties}) => <RadioGroup {...inputProperties} ref={refFunction}/>;
|
const RadioGroupComponent = ({ refFunction, inputProperties, itemProperties }) => (
|
||||||
|
<Components.FormItem {...inputProperties} {...itemProperties}>
|
||||||
|
<Form.Check {...inputProperties} ref={refFunction} />
|
||||||
|
</Components.FormItem>
|
||||||
|
);
|
||||||
|
|
||||||
registerComponent('FormComponentRadioGroup', RadioGroupComponent);
|
registerComponent('FormComponentRadioGroup', RadioGroupComponent);
|
||||||
|
|
|
@ -1,20 +1,31 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { intlShape } from 'meteor/vulcan:i18n';
|
import { intlShape } from 'meteor/vulcan:i18n';
|
||||||
import { Select } from 'formsy-react-components';
|
import Form from 'react-bootstrap/lib/Form';
|
||||||
import { registerComponent } from 'meteor/vulcan:core';
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
// copied from vulcan:forms/utils.js to avoid extra dependency
|
// copied from vulcan:forms/utils.js to avoid extra dependency
|
||||||
const getFieldType = datatype => datatype && datatype[0].type;
|
const getFieldType = datatype => datatype && datatype[0].type;
|
||||||
|
|
||||||
const SelectComponent = ({refFunction, inputProperties, datatype, ...properties}, { intl }) => {
|
const SelectComponent = ({ refFunction, inputProperties, itemProperties, datatype }, { intl }) => {
|
||||||
const noneOption = {
|
const noneOption = {
|
||||||
label: intl.formatMessage({ id: 'forms.select_option' }),
|
label: intl.formatMessage({ id: 'forms.select_option' }),
|
||||||
value: getFieldType(datatype) === String || getFieldType(datatype) === Number ? '' : null, // depending on field type, empty value can be '' or null
|
value: getFieldType(datatype) === String || getFieldType(datatype) === Number ? '' : null, // depending on field type, empty value can be '' or null
|
||||||
disabled: true,
|
disabled: true,
|
||||||
};
|
};
|
||||||
let otherOptions = Array.isArray(inputProperties.options) && inputProperties.options.length ? inputProperties.options : [];
|
let otherOptions =
|
||||||
|
Array.isArray(inputProperties.options) && inputProperties.options.length
|
||||||
|
? inputProperties.options
|
||||||
|
: [];
|
||||||
const options = [noneOption, ...otherOptions];
|
const options = [noneOption, ...otherOptions];
|
||||||
return <Select {...inputProperties} options={options} ref={refFunction}/>;
|
return (
|
||||||
|
<Components.FormItem {...inputProperties} {...itemProperties}>
|
||||||
|
<Form.Control as="select" {...inputProperties} ref={refFunction}>
|
||||||
|
{options.map((option, i) => (
|
||||||
|
<option key={i} {...option}>{option.value}</option>
|
||||||
|
))}
|
||||||
|
</Form.Control>
|
||||||
|
</Components.FormItem>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
SelectComponent.contextTypes = {
|
SelectComponent.contextTypes = {
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { intlShape } from 'meteor/vulcan:i18n';
|
import { intlShape } from 'meteor/vulcan:i18n';
|
||||||
import { Select } from 'formsy-react-components';
|
import Form from 'react-bootstrap/lib/Form';
|
||||||
import { registerComponent } from 'meteor/vulcan:core';
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
const SelectMultipleComponent = ({refFunction, inputProperties, ...properties}, { intl }) => {
|
const SelectMultipleComponent = ({ refFunction, inputProperties, itemProperties }, { intl }) => {
|
||||||
inputProperties.multiple = true;
|
inputProperties.multiple = true;
|
||||||
|
|
||||||
return <Select {...inputProperties} ref={refFunction}/>;
|
return (
|
||||||
|
<Components.FormItem {...inputProperties} {...itemProperties}>
|
||||||
|
<Form.Control as="select" {...inputProperties} ref={refFunction} />
|
||||||
|
</Components.FormItem>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
SelectMultipleComponent.contextTypes = {
|
SelectMultipleComponent.contextTypes = {
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { registerComponent } from 'meteor/vulcan:core';
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
const parseUrl = value => {
|
const parseUrl = value => {
|
||||||
return value && value.toString().slice(0,4) === 'http' ? <a href={value} target="_blank">{value}</a> : value;
|
return value && value.toString().slice(0, 4) === 'http' ? (
|
||||||
|
<a href={value} target="_blank">
|
||||||
|
{value}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
value
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const StaticComponent = ({ value, label }) => (
|
const StaticComponent = ({ inputProperties, itemProperties }) => (
|
||||||
<div className="form-group row">
|
<Components.FormItem {...inputProperties} {...itemProperties}>
|
||||||
<label className="control-label col-sm-3">{label}</label>
|
<div>{parseUrl(inputProperties.value)}</div>
|
||||||
<div className="col-sm-9">{parseUrl(value)}</div>
|
</Components.FormItem>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
registerComponent('FormComponentStaticText', StaticComponent);
|
registerComponent('FormComponentStaticText', StaticComponent);
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Textarea } from 'formsy-react-components';
|
import Form from 'react-bootstrap/lib/Form';
|
||||||
import { registerComponent } from 'meteor/vulcan:core';
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
const TextareaComponent = ({refFunction, inputProperties, ...properties}) => <Textarea ref={refFunction} {...inputProperties}/>;
|
const TextareaComponent = ({ refFunction, inputProperties, itemProperties }) => (
|
||||||
|
<Components.FormItem {...inputProperties} {...itemProperties}>
|
||||||
|
<Form.Control as="textarea" ref={refFunction} {...inputProperties} />
|
||||||
|
</Components.FormItem>
|
||||||
|
);
|
||||||
|
|
||||||
registerComponent('FormComponentTextarea', TextareaComponent);
|
registerComponent('FormComponentTextarea', TextareaComponent);
|
||||||
|
|
|
@ -1,56 +1,44 @@
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import DateTimePicker from 'react-datetime';
|
import DateTimePicker from 'react-datetime';
|
||||||
import { registerComponent } from 'meteor/vulcan:core';
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
class Time extends PureComponent {
|
class Time extends PureComponent {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.updateDate = this.updateDate.bind(this);
|
this.updateDate = this.updateDate.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// when the datetime picker has mounted, SmartForm will catch the date value (no formsy mixin in this component)
|
|
||||||
// componentDidMount() {
|
|
||||||
// if (this.props.value) {
|
|
||||||
// this.context.updateCurrentValues({[this.props.path]: this.props.value});
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
updateDate(mDate) {
|
updateDate(mDate) {
|
||||||
// if this is a properly formatted moment date, update time
|
// if this is a properly formatted moment date, update time
|
||||||
if (typeof mDate === 'object') {
|
if (typeof mDate === 'object') {
|
||||||
this.context.updateCurrentValues({[this.props.path]: mDate.format('HH:mm')});
|
this.context.updateCurrentValues({ [this.props.path]: mDate.format('HH:mm') });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
|
|
||||||
// transform time string into date object to work inside datetimepicker
|
// transform time string into date object to work inside datetimepicker
|
||||||
const time = this.props.value;
|
const time = this.props.value;
|
||||||
if (time) {
|
if (time) {
|
||||||
date.setHours(parseInt(time.substr(0,2)), parseInt(time.substr(3,5)));
|
date.setHours(parseInt(time.substr(0, 2)), parseInt(time.substr(3, 5)));
|
||||||
} else {
|
} else {
|
||||||
date.setHours(0,0);
|
date.setHours(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="form-group row">
|
<Components.FormItem {...this.props.inputProperties} {...this.props.itemProperties}>
|
||||||
<label className="control-label col-sm-3">{this.props.label}</label>
|
<DateTimePicker
|
||||||
<div className="col-sm-9">
|
value={date}
|
||||||
<DateTimePicker
|
viewMode="time"
|
||||||
value={date}
|
dateFormat={false}
|
||||||
viewMode="time"
|
timeFormat="HH:mm"
|
||||||
dateFormat={false}
|
// newDate argument is a Moment object given by react-datetime
|
||||||
timeFormat="HH:mm"
|
onChange={newDate => this.updateDate(newDate)}
|
||||||
// newDate argument is a Moment object given by react-datetime
|
inputProps={{ name: this.props.name }}
|
||||||
onChange={newDate => this.updateDate(newDate)}
|
/>
|
||||||
inputProps={{name: this.props.name}}
|
</Components.FormItem>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,4 +58,4 @@ Time.contextTypes = {
|
||||||
|
|
||||||
export default Time;
|
export default Time;
|
||||||
|
|
||||||
registerComponent('FormComponentTime', Time);
|
registerComponent('FormComponentTime', Time);
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Input } from 'formsy-react-components';
|
import Form from 'react-bootstrap/lib/Form';
|
||||||
import { registerComponent } from 'meteor/vulcan:core';
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
const UrlComponent = ({refFunction, inputProperties, ...properties}) => <Input ref={refFunction} {...inputProperties} type="url" />;
|
const UrlComponent = ({ refFunction, inputProperties, itemProperties }) => (
|
||||||
|
<Components.FormItem {...inputProperties} {...itemProperties}>
|
||||||
|
<Form.Control ref={refFunction} {...inputProperties} {...itemProperties} type="url" />
|
||||||
|
</Components.FormItem>
|
||||||
|
);
|
||||||
|
|
||||||
registerComponent('FormComponentUrl', UrlComponent);
|
registerComponent('FormComponentUrl', UrlComponent);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import '../components/forms/FormElement.jsx';
|
||||||
import '../components/forms/Checkbox.jsx';
|
import '../components/forms/Checkbox.jsx';
|
||||||
import '../components/forms/Checkboxgroup.jsx';
|
import '../components/forms/Checkboxgroup.jsx';
|
||||||
import '../components/forms/Date.jsx';
|
import '../components/forms/Date.jsx';
|
||||||
|
@ -15,6 +16,7 @@ import '../components/forms/Url.jsx';
|
||||||
import '../components/forms/StaticText.jsx';
|
import '../components/forms/StaticText.jsx';
|
||||||
import '../components/forms/FormComponentInner.jsx';
|
import '../components/forms/FormComponentInner.jsx';
|
||||||
import '../components/forms/FormControl.jsx'; // note: only used by old accounts package, remove soon?
|
import '../components/forms/FormControl.jsx'; // note: only used by old accounts package, remove soon?
|
||||||
|
import '../components/forms/FormItem.jsx';
|
||||||
|
|
||||||
import '../components/ui/Button.jsx';
|
import '../components/ui/Button.jsx';
|
||||||
import '../components/ui/Alert.jsx';
|
import '../components/ui/Alert.jsx';
|
||||||
|
|
Loading…
Add table
Reference in a new issue