@bigfishtv/cockpit
Version:
236 lines (216 loc) • 6.28 kB
JavaScript
import React, { Component } from 'react'
import classnames from 'classnames'
import { connect } from 'react-redux'
import isEqual from 'lodash/isEqual'
import { get } from '../../api/xhrUtils'
import { userCanAccess } from '../../utils/roleUtils'
import { collectValues, getChildByKeyValue } from '../../utils/treeUtils'
import Button from '../button/Button'
import Icon from '../Icon'
import Tree from '../tree/Tree'
import Modal from '../modal/Modal'
import Spinner from '../Spinner'
function markRecursive(children) {
children.map(page => {
page.userCanAccess = true
markRecursive(page.children)
})
}
function filterData(data, user) {
return data.map(page => {
page.userCanAccess = userCanAccess([{ model: 'Pages', foreign_key: page.id }], user)
if (page.userCanAccess) {
markRecursive(page.children)
} else {
page.children = filterData(page.children, user)
}
return page
})
}
const ModalToolbar = props => {
const { allCollapsed, collapsedIds, onCollapseAll, onExpandAll } = props.modalToolbarProps
return (
<div>
<Button text="Collapse All" onClick={onCollapseAll} disabled={allCollapsed} />
<Button text="Expand All" onClick={onExpandAll} disabled={!collapsedIds.length} />
</div>
)
}
const ModalActions = props => {
const { selectedId, primaryActionText, onSave, onClose } = props.modalActionProps
return (
<div>
<Button text={primaryActionText} style="primary" onClick={onSave} disabled={selectedId === null} />
<Button text="Cancel" onClick={onClose} />
</div>
)
}
const TreeCell = props => {
const {
title,
status,
isCollapsed,
selectedDrag,
showIndicator,
onIndicatorClick,
onIndicatorDoubleClick,
isOver,
position,
onClick,
onDoubleClick,
selected,
} = props
const userCanAccess = props.userCanAccess !== false
return (
<div className={classnames('tree-item', isOver && 'drag-' + position)}>
<div
className={classnames('tree-cell', { dragging: selectedDrag, selected: selected, disabled: !userCanAccess })}
onClick={userCanAccess ? onClick : null}
onDoubleClick={userCanAccess ? onDoubleClick : null}>
<div className="tree-cell-icon">
{showIndicator && (
<div
className={classnames('tree-cell-control', isCollapsed && 'collapsed')}
onClick={onIndicatorClick}
onDoubleClick={onIndicatorDoubleClick}>
<Icon name={'chevron-' + (isCollapsed ? 'right' : 'down')} size="18" />
</div>
)}
</div>
{status !== undefined && (
<div className="tree-cell-status">
<div className={classnames('status', status)} />
</div>
)}
{!userCanAccess && (
<div className="tree-cell-icon">
<Icon name="lock" size={12} />
</div>
)}
<div className={classnames('tree-cell-title', { disabled: !userCanAccess })}>{title}</div>
</div>
</div>
)
}
export default class TreeModal extends Component {
constructor() {
super()
this.state = {
data: [],
selectedId: null,
collapsedIds: [],
selectedIds: [],
allCollapsed: false,
loading: true,
}
}
static defaultProps = {
data: [],
url: '/admin/pages.json',
title: 'Select Parent Page',
primaryActionText: 'Select Page',
startCollapsed: true,
filterData,
size: 'small',
}
componentDidMount() {
const { url } = this.props
if (!url) {
if (this.props.data && this.props.data.length > 0) {
this.setState({ loading: false, data: this.props.data }, () => {
if (this.props.startCollapsed) this.handleCollapseAll()
})
}
return
}
get({
url,
quiet: true,
callback: data =>
this.setState({ data: this._filterData(data), loading: false }, () => {
if (this.props.startCollapsed) this.handleCollapseAll()
}),
errorCallback: data => console.warn('Error getting pages', data),
})
}
_filterData(data) {
if (!this.props.filterData) {
return data
}
return this.props.filterData(data, this.props.viewer)
}
handleSelectionChange = selectedIds => {
const selectedId = selectedIds.length ? selectedIds[0] : null
this.setState({ selectedId, selectedIds })
}
handleCollapseChange = collapsedIds => {
const collapsableIds = collectValues(this.state.data, 'id', item => item.children && item.children.length > 0)
const allCollapsed = isEqual(collapsableIds.sort(), collapsedIds.sort())
this.setState({ collapsedIds, allCollapsed })
}
handleCollapseAll = () => {
const collapsedIds = collectValues(this.state.data, 'id', item => item.children && item.children.length > 0)
this.setState({ collapsedIds, allCollapsed: true })
}
handleSelectedItem = item => {
this.props.onSave(item)
this.props.closeModal()
}
handleCombinationChange = mixed => {
const newState = {}
Object.keys(mixed).map(key => {
if (key in this.state) newState[key] = mixed[key]
})
this.setState(newState)
}
handleSave = () => {
this.handleSelectedItem(getChildByKeyValue(this.state.data, 'id', this.state.selectedId))
}
handleClose = () => {
this.props.onClose()
this.props.closeModal()
}
render() {
const modalToolbarProps = {
...this.state,
onCollapseAll: this.handleCollapseAll,
onExpandAll: () => this.setState({ collapsedIds: [], allCollapsed: false }),
}
const modalActionProps = {
...this.state,
primaryActionText: this.props.primaryActionText,
onSave: this.handleSave,
onClose: this.handleClose,
}
return (
<Modal
title={this.props.title}
size={this.props.size}
ModalToolbar={ModalToolbar}
modalToolbarProps={modalToolbarProps}
ModalActions={ModalActions}
modalActionProps={modalActionProps}
onClose={this.handleClose}
onSave={this.handleSave}>
{this.state.loading ? (
<div className="loader-center">{<Spinner />}</div>
) : (
<Tree
value={this.state.data}
TreeCell={TreeCell}
multiselect={false}
reorderable={false}
breadcrumbs={false}
selectedIds={this.state.selectedIds}
onSelectItem={this.handleSelectedItem}
onSelectionChange={this.handleSelectionChange}
collapsedIds={this.state.collapsedIds}
onCollapseChange={this.handleCollapseChange}
onCombinationChange={this.handleCombinationChange}
/>
)}
</Modal>
)
}
}