superdesk/superdesk-client-core

View on GitHub
scripts/extensions/broadcasting/src/rundowns/prepare-create-edit-rundown-item.ts

Summary

Maintainability
D
1 day
Test Coverage
import {isEqual} from 'lodash';
import {rundownItemContentProfile} from '../rundown-items/content-profile';
import {
    IAuthoringAutoSave,
    IAuthoringStorage,
    IPatchResponseExtraFields,
} from 'superdesk-api';
import {IRundown, IRundownItem} from '../interfaces';
import {superdesk} from '../superdesk';
import {IWithAuthoringReactKey} from '../rundown-templates/template-edit';
import {prepareRundownItemForSaving} from './rundown-view-edit';

const {tryLocking, fixPatchResponse, fixPatchRequest} = superdesk.helpers;
const {generatePatch} = superdesk.utilities;
const {httpRequestJsonLocal} = superdesk;

interface IRundownItemActionBase extends IWithAuthoringReactKey {
    sideWidget: null | {
        name: string;
        pinned: boolean;
    };
}

interface ICreate extends IRundownItemActionBase {
    type: 'create';
    rundownId: IRundown['_id'];
    initialData: Partial<IRundownItem>;
    authoringStorage: IAuthoringStorage<IRundownItem>;
}

interface IEdit extends IRundownItemActionBase {
    type: 'edit';
    itemId: IRundownItem['_id'];
    authoringStorage: IAuthoringStorage<IRundownItem>;
}

interface IPreview extends IRundownItemActionBase {
    type: 'preview';
    itemId: IRundownItem['_id'];
    authoringStorage: IAuthoringStorage<IRundownItem>;
}

export type IRundownItemActionNext = ICreate | IEdit | IPreview | null;

function getRundownItemAuthoringStorage(id: IRundownItem['_id']): IAuthoringStorage<IRundownItem> {
    class AutoSaveRundownItem implements IAuthoringAutoSave<IRundownItem> {
        get() {
            return httpRequestJsonLocal<IRundownItem>({
                method: 'GET',
                path: `/rundown_items/${id}`,
            });
        }

        delete() {
            return Promise.resolve();
        }

        schedule(
            getItem: () => IRundownItem,
            callback: (autosaved: IRundownItem) => void,
        ) {
            callback(getItem());
        }

        cancel() {
            // noop
        }

        flush(): Promise<void> {
            return Promise.resolve();
        }
    }

    const authoringStorageRundownItem: IAuthoringStorage<IRundownItem> = {
        autosave: new AutoSaveRundownItem(),
        getEntity: () => {
            return httpRequestJsonLocal<IRundownItem>({
                method: 'GET',
                path: `/rundown_items/${id}`,
            }).then((saved) => ({saved, autosaved: null}));
        },
        isLockedInCurrentSession: () => false,
        forceLock: (entity) => {
            return tryLocking<IRundownItem>('/rundown_items', entity._id, true)

                // force-unlock can't fail
                .then((res) => res.success ? res.latestEntity : entity);
        },
        saveEntity: (current, original) => {
            const patch: Partial<IRundownItem> = {
                ...fixPatchRequest(
                    prepareRundownItemForSaving(
                        generatePatch(original, current, {undefinedEqNull: true}),
                    ),
                ),
                fields_meta: current.fields_meta ?? {}, // maintain fields_meta; attempting to patch would drop fields
            };

            return httpRequestJsonLocal<IRundownItem & IPatchResponseExtraFields>({
                method: 'PATCH',
                path: `/rundown_items/${id}`,
                payload: patch,
                headers: {
                    'If-Match': original._etag,
                },
            }).then((patchRes) => fixPatchResponse(patchRes));
        },
        getContentProfile: () => {
            return Promise.resolve(rundownItemContentProfile);
        },
        closeAuthoring: (current, original, _cancelAutosave, doClose) => {
            const warnAboutLosingChanges = !isEqual(current, original);

            if (warnAboutLosingChanges) {
                return superdesk.ui.confirm('Discard unsaved changes?').then((confirmed) => {
                    if (confirmed) {
                        doClose();
                    }
                });
            } else {
                doClose();
            }

            return Promise.resolve();
        },
        getUserPreferences: () => Promise.resolve({'spellchecker:status': {enabled: true}}), // FINISH: remove test data
    };

    return authoringStorageRundownItem;
}

function getRundownItemCreationAuthoringStorage(
    rundownId: IRundown['_id'],
    initialData: Partial<IRundownItem>,
    onSave: (item: IRundownItem) => Promise<IRundownItem>,
): IAuthoringStorage<IRundownItem> {
    class AutoSaveRundownItem implements IAuthoringAutoSave<IRundownItem> {
        get() {
            return Promise.resolve(initialData as IRundownItem);
        }

        delete() {
            return Promise.resolve();
        }

        schedule(
            getItem: () => IRundownItem,
            callback: (autosaved: IRundownItem) => void,
        ) {
            callback(getItem());
        }

        cancel() {
            // noop
        }

        flush(): Promise<void> {
            return Promise.resolve();
        }
    }

    const contentProfile = rundownItemContentProfile;

    const authoringStorageRundownItem: IAuthoringStorage<IRundownItem> = {
        autosave: new AutoSaveRundownItem(),
        getEntity: () => {
            return Promise.resolve({saved: initialData as IRundownItem, autosaved: null});
        },
        isLockedInCurrentSession: () => true,
        forceLock: (entity) => {
            return Promise.resolve(entity);
        },
        saveEntity: (current) => {
            const allFields = contentProfile.header.merge(contentProfile.content);
            const readOnlyFields = allFields.filter((field) => field.fieldConfig.readOnly === true);

            const itemToSave: IRundownItem = {
                ...current,
                rundown: rundownId, // read-only, but needs to be sent when creating an item
            };

            if (itemToSave.duration == null) {
                itemToSave.duration = itemToSave.planned_duration;
            }

            // same logic is applied when creating a rundown item inside the template
            readOnlyFields.toArray().forEach((field) => {
                /**
                 * Remove read-only fields to avoid getting an error from the server.
                 * Since it's read-only it would contain an empty value anyway.
                 */
                delete (itemToSave as {[key: string]: any})[field.id];
            });

            return onSave(itemToSave);
        },
        getContentProfile: () => {
            return Promise.resolve(contentProfile);
        },
        closeAuthoring: (_current, _original, _cancelAutosave, doClose) => {
            return superdesk.ui.confirm('Discard unsaved changes?').then((confirmed) => {
                if (confirmed) {
                    doClose();
                }
            });
        },
        getUserPreferences: () => Promise.resolve({'spellchecker:status': {enabled: true}}), // FINISH: remove test data
    };

    return authoringStorageRundownItem;
}

export function prepareForCreation(
    rundownId: string,
    currentAction: IRundownItemActionNext,
    initialValue: Partial<IRundownItem>,
    onSave: (item: IRundownItem) => Promise<IRundownItem>,
): ICreate {
    return {
        type: 'create',
        rundownId,
        initialData: initialValue,
        authoringStorage: getRundownItemCreationAuthoringStorage(
            rundownId,
            initialValue,
            onSave,
        ),
        authoringReactKey: currentAction == null ? 0 : currentAction.authoringReactKey + 1,
        sideWidget: null,
    };
}

export function prepareForEditing(
    currentAction: IRundownItemActionNext,
    id: IRundownItem['_id'],
): IEdit {
    return {
        type: 'edit',
        itemId: id,
        authoringStorage: getRundownItemAuthoringStorage(id),
        authoringReactKey: currentAction == null ? 0 : currentAction.authoringReactKey + 1,
        sideWidget: null,
    };
}

export function prepareForPreview(
    currentAction: IRundownItemActionNext,
    id: IRundownItem['_id'],
): IPreview {
    return {
        type: 'preview',
        itemId: id,
        authoringStorage: getRundownItemAuthoringStorage(id),
        authoringReactKey: currentAction == null ? 0 : currentAction.authoringReactKey + 1,
        sideWidget: null,
    };
}