UNPKG

acklen-keystone

Version:

Web Application Framework and Admin GUI / Content Management System built on Express.js and Mongoose

294 lines (258 loc) 7.3 kB
import React, { Component } from 'react'; import SortableTree, { getFlatDataFromTree } from 'react-sortable-tree'; import { browserHistory } from 'react-router'; export default class TreeView extends Component { constructor (props) { super(props); this.state = { treeData: this.loadData(props.items), }; this.nodeMoved = this.nodeMoved.bind(this); this.save = this.save.bind(this); this.canDrop = this.canDrop.bind(this); this.getFlatData = this.getFlatData.bind(this); this.updateContentDimensions = this.updateContentDimensions.bind(this); this.addMenu = this.addMenu.bind(this); this.deleteMenu = this.deleteMenu.bind(this); } componentDidMount () { window.addEventListener('resize', this.updateContentDimensions); this.updateContentDimensions(); } componentWillReceiveProps (nextProps) { if (nextProps.items !== this.props.items) { this.state = { treeData: this.loadData(nextProps.items), }; } } componentWillUnmount () { window.removeEventListener('resize', this.updateContentDimensions); } updateContentDimensions (alerts) { const element = document.querySelector('#tree-view-container'); const height = window.innerHeight; if (element) { element.style.height = `${height - 380}px`; } } addMenu (node) { const nodeParent = this.state.treeData .filter((item) => item.id === node.parent.id); const defaultValues = { parent: { value: node.parent.id, className: 'disabled-field', }, type: { value: nodeParent.length > 0 ? 'content' : 'option', className: 'hide-field', }, }; this.props.onAddMenu(defaultValues); } editMenu (node) { browserHistory.push(`/keystone/${this.props.listId}/${node.id}`); } deleteMenu (node, e) { this.props.onDeleteMenu(node, e); } findMenuItem (parent, id) { if (parent.id === id) { return parent; } else if (parent.children) { let result = null; for (let i = 0; result === null && i < parent.children.length; ++i) { const child = parent.children[i]; result = this.findMenuItem(child, id); } return result; } return null; } buildTreeView (arr) { const treeView = []; const children = {}; for (let i = 0; i < arr.length; ++i) { const item = arr[i]; const p = item.fields.parent; const target = !p ? treeView : (children[p.id] || (children[p.id] = [])); let truncatedName = item.name; if (truncatedName.length > 70) { truncatedName = `${truncatedName.substr(0, 66)}...`; } let truncatedTitle = item.fields.title; if (truncatedTitle && truncatedTitle.title > 100) { truncatedTitle = `${truncatedTitle.substr(0, 96)}...`; } let itemFound; if (this.treeView && this.treeView.props.treeData) { this.treeView.props.treeData.forEach((parent) => { if (!itemFound) { itemFound = this.findMenuItem(parent, item.id); } if (parent.id === item.id) { itemFound = parent; } }); } target.push({ id: item.id, title: truncatedName, subtitle: truncatedTitle, expanded: itemFound ? itemFound.expanded : true, canDrag: item.fields.parent, parent: item.fields.parent, state: item.fields.state, }); } const findChildren = (parent) => { if (children[parent.id]) { parent.children = children[parent.id]; for (let i = 0; i < parent.children.length; ++i) { findChildren(parent.children[i]); } } }; for (let i = 0; i < treeView.length; ++i) { findChildren(treeView[i]); } return treeView; } canShowAddMenu (path) { return path.length > 0 && path.length < 4; } canShowDeleteMenu (node) { return node.parent && this.props.listId === 'menus'; } canDrag ({ node }) { return node.canDrag; }; canDrop ({ node, nextParent, prevPath, nextPath }) { let canDrop = nextPath.length > 1 && nextPath.length < 5; const flatData = this.getFlatData(); if (nextPath.length === 3) { const children = flatData.filter((item) => item.parent === node.id); let grandChildren = true; children.forEach((child) => { if (grandChildren) { grandChildren = flatData .filter((item) => item.parent === child.id).length === 0; } }); canDrop = grandChildren; } if (nextPath.length === 4) { const children = flatData.filter((item) => item.parent === node.id); canDrop = children.length === 0; } return nextParent && canDrop; } canShowEditMenu (node) { return node.parent; } isOnDraftState (node) { return node.state === 'draft'; } nodeMoved ({ treeData, node }) { const findChildren = (parent) => { if (parent.children) { for (let i = 0; i < parent.children.length; ++i) { const child = parent.children[i]; if (node.id === child.id) { child.parent = { id: parent.id, name: parent.name, }; } findChildren(parent.children[i]); } } }; const newTreeData = treeData.map((item) => { const newItem = item; findChildren(item); return newItem; }); this.setState({ treeData: newTreeData }); this.save(); } loadData (items) { return this.buildTreeView(items); } getFlatData () { return getFlatDataFromTree({ treeData: this.state.treeData, getNodeKey: ({ node }) => node.id, ignoreCollapsed: false, }).map(({ node, parentNode, treeIndex }) => ({ id: node.id, name: node.title, title: node.subtitle, parent: parentNode ? parentNode.id : null, order: treeIndex, })); } save () { const flatData = this.getFlatData(); this.props.onSave(flatData); } render () { return ( <div id="tree-view-container"> <SortableTree ref={(treeView) => { this.treeView = treeView; }} canDrag={this.canDrag} canDrop={this.canDrop} treeData={this.state.treeData} onChange={treeData => this.setState({ treeData })} onMoveNode={this.nodeMoved} generateNodeProps={({ node, path }) => ({ buttons: [ <div key={node} style={{ marginRight: 10 }}> { this.isOnDraftState(node) ? ( <span style={{ color: 'red' }}> draft </span>) : null } </div>, <div key={node} style={{ marginRight: 10 }}> { this.canShowAddMenu(path) ? ( <button onClick={() => this.addMenu({ parent: node })} disabled={this.props.loading} style={this.props.loading ? { color: 'lightgray' } : null} > <span className="octicon octicon-plus" /> </button>) : null } </div>, <div key={node} style={{ marginRight: 10 }}> { this.canShowEditMenu(node) ? ( <button onClick={() => this.editMenu(node)} disabled={this.props.loading} style={this.props.loading ? { color: 'lightgray' } : null} > <span className="octicon octicon-pencil" /> </button>) : null } </div>, <div key={node}> { this.canShowDeleteMenu(node) ? ( <button onClick={(e) => this.deleteMenu(node, e)} disabled={this.props.loading} style={this.props.loading ? { color: 'lightgray' } : null} > <span className="octicon octicon-trashcan" /> </button>) : null } </div>, ], })} /> </div> ); } }