application/components/admin/ssts.js
import React, {Component} from 'react';
import _ from 'lodash';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';
import LoadingIndicator from './../common/loading-indicator';
import Tree from 'react-ui-tree';
import {connect} from 'react-redux';
import ExpandingCard from './../expanding-card';
import {
snackbarShowMessage
} from './../../redux/actions/snackbar-actions';
import {
sstssFetch,
sstsCreate,
sstsUpdateMultiple
} from './../../redux/actions/ssts-actions';
function buildTree(arr) {
let roots = [];
let children = {};
// find the top level nodes and hash the children based on parent
for (let i = 0, len = arr.length; i < len; ++i) {
let item = arr[i];
let p = item.parent_id;
let target = !p ? roots : (children[p] || (children[p] = []));
target.push(item);
}
// function to recursively build the tree
const findChildren = function(parent) {
if (children[parent.id]) {
parent.children = children[parent.id];
for (let i = 0, len = parent.children.length; i < len; ++i) {
findChildren(parent.children[i]);
}
}
};
// enumerate through to handle the case where there are multiple roots
for (let i = 0, len = roots.length; i < len; ++i) {
findChildren(roots[i]);
}
return roots;
}
class SstsCard extends Component {
constructor(props) {
super(props);
this.state = {
expanded: false,
hasLoaded: false,
activeNode: null,
modalOpen: false,
newLabel: null,
isDirty: false,
tree: {
'module': 'Sections',
'children': []
}
};
}
fetchTree() {
const {dispatch} = this.props;
dispatch(sstssFetch({'limit': 100, 'per_page': 100}))
.then(() => this.setState({hasLoaded: true}))
.then(() => {
let nodes = [];
this.props.ssts.items.map((item) => {
nodes.push({
'module': item.get('label'),
'slug': item.get('slug'),
'uuid': item.get('uuid'),
'id': item.get('id'),
'parent_id': item.get('parent_id')
});
});
const newTree = Object.assign(this.state.tree, {}, {
'children': buildTree(nodes)
});
this.setState({tree: newTree});
});
}
addNode(e) {
this.setState({
newLabel: null,
modalOpen: true
});
}
renderNode(node) {
let klasses = ['tree-leaf'];
if (node == this.state.activeNode) {
klasses.push('is-active');
}
return (
<span className={klasses.join(' ')} onClick={this.onClickNode.bind(this, node)}>
{node.module}
</span>
);
}
onClickNode(node) {
this.setState({activeNode: node});
}
onTreeChange(tree) {
this.setState({tree: tree, isDirty: true});
}
handleToggle(expanded) {
if (this.props.onToggle) {
this.props.onToggle('ssts', expanded);
}
if (expanded && !this.state.hasLoaded) {
this.fetchTree.call(this);
}
}
handleRemoveNode(node) {
}
handleTreeSave(e) {
const {dispatch} = this.props;
let roots = [];
const flattenTree = function(nodes, parent) {
let node;
let newNode;
for (let i = 0; i < nodes.length; i++) {
node = nodes[i];
newNode = {
'label': node.module,
'slug': node.slug,
'uuid': node.uuid,
'id': node.id
};
if (parent) {
newNode['parent_id'] = parent.id
}
if (node.children && node.children.length) {
flattenTree(node.children, node);
}
roots.push(newNode);
}
}
flattenTree(this.state.tree.children, null);
dispatch(sstsUpdateMultiple(roots))
.then(() => this.setState({isDirty: false}))
.then(() => this.fetchTree.call(this))
.then(() => dispatch(snackbarShowMessage('Settings updated.')));
}
handleModalClose(e) {
this.setState({
modalOpen: false
});
}
handleModalSave(e) {
var {dispatch} = this.props;
dispatch(sstsCreate({'label': this.state.newLabel}))
.then(() => {
this.setState({
hasLoaded: false
});
})
.then(() => {
this.setState({
modalOpen: false
});
})
.then(() => {
this.fetchTree.call(this);
});
}
handleLabelUpdate(e) {
this.setState({
newLabel: e.target.value
});
}
render() {
if (!this.props.application.settings.get('ssts')) {
return <span></span>;
}
const actions = [
<FlatButton
label='Cancel'
primary={true}
onClick={this.handleModalClose.bind(this)}
/>,
<FlatButton
label='Save'
primary={true}
onClick={this.handleModalSave.bind(this)}
/>
];
return (
<ExpandingCard
onToggle={this.handleToggle.bind(this)}
title="Section/Subsection/Topic/Subtopic"
subtitle="SSTS Classification Module"
expanded={this.props.openCard === 'ssts'}
>
<p>Section/Subsection/Topic/Subtopic is a <a href="https://snworks.zendesk.com/hc/en-us/articles/360028705152" target="_blank">more rigid content classification system</a> that can be used along side, or in place of, tags to place content on your site.</p>
{
!this.state.hasLoaded
? <LoadingIndicator />
: (
<div className="ssts-container">
<RaisedButton
onClick={this.addNode.bind(this)}
label='Add Section'
/>
<RaisedButton
onClick={this.handleTreeSave.bind(this)}
disabled={!this.state.isDirty}
label='Save Changes'
primary={true}
/>
<div class="tree-container">
<Tree
paddingLeft={20}
tree={this.state.tree}
onChange={this.onTreeChange.bind(this)}
renderNode={this.renderNode.bind(this)}
/>
</div>
</div>
)
}
<Dialog
title='New Label'
actions={actions}
modal={true}
open={this.state.modalOpen}
>
<TextField
floatingLabelText='Label'
value={this.state.newLabel}
onChange={this.handleLabelUpdate.bind(this)}
fullWidth={true}
/>
</Dialog>
</ExpandingCard>
);
}
}
let mapStateToProps = (state) => {
return {
application: state.application,
ssts: state.sstss
};
};
export default connect(mapStateToProps)(SstsCard);