superdesk/superdesk-client-core

View on GitHub
scripts/apps/monitoring/MultiActionBarReact.tsx

Summary

Maintainability
F
4 days
Test Coverage
import React from 'react';

import {IExtensionActivationResult, IArticleActionBulk, IArticle} from 'superdesk-api';
import {flatMap, groupBy} from 'lodash';
import {extensions} from 'appConfig';
import {DropdownTree} from 'core/ui/components/dropdown-tree';
import {Icon} from 'core/ui/components/Icon2';
import {sortByDisplayPriority} from 'core/helpers/sortByDisplayPriority';

export interface IArticleActionBulkExtended extends IArticleActionBulk {
    // this is possible for all extensions since they don't depend on external state
    // most of core actions are relying on service state which would be destroyed if we closed the bar too early
    // when `canAutocloseMultiActionBar` set to false, core actions close it themselves when they are done
    canAutocloseMultiActionBar: boolean;
}

interface IProps {
    articles: Array<IArticle>;
    compact: boolean;
    getCoreActions(articles: Array<IArticle>): Array<IArticleActionBulkExtended>;
    hideMultiActionBar(): void;
}

interface IState {
    actions?: Array<IArticleActionBulkExtended>;
}

function getActionsBulkFromExtensions(articles): Promise<Array<IArticleActionBulkExtended>> {
    const getActionsBulk
    : Array<IExtensionActivationResult['contributions']['entities']['article']['getActionsBulk']>
    = flatMap(
        Object.values(extensions).map(({activationResult}) => activationResult),
        (activationResult) =>
            activationResult.contributions != null
            && activationResult.contributions.entities != null
            && activationResult.contributions.entities.article != null
            && activationResult.contributions.entities.article.getActionsBulk != null
                ? activationResult.contributions.entities.article.getActionsBulk
                : [],
    );

    return Promise.all(
        getActionsBulk.map((getPromise) => getPromise(articles)),
    ).then((res) => flatMap(res).map((action) => ({...action, canAutocloseMultiActionBar: true})));
}

export class MultiActionBarReact extends React.Component<IProps, IState> {
    constructor(props: IProps) {
        super(props);

        this.state = {};

        this.onTrigger = this.onTrigger.bind(this);
    }
    private onTrigger(action: IArticleActionBulkExtended) {
        if (action.canAutocloseMultiActionBar) {
            this.props.hideMultiActionBar();
        }

        action.onTrigger();
    }
    componentDidMount() {
        getActionsBulkFromExtensions(this.props.articles).then((actionsBulkFromExtensions) => {
            this.setState({
                actions: sortByDisplayPriority(
                    this.props.getCoreActions(this.props.articles).concat(actionsBulkFromExtensions),
                ),
            });
        });
    }
    componentDidUpdate(prevProps) {
        // update when more items are selected / deselected
        if (prevProps !== this.props) {
            getActionsBulkFromExtensions(this.props.articles).then((actionsBulkFromExtensions) => {
                this.setState({
                    actions: sortByDisplayPriority(
                        this.props.getCoreActions(this.props.articles).concat(actionsBulkFromExtensions),
                    ),
                });
            });
        }
    }
    render() {
        if (this.state.actions == null) {
            return null;
        }

        if (this.props.compact) {
            return (
                <div className="right-stack" data-test-id="multi-actions-dropdown">
                    <DropdownTree
                        getToggleElement={(isOpen, onClick) => (
                            <button
                                onClick={onClick}
                                className="navbtn"
                                data-test-id="dropdown-toggle"
                            >
                                <i className="icon-dots-vertical" />
                            </button>
                        )}
                        groups={[{render: () => null, items: this.state.actions}]}
                        renderItem={(key, item, closeDropdown) => (
                            <button
                                key={key}
                                style={{
                                    display: 'block',
                                    width: '100%',
                                    padding: 0,
                                    textAlign: 'start',
                                    whiteSpace: 'nowrap',
                                }}
                                onClick={() => {
                                    closeDropdown();
                                    this.onTrigger(item);
                                }}
                                data-test-id={item.label}
                            >
                                <span
                                    style={{
                                        display: 'flex',
                                        justifyContent: 'flex-start',
                                        alignItems: 'center',
                                        padding: '10px',
                                    }}
                                >
                                    <i className={item.icon} style={{marginInlineEnd: 10}} />
                                    <span>{item.label}</span>
                                </span>
                            </button>
                        )}
                    />
                </div>
            );
        } else {
            const groups = groupBy(this.state.actions, (item) => item.group && item.group.label);
            const groupNames = Object.keys(groups);

            return (
                <div className="multi-action-bar-inline" data-test-id="multi-actions-inline">
                    {
                        groupNames.map((group, i) => group === 'undefined'
                            ? groups[group].map((action, key) => (
                                <button
                                    onClick={() => {
                                        this.onTrigger(action);
                                    }}
                                    className="navbtn strict"
                                    title={action.label}
                                    key={key}
                                    data-test-id={action.label}
                                >
                                    <Icon className={action.icon} size={22} />
                                </button>
                            ))
                            : (
                                <DropdownTree
                                    getToggleElement={(isOpen, onClick) => (
                                        <button
                                            onClick={onClick}
                                            className="navbtn"
                                            title={groups[group][0].group.label}
                                            data-test-id="dropdown-toggle"
                                        >
                                            <Icon className={groups[group][0].group.icon} size={22} />
                                        </button>
                                    )}
                                    key={i}
                                    groups={[{render: () => null, items: groups[group]}]}
                                    renderItem={(key, item, closeDropdown) => (
                                        <button
                                            key={key}
                                            style={{
                                                display: 'block',
                                                width: '100%',
                                                padding: 0,
                                                textAlign: 'start',
                                                whiteSpace: 'nowrap',
                                            }}
                                            onClick={() => {
                                                closeDropdown();
                                                this.onTrigger(item);
                                            }}
                                            data-test-id={item.label}
                                        >
                                            <span
                                                style={{
                                                    display: 'flex',
                                                    justifyContent: 'flex-start',
                                                    alignItems: 'center',
                                                    padding: '10px',
                                                }}
                                            >
                                                <i className={item.icon} style={{marginInlineEnd: 10}} />
                                                <span>{item.label}</span>
                                            </span>
                                        </button>
                                    )}
                                />
                            ),
                        )
                    }
                </div>
            );
        }
    }
}