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": [1, "always", { "objectsInObjects": false, "arraysInObjects": false }],
|
||||
"babel/object-shorthand": 0,
|
||||
"babel/arrow-parens": 0,
|
||||
"no-await-in-loop": 1,
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
const {esNextPaths} = require('./.vulcan/shared/pathsByLanguageVersion');
|
||||
|
||||
module.exports = {
|
||||
bracketSpacing: false,
|
||||
bracketSpacing: true,
|
||||
singleQuote: true,
|
||||
jsxBracketSameLine: true,
|
||||
trailingComma: 'es5',
|
||||
printWidth: 80,
|
||||
printWidth: 100,
|
||||
parser: 'babylon',
|
||||
|
||||
overrides: [
|
||||
|
|
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": "npm run test-unit",
|
||||
"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": {
|
||||
"hooks": {
|
||||
|
@ -105,10 +106,13 @@
|
|||
},
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@userfrosting/merge-package-dependencies": "^1.2.0",
|
||||
"autoprefixer": "^6.3.6",
|
||||
"babel-eslint": "^7.0.0",
|
||||
"babylon": "^6.18.0",
|
||||
"colors": "^1.3.2",
|
||||
"chromedriver": "^2.40.0",
|
||||
"diff": "^3.5.0",
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme-adapter-react-16.3": "^1.4.0",
|
||||
"eslint": "^3.19.0",
|
||||
|
|
|
@ -6,7 +6,7 @@ export class AccountsFormMessage extends React.Component {
|
|||
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
|
||||
return message ? (
|
||||
<div style={style} className={[ className, type ].join(' ')}>{ message }</div>
|
||||
<div style={style} className={[className, type].join(' ')}>{ message }</div>
|
||||
) : null;
|
||||
}
|
||||
}
|
||||
|
|
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 './routes.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';
|
||||
|
||||
addRoute({ name: 'admin', path: '/admin', component: () => getDynamicComponent(import('../components/AdminHome.jsx'))});
|
||||
addRoute({ name: 'admin2', path: '/admin/users', component: () => getDynamicComponent(import('../components/AdminHome.jsx'))});
|
||||
addRoute({
|
||||
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 { withRouter} from 'react-router';
|
||||
|
||||
const DummyErrorCatcher = ({ children }) => children;
|
||||
|
||||
// see https://stackoverflow.com/questions/42862028/react-router-v4-with-multiple-layouts
|
||||
const RouteWithLayout = ({ layoutName, component, currentRoute, ...rest }) => {
|
||||
|
||||
// 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 (
|
||||
<Route
|
||||
|
@ -38,8 +36,8 @@ const RouteWithLayout = ({ layoutName, component, currentRoute, ...rest }) => {
|
|||
{...rest}
|
||||
render={props => {
|
||||
|
||||
const layoutProps = {...props, currentRoute}
|
||||
const childComponentProps = {...props, currentRoute}
|
||||
const layoutProps = { ...props, currentRoute };
|
||||
const childComponentProps = { ...props, currentRoute };
|
||||
const layout = layoutName ? Components[layoutName] : Components.Layout;
|
||||
return React.createElement(layout, layoutProps, <ErrorCatcher>{React.createElement(component, childComponentProps)}</ErrorCatcher>);
|
||||
}}
|
||||
|
|
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
|
||||
|
||||
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 }} />
|
||||
</Components.Alert>
|
||||
);
|
||||
|
|
|
@ -1,31 +1,63 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import { withCurrentUser } from 'meteor/vulcan:core';
|
||||
import { withRouter } from 'react-router';
|
||||
import React, {PureComponent} from 'react';
|
||||
import {Components} from 'meteor/vulcan:lib';
|
||||
import withCurrentUser from './withCurrentUser';
|
||||
import {withRouter} from 'react-router';
|
||||
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
|
||||
return WrappedComponent => {
|
||||
class AccessComponent extends PureComponent {
|
||||
|
||||
// if there are any groups defined check if user belongs, else just check if user exists
|
||||
canAccess = currentUser => {
|
||||
return groups ? Users.isMemberOf(currentUser, groups) : currentUser;
|
||||
}
|
||||
};
|
||||
|
||||
// redirect on constructor if user cannot access
|
||||
constructor(props) {
|
||||
super(props);
|
||||
if(!this.canAccess(props.currentUser) && typeof redirect === 'string') {
|
||||
if (
|
||||
!this.canAccess(props.currentUser) &&
|
||||
typeof redirect === 'string'
|
||||
) {
|
||||
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() {
|
||||
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) {
|
||||
registerCallback({
|
||||
name: `${typeName}.create.validate`,
|
||||
iterator: {document: 'The document being inserted'},
|
||||
iterator: { validationErrors: 'An array that can be used to accumulate validation errors' },
|
||||
properties: [
|
||||
{document: 'The document being inserted'},
|
||||
{currentUser: 'The current user'},
|
||||
{
|
||||
validationErrors:
|
||||
'An object that can be used to accumulate validation errors',
|
||||
},
|
||||
{ document: 'The document being inserted' },
|
||||
{ currentUser: 'The current user' },
|
||||
{ collection: 'The collection the document belongs to' },
|
||||
{ context: 'The context of the mutation'},
|
||||
],
|
||||
runs: 'sync',
|
||||
returns: 'document',
|
||||
|
@ -467,14 +465,13 @@ const registerCollectionCallbacks = (typeName, options) => {
|
|||
if (options.update) {
|
||||
registerCallback({
|
||||
name: `${typeName}.update.validate`,
|
||||
iterator: {data: 'The client data'},
|
||||
iterator: { validationErrors: 'An object that can be used to accumulate validation errors' },
|
||||
properties: [
|
||||
{document: 'The document being edited'},
|
||||
{currentUser: 'The current user'},
|
||||
{
|
||||
validationErrors:
|
||||
'An object that can be used to accumulate validation errors',
|
||||
},
|
||||
{ document: 'The document being edited' },
|
||||
{ data: 'The client data' },
|
||||
{ currentUser: 'The current user' },
|
||||
{ collection: 'The collection the document belongs to' },
|
||||
{ context: 'The context of the mutation'},
|
||||
],
|
||||
runs: 'sync',
|
||||
returns: 'modifier',
|
||||
|
@ -522,13 +519,12 @@ const registerCollectionCallbacks = (typeName, options) => {
|
|||
if (options.delete) {
|
||||
registerCallback({
|
||||
name: `${typeName}.delete.validate`,
|
||||
iterator: {document: 'The document being removed'},
|
||||
iterator: { validationErrors: 'An object that can be used to accumulate validation errors' },
|
||||
properties: [
|
||||
{currentUser: 'The current user'},
|
||||
{
|
||||
validationErrors:
|
||||
'An object that can be used to accumulate validation errors',
|
||||
},
|
||||
{ currentUser: 'The current user' },
|
||||
{ document: 'The document being removed' },
|
||||
{ collection: 'The collection the document belongs to'},
|
||||
{ context: 'The context of this mutation'}
|
||||
],
|
||||
runs: 'sync',
|
||||
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 RouterHook } from './components/RouterHook.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 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 withMutation } from './containers/withMutation.js';
|
||||
export { default as withUpsert } from './containers/withUpsert.js';
|
||||
export { default as withSiteData } from './containers/withSiteData.js';
|
||||
|
||||
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,10 +2,12 @@ import React from 'react';
|
|||
import { registerComponent, Components, Routes } from 'meteor/vulcan:lib';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const RoutePath = ({ document }) =>
|
||||
<Link to={document.path}>{document.path}</Link>;
|
||||
const RoutePath = ({document}) => (
|
||||
<Link to={document.path}>{document.path}</Link>
|
||||
);
|
||||
|
||||
const RoutesDashboard = props =>
|
||||
const RoutesDashboard = props => {
|
||||
return (
|
||||
<div className="routes">
|
||||
<Components.Datatable
|
||||
showSearch={false}
|
||||
|
@ -16,11 +18,14 @@ const RoutesDashboard = props =>
|
|||
'name',
|
||||
{
|
||||
name: 'path',
|
||||
component: RoutePath
|
||||
component: RoutePath,
|
||||
},
|
||||
'componentName',
|
||||
'layoutName'
|
||||
]}
|
||||
/>
|
||||
</div>;
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
registerComponent('Routes', RoutesDashboard);
|
|
@ -1,4 +1,4 @@
|
|||
import '../components/AdminLayout.jsx';
|
||||
import '../components/DebugLayout.jsx';
|
||||
|
||||
import '../components/Emails.jsx';
|
||||
import '../components/Groups.jsx';
|
||||
|
|
|
@ -1,14 +1,54 @@
|
|||
import { addRoute, getDynamicComponent } from 'meteor/vulcan:lib';
|
||||
import {addRoute, getDynamicComponent} from 'meteor/vulcan:lib';
|
||||
|
||||
addRoute([
|
||||
// {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: 'debugSettings', path: '/debug/settings', componentName: 'Settings', layoutName: 'AdminLayout' },
|
||||
{ name: 'debugCallbacks', path: '/debug/callbacks', componentName: 'Callbacks', layoutName: 'AdminLayout' },
|
||||
{
|
||||
name: 'debug',
|
||||
path: '/debug',
|
||||
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: 'debugEmails', path: '/debug/emails', componentName: 'Emails', layoutName: 'AdminLayout' },
|
||||
{ name: 'debugRoutes', path: '/debug/routes', componentName: 'Routes', layoutName: 'AdminLayout' },
|
||||
{ name: 'debugComponents', path: '/debug/components', componentName: 'Components', layoutName: 'AdminLayout' },
|
||||
{ name: 'debugI18n', path: '/debug/i18n', componentName: 'I18n', layoutName: 'AdminLayout' },
|
||||
{
|
||||
name: 'debugEmails',
|
||||
path: '/debug/emails',
|
||||
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 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 { 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: fix this error: Error: getaddrinfo ENOTFOUND
|
||||
|
||||
if (typeof to === 'object') {
|
||||
// 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');
|
||||
|
@ -102,14 +103,15 @@ VulcanEmail.send = (to, subject, html, text, throwErrors, cc, bcc, replyTo, head
|
|||
|
||||
const email = {
|
||||
from: from,
|
||||
to: to,
|
||||
cc: cc,
|
||||
bcc: bcc,
|
||||
replyTo: replyTo,
|
||||
subject: subject,
|
||||
headers: headers,
|
||||
text: text,
|
||||
html: html,
|
||||
to,
|
||||
cc,
|
||||
bcc,
|
||||
replyTo,
|
||||
subject,
|
||||
headers,
|
||||
text,
|
||||
html,
|
||||
attachments,
|
||||
};
|
||||
|
||||
const shouldSendEmail = process.env.NODE_ENV === 'production' || getSetting('enableDevelopmentEmails', false);
|
||||
|
@ -153,9 +155,9 @@ VulcanEmail.build = async ({ emailName, variables, locale }) => {
|
|||
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 });
|
||||
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));
|
||||
|
|
|
@ -3,9 +3,7 @@ import { withMutation } from 'meteor/vulcan:core';
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage, intlShape } from 'meteor/vulcan:i18n';
|
||||
import FRC from 'formsy-react-components';
|
||||
|
||||
const Input = FRC.Input;
|
||||
import Form from 'react-bootstrap/lib/Form';
|
||||
|
||||
class EmbedURL extends Component {
|
||||
constructor(props) {
|
||||
|
@ -161,7 +159,7 @@ class EmbedURL extends Component {
|
|||
<label className="control-label col-sm-3">{this.props.label}</label>
|
||||
<div className="col-sm-9 embedly-form-control">
|
||||
<div className="embedly-url-field">
|
||||
<Input
|
||||
<Form.Control
|
||||
{...inputProperties}
|
||||
onBlur={this.handleBlur}
|
||||
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 { 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() {
|
||||
const { error } = this.state;
|
||||
return error ? (
|
||||
<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>
|
||||
) : (
|
||||
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 PropTypes from 'prop-types';
|
||||
import { intlShape } from 'meteor/vulcan:i18n';
|
||||
import Formsy from 'formsy-react';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import get from 'lodash/get';
|
||||
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.
|
||||
|
||||
*/
|
||||
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 {
|
||||
schema = this.state.schema,
|
||||
excludeHiddenFields = true,
|
||||
replaceIntlFields = false,
|
||||
addExtraFields = true
|
||||
} = args;
|
||||
} = args0;
|
||||
|
||||
const { fields, addFields } = this.props;
|
||||
|
||||
|
@ -619,7 +620,7 @@ class SmartForm extends Component {
|
|||
...newValues
|
||||
} // Submit form after setState update completed
|
||||
}),
|
||||
() => this.submitForm(this.form.getModel())
|
||||
() => this.submitForm()
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -856,7 +857,7 @@ class SmartForm extends Component {
|
|||
*/
|
||||
formKeyDown = event => {
|
||||
if ((event.ctrlKey || event.metaKey) && event.keyCode === 13) {
|
||||
this.submitForm(this.form.getModel());
|
||||
this.submitForm();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -920,8 +921,9 @@ class SmartForm extends Component {
|
|||
Submit form handler
|
||||
|
||||
*/
|
||||
submitForm = data => {
|
||||
// note: we can discard the data collected by Formsy because all the data we need is already available via getDocument()
|
||||
submitForm = event => {
|
||||
|
||||
event && event.preventDefault();
|
||||
|
||||
// if form is disabled (there is already a submit handler running) don't do anything
|
||||
if (this.state.disabled) {
|
||||
|
@ -931,9 +933,9 @@ class SmartForm extends Component {
|
|||
// clear errors and disable form while it's submitting
|
||||
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
|
||||
data = this.getData({ replaceIntlFields: true, addExtraFields: false });
|
||||
let data = this.getData({ replaceIntlFields: true, addExtraFields: false });
|
||||
|
||||
// if there's a submit callback, run it
|
||||
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() {
|
||||
const fieldGroups = this.getFieldGroups();
|
||||
const collectionName = this.props.collectionName;
|
||||
const FormComponents = mergeWithComponents(this.props.formComponents);
|
||||
|
||||
return (
|
||||
<div className={'document-' + this.getFormType()}>
|
||||
<Formsy.Form
|
||||
id={this.props.id}
|
||||
onSubmit={this.submitForm}
|
||||
onKeyDown={this.formKeyDown}
|
||||
ref={e => {
|
||||
this.form = e;
|
||||
}}
|
||||
>
|
||||
<FormComponents.FormErrors errors={this.state.errors} />
|
||||
<FormComponents.FormElement {...this.getFormProps()}>
|
||||
<FormComponents.FormErrors {...this.getFormErrorsProps()} />
|
||||
|
||||
{fieldGroups.map(group => (
|
||||
<FormComponents.FormGroup
|
||||
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.getFieldGroups().map(group => (
|
||||
<FormComponents.FormGroup {...this.getFormGroupProps(group)} />
|
||||
))}
|
||||
|
||||
{this.props.repeatErrors && this.renderErrors()}
|
||||
|
||||
<FormComponents.FormSubmit
|
||||
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={collectionName}
|
||||
currentValues={this.state.currentValues}
|
||||
deletedValues={this.state.deletedValues}
|
||||
errors={this.state.errors}
|
||||
/>
|
||||
</Formsy.Form>
|
||||
</div>
|
||||
<FormComponents.FormSubmit {...this.getFormSubmitProps()} />
|
||||
</FormComponents.FormElement>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,14 +28,14 @@ class FormComponent extends Component {
|
|||
}
|
||||
|
||||
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
|
||||
const includesPathOrChildren = deletedValues =>
|
||||
deletedValues.some(deletedPath => deletedPath.includes(path));
|
||||
|
||||
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 deleteChanged =
|
||||
includesPathOrChildren(deletedValues) !==
|
||||
|
@ -93,7 +93,8 @@ class FormComponent extends Component {
|
|||
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 === '') {
|
||||
value = null;
|
||||
|
|
|
@ -97,15 +97,12 @@ class FormNestedArray extends PureComponent {
|
|||
)
|
||||
),
|
||||
(!maxCount || arrayLength < maxCount) && (
|
||||
<Components.Button
|
||||
<Components.FormNestedFoot
|
||||
key="add-button"
|
||||
size="small"
|
||||
variant="success"
|
||||
onClick={this.addItem}
|
||||
className="form-nested-button"
|
||||
>
|
||||
<Components.IconAdd height={12} width={12} />
|
||||
</Components.Button>
|
||||
addItem={this.addItem}
|
||||
label={this.props.label}
|
||||
className="form-nested-foot"
|
||||
/>
|
||||
),
|
||||
hasErrors ? (
|
||||
<FormComponents.FieldErrors
|
||||
|
|
|
@ -2,8 +2,8 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||
|
||||
const FormNestedFoot = ({ label, addItem }) => (
|
||||
<Components.Button size="small" variant="success" iconButton onClick={addItem} className="form-nested-button">
|
||||
const FormNestedFoot = ({ addItem }) => (
|
||||
<Components.Button size="small" variant="success" onClick={addItem} className="form-nested-button">
|
||||
<Components.IconAdd height={12} width={12} />
|
||||
</Components.Button>
|
||||
);
|
||||
|
|
|
@ -51,6 +51,8 @@ import {
|
|||
import withCollectionProps from './withCollectionProps';
|
||||
import { callbackProps } from './propTypes';
|
||||
|
||||
const intlSuffix = '_intl';
|
||||
|
||||
class FormWrapper extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -94,8 +96,11 @@ class FormWrapper extends PureComponent {
|
|||
|
||||
// if "fields" prop is specified, restrict list of fields to it
|
||||
if (typeof fields !== 'undefined' && fields.length > 0) {
|
||||
queryFields = _.intersection(queryFields, fields);
|
||||
mutationFields = _.intersection(mutationFields, fields);
|
||||
// add "_intl" suffix to all fields in case some of them are intl 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
|
||||
|
@ -105,7 +110,7 @@ class FormWrapper extends PureComponent {
|
|||
}
|
||||
|
||||
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.
|
||||
|
|
|
@ -49,15 +49,23 @@ export function registerComponent(name, rawComponent, ...hocs) {
|
|||
* @param {String} name The name of the component to get.
|
||||
* @returns {Function|React Component} A (wrapped) React component
|
||||
*/
|
||||
export const getComponent = (name) => {
|
||||
export const getComponent = name => {
|
||||
const component = ComponentsTable[name];
|
||||
if (!component) {
|
||||
throw new Error(`Component ${name} not registered.`);
|
||||
}
|
||||
if (component.hocs) {
|
||||
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;
|
||||
if (typeof actualHoc !== 'function') {
|
||||
throw new Error(`In registered component ${name}, an hoc is of type ${typeof actualHoc}`);
|
||||
}
|
||||
return actualHoc(...args);
|
||||
});
|
||||
return compose(...hocs)(component.rawComponent);
|
||||
|
@ -202,4 +210,3 @@ export const delayedComponent = name => {
|
|||
// return proxy;
|
||||
//};
|
||||
export const mergeWithComponents = myComponents => (myComponents ? { ...Components, ...myComponents } : Components);
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import { delayedComponent } from './components';
|
|||
* `Components.DynamicLoading` in the meantime.
|
||||
*
|
||||
* @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
|
||||
* import { addRoute, dynamicLoader, getDynamicComponent } from 'meteor/vulcan:core';
|
||||
|
|
|
@ -204,6 +204,9 @@ const createApolloServer = (givenOptions = {}, givenConfig = {}) => {
|
|||
|
||||
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 headers:');
|
||||
// console.log(headers);
|
||||
|
|
|
@ -51,7 +51,7 @@ GraphQL @intl directive resolver
|
|||
*/
|
||||
class IntlDirective extends SchemaDirectiveVisitor {
|
||||
visitFieldDefinition(field, details) {
|
||||
const {resolve = defaultFieldResolver, name} = field;
|
||||
const { resolve = defaultFieldResolver, name } = field;
|
||||
field.resolve = async function(...args) {
|
||||
const [doc, graphQLArguments, context] = args;
|
||||
const fieldValue = await resolve.apply(this, args);
|
||||
|
|
|
@ -1,25 +1,29 @@
|
|||
/*
|
||||
|
||||
Mutations have four steps:
|
||||
Mutations have five steps:
|
||||
|
||||
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:
|
||||
|
||||
- Check that the current user has permission to insert/edit each field.
|
||||
- Add userId to document (insert only).
|
||||
- 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
|
||||
|
||||
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.
|
||||
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');
|
||||
|
||||
/*
|
||||
|
||||
Create
|
||||
|
||||
*/
|
||||
export const createMutator = async ({ collection, document, data, currentUser, validate, context }) => {
|
||||
|
||||
const { collectionName, typeName } = collection.options;
|
||||
|
||||
debug('');
|
||||
debugGroup(`--------------- start \x1b[36m${collectionName} Create Mutator\x1b[0m ---------------`);
|
||||
debug(`validate: ${validate}`);
|
||||
debug(document || data);
|
||||
|
||||
// OpenCRUD backwards compatibility: accept either data or 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 callbackProperties = { currentUser, collection, context };
|
||||
startDebugMutator(collectionName, 'Update', { validate, document });
|
||||
|
||||
/*
|
||||
|
||||
Properties
|
||||
|
||||
*/
|
||||
const properties = { data, currentUser, collection, context, document: document };
|
||||
|
||||
/*
|
||||
|
||||
Validation
|
||||
|
||||
*/
|
||||
if (validate) {
|
||||
|
||||
let validationErrors = [];
|
||||
|
||||
validationErrors = validationErrors.concat(validateDocument(newDocument, collection, context));
|
||||
|
||||
validationErrors = validationErrors.concat(validateDocument(document, collection, context));
|
||||
// run validation callbacks
|
||||
validationErrors = await runCallbacks({ name: `${typeName.toLowerCase()}.create.validate`, iterator: validationErrors, properties: { ...callbackProperties, document: newDocument } });
|
||||
validationErrors = await runCallbacks({ name: '*.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 });
|
||||
// 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) {
|
||||
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
|
||||
if (currentUser) {
|
||||
const userIdInSchema = Object.keys(schema).find(key => key === 'userId');
|
||||
if (!!userIdInSchema && !newDocument.userId) newDocument.userId = currentUser._id;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
run onCreate step
|
||||
userId
|
||||
|
||||
If user is logged in, check if userId field is in the schema and add it to document if needed
|
||||
|
||||
*/
|
||||
if (currentUser) {
|
||||
const userIdInSchema = Object.keys(schema).find(key => key === 'userId');
|
||||
if (!!userIdInSchema && !document.userId) document.userId = currentUser._id;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
onCreate
|
||||
|
||||
note: cannot use forEach with async/await.
|
||||
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
|
||||
|
||||
*/
|
||||
for(let fieldName of Object.keys(schema)) {
|
||||
for (let fieldName of Object.keys(schema)) {
|
||||
let autoValue;
|
||||
if (schema[fieldName].onCreate) {
|
||||
// OpenCRUD backwards compatibility: keep both newDocument and data for now, but phase our newDocument eventually
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
autoValue = await schema[fieldName].onCreate({ newDocument: clone(newDocument), data: clone(newDocument), currentUser, context });
|
||||
// OpenCRUD backwards compatibility: keep both newDocument and data for now, but phase out newDocument eventually
|
||||
autoValue = await schema[fieldName].onCreate(properties); // eslint-disable-line no-await-in-loop
|
||||
} else if (schema[fieldName].onInsert) {
|
||||
// OpenCRUD backwards compatibility
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
autoValue = await schema[fieldName].onInsert(clone(newDocument), currentUser);
|
||||
autoValue = await schema[fieldName].onInsert(clone(document), currentUser); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
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'];
|
||||
// }
|
||||
|
||||
// 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
|
||||
newDocument = await runCallbacks(`${collectionName.toLowerCase()}.new.before`, newDocument, currentUser);
|
||||
newDocument = await runCallbacks(`${collectionName.toLowerCase()}.new.sync`, newDocument, currentUser);
|
||||
document = await runCallbacks(`${collectionName.toLowerCase()}.new.before`, document, 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
|
||||
newDocument = await runCallbacks({ name: `${typeName.toLowerCase()}.create.after`, iterator: newDocument, properties: callbackProperties });
|
||||
newDocument = await runCallbacks({ name: '*.create.after', iterator: newDocument, properties: callbackProperties });
|
||||
document = await runCallbacks({ name: `${typeName.toLowerCase()}.create.after`, iterator: document, properties });
|
||||
document = await runCallbacks({ name: '*.create.after', iterator: document, properties });
|
||||
// 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
|
||||
await runCallbacksAsync({ name: `${typeName.toLowerCase()}.create.async`, properties: { insertedDocument, document: insertedDocument, ...callbackProperties }});
|
||||
await runCallbacksAsync({ name: '*.create.async', properties: { insertedDocument, document: insertedDocument, ...callbackProperties }});
|
||||
document = await Connectors.get(collection, document._id);
|
||||
|
||||
/*
|
||||
|
||||
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
|
||||
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');
|
||||
debug(newDocument);
|
||||
debugGroupEnd();
|
||||
debug(`--------------- end \x1b[36m${collectionName} Create Mutator\x1b[0m ---------------`);
|
||||
debug('');
|
||||
endDebugMutator(collectionName, 'Create', { document });
|
||||
|
||||
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 schema = collection.simpleSchema()._schema;
|
||||
|
||||
// OpenCRUD backwards compatibility
|
||||
selector = selector || { _id: documentId };
|
||||
data = data || modifierToData({ $set: set, $unset: unset });
|
||||
|
||||
startDebugMutator(collectionName, 'Update', { selector, data });
|
||||
|
||||
if (isEmpty(selector)) {
|
||||
throw new Error('Selector cannot be empty');
|
||||
}
|
||||
|
||||
// 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)}`);
|
||||
}
|
||||
|
||||
const callbackProperties = { data, document, currentUser, collection, context };
|
||||
// get a "preview" of the new document
|
||||
let document = { ...oldDocument, ...data };
|
||||
document = pickBy(document, f => f !== null);
|
||||
|
||||
debug('');
|
||||
debugGroup(`--------------- start \x1b[36m${collectionName} Update Mutator\x1b[0m ---------------`);
|
||||
debug('// collectionName: ', collectionName);
|
||||
debug('// selector: ', selector);
|
||||
debug('// data: ', data);
|
||||
/*
|
||||
|
||||
Properties
|
||||
|
||||
*/
|
||||
const properties = { data, oldDocument, document, currentUser, collection, context };
|
||||
|
||||
/*
|
||||
|
||||
Validation
|
||||
|
||||
*/
|
||||
if (validate) {
|
||||
|
||||
let validationErrors = [];
|
||||
|
||||
validationErrors = validationErrors.concat(validateData(data, document, collection, context));
|
||||
|
||||
validationErrors = await runCallbacks({ name: `${typeName.toLowerCase()}.update.validate`, iterator: validationErrors, properties: callbackProperties });
|
||||
validationErrors = await runCallbacks({ name: '*.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 });
|
||||
// OpenCRUD backwards compatibility
|
||||
data = modifierToData(await runCallbacks(`${collectionName.toLowerCase()}.edit.validate`, dataToModifier(data), document, currentUser, validationErrors));
|
||||
|
||||
if (validationErrors.length) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('// validationErrors');
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(validationErrors);
|
||||
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 } });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
|
||||
// get a "preview" of the new document
|
||||
let newDocument = { ...document, ...data};
|
||||
newDocument = pickBy(newDocument, f => f !== null);
|
||||
onUpdate
|
||||
|
||||
// run onUpdate step
|
||||
for(let fieldName of Object.keys(schema)) {
|
||||
*/
|
||||
for (let fieldName of Object.keys(schema)) {
|
||||
let autoValue;
|
||||
if (schema[fieldName].onUpdate) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
autoValue = await schema[fieldName].onUpdate({ data: clone(data), document, currentUser, newDocument, context });
|
||||
autoValue = await schema[fieldName].onUpdate(properties); // eslint-disable-line no-await-in-loop
|
||||
} else if (schema[fieldName].onEdit) {
|
||||
// OpenCRUD backwards compatibility
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
autoValue = await schema[fieldName].onEdit(dataToModifier(clone(data)), document, currentUser, newDocument);
|
||||
autoValue = await schema[fieldName].onEdit(dataToModifier(clone(data)), document, currentUser, document); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
if (typeof autoValue !== 'undefined') {
|
||||
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
|
||||
data = modifierToData(await runCallbacks(`${collectionName.toLowerCase()}.edit.before`, dataToModifier(data), document, currentUser, newDocument));
|
||||
data = modifierToData(await runCallbacks(`${collectionName.toLowerCase()}.edit.sync`, 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, document));
|
||||
|
||||
// update connector requires a modifier, so get it from data
|
||||
const modifier = dataToModifier(data);
|
||||
|
||||
// remove empty modifiers
|
||||
if (_.isEmpty(modifier.$set)) {
|
||||
if (isEmpty(modifier.$set)) {
|
||||
delete modifier.$set;
|
||||
}
|
||||
if (_.isEmpty(modifier.$unset)) {
|
||||
if (isEmpty(modifier.$unset)) {
|
||||
delete modifier.$unset;
|
||||
}
|
||||
|
||||
if (!_.isEmpty(modifier)) {
|
||||
/*
|
||||
|
||||
DB Operation
|
||||
|
||||
*/
|
||||
if (!isEmpty(modifier)) {
|
||||
// update document
|
||||
await Connectors.update(collection, selector, modifier, { removeEmptyStrings: false });
|
||||
|
||||
// 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
|
||||
// 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
|
||||
await runCallbacksAsync({ name: `${typeName.toLowerCase()}.update.async`, properties: { ...callbackProperties,newDocument, document: newDocument, oldDocument: document }});
|
||||
await runCallbacksAsync({ name: '*.update.async', properties: { ...callbackProperties, newDocument, document: newDocument, oldDocument: document }});
|
||||
await runCallbacksAsync({ name: `${typeName.toLowerCase()}.update.async`, properties });
|
||||
await runCallbacksAsync({ name: '*.update.async', properties });
|
||||
// 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');
|
||||
debug('// modifier: ', modifier);
|
||||
debugGroupEnd();
|
||||
debug(`--------------- end \x1b[36m${collectionName} Update Mutator\x1b[0m ---------------`);
|
||||
debug('');
|
||||
endDebugMutator(collectionName, 'Update', { modifier });
|
||||
|
||||
return { data: newDocument };
|
||||
return { data: document };
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
Delete
|
||||
|
||||
*/
|
||||
export const deleteMutator = async ({ collection, documentId, selector, currentUser, validate, context, document }) => {
|
||||
|
||||
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;
|
||||
|
||||
// OpenCRUD backwards compatibility
|
||||
selector = selector || { _id: documentId };
|
||||
|
||||
|
@ -290,51 +335,68 @@ export const deleteMutator = async ({ collection, documentId, selector, currentU
|
|||
throw new Error('Selector cannot be empty');
|
||||
}
|
||||
|
||||
document = document || await Connectors.get(collection, selector);
|
||||
document = document || (await Connectors.get(collection, selector));
|
||||
|
||||
if (!document) {
|
||||
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) {
|
||||
|
||||
let validationErrors = [];
|
||||
|
||||
validationErrors = await runCallbacks({ name: `${typeName.toLowerCase()}.delete.validate`, iterator: validationErrors, properties: callbackProperties });
|
||||
validationErrors = await runCallbacks({ name: '*.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 });
|
||||
// OpenCRUD backwards compatibility
|
||||
document = await runCallbacks(`${collectionName.toLowerCase()}.remove.validate`, document, currentUser);
|
||||
|
||||
if (validationErrors.length) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('// validationErrors');
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(validationErrors);
|
||||
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 } });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
|
||||
// run onRemove step
|
||||
for(let fieldName of Object.keys(schema)) {
|
||||
onDelete
|
||||
|
||||
*/
|
||||
for (let fieldName of Object.keys(schema)) {
|
||||
if (schema[fieldName].onDelete) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await schema[fieldName].onDelete({ document, currentUser, context });
|
||||
await schema[fieldName].onDelete(properties); // eslint-disable-line no-await-in-loop
|
||||
} else if (schema[fieldName].onRemove) {
|
||||
// OpenCRUD backwards compatibility
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await schema[fieldName].onRemove(document, currentUser);
|
||||
await schema[fieldName].onRemove(document, currentUser); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
await runCallbacks(`${collectionName.toLowerCase()}.remove.before`, document, currentUser);
|
||||
await runCallbacks(`${collectionName.toLowerCase()}.remove.sync`, document, currentUser);
|
||||
|
||||
/*
|
||||
|
||||
DB Operation
|
||||
|
||||
*/
|
||||
await Connectors.delete(collection, selector);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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
|
||||
await runCallbacksAsync(`${collectionName.toLowerCase()}.remove.async`, document, currentUser, collection);
|
||||
|
||||
debugGroupEnd();
|
||||
debug(`--------------- end \x1b[36m${collectionName} Delete Mutator\x1b[0m ---------------`);
|
||||
debug('');
|
||||
endDebugMutator(collectionName, 'Delete');
|
||||
|
||||
return { data: document };
|
||||
};
|
||||
|
@ -362,3 +427,20 @@ export const removeMutation = deleteMutator;
|
|||
export const newMutator = createMutator;
|
||||
export const editMutator = updateMutator;
|
||||
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 { Checkbox } from 'formsy-react-components';
|
||||
import { registerComponent } from 'meteor/vulcan:core';
|
||||
import Form from 'react-bootstrap/lib/Form';
|
||||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||
|
||||
const CheckboxComponent = ({refFunction, inputProperties}) =>
|
||||
<Checkbox {...inputProperties} ref={refFunction} />;
|
||||
const CheckboxComponent = ({ refFunction, path, inputProperties, itemProperties }) => (
|
||||
<Components.FormItem {...inputProperties} {...itemProperties}>
|
||||
<Form.Check {...inputProperties} id={path} ref={refFunction} checked={!!inputProperties.value}/>
|
||||
</Components.FormItem>
|
||||
);
|
||||
|
||||
registerComponent('FormComponentCheckbox', CheckboxComponent);
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React from 'react';
|
||||
import { Checkbox } from 'formsy-react-components';
|
||||
import { registerComponent } from 'meteor/vulcan:core';
|
||||
import Form from 'react-bootstrap/lib/Form';
|
||||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||
import without from 'lodash/without';
|
||||
import uniq from 'lodash/uniq';
|
||||
import intersection from 'lodash/intersection';
|
||||
|
||||
// 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;
|
||||
|
||||
|
@ -22,25 +22,28 @@ const CheckboxGroupComponent = ({ refFunction, label, path, value, formType, upd
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="form-group row">
|
||||
<label className="control-label col-sm-3">{label}</label>
|
||||
<div className="col-sm-9">
|
||||
<Components.FormItem {...inputProperties} {...itemProperties}>
|
||||
<div>
|
||||
{options.map((option, i) => (
|
||||
<Checkbox
|
||||
<Form.Check
|
||||
layout="elementOnly"
|
||||
key={i}
|
||||
{...inputProperties}
|
||||
label={option.label}
|
||||
value={value.includes(option.value)}
|
||||
checked={!!value.includes(option.value)}
|
||||
id={`${path}.${i}`}
|
||||
path={`${path}.${i}`}
|
||||
ref={refFunction}
|
||||
onChange={(name, isChecked) => {
|
||||
onChange={event => {
|
||||
const isChecked = event.target.checked;
|
||||
const newValue = isChecked ? [...value, option.value] : without(value, option.value);
|
||||
updateCurrentValues({ [path]: newValue });
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Components.FormItem>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,43 +1,34 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import DateTimePicker from 'react-datetime';
|
||||
import { registerComponent } from 'meteor/vulcan:core';
|
||||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||
|
||||
class DateComponent extends PureComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
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) {
|
||||
this.context.updateCurrentValues({[this.props.path]: date});
|
||||
this.context.updateCurrentValues({ [this.props.path]: date });
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="form-group row">
|
||||
<label className="control-label col-sm-3">{this.props.label}</label>
|
||||
<div className="col-sm-9">
|
||||
<Components.FormItem {...this.props.inputProperties} {...this.props.itemProperties}>
|
||||
<DateTimePicker
|
||||
value={date}
|
||||
timeFormat={false}
|
||||
// newDate argument is a Moment object given by react-datetime
|
||||
onChange={newDate => this.updateDate(newDate)}
|
||||
inputProps={{name: this.props.name}}
|
||||
inputProps={{ name: this.props.name }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Components.FormItem>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,17 +2,20 @@ import React, { PureComponent } from 'react';
|
|||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||
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 {
|
||||
|
||||
state = {
|
||||
year: null,
|
||||
month: null,
|
||||
day: null,
|
||||
}
|
||||
};
|
||||
|
||||
updateDate = (date) => {
|
||||
updateDate = date => {
|
||||
const { value, path } = this.props;
|
||||
let newDate;
|
||||
this.setState(date, () => {
|
||||
|
@ -20,7 +23,10 @@ class DateComponent2 extends PureComponent {
|
|||
if (isEmptyValue(value)) {
|
||||
if (year && month && day) {
|
||||
// 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() });
|
||||
}
|
||||
} else {
|
||||
|
@ -32,10 +38,9 @@ class DateComponent2 extends PureComponent {
|
|||
this.props.updateCurrentValues({ [path]: newDate.toDate() });
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
const { path, value } = this.props;
|
||||
const months = moment.months();
|
||||
const mDate = !isEmptyValue(value) && moment(value);
|
||||
|
@ -45,10 +50,10 @@ class DateComponent2 extends PureComponent {
|
|||
name: `${path}.month`,
|
||||
layout: 'vertical',
|
||||
options: months.map((m, i) => ({ label: m, value: m })),
|
||||
value: mDate && mDate.format('MMMM') || '',
|
||||
value: (mDate && mDate.format('MMMM')) || '',
|
||||
onChange: (name, value) => {
|
||||
this.updateDate({ month: value });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const dayProperties = {
|
||||
|
@ -56,10 +61,10 @@ class DateComponent2 extends PureComponent {
|
|||
name: `${path}.day`,
|
||||
layout: 'vertical',
|
||||
maxLength: 2,
|
||||
value: mDate && mDate.format('DD') || '',
|
||||
onBlur: (e) => {
|
||||
value: (mDate && mDate.format('DD')) || '',
|
||||
onBlur: e => {
|
||||
this.updateDate({ day: e.target.value });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const yearProperties = {
|
||||
|
@ -67,21 +72,29 @@ class DateComponent2 extends PureComponent {
|
|||
name: `${path}.year`,
|
||||
layout: 'vertical',
|
||||
maxLength: 4,
|
||||
value: mDate && mDate.format('YYYY') || '',
|
||||
onBlur: (e) => {
|
||||
value: (mDate && mDate.format('YYYY')) || '',
|
||||
onBlur: e => {
|
||||
this.updateDate({ year: e.target.value });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="form-group row">
|
||||
<label className="control-label col-sm-3">{this.props.label}</label>
|
||||
<div className="col-sm-9" style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<div><Components.FormComponentSelect inputProperties={monthProperties} 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>
|
||||
<Components.FormItem {...this.props.inputProperties} {...this.props.itemProperties}>
|
||||
<div>
|
||||
<div>
|
||||
<Components.FormComponentSelect
|
||||
inputProperties={monthProperties}
|
||||
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>
|
||||
</Components.FormItem>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,43 +1,35 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import DateTimePicker from 'react-datetime';
|
||||
import { registerComponent } from 'meteor/vulcan:core';
|
||||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||
|
||||
class DateTime extends PureComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
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) {
|
||||
this.context.updateCurrentValues({[this.props.path]: date});
|
||||
this.context.updateCurrentValues({ [this.props.path]: date });
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="form-group row">
|
||||
<label className="control-label col-sm-3">{this.props.label}</label>
|
||||
<div className="col-sm-9">
|
||||
<Components.FormItem {...this.props.inputProperties} {...this.props.itemProperties}>
|
||||
<DateTimePicker
|
||||
value={date}
|
||||
// newDate argument is a Moment object given by react-datetime
|
||||
onChange={newDate => this.updateDate(newDate._d)}
|
||||
format={'x'}
|
||||
inputProps={{name: this.props.name}}
|
||||
inputProps={{ name: this.props.name }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Components.FormItem>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import React from 'react';
|
||||
import { Input } from 'formsy-react-components';
|
||||
import { registerComponent } from 'meteor/vulcan:core';
|
||||
import Form from 'react-bootstrap/lib/Form';
|
||||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||
|
||||
const Default = ({refFunction, inputProperties}) =>
|
||||
<Input {...inputProperties} ref={refFunction} type="text" />;
|
||||
const Default = ({ refFunction, inputProperties, itemProperties }) => (
|
||||
<Components.FormItem {...inputProperties} {...itemProperties}>
|
||||
<Form.Control {...inputProperties} ref={refFunction} type="text" />
|
||||
</Components.FormItem>
|
||||
);
|
||||
|
||||
registerComponent('FormComponentDefault', Default);
|
||||
registerComponent('FormComponentText', Default);
|
||||
registerComponent('FormComponentDefault', Default);
|
||||
registerComponent('FormComponentText', Default);
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import React from 'react';
|
||||
import { Input } from 'formsy-react-components';
|
||||
import { registerComponent } from 'meteor/vulcan:core';
|
||||
import Form from 'react-bootstrap/lib/Form';
|
||||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||
|
||||
const EmailComponent = ({refFunction, inputProperties}) =>
|
||||
<Input {...inputProperties} ref={refFunction} type="email" />;
|
||||
const EmailComponent = ({ refFunction, inputProperties, itemProperties }) => (
|
||||
<Components.FormItem {...inputProperties} {...itemProperties}>
|
||||
<Form.Control {...inputProperties} ref={refFunction} type="email" />
|
||||
</Components.FormItem>
|
||||
);
|
||||
|
||||
registerComponent('FormComponentEmail', EmailComponent);
|
||||
|
|
|
@ -21,15 +21,20 @@ class FormComponentInner extends PureComponent {
|
|||
};
|
||||
|
||||
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
|
||||
// and avoid https://facebook.github.io/react/warnings/unknown-prop.html warnings
|
||||
const inputProperties = {
|
||||
name,
|
||||
path,
|
||||
options,
|
||||
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,
|
||||
disabled,
|
||||
...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 { Input } from 'formsy-react-components';
|
||||
import { registerComponent } from 'meteor/vulcan:core';
|
||||
import Form from 'react-bootstrap/lib/Form';
|
||||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||
|
||||
const NumberComponent = ({refFunction, inputProperties}) =>
|
||||
<Input {...inputProperties} ref={refFunction} type="number" />;
|
||||
const NumberComponent = ({ refFunction, inputProperties, itemProperties }) => (
|
||||
<Components.FormItem {...inputProperties} {...itemProperties}>
|
||||
<Form.Control {...inputProperties} ref={refFunction} type="number" />
|
||||
</Components.FormItem>
|
||||
);
|
||||
|
||||
registerComponent('FormComponentNumber', NumberComponent);
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import React from 'react';
|
||||
import { RadioGroup } from 'formsy-react-components';
|
||||
import { registerComponent } from 'meteor/vulcan:core';
|
||||
import Form from 'react-bootstrap/lib/Form';
|
||||
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);
|
|
@ -1,20 +1,31 @@
|
|||
import React from 'react';
|
||||
import { intlShape } from 'meteor/vulcan:i18n';
|
||||
import { Select } from 'formsy-react-components';
|
||||
import { registerComponent } from 'meteor/vulcan:core';
|
||||
import Form from 'react-bootstrap/lib/Form';
|
||||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||
|
||||
// copied from vulcan:forms/utils.js to avoid extra dependency
|
||||
const getFieldType = datatype => datatype && datatype[0].type;
|
||||
|
||||
const SelectComponent = ({refFunction, inputProperties, datatype, ...properties}, { intl }) => {
|
||||
const SelectComponent = ({ refFunction, inputProperties, itemProperties, datatype }, { intl }) => {
|
||||
const noneOption = {
|
||||
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
|
||||
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];
|
||||
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 = {
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
import React from 'react';
|
||||
import { intlShape } from 'meteor/vulcan:i18n';
|
||||
import { Select } from 'formsy-react-components';
|
||||
import { registerComponent } from 'meteor/vulcan:core';
|
||||
import Form from 'react-bootstrap/lib/Form';
|
||||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||
|
||||
const SelectMultipleComponent = ({refFunction, inputProperties, ...properties}, { intl }) => {
|
||||
const SelectMultipleComponent = ({ refFunction, inputProperties, itemProperties }, { intl }) => {
|
||||
inputProperties.multiple = true;
|
||||
|
||||
return <Select {...inputProperties} ref={refFunction}/>;
|
||||
return (
|
||||
<Components.FormItem {...inputProperties} {...itemProperties}>
|
||||
<Form.Control as="select" {...inputProperties} ref={refFunction} />
|
||||
</Components.FormItem>
|
||||
);
|
||||
};
|
||||
|
||||
SelectMultipleComponent.contextTypes = {
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
import React from 'react';
|
||||
import { registerComponent } from 'meteor/vulcan:core';
|
||||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||
|
||||
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 }) => (
|
||||
<div className="form-group row">
|
||||
<label className="control-label col-sm-3">{label}</label>
|
||||
<div className="col-sm-9">{parseUrl(value)}</div>
|
||||
</div>
|
||||
const StaticComponent = ({ inputProperties, itemProperties }) => (
|
||||
<Components.FormItem {...inputProperties} {...itemProperties}>
|
||||
<div>{parseUrl(inputProperties.value)}</div>
|
||||
</Components.FormItem>
|
||||
);
|
||||
|
||||
registerComponent('FormComponentStaticText', StaticComponent);
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import React from 'react';
|
||||
import { Textarea } from 'formsy-react-components';
|
||||
import { registerComponent } from 'meteor/vulcan:core';
|
||||
import Form from 'react-bootstrap/lib/Form';
|
||||
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);
|
|
@ -1,45 +1,34 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import DateTimePicker from 'react-datetime';
|
||||
import { registerComponent } from 'meteor/vulcan:core';
|
||||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||
|
||||
class Time extends PureComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
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) {
|
||||
// if this is a properly formatted moment date, update time
|
||||
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() {
|
||||
|
||||
const date = new Date();
|
||||
|
||||
// transform time string into date object to work inside datetimepicker
|
||||
const time = this.props.value;
|
||||
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 {
|
||||
date.setHours(0,0);
|
||||
date.setHours(0, 0);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="form-group row">
|
||||
<label className="control-label col-sm-3">{this.props.label}</label>
|
||||
<div className="col-sm-9">
|
||||
<Components.FormItem {...this.props.inputProperties} {...this.props.itemProperties}>
|
||||
<DateTimePicker
|
||||
value={date}
|
||||
viewMode="time"
|
||||
|
@ -47,10 +36,9 @@ class Time extends PureComponent {
|
|||
timeFormat="HH:mm"
|
||||
// newDate argument is a Moment object given by react-datetime
|
||||
onChange={newDate => this.updateDate(newDate)}
|
||||
inputProps={{name: this.props.name}}
|
||||
inputProps={{ name: this.props.name }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Components.FormItem>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import React from 'react';
|
||||
import { Input } from 'formsy-react-components';
|
||||
import { registerComponent } from 'meteor/vulcan:core';
|
||||
import Form from 'react-bootstrap/lib/Form';
|
||||
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);
|
|
@ -1,3 +1,4 @@
|
|||
import '../components/forms/FormElement.jsx';
|
||||
import '../components/forms/Checkbox.jsx';
|
||||
import '../components/forms/Checkboxgroup.jsx';
|
||||
import '../components/forms/Date.jsx';
|
||||
|
@ -15,6 +16,7 @@ import '../components/forms/Url.jsx';
|
|||
import '../components/forms/StaticText.jsx';
|
||||
import '../components/forms/FormComponentInner.jsx';
|
||||
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/Alert.jsx';
|
||||
|
|
Loading…
Add table
Reference in a new issue