Home Reference Source

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);