UNPKG

zotero-web-library

Version:

Web library from zotero.org

1,026 lines (962 loc) 28.7 kB
'use strict'; 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 log = require('libzotero/lib/Log').Logger('zotero-web-library:ItemDetails'); var React = require('react'); var LoadingSpinner = require('./LoadingSpinner.js'); //TODO: trigger showChildren with an extra itemID filter so quick clicks back and forth //between items don't overwrite with the wrong children? var editMatches = function editMatches(props, edit) { if (props === null || edit === null) { return false; } if (edit.field != props.field) { return false; } //field is the same, make sure index matches if set if (edit.creatorIndex != props.creatorIndex) { //log.debug("creatorIndex mismatch"); return false; } if (props.tagIndex != edit.tagIndex) { //log.debug("tagIndex mismatch"); return false; } return true; }; var genericDisplayedFields = function genericDisplayedFields(item) { var genericDisplayedFields = Object.keys(item.apiObj.data).filter(function (field) { if (item.hideFields.indexOf(field) != -1) { return false; } if (!item.fieldMap.hasOwnProperty(field)) { return false; } if (field == 'title' || field == 'creators' || field == 'itemType') { return false; } return true; }); return genericDisplayedFields; }; var CreatorRow = React.createClass({ displayName: 'CreatorRow', getDefaultProps: function getDefaultProps() { return { item: null, library: null, creatorIndex: 0, edit: null }; }, render: function render() { //log.debug("CreatorRow render"); if (this.props.item == null) { return null; } var item = this.props.item; var creator = item.get('creators')[this.props.creatorIndex]; var nameSpans = null; if (creator.name && creator.name != '') { nameSpans = React.createElement(ItemField, _extends({}, this.props, { key: 'name', field: 'name' })); } else { nameSpans = [React.createElement(ItemField, _extends({}, this.props, { key: 'lastName', field: 'lastName' })), ', ', React.createElement(ItemField, _extends({}, this.props, { key: 'firstName', field: 'firstName' }))]; } return React.createElement( 'tr', { className: 'creator-row' }, React.createElement( 'th', null, React.createElement(ItemField, _extends({}, this.props, { field: 'creatorType' })) ), React.createElement( 'td', null, nameSpans, React.createElement( 'div', { className: 'btn-toolbar', role: 'toolbar' }, React.createElement(ToggleCreatorFieldButton, this.props), React.createElement(AddRemoveCreatorFieldButtons, this.props) ) ) ); } }); var ToggleCreatorFieldButton = React.createClass({ displayName: 'ToggleCreatorFieldButton', render: function render() { //log.debug("ToggleCreatorFieldButton render"); return React.createElement( 'div', { className: 'btn-group' }, React.createElement( 'button', { type: 'button', className: 'switch-two-field-creator-link btn btn-default', title: 'Toggle single field creator', 'data-itemkey': this.props.item.get('key'), 'data-creatorindex': this.props.creatorIndex, onClick: this.switchCreatorFields }, React.createElement('span', { className: 'fonticon glyphicons glyphicons-unchecked' }) ) ); }, switchCreatorFields: function switchCreatorFields(evt) { //log.debug("CreatorRow switchCreatorFields"); var creatorIndex = this.props.creatorIndex; var item = this.props.item; var creators = item.get('creators'); var creator = creators[creatorIndex]; //split a single name creator into first/last, or combine first/last //into a single name if (creator.name !== undefined) { var split = creator.name.split(' '); if (split.length > 1) { creator.lastName = split.splice(-1, 1)[0]; creator.firstName = split.join(' '); } else { creator.lastName = creator.name; creator.firstName = ''; } delete creator.name; } else { if (creator.firstName === '' && creator.lastName === '') { creator.name = ''; } else { creator.name = creator.firstName + ' ' + creator.lastName; } delete creator.firstName; delete creator.lastName; } creators[creatorIndex] = creator; Zotero.ui.saveItem(item); this.props.parentItemDetailsInstance.setState({ item: item }); } }); var AddRemoveCreatorFieldButtons = React.createClass({ displayName: 'AddRemoveCreatorFieldButtons', render: function render() { //log.debug("AddRemoveCreatorFieldButtons render"); return React.createElement( 'div', { className: 'btn-group' }, React.createElement( 'button', { type: 'button', className: 'btn btn-default', 'data-creatorindex': this.props.creatorIndex, onClick: this.removeCreator }, React.createElement('span', { className: 'fonticon glyphicons glyphicons-minus' }) ), React.createElement( 'button', { type: 'button', className: 'btn btn-default', 'data-creatorindex': this.props.creatorIndex, onClick: this.addCreator }, React.createElement('span', { className: 'fonticon glyphicons glyphicons-plus' }) ) ); }, addCreator: function addCreator(evt) { log.debug('addCreator', 3); var item = this.props.item; var creatorIndex = this.props.creatorIndex; var creators = item.get('creators'); var newCreator = { creatorType: 'author', firstName: '', lastName: '' }; creators.splice(creatorIndex + 1, 0, newCreator); this.props.parentItemDetailsInstance.setState({ item: item, edit: { field: 'lastName', creatorIndex: creatorIndex + 1 } }); }, removeCreator: function removeCreator(evt) { log.debug('removeCreator', 3); var creatorIndex = this.props.creatorIndex; var item = this.props.item; var creators = item.get('creators'); creators.splice(creatorIndex, 1); Zotero.ui.saveItem(item); this.props.parentItemDetailsInstance.setState({ item: item }); } }); var ItemNavTabs = React.createClass({ displayName: 'ItemNavTabs', getDefaultProps: function getDefaultProps() { return { item: null }; }, render: function render() { log.debug('ItemNavTabs render', 4); if (this.props.item == null) { return null; } if (!this.props.item.isSupplementaryItem()) { return React.createElement( 'ul', { className: 'nav nav-tabs', role: 'tablist' }, React.createElement( 'li', { role: 'presentation', className: 'active' }, React.createElement( 'a', { href: '#item-info-panel', 'aria-controls': 'item-info-panel', role: 'tab', 'data-toggle': 'tab' }, 'Info' ) ), React.createElement( 'li', { role: 'presentation' }, React.createElement( 'a', { href: '#item-children-panel', 'aria-controls': 'item-children-panel', role: 'tab', 'data-toggle': 'tab' }, 'Children' ) ), React.createElement( 'li', { role: 'presentation' }, React.createElement( 'a', { href: '#item-tags-panel', 'aria-controls': 'item-tags-panel', role: 'tab', 'data-toggle': 'tab' }, 'Tags' ) ) ); } return null; } }); var ItemFieldRow = React.createClass({ displayName: 'ItemFieldRow', getDefaultProps: function getDefaultProps() { return { item: null, edit: null }; }, render: function render() { //log.debug("ItemFieldRow render"); var item = this.props.item; var field = this.props.field; var placeholderOrValue = React.createElement(ItemField, { item: item, field: field, edit: this.props.edit, parentItemDetailsInstance: this.props.parentItemDetailsInstance }); if (field == 'url') { var url = item.get('url'); return React.createElement( 'tr', null, React.createElement( 'th', null, React.createElement( 'a', { rel: 'nofollow', href: url }, item.fieldMap[field] ) ), React.createElement( 'td', { className: field }, placeholderOrValue ) ); } else if (field == 'DOI') { var doi = item.get('DOI'); return React.createElement( 'tr', null, React.createElement( 'th', null, React.createElement( 'a', { rel: 'nofollow', href: 'http://dx.doi.org/' + doi }, item.fieldMap[field] ) ), React.createElement( 'td', { className: field }, placeholderOrValue ) ); } else if (Zotero.config.richTextFields[field]) { return React.createElement( 'tr', null, React.createElement( 'th', null, item.fieldMap[field], '}' ), React.createElement( 'td', { className: field }, placeholderOrValue ) ); } else { return React.createElement( 'tr', null, React.createElement( 'th', null, item.fieldMap[field] || field ), React.createElement( 'td', { className: field }, placeholderOrValue ) ); } } }); //set onChange var ItemField = React.createClass({ displayName: 'ItemField', getDefaultProps: function getDefaultProps() { return { item: null, field: null, edit: null, creatorIndex: null, tagIndex: null }; }, handleChange: function handleChange(evt) { //set field to new value var item = this.props.item; switch (this.props.field) { case 'creatorType': case 'name': case 'firstName': case 'lastName': var creators = item.get('creators'); var creator = creators[this.props.creatorIndex]; creator[this.props.field] = evt.target.value; break; case 'tag': var tags = item.get('tags'); var tag = tags[this.props.tagIndex]; tag.tag = evt.target.value; break; default: item.set(this.props.field, evt.target.value); } this.props.parentItemDetailsInstance.setState({ item: item }); }, handleBlur: function handleBlur(evt) { //save item, move edit to next field this.handleChange(evt); this.props.parentItemDetailsInstance.setState({ edit: null }); Zotero.ui.saveItem(this.props.item); }, handleFocus: function handleFocus(evt) { var field = evt.currentTarget.getAttribute('data-field'); var creatorIndex = evt.target.getAttribute('data-creatorindex'); var tagIndex = evt.target.getAttribute('data-tagindex'); var edit = { field: field, creatorIndex: creatorIndex, tagIndex: tagIndex }; this.props.parentItemDetailsInstance.setState({ edit: edit }); }, checkKey: function checkKey(evt) { evt.stopPropagation(); if (evt.keyCode === Zotero.ui.keyCode.ENTER) { //var nextEdit = Zotero.ui.widgets.reactitem.nextEditField(this.props.item, this.props.edit); evt.target.blur(); } }, render: function render() { var item = this.props.item; var field = this.props.field; var creatorField = false; var tagField = false; var value; switch (field) { case 'creatorType': case 'name': case 'firstName': case 'lastName': creatorField = true; var creatorIndex = this.props.creatorIndex; var creator = item.get('creators')[creatorIndex]; value = creator[field]; var creatorPlaceHolders = { 'name': '(name)', 'lastName': '(Last Name)', 'firstName': '(First Name)' }; break; case 'tag': tagField = true; var tagIndex = this.props.tagIndex; var tag = item.get('tags')[tagIndex]; value = tag.tag; break; default: value = item.get(field); } var editThisField = editMatches(this.props, this.props.edit); if (!editThisField) { var spanProps = { className: 'editable-item-field', tabIndex: 0, 'data-field': field, onFocus: this.handleFocus }; var p = null; if (creatorField) { spanProps.className += ' creator-field'; spanProps['data-creatorindex'] = this.props.creatorIndex; p = value == '' ? creatorPlaceHolders[field] : value; } else if (tagField) { spanProps.className += ' tag-field'; spanProps['data-tagindex'] = this.props.tagIndex; p = value; } else { p = value == '' ? React.createElement('div', { className: 'empty-field-placeholder' }) : Zotero.format.itemField(field, item); } return React.createElement( 'span', spanProps, p ); } var focusEl = function focusEl(el) { if (el != null) { el.focus(); } }; var inputProps = { className: 'form-control item-field-control ' + this.props.field, name: field, defaultValue: value, //onChange: this.handleChange, onKeyDown: this.checkKey, onBlur: this.handleBlur, creatorindex: this.props.creatorIndex, tagindex: this.props.tagIndex, ref: focusEl }; if (creatorField) { inputProps.placeholder = creatorPlaceHolders[field]; } switch (this.props.field) { case null: return null; break; case 'itemType': var itemTypeOptions = item.itemTypes.map(function (itemType) { return React.createElement( 'option', { key: itemType.itemType, label: itemType.localized, value: itemType.itemType }, itemType.localized ); }); return React.createElement( 'select', inputProps, itemTypeOptions ); break; case 'creatorType': var creatorTypeOptions = item.creatorTypes[item.get('itemType')].map(function (creatorType) { return React.createElement( 'option', { key: creatorType.creatorType, label: creatorType.localized, value: creatorType.creatorType }, creatorType.localized ); }); return React.createElement( 'select', _extends({ id: 'creatorType' }, inputProps, { 'data-creatorindex': this.props.creatorIndex }), creatorTypeOptions ); break; default: if (Zotero.config.largeFields[this.props.field]) { return React.createElement('textarea', inputProps); } else if (Zotero.config.richTextFields[this.props.field]) { return React.createElement('textarea', _extends({}, inputProps, { className: 'rte default' })); } else { //default single line input field return React.createElement('input', _extends({ type: 'text' }, inputProps)); } } } }); var ItemInfoPanel = React.createClass({ displayName: 'ItemInfoPanel', componentWillMount: function componentWillMount() { var _this = this; var library = this.props.library; library.listen('totalResultsLoaded', function () { _this.setState({ libraryItemsLoaded: true }); }); }, getDefaultProps: function getDefaultProps() { return { item: null, loading: false, edit: null }; }, getInitialState: function getInitialState() { return { libraryItemsLoaded: false }; }, render: function render() { log.debug('ItemInfoPanel render', 3); var reactInstance = this; var library = this.props.library; var item = this.props.item; var itemCountP = React.createElement( 'p', { className: 'item-count', hidden: !this.state.libraryItemsLoaded }, library.items.totalResults + ' items in this view' ); var edit = this.props.edit; if (item == null) { return React.createElement( 'div', { id: 'item-info-panel', role: 'tabpanel', className: 'item-details-div tab-pane active' }, React.createElement(LoadingSpinner, { loading: this.props.loading }), itemCountP ); } var itemKey = item.get('key'); var libraryType = item.owningLibrary.libraryType; var parentUrl = false; if (item.get('parentItem')) { parentUrl = library.websiteUrl({ itemKey: item.get('parentItem') }); } var parentLink = parentUrl ? React.createElement( 'a', { href: parentUrl, className: 'item-select-link', 'data-itemkey': item.get('parentItem') }, 'Parent Item' ) : null; var libraryIDSpan; if (libraryType == 'user') { libraryIDSpan = React.createElement('span', { id: 'libraryUserID', title: item.apiObj.library.id }); } else { libraryIDSpan = React.createElement('span', { id: 'libraryGroupID', title: item.apiObj.library.id }); } //the Zotero user that created the item, if it's a group library item var zoteroItemCreatorRow = null; if (libraryType == 'group') { zoteroItemCreatorRow = React.createElement( 'tr', null, React.createElement( 'th', null, 'Added by' ), React.createElement( 'td', { className: 'user-creator' }, React.createElement( 'a', { href: item.apiObj.meta.createdByUser.links.alternate.href, className: 'user-link' }, item.apiObj.meta.createdByUser.name ) ) ); } var creatorRows = []; var creators = item.get('creators'); if (creators.length == 0) { creators.push({ lastName: '', firstName: '' }); } if (item.isSupplementaryItem()) { creatorRows = null; } else { creatorRows = item.get('creators').map(function (creator, ind) { return React.createElement(CreatorRow, { key: ind, library: library, creatorIndex: ind, item: item, edit: edit, parentItemDetailsInstance: reactInstance.props.parentItemDetailsInstance }); }); } var genericFieldRows = []; //filter out fields we don't want to display or don't want to include as generic var genDisplayedFields = genericDisplayedFields(item); genDisplayedFields.forEach(function (key) { genericFieldRows.push(React.createElement(ItemFieldRow, _extends({ key: key }, reactInstance.props, { field: key }))); }); var typeAndTitle = ['itemType', 'title'].map(function (key) { return React.createElement(ItemFieldRow, _extends({ key: key }, reactInstance.props, { field: key })); }); return React.createElement( 'div', { id: 'item-info-panel', role: 'tabpanel', className: 'item-details-div tab-pane active' }, React.createElement(LoadingSpinner, { loading: this.props.loading }), parentLink, libraryIDSpan, React.createElement( 'table', { className: 'item-info-table table', 'data-itemkey': itemKey }, React.createElement( 'tbody', null, zoteroItemCreatorRow, typeAndTitle, creatorRows, genericFieldRows ) ) ); } }); var TagListRow = React.createClass({ displayName: 'TagListRow', getDefaultProps: function getDefaultProps() { return { tagIndex: 0, tag: { tag: '' }, item: null, library: null, edit: null }; }, removeTag: function removeTag(evt) { var tag = this.props.tag.tag; var item = this.props.item; var tagIndex = this.props.tagIndex; var tags = item.get('tags'); tags.splice(tagIndex, 1); Zotero.ui.saveItem(item); this.props.parentItemDetailsInstance.setState({ item: item }); }, render: function render() { return React.createElement( 'div', { className: 'row item-tag-row' }, React.createElement( 'div', { className: 'col-xs-1' }, React.createElement('span', { className: 'glyphicons fonticon glyphicons-tag' }) ), React.createElement( 'div', { className: 'col-xs-9' }, React.createElement(ItemField, _extends({}, this.props, { field: 'tag' })) ), React.createElement( 'div', { className: 'col-xs-2' }, React.createElement( 'button', { type: 'button', className: 'remove-tag-link btn btn-default', onClick: this.removeTag }, React.createElement('span', { className: 'glyphicons fonticon glyphicons-minus' }) ) ) ); } }); var ItemTagsPanel = React.createClass({ displayName: 'ItemTagsPanel', getInitialState: function getInitialState() { return { newTagString: '' }; }, newTagChange: function newTagChange(evt) { this.setState({ newTagString: evt.target.value }); }, //add the new tag to the item and save if keydown is ENTER checkKey: function checkKey(evt) { evt.stopPropagation(); if (evt.keyCode === Zotero.ui.keyCode.ENTER) { var item = this.props.item; var tags = item.get('tags'); tags.push({ tag: evt.target.value }); Zotero.ui.saveItem(item); this.setState({ newTagString: '' }); this.props.parentItemDetailsInstance.setState({ item: item }); } }, render: function render() { log.debug('ItemTagsPanel render', 3); var reactInstance = this; var item = this.props.item; var library = this.props.library; if (item == null) { return React.createElement('div', { id: 'item-tags-panel', role: 'tabpanel', className: 'item-tags-div tab-pane' }); } var tagRows = item.apiObj.data.tags.map(function (tag, ind) { return React.createElement(TagListRow, _extends({ key: tag.tag }, reactInstance.props, { tag: tag, tagIndex: ind })); }); return React.createElement( 'div', { id: 'item-tags-panel', role: 'tabpanel', className: 'item-tags-div tab-pane' }, React.createElement( 'p', null, React.createElement( 'span', { className: 'tag-count' }, item.get('tags').length ), ' tags' ), React.createElement( 'button', { className: 'add-tag-button btn btn-default' }, 'Add Tag' ), React.createElement( 'div', { className: 'item-tags-list' }, tagRows ), React.createElement( 'div', { className: 'add-tag-form form-horizontal' }, React.createElement( 'div', { className: 'form-group' }, React.createElement( 'div', { className: 'col-xs-1' }, React.createElement( 'label', { htmlFor: 'add-tag-input' }, React.createElement('span', { className: 'glyphicons fonticon glyphicons-tag' }) ) ), React.createElement( 'div', { className: 'col-xs-11' }, React.createElement('input', { type: 'text', onKeyDown: this.checkKey, onChange: this.newTagChange, value: this.state.newTagString, id: 'add-tag-input', className: 'add-tag-input form-control' }) ) ) ) ); } }); var ItemChildrenPanel = React.createClass({ displayName: 'ItemChildrenPanel', getDefaultProps: function getDefaultProps() { return { childItems: [] }; }, triggerUpload: function triggerUpload() { this.props.library.trigger('uploadAttachment'); }, render: function render() { log.debug('ItemChildrenPanel render', 3); var childListEntries = this.props.childItems.map(function (item) { var title = item.get('title'); var href = Zotero.url.itemHref(item); var iconClass = item.itemTypeIconClass(); var key = item.get('key'); if (item.itemType == 'note') { return React.createElement( 'li', { key: key }, React.createElement('span', { className: 'fonticon barefonticon ' + iconClass }), React.createElement( 'a', { className: 'item-select-link', 'data-itemkey': item.get('key'), href: href, title: title }, title ) ); } else if (item.attachmentDownloadUrl == false) { return React.createElement( 'li', { key: key }, React.createElement('span', { className: 'fonticon barefonticon ' + iconClass }), title, '(', React.createElement( 'a', { className: 'item-select-link', 'data-itemkey': item.get('key'), href: href, title: title }, 'Attachment Details' ), ')' ); } else { var attachmentDownloadUrl = Zotero.url.attachmentDownloadUrl(item); return React.createElement( 'li', { key: key }, React.createElement('span', { className: 'fonticon barefonticon ' + iconClass }), React.createElement( 'a', { className: 'itemdownloadlink', href: attachmentDownloadUrl }, title, ' ', Zotero.url.attachmentFileDetails(item) ), '(', React.createElement( 'a', { className: 'item-select-link', 'data-itemkey': item.get('key'), href: href, title: title }, 'Attachment Details' ), ')' ); } }); return React.createElement( 'div', { id: 'item-children-panel', role: 'tabpanel', className: 'item-children-div tab-pane' }, React.createElement( 'ul', { id: 'notes-and-attachments' }, childListEntries ), React.createElement( 'button', { type: 'button', onClick: this.triggerUpload, id: 'upload-attachment-link', className: 'btn btn-primary upload-attachment-button', hidden: !Zotero.config.librarySettings.allowUpload }, 'Upload File' ) ); } }); var ItemDetails = React.createClass({ displayName: 'ItemDetails', getInitialState: function getInitialState() { return { item: null, childItems: [], itemLoading: false, childrenLoading: false, libraryItemsLoaded: false, edit: null }; }, componentWillMount: function componentWillMount() { var reactInstance = this; var library = this.props.library; library.listen('displayedItemChanged', reactInstance.loadItem, {}); library.listen('uploadSuccessful', reactInstance.refreshChildren, {}); library.listen('tagsChanged', reactInstance.updateTypeahead, {}); library.listen('showChildren', reactInstance.refreshChildren, {}); library.trigger('displayedItemChanged'); }, componentDidMount: function componentDidMount() {}, loadItem: function loadItem() { log.debug('Zotero eventful loadItem', 3); var reactInstance = this; var library = this.props.library; //clean up RTEs before we end up removing their dom elements out from under them //Zotero.ui.cleanUpRte(widgetEl); //get the key of the item we need to display, or display library stats var itemKey = Zotero.state.getUrlVar('itemKey'); if (!itemKey) { log.debug('No itemKey - ' + itemKey, 3); reactInstance.setState({ item: null }); return Promise.reject(new Error('No itemkey - ' + itemKey)); } //if we are showing an item, load it from local library of API //then display it var loadedItem = library.items.getItem(itemKey); var loadingPromise; if (loadedItem) { log.debug('have item locally, loading details into ui', 3); loadingPromise = Promise.resolve(loadedItem); } else { log.debug('must fetch item from server', 3); reactInstance.setState({ itemLoading: true }); loadingPromise = library.loadItem(itemKey); } loadingPromise.then(function (item) { loadedItem = item; }).then(function () { return loadedItem.getCreatorTypes(loadedItem.get('itemType')); }).then(function (creatorTypes) { reactInstance.setState({ item: loadedItem, itemLoading: false }); library.trigger('showChildren'); //Zotero.eventful.initTriggers(widgetEl); try { //trigger event for Zotero translator detection var ev = document.createEvent('HTMLEvents'); ev.initEvent('ZoteroItemUpdated', true, true); document.dispatchEvent(ev); } catch (e) { log.error('Error triggering ZoteroItemUpdated event'); } }); loadingPromise.catch(function (err) { log.error('loadItem promise failed'); log.error(err); }).then(function () { reactInstance.setState({ itemLoading: false }); }).catch(Zotero.catchPromiseError); return loadingPromise; }, refreshChildren: function refreshChildren() { log.debug('reactitem.refreshChildren', 3); var reactInstance = this; var library = this.props.library; var itemKey = Zotero.state.getUrlVar('itemKey'); if (!itemKey) { log.debug('No itemKey - ' + itemKey, 3); return Promise.reject(new Error('No itemkey - ' + itemKey)); } var item = library.items.getItem(itemKey); reactInstance.setState({ loadingChildren: true }); var p = item.getChildren(library).then(function (childItems) { reactInstance.setState({ childItems: childItems, loadingChildren: false }); }).catch(Zotero.catchPromiseError); return p; }, updateTypeahead: function updateTypeahead() { log.debug('updateTypeahead', 3); return; }, addTagTypeahead: function addTagTypeahead() { //TODO: reactify }, addTagTypeaheadToInput: function addTagTypeaheadToInput() { //TODO: reactify }, render: function render() { log.debug('ItemDetails render', 3); var reactInstance = this; var library = this.props.library; var item = this.state.item; var childItems = this.state.childItems; return React.createElement( 'div', { role: 'tabpanel' }, React.createElement(ItemNavTabs, { library: library, item: item }), React.createElement( 'div', { className: 'tab-content' }, React.createElement(ItemInfoPanel, { library: library, item: item, loading: this.state.itemLoading, libraryItemsLoaded: this.state.libraryItemsLoaded, edit: this.state.edit, parentItemDetailsInstance: reactInstance }), React.createElement(ItemChildrenPanel, { parentItemDetailsInstance: reactInstance, library: library, childItems: childItems, loading: this.state.childrenLoading }), React.createElement(ItemTagsPanel, { parentItemDetailsInstance: reactInstance, library: library, item: item, edit: this.state.edit }) ) ); } }); module.exports = ItemDetails;