/* eslint jsx-a11y/tabindex-no-positive: 0 */ import _ from 'underscore' import React, {Component, PropTypes} from 'react' import { Menu, RetinaImg, LabelColorizer, BoldedSearchResult, } from 'nylas-component-kit' import { Utils, Actions, TaskQueueStatusStore, DatabaseStore, TaskFactory, Category, SyncbackCategoryTask, CategoryStore, FocusedPerspectiveStore, } from 'nylas-exports' import {Categories} from 'nylas-observables' export default class CategoryPickerPopover extends Component { static propTypes = { threads: PropTypes.array.isRequired, account: PropTypes.object.isRequired, }; constructor(props) { super(props) this._categories = [] this._standardCategories = [] this._userCategories = [] this.state = this._recalculateState(this.props, {searchValue: ''}) } componentDidMount() { this._registerObservables() } componentWillReceiveProps(nextProps) { this._registerObservables(nextProps) this.setState(this._recalculateState(nextProps)) } componentWillUnmount() { this._unregisterObservables() } _registerObservables = (props = this.props) => { this._unregisterObservables() this.disposables = [ Categories.forAccount(props.account).sort().subscribe(this._onCategoriesChanged), ] }; _unregisterObservables = () => { if (this.disposables) { this.disposables.forEach(disp => disp.dispose()) } }; _isInSearch = (searchValue, category) => { return Utils.wordSearchRegExp(searchValue).test(category.displayName) }; _isUserFacing = (allInInbox, category) => { const currentCategories = FocusedPerspectiveStore.current().categories() || [] const currentCategoryIds = _.pluck(currentCategories, 'id') const {account} = this.props let hiddenCategories = [] if (account) { if (account.usesLabels()) { hiddenCategories = Category.StandardCategoryNames.concat(["N1-Snoozed"]) if (allInInbox) { hiddenCategories.push("inbox") } if (category.divider) { return false } } else if (account.usesFolders()) { hiddenCategories = ["drafts", "sent", "N1-Snoozed"] } } return ( (!hiddenCategories.includes(category.name)) && (!hiddenCategories.includes(category.displayName)) && (!currentCategoryIds.includes(category.id)) ) }; _itemForCategory = ({usageCount, numThreads}, category) => { if (category.divider) { return category } const item = category.toJSON() item.category = category item.backgroundColor = LabelColorizer.backgroundColorDark(category) item.usage = usageCount[category.id] || 0 item.numThreads = numThreads return item }; _allInInbox = (usageCount, numThreads) => { const {account} = this.props const inbox = CategoryStore.getStandardCategory(account, "inbox") if (!inbox) return false return usageCount[inbox.id] === numThreads }; _categoryUsageCount = (props) => { const {threads} = props const categoryUsageCount = {} _.flatten(_.pluck(threads, 'categories')).forEach((category) => { categoryUsageCount[category.id] = categoryUsageCount[category.id] || 0 categoryUsageCount[category.id] += 1 }) return categoryUsageCount; }; _recalculateState = (props = this.props, {searchValue = (this.state.searchValue || "")} = {}) => { const {account, threads} = props const numThreads = threads.length let categories; if (numThreads === 0) { return {categoryData: [], searchValue} } if (account.usesLabels()) { categories = this._categories } else { categories = this._standardCategories .concat([{divider: true, id: "category-divider"}]) .concat(this._userCategories) } const usageCount = this._categoryUsageCount(props, categories) const allInInbox = this._allInInbox(usageCount, numThreads) const displayData = {usageCount, numThreads} const categoryData = _.chain(categories) .filter(_.partial(this._isUserFacing, allInInbox)) .filter(_.partial(this._isInSearch, searchValue)) .map(_.partial(this._itemForCategory, displayData)) .value() if (searchValue.length > 0) { const newItemData = { searchValue: searchValue, newCategoryItem: true, id: "category-create-new", } categoryData.push(newItemData) } return {categoryData, searchValue} }; _onCategoriesChanged = (categories) => { this._categories = categories this._standardCategories = categories.filter((cat) => cat.isStandardCategory()) this._userCategories = categories.filter((cat) => cat.isUserCategory()) this.setState(this._recalculateState()) }; _onEscape = () => { Actions.closePopover() }; _onSelectCategory = (item) => { const {account, threads} = this.props if (threads.length === 0) return; if (item.newCategoryItem) { const category = new Category({ displayName: this.state.searchValue, accountId: account.id, }) const syncbackTask = new SyncbackCategoryTask({category}) TaskQueueStatusStore.waitForPerformRemote(syncbackTask).then(() => { DatabaseStore.findBy(category.constructor, {clientId: category.clientId}) .then((cat) => { const applyTask = TaskFactory.taskForApplyingCategory({ threads: threads, category: cat, }) Actions.queueTask(applyTask) }) }) Actions.queueTask(syncbackTask) } else if (item.usage === threads.length) { const applyTask = TaskFactory.taskForRemovingCategory({ threads: threads, category: item.category, }) Actions.queueTask(applyTask) } else { const applyTask = TaskFactory.taskForApplyingCategory({ threads: threads, category: item.category, }) Actions.queueTask(applyTask) } if (account.usesFolders()) { // In case we are drilled down into a message Actions.popSheet() } Actions.closePopover() }; _onSearchValueChange = (event) => { this.setState( this._recalculateState(this.props, {searchValue: event.target.value}) ) }; _renderFolderIcon = (item) => { return ( ) }; _renderCheckbox = (item) => { const styles = {} let checkStatus; styles.backgroundColor = item.backgroundColor if (item.usage === 0) { checkStatus = } else if (item.usage < item.numThreads) { checkStatus = ( this._onSelectCategory(item)} /> ) } else { checkStatus = ( this._onSelectCategory(item)} /> ) } return ( this._onSelectCategory(item)} /> {checkStatus} ) }; _renderCreateNewItem = ({searchValue}) => { const {account} = this.props let picName = '' if (account) { picName = account.usesLabels() ? 'tag' : 'folder' } return ( “{searchValue}” (create new) ) }; _renderItem = (item) => { if (item.divider) { return } else if (item.newCategoryItem) { return this._renderCreateNewItem(item) } const {account} = this.props let icon; if (account) { icon = account.usesLabels() ? this._renderCheckbox(item) : this._renderFolderIcon(item); } else { return } return ( {icon} ) }; render() { const {account} = this.props let placeholder = '' if (account) { placeholder = account.usesLabels() ? 'Label as' : 'Move to folder' } const headerComponents = [ , ] return ( item.id} itemContent={this._renderItem} onSelect={this._onSelectCategory} onEscape={this._onEscape} defaultSelectedIndex={this.state.searchValue === "" ? -1 : 0} /> ) } }