UNPKG

@bigfishtv/cockpit

Version:

504 lines (426 loc) 17 kB
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _dec, _class, _class2, _temp, _initialiseProps; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } import PropTypes from 'prop-types'; import React, { Component } from 'react'; import isEqual from 'lodash/isEqual'; import { connect } from 'react-redux'; import Tree from '../tree/Tree'; import Button from '../button/Button'; import Panel from '../container/panel/Panel'; import AssetFolderEditModal from '../asset/AssetFolderEditModal'; import AssetFoldersToolbar from '../asset/AssetFoldersToolbar'; import AssetFolderTreeCell from './AssetFolderTreeCell'; import * as DragTypes from '../../constants/DragTypes'; import newId from '../../utils/newId'; import { modalHandler } from '../modal/ModalHost'; import { get, post } from '../../api/xhrUtils'; import { userCanAccess } from '../../utils/roleUtils'; import { showPrompt } from '../../utils/promptUtils'; import { pruneTree, collectValues, getChildByKeyValue, getParentByChildId, setKeyValueById, sortByKey, appendChildToParent, collectChildrenKeyValues } from '../../utils/treeUtils'; // return only the folders (and their subfolders) that user has permission to access function filterAssetFolders(data, user) { return data.reduce(function (acc, folder) { if (userCanAccess([{ model: 'Tank.AssetFolders', foreign_key: folder.id }], user)) { acc.push(folder); } else if (folder.children && folder.children.length) { acc = acc.concat(filterAssetFolders(folder.children, user)); } return acc; }, []); } var unsortedTreeItem = { id: null, parent_id: null, locked: true, title: '[Unsorted]', children: [], uid: '_unsorted_' /** * Displays folder tree corresponding to tank asset folders * Has functionality for creating, deleting and editing asset folders * Doesn't have functionality for modifying assets, only callback props */ };var AssetFinderSidebarTree = (_dec = connect(function (_ref) { var viewer = _ref.viewer; return { viewer: viewer }; }), _dec(_class = (_temp = _class2 = function (_Component) { _inherits(AssetFinderSidebarTree, _Component); function AssetFinderSidebarTree(props) { _classCallCheck(this, AssetFinderSidebarTree); var _this = _possibleConstructorReturn(this, _Component.call(this)); _initialiseProps.call(_this); _this.state = { data: [], selectedId: props.defaultSelectedId, selectedIds: props.defaultSelectedId ? [props.defaultSelectedId] : [], lockedIds: [], collapsedIds: localStorage.assetFinderCollapsedIds ? JSON.parse(localStorage.assetFinderCollapsedIds) : [], allCollapsed: true }; return _this; } AssetFinderSidebarTree.prototype.componentDidUpdate = function componentDidUpdate() { localStorage.assetFinderCollapsedIds = JSON.stringify(this.state.collapsedIds); }; AssetFinderSidebarTree.prototype.componentDidMount = function componentDidMount() { var _this2 = this; var callback = function callback(returnedData) { var data = sortByKey(filterAssetFolders(returnedData, _this2.props.viewer), 'title'); var lockedIds = collectValues(returnedData, 'id', function (item) { return item.locked; }); var collapsedIds = collectValues(returnedData, 'id', function (item) { return item.children && item.children.length > 0; }); if (localStorage.assetFinderCollapsedIds) { var savedCollapsedIds = JSON.parse(localStorage.assetFinderCollapsedIds); collapsedIds = collapsedIds.filter(function (id) { return savedCollapsedIds.indexOf(id) >= 0; }); } _this2.setState({ data: data, lockedIds: lockedIds, collapsedIds: collapsedIds }, function () { var _state = _this2.state, data = _state.data, selectedId = _state.selectedId; var folderId = selectedId === null ? data.length ? data[0].id : null : selectedId; var selectedFolder = getChildByKeyValue(data, 'id', folderId); if (folderId !== selectedId) _this2.setState({ selectedIds: [folderId] }); _this2.props.onFolderChange(selectedFolder); }); }; // first we trial the folderGetUrl, but if we get // an error (Tank < 3.0.4) then we use our fallback url // This is a hack so we don't need to add a breaking change // to Cockpit just yet. We should remove this hack in the next // major version or add some kind of detection for Tank version var fallbackUrl = '/tank/asset_folders.json'; get({ url: this.props.folderGetUrl, callback: callback, quietError: true, callbackError: function callbackError() { get({ url: fallbackUrl, callback: callback }); } }); }; AssetFinderSidebarTree.prototype.getSubmitUrl = function getSubmitUrl(data) { return data.id ? this.props.folderUpdateUrl + '/' + data.id + '.json' : this.props.folderAddUrl; }; // when tree selection changes, will only ever be one long in this instance of tree // when tree collapse occurs // called from toolbar button // called from toolbar button // called from controlled tree when it wants to update multiple state variables without a rerender // called from Tray button // called from AssetFolderEditModal upon folder creation // called from Tray button // called from AssetFolderEditModal upon folder edit // called from Tray button AssetFinderSidebarTree.prototype.handleFolderDeleteSave = function handleFolderDeleteSave(folderId) { // optimistically remove folders locally var data = sortByKey(pruneTree(this.state.data, 'id', [folderId]), 'title'); this.setState({ data: data, selectedIds: [], selectedId: null }); post({ url: this.props.folderDeleteUrl, data: [folderId], callback: function callback() {} }); }; AssetFinderSidebarTree.prototype.canDeleteFolder = function canDeleteFolder(folderId) { var folder = getChildByKeyValue(this.state.data, 'id', folderId); return folder && 'parent_id' in folder ? true : false; }; AssetFinderSidebarTree.prototype.canEditFolder = function canEditFolder(folderId) { var folder = getChildByKeyValue(this.state.data, 'id', folderId); return folder && 'parent_id' in folder ? true : false; }; AssetFinderSidebarTree.prototype.render = function render() { var _state2 = this.state, data = _state2.data, collapsedIds = _state2.collapsedIds, selectedIds = _state2.selectedIds, selectedId = _state2.selectedId, allCollapsed = _state2.allCollapsed; var toolProps = { editable: selectedIds.length ? this.canEditFolder(selectedIds[0]) : false, removable: selectedIds.length ? this.canDeleteFolder(selectedIds[0]) : false, onCollapseAll: this.handleCollapseAll, onExpandAll: this.handleExpandAll, onCreate: this.handleFolderAdd, onRemove: this.handleFolderDelete, onEdit: this.handleFolderEdit, currentFolder: selectedId !== null ? getChildByKeyValue(data, 'id', selectedId) : null, allCollapsed: allCollapsed }; return React.createElement( Panel, _extends({ title: 'Folders', PanelToolbar: AssetFoldersToolbar }, toolProps), React.createElement(Tree, { value: this.props.showUnsortedFolder ? [unsortedTreeItem].concat(this.state.data) : this.state.data, TreeCell: AssetFolderTreeCell, multiselect: false, reorderable: false, stickySelect: this.props.showUnsortedFolder ? false : true, dropTargetType: DragTypes.ASSET_CELL, treeItemTarget: treeItemTarget(this), treeItemSource: treeItemSource(this), onSelectItem: this.handleFolderEdit, selectedIds: selectedIds, collapsedIds: collapsedIds, onSelectionChange: this.handleSelectionChange, onCollapseChange: this.handleCollapseChange, onCombinationChange: this.handleCombinationChange }) ); }; return AssetFinderSidebarTree; }(Component), _class2.propTypes = { /** Called when a folder is selected */ onFolderChange: PropTypes.func, /** Optional callback for overriding adding new folder functionality */ onFolderAdd: PropTypes.func, /** Optional callback for overriding folder edit functionality */ onEdit: PropTypes.func, /** Called when assets are dragged into a different folder */ onMoveAssetsToFolder: PropTypes.func, /** Asset folder id to be automatically selected on mount */ defaultSelectedId: PropTypes.number, folderGetUrl: PropTypes.string, folderAddUrl: PropTypes.string, folderUpdateUrl: PropTypes.string, folderDeleteUrl: PropTypes.string, showUnsortedFolder: PropTypes.bool }, _class2.defaultProps = { onFolderChange: function onFolderChange() { return console.warn('[MediaSidebarTree] no onFolderChange prop'); }, onFolderAdd: function onFolderAdd() { return console.warn('[MediaSidebarTree] no onFolderAdd prop'); }, onEdit: function onEdit() { return console.warn('[MediaSidebarTree] no onEdit prop'); }, onMoveAssetsToFolder: function onMoveAssetsToFolder() { return console.warn('[MediaSidebarTree] no onMoveAssetsToFolder prop'); }, showUnsortedFolder: true, defaultSelectedId: null, folderGetUrl: null, folderAddUrl: null, folderUpdateUrl: null, folderDeleteUrl: null }, _initialiseProps = function _initialiseProps() { var _this3 = this; this.handleSelectionChange = function (selectedIds) { if (!_this3.props.showUnsortedFolder && !selectedIds.length) return; var selectedId = selectedIds.length ? selectedIds[0] : null; _this3.setState({ selectedId: selectedId, selectedIds: selectedIds }); var item = selectedId ? getChildByKeyValue(_this3.state.data, 'id', selectedId) : null; _this3.props.onFolderChange(item); }; this.handleCollapseChange = function (collapsedIds) { var collapsableIds = collectValues(_this3.state.data, 'id', function (item) { return item.children && item.children.length > 0; }); var allCollapsed = isEqual(collapsableIds.sort(), collapsedIds.sort()); _this3.setState({ collapsedIds: collapsedIds, allCollapsed: allCollapsed }); }; this.handleCollapseAll = function () { var collapsedIds = collectValues(_this3.state.data, 'id', function (item) { return item.children && item.children.length > 0; }); _this3.setState({ collapsedIds: collapsedIds, allCollapsed: true }); }; this.handleExpandAll = function () { _this3.setState({ collapsedIds: [], allCollapsed: false }); }; this.handleCombinationChange = function (mixed) { var newState = {}; Object.keys(mixed).map(function (key) { if (key in _this3.state) newState[key] = mixed[key]; }); _this3.setState(newState); }; this.handleFolderAdd = function () { _this3.props.onFolderAdd(); var selectedIds = _this3.state.selectedIds; modalHandler.add({ Component: AssetFolderEditModal, props: { onSave: _this3.handleFolderAddSave, onClose: function onClose() {}, selectedIds: selectedIds, parentId: selectedIds.length ? selectedIds[0] : null, data: _this3.state.data } }); }; this.handleFolderAddSave = function (_ref2) { var folderId = _ref2.folderId, folderName = _ref2.folderName, parentId = _ref2.parentId; var isNew = !folderId ? true : false; var tempId = newId(); var item = { id: isNew ? tempId : folderId, title: folderName, children: null }; if (parentId == -1) parentId = null; var data = sortByKey(appendChildToParent(_this3.state.data, parentId, item), 'title'); // this.setState({data}); var changes = { id: folderId, title: folderName, parent_id: parentId }; post({ url: _this3.getSubmitUrl({ id: isNew ? folderId : false }), data: changes, callback: function callback(returnedData) { var createdId = returnedData.id; var data2 = sortByKey(setKeyValueById(data, tempId, 'id', createdId), 'title'); _this3.setState({ data: data2 }, function () { _this3.props.onFolderChange(returnedData); }); } }); }; this.handleFolderEdit = function () { var _state3 = _this3.state, data = _state3.data, selectedId = _state3.selectedId; if (selectedId === null) return; var item = getChildByKeyValue(data, 'id', selectedId); var parent = getParentByChildId(selectedId, data); _this3.props.onEdit(); modalHandler.add({ Component: AssetFolderEditModal, props: { onSave: _this3.handleFolderEditSave, onClose: function onClose() {}, title: 'Edit Folder', primaryActionText: 'Save', folderName: item.title, data: data, parentId: parent.id, folderId: item.id, selectedIds: [parent.id] } }); }; this.handleFolderEditSave = function (_ref3) { var folderId = _ref3.folderId, folderName = _ref3.folderName, parentId = _ref3.parentId; var item = getChildByKeyValue(_this3.state.data, 'id', folderId); item.title = folderName; item.parent_id = parentId; var data = sortByKey(appendChildToParent( // current tree minus id of updated pruneTree(_this3.state.data, 'id', [folderId]), parentId, item // item to be updated ), 'title'); var changes = { id: folderId, title: folderName }; if (parentId === null) changes.parent = null;else changes.parent_id = parentId; post({ url: _this3.getSubmitUrl(changes), data: changes, callback: function callback() { return _this3.setState({ data: data }); } }); }; this.handleFolderDelete = function () { var _state4 = _this3.state, data = _state4.data, selectedId = _state4.selectedId; if (selectedId === null) { console.warn('No folder selected for delete!'); return; } var selectedFolder = getChildByKeyValue(data, 'id', selectedId); var folderTitles = [selectedFolder.title].concat(collectChildrenKeyValues(data, selectedId, 'title')); showPrompt({ title: 'Delete Folder', message: React.createElement( 'div', null, React.createElement( 'p', null, 'Deleting a folder affects assets within a folder. This includes assets inside the following folders:' ), React.createElement( 'ul', null, folderTitles.map(function (title, i) { return React.createElement( 'li', { key: i }, title ); }) ) ), Buttons: function Buttons(props) { return React.createElement(Button, { text: 'Delete All', style: 'error', onClick: function onClick() { return props.callback('delete'); } }); }, callback: function callback() { return _this3.handleFolderDeleteSave(selectedId); } }); }; }, _temp)) || _class); // drag source & target configs for allowing asset cells to be dropped into folders export { AssetFinderSidebarTree as default }; var expandTimeout = null; var treeItemSource = function treeItemSource() { return { beginDrag: function beginDrag(props) { return { id: props.id }; } }; }; var treeItemTarget = function treeItemTarget(parent) { return { drop: function drop(props, monitor) { // goes through all drop targets and resets their forceExpand state so they don't randomly open upon drag if hover expanded for (var key in monitor.internalMonitor.registry.handlers) { if (key.charAt(0) == 'T') { var item = monitor.internalMonitor.registry.handlers[key].component; if (item.state && item.state.forceExpand) { item.props.onCollapse(); item.setState({ forceExpand: false }); } } } if (monitor.isOver({ shallow: true })) { if (!props.selected) { var assetId = monitor.getItem().id; var folderId = props.id; parent.props.onMoveAssetsToFolder(folderId, [assetId]); } } }, hover: function hover(props, monitor, component) { var isOverCurrent = monitor.isOver({ shallow: true }); if (isOverCurrent) { var ownId = props.id; var draggedId = monitor.getItem().id; if (draggedId === ownId) return; // hover expand timeout if (component.props.collapsed && component.state.position != 'into') { if (expandTimeout !== null) clearTimeout(expandTimeout); expandTimeout = setTimeout(function () { component.setState({ forceExpand: true }); }, 1000); } component.setState({ position: 'into' }); } } }; };