mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Add generic React components
Includes moving some components from AssetAdmin
This commit is contained in:
parent
b0ba742c1f
commit
0ca090a391
@ -92,27 +92,27 @@ class CampaignAdmin extends LeftAndMain implements PermissionProvider {
|
|||||||
"customValidationMessage": "",
|
"customValidationMessage": "",
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"data": {
|
"data": {
|
||||||
'collectionReadUrl': {
|
"collectionReadUrl": {
|
||||||
'url': 'admin\/campaigns\/items',
|
"url": "admin\/campaigns\/items",
|
||||||
'method': 'GET'
|
"method": "GET"
|
||||||
},
|
},
|
||||||
'itemReadUrl': {
|
"itemReadUrl": {
|
||||||
'url': 'admin\/campaigns\/item\/:id',
|
"url": "admin\/campaigns\/item\/:id",
|
||||||
'method': 'GET'
|
"method": "GET"
|
||||||
},
|
},
|
||||||
'itemUpdateUrl': {
|
"itemUpdateUrl": {
|
||||||
'url': 'admin\/campaigns\/item\/:id',
|
"url": "admin\/campaigns\/item\/:id",
|
||||||
'method': 'PUT'
|
"method": "PUT"
|
||||||
},
|
},
|
||||||
'itemCreateUrl': {
|
"itemCreateUrl": {
|
||||||
'url': 'admin\/campaigns\/item\/:id',
|
"url": "admin\/campaigns\/item\/:id",
|
||||||
'method': 'POST'
|
"method": "POST"
|
||||||
},
|
},
|
||||||
'itemDeleteUrl': {
|
"itemDeleteUrl": {
|
||||||
'url': 'admin\/campaigns\/item\/:id',
|
"url": "admin\/campaigns\/item\/:id",
|
||||||
'method': 'DELETE'
|
"method": "DELETE"
|
||||||
},
|
},
|
||||||
'editFormSchemaUrl': 'admin\/campaigns\/schema\/DetailEditForm'
|
"editFormSchemaUrl": "admin\/campaigns\/schema\/DetailEditForm"
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"name": "SecurityID",
|
"name": "SecurityID",
|
||||||
|
@ -18,6 +18,7 @@ if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
|
|||||||
"ModelAdmin.REALLYDELETE": "Do you really want to delete?",
|
"ModelAdmin.REALLYDELETE": "Do you really want to delete?",
|
||||||
"ModelAdmin.DELETED": "Deleted",
|
"ModelAdmin.DELETED": "Deleted",
|
||||||
"ModelAdmin.VALIDATIONERROR": "Validation Error",
|
"ModelAdmin.VALIDATIONERROR": "Validation Error",
|
||||||
"LeftAndMain.PAGEWASDELETED": "This page was deleted. To edit a page, select it from the left."
|
"LeftAndMain.PAGEWASDELETED": "This page was deleted. To edit a page, select it from the left.",
|
||||||
|
"Campaigns.ADDCAMPAIGN": "Add campaign"
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -13,5 +13,6 @@
|
|||||||
"ModelAdmin.REALLYDELETE": "Do you really want to delete?",
|
"ModelAdmin.REALLYDELETE": "Do you really want to delete?",
|
||||||
"ModelAdmin.DELETED": "Deleted",
|
"ModelAdmin.DELETED": "Deleted",
|
||||||
"ModelAdmin.VALIDATIONERROR": "Validation Error",
|
"ModelAdmin.VALIDATIONERROR": "Validation Error",
|
||||||
"LeftAndMain.PAGEWASDELETED": "This page was deleted. To edit a page, select it from the left."
|
"LeftAndMain.PAGEWASDELETED": "This page was deleted. To edit a page, select it from the left.",
|
||||||
|
"Campaigns.ADDCAMPAIGN": "Add campaign"
|
||||||
}
|
}
|
@ -1,15 +0,0 @@
|
|||||||
import { Component } from 'react';
|
|
||||||
|
|
||||||
export default class SilverStripeComponent extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
37
admin/javascript/src/components/action/README.md
Normal file
37
admin/javascript/src/components/action/README.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Action
|
||||||
|
|
||||||
|
This component is used to display a button which is linked to an action.
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
### handleClick (function)
|
||||||
|
|
||||||
|
The handler for when a button is clicked, is passed the click event as the only argument.
|
||||||
|
|
||||||
|
### text (string)
|
||||||
|
|
||||||
|
The text to be shown in the button.
|
||||||
|
|
||||||
|
### type (string)
|
||||||
|
|
||||||
|
The type of button to be shown, adds a class to the button.
|
||||||
|
|
||||||
|
Accepted values are:
|
||||||
|
* 'danger'
|
||||||
|
* 'success'
|
||||||
|
* 'primary'
|
||||||
|
* 'link'
|
||||||
|
* 'secondary'
|
||||||
|
* 'complete'
|
||||||
|
|
||||||
|
### icon (string)
|
||||||
|
|
||||||
|
The icon to be used on the button, adds font-icon-{this.props.icon} class to the button. See available icons [here](../../../../fonts/incon-reference.html).
|
||||||
|
|
||||||
|
### loading (boolean)
|
||||||
|
|
||||||
|
If true, replaces the text/icon with a loading icon.
|
||||||
|
|
||||||
|
### disabled (boolean)
|
||||||
|
|
||||||
|
If true, gives the button a visually disabled state and disables click events.
|
109
admin/javascript/src/components/action/index.js
Normal file
109
admin/javascript/src/components/action/index.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||||
|
|
||||||
|
class ActionComponent extends SilverStripeComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleClick = this.handleClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<button className={this.getButtonClasses()} onClick={this.handleClick}>
|
||||||
|
{this.getLoadingIcon()}
|
||||||
|
{this.props.text}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the necessary button classes based on the given props
|
||||||
|
*
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
getButtonClasses() {
|
||||||
|
var buttonClasses = 'btn';
|
||||||
|
|
||||||
|
// If there is no text
|
||||||
|
if (typeof this.props.text === 'undefined') {
|
||||||
|
buttonClasses += ' no-text';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add 'type' class
|
||||||
|
if (this.props.type === 'danger') {
|
||||||
|
buttonClasses += ' btn-danger';
|
||||||
|
} else if (this.props.type === 'success') {
|
||||||
|
buttonClasses += ' btn-success';
|
||||||
|
} else if (this.props.type === 'primary') {
|
||||||
|
buttonClasses += ' btn-primary';
|
||||||
|
} else if (this.props.type === 'link') {
|
||||||
|
buttonClasses += ' btn-link';
|
||||||
|
} else if (this.props.type === 'secondary') {
|
||||||
|
buttonClasses += ' btn-secondary';
|
||||||
|
} else if (this.props.type === 'complete') {
|
||||||
|
buttonClasses += ' btn-success-outline';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add icon class
|
||||||
|
if (typeof this.props.icon !== 'undefined') {
|
||||||
|
buttonClasses += ` font-icon-${this.props.icon}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add loading class
|
||||||
|
if (this.props.loading === true) {
|
||||||
|
buttonClasses += ' btn--loading';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add disabled class
|
||||||
|
if (this.props.disabled === true) {
|
||||||
|
buttonClasses += ' disabled';
|
||||||
|
}
|
||||||
|
|
||||||
|
return buttonClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns markup for the loading icon
|
||||||
|
*
|
||||||
|
* @returns object|null
|
||||||
|
*/
|
||||||
|
getLoadingIcon() {
|
||||||
|
if (this.props.loading) {
|
||||||
|
return (
|
||||||
|
<div className="btn__loading-icon" >
|
||||||
|
<svg viewBox="0 0 44 12">
|
||||||
|
<circle cx="6" cy="6" r="6" />
|
||||||
|
<circle cx="22" cy="6" r="6" />
|
||||||
|
<circle cx="38" cy="6" r="6" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler triggered when a user clicks the button.
|
||||||
|
*
|
||||||
|
* @param object event
|
||||||
|
* @returns null
|
||||||
|
*/
|
||||||
|
handleClick(event) {
|
||||||
|
if (typeof this.props.handleClick === 'function' && this.props.disabled !== true) {
|
||||||
|
this.props.handleClick(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionComponent.propTypes = {
|
||||||
|
type: React.PropTypes.string,
|
||||||
|
icon: React.PropTypes.string,
|
||||||
|
text: React.PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ActionComponent;
|
123
admin/javascript/src/components/action/styles.scss
Normal file
123
admin/javascript/src/components/action/styles.scss
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
// General buttons
|
||||||
|
.btn {
|
||||||
|
height: 32px;
|
||||||
|
margin-right: 1rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Button icons
|
||||||
|
.btn[class*="font-icon-"]::before {
|
||||||
|
font-size: 16px;
|
||||||
|
position: relative;
|
||||||
|
top: 3px;
|
||||||
|
margin-right: 6px;
|
||||||
|
line-height: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-text[class*="font-icon-"]::before {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
margin-right: 1rem;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
border-left: 1px solid darken($btn-success-bg, 6%);
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SVG loading icon
|
||||||
|
.btn__loading-icon {
|
||||||
|
float: left;
|
||||||
|
margin: 0 4px 0 0;
|
||||||
|
height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: $btn-padding-y;
|
||||||
|
transform: translate(-50%);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 24px;
|
||||||
|
height: 20px;
|
||||||
|
|
||||||
|
circle {
|
||||||
|
width: 4px;
|
||||||
|
height: 5px;
|
||||||
|
animation: loading-icon 1.2s infinite ease-in-out both;
|
||||||
|
fill: $gray;
|
||||||
|
transform-origin: 50% 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
circle:nth-child(1) {
|
||||||
|
animation-delay: -.32s;
|
||||||
|
}
|
||||||
|
|
||||||
|
circle:nth-child(2) {
|
||||||
|
animation-delay: -.16s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn--loading {
|
||||||
|
> span,
|
||||||
|
&::before {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading-icon {
|
||||||
|
0%, 80%, 100% { transform: scale(0); }
|
||||||
|
40% { transform: scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specific button types
|
||||||
|
.btn-link {
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
border-color: transparent;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active,
|
||||||
|
&:focus {
|
||||||
|
background-color: $gray-lighter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success-outline {
|
||||||
|
border-color: lighten($brand-success,10%);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active,
|
||||||
|
&:focus {
|
||||||
|
color: $brand-success;
|
||||||
|
background-image: none;
|
||||||
|
background-color: transparent;
|
||||||
|
border-color: lighten($brand-success,10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg circle {
|
||||||
|
fill: $brand-success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
border-bottom-color: $btn-success-shadow;
|
||||||
|
|
||||||
|
svg circle {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
|||||||
|
|
17
admin/javascript/src/components/form-action/README.md
Normal file
17
admin/javascript/src/components/form-action/README.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# FormActionComponent
|
||||||
|
|
||||||
|
Used for form actions. For example a submit button.
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
### className
|
||||||
|
|
||||||
|
CSS class names to use on the button. Defaults to `btn btn-primary`
|
||||||
|
|
||||||
|
### label (required)
|
||||||
|
|
||||||
|
The text to display on the button.
|
||||||
|
|
||||||
|
### type
|
||||||
|
|
||||||
|
Used for the button's `type` attribute. Defaults to `button`
|
27
admin/javascript/src/components/form-action/index.js
Normal file
27
admin/javascript/src/components/form-action/index.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||||
|
|
||||||
|
class FormActionComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<button type={this.props.type} className={this.props.className}>
|
||||||
|
{this.props.label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
FormActionComponent.propTypes = {
|
||||||
|
className: React.PropTypes.string,
|
||||||
|
label: React.PropTypes.string.isRequired,
|
||||||
|
type: React.PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
FormActionComponent.defaultProps = {
|
||||||
|
className: 'btn btn-primary',
|
||||||
|
type: 'button'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormActionComponent;
|
23
admin/javascript/src/components/form-builder/README.md
Normal file
23
admin/javascript/src/components/form-builder/README.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# FormBuilderComponent
|
||||||
|
|
||||||
|
Used to generate forms, made up of field components and actions, from FormFieldSchema data.
|
||||||
|
|
||||||
|
This component will be moved to Framweork or CMS when dependency injection is implemented.
|
||||||
|
|
||||||
|
## PropTypes
|
||||||
|
|
||||||
|
### actions
|
||||||
|
|
||||||
|
Actions the component can dispatch. This should include but is not limited to:
|
||||||
|
|
||||||
|
#### setSchema
|
||||||
|
|
||||||
|
An action to call when the response from fetching schema data is returned. This would normally be a simple action to set the store's `schema` key to the returned data.
|
||||||
|
|
||||||
|
### formSchemaUrl
|
||||||
|
|
||||||
|
The schema URL where the form will be scaffolded from e.g. '/admin/pages/schema/1'.
|
||||||
|
|
||||||
|
### schema
|
||||||
|
|
||||||
|
JSON schema representing the form. Used as the blueprint for generating the form.
|
230
admin/javascript/src/components/form-builder/index.js
Normal file
230
admin/javascript/src/components/form-builder/index.js
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
import $ from 'jQuery';
|
||||||
|
import * as schemaActions from '../../state/schema/actions';
|
||||||
|
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||||
|
import FormComponent from '../form';
|
||||||
|
import TextField from '../text-field';
|
||||||
|
|
||||||
|
// Using this to map field types to components until we implement dependency injection.
|
||||||
|
var fakeInjector = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Components registered with the fake DI container.
|
||||||
|
*/
|
||||||
|
components: {
|
||||||
|
'TextField': TextField
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the component matching the passed component name.
|
||||||
|
* Used when a component type is provided bt the form schema.
|
||||||
|
*
|
||||||
|
* @param string componentName - The name of the component to get from the injector.
|
||||||
|
*
|
||||||
|
* @return object|null
|
||||||
|
*/
|
||||||
|
getComponentByName: function (componentName) {
|
||||||
|
return this.components[componentName];
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default data type to component mappings.
|
||||||
|
* Used as a fallback when no component type is provided in the form schema.
|
||||||
|
*
|
||||||
|
* @param string dataType - The data type provided by the form schema.
|
||||||
|
*
|
||||||
|
* @return object|null
|
||||||
|
*/
|
||||||
|
getComponentByDataType: function (dataType) {
|
||||||
|
switch (dataType) {
|
||||||
|
case 'String':
|
||||||
|
return this.components.TextField;
|
||||||
|
case 'Hidden':
|
||||||
|
return this.components.TextField;
|
||||||
|
case 'Text':
|
||||||
|
// Textarea field (not implemented)
|
||||||
|
return null;
|
||||||
|
case 'HTML':
|
||||||
|
// HTML editor field (not implemented)
|
||||||
|
return null;
|
||||||
|
case 'Integer':
|
||||||
|
// Numeric field (not implemented)
|
||||||
|
return null;
|
||||||
|
case 'Decimal':
|
||||||
|
// Numeric field (not implemented)
|
||||||
|
return null;
|
||||||
|
case 'MultiSelect':
|
||||||
|
// Radio field (not implemented)
|
||||||
|
return null;
|
||||||
|
case 'SingleSelect':
|
||||||
|
// Dropdown field (not implemented)
|
||||||
|
return null;
|
||||||
|
case 'Date':
|
||||||
|
// DateTime field (not implemented)
|
||||||
|
return null;
|
||||||
|
case 'DateTime':
|
||||||
|
// DateTime field (not implemented)
|
||||||
|
return null;
|
||||||
|
case 'Time':
|
||||||
|
// DateTime field (not implemented)
|
||||||
|
return null;
|
||||||
|
case 'Boolean':
|
||||||
|
// Checkbox field (not implemented)
|
||||||
|
return null;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FormBuilderComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.formSchemaPromise = null;
|
||||||
|
this.isFetching = false;
|
||||||
|
|
||||||
|
this.fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches data used to generate a form. This can be form schema and or form state data.
|
||||||
|
* When the response comes back the data is saved to state.
|
||||||
|
*
|
||||||
|
* @param boolean schema - If form schema data should be returned in the response.
|
||||||
|
* @param boolean state - If form state data should be returned in the response.
|
||||||
|
*
|
||||||
|
* @return object - Promise from the AJAX request.
|
||||||
|
*/
|
||||||
|
fetch(schema = true, state = false) {
|
||||||
|
var headerValues = [];
|
||||||
|
|
||||||
|
if (this.isFetching === true) {
|
||||||
|
return this.formSchemaPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema === true) {
|
||||||
|
headerValues.push('schema');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state === true) {
|
||||||
|
headerValues.push('state');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.formSchemaPromise = $.ajax({
|
||||||
|
method: 'GET',
|
||||||
|
headers: { 'X-FormSchema-Request': headerValues.join() },
|
||||||
|
url: this.props.formSchemaUrl
|
||||||
|
}).done((data, status, xhr) => {
|
||||||
|
this.isFetching = false;
|
||||||
|
this.props.actions.setSchema(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.isFetching = true;
|
||||||
|
|
||||||
|
return this.formSchemaPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets form schema for the FormBuilder.
|
||||||
|
*
|
||||||
|
* @return object|undefined
|
||||||
|
*/
|
||||||
|
getFormSchema() {
|
||||||
|
return this.props.schema.forms.find(function (form) {
|
||||||
|
return form.schema.schema_url === this.props.formSchemaUrl;
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a list of schema fields to their React Component.
|
||||||
|
* Only top level form fields are handled here, composite fields (TabSets etc),
|
||||||
|
* are responsible for mapping and rendering their children.
|
||||||
|
*
|
||||||
|
* @param array fields
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
mapFieldsToComponents(fields) {
|
||||||
|
return fields.map((field, i) => {
|
||||||
|
|
||||||
|
const Component = field.component !== null
|
||||||
|
? fakeInjector.getComponentByName(field.component)
|
||||||
|
: fakeInjector.getComponentByDataType(field.type);
|
||||||
|
|
||||||
|
if (Component === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Props which every form field receives.
|
||||||
|
let props = {
|
||||||
|
attributes: field.attributes,
|
||||||
|
data: field.data,
|
||||||
|
description: field.description,
|
||||||
|
extraClass: field.extraClass,
|
||||||
|
fields: field.children,
|
||||||
|
id: field.id,
|
||||||
|
name: field.name
|
||||||
|
};
|
||||||
|
|
||||||
|
// Structural fields (like TabSets) are not posted back to the server and don't receive some props.
|
||||||
|
if (field.type !== 'Structural') {
|
||||||
|
props.rightTitle = field.rightTitle;
|
||||||
|
props.leftTitle = field.leftTitle;
|
||||||
|
props.readOnly = field.readOnly;
|
||||||
|
props.disabled = field.disabled;
|
||||||
|
props.customValidationMessage = field.customValidationMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dropdown and Radio fields need some options...
|
||||||
|
if (field.type === 'MultiSelect' || field.type === 'SingleSelect') {
|
||||||
|
props.source = field.source;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Component key={i} {...props} />
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
// If the response from fetching the initial data
|
||||||
|
// hasn't come back yet, don't render anything.
|
||||||
|
if (this.props.schema.forms.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = this.getFormSchema().schema;
|
||||||
|
|
||||||
|
const formProps = {
|
||||||
|
actions: schema.actions,
|
||||||
|
attributes: schema.attributes,
|
||||||
|
data: schema.data,
|
||||||
|
fields: schema.fields,
|
||||||
|
mapFieldsToComponents: this.mapFieldsToComponents
|
||||||
|
};
|
||||||
|
|
||||||
|
return <FormComponent {...formProps} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormBuilderComponent.propTypes = {
|
||||||
|
actions: React.PropTypes.object.isRequired,
|
||||||
|
formSchemaUrl: React.PropTypes.string.isRequired,
|
||||||
|
schema: React.PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
schema: state.schema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return {
|
||||||
|
actions: bindActionCreators(schemaActions, dispatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(FormBuilderComponent);
|
@ -0,0 +1,31 @@
|
|||||||
|
jest.unmock('../../../SilverStripeComponent');
|
||||||
|
jest.unmock('../');
|
||||||
|
|
||||||
|
import { FormBuilderComponent } from '../';
|
||||||
|
|
||||||
|
describe('FormBuilderComponent', () => {
|
||||||
|
|
||||||
|
describe('getFormSchema()', () => {
|
||||||
|
|
||||||
|
var formBuilder;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const props = {
|
||||||
|
store: {
|
||||||
|
getState: () => {}
|
||||||
|
},
|
||||||
|
actions: {},
|
||||||
|
formSchemaUrl: 'admin/assets/schema/1',
|
||||||
|
schema: { forms: [{ schema: { id: '1', schema_url: 'admin/assets/schema/1' } }] }
|
||||||
|
};
|
||||||
|
|
||||||
|
formBuilder = new FormBuilderComponent(props);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the form schema for the FormBuilder', () => {
|
||||||
|
const form = formBuilder.getFormSchema();
|
||||||
|
expect(form.schema.id).toBe('1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
37
admin/javascript/src/components/form/README.md
Normal file
37
admin/javascript/src/components/form/README.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# FormComponent
|
||||||
|
|
||||||
|
The FormComponent is used to render forms in SilverStripe. The only time you should need to use `FormComponent` directly is when you're composing custom layouts. Forms can be automatically generated from a schema using the `FormBuilder` component.
|
||||||
|
|
||||||
|
This component should be moved to Framework when dependency injection is implemented.
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
### actions (required)
|
||||||
|
|
||||||
|
A list of objects representing the form actions. For example the submit button.
|
||||||
|
|
||||||
|
### attributes (required)
|
||||||
|
|
||||||
|
An object of HTML attributes for the form. For example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
action: 'admin/assets/EditForm',
|
||||||
|
class: 'cms-edit-form root-form AssetAdmin LeftAndMain',
|
||||||
|
enctype: 'multipart/form-data',
|
||||||
|
id: 'Form_EditForm',
|
||||||
|
method: 'POST'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### data
|
||||||
|
|
||||||
|
Ad hoc data passed to the front-end from the server.
|
||||||
|
|
||||||
|
### fields (required)
|
||||||
|
|
||||||
|
A list of field objects to display in the form. These objects should be transformed to Components using the `this.props.mapFieldsToComponents` method.
|
||||||
|
|
||||||
|
### mapFieldsToComponents (required)
|
||||||
|
|
||||||
|
A function that maps each schema field (`this.props.fields`) to the component responsibe for render it.
|
59
admin/javascript/src/components/form/index.js
Normal file
59
admin/javascript/src/components/form/index.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||||
|
import FormActionComponent from '../form-action';
|
||||||
|
|
||||||
|
class FormComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the components responsible for perfoming actions on the form.
|
||||||
|
* For example form submission.
|
||||||
|
*
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
getFormActionComponents() {
|
||||||
|
return this.props.actions.map((action) => {
|
||||||
|
return <FormActionComponent {...action} />;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const attr = this.props.attributes;
|
||||||
|
const fields = this.props.mapFieldsToComponents(this.props.fields);
|
||||||
|
const actions = this.getFormActionComponents();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form id={attr.id} className={attr.className} encType={attr.enctype} method={attr.method} action={attr.action}>
|
||||||
|
{fields &&
|
||||||
|
<fieldset className='form-group'>
|
||||||
|
{fields}
|
||||||
|
</fieldset>
|
||||||
|
}
|
||||||
|
|
||||||
|
{actions &&
|
||||||
|
<div className='actions-fix-btm'>
|
||||||
|
<div className='btn-group' role='group'>
|
||||||
|
{actions}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
FormComponent.propTypes = {
|
||||||
|
actions: React.PropTypes.array.isRequired,
|
||||||
|
attributes: React.PropTypes.shape({
|
||||||
|
action: React.PropTypes.string.isRequired,
|
||||||
|
className: React.PropTypes.string.isRequired,
|
||||||
|
enctype: React.PropTypes.string.isRequired,
|
||||||
|
id: React.PropTypes.string.isRequired,
|
||||||
|
method: React.PropTypes.string.isRequired
|
||||||
|
}),
|
||||||
|
data: React.PropTypes.array,
|
||||||
|
fields: React.PropTypes.array.isRequired,
|
||||||
|
mapFieldsToComponents: React.PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormComponent;
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SilverStripeComponent from 'silverstripe-component';
|
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||||
|
|
||||||
class GridFieldCellComponent extends SilverStripeComponent {
|
class GridFieldCellComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SilverStripeComponent from 'silverstripe-component';
|
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||||
|
|
||||||
class GridFieldHeaderCellComponent extends SilverStripeComponent {
|
class GridFieldHeaderCellComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SilverStripeComponent from 'silverstripe-component';
|
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||||
import GridFieldRowComponent from '../grid-field-row';
|
import GridFieldRowComponent from '../grid-field-row';
|
||||||
|
|
||||||
class GridFieldHeaderComponent extends SilverStripeComponent {
|
class GridFieldHeaderComponent extends SilverStripeComponent {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SilverStripeComponent from 'silverstripe-component';
|
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||||
|
|
||||||
class GridFieldRowComponent extends SilverStripeComponent {
|
class GridFieldRowComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SilverStripeComponent from 'silverstripe-component';
|
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||||
|
|
||||||
class GridFieldTableComponent extends SilverStripeComponent {
|
class GridFieldTableComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
|
48
admin/javascript/src/components/hidden-field/index.js
Normal file
48
admin/javascript/src/components/hidden-field/index.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||||
|
|
||||||
|
class HiddenFieldComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className='field hidden'>
|
||||||
|
<input {...this.getInputProps()} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getInputProps() {
|
||||||
|
return {
|
||||||
|
className: ['hidden', this.props.extraClass].join(' '),
|
||||||
|
id: this.props.name,
|
||||||
|
name: this.props.name,
|
||||||
|
onChange: this.props.onChange,
|
||||||
|
type: 'hidden',
|
||||||
|
value: this.props.value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(event) {
|
||||||
|
if (typeof this.props.onChange === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HiddenFieldComponent.propTypes = {
|
||||||
|
label: React.PropTypes.string,
|
||||||
|
extraClass: React.PropTypes.string,
|
||||||
|
name: React.PropTypes.string.isRequired,
|
||||||
|
onChange: React.PropTypes.func,
|
||||||
|
value: React.PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HiddenFieldComponent;
|
21
admin/javascript/src/components/hidden-field/readme.md
Normal file
21
admin/javascript/src/components/hidden-field/readme.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Hidden Field Component
|
||||||
|
|
||||||
|
Generates an `<input type="hidden" />`
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
### extraClass
|
||||||
|
|
||||||
|
Addition CSS classes to apply to the `<input>` element.
|
||||||
|
|
||||||
|
### name (required)
|
||||||
|
|
||||||
|
Used for the field's `name` attribute.
|
||||||
|
|
||||||
|
### onChange
|
||||||
|
|
||||||
|
Handler function called when the field's value changes.
|
||||||
|
|
||||||
|
### value
|
||||||
|
|
||||||
|
The field's value.
|
3
admin/javascript/src/components/hidden-field/styles.scss
Normal file
3
admin/javascript/src/components/hidden-field/styles.scss
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.field.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SilverStripeComponent from 'silverstripe-component';
|
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||||
|
|
||||||
class NorthHeaderBreadcrumbsComponent extends SilverStripeComponent {
|
class NorthHeaderBreadcrumbsComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import NorthHeaderBreadcrumbsComponent from '../north-header-breadcrumbs';
|
import NorthHeaderBreadcrumbsComponent from '../north-header-breadcrumbs';
|
||||||
import SilverStripeComponent from 'silverstripe-component';
|
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||||
|
|
||||||
class NorthHeaderComponent extends SilverStripeComponent {
|
class NorthHeaderComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
|
55
admin/javascript/src/components/text-field/index.js
Normal file
55
admin/javascript/src/components/text-field/index.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||||
|
|
||||||
|
class TextFieldComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className='field text'>
|
||||||
|
{this.props.label &&
|
||||||
|
<label className='left' htmlFor={'gallery_' + this.props.name}>
|
||||||
|
{this.props.label}
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
<div className='middleColumn'>
|
||||||
|
<input {...this.getInputProps()} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getInputProps() {
|
||||||
|
return {
|
||||||
|
className: ['text', this.props.extraClass].join(' '),
|
||||||
|
id: `gallery_${this.props.name}`,
|
||||||
|
name: this.props.name,
|
||||||
|
onChange: this.props.onChange,
|
||||||
|
type: 'text',
|
||||||
|
value: this.props.value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(event) {
|
||||||
|
if (typeof this.props.onChange === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextFieldComponent.propTypes = {
|
||||||
|
label: React.PropTypes.string,
|
||||||
|
extraClass: React.PropTypes.string,
|
||||||
|
name: React.PropTypes.string.isRequired,
|
||||||
|
onChange: React.PropTypes.func,
|
||||||
|
value: React.PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TextFieldComponent;
|
25
admin/javascript/src/components/text-field/readme.md
Normal file
25
admin/javascript/src/components/text-field/readme.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Text Field Component
|
||||||
|
|
||||||
|
Generates an editable text field.
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
### label
|
||||||
|
|
||||||
|
The label text to display with the field.
|
||||||
|
|
||||||
|
### extraClass
|
||||||
|
|
||||||
|
Addition CSS classes to apply to the `<input>` element.
|
||||||
|
|
||||||
|
### name (required)
|
||||||
|
|
||||||
|
Used for the field's `name` attribute.
|
||||||
|
|
||||||
|
### onChange
|
||||||
|
|
||||||
|
Handler function called when the field's value changes.
|
||||||
|
|
||||||
|
### value
|
||||||
|
|
||||||
|
The field's value.
|
@ -0,0 +1,37 @@
|
|||||||
|
jest.unmock('react');
|
||||||
|
jest.unmock('react-addons-test-utils');
|
||||||
|
jest.unmock('../');
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import ReactTestUtils from 'react-addons-test-utils';
|
||||||
|
import TextFieldComponent from '../';
|
||||||
|
|
||||||
|
describe('TextFieldComponent', function() {
|
||||||
|
|
||||||
|
var props;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
props = {
|
||||||
|
label: '',
|
||||||
|
name: '',
|
||||||
|
value: '',
|
||||||
|
onChange: jest.genMockFunction()
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('handleChange()', function () {
|
||||||
|
var textField;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
textField = ReactTestUtils.renderIntoDocument(
|
||||||
|
<TextFieldComponent {...props} />
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call the onChange function on props', function () {
|
||||||
|
textField.handleChange();
|
||||||
|
|
||||||
|
expect(textField.props.onChange.mock.calls.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,20 +1,39 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import SilverStripeComponent from 'silverstripe-component';
|
import SilverStripeComponent from 'silverstripe-component';
|
||||||
import NorthHeader from '../../components/north-header';
|
import ActionButton from 'action-button';
|
||||||
import GridField from '../grid-field';
|
import i18n from 'i18n';
|
||||||
|
import NorthHeader from 'north-header';
|
||||||
|
import GridField from 'grid-field';
|
||||||
|
|
||||||
class CampaignAdminContainer extends SilverStripeComponent {
|
class CampaignAdminContainer extends SilverStripeComponent {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.addCampaign = this.addCampaign.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<NorthHeader></NorthHeader>
|
<NorthHeader />
|
||||||
<GridField></GridField>
|
|
||||||
|
<ActionButton
|
||||||
|
text={i18n._t('Campaigns.ADDCAMPAIGN')}
|
||||||
|
type={'secondary'}
|
||||||
|
icon={'plus-circled'}
|
||||||
|
handleClick={this.addCampaign} />
|
||||||
|
|
||||||
|
<GridField />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addCampaign() {
|
||||||
|
//Add campaign
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CampaignAdminContainer.propTypes = {
|
CampaignAdminContainer.propTypes = {
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
.CampaignAdmin {
|
|
||||||
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SilverStripeComponent from 'silverstripe-component';
|
import SilverStripeComponent from '../../SilverStripeComponent';
|
||||||
import GridFieldTable from '../../components/grid-field-table';
|
import GridFieldTable from '../../components/grid-field-table';
|
||||||
import GridFieldHeader from '../../components/grid-field-header';
|
import GridFieldHeader from '../../components/grid-field-header';
|
||||||
import GridFieldHeaderCell from '../../components/grid-field-header-cell';
|
import GridFieldHeaderCell from '../../components/grid-field-header-cell';
|
||||||
|
5
admin/javascript/src/state/schema/README.md
Normal file
5
admin/javascript/src/state/schema/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Schema state
|
||||||
|
|
||||||
|
Manages state associated with the FormFieldSchema.
|
||||||
|
|
||||||
|
When dependency injection is implemented, this will be moved into either Framework or CMS. We can't moveit there sooner because there is no way of extending state.
|
5
admin/javascript/src/state/schema/action-types.js
Normal file
5
admin/javascript/src/state/schema/action-types.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const ACTION_TYPES = {
|
||||||
|
SET_SCHEMA: 'SET_SCHEMA'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ACTION_TYPES;
|
15
admin/javascript/src/state/schema/actions.js
Normal file
15
admin/javascript/src/state/schema/actions.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import ACTION_TYPES from './action-types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the schema being used to generate the curent layout.
|
||||||
|
*
|
||||||
|
* @param string schema - JSON schema for the layout.
|
||||||
|
*/
|
||||||
|
export function setSchema(schema) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
return dispatch ({
|
||||||
|
type: ACTION_TYPES.SET_SCHEMA,
|
||||||
|
payload: schema
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
33
admin/javascript/src/state/schema/reducer.js
Normal file
33
admin/javascript/src/state/schema/reducer.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import deepFreeze from 'deep-freeze';
|
||||||
|
import ACTION_TYPES from './action-types';
|
||||||
|
|
||||||
|
const initialState = deepFreeze({
|
||||||
|
forms: []
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function schemaReducer(state = initialState, action = null) {
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case ACTION_TYPES.SET_SCHEMA:
|
||||||
|
if (state.forms.length === 0) {
|
||||||
|
return deepFreeze(Object.assign({}, state, { forms: [action.payload] }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the form which has a matching `schema.id` property.
|
||||||
|
return deepFreeze(Object.assign({}, state, {
|
||||||
|
forms: state.forms.map((form) => {
|
||||||
|
if (form.schema.id === action.payload.schema.id) {
|
||||||
|
// Only replace the `schema` key incase other actions have updated other keys.
|
||||||
|
return Object.assign({}, form, action.payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
return form;
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
72
admin/javascript/src/state/schema/tests/reducer-test.js
Normal file
72
admin/javascript/src/state/schema/tests/reducer-test.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
jest.unmock('deep-freeze');
|
||||||
|
jest.unmock('../action-types.js');
|
||||||
|
jest.unmock('../reducer.js');
|
||||||
|
|
||||||
|
import schemaReducer from '../reducer.js';
|
||||||
|
import ACTION_TYPES from '../action-types';
|
||||||
|
|
||||||
|
describe('schemaReducer', () => {
|
||||||
|
|
||||||
|
describe('SET_SCHEMA', () => {
|
||||||
|
|
||||||
|
it('should create a new form when none exist', () => {
|
||||||
|
const initialState = { forms: [] };
|
||||||
|
const serverResponse = { id : 'TestForm' };
|
||||||
|
|
||||||
|
const nextState = schemaReducer(initialState, {
|
||||||
|
type: ACTION_TYPES.SET_SCHEMA,
|
||||||
|
payload: { schema: serverResponse }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(nextState.forms.length).toBe(1);
|
||||||
|
expect(JSON.stringify(nextState.forms[0])).toBe(JSON.stringify({ schema: { id: 'TestForm' } }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update an existing form', () => {
|
||||||
|
const initialState = {
|
||||||
|
forms: [{
|
||||||
|
schema: {
|
||||||
|
id: 'TestForm',
|
||||||
|
name: 'TestForm'
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
const serverResponse = { id: 'TestForm', name: 'BetterTestForm' };
|
||||||
|
|
||||||
|
const nextState = schemaReducer(initialState, {
|
||||||
|
type: ACTION_TYPES.SET_SCHEMA,
|
||||||
|
payload: { schema: serverResponse }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(nextState.forms.length).toBe(1);
|
||||||
|
expect(JSON.stringify(nextState.forms[0])).toBe(JSON.stringify({ schema: { id: 'TestForm', name: 'BetterTestForm' } }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should only update the the form's 'schema' key", () => {
|
||||||
|
const initialState = {
|
||||||
|
forms: [{
|
||||||
|
schema: {
|
||||||
|
id: 'TestForm',
|
||||||
|
name: 'TestForm'
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
error: 'Oops!'
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
const serverResponse = { id: 'TestForm', name: 'BetterTestForm' };
|
||||||
|
|
||||||
|
const nextState = schemaReducer(initialState, {
|
||||||
|
type: ACTION_TYPES.SET_SCHEMA,
|
||||||
|
payload: { schema: serverResponse }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(nextState.forms.length).toBe(1);
|
||||||
|
expect(JSON.stringify(nextState.forms[0])).toBe(JSON.stringify({ schema: { id: 'TestForm', name: 'BetterTestForm' }, state: { error: 'Oops!' } }));
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -13,3 +13,5 @@
|
|||||||
@import "../components/grid-field-row/styles";
|
@import "../components/grid-field-row/styles";
|
||||||
@import "../components/north-header/styles";
|
@import "../components/north-header/styles";
|
||||||
@import "../components/north-header-breadcrumbs/styles";
|
@import "../components/north-header-breadcrumbs/styles";
|
||||||
|
@import "../components/action/styles";
|
||||||
|
@import "../components/hidden-field/styles";
|
||||||
|
@ -326,7 +326,7 @@ form.small .field, .field.small {
|
|||||||
.chzn-container-single .chzn-single {
|
.chzn-container-single .chzn-single {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
line-height: 30px; /* not relative, as then we'd had to redo most of chzn */
|
line-height: 30px; /* not relative, as then we'd had to redo most of chzn */
|
||||||
font-size: $font-base-size;
|
font-size: $font-size-root;
|
||||||
background-image: linear-gradient(#efefef, #fff 10%, #fff 90%, #efefef);
|
background-image: linear-gradient(#efefef, #fff 10%, #fff 90%, #efefef);
|
||||||
|
|
||||||
&:hover, &:focus, &:active {
|
&:hover, &:focus, &:active {
|
||||||
@ -524,7 +524,7 @@ form.small .field, .field.small {
|
|||||||
|
|
||||||
&.ss-ui-button-small {
|
&.ss-ui-button-small {
|
||||||
.ui-button-text {
|
.ui-button-text {
|
||||||
font-size: $font-base-size - 2;
|
font-size: $font-size-sm;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,6 @@
|
|||||||
padding: $grid-y*1.5 8px;
|
padding: $grid-y*1.5 8px;
|
||||||
position: relative;
|
position: relative;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-size: $font-base-size;
|
|
||||||
transition: padding .2s;
|
transition: padding .2s;
|
||||||
min-height: 52px;
|
min-height: 52px;
|
||||||
transition: padding .2s;
|
transition: padding .2s;
|
||||||
@ -55,7 +54,6 @@
|
|||||||
|
|
||||||
span {
|
span {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: $font-base-size;
|
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
padding: 6px 0;
|
padding: 6px 0;
|
||||||
margin-left: 32px;
|
margin-left: 32px;
|
||||||
@ -65,7 +63,7 @@
|
|||||||
.cms-login-status {
|
.cms-login-status {
|
||||||
padding: $grid-y*1.5 8px;
|
padding: $grid-y*1.5 8px;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
font-size: $font-base-size - 1;
|
font-size: $font-size-sm;
|
||||||
min-height: 28px;
|
min-height: 28px;
|
||||||
transition: padding .2s;
|
transition: padding .2s;
|
||||||
|
|
||||||
@ -278,7 +276,6 @@
|
|||||||
display: block;
|
display: block;
|
||||||
line-height: $grid-y * 2;
|
line-height: $grid-y * 2;
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
font-size: $font-base-size;
|
|
||||||
color: $color-text-default;
|
color: $color-text-default;
|
||||||
padding: (2 * $grid-y + 1) 5px (2 * $grid-y + 1) 8px;
|
padding: (2 * $grid-y + 1) 5px (2 * $grid-y + 1) 8px;
|
||||||
background-color: $base-menu-bg;
|
background-color: $base-menu-bg;
|
||||||
|
@ -127,11 +127,11 @@ Used in side panels and action tabs
|
|||||||
line-height: $grid-y * 2;
|
line-height: $grid-y * 2;
|
||||||
}
|
}
|
||||||
h3 {
|
h3 {
|
||||||
font-size: $font-base-size + 1;
|
font-size: $font-size-root;
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
font-size: $font-base-size;
|
font-size: $font-size-root -1;
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,8 +69,6 @@
|
|||||||
|
|
||||||
// Reset the font and vertical alignment.
|
// Reset the font and vertical alignment.
|
||||||
@mixin reset-font {
|
@mixin reset-font {
|
||||||
font: inherit;
|
|
||||||
font-size: 100%;
|
|
||||||
vertical-align: baseline; }
|
vertical-align: baseline; }
|
||||||
|
|
||||||
// Resets the outline when focus.
|
// Resets the outline when focus.
|
||||||
|
@ -37,11 +37,6 @@ body.cms {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body .ui-widget {
|
|
||||||
font-family: $font-family;
|
|
||||||
font-size: $font-base-size;
|
|
||||||
}
|
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
@ -111,7 +106,7 @@ body.cms {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: $font-base-size + 2;
|
font-size: $font-size-h4;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-bottom: $grid-x;
|
margin-bottom: $grid-x;
|
||||||
@ -1109,11 +1104,11 @@ body.cms {
|
|||||||
line-height: $grid-y * 2;
|
line-height: $grid-y * 2;
|
||||||
}
|
}
|
||||||
h3 {
|
h3 {
|
||||||
font-size: $font-base-size + 1;
|
font-size: $font-size-h5;
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
font-size: $font-base-size;
|
font-size: $font-size-h5;
|
||||||
margin:5px 0;
|
margin:5px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1131,7 +1126,7 @@ body.cms {
|
|||||||
label {
|
label {
|
||||||
float: none;
|
float: none;
|
||||||
width: auto;
|
width: auto;
|
||||||
font-size: $font-base-size;
|
font-size: $font-size-root;
|
||||||
padding: 0 $grid-x 4px 0;
|
padding: 0 $grid-x 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1441,7 +1436,7 @@ form.member-profile-form {
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
.toggle {
|
.toggle {
|
||||||
font-size: $font-base-size - 1;
|
font-size: $font-size-sm;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1669,7 +1664,7 @@ form.member-profile-form {
|
|||||||
|
|
||||||
// Titlebar for pop-up dialog.
|
// Titlebar for pop-up dialog.
|
||||||
.ui-dialog-titlebar.ui-widget-header {
|
.ui-dialog-titlebar.ui-widget-header {
|
||||||
font-size: $font-base-size+2;
|
font-size: $font-size-root +1;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border:none;
|
border:none;
|
||||||
background: transparent url(../images/textures/cms_content_header.png) repeat;
|
background: transparent url(../images/textures/cms_content_header.png) repeat;
|
||||||
@ -2019,7 +2014,7 @@ body.cms-dialog {
|
|||||||
|
|
||||||
.flyout {
|
.flyout {
|
||||||
height: 26px - 2*4px; // minus padding
|
height: 26px - 2*4px; // minus padding
|
||||||
font-size: $font-base-size+2;
|
font-size: $font-size-root +1;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border-top-left-radius: 3px;
|
border-top-left-radius: 3px;
|
||||||
border-bottom-left-radius: 3px;
|
border-bottom-left-radius: 3px;
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
* Contains the basic typography related styles for the admin interface.
|
* Contains the basic typography related styles for the admin interface.
|
||||||
*/
|
*/
|
||||||
body, html {
|
body, html {
|
||||||
font-size: $font-base-size;
|
|
||||||
line-height: $grid-y * 2;
|
line-height: $grid-y * 2;
|
||||||
font-family: $font-family;
|
font-family: $font-family;
|
||||||
color: $color-text;
|
color: $color-text;
|
||||||
@ -18,22 +17,9 @@ body, html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: $font-base-size + 6;
|
|
||||||
line-height: $grid-y * 3;
|
line-height: $grid-y * 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: $font-base-size + 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-size: $font-base-size + 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
font-size: $font-base-size;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
p {
|
||||||
line-height: $grid-y * 2;
|
line-height: $grid-y * 2;
|
||||||
margin-bottom: $grid-y * 2;
|
margin-bottom: $grid-y * 2;
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
.ui-widget-content,
|
.ui-widget-content,
|
||||||
.ui-widget {
|
.ui-widget {
|
||||||
color: $color-text;
|
color: $color-text;
|
||||||
font-size: $font-base-size;
|
font-size: 1em;
|
||||||
font-family: $font-family;
|
font-family: $font-family;
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
@ -32,8 +32,6 @@
|
|||||||
text-shadow: lighten($color-base, 10%) 1px 1px 0;
|
text-shadow: lighten($color-base, 10%) 1px 1px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
& a.ui-dialog-titlebar-close {
|
& a.ui-dialog-titlebar-close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -5px;
|
top: -5px;
|
||||||
@ -65,15 +63,6 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-widget input,
|
|
||||||
.ui-widget select,
|
|
||||||
.ui-widget textarea,
|
|
||||||
.ui-widget button {
|
|
||||||
color: $color-text;
|
|
||||||
font-size: $font-base-size;
|
|
||||||
font-family: $font-family;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui-accordion {
|
.ui-accordion {
|
||||||
.ui-accordion-header {
|
.ui-accordion-header {
|
||||||
border-color: $color-button-generic-border;
|
border-color: $color-button-generic-border;
|
||||||
|
@ -26,17 +26,17 @@
|
|||||||
//
|
//
|
||||||
// Grayscale and brand colors for use across Bootstrap.
|
// Grayscale and brand colors for use across Bootstrap.
|
||||||
|
|
||||||
// $gray-dark: #373a3c;
|
$gray-dark: #4f5861;
|
||||||
// $gray: #55595c;
|
$gray: #55595c;
|
||||||
// $gray-light: #818a91;
|
$gray-light: #d3d9dd;
|
||||||
$gray-lighter: #e8e9ea;
|
$gray-lighter: #e8e9ea;
|
||||||
// $gray-lightest: #f7f7f9;
|
// $gray-lightest: #f7f7f9;
|
||||||
//
|
//
|
||||||
// $brand-primary: #0275d8;
|
// $brand-primary: #0275d8;
|
||||||
// $brand-success: #5cb85c;
|
$brand-success: #3fa142;
|
||||||
// $brand-info: #5bc0de;
|
// $brand-info: #5bc0de;
|
||||||
// $brand-warning: #f0ad4e;
|
// $brand-warning: #f0ad4e;
|
||||||
// $brand-danger: #d9534f;
|
$brand-danger: #D40404;
|
||||||
|
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
@ -142,26 +142,26 @@ $gray-lighter: #e8e9ea;
|
|||||||
//
|
//
|
||||||
// Font, line-height, and color for body text, headings, and more.
|
// Font, line-height, and color for body text, headings, and more.
|
||||||
|
|
||||||
// $font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
$font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
// $font-family-serif: Georgia, "Times New Roman", Times, serif;
|
$font-family-serif: Georgia, "Times New Roman", Times, serif;
|
||||||
// $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
|
$font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||||
// $font-family-base: $font-family-sans-serif;
|
$font-family-base: $font-family-sans-serif;
|
||||||
|
|
||||||
// Pixel value used to responsively scale all typography. Applied to the `<html>` element.
|
// Pixel value used to responsively scale all typography. Applied to the `<html>` element.
|
||||||
// $font-size-root: 16px;
|
$font-size-root: 13px;
|
||||||
//
|
|
||||||
// $font-size-base: 1rem;
|
$font-size-base: 1rem;
|
||||||
// $font-size-lg: 1.25rem;
|
$font-size-lg: 1.23rem; /* 16px */
|
||||||
// $font-size-sm: .875rem;
|
$font-size-sm: .846rem; /* 11px */
|
||||||
// $font-size-xs: .75rem;
|
$font-size-xs: .769rem; /* 10px */
|
||||||
//
|
|
||||||
// $font-size-h1: 2.5rem;
|
$font-size-h1: 2.5rem;
|
||||||
// $font-size-h2: 2rem;
|
$font-size-h2: 18px; /* 2rem; */
|
||||||
// $font-size-h3: 1.75rem;
|
$font-size-h3: 16px; /* 1.75rem; */
|
||||||
// $font-size-h4: 1.5rem;
|
$font-size-h4: 14px; /* 1.5rem; */
|
||||||
// $font-size-h5: 1.25rem;
|
$font-size-h5: 13px; /* 1.25rem; */
|
||||||
// $font-size-h6: 1rem;
|
$font-size-h6: 1rem;
|
||||||
//
|
|
||||||
// $display1-size: 6rem;
|
// $display1-size: 6rem;
|
||||||
// $display2-size: 5.5rem;
|
// $display2-size: 5.5rem;
|
||||||
// $display3-size: 4.5rem;
|
// $display3-size: 4.5rem;
|
||||||
@ -171,9 +171,9 @@ $gray-lighter: #e8e9ea;
|
|||||||
// $display2-weight: 300;
|
// $display2-weight: 300;
|
||||||
// $display3-weight: 300;
|
// $display3-weight: 300;
|
||||||
// $display4-weight: 300;
|
// $display4-weight: 300;
|
||||||
//
|
|
||||||
// $line-height: 1.5;
|
$line-height: 1.538;
|
||||||
//
|
|
||||||
// $headings-margin-bottom: ($spacer / 2);
|
// $headings-margin-bottom: ($spacer / 2);
|
||||||
// $headings-font-family: inherit;
|
// $headings-font-family: inherit;
|
||||||
// $headings-font-weight: 500;
|
// $headings-font-weight: 500;
|
||||||
@ -239,34 +239,40 @@ $gray-lighter: #e8e9ea;
|
|||||||
//
|
//
|
||||||
// For each of Bootstrap's buttons, define text, background and border color.
|
// For each of Bootstrap's buttons, define text, background and border color.
|
||||||
|
|
||||||
// $btn-padding-x: 1rem;
|
$btn-padding-x: 1.1rem;
|
||||||
// $btn-padding-y: .375rem;
|
$btn-padding-y: .3846rem;
|
||||||
// $btn-font-weight: normal;
|
$btn-font-weight: normal;
|
||||||
//
|
|
||||||
// $btn-primary-color: #fff;
|
$btn-primary-color: #fff;
|
||||||
// $btn-primary-bg: $brand-primary;
|
// $btn-primary-bg: $brand-primary;
|
||||||
// $btn-primary-border: $btn-primary-bg;
|
// $btn-primary-border: $btn-primary-bg;
|
||||||
//
|
|
||||||
// $btn-secondary-color: $gray-dark;
|
$btn-secondary-color: $gray-dark;
|
||||||
// $btn-secondary-bg: #fff;
|
$btn-secondary-bg: transparent;
|
||||||
// $btn-secondary-border: #ccc;
|
$btn-secondary-border: transparent;
|
||||||
//
|
|
||||||
// $btn-info-color: #fff;
|
// $btn-info-color: #fff;
|
||||||
// $btn-info-bg: $brand-info;
|
// $btn-info-bg: $brand-info;
|
||||||
// $btn-info-border: $btn-info-bg;
|
// $btn-info-border: $btn-info-bg;
|
||||||
//
|
|
||||||
// $btn-success-color: #fff;
|
$btn-success-color: #fff;
|
||||||
// $btn-success-bg: $brand-success;
|
$btn-success-bg: $brand-success;
|
||||||
// $btn-success-border: $btn-success-bg;
|
$btn-success-border: $btn-success-bg;
|
||||||
//
|
$btn-success-shadow: darken($btn-success-bg, 6%);
|
||||||
|
|
||||||
|
$btn-complete-color: #555;
|
||||||
|
$btn-complete-bg: $brand-success;
|
||||||
|
$btn-complete-border: $gray-light;
|
||||||
|
$btn-complete-shadow: darken($btn-success-bg, 6%);
|
||||||
|
|
||||||
// $btn-warning-color: #fff;
|
// $btn-warning-color: #fff;
|
||||||
// $btn-warning-bg: $brand-warning;
|
// $btn-warning-bg: $brand-warning;
|
||||||
// $btn-warning-border: $btn-warning-bg;
|
// $btn-warning-border: $btn-warning-bg;
|
||||||
//
|
|
||||||
// $btn-danger-color: #fff;
|
$btn-danger-color: $brand-danger;
|
||||||
// $btn-danger-bg: $brand-danger;
|
$btn-danger-bg: transparent;
|
||||||
// $btn-danger-border: $btn-danger-bg;
|
$btn-danger-border: transparent;
|
||||||
//
|
|
||||||
// $btn-link-disabled-color: $gray-light;
|
// $btn-link-disabled-color: $gray-light;
|
||||||
//
|
//
|
||||||
// $btn-padding-x-sm: .75rem;
|
// $btn-padding-x-sm: .75rem;
|
||||||
|
@ -98,7 +98,6 @@ $tab-panel-texture-background: $tab-panel-texture-color url(../images/textures/b
|
|||||||
* Typography.
|
* Typography.
|
||||||
* ------------------------------------------------ */
|
* ------------------------------------------------ */
|
||||||
$font-family: Arial, sans-serif !default;
|
$font-family: Arial, sans-serif !default;
|
||||||
$font-base-size: 12px !default;
|
|
||||||
|
|
||||||
/** -----------------------------------------------
|
/** -----------------------------------------------
|
||||||
* Grid Units (px)
|
* Grid Units (px)
|
||||||
|
34
gulpfile.js
34
gulpfile.js
@ -213,10 +213,23 @@ gulp.task('bundle-lib', function bundleLib() {
|
|||||||
comments: false
|
comments: false
|
||||||
}))
|
}))
|
||||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/config.js', { expose: 'config' })
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/config.js', { expose: 'config' })
|
||||||
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/jQuery.js', { expose: 'jQuery' })
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/action', { expose: 'action-button' })
|
||||||
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/form', { expose: 'form' })
|
||||||
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/form-action', { expose: 'form-action' })
|
||||||
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/form-builder', { expose: 'form-builder' })
|
||||||
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/sections/grid-field', { expose: 'grid-field' })
|
||||||
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field-cell', { expose: 'grid-field-cell' })
|
||||||
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field-header', { expose: 'grid-field-header' })
|
||||||
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field-header-cell', { expose: 'grid-field-header-cell' })
|
||||||
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field-row', { expose: 'grid-field-row' })
|
||||||
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field-table', { expose: 'grid-field-table' })
|
||||||
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/i18n.js', { expose: 'i18n' })
|
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/i18n.js', { expose: 'i18n' })
|
||||||
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/router.js', { expose: 'router' })
|
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/jQuery.js', { expose: 'jQuery' })
|
||||||
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/north-header', { expose: 'north-header' })
|
||||||
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/north-header-breadcrumbs', { expose: 'north-header-breadcrumbs' })
|
||||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/reducer-register.js', { expose: 'reducer-register' })
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/reducer-register.js', { expose: 'reducer-register' })
|
||||||
|
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/router.js', { expose: 'router' })
|
||||||
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/text-field', { expose: 'text-field' })
|
||||||
.bundle()
|
.bundle()
|
||||||
.on('update', bundleLib)
|
.on('update', bundleLib)
|
||||||
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
||||||
@ -228,14 +241,20 @@ gulp.task('bundle-lib', function bundleLib() {
|
|||||||
|
|
||||||
gulp.task('bundle-react', function bundleReact() {
|
gulp.task('bundle-react', function bundleReact() {
|
||||||
return browserify(Object.assign({}, browserifyOptions))
|
return browserify(Object.assign({}, browserifyOptions))
|
||||||
.require('react-addons-test-utils', { expose: 'react-addons-test-utils' })
|
.transform(babelify.configure({
|
||||||
|
presets: ['es2015'],
|
||||||
|
ignore: /(node_modules)/
|
||||||
|
}))
|
||||||
|
.require('deep-freeze', { expose: 'deep-freeze' })
|
||||||
.require('react', { expose: 'react' })
|
.require('react', { expose: 'react' })
|
||||||
|
.require('react-addons-css-transition-group', { expose: 'react-addons-css-transition-group' })
|
||||||
|
.require('react-addons-test-utils', { expose: 'react-addons-test-utils' })
|
||||||
.require('react-dom', { expose: 'react-dom' })
|
.require('react-dom', { expose: 'react-dom' })
|
||||||
.require('redux', { expose: 'redux' })
|
|
||||||
.require('react-redux', { expose: 'react-redux' })
|
.require('react-redux', { expose: 'react-redux' })
|
||||||
|
.require('redux', { expose: 'redux' })
|
||||||
.require('redux-thunk', { expose: 'redux-thunk' })
|
.require('redux-thunk', { expose: 'redux-thunk' })
|
||||||
.require('isomorphic-fetch', { expose: 'isomorphic-fetch' })
|
.require('isomorphic-fetch', { expose: 'isomorphic-fetch' })
|
||||||
.require(PATHS.ADMIN_JAVASCRIPT_DIST + '/SilverStripeComponent', { expose: 'silverstripe-component' })
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/SilverStripeComponent', { expose: 'silverstripe-component' })
|
||||||
.bundle()
|
.bundle()
|
||||||
.on('update', bundleReact)
|
.on('update', bundleReact)
|
||||||
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
||||||
@ -271,18 +290,21 @@ gulp.task('bundle-campaign-admin', function bundleCampaignAdmin() {
|
|||||||
presets: ['es2015', 'react'],
|
presets: ['es2015', 'react'],
|
||||||
ignore: /(node_modules)/
|
ignore: /(node_modules)/
|
||||||
}))
|
}))
|
||||||
|
.external('action-button')
|
||||||
.external('deep-freeze')
|
.external('deep-freeze')
|
||||||
|
.external('grid-field')
|
||||||
.external('i18n')
|
.external('i18n')
|
||||||
.external('jQuery')
|
.external('jQuery')
|
||||||
|
.external('north-header')
|
||||||
.external('page.js')
|
.external('page.js')
|
||||||
.external('react')
|
.external('react')
|
||||||
.external('react-addons-test-utils')
|
.external('react-addons-test-utils')
|
||||||
.external('react-dom')
|
.external('react-dom')
|
||||||
.external('react-redux')
|
.external('react-redux')
|
||||||
|
.external('reducer-register')
|
||||||
.external('redux')
|
.external('redux')
|
||||||
.external('redux-thunk')
|
.external('redux-thunk')
|
||||||
.external('silverstripe-component')
|
.external('silverstripe-component')
|
||||||
.external('reducer-register')
|
|
||||||
.bundle()
|
.bundle()
|
||||||
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
||||||
.pipe(source(bundleFileName))
|
.pipe(source(bundleFileName))
|
||||||
|
@ -40,7 +40,6 @@
|
|||||||
"blueimp-tmpl": "^1.0.2",
|
"blueimp-tmpl": "^1.0.2",
|
||||||
"bootstrap": "^4.0.0-alpha.2",
|
"bootstrap": "^4.0.0-alpha.2",
|
||||||
"deep-freeze": "0.0.1",
|
"deep-freeze": "0.0.1",
|
||||||
"isomorphic-fetch": "^2.2.1",
|
|
||||||
"jquery-sizes": "^0.33.0",
|
"jquery-sizes": "^0.33.0",
|
||||||
"npm-shrinkwrap": "^5.4.1",
|
"npm-shrinkwrap": "^5.4.1",
|
||||||
"page.js": "^4.13.3",
|
"page.js": "^4.13.3",
|
||||||
|
@ -114,7 +114,6 @@ $gf_grid_x: 16px;
|
|||||||
}
|
}
|
||||||
.grid-csv-button, .grid-print-button {
|
.grid-csv-button, .grid-print-button {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-size: $font-base-size;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user