superdesk/superdesk-client-core

View on GitHub
scripts/apps/authoring-react/macros/interactive-macros-display.tsx

Summary

Maintainability
B
4 hrs
Test Coverage
import React from 'react';
import {IconButton} from 'superdesk-ui-framework/react/components/IconButton';
import {Button, Icon, Label, Text, Container} from 'superdesk-ui-framework/react';
import {ContentDivider} from 'superdesk-ui-framework/react/components/ContentDivider';
import {Spacer} from 'core/ui/components/Spacer';
import {gettext} from 'core/utils';
import {IMacro} from 'superdesk-interfaces/Macro';
import {addEditorEventListener, dispatchEditorEvent} from '../authoring-react-editor-events';
import {highlightDistinctMatches} from './macros';
import {editorId} from '../article-widgets/find-and-replace';

interface IProps {
    currentMacro: IMacro;
    onClose(): void;
}

interface IState {
    replaceTarget: string | null;
    replaceValue: string | null;
    currentSelectionIndex: number | null;
}

export class InteractiveMacrosDisplay extends React.PureComponent<IProps, IState> {
    private eventListenerToRemoveBeforeUnmounting: () => void;
    private isAwaitingSelectionIndex: boolean;

    constructor(props: IProps) {
        super(props);

        this.state = {
            replaceTarget: null,
            replaceValue: null,
            currentSelectionIndex: null,
        };
        this.eventListenerToRemoveBeforeUnmounting = () => null;
        this.replaceMatch = this.replaceMatch.bind(this);
        this.goToNextMatchingValue = this.goToNextMatchingValue.bind(this);
        this.goToPrevMatchingValue = this.goToPrevMatchingValue.bind(this);
        this.requestSelectionIndex = this.requestSelectionIndex.bind(this);

        this.isAwaitingSelectionIndex = false;
    }

    goToNextMatchingValue() {
        dispatchEditorEvent('find_and_replace__find_next', {editorId});
        this.requestSelectionIndex();
    }

    goToPrevMatchingValue() {
        dispatchEditorEvent('find_and_replace__find_prev', {editorId});
        this.requestSelectionIndex();
    }

    requestSelectionIndex() {
        this.isAwaitingSelectionIndex = true;
        dispatchEditorEvent('find_and_replace__request_for_current_selection_index', null);
    }

    replaceMatch(callback: () => void) {
        dispatchEditorEvent('find_and_replace__replace', {
            editorId,
            replaceWith: this.state.replaceValue,
            replaceAllMatches: false,
        });
        setTimeout(callback);
    }

    componentDidMount(): void {
        this.eventListenerToRemoveBeforeUnmounting =
            addEditorEventListener('find_and_replace__receive_current_selection_index', (event) => {
                // we only request selection index when we want to do the replacing
                // the `if` below also checks if the same class instance requested the replacing
                if (event.detail.editorId === editorId && this.isAwaitingSelectionIndex) {
                    this.setState({
                        currentSelectionIndex: event.detail.selectionIndex + 1,
                        replaceValue: Object.values(this.props.currentMacro.diff)[event.detail.selectionIndex],
                        replaceTarget: Object.keys(this.props.currentMacro.diff)[event.detail.selectionIndex],
                    });
                    this.isAwaitingSelectionIndex = false;
                }
            });

        highlightDistinctMatches(this.props.currentMacro.diff);
        this.goToNextMatchingValue();
    }

    componentWillUnmount() {
        this.eventListenerToRemoveBeforeUnmounting();
    }

    render(): React.ReactNode {
        return (
            <Container>
                <Spacer v gap="8">
                    <Button
                        expand
                        onClick={() => null}
                        disabled
                        text={this.props.currentMacro.label}
                        size="large"
                        style="hollow"
                    />
                    <Spacer h gap="4" justifyContent="start">
                        <Text weight="medium">
                            {gettext(
                                '{{n}} of {{total}} matches',
                                {
                                    n: this.state.currentSelectionIndex,
                                    total: Object.keys(this.props.currentMacro.diff).length,
                                })}
                        </Text>
                        <Spacer h gap="4" justifyContent="end" noGrow>
                            <IconButton
                                ariaValue={gettext('Next match')}
                                onClick={() => {
                                    this.goToNextMatchingValue();
                                }}
                                size="default"
                                icon="chevron-down-thin"
                            />
                            <IconButton
                                ariaValue={gettext('Previous match')}
                                onClick={() => {
                                    this.goToPrevMatchingValue();
                                }}
                                size="default"
                                icon="chevron-up-thin"
                            />
                        </Spacer>
                    </Spacer>
                    {this.state.replaceTarget != null && (
                        <>
                            <ContentDivider type="dotted" margin="x-small" />
                            <Spacer
                                h
                                gap="4"
                                justifyContent="start"
                                noGrow
                                alignItems="center"
                                style={{flexWrap: 'wrap'}}
                            >
                                <Label size="large" text={this.state.replaceTarget} />
                                <Icon name="arrow-right" size="small" />
                                <Label size="large" text={this.state.replaceValue} />
                            </Spacer>
                            <ContentDivider type="dotted" margin="x-small" />
                        </>
                    )}
                    <Spacer h gap="4" justifyContent="end" noGrow>
                        <Button
                            style="hollow"
                            text={gettext('Cancel')}
                            onClick={() => {
                                highlightDistinctMatches({});
                                this.props.onClose();
                            }}
                        />
                        <Button
                            style="hollow"
                            type="primary"
                            text={gettext('Replace')}
                            onClick={() => {
                                this.replaceMatch(() => highlightDistinctMatches(this.props.currentMacro.diff));
                            }}
                        />
                    </Spacer>
                </Spacer>
            </Container>
        );
    }
}