UNPKG

@bigfishtv/cockpit

Version:

394 lines (365 loc) 12.1 kB
import React, { Component } from 'react' import { Fieldset } from '@bigfishtv/react-forms' import deepEqual from 'deep-equal' import classnames from 'classnames' import _get from 'lodash/get' import _truncate from 'lodash/truncate' import * as xhrUtils from '../../api/xhrUtils' import { showDeletePrompt } from '../../utils/promptUtils' import { titleCase } from '../../utils/stringUtils' import { modalHandler } from '../modal/ModalHost' import { flatten, getParentByChildId, sortByKey, pruneTree, appendChildToParent, replaceChild, } from '../../utils/treeUtils' import modalFormValueContext from '../../decorators/modalFormValueContext' import { AutoTableIndexContainer, AutoTableIndexBase } from './AutoTableIndex' import MainContent from '../container/MainContent' import Bulkhead from '../page/Bulkhead' import Panel from '../container/panel/Panel' import Tree from '../tree/Tree' import Field from '../form/Field' import Modal from '../modal/Modal' import TreeSelectModal from '../modal/TreeSelectModal' import Button from '../button/Button' import DropdownItem from '../button/dropdown/DropdownItem' import DropdownItemDivider from '../button/dropdown/DropdownItemDivider' import DropdownButton from '../button/dropdown/DropdownButton' import Icon from '../Icon' import TreeModal from '../modal/TreeModal' const DefaultBulkheadToolbar = ({ modelLabel, model, addUrl }) => ( <Button text={'New ' + titleCase(modelLabel || model)} onClick={() => (window.location.href = addUrl)} style="primary" size="large" /> ) // used in sidebar const DefaultTreeCell = props => { const { id, title, locked, isCollapsed, showIndicator, onIndicatorClick, onIndicatorDoubleClick, isOver, position, onClick, onDoubleClick, selected, data, } = props const items = id === null ? data : data.filter(({ folder_id }) => (id === '_unsorted' ? folder_id === null : folder_id === id)) return ( <div className={classnames('tree-item', isOver && 'drag-' + position)}> <div className={classnames('tree-cell tree-cell-small', { selected: selected })} onClick={onClick} onDoubleClick={onDoubleClick}> {showIndicator ? ( <div className={classnames('tree-cell-icon', isCollapsed && 'collapsed')} onClick={onIndicatorClick} onDoubleClick={onIndicatorDoubleClick}> <Icon name={'chevron-' + (isCollapsed ? 'right' : 'down')} size={18} /> </div> ) : ( <div className="tree-cell-icon" /> )} {locked && ( <div className="tree-cell-icon"> <Icon name="lock" size={12} /> </div> )} <div className="tree-cell-title"> {_truncate(title, { length: 45 })} ({items.length}) </div> </div> </div> ) } // used in sidebar const DefaultTray = props => { const { onFolderAdd, onFolderDelete, onEdit, currentFolderId } = props return ( <div className="finder-menu-footer"> <Button size="small" text="Add Folder" onClick={onFolderAdd} /> <Button size="small" text="Delete" onClick={onFolderDelete} /> <Button size="small" text="Edit" onClick={onEdit} disabled={currentFolderId === null} /> </div> ) } export const DefaultFolderSidebarToolbar = props => ( <DropdownButton style="icon" caret={false} pullRight={true} text={<Icon name="hamburger" />}> <DropdownItem text="New Folder" icon="folder-new" onClick={props.handleFolderAdd} /> <DropdownItem text="Edit" icon="edit" onClick={props.handleFolderEdit} disabled={!props.currentFolderId} /> <DropdownItemDivider /> <DropdownItem text="Remove" icon="remove" onClick={props.handleFolderDelete} disabled={!props.currentFolderId} /> </DropdownButton> ) @modalFormValueContext export class DefaultFolderEditModal extends Component { static defaultProps = { isNew: false, } handleSave = () => { this.props.onSave(this.props.formValue, this.props.isNew) } handleClose = (didSave = false) => { this.props.onClose(this.props.formValue, didSave, this.props.isNew) } render() { const { foldersUrl } = this.props const { title } = this.props.formValue.value return ( <Modal {...this.props} title={title || 'New Folder'} size="small" onClose={this.handleClose} onSave={this.handleSave}> <Fieldset formValue={this.props.formValue}> <Field select="title" label="Title" placeholder="Title" autoFocus /> <Field select="parent" label="Parent Folder"> <TreeSelectModal buttonText="Select Folder" modalProps={{ url: foldersUrl, title: 'Select Parent Folder', primaryActionText: 'Select Parent Folder', filterData: (data, user) => sortByKey(data, 'title'), }} /> </Field> </Fieldset> </Modal> ) } } export default class AutoFolderTableIndex extends Component { static defaultProps = { Tray: DefaultTray, TreeCell: DefaultTreeCell, BulkheadToolbar: DefaultBulkheadToolbar, SidebarToolbar: DefaultFolderSidebarToolbar, FolderEditModal: DefaultFolderEditModal, foldersUrl: '/tank/folders.json', folderDeleteUrl: '/tank/folders/delete.json', getFolderSubmitUrl: data => `/tank/folders/${data.id ? 'edit/' + data.id : 'add'}.json`, componentResolver: () => {}, attributeModified: () => {}, newFolderDefaultValue: {}, } constructor(props) { super(props) this.state = { foldersData: props.folders || [], currentFolderId: null, collapsedFolderIds: [], } } handleFolderSelection = (ids, handleFilterChange) => { const currentFolderId = ids[0] this.setState( { currentFolderId }, () => handleFilterChange && handleFilterChange('folder_id', currentFolderId == '_unsorted' ? val => val === null : currentFolderId) ) } handleFolderCollapse = collapsedIds => { this.setState({ collapsedIds }) } handleFolderAdd = () => { console.log('opening folder add modal') modalHandler.add({ Component: this.props.FolderEditModal, props: { isNew: true, defaultValue: this.props.newFolderDefaultValue, foldersUrl: this.props.foldersUrl, onSave: this.handleFolderEditSave.bind(this, {}), onClose: this.handleFolderEditClose.bind(this, {}), }, }) } handleFolderEdit = () => { const { foldersData, currentFolderId } = this.state if (!currentFolderId) return // because folder data is threaded we gotta flatten it to find current selection and set its parent const folder = flatten(foldersData).filter(item => item.id === currentFolderId)[0] folder.parent = getParentByChildId(currentFolderId, foldersData) modalHandler.add({ Component: this.props.FolderEditModal, props: { defaultValue: folder, foldersUrl: this.props.foldersUrl, onSave: this.handleFolderEditSave.bind(this, folder), onClose: this.handleFolderEditClose.bind(this, folder), }, }) } handleFolderEditSave = (oldRowValue, newRowValue, isNew) => { newRowValue = newRowValue.value if (!deepEqual(oldRowValue, newRowValue)) { const postUrl = this.props.getFolderSubmitUrl({ id: isNew ? false : newRowValue.id }) const hasNewParent = _get(newRowValue, 'parent.id', null) != _get(oldRowValue, 'parent.id') if (hasNewParent) newRowValue.parent_id = _get(newRowValue, 'parent.id', null) xhrUtils.post({ url: postUrl, data: newRowValue, callback: folder => { let foldersData = this.state.foldersData if (isNew) { foldersData = appendChildToParent(foldersData, folder.parent_id, folder) foldersData = sortByKey(foldersData, 'title') } else { if (hasNewParent) { foldersData = pruneTree(foldersData, 'id', folder.id) foldersData = appendChildToParent(foldersData, folder.parent_id, folder) foldersData = sortByKey(foldersData, 'title') } else { foldersData = replaceChild(this.state.foldersData, folder) } } this.setState({ foldersData }) this.handleFolderSelection([folder.id]) }, }) } else { console.log('Nothing edited, not saving.') } } handleFolderEditClose = (oldRowValue, newFormValue, isNew) => { console.log('MODAL CLOSED, W/E') } handleFolderDelete = () => { const { foldersData, currentFolderId } = this.state showDeletePrompt({ subject: 'email template folder', queryUrl: this.props.folderDeleteUrl, style: 'error', selectedIds: [currentFolderId], data: flatten(foldersData).filter(item => item.id === currentFolderId), callback: response => { this.setState({ foldersData: pruneTree(foldersData, 'id', [currentFolderId]), currentFolderId: null, }) }, }) } handleMove = (selectedItems, updateCallback) => { modalHandler.add({ Component: TreeModal, props: { title: 'Select Folder', primaryActionText: 'Confirm', url: this.props.foldersUrl, onClose: () => {}, onSave: folder => { if (folder) { const selectedIds = selectedItems.map(({ id }) => id) xhrUtils.post({ url: `${this.props.moveUrl}?folder_id=${folder.id}`, data: { id: selectedIds }, successMessage: `${selectedItems.length} item${selectedItems.length > 1 ? 's' : ''} moved to ${ folder.title }`, failureMessage: 'There was an error moving items', callback: () => { const updateDataFunc = data => data.map(item => (~selectedIds.indexOf(item.id) ? { ...item, folder_id: folder.id } : item)) updateCallback(updateDataFunc) }, }) } }, }, }) } render() { const { SidebarToolbar, BulkheadToolbar, TreeCell, sidebarTitle } = this.props const { collapsedFolderIds, currentFolderId, foldersData } = this.state const sidebarProps = { currentFolderId, handleFolderAdd: this.handleFolderAdd, handleFolderEdit: this.handleFolderEdit, handleFolderDelete: this.handleFolderDelete, } const _foldersData = [{ id: null, title: '[All]' }, { id: '_unsorted', title: '[Unsorted]' }, ...foldersData] return ( <AutoTableIndexContainer {...this.props} defaultValue={this.props.data} movable={!!this.props.moveUrl} onMove={this.handleMove}> {props => { const { model, panelProps, handleFilterChange, originalData } = props return ( <MainContent> <Bulkhead title={titleCase(model)} Toolbar={() => <BulkheadToolbar {...props} {...panelProps} />} /> <div className="finder"> <div className="finder-menu"> <Panel title={sidebarTitle || 'Folders'} PanelToolbar={() => <SidebarToolbar {...sidebarProps} />}> <Tree value={_foldersData} TreeCell={props => <TreeCell {...props} data={originalData} />} dropTargetType={'TANK_FOLDER'} treeItemTarget={treeItemTarget(this)} treeItemSource={treeItemSource(this)} onSelectItem={this.handleFolderEdit} selectedIds={[currentFolderId]} collapsedIds={collapsedFolderIds} onSelectionChange={ids => this.handleFolderSelection(ids, handleFilterChange)} onCollapseChange={this.handleFolderCollapse} onCombinationChange={() => console.log('HANDLE COMBINATION CHANGE')} /> </Panel> </div> <div className="finder-content" ref="finderContent"> <AutoTableIndexBase {...props} /> </div> </div> </MainContent> ) }} </AutoTableIndexContainer> ) } } // drag source & target configs for allowing projects to be dropped into sector folders const treeItemSource = parent => ({ beginDrag(props, monitor, component) { return { id: props.id } }, }) const treeItemTarget = parent => ({ drop(props, monitor, component) { if (monitor.isOver({ shallow: true })) { if (!props.selected) { const sectorId = monitor.getItem().id const parentId = props.id parent.onMoveSector(parentId, sectorId) } else { console.log('no need to move asset into already opened folder') } } }, hover(props, monitor, component) { if (monitor.isOver({ shallow: true })) { const ownId = props.id const draggedId = monitor.getItem().id if (draggedId === ownId) return component.setState({ position: 'into' }) } }, })