/** @module AppTree */
import React from 'react';
import ReactDOM from 'react-dom';
import { DashIcon } from './components';
import renderOrderMenu from './others/renderOrderMenu';
import AIOTree, { getTreeParentById } from 'react-aiot';
import { message, Icon, Popconfirm } from 'react-aiot';
import { hooks, rmlOpts, i18n, urlParam, addUrlParam, ICON_OBJ_FOLDER_CLOSED,
ICON_OBJ_FOLDER_OPEN, ICON_OBJ_FOLDER_ROOT, ICON_OBJ_FOLDER_COLLECTION, ICON_OBJ_FOLDER_GALLERY } from './util';
import { draggable, droppable } from './util/dragdrop';
import produce from 'immer';
import $ from 'jquery';
import { FILTER_SELECTOR } from './others/filter';
import createLockedToolTipText from './hooks/permissions';
import { inject, observer, Observer } from 'mobx-react';
import { toggleSortable, orderUrl } from './hooks/sortable';
import MetaBox from './components/MetaBox';
/**
* The latest queried folder.
*
* @type object
*/
export let latestQueriedFolder = { node: null };
message.config({ top: 50 });
/**
* The application tree handler for Real Media Library.
*
* @param {string} id The HTML id (needed to localStorage support)
* @param {object} [attachmentsBrowser] The attachments browser (for media grid view)
* @param {boolean} [isModal=false] If true the given app tree is a modal dialog
* @param {module:AppTree~AppTree~init} [init]
* @see module:store.StoredAppTree
* @see module:react-aiot~Tree
* @extends React.Component
*/
@inject('store')
@observer
class AppTree extends React.Component {
/**
* Initialize properties and state for AIOTree component.
* Also handles the responsiveness.
*/
constructor(props) {
super(props);
// Add respnsive handler for non-modal views
!props.isModal && $(window).resize(this.handleWindowResize);
const isMobile = this._isMobile();
// State refs (see https://github.com/reactjs/redux/issues/1793) and #resolveStateRefs
this.stateRefs = {
keysCreatable: 'icon,iconActive,toolTipTitle,toolTipText,onClick,label'.split(','),
keysToolbar: 'content,toolTipTitle,toolTipText,onClick,onCancel,onSave,modifier,label,save,menu'.split(','),
// Icons
ICON_OBJ_FOLDER_CLOSED,
ICON_OBJ_FOLDER_OPEN,
ICON_OBJ_FOLDER_ROOT,
ICON_OBJ_FOLDER_COLLECTION,
ICON_OBJ_FOLDER_GALLERY,
ICON_SETTINGS: <Icon type="setting" />,
ICON_LOCKED: <Icon type="lock" />,
ICON_ORDER: <DashIcon name="move" />,
ICON_RELOAD: <Icon type="reload" />,
ICON_RENAME: <Icon type="edit" />,
ICON_TRASH: <Icon type="delete" />,
ICON_SORT: <DashIcon name="sort" />,
ICON_SAVE: <Icon type="save" />,
ICON_ELLIPSIS: <Icon type="ellipsis" />,
// Creatable
handleCreatableClickBackButton: () => this.handleCreatableClick(),
handleCreatableClickFolder: () => this.handleCreatableClick('folder', 0),
handleCreatableClickCollection: () => this.handleCreatableClick('collection', 1),
handleCreatableClickGallery: () => this.handleCreatableClick('gallery', 2),
// Toolbar buttons
renderOrderMenu: renderOrderMenu.bind(this),
handleOrderClick: this.handleOrderClick,
handleOrderCancel: this.handleOrderCancel,
handleReload: this.handleReload,
handleRenameClick: this.handleRenameClick,
handleRenameCancel: this.handleRenameCancel,
handleTrashModifier: body => {
const node = this.getTreeItemById();
return node ? <Popconfirm placement="bottom" onConfirm={ this.handleTrash }
title={i18n('deleteConfirm', { name: node.title }, 'maxWidth')}
okText={i18n('ok')} cancelText={i18n('cancel')}>{ body }</Popconfirm> : body;
},
handleSortClick: () => this._handleSortNode('sort'),
handleSortCancel: () => this._handleSortNode(),
handleDetailsClick: () => this._handleDetails('details'),
handleDetailsCancel: () => this._handleDetails(),
handleDetailsSave: () => this._handleDetails('save'),
handleUserSettingsClick: () => this._handleDetails('usersettings'),
handleUserSettingsCancel: () => this._handleDetails(),
handleUserSettingsSave: () => this._handleDetails('save')
};
// Determine selected id and fetch tree
let selectedId = urlParam('rml_folder');
this.attachmentsBrowser = props.attachmentsBrowser;
this.state = {
// Custom
currentFolderRestrictions: [],
isModal: props.isModal,
isMoveable: true,
isWPAttachmentsSortMode: false, // See modal.js
initialSelectedId: !selectedId || selectedId === 'all' ? 'all' : +selectedId,
metaBoxId: false,
metaBoxErrors: false,
// Creatables
availableCreatables: 'folder,collection,gallery'.split(','),
selectedCreatableType: undefined, // The selected folder type
creatable_folder: {
icon: 'ICON_OBJ_FOLDER_CLOSED',
iconActive: 'ICON_OBJ_FOLDER_OPEN',
visibleInFolderType: [undefined, 0],
cssClasses: 'page-title-action add-new-h2',
toolTipTitle: 'i18n.creatable0ToolTipTitle',
toolTipText: 'i18n.creatable0ToolTipText',
label: '+',
onClick: 'handleCreatableClickFolder'
},
creatable_collection: {
icon: 'ICON_OBJ_FOLDER_COLLECTION',
visibleInFolderType: [undefined, 0, 1],
cssClasses: 'page-title-action add-new-h2',
toolTipTitle: 'i18n.creatable1ToolTipTitle',
toolTipText: 'i18n.creatable1ToolTipText',
label: '+',
onClick: 'handleCreatableClickCollection'
},
creatable_gallery: {
icon: 'ICON_OBJ_FOLDER_GALLERY',
visibleInFolderType: [1],
visible: false,
cssClasses: 'page-title-action add-new-h2',
toolTipTitle: 'i18n.creatable2ToolTipTitle',
toolTipText: 'i18n.creatable2ToolTipText',
label: '+',
onClick: 'handleCreatableClickGallery'
},
creatableBackButton: {
cssClasses: 'page-title-action add-new-h2',
label: 'i18n.cancel',
onClick: 'handleCreatableClickBackButton'
},
// Toolbar buttons
availableToolbarButtons: 'locked,usersettings,order,reload,rename,trash,sort,details'.split(','),
toolbar_usersettings: {
content: 'ICON_SETTINGS',
visible: !!+rmlOpts.userSettings,
toolTipTitle: 'i18n.userSettingsToolTipTitle',
toolTipText: 'i18n.userSettingsToolTipText',
onClick: 'handleUserSettingsClick',
onCancel: 'handleUserSettingsCancel',
onSave: 'handleUserSettingsSave'
},
toolbar_locked: {
content: 'ICON_LOCKED',
visible: false,
toolTipTitle: 'i18n.lockedToolTipTitle',
toolTipText: '' // Lazy
},
toolbar_order: {
content: 'ICON_ORDER',
toolTipTitle: 'i18n.orderToolTipTitle',
toolTipText: 'i18n.orderToolTipText',
onClick: 'handleOrderClick',
onCancel: 'handleOrderCancel',
menu: 'resolve.renderOrderMenu',
toolTipPlacement: 'topLeft',
dropdownPlacement: 'bottomLeft'
},
toolbar_reload: {
content: 'ICON_RELOAD',
toolTipTitle: 'i18n.refreshToolTipTitle',
toolTipText: 'i18n.refreshToolTipText',
onClick: 'handleReload'
},
toolbar_rename: {
content: 'ICON_RENAME',
toolTipTitle: 'i18n.renameToolTipTitle',
toolTipText: 'i18n.renameToolTipText',
onClick: 'handleRenameClick',
onCancel: 'handleRenameCancel',
disabled: true
},
toolbar_trash: {
content: 'ICON_TRASH',
toolTipTitle: 'i18n.trashToolTipTitle',
toolTipText: 'i18n.trashToolTipText',
modifier: 'handleTrashModifier',
disabled: true
},
toolbar_sort: {
content: 'ICON_SORT',
toolTipTitle: 'i18n.sortToolTipTitle',
toolTipText: 'i18n.sortToolTipText',
onClick: 'handleSortClick',
onCancel: 'handleSortCancel'
},
toolbar_details: {
content: 'ICON_ELLIPSIS',
disabled: true,
toolTipTitle: 'i18n.detailsToolTipTitle',
toolTipText: 'i18n.detailsToolTipText',
onClick: 'handleDetailsClick',
onCancel: 'handleDetailsCancel',
onSave: 'handleDetailsSave'
},
toolbarBackButton: {
label: 'i18n.cancel',
save: 'i18n.save'
},
// AIO
isResizable: !isMobile,
isSticky: !isMobile,
isStickyHeader: !isMobile,
isFullWidth: isMobile,
style: isMobile ? { marginLeft: 10 } : {},
isSortable: true,
isSortableDisabled: false,
isTreeBusy: false,
isBusyHeader: false,
sortableDelay: 100,
headerStickyAttr: {
top: '#wpadminbar'
},
isCreatableLinkDisabled: false,
toolbarActiveButton: undefined,
isTreeLinkDisabled: false
};
// What happens if the attachments browser is available? We will add a reference to this React element
this.attachmentsBrowser && (this.attachmentsBrowser.controller.$RmlAppTree = this);
/**
* Called on initialzation and allows you to modify the init state.
*
* @callback module:AppTree~AppTree~init
* @param {object} state The default state
* @param {AppTree} tree The AppTree component instance
* @returns {object} The new state
*/
props.init && (this.state = props.init(this.state, this));
/**
* The React AppTree instance gets constructed and you can modify it here.
*
* @event module:util/hooks#tree/init
* @param {object} state
* @param {object} props
* @this module:AppTree~AppTree
*/
hooks.call('tree/init', [this.state, props], this);
this.initialSelectedId = this.state.initialSelectedId;
}
/**
* Render AIO tree with tax switcher.
*/
render() {
const { staticTree, tree, selectedId } = this.props.store,
{ metaBoxId, metaBoxErrors, isBusyHeader } = this.state;
return <AIOTree ref={ this.doRef } id={ this.props.id } rootId={ +rmlOpts.rootId }
staticTree={ staticTree } selectedId={ selectedId } tree={ tree.length > 0 ? tree : [] }
opposite={ document.getElementById('wpbody-content') } onSelect={ this.handleSelect }
onRenameClose={ this.handleRenameClose } onAddClose={ this.handleAddClose }
onNodeExpand={ () => setTimeout(() => droppable(this), 200) } renderItem={ this.onTreeNodeRender }
onNodePressF2={ this.handleRenameClick } onSort={ this.handleSort } onResize={ this.handleResize }
headline={ <span style={{ paddingRight: 5 }}>{ i18n('folders') }</span> }
renameSaveText={ this.stateRefs.ICON_SAVE } renameAddText={ this.stateRefs.ICON_SAVE }
noFoldersTitle={ i18n('noFoldersTitle') } noFoldersDescription={ i18n('noFoldersDescription') }
noSearchResult={ i18n('noSearchResult') } innerClassName="wrap" theme="wordpress"
creatable={ this.renderCreatables() } toolbar={ this.renderToolbarButtons() }
{ ...this.state }>
{ metaBoxId !== false && (<MetaBox patcher={ patcher => (this.metaboxPatcher = patcher) } busy={ isBusyHeader }
errors={ metaBoxErrors } id={ metaBoxId } />) }
</AIOTree>;
}
/**
* @returns {object}
*/
renderToolbarButtons = () => {
const { availableToolbarButtons, toolbarBackButton } = this.state, toolbar = {
buttons: { },
backButton: this.resolveStateRefs(toolbarBackButton, 'keysToolbar')
};
for (let i = 0; i < availableToolbarButtons.length; i++) {
toolbar.buttons[availableToolbarButtons[i]] = this.resolveStateRefs(this.state['toolbar_' + availableToolbarButtons[i]], 'keysToolbar');
}
return toolbar;
}
/**
* @returns {object}
*/
renderCreatables = () => {
const { availableCreatables, creatableBackButton } = this.state, creatable = {
buttons: { },
backButton: this.resolveStateRefs(creatableBackButton, 'keysCreatable')
};
for (let i = 0; i < availableCreatables.length; i++) {
creatable.buttons[availableCreatables[i]] = this.resolveStateRefs(this.state['creatable_' + availableCreatables[i]], 'keysCreatable');
}
return creatable;
}
/**
* Iterates all available values in an object and resolve it with the available
* this::stateRefs.
*
* @returns {object}
*/
resolveStateRefs(_obj, keys) {
const obj = Object.assign({}, _obj);
let value, newValue;
for (let key in obj) {
if (obj.hasOwnProperty(key) && (value = obj[key]) && this.stateRefs[keys].indexOf(key) > -1 &&
typeof value === 'string' && (newValue = this.resolveStateRef(value))) {
obj[key] = newValue;
}
}
return obj;
}
/**
* Resolve single state ref key.
*
* @returns {object}
*/
resolveStateRef(key) {
if (typeof key !== 'string') {
return;
}
if (key.indexOf('i18n.') === 0) {
return i18n(key.substr(5));
}else if (key.indexOf('resolve.') === 0) {
return this.stateRefs[key.substr(8)]();
}else if (this.stateRefs[key]) {
return this.stateRefs[key];
}
}
doRef = ref => this.ref = ref
/**
* Remove resize handler.
*/
componentWillUnmount() {
$(window).off('resize', this.handleWindowResize);
}
/**
* Fetch initial tree.
*/
componentWillMount() {
console.log(this);
this.fetchTree(this.initialSelectedId);
}
/**
* Initiate draggable and droppable
*/
componentDidMount() {
draggable(this);
droppable(this);
this.handleResize();
// If order should be enabled in list mode, then activate it now
if (rmlOpts.listMode === 'list' && window.location.hash === '#order') {
this.handleOrderClick();
window.location.hash = '';
}
}
/**
* When the component updates the droppable zone is reinitialized.
* Also the toolbar buttons gets disabled or enabled depending on selected node.
*/
componentDidUpdate() {
const { selectedCreatableType } = this.state,
selected = this.getTreeItemById();
if ((selected && selectedCreatableType !== selected.properties.type) || (!selected && selectedCreatableType !== undefined)) {
this._updateCreatableButtons(selected ? selected.properties.type : undefined);
}
// Enable / Disable toolbar buttons
this._updateToolbarButtons();
// Enable locked toolbar item
createLockedToolTipText(this);
draggable(this);
droppable(this);
}
/**
* Return the backbone filter view for the given attachments browser.
*
* @returns object
*/
getBackboneFilter() {
const { attachmentsBrowser } = this;
return attachmentsBrowser && attachmentsBrowser.toolbar.get('rml_folder');
}
/**
* Get the selected node id.
*
* @returns {string|int}
*/
getSelectedId() {
return this.props.store.selectedId;
}
/**
* Get tree item by id.
*
* @param {string|int} [id=Current]
* @param {boolean} [excludeStatic=true]
* @returns {object} Tree node
*/
getTreeItemById(id = this.getSelectedId(), excludeStatic = true) {
return this.props.store.getTreeItemById(id, excludeStatic);
}
/**
* Update a tree item by id.
*
* @param {function|array} callback The callback with one argument (node draft) and should return the new node.
* @param {string|int} [id=Current] The id which should be updated
*/
updateTreeItemById(callback, id = this.getSelectedId()) {
const node = this.props.store.getTreeItemById(id);
node && node.setter(callback);
}
/**
* Updates the create node. That's the node without id and the input field.
*
* @param {function} callback The callback with one argument (node draft) and should return the new node.
*/
async updateCreateNode(callback) {
// Root update
const createRoot = this.state.createRoot;
createRoot && this.setState({
createRoot: produce(createRoot, callback)
});
// Child node update
const node = this.getTreeItemById();
node && node.$create && this.updateTreeItemById(node => {
node.$create = produce(node.$create, callback);
});
}
/**
* Handles the creatable click and creates a new node depending on the selected one.
*
* @method
*/
handleCreatableClick = (type, typeInt) => {
let createRoot = undefined,
$create = undefined;
if (type) {
// Activate create
const creatable = this.state['creatable_' + type],
newNode = {
$rename: true,
icon: this.resolveStateRef(creatable.icon),
iconActive: this.resolveStateRef(creatable.iconActive),
parent: +rmlOpts.rootId,
typeInt
}, selectedId = this.getSelectedId();
if (typeof selectedId !== 'number' || selectedId === +rmlOpts.rootId) {
createRoot = newNode;
}else{
$create = newNode;
newNode.parent = selectedId;
}
}
this.setState({
isTreeLinkDisabled: !!type,
isCreatableLinkCancel: !!type,
isToolbarActive: !type,
createRoot
});
this.updateTreeItemById(node => { node.$create = $create });
}
/**
* A node gets selected. Depending on the fast mode the page gets reloaded
* or the wp list table gets reloaded.
*
* @method
*/
handleSelect = id => {
// Do nothing when sort mode is active
if (this.state.toolbarActiveButton === 'sort') {
return;
}
const select = this.getTreeItemById(id, false),
setter = (_id, $busy) => {
latestQueriedFolder.node = select;
latestQueriedFolder.node.setter(node => {
node.$busy = $busy;
node.selected = true;
});
};
if (this.attachmentsBrowser) {
!id && this.attachmentsBrowser.collection.props.set({ignore: (+ new Date())}); // Reload the view
this._handleBackboneFilterSelection(select.id);
}else{
let href = window.location.href;
urlParam('orderby') === 'rml' && (href = href.split('?')[0]);
select.properties && select.contentCustomOrder === 1 &&
(href = orderUrl(href));
window.location.href = addUrlParam(href, 'rml_folder', select.id);
}
setter(select.id, !this.attachmentsBrowser);
}
/**
* When resizing the container set ideal width for attachments.
*/
handleResize = () => {
const { attachmentsBrowser } = this;
attachmentsBrowser && attachmentsBrowser.attachments.setColumns();
}
/**
* Handle order click.
*
* @method
*/
handleOrderClick = () => {
if (toggleSortable(this.getTreeItemById(), true, this.attachmentsBrowser)) {
this.setState({
isMoveable: false,
toolbarActiveButton: 'order',
toolbarBackButton: produce(this.state.toolbarBackButton, draft => {
draft.label = 'i18n.back';
})
});
}
}
/**
* Handle order cancel.
*
* @method
*/
handleOrderCancel = () => {
toggleSortable(this.getTreeItemById(), false, this.attachmentsBrowser);
this.setState({
isMoveable: true,
toolbarActiveButton: undefined,
toolbarBackButton: produce(this.state.toolbarBackButton, draft => {
draft.label = 'i18n.cancel';
})
});
}
/**
* Handle rename click and enable the input field if necessery.
*
* @method
*/
handleRenameClick = () => this._handleRenameNode('rename', true, true, true)
/**
* Handle rename cancel.
*
* @method
*/
handleRenameCancel = () => this._handleRenameNode(undefined, false, false, undefined)
/**
* Handle rename close and depending on the save state create the new node.
*
* @method
*/
handleRenameClose = async (save, inputValue, { id }) => {
if (save && inputValue.length) {
const hide = message.loading(i18n('renameLoadingText', { name: inputValue }));
try {
const { name } = await this.props.store.getTreeItemById(id).setName(inputValue);
message.success(i18n('renameSuccess', { name }));
this.handleRenameCancel();
}catch(e) {
message.error(e.responseJSON.message);
}finally{
hide();
}
}else{
this.handleRenameCancel();
}
}
/**
* Handle add close and remove the new node.
*
* @method
*/
handleAddClose = async (save, name, { parent, typeInt }) => {
if (save) {
this.updateCreateNode(node => { node.$busy = true; });
const hide = message.loading(i18n('addLoadingText', { name }));
try {
const newObj = await this.props.store.persist(name, { parent, typeInt }, () => this.handleCreatableClick());
message.success(i18n('addSuccess', { name }));
// Modify all available attachments browsers filter
let backboneFilter, lastSlugs;
$(FILTER_SELECTOR).each(function() {
backboneFilter = $(this).data('backboneView');
if (backboneFilter) {
lastSlugs = backboneFilter.lastSlugs;
lastSlugs.names.push('(NEW) ' + name);
lastSlugs.slugs.push(newObj.id);
lastSlugs.types.push(typeInt);
backboneFilter.createFilters(lastSlugs);
}
});
}catch(e) {
message.error(e.responseJSON.message);
this.updateCreateNode(node => { node.$busy = false; });
}finally{
hide();
}
}else{
this.handleCreatableClick();
}
}
/**
* Handle trashing of a category. If the category has subcategories the
* trash is forbidden.
*
* @method
*/
handleTrash = async () => {
const node = this.getTreeItemById();
// Check if subdirectories
if (node.childNodes.filter(node => node.$visible).length) {
return message.error(i18n('deleteFailedSub', { name: node.title }));
}
const hide = message.loading(<span>Deleteing <strong>{ node.title }</strong>...</span>);
try {
await node.trash();
message.success(i18n('deleteSuccess', { name: node.title }));
// Select parent
const parentId = getTreeParentById(node.id, this.props.store.tree);
this.handleSelect(parentId === 0 ? +rmlOpts.rootId : parentId);
}catch(e) {
message.error(e.responseJSON.message);
}finally{
hide();
}
}
/**
* Handle categories sorting and update the tree so the changes are visible. If sorting
* is cancelled the old tree gets restored.
*
* @method
*/
handleSort = async (props) => {
this.setState({
isSortableBusy: true,
isToolbarBusy: true
});
const hide = message.loading(i18n('sortLoadingText')),
{ toolbarActiveButton } = this.state,
{ store } = this.props;
try {
await store.handleSort(props);
message.success(i18n('sortedSuccess'));
}catch (e) {
message.error(e.responseJSON.message);
}finally{
hide();
this._handleSortNode(toolbarActiveButton, false);
}
}
/**
* Handle responsiveness on window resize.
*
* @method
*/
handleWindowResize = () => {
const isMobile = this._isMobile();
this.setState({
isSticky: !isMobile,
isStickyHeader: !isMobile,
isResizable: !isMobile,
isFullWidth: isMobile,
style: isMobile ? { marginLeft: 10 } : {}
});
}
/**
* Handle refesh of content.
*/
handleReload = () => {
this.handleSelect();
}
handleDestroy() {
ReactDOM.unmountComponentAtNode(this.ref.container.parentNode);
}
/**
* A node item should be an observer (mobx).
*/
onTreeNodeRender = (createTreeNode, TreeNode, node) => {
return <Observer key={ node.id }>{ () => createTreeNode(node) }</Observer>;
}
/**
* Handle rename node states (helper).
*
* @method
*/
_handleRenameNode = (toolbarActiveButton, isCreatableLinkDisabled, isTreeLinkDisabled, nodeRename) => {
this.setState({ // Make other nodes editable / not editable
isCreatableLinkDisabled,
isTreeLinkDisabled,
toolbarActiveButton
});
this.updateTreeItemById(node => { // Make selected node editable / not editable
node.$rename = nodeRename;
});
}
/**
* Checks if the current window size is mobile.
*
* @returns {boolean}
* @method
*/
_isMobile = () => $(window).width() <= 700
/**
* Handle the sort node button.
*
* @method
*/
_handleSortNode = (toolbarActiveButton, isBusy) => {
this.setState({
isCreatableLinkDisabled: !!toolbarActiveButton,
toolbarActiveButton,
sortableDelay: toolbarActiveButton ? 0 : 100,
toolbarBackButton: produce(this.state.toolbarBackButton, draft => {
draft.label = 'i18n.' + (toolbarActiveButton ? 'back' : 'cancel');
})
});
typeof isBusy === 'boolean' && this.setState({ isSortableBusy: isBusy });
typeof isBusy === 'boolean' && this.setState({ isToolbarBusy: isBusy });
}
/**
* Handle the details meta box.
*/
_handleDetails = async action => {
action !== 'save' && this.setState({
toolbarActiveButton: action,
metaBoxId: action ? (action === 'usersettings' ? action : this.props.store.selectedId) : false
});
if (action === 'save') {
this.setState({ isBusyHeader: true, metaBoxErrors: [] });
try {
await this.metaboxPatcher();
this._handleDetails();
}catch ({ responseJSON: { message } }) {
this.setState({ metaBoxErrors: message });
}finally{
this.setState({ isBusyHeader: false });
}
}
}
/**
* Set the attachments browser location.
*
* @param {int} [id=Current selected id] The id
*/
_handleBackboneFilterSelection(id = this.getSelectedId()) {
const attachmentsBrowser = this.attachmentsBrowser;
if (attachmentsBrowser) {
setTimeout(() => {
const backboneFilter = this.getBackboneFilter();
backboneFilter && backboneFilter.$el.val(id).change();
// Reset bulk select in no-modal mode
attachmentsBrowser.$el.parents('.media-modal').size() === 0 && attachmentsBrowser.controller.state().get('selection').reset();
// Check if folder needs refresh
const { store } = this.props;
if (store.foldersNeedsRefresh.indexOf(id) > -1) {
store.removeFoldersNeedsRefresh(id);
this.handleReload();
}
}, 0);
}
}
/**
* Update the creatable buttons regarding the selected type.
*
* @param {int} selectedCreatableType
*/
_updateCreatableButtons(selectedCreatableType) {
this.setState({ selectedCreatableType });
this.state.availableCreatables.forEach(c => this.setState({
['creatable_' + c]: produce(this.state['creatable_' + c], v => {
v.visible = v.visibleInFolderType.indexOf(selectedCreatableType) > -1;
})
}));
}
_updateToolbarButtons() {
const { isWPAttachmentsSortMode, toolbar_order, toolbar_rename, toolbar_trash, toolbar_details } = this.state,
selected = this.getTreeItemById(),
disableIfStatic = !selected,
restrictions = selected && selected.properties && selected.properties.restrictions || [];
const disableOrder = disableIfStatic || isWPAttachmentsSortMode || (selected && selected.contentCustomOrder === 2);
toolbar_order.disabled !== disableOrder && this.setState({
toolbar_order: produce(toolbar_order, draft => {
draft.disabled = disableOrder;
})
});
const disableRename = disableIfStatic || restrictions.indexOf('ren') > -1;
toolbar_rename.disabled !== disableRename && this.setState({
toolbar_rename: produce(toolbar_rename, draft => {
draft.disabled = disableRename;
})
});
const disableTrash = disableIfStatic || restrictions.indexOf('del') > -1;
toolbar_trash.disabled !== disableTrash && this.setState({
toolbar_trash: produce(toolbar_trash, draft => {
draft.disabled = disableTrash;
})
});
toolbar_details.disabled !== disableIfStatic && this.setState({
toolbar_details: produce(toolbar_details, draft => {
draft.disabled = disableIfStatic;
})
});
}
/**
* Fetch folder tree.
*/
async fetchTree(setSelectedId) {
this.setState({ isTreeBusy: true });
const { slugs } = await this.props.store.fetchTree(setSelectedId);
// Modify all available attachments browsers filter
$(FILTER_SELECTOR).each(function() {
const backboneFilter = $(this).data('backboneView');
backboneFilter && backboneFilter.createFilters(slugs);
});
this._handleBackboneFilterSelection();
// Modify this tree
this.setState({ isTreeBusy: false });
latestQueriedFolder.node = this.props.store.selected;
}
/**
* Update the folder count. If you pass no argument the folder count is
* requested from server.
*
* @param {object} counts Key value map of folder and count
*/
async fetchCounts(counts) {
return await this.props.store.fetchCounts(counts);
}
}
export default AppTree;