superdesk/superdesk-client-core

View on GitHub
scripts/extensions/broadcasting/src/page.tsx

Summary

Maintainability
D
1 day
Test Coverage
import * as React from 'react';
import {noop} from 'lodash';
import * as Layout from 'superdesk-ui-framework/react/components/Layouts';

import {
    ButtonGroup,
    NavButton,
    SubNav,
    Dropdown,
    CreateButton,
    Button,
    SearchBar,
    Modal,
} from 'superdesk-ui-framework/react';

import {ManageRundownTemplates} from './rundown-templates/manage-rundown-templates';
import {CreateShowModal} from './shows/create-show-modal';

import {classnames, showModal} from '@superdesk/common';

import {CreateRundownFromTemplate} from './rundowns/create-rundown-from-template';
import {RundownsList} from './rundowns/rundowns-list';
import {IRundownAction, RundownViewEdit} from './rundowns/rundown-view-edit';
import {IRundown, IRundownFilters} from './interfaces';
import {FilteringInputs} from './rundowns/components/filtering-inputs';
import {AppliedFilters} from './rundowns/components/applied-filters';

import {superdesk} from './superdesk';
import {ManageShows} from './shows/manage-shows';
import {IRundownItemActionNext, prepareForPreview} from './rundowns/prepare-create-edit-rundown-item';
import {events, IBroadcastingEvents} from './events';
import {IPage} from 'superdesk-api';

const {gettext} = superdesk.localization;
const {isLockedInCurrentSession} = superdesk.utilities;
const {tryLocking, tryUnlocking} = superdesk.helpers;

type IProps = React.ComponentProps<IPage['component']>;

interface IState {
    rundownAction: IRundownAction;
    rundownItemAction: IRundownItemActionNext;
    searchString: string;
    filtersOpen: boolean;
    filters: IRundownFilters;
    filtersApplied: IRundownFilters;
}

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

        this.state = {
            rundownAction: null,
            rundownItemAction: null,
            searchString: '',
            filtersOpen: false,
            filters: {},
            filtersApplied: {},
        };

        this.updateFullWidthCapability = this.updateFullWidthCapability.bind(this);
        this.setFilter = this.setFilter.bind(this);
        this.prepareRundownEditing = this.prepareRundownEditing.bind(this);
        this.prepareNextRundownItemAction = this.prepareNextRundownItemAction.bind(this);
        this.openRundownItemEventHandler = this.openRundownItemEventHandler.bind(this);
    }

    private updateFullWidthCapability(options?: {disable: true}) {
        if (options?.disable === true) {
            this.props.setupFullWidthCapability({enabled: false});
            return;
        }

        const {rundownAction} = this.state;

        if (rundownAction == null) {
            this.props.setupFullWidthCapability({enabled: true, allowed: false});
        } else {
            this.props.setupFullWidthCapability({
                enabled: true,
                allowed: true,
                onToggle: (val) => {
                    this.setState({
                        rundownAction: {
                            ...rundownAction,
                            fullWidth: val,
                        },
                    });
                },
            });
        }
    }

    private setFilter(filters: Partial<IState['filters']>) {
        this.setState({
            filters: {
                ...this.state.filters,
                ...filters,
            },
        });
    }

    private prepareRundownEditing(id: IRundown['_id']): Promise<IState['rundownAction']> {
        return tryLocking<IRundown>('/rundowns', id).then(({success}) => {
            if (success) {
                return {mode: 'edit', id, fullWidth: this.state.rundownAction?.fullWidth ?? false};
            } else {
                return {mode: 'view', id, fullWidth: this.state.rundownAction?.fullWidth ?? false};
            }
        });
    }

    private prepareNextRundownItemAction(actionNext: IRundownItemActionNext): Promise<IState['rundownItemAction']> {
        if (actionNext != null && actionNext.type === 'edit') {
            return tryLocking('/rundown_items', actionNext.itemId).then(() => {
                return actionNext;
            });
        } else {
            return Promise.resolve(actionNext);
        }
    }

    private openRundownItemEventHandler(event: CustomEvent<IBroadcastingEvents['openRundownItem']>): void {
        const {rundownId, rundownItemId, sidePanel} = event.detail;

        Promise.all([
            this.prepareRundownEditing(rundownId),
            this.prepareNextRundownItemAction(prepareForPreview(this.state.rundownItemAction, rundownItemId)),
        ]).then(([rundownViewEditNext, rundownItemActionNext]) => {
            const _rundownItemActionNext =
                rundownItemActionNext == null || sidePanel == null
                    ? rundownItemActionNext
                    : {
                        ...rundownItemActionNext,
                        sideWidget: {
                            name: sidePanel,
                            pinned: false,
                        },
                    };

            this.setState({
                rundownAction: rundownViewEditNext,
                rundownItemAction: _rundownItemActionNext,
            });
        });
    }

    componentDidMount(): void {
        events.addListener('openRundownItem', this.openRundownItemEventHandler);
        events.dispatchEvent('broadcastingPageDidLoad', true);

        this.updateFullWidthCapability();
    }

    componentDidUpdate(): void {
        this.updateFullWidthCapability();
    }

    componentWillUnmount(): void {
        events.removeListener('openRundownItem', this.openRundownItemEventHandler);

        this.updateFullWidthCapability({disable: true});
    }

    render() {
        const {rundownAction} = this.state;
        const rundownItemOpen = rundownAction != null && this.state.rundownItemAction != null;
        const rundownsListVisible = rundownAction?.fullWidth !== true && !rundownItemOpen;

        return (
            <div
                style={{
                    marginBlockStart: 'var(--top-navigation-height)',
                    width: '100%',
                    height: 'calc(100% - var(--top-navigation-height))',
                }}
            >
                <div
                    className={classnames(
                        'sd-content sd-content-wrapper',
                        {'sd-content-wrapper--editor-full': rundownsListVisible !== true},
                    )}
                >
                    {
                        rundownsListVisible && (
                            <React.Fragment>
                                <Layout.LayoutContainer>
                                    <Layout.HeaderPanel>
                                        <SubNav zIndex={2}>
                                            <SearchBar
                                                placeholder={gettext('Search')}
                                                value={this.state.searchString}
                                                onSubmit={(val) => {
                                                    if (typeof val === 'number') {
                                                        throw new Error('invalid state');
                                                    }

                                                    this.setState({
                                                        searchString: val,
                                                    });
                                                }}
                                            />

                                            <ButtonGroup align="end" spaces="no-space">
                                                <Dropdown
                                                    items={[
                                                        {
                                                            type: 'group',
                                                            label: gettext('Settings'),
                                                            items: [
                                                                'divider',
                                                                {
                                                                    icon: 'switches',
                                                                    label: gettext('Manage Rundown Templates'),
                                                                    onSelect: () => {
                                                                        showModal(({closeModal}) => (
                                                                            <ManageRundownTemplates
                                                                                dialogTitle={
                                                                                    gettext('Manage rundown templates')
                                                                                }
                                                                                closeModal={closeModal}
                                                                            />
                                                                        ));
                                                                    },
                                                                },
                                                                {
                                                                    icon: 'switches',
                                                                    label: gettext('Manage Shows'),
                                                                    onSelect: () => {
                                                                        showModal(({closeModal}) => (
                                                                            <Modal
                                                                                visible
                                                                                headerTemplate={gettext('Manage Shows')}
                                                                                contentBg="medium"
                                                                                contentPadding="none"
                                                                                size="large"
                                                                                onHide={() => {
                                                                                    closeModal();
                                                                                }}
                                                                                zIndex={1050}
                                                                            >
                                                                                <ManageShows />
                                                                            </Modal>
                                                                        ));
                                                                    },
                                                                },
                                                            ],
                                                        },
                                                    ]}
                                                >
                                                    <NavButton icon="settings" onClick={() => false} />
                                                </Dropdown>

                                                <Dropdown
                                                    header={[
                                                        {
                                                            type: 'group',
                                                            label: gettext('Create new'),
                                                            items: [
                                                                {
                                                                    icon: 'rundown',
                                                                    label: gettext('Rundown'),
                                                                    onSelect: () => {
                                                                        showModal(({closeModal}) => (
                                                                            <CreateRundownFromTemplate
                                                                                onClose={closeModal}
                                                                            />
                                                                        ));
                                                                    },
                                                                },
                                                            ],
                                                        },
                                                    ]}
                                                    items={[]}
                                                    footer={[
                                                        {
                                                            type: 'group',
                                                            items: [
                                                                {
                                                                    icon: 'rundown',
                                                                    label: gettext('Create new Show'),
                                                                    onSelect: () => {
                                                                        showModal(CreateShowModal);
                                                                    },
                                                                },
                                                            ],
                                                        },
                                                    ]}
                                                >

                                                    <CreateButton
                                                        ariaValue={gettext('Create')}
                                                        onClick={noop}
                                                    />
                                                </Dropdown>
                                            </ButtonGroup>
                                        </SubNav>
                                        <SubNav zIndex={1}>
                                            <ButtonGroup align="start">
                                                <NavButton
                                                    icon="filter-large"
                                                    onClick={() => {
                                                        this.setState({
                                                            filtersOpen: !this.state.filtersOpen,
                                                        });
                                                    }}
                                                />

                                                <div>
                                                    <AppliedFilters
                                                        filters={this.state.filtersApplied}
                                                        onChange={(val) => {
                                                            this.setState({
                                                                filters: val,
                                                                filtersApplied: val,
                                                            });
                                                        }}
                                                    />
                                                </div>
                                            </ButtonGroup>
                                        </SubNav>
                                    </Layout.HeaderPanel>

                                    <Layout.LeftPanel open={this.state.filtersOpen}>
                                        <Layout.Panel side="left" background="grey">
                                            <Layout.PanelHeader
                                                title="Advanced filters"
                                                onClose={() => {
                                                    this.setState({filtersOpen: false});
                                                }}
                                            />

                                            <Layout.PanelContent>
                                                <Layout.PanelContentBlock>
                                                    <FilteringInputs
                                                        filters={this.state.filters}
                                                        onChange={this.setFilter}
                                                    />
                                                </Layout.PanelContentBlock>
                                            </Layout.PanelContent>
                                            <Layout.PanelFooter>
                                                <Button
                                                    text={gettext('Clear')}
                                                    style="hollow"
                                                    onClick={() => {
                                                        this.setState({
                                                            filters: {},
                                                            filtersApplied: {},
                                                        });
                                                    }}
                                                />

                                                <Button
                                                    text={gettext('Filter')}
                                                    type="primary"
                                                    onClick={() => {
                                                        this.setState({filtersApplied: this.state.filters});
                                                    }}
                                                />
                                            </Layout.PanelFooter>
                                        </Layout.Panel>
                                    </Layout.LeftPanel>

                                    <Layout.MainPanel>
                                        <RundownsList
                                            rundownAction={rundownAction}
                                            preview={(id) => {
                                                this.setState({
                                                    rundownAction: {
                                                        mode: 'view',
                                                        fullWidth: this.state.rundownAction?.fullWidth ?? false,
                                                        id,
                                                    },
                                                });
                                            }}
                                            onEditModeChange={(id, rundownItemAction) => {
                                                Promise.all([
                                                    this.prepareRundownEditing(id),
                                                    this.prepareNextRundownItemAction(rundownItemAction ?? null),
                                                ]).then(([rundownViewEditNext, rundownItemActionNext]) => {
                                                    this.setState({
                                                        rundownAction: rundownViewEditNext,
                                                        rundownItemAction: rundownItemActionNext,
                                                    });
                                                });
                                            }}
                                            searchString={this.state.searchString}
                                            filters={this.state.filtersApplied}
                                            rundownItemAction={this.state.rundownItemAction}
                                        />
                                    </Layout.MainPanel>

                                    <Layout.OverlayPanel />
                                </Layout.LayoutContainer>

                                <Layout.ContentSplitter visible={true} disabled />
                            </React.Fragment>
                        )
                    }
                    <Layout.AuthoringContainer open={rundownAction != null}>
                        {
                            rundownAction != null && (
                                <RundownViewEdit
                                    key={rundownAction.id + rundownAction.mode}
                                    rundownAction={rundownAction}
                                    rundownId={rundownAction.id}
                                    onClose={(rundown: IRundown) => {
                                        const doUnlock = isLockedInCurrentSession(rundown)
                                            ? tryUnlocking('/rundowns', rundown._id)
                                            : Promise.resolve();

                                        doUnlock.finally(() => {
                                            this.setState({rundownAction: null, rundownItemAction: null});
                                        });
                                    }}
                                    readOnly={rundownAction == null || rundownAction.mode === 'view'}
                                    rundownItemAction={this.state.rundownItemAction}
                                    onRundownActionChange={(actionNext) => {
                                        this.setState({rundownAction: actionNext});
                                    }}
                                    onRundownItemActionChange={(rundownItemAction) => {
                                        this.prepareNextRundownItemAction(rundownItemAction).then((next) => {
                                            this.setState({rundownItemAction: next});
                                        });
                                    }}
                                />
                            )
                        }
                    </Layout.AuthoringContainer>
                </div>
            </div>
        );
    }
}