@bigfishtv/cockpit
Version:
222 lines (200 loc) • 6.53 kB
JavaScript
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { withFormValue, update } from '@bigfishtv/react-forms'
import deepDuplicate from '../../utils/deepDuplicate'
import newId from '../../utils/newId'
import { modalHandler } from '../modal/ModalHost'
import Breadcrumb from '../breadcrumb/Breadcrumb'
import SectionTray from '../SectionTray'
import Section from '../container/Section'
import ReorderableCellsModal from '../modal/ReorderableCellsModal'
import SectionsPublishModal from '../modal/SectionsPublishModal'
/*
Section types data format:
[
{
property: 'example_sections',
title: 'Example Section Title',
icon: 'example-icon',
component: ExampleSection,
}
]
*/
// we define this because react-docgen fails when defaultProp directly references an imported component
const DefaultSectionTray = props => <SectionTray {...props} />
export default class Sections extends Component {
static propTypes = {
SectionTypes: PropTypes.array,
formValue: PropTypes.object,
showTray: PropTypes.bool,
}
static defaultProps = {
collapsible: true,
reorderable: true,
removable: true,
duplicable: true,
publishable: false,
showTray: true,
getBlankSection: (sectionType, formValue) => ({}),
SectionTray: DefaultSectionTray,
getSectionTitle: (section, sectionType) =>
section.title || section.name || section.label || <em>{sectionType.title}</em>,
}
constructor() {
super()
this.state = {
collapsed: false,
}
}
addSection = sectionType => {
const { formValue, getBlankSection } = this.props
if (!sectionType.property) {
console.warn("sectionType.property doesn't exist for " + sectionType.title)
return
}
const values = formValue.select(sectionType.property)
const value = values.value && values.value.length ? values.value.slice() : []
value.push({
...getBlankSection(sectionType, formValue),
id: newId(),
order: this.getNextOrderId(),
isNew: true,
})
values.update(value)
}
duplicateSection(sectionType, index) {
let formValue = this.props.formValue
let value = formValue.value
// update order for all sections greater duplicated section
const { order } = value[sectionType.property][index]
this.getSections()
.filter(_s => _s.section.order > order)
.forEach(_s => {
const path = [_s.sectionType.property, _s.index, 'order']
value = update(value, path, formValue.select(path).value + 1)
})
// duplicate the section
let sections = formValue.select(sectionType.property).value
const section = {
...deepDuplicate(sections[index]),
order: order + 1,
}
// splice new section into existing array
sections = [...sections.slice(0, index + 1), section, ...sections.slice(index + 1)]
// update sections array into value
value = update(value, [sectionType.property], sections)
// update formValue in one fell swoop
formValue.update(value)
}
removeSection(sectionType, index) {
if (!confirm('Are you sure you want to remove this?')) {
return
}
const values = this.props.formValue.select(sectionType.property)
const value = values.value.filter((val, i) => i !== index)
values.update(value)
}
reorderSections(selectedSection) {
const sections = this.getSections().map(section => ({
id: section.sectionType.property + section.index,
index: section.index,
property: section.sectionType.property,
title: this.props.getSectionTitle(section.section, section.sectionType),
}))
modalHandler.add({
Component: ReorderableCellsModal,
props: {
value: sections,
onSave: this.handleReorder,
onClose: () => {},
},
})
}
handleReorder = newList => {
let { formValue } = this.props
formValue.update(
newList.reduce((acc, item, order) => update(acc, [item.property, item.index, 'order'], order), formValue.value)
)
}
publishSections(formValue, sections) {
modalHandler.add({
Component: SectionsPublishModal,
props: {
formValue,
sections,
},
})
}
getNextOrderId() {
const sections = this.getSections()
return sections.length ? sections[sections.length - 1].section.order + 1 : 0
}
/**
* Combine all sections into a single array of objects with { sectionType, section, index } keys
* ordered by section.order
*
* It ignores section types that don't have a property or component value.
*
* @return array The array of sections
*/
getSections() {
const { SectionTypes, formValue } = this.props
const val = formValue.value
const typesByProperty = {}
// filter out duplicate section types or types without components, etc
SectionTypes.forEach(sectionType => {
if (
sectionType.component &&
sectionType.property &&
!typesByProperty[sectionType.property] &&
val &&
val[sectionType.property] &&
val[sectionType.property].length
) {
typesByProperty[sectionType.property] = sectionType
}
})
// join sections together and sort
return Object.keys(typesByProperty)
.reduce((acc, sectionTypeProperty) => {
const sectionType = typesByProperty[sectionTypeProperty]
return acc.concat(val[sectionType.property].map((section, index) => ({ sectionType, section, index })))
}, [])
.sort((a, b) => a.section.order - b.section.order)
}
renderSection = ({ sectionType, section, index }, i, allSections) => {
const { SectionTypes, formValue, select, ...props } = this.props
const sectionFormValue = formValue.select([sectionType.property, index])
return (
<Breadcrumb
title={section.title || sectionType.title}
key={`${sectionType.property}${index}${this.state.collapsed ? 'a' : 'b'}`}>
<Section
{...props}
Component={sectionType.component}
icon={sectionType.icon}
sectionProperty={sectionType.property}
formValue={sectionFormValue}
collapsed={this.state.collapsed}
onToggleCollapsed={() => this.setState({ collapsed: !this.state.collapsed })}
onDuplicate={() => this.duplicateSection(sectionType, index)}
onRemove={() => this.removeSection(sectionType, index)}
onReorder={() => this.reorderSections(section)}
onPublishEdit={() => this.publishSections(sectionFormValue, allSections)}
isNew={section.isNew}
allSections={allSections}
/>
</Breadcrumb>
)
}
render() {
const { SectionTypes, SectionTray, showTray } = this.props
return (
<div>
{this.getSections().map(this.renderSection)}
{showTray && <SectionTray SectionTypes={SectionTypes} addSection={this.addSection} />}
</div>
)
}
}