mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
CampaignAdmin and GridField React sections
Also removes watchify because it wasn't working. Add SilverStripeBackend class used to fetch data from endpoints for the front-end
This commit is contained in:
parent
7580d35be8
commit
f8c17bed3b
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@ npm-debug.log
|
|||||||
css/GridField_print.css
|
css/GridField_print.css
|
||||||
admin/thirdparty/chosen/node_modules
|
admin/thirdparty/chosen/node_modules
|
||||||
node_modules/
|
node_modules/
|
||||||
|
coverage/
|
||||||
|
102
admin/code/CampaignAdmin.php
Normal file
102
admin/code/CampaignAdmin.php
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Campaign section of the CMS
|
||||||
|
*
|
||||||
|
* @package framework
|
||||||
|
* @subpackage admin
|
||||||
|
*/
|
||||||
|
class CampaignAdmin extends LeftAndMain implements PermissionProvider {
|
||||||
|
|
||||||
|
private static $allowed_actions = [
|
||||||
|
'createCampaign',
|
||||||
|
'readCampaign',
|
||||||
|
'updateCampaign',
|
||||||
|
'deleteCampaign',
|
||||||
|
];
|
||||||
|
|
||||||
|
private static $menu_priority = 11;
|
||||||
|
|
||||||
|
private static $menu_title = 'Campaigns';
|
||||||
|
|
||||||
|
private static $url_handlers = [
|
||||||
|
'POST item/$ID' => 'createCampaign',
|
||||||
|
'GET item/$ID' => 'readCampaign',
|
||||||
|
'PUT item/$ID' => 'updateCampaign',
|
||||||
|
'DELETE item/$ID' => 'deleteCampaign',
|
||||||
|
];
|
||||||
|
|
||||||
|
private static $url_segment = 'campaigns';
|
||||||
|
|
||||||
|
public function init() {
|
||||||
|
parent::init();
|
||||||
|
|
||||||
|
Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/dist/bundle-react.js');
|
||||||
|
Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/dist/campaign-admin.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEditForm($id = null, $fields = null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST endpoint to create a campaign.
|
||||||
|
*
|
||||||
|
* @param SS_HTTPRequest $request
|
||||||
|
*
|
||||||
|
* @return SS_HTTPResponse
|
||||||
|
*/
|
||||||
|
public function createCampaign(SS_HTTPRequest $request) {
|
||||||
|
$response = new SS_HTTPResponse();
|
||||||
|
$response->addHeader('Content-Type', 'application/json');
|
||||||
|
$response->setBody(Convert::raw2json(['campaign' => 'create']));
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST endpoint to get a campaign.
|
||||||
|
*
|
||||||
|
* @param SS_HTTPRequest $request
|
||||||
|
*
|
||||||
|
* @return SS_HTTPResponse
|
||||||
|
*/
|
||||||
|
public function readCampaign(SS_HTTPRequest $request) {
|
||||||
|
$response = new SS_HTTPResponse();
|
||||||
|
$response->addHeader('Content-Type', 'application/json');
|
||||||
|
$response->setBody(Convert::raw2json(['campaign' => 'read']));
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST endpoint to update a campaign.
|
||||||
|
*
|
||||||
|
* @param SS_HTTPRequest $request
|
||||||
|
*
|
||||||
|
* @return SS_HTTPResponse
|
||||||
|
*/
|
||||||
|
public function updateCampaign(SS_HTTPRequest $request) {
|
||||||
|
$response = new SS_HTTPResponse();
|
||||||
|
$response->addHeader('Content-Type', 'application/json');
|
||||||
|
$response->setBody(Convert::raw2json(['campaign' => 'update']));
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST endpoint to delete a campaign.
|
||||||
|
*
|
||||||
|
* @param SS_HTTPRequest $request
|
||||||
|
*
|
||||||
|
* @return SS_HTTPResponse
|
||||||
|
*/
|
||||||
|
public function deleteCampaign(SS_HTTPRequest $request) {
|
||||||
|
$response = new SS_HTTPResponse();
|
||||||
|
$response->addHeader('Content-Type', 'application/json');
|
||||||
|
$response->setBody(Convert::raw2json(['campaign' => 'delete']));
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -481,6 +481,8 @@ class LeftAndMain extends Controller implements PermissionProvider {
|
|||||||
Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/src/jquery.entwine.inspector.js');
|
Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/src/jquery.entwine.inspector.js');
|
||||||
Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/dist/leaktools.js');
|
Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/dist/leaktools.js');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/dist/boot.js');
|
||||||
|
|
||||||
Requirements::css(FRAMEWORK_ADMIN_DIR . '/css/bootstrap/bootstrap.css');
|
Requirements::css(FRAMEWORK_ADMIN_DIR . '/css/bootstrap/bootstrap.css');
|
||||||
Requirements::css(FRAMEWORK_ADMIN_DIR . '/thirdparty/jquery-notice/jquery.notice.css');
|
Requirements::css(FRAMEWORK_ADMIN_DIR . '/thirdparty/jquery-notice/jquery.notice.css');
|
||||||
|
BIN
admin/images/sprites/src/menu-icons/16x16-2x/collection.png
Normal file
BIN
admin/images/sprites/src/menu-icons/16x16-2x/collection.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 212 B |
BIN
admin/images/sprites/src/menu-icons/16x16/collection.png
Normal file
BIN
admin/images/sprites/src/menu-icons/16x16/collection.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 184 B |
BIN
admin/images/sprites/src/menu-icons/24x24/collection.png
Normal file
BIN
admin/images/sprites/src/menu-icons/24x24/collection.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 211 B |
23
admin/javascript/src/boot/campaign-admin.js
Normal file
23
admin/javascript/src/boot/campaign-admin.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import reducerRegister from 'reducer-register';
|
||||||
|
import $ from 'jQuery';
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import CampaignAdmin from '../sections/campaign-admin/controller';
|
||||||
|
import campaignsReducer from '../state/campaigns/reducer';
|
||||||
|
|
||||||
|
// TODO: Move this to the controller.
|
||||||
|
reducerRegister.add('campaigns', campaignsReducer);
|
||||||
|
|
||||||
|
$.entwine('ss', function ($) {
|
||||||
|
|
||||||
|
$('.cms-content.CampaignAdmin').entwine({
|
||||||
|
onadd: function () {
|
||||||
|
ReactDOM.render(<CampaignAdmin />, this[0]);
|
||||||
|
},
|
||||||
|
|
||||||
|
onremove: function () {
|
||||||
|
ReactDOM.unmountComponentAtNode(this[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
15
admin/javascript/src/boot/index.js
Normal file
15
admin/javascript/src/boot/index.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { combineReducers, createStore, applyMiddleware } from 'redux';
|
||||||
|
import thunkMiddleware from 'redux-thunk';
|
||||||
|
import createLogger from 'redux-logger';
|
||||||
|
import reducerRegister from 'reducer-register';
|
||||||
|
|
||||||
|
function appBoot() {
|
||||||
|
const initialState = {};
|
||||||
|
const rootReducer = combineReducers(reducerRegister.getAll());
|
||||||
|
const createStoreWithMiddleware = applyMiddleware(thunkMiddleware, createLogger())(createStore);
|
||||||
|
const store = createStoreWithMiddleware(rootReducer, initialState);
|
||||||
|
|
||||||
|
console.log(store.getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = appBoot;
|
@ -0,0 +1,5 @@
|
|||||||
|
# GridFieldCell
|
||||||
|
|
||||||
|
This component represents a data cell in a GridFieldRow.
|
||||||
|
|
||||||
|
## Props
|
14
admin/javascript/src/components/grid-field-cell/index.js
Normal file
14
admin/javascript/src/components/grid-field-cell/index.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SilverStripeComponent from 'silverstripe-component';
|
||||||
|
|
||||||
|
class GridFieldCellComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<td className='grid-field-cell-component'>{this.props.children}</td>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GridFieldCellComponent;
|
@ -0,0 +1,3 @@
|
|||||||
|
.grid-field-cell-component {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
jest.dontMock('../index');
|
||||||
|
|
||||||
|
describe('GridFieldCellComponent', () => {
|
||||||
|
|
||||||
|
});
|
@ -0,0 +1,6 @@
|
|||||||
|
# GridFieldHeaderCell
|
||||||
|
|
||||||
|
This component is a cell in a GridFirldHeader component.
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SilverStripeComponent from 'silverstripe-component';
|
||||||
|
|
||||||
|
class GridFieldHeaderCellComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<th className='grid-field-header-cell-component'>{this.props.children}</th>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GridFieldHeaderCellComponent;
|
@ -0,0 +1,5 @@
|
|||||||
|
jest.dontMock('../index');
|
||||||
|
|
||||||
|
describe('GridFieldHeaderCellComponent', () => {
|
||||||
|
|
||||||
|
});
|
@ -0,0 +1,5 @@
|
|||||||
|
# GridFieldHeader
|
||||||
|
|
||||||
|
This component is used to display a tabel header row on a GridFieldComponent.
|
||||||
|
|
||||||
|
## Props
|
17
admin/javascript/src/components/grid-field-header/index.js
Normal file
17
admin/javascript/src/components/grid-field-header/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SilverStripeComponent from 'silverstripe-component';
|
||||||
|
import GridFieldRowComponent from '../grid-field-row';
|
||||||
|
|
||||||
|
class GridFieldHeaderComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<thead className='grid-field-header-component'>
|
||||||
|
<GridFieldRowComponent>{this.props.children}</GridFieldRowComponent>
|
||||||
|
</thead>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GridFieldHeaderComponent;
|
@ -0,0 +1,3 @@
|
|||||||
|
.grid-field-header-component {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
jest.dontMock('../index');
|
||||||
|
|
||||||
|
describe('GridFieldHeaderComponent', () => {
|
||||||
|
|
||||||
|
});
|
9
admin/javascript/src/components/grid-field-row/README.md
Normal file
9
admin/javascript/src/components/grid-field-row/README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# GridFieldRow
|
||||||
|
|
||||||
|
Represents a row in a GridField.
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
### cells (array)
|
||||||
|
|
||||||
|
The table data to display in the row.
|
14
admin/javascript/src/components/grid-field-row/index.js
Normal file
14
admin/javascript/src/components/grid-field-row/index.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SilverStripeComponent from 'silverstripe-component';
|
||||||
|
|
||||||
|
class GridFieldRowComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<tr className='grid-field-row-component'>{this.props.children}</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GridFieldRowComponent;
|
@ -0,0 +1,3 @@
|
|||||||
|
.grid-field-row-component {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
jest.dontMock('../index');
|
||||||
|
|
||||||
|
describe('GridFieldRowComponent', () => {
|
||||||
|
|
||||||
|
});
|
13
admin/javascript/src/components/grid-field/README.md
Normal file
13
admin/javascript/src/components/grid-field/README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# GridField
|
||||||
|
|
||||||
|
This component is used to display structured data in an extendible table layout.
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
### Headings (array)
|
||||||
|
|
||||||
|
The column headings.
|
||||||
|
|
||||||
|
### Rows (array)
|
||||||
|
|
||||||
|
The table rows.
|
65
admin/javascript/src/components/grid-field/index.js
Normal file
65
admin/javascript/src/components/grid-field/index.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SilverStripeComponent from 'silverstripe-component';
|
||||||
|
|
||||||
|
class GridFieldComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<table className='grid-field-component [ table ]'>
|
||||||
|
{this.generateHeader()}
|
||||||
|
<tbody>
|
||||||
|
{this.generateRows()}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the header component.
|
||||||
|
*
|
||||||
|
* Uses the header component passed via the `header` prop if it exists.
|
||||||
|
* Otherwise generates a header from the `data` prop.
|
||||||
|
*
|
||||||
|
* @return object|null
|
||||||
|
*/
|
||||||
|
generateHeader() {
|
||||||
|
if (typeof this.props.header !== 'undefined') {
|
||||||
|
return this.props.header;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.props.data !== 'undefined') {
|
||||||
|
// TODO: Generate the header.
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the table rows.
|
||||||
|
*
|
||||||
|
* Uses the components passed via the `rows` props if it exists.
|
||||||
|
* Otherwise generates rows from the `data` prop.
|
||||||
|
*
|
||||||
|
* @return object|null
|
||||||
|
*/
|
||||||
|
generateRows() {
|
||||||
|
if (typeof this.props.rows !== 'undefined') {
|
||||||
|
return this.props.rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.props.data !== 'undefined') {
|
||||||
|
// TODO: Generate the rows.
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
GridFieldComponent.propTypes = {
|
||||||
|
data: React.PropTypes.object,
|
||||||
|
header: React.PropTypes.object,
|
||||||
|
rows: React.PropTypes.array
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GridFieldComponent;
|
3
admin/javascript/src/components/grid-field/styles.scss
Normal file
3
admin/javascript/src/components/grid-field/styles.scss
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.grid-field-component {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
jest.dontMock('../index');
|
||||||
|
|
||||||
|
describe('GridFieldComponent', () => {
|
||||||
|
|
||||||
|
});
|
17
admin/javascript/src/mocks/jQuery.js
Normal file
17
admin/javascript/src/mocks/jQuery.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
function jQuery() {
|
||||||
|
return {
|
||||||
|
// Add jQuery methods such as 'find', 'change', 'trigger' as needed.
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var mockAjaxFn = jest.genMockFunction();
|
||||||
|
|
||||||
|
mockAjaxFn.mockReturnValue({
|
||||||
|
done: jest.genMockFunction(),
|
||||||
|
fail: jest.genMockFunction(),
|
||||||
|
always: jest.genMockFunction()
|
||||||
|
});
|
||||||
|
|
||||||
|
jQuery.ajax = mockAjaxFn;
|
||||||
|
|
||||||
|
export default jQuery;
|
64
admin/javascript/src/reducer-register.js
Normal file
64
admin/javascript/src/reducer-register.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* The register of Redux reducers.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
var register = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The central register of Redux reducers for the CMS. All registered reducers are combined when the application boots.
|
||||||
|
*/
|
||||||
|
class ReducerRegister {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a reducer to the register.
|
||||||
|
*
|
||||||
|
* @param string key - The key to register the reducer against.
|
||||||
|
* @param object reducer - Redux reducer.
|
||||||
|
*/
|
||||||
|
add(key, reducer) {
|
||||||
|
if (typeof register[key] !== 'undefined') {
|
||||||
|
throw new Error(`Reducer already exists at '${key}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
register[key] = reducer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all reducers from the register.
|
||||||
|
*
|
||||||
|
* @return object
|
||||||
|
*/
|
||||||
|
getAll() {
|
||||||
|
return register;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a reducer from the register.
|
||||||
|
*
|
||||||
|
* @param string [key] - The key the reducer is registered against.
|
||||||
|
*
|
||||||
|
* @return object|undefined
|
||||||
|
*/
|
||||||
|
getByKey(key) {
|
||||||
|
return register[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a reducer from the register.
|
||||||
|
*
|
||||||
|
* @param string key - The key the reducer is registered against.
|
||||||
|
*/
|
||||||
|
remove(key) {
|
||||||
|
delete register[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an instance to export. The same instance is exported to
|
||||||
|
// each script which imports the reducerRegister. This means the
|
||||||
|
// same register is available throughout the application.
|
||||||
|
let reducerRegister = new ReducerRegister();
|
||||||
|
|
||||||
|
export default reducerRegister;
|
3
admin/javascript/src/sections/campaign-admin/README.md
Normal file
3
admin/javascript/src/sections/campaign-admin/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# CampaignAdmin
|
||||||
|
|
||||||
|
This section is used for managing Campaigns in the CMS.
|
60
admin/javascript/src/sections/campaign-admin/controller.js
Normal file
60
admin/javascript/src/sections/campaign-admin/controller.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SilverStripeComponent from 'silverstripe-component';
|
||||||
|
import NorthHeader from '../../components/north-header';
|
||||||
|
import GridField from '../../components/grid-field';
|
||||||
|
import GridFieldHeader from '../../components/grid-field-header';
|
||||||
|
import GridFieldHeaderCell from '../../components/grid-field-header-cell';
|
||||||
|
import GridFieldRow from '../../components/grid-field-row';
|
||||||
|
import GridFieldCell from '../../components/grid-field-cell';
|
||||||
|
|
||||||
|
class CampaignAdminContainer extends SilverStripeComponent {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
// TODO: This will be an AJAX call and it's response stored in state.
|
||||||
|
this.mockData = {
|
||||||
|
campaigns: [
|
||||||
|
{
|
||||||
|
title: 'SilverStripe 4.0 release',
|
||||||
|
description: 'All the stuff related to the 4.0 announcement',
|
||||||
|
changes: 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'March release',
|
||||||
|
description: 'march release stuff',
|
||||||
|
changes: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'About us',
|
||||||
|
description: 'The team',
|
||||||
|
changes: 1345
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const columnNames = ['title', 'changes', 'description'];
|
||||||
|
|
||||||
|
const headerCells = columnNames.map((columnName, i) => <GridFieldHeaderCell key={i}>{columnName}</GridFieldHeaderCell>);
|
||||||
|
const header = <GridFieldHeader>{headerCells}</GridFieldHeader>;
|
||||||
|
|
||||||
|
const rows = this.mockData.campaigns.map((campaign, i) => {
|
||||||
|
const cells = columnNames.map((columnName, i) => {
|
||||||
|
return <GridFieldCell key={i}>{campaign[columnName]}</GridFieldCell>
|
||||||
|
});
|
||||||
|
return <GridFieldRow key={i}>{cells}</GridFieldRow>;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<NorthHeader></NorthHeader>
|
||||||
|
<GridField header={header} rows={rows}></GridField>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CampaignAdminContainer;
|
3
admin/javascript/src/sections/campaign-admin/styles.scss
Normal file
3
admin/javascript/src/sections/campaign-admin/styles.scss
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.CampaignAdmin {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
jest.dontMock('../controller');
|
||||||
|
|
||||||
|
describe('CampaignAdminContainer', () => {
|
||||||
|
|
||||||
|
});
|
58
admin/javascript/src/silverstripe-backend.js
Normal file
58
admin/javascript/src/silverstripe-backend.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import $ from 'jQuery';
|
||||||
|
|
||||||
|
class SilverStripeBackend {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a network request using the GET HTTP verb.
|
||||||
|
*
|
||||||
|
* @param string url - Endpoint URL.
|
||||||
|
*
|
||||||
|
* @return object - jqXHR. See http://api.jquery.com/Types/#jqXHR
|
||||||
|
*/
|
||||||
|
get(url) {
|
||||||
|
return $.ajax({ type: 'GET', url });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a network request using the POST HTTP verb.
|
||||||
|
*
|
||||||
|
* @param string url - Endpoint URL.
|
||||||
|
* @param object data - Data to send with the request.
|
||||||
|
*
|
||||||
|
* @return object - jqXHR. See http://api.jquery.com/Types/#jqXHR
|
||||||
|
*/
|
||||||
|
post(url, data) {
|
||||||
|
return $.ajax({ type: 'POST', url, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a newtwork request using the PUT HTTP verb.
|
||||||
|
*
|
||||||
|
* @param string url - Endpoint URL.
|
||||||
|
* @param object data - Data to send with the request.
|
||||||
|
*
|
||||||
|
* @return object - jqXHR. See http://api.jquery.com/Types/#jqXHR
|
||||||
|
*/
|
||||||
|
put(url, data) {
|
||||||
|
return $.ajax({ type: 'PUT', url, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a newtwork request using the DELETE HTTP verb.
|
||||||
|
*
|
||||||
|
* @param string url - Endpoint URL.
|
||||||
|
* @param object data - Data to send with the request.
|
||||||
|
*
|
||||||
|
* @return object - jqXHR. See http://api.jquery.com/Types/#jqXHR
|
||||||
|
*/
|
||||||
|
delete(url, data) {
|
||||||
|
return $.ajax({ type: 'DELETE', url, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exported as a singleton so we can implement things like
|
||||||
|
// global caching and request batching at some stage.
|
||||||
|
let backend = new SilverStripeBackend();
|
||||||
|
|
||||||
|
export default backend;
|
8
admin/javascript/src/state/campaigns/action-types.js
Normal file
8
admin/javascript/src/state/campaigns/action-types.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export default {
|
||||||
|
CREATE_CAMPAIGN: 'CREATE_CAMPAIGN',
|
||||||
|
UPDATE_CAMPAIGN: 'UPDATE_CAMPAIGN',
|
||||||
|
DELETE_CAMPAIGN: 'DELETE_CAMPAIGN',
|
||||||
|
FETCH_CAMPAIGN_REQUEST: 'FETCH_CAMPAIGN_REQUEST',
|
||||||
|
FETCH_CAMPAIGN_FAILURE: 'FETCH_CAMPAIGN_FAILURE',
|
||||||
|
FETCH_CAMPAIGN_SUCCESS: 'FETCH_CAMPAIGN_SUCCESS'
|
||||||
|
};
|
0
admin/javascript/src/state/campaigns/actions.js
Normal file
0
admin/javascript/src/state/campaigns/actions.js
Normal file
49
admin/javascript/src/state/campaigns/reducer.js
Normal file
49
admin/javascript/src/state/campaigns/reducer.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import deepFreeze from 'deep-freeze';
|
||||||
|
import ACTION_TYPES from './action-types';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
isFetching: false,
|
||||||
|
items: []
|
||||||
|
};
|
||||||
|
|
||||||
|
function campaignsReducer(state = initialState, action) {
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case ACTION_TYPES.CREATE_CAMPAIGN:
|
||||||
|
return deepFreeze(Object.assign({}, state, {
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
case ACTION_TYPES.UPDATE_CAMPAIGN:
|
||||||
|
return deepFreeze(Object.assign({}, state, {
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
case ACTION_TYPES.DELETE_CAMPAIGN:
|
||||||
|
return deepFreeze(Object.assign({}, state, {
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
case ACTION_TYPES.FETCH_CAMPAIGN_REQUEST:
|
||||||
|
return deepFreeze(Object.assign({}, state, {
|
||||||
|
isFetching: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
case ACTION_TYPES.FETCH_CAMPAIGN_FAILURE:
|
||||||
|
return deepFreeze(Object.assign({}, state, {
|
||||||
|
isFetching: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
case ACTION_TYPES.FETCH_CAMPAIGN_SUCCESS:
|
||||||
|
return deepFreeze(Object.assign({}, state, {
|
||||||
|
isFetching: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default campaignsReducer;
|
@ -0,0 +1,70 @@
|
|||||||
|
jest.dontMock('deep-freeze');
|
||||||
|
jest.dontMock('../reducer');
|
||||||
|
jest.dontMock('../action-types');
|
||||||
|
|
||||||
|
var campaignsReducer = require('../reducer').default,
|
||||||
|
ACTION_TYPES = require('../action-types').default;
|
||||||
|
|
||||||
|
describe('campaignsReducer', () => {
|
||||||
|
|
||||||
|
describe('CREATE_CAMPAIGN', () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('UPDATE_CAMPAIGN', () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DELETE_CAMPAIGN', () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('FETCH_CAMPAIGN_REQUEST', () => {
|
||||||
|
|
||||||
|
it('should set the "isFetching" flag', () => {
|
||||||
|
const initialState = {
|
||||||
|
isFetching: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = { type: ACTION_TYPES.FETCH_CAMPAIGN_REQUEST };
|
||||||
|
|
||||||
|
const nextState = campaignsReducer(initialState, action);
|
||||||
|
|
||||||
|
expect(nextState.isFetching).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('FETCH_CAMPAIGN_FAILURE', () => {
|
||||||
|
|
||||||
|
it('should unset the "isFetching" flag', () => {
|
||||||
|
const initialState = {
|
||||||
|
isFetching: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = { type: ACTION_TYPES.FETCH_CAMPAIGN_FAILURE };
|
||||||
|
|
||||||
|
const nextState = campaignsReducer(initialState, action);
|
||||||
|
|
||||||
|
expect(nextState.isFetching).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('FETCH_CAMPAIGN_SUCCESS', () => {
|
||||||
|
|
||||||
|
it('should unset the "isFetching" flag', () => {
|
||||||
|
const initialState = {
|
||||||
|
isFetching: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = { type: ACTION_TYPES.FETCH_CAMPAIGN_FAILURE };
|
||||||
|
|
||||||
|
const nextState = campaignsReducer(initialState, action);
|
||||||
|
|
||||||
|
expect(nextState.isFetching).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
14
admin/javascript/src/styles/main.scss
Normal file
14
admin/javascript/src/styles/main.scss
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/** -----------------------------
|
||||||
|
* Sections
|
||||||
|
* ------------------------------ */
|
||||||
|
@import "../sections/campaign-admin/styles";
|
||||||
|
|
||||||
|
/** -----------------------------
|
||||||
|
* components
|
||||||
|
* ------------------------------ */
|
||||||
|
@import "../components/grid-field/styles";
|
||||||
|
@import "../components/grid-field-cell/styles";
|
||||||
|
@import "../components/grid-field-header/styles";
|
||||||
|
@import "../components/grid-field-header-cell/styles";
|
||||||
|
@import "../components/grid-field-row/styles";
|
||||||
|
@import "../components/north-header/styles";
|
44
admin/javascript/src/tests/reducer-register-test.js
Normal file
44
admin/javascript/src/tests/reducer-register-test.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
jest.dontMock('../reducer-register');
|
||||||
|
|
||||||
|
var reducerRegister = require('../reducer-register').default;
|
||||||
|
|
||||||
|
describe('ReducerRegister', () => {
|
||||||
|
|
||||||
|
var reducer = () => null;
|
||||||
|
|
||||||
|
it('should add a reducer to the register', () => {
|
||||||
|
expect(reducerRegister.getAll().test).toBe(undefined);
|
||||||
|
|
||||||
|
reducerRegister.add('test', reducer);
|
||||||
|
expect(reducerRegister.getAll().test).toBe(reducer);
|
||||||
|
|
||||||
|
reducerRegister.remove('test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove a reducer from the register', () => {
|
||||||
|
reducerRegister.add('test', reducer);
|
||||||
|
expect(reducerRegister.getAll().test).toBe(reducer);
|
||||||
|
|
||||||
|
reducerRegister.remove('test');
|
||||||
|
expect(reducerRegister.getAll().test).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get all reducers from the register', () => {
|
||||||
|
reducerRegister.add('test1', reducer);
|
||||||
|
reducerRegister.add('test2', reducer);
|
||||||
|
|
||||||
|
expect(reducerRegister.getAll().test1).toBe(reducer);
|
||||||
|
expect(reducerRegister.getAll().test2).toBe(reducer);
|
||||||
|
|
||||||
|
reducerRegister.remove('test1');
|
||||||
|
reducerRegister.remove('test2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get a single reducer from the register', () => {
|
||||||
|
reducerRegister.add('test', reducer);
|
||||||
|
expect(reducerRegister.getByKey('test')).toBe(reducer);
|
||||||
|
|
||||||
|
reducerRegister.remove('test');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
106
admin/javascript/src/tests/silverstripe-backend-test.js
Normal file
106
admin/javascript/src/tests/silverstripe-backend-test.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
jest.mock('jQuery');
|
||||||
|
jest.unmock('../silverstripe-backend');
|
||||||
|
|
||||||
|
import $ from 'jQuery';
|
||||||
|
import backend from '../silverstripe-backend';
|
||||||
|
|
||||||
|
describe('SilverStripeBackend', () => {
|
||||||
|
|
||||||
|
describe('get()', () => {
|
||||||
|
|
||||||
|
it('should return a jqXHR', () => {
|
||||||
|
var jqxhr = backend.get('http://example.com');
|
||||||
|
|
||||||
|
expect(typeof jqxhr).toBe('object');
|
||||||
|
expect(typeof jqxhr.done).toBe('function');
|
||||||
|
expect(typeof jqxhr.fail).toBe('function');
|
||||||
|
expect(typeof jqxhr.always).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send a GET request to an endpoint', () => {
|
||||||
|
backend.get('http://example.com');
|
||||||
|
|
||||||
|
expect($.ajax).toBeCalledWith({
|
||||||
|
type: 'GET',
|
||||||
|
url: 'http://example.com'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('post()', () => {
|
||||||
|
|
||||||
|
it('should return a jqXHR', () => {
|
||||||
|
var jqxhr = backend.get('http://example.com/item');
|
||||||
|
|
||||||
|
expect(typeof jqxhr).toBe('object');
|
||||||
|
expect(typeof jqxhr.done).toBe('function');
|
||||||
|
expect(typeof jqxhr.fail).toBe('function');
|
||||||
|
expect(typeof jqxhr.always).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send a POST request to an endpoint', () => {
|
||||||
|
const postData = { id: 1 };
|
||||||
|
|
||||||
|
backend.post('http://example.com', postData);
|
||||||
|
|
||||||
|
expect($.ajax).toBeCalledWith({
|
||||||
|
type: 'POST',
|
||||||
|
url: 'http://example.com',
|
||||||
|
data: postData
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('put()', () => {
|
||||||
|
|
||||||
|
it('should return a jqXHR', () => {
|
||||||
|
var jqxhr = backend.get('http://example.com/item');
|
||||||
|
|
||||||
|
expect(typeof jqxhr).toBe('object');
|
||||||
|
expect(typeof jqxhr.done).toBe('function');
|
||||||
|
expect(typeof jqxhr.fail).toBe('function');
|
||||||
|
expect(typeof jqxhr.always).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send a PUT request to an endpoint', () => {
|
||||||
|
const putData = { id: 1 };
|
||||||
|
|
||||||
|
backend.put('http://example.com', putData);
|
||||||
|
|
||||||
|
expect($.ajax).toBeCalledWith({
|
||||||
|
type: 'PUT',
|
||||||
|
url: 'http://example.com',
|
||||||
|
data: putData
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('delete()', () => {
|
||||||
|
|
||||||
|
it('should return a jqXHR', () => {
|
||||||
|
var jqxhr = backend.get('http://example.com/item');
|
||||||
|
|
||||||
|
expect(typeof jqxhr).toBe('object');
|
||||||
|
expect(typeof jqxhr.done).toBe('function');
|
||||||
|
expect(typeof jqxhr.fail).toBe('function');
|
||||||
|
expect(typeof jqxhr.always).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send a DELETE request to an endpoint', () => {
|
||||||
|
const deleteData = { id: 1 };
|
||||||
|
|
||||||
|
backend.delete('http://example.com', deleteData);
|
||||||
|
|
||||||
|
expect($.ajax).toBeCalledWith({
|
||||||
|
type: 'DELETE',
|
||||||
|
url: 'http://example.com',
|
||||||
|
data: deleteData
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -16,6 +16,9 @@
|
|||||||
background-image: sprite-url($sprite);
|
background-image: sprite-url($sprite);
|
||||||
background-size: ceil(image-width(sprite-path($sprite)) / 2) auto;
|
background-size: ceil(image-width(sprite-path($sprite)) / 2) auto;
|
||||||
|
|
||||||
|
&.icon-campaignadmin {
|
||||||
|
background-position: 0 round(nth(sprite-position($sprite, "collection"), 2) / 2);
|
||||||
|
}
|
||||||
&.icon-assetadmin {
|
&.icon-assetadmin {
|
||||||
background-position: 0 round(nth(sprite-position($sprite, "picture"), 2) / 2);
|
background-position: 0 round(nth(sprite-position($sprite, "picture"), 2) / 2);
|
||||||
}
|
}
|
||||||
@ -289,7 +292,11 @@
|
|||||||
height: 16px;
|
height: 16px;
|
||||||
@extend .retina-menu-icons-16x16-2x;
|
@extend .retina-menu-icons-16x16-2x;
|
||||||
|
|
||||||
&.icon-assetadmin {
|
&.icon-campaignadmin {
|
||||||
|
@include retina-sprite($menu-icons-16x16-2x-collection);
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
&.icon-assetadmin {
|
||||||
@include retina-sprite($menu-icons-16x16-2x-picture);
|
@include retina-sprite($menu-icons-16x16-2x-picture);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,9 @@
|
|||||||
height: 24px;
|
height: 24px;
|
||||||
@extend .icon-menu-icons-24x24;
|
@extend .icon-menu-icons-24x24;
|
||||||
|
|
||||||
|
&.icon-campaignadmin {
|
||||||
|
@include sprite($menu-icons-24x24-collection, inline-block);
|
||||||
|
}
|
||||||
&.icon-assetadmin {
|
&.icon-assetadmin {
|
||||||
@include sprite($menu-icons-24x24-picture, inline-block);
|
@include sprite($menu-icons-24x24-picture, inline-block);
|
||||||
}
|
}
|
||||||
@ -99,6 +102,9 @@
|
|||||||
height: 16px;
|
height: 16px;
|
||||||
@extend .icon-menu-icons-16x16;
|
@extend .icon-menu-icons-16x16;
|
||||||
|
|
||||||
|
&.icon-campaignadmin {
|
||||||
|
@include sprite($menu-icons-16x16-collection, inline-block);
|
||||||
|
}
|
||||||
&.icon-assetadmin {
|
&.icon-assetadmin {
|
||||||
@include sprite($menu-icons-16x16-picture, inline-block);
|
@include sprite($menu-icons-16x16-picture, inline-block);
|
||||||
}
|
}
|
||||||
|
@ -120,39 +120,42 @@ $sprites-64x64-2x-tab-list-hover: -0px -160px 80px 80px;
|
|||||||
$sprites-64x64-2x-tab-list: -0px -240px 80px 80px;
|
$sprites-64x64-2x-tab-list: -0px -240px 80px 80px;
|
||||||
$sprites-64x64-2x-tab-tree-hover: -0px -320px 80px 80px;
|
$sprites-64x64-2x-tab-tree-hover: -0px -320px 80px 80px;
|
||||||
$sprites-64x64-2x-tab-tree: -0px -400px 80px 80px;
|
$sprites-64x64-2x-tab-tree: -0px -400px 80px 80px;
|
||||||
$menu-icons-24x24-home: -0px -0px 24px 24px;
|
|
||||||
$menu-icons-24x24-blog: -0px -24px 24px 24px;
|
|
||||||
$menu-icons-24x24-db: -0px -48px 24px 24px;
|
|
||||||
$menu-icons-24x24-document: -0px -72px 24px 24px;
|
|
||||||
$menu-icons-24x24-gears: -0px -96px 24px 24px;
|
|
||||||
$menu-icons-24x24-community: -0px -120px 24px 24px;
|
|
||||||
$menu-icons-24x24-information: -0px -144px 24px 24px;
|
|
||||||
$menu-icons-24x24-network: -0px -168px 24px 24px;
|
|
||||||
$menu-icons-24x24-pencil: -0px -192px 24px 24px;
|
|
||||||
$menu-icons-24x24-picture: -0px -216px 24px 24px;
|
|
||||||
$menu-icons-24x24-pie-chart: -0px -240px 24px 24px;
|
|
||||||
$menu-icons-16x16-2x-home: -0px -0px 32px 32px;
|
|
||||||
$menu-icons-16x16-2x-blog: -0px -32px 32px 32px;
|
|
||||||
$menu-icons-16x16-2x-db: -0px -64px 32px 32px;
|
|
||||||
$menu-icons-16x16-2x-document: -0px -96px 32px 32px;
|
|
||||||
$menu-icons-16x16-2x-gears: -0px -128px 32px 32px;
|
|
||||||
$menu-icons-16x16-2x-community: -0px -160px 32px 32px;
|
|
||||||
$menu-icons-16x16-2x-information: -0px -192px 32px 32px;
|
|
||||||
$menu-icons-16x16-2x-network: -0px -224px 32px 32px;
|
|
||||||
$menu-icons-16x16-2x-pencil: -0px -256px 32px 32px;
|
|
||||||
$menu-icons-16x16-2x-picture: -0px -288px 32px 32px;
|
|
||||||
$menu-icons-16x16-2x-pie-chart: -0px -320px 32px 32px;
|
|
||||||
$menu-icons-16x16-home: -0px -0px 16px 16px;
|
$menu-icons-16x16-home: -0px -0px 16px 16px;
|
||||||
$menu-icons-16x16-blog: -0px -16px 16px 16px;
|
$menu-icons-16x16-blog: -0px -16px 16px 16px;
|
||||||
$menu-icons-16x16-db: -0px -32px 16px 16px;
|
$menu-icons-16x16-community: -0px -32px 16px 16px;
|
||||||
$menu-icons-16x16-document: -0px -48px 16px 16px;
|
$menu-icons-16x16-db: -0px -48px 16px 16px;
|
||||||
$menu-icons-16x16-gears: -0px -64px 16px 16px;
|
$menu-icons-16x16-document: -0px -64px 16px 16px;
|
||||||
$menu-icons-16x16-community: -0px -80px 16px 16px;
|
$menu-icons-16x16-gears: -0px -80px 16px 16px;
|
||||||
$menu-icons-16x16-information: -0px -96px 16px 16px;
|
$menu-icons-16x16-collection: -0px -96px 16px 16px;
|
||||||
$menu-icons-16x16-network: -0px -112px 16px 16px;
|
$menu-icons-16x16-information: -0px -112px 16px 16px;
|
||||||
$menu-icons-16x16-pencil: -0px -128px 16px 16px;
|
$menu-icons-16x16-network: -0px -128px 16px 16px;
|
||||||
$menu-icons-16x16-picture: -0px -144px 16px 16px;
|
$menu-icons-16x16-pencil: -0px -144px 16px 16px;
|
||||||
$menu-icons-16x16-pie-chart: -0px -160px 16px 16px;
|
$menu-icons-16x16-picture: -0px -160px 16px 16px;
|
||||||
|
$menu-icons-16x16-pie-chart: -0px -176px 16px 16px;
|
||||||
|
$menu-icons-16x16-2x-home: -0px -0px 32px 32px;
|
||||||
|
$menu-icons-16x16-2x-blog: -0px -32px 32px 32px;
|
||||||
|
$menu-icons-16x16-2x-community: -0px -64px 32px 32px;
|
||||||
|
$menu-icons-16x16-2x-db: -0px -96px 32px 32px;
|
||||||
|
$menu-icons-16x16-2x-document: -0px -128px 32px 32px;
|
||||||
|
$menu-icons-16x16-2x-gears: -0px -160px 32px 32px;
|
||||||
|
$menu-icons-16x16-2x-collection: -0px -192px 32px 32px;
|
||||||
|
$menu-icons-16x16-2x-information: -0px -224px 32px 32px;
|
||||||
|
$menu-icons-16x16-2x-network: -0px -256px 32px 32px;
|
||||||
|
$menu-icons-16x16-2x-pencil: -0px -288px 32px 32px;
|
||||||
|
$menu-icons-16x16-2x-picture: -0px -320px 32px 32px;
|
||||||
|
$menu-icons-16x16-2x-pie-chart: -0px -352px 32px 32px;
|
||||||
|
$menu-icons-24x24-home: -0px -0px 24px 24px;
|
||||||
|
$menu-icons-24x24-blog: -0px -24px 24px 24px;
|
||||||
|
$menu-icons-24x24-community: -0px -48px 24px 24px;
|
||||||
|
$menu-icons-24x24-db: -0px -72px 24px 24px;
|
||||||
|
$menu-icons-24x24-document: -0px -96px 24px 24px;
|
||||||
|
$menu-icons-24x24-gears: -0px -120px 24px 24px;
|
||||||
|
$menu-icons-24x24-collection: -0px -144px 24px 24px;
|
||||||
|
$menu-icons-24x24-information: -0px -168px 24px 24px;
|
||||||
|
$menu-icons-24x24-network: -0px -192px 24px 24px;
|
||||||
|
$menu-icons-24x24-pencil: -0px -216px 24px 24px;
|
||||||
|
$menu-icons-24x24-picture: -0px -240px 24px 24px;
|
||||||
|
$menu-icons-24x24-pie-chart: -0px -264px 24px 24px;
|
||||||
$menu-icons-24x24-2x-home: -0px -0px 48px 48px;
|
$menu-icons-24x24-2x-home: -0px -0px 48px 48px;
|
||||||
$menu-icons-24x24-2x-blog: -0px -48px 48px 48px;
|
$menu-icons-24x24-2x-blog: -0px -48px 48px 48px;
|
||||||
$menu-icons-24x24-2x-db: -0px -96px 48px 48px;
|
$menu-icons-24x24-2x-db: -0px -96px 48px 48px;
|
||||||
@ -204,15 +207,15 @@ $menu-icons-24x24-2x-pie-chart: -0px -480px 48px 48px;
|
|||||||
.icon-sprites-64x64-2x {
|
.icon-sprites-64x64-2x {
|
||||||
background-image: url('../images/sprites/dist/sprite-sprites-64x64-2x.png');
|
background-image: url('../images/sprites/dist/sprite-sprites-64x64-2x.png');
|
||||||
}
|
}
|
||||||
.icon-menu-icons-24x24 {
|
.icon-menu-icons-16x16 {
|
||||||
background-image: url('../images/sprites/dist/sprite-menu-icons-24x24.png');
|
background-image: url('../images/sprites/dist/sprite-menu-icons-16x16.png');
|
||||||
}
|
}
|
||||||
.icon-menu-icons-16x16-2x {
|
.icon-menu-icons-16x16-2x {
|
||||||
background-image: url('../images/sprites/dist/sprite-menu-icons-16x16-2x.png');
|
background-image: url('../images/sprites/dist/sprite-menu-icons-16x16-2x.png');
|
||||||
}
|
}
|
||||||
.icon-menu-icons-16x16 {
|
.icon-menu-icons-24x24 {
|
||||||
background-image: url('../images/sprites/dist/sprite-menu-icons-16x16.png');
|
background-image: url('../images/sprites/dist/sprite-menu-icons-24x24.png');
|
||||||
}
|
}
|
||||||
.icon-menu-icons-24x24-2x {
|
.icon-menu-icons-24x24-2x {
|
||||||
background-image: url('../images/sprites/dist/sprite-menu-icons-24x24-2x.png');
|
background-image: url('../images/sprites/dist/sprite-menu-icons-24x24-2x.png');
|
||||||
}
|
}
|
@ -29,7 +29,7 @@
|
|||||||
// $gray-dark: #373a3c;
|
// $gray-dark: #373a3c;
|
||||||
// $gray: #55595c;
|
// $gray: #55595c;
|
||||||
// $gray-light: #818a91;
|
// $gray-light: #818a91;
|
||||||
// $gray-lighter: #eceeef;
|
$gray-lighter: #e8e9ea;
|
||||||
// $gray-lightest: #f7f7f9;
|
// $gray-lightest: #f7f7f9;
|
||||||
//
|
//
|
||||||
// $brand-primary: #0275d8;
|
// $brand-primary: #0275d8;
|
||||||
|
@ -51,6 +51,11 @@
|
|||||||
@import "SecurityAdmin.scss";
|
@import "SecurityAdmin.scss";
|
||||||
@import "CMSSecurity.scss";
|
@import "CMSSecurity.scss";
|
||||||
|
|
||||||
|
/** -----------------------------
|
||||||
|
* Include React components' css
|
||||||
|
* ------------------------------ */
|
||||||
|
@import "../javascript/src/styles/main.scss";
|
||||||
|
|
||||||
/** -----------------------------
|
/** -----------------------------
|
||||||
* Retina graphics
|
* Retina graphics
|
||||||
* ----------------------------- */
|
* ----------------------------- */
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
* and leave the actual styling to _style.scss and auxilliary files.
|
* and leave the actual styling to _style.scss and auxilliary files.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@import "../bootstrap/variables.scss";
|
||||||
|
|
||||||
/** -----------------------------------------------
|
/** -----------------------------------------------
|
||||||
* Colours
|
* Colours
|
||||||
* ------------------------------------------------ */
|
* ------------------------------------------------ */
|
||||||
|
@ -295,6 +295,18 @@ $ npm run sanity
|
|||||||
|
|
||||||
`sanity` makes sure files in `thirdparty` match files copied from `node_modules`. You should never commit custom changes to a library file. This script will catch them if you do :smile:
|
`sanity` makes sure files in `thirdparty` match files copied from `node_modules`. You should never commit custom changes to a library file. This script will catch them if you do :smile:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
This script runs the JavaScript unit tests.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ npm run coverage
|
||||||
|
```
|
||||||
|
|
||||||
|
This script generates a coverage report for the JavaScript unit tests. The report is generated in the `coverage` directory.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ npm run css
|
$ npm run css
|
||||||
```
|
```
|
||||||
|
95
gulpfile.js
95
gulpfile.js
@ -10,7 +10,6 @@ var gulp = require('gulp'),
|
|||||||
autoprefixer = require('autoprefixer'),
|
autoprefixer = require('autoprefixer'),
|
||||||
browserify = require('browserify'),
|
browserify = require('browserify'),
|
||||||
babelify = require('babelify'),
|
babelify = require('babelify'),
|
||||||
watchify = require('watchify'),
|
|
||||||
source = require('vinyl-source-stream'),
|
source = require('vinyl-source-stream'),
|
||||||
buffer = require('vinyl-buffer'),
|
buffer = require('vinyl-buffer'),
|
||||||
path = require('path'),
|
path = require('path'),
|
||||||
@ -42,11 +41,7 @@ var PATHS = {
|
|||||||
// Folders which contain both scss and css folders to be compiled
|
// Folders which contain both scss and css folders to be compiled
|
||||||
var rootCompileFolders = [PATHS.FRAMEWORK, PATHS.ADMIN, PATHS.FRAMEWORK_DEV_INSTALL]
|
var rootCompileFolders = [PATHS.FRAMEWORK, PATHS.ADMIN, PATHS.FRAMEWORK_DEV_INSTALL]
|
||||||
|
|
||||||
var browserifyOptions = {
|
var browserifyOptions = {};
|
||||||
cache: {},
|
|
||||||
packageCache: {},
|
|
||||||
poll: true
|
|
||||||
};
|
|
||||||
|
|
||||||
// Used for autoprefixing css properties (same as Bootstrap Aplha.2 defaults)
|
// Used for autoprefixing css properties (same as Bootstrap Aplha.2 defaults)
|
||||||
var supportedBrowsers = [
|
var supportedBrowsers = [
|
||||||
@ -173,17 +168,23 @@ if (!semver.satisfies(process.versions.node, packageJson.engines.node)) {
|
|||||||
|
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
browserifyOptions.debug = true;
|
browserifyOptions.debug = true;
|
||||||
browserifyOptions.plugin = [watchify];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gulp.task('build', ['umd', 'bundle']);
|
gulp.task('build', ['umd', 'bundle'], function () {
|
||||||
|
if (isDev) {
|
||||||
|
gulp.watch([
|
||||||
|
PATHS.ADMIN_JAVASCRIPT_SRC + '/**/*.js',
|
||||||
|
PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/**/*.js',
|
||||||
|
], ['build']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task('bundle', ['bundle-lib', 'bundle-leftandmain', 'bundle-react']);
|
gulp.task('bundle', ['bundle-lib', 'bundle-leftandmain', 'bundle-boot', 'bundle-react', 'bundle-campaign-admin']);
|
||||||
|
|
||||||
gulp.task('bundle-leftandmain', function bundleLeftAndMain() {
|
gulp.task('bundle-leftandmain', function bundleLeftAndMain() {
|
||||||
return browserify(Object.assign({}, browserifyOptions, {
|
var bundleFileName = 'bundle-leftandmain.js';
|
||||||
entries: PATHS.ADMIN_JAVASCRIPT_SRC + '/bundles/leftandmain.js'
|
|
||||||
}))
|
return browserify(Object.assign({}, browserifyOptions, { entries: PATHS.ADMIN_JAVASCRIPT_SRC + '/bundles/leftandmain.js' }))
|
||||||
.transform(babelify.configure({
|
.transform(babelify.configure({
|
||||||
presets: ['es2015'],
|
presets: ['es2015'],
|
||||||
ignore: /(thirdparty)/,
|
ignore: /(thirdparty)/,
|
||||||
@ -194,18 +195,18 @@ gulp.task('bundle-leftandmain', function bundleLeftAndMain() {
|
|||||||
.external('i18n')
|
.external('i18n')
|
||||||
.external('router')
|
.external('router')
|
||||||
.bundle()
|
.bundle()
|
||||||
.on('update', bundleLeftAndMain)
|
.on('update', bundleLeftAndMain)
|
||||||
.on('error', notify.onError({ message: 'Error: <%= error.message %>' }))
|
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
||||||
.pipe(source('bundle-leftandmain.js'))
|
.pipe(source(bundleFileName))
|
||||||
.pipe(buffer())
|
.pipe(buffer())
|
||||||
.pipe(gulpif(!isDev, uglify()))
|
.pipe(gulpif(!isDev, uglify()))
|
||||||
.pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
|
.pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('bundle-lib', function bundleLib() {
|
gulp.task('bundle-lib', function bundleLib() {
|
||||||
return browserify(Object.assign({}, browserifyOptions, {
|
var bundleFileName = 'bundle-lib.js';
|
||||||
entries: PATHS.ADMIN_JAVASCRIPT_SRC + '/bundles/lib.js'
|
|
||||||
}))
|
return browserify(Object.assign({}, browserifyOptions, { entries: PATHS.ADMIN_JAVASCRIPT_SRC + '/bundles/lib.js' }))
|
||||||
.transform(babelify.configure({
|
.transform(babelify.configure({
|
||||||
presets: ['es2015'],
|
presets: ['es2015'],
|
||||||
ignore: /(thirdparty)/,
|
ignore: /(thirdparty)/,
|
||||||
@ -215,10 +216,11 @@ gulp.task('bundle-lib', function bundleLib() {
|
|||||||
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/jQuery.js', { expose: 'jQuery' })
|
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/jQuery.js', { expose: 'jQuery' })
|
||||||
.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 + '/router.js', { expose: 'router' })
|
||||||
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/reducer-register.js', { expose: 'reducer-register' })
|
||||||
.bundle()
|
.bundle()
|
||||||
.on('update', bundleLib)
|
.on('update', bundleLib)
|
||||||
.on('error', notify.onError({ message: 'Error: <%= error.message %>' }))
|
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
||||||
.pipe(source('bundle-lib.js'))
|
.pipe(source(bundleFileName))
|
||||||
.pipe(buffer())
|
.pipe(buffer())
|
||||||
.pipe(gulpif(!isDev, uglify()))
|
.pipe(gulpif(!isDev, uglify()))
|
||||||
.pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
|
.pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
|
||||||
@ -235,9 +237,54 @@ gulp.task('bundle-react', function bundleReact() {
|
|||||||
.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_DIST + '/SilverStripeComponent', { expose: 'silverstripe-component' })
|
||||||
.bundle()
|
.bundle()
|
||||||
.on('update', bundleReact)
|
.on('update', bundleReact)
|
||||||
.on('error', notify.onError({ message: 'Error: <%= error.message %>' }))
|
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
||||||
.pipe(source('bundle-react.js'))
|
.pipe(source(bundleFileName))
|
||||||
|
.pipe(buffer())
|
||||||
|
.pipe(gulpif(!isDev, uglify()))
|
||||||
|
.pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('bundle-boot', function bundleBoot() {
|
||||||
|
var bundleFileName = 'boot.js';
|
||||||
|
|
||||||
|
return browserify(Object.assign({}, browserifyOptions, { entries: PATHS.ADMIN_JAVASCRIPT_SRC + '/boot/index.js' }))
|
||||||
|
.transform(babelify.configure({
|
||||||
|
presets: ['es2015'],
|
||||||
|
ignore: /(node_modules)/
|
||||||
|
}))
|
||||||
|
.external('reducer-register')
|
||||||
|
.bundle()
|
||||||
|
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
||||||
|
.pipe(source(bundleFileName))
|
||||||
|
.pipe(buffer())
|
||||||
|
.pipe(gulpif(!isDev, uglify()))
|
||||||
|
.pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('bundle-campaign-admin', function bundleCampaignAdmin() {
|
||||||
|
var bundleFileName = 'campaign-admin.js';
|
||||||
|
|
||||||
|
return browserify(Object.assign({}, browserifyOptions, { entries: PATHS.ADMIN_JAVASCRIPT_SRC + '/boot/campaign-admin.js' }))
|
||||||
|
.transform(babelify.configure({
|
||||||
|
presets: ['es2015', 'react'],
|
||||||
|
ignore: /(node_modules)/
|
||||||
|
}))
|
||||||
|
.external('deep-freeze')
|
||||||
|
.external('i18n')
|
||||||
|
.external('jQuery')
|
||||||
|
.external('page.js')
|
||||||
|
.external('react')
|
||||||
|
.external('react-addons-test-utils')
|
||||||
|
.external('react-dom')
|
||||||
|
.external('react-redux')
|
||||||
|
.external('redux')
|
||||||
|
.external('redux-thunk')
|
||||||
|
.external('silverstripe-component')
|
||||||
|
.external('reducer-register')
|
||||||
|
.bundle()
|
||||||
|
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
||||||
|
.pipe(source(bundleFileName))
|
||||||
.pipe(buffer())
|
.pipe(buffer())
|
||||||
.pipe(gulpif(!isDev, uglify()))
|
.pipe(gulpif(!isDev, uglify()))
|
||||||
.pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
|
.pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
|
||||||
|
52
package.json
52
package.json
@ -12,11 +12,13 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "gulp build",
|
"build": "gulp build",
|
||||||
"bundle": "gulp bundle",
|
"bundle": "gulp bundle",
|
||||||
"sanity": "gulp sanity",
|
"coverage": "jest --coverage",
|
||||||
"thirdparty": "gulp thirdparty",
|
|
||||||
"css": "gulp css",
|
"css": "gulp css",
|
||||||
|
"lock": "npm-shrinkwrap --dev",
|
||||||
|
"sanity": "gulp sanity",
|
||||||
"sprites": "gulp sprites",
|
"sprites": "gulp sprites",
|
||||||
"lock": "npm-shrinkwrap --dev"
|
"test": "jest",
|
||||||
|
"thirdparty": "gulp thirdparty"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -35,8 +37,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^6.3.1",
|
"autoprefixer": "^6.3.1",
|
||||||
"babel-core": "^6.4.0",
|
"babel-core": "^6.4.0",
|
||||||
|
"babel-jest": "^9.0.3",
|
||||||
"babel-plugin-transform-es2015-modules-umd": "^6.4.0",
|
"babel-plugin-transform-es2015-modules-umd": "^6.4.0",
|
||||||
"babel-preset-es2015": "^6.3.13",
|
"babel-preset-es2015": "^6.6.0",
|
||||||
|
"babel-preset-react": "^6.5.0",
|
||||||
"babelify": "^7.2.0",
|
"babelify": "^7.2.0",
|
||||||
"browserify": "^13.0.0",
|
"browserify": "^13.0.0",
|
||||||
"event-stream": "^3.3.2",
|
"event-stream": "^3.3.2",
|
||||||
@ -51,29 +55,33 @@
|
|||||||
"gulp-sourcemaps": "^1.6.0",
|
"gulp-sourcemaps": "^1.6.0",
|
||||||
"gulp-uglify": "^1.5.1",
|
"gulp-uglify": "^1.5.1",
|
||||||
"gulp-util": "^3.0.7",
|
"gulp-util": "^3.0.7",
|
||||||
|
"jest-cli": "^0.9.2",
|
||||||
|
"npm-shrinkwrap": "^5.4.1",
|
||||||
|
"react-addons-test-utils": "^0.14.6",
|
||||||
|
"redux-logger": "^2.6.1",
|
||||||
"semver": "^5.1.0",
|
"semver": "^5.1.0",
|
||||||
"sprity": "^1.0.8",
|
"sprity": "^1.0.8",
|
||||||
"sprity-sass": "^1.0.4",
|
"sprity-sass": "^1.0.4",
|
||||||
"vinyl-buffer": "^1.0.0",
|
"vinyl-buffer": "^1.0.0",
|
||||||
"vinyl-source-stream": "^1.1.0",
|
"vinyl-source-stream": "^1.1.0",
|
||||||
"watchify": "^3.7.0"
|
"watchify": "^3.7.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"jest": {
|
||||||
"blueimp-file-upload": "^6.0.3",
|
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
|
||||||
"blueimp-load-image": "^1.1.3",
|
"testPathDirs": [
|
||||||
"blueimp-tmpl": "^1.0.2",
|
"admin/javascript/src"
|
||||||
"bootstrap": "^4.0.0-alpha.2",
|
],
|
||||||
"isomorphic-fetch": "^2.2.1",
|
"testDirectoryName": "tests",
|
||||||
"jquery-sizes": "^0.33.0",
|
"mocksPattern": "mocks",
|
||||||
"json-js": "^1.1.2",
|
"unmockedModulePathPatterns": [
|
||||||
"npm-shrinkwrap": "^5.4.1",
|
"<rootDir>/node_modules/react"
|
||||||
"page.js": "^4.13.3",
|
],
|
||||||
"react": "^0.14.6",
|
"bail": true,
|
||||||
"react-addons-test-utils": "^0.14.6",
|
"testRunner": "<rootDir>/node_modules/jest-cli/src/testRunners/jasmine/jasmine2.js"
|
||||||
"react-dom": "^0.14.6",
|
},
|
||||||
"react-redux": "^4.0.6",
|
"babel": {
|
||||||
"redux": "^3.0.5",
|
"presets": [
|
||||||
"redux-thunk": "^1.0.3",
|
"es2015"
|
||||||
"tinymce": "^4.3.3"
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user