bookbrainz/bookbrainz-site

View on GitHub
src/client/unified-form/helpers.ts

Summary

Maintainability
D
2 days
Test Coverage
import {ADD_AUTHOR, ADD_PUBLISHER} from './cover-tab/action';
import {Action, State} from './interface/type';
import {CLOSE_ENTITY_MODAL, DUMP_EDITION, LOAD_EDITION, OPEN_ENTITY_MODAL} from './action';
import {ISBNReducer, authorsReducer, autoISBNReducer, publishersReducer} from './cover-tab/reducer';
import {seriesReducer, worksReducer} from './content-tab/reducer';
import {ADD_EDITION_GROUP} from './detail-tab/action';
import {DUPLICATE_WORK} from './content-tab/action';
import Immutable from 'immutable';
import aliasEditorReducer from '../entity-editor/alias-editor/reducer';
import annotationSectionReducer from '../entity-editor/annotation-section/reducer';
import authorCreditEditorReducer from '../entity-editor/author-credit-editor/reducer';
import authorSectionReducer from '../entity-editor/author-section/reducer';
import buttonBarReducer from '../entity-editor/button-bar/reducer';
import {camelCase} from 'lodash';
import {combineReducers} from 'redux-immutable';
import {convertMapToObject} from '../helpers/utils';
import editionGroupSectionReducer from '../entity-editor/edition-group-section/reducer';
import editionGroupsReducer from './detail-tab/reducer';
import editionSectionReducer from '../entity-editor/edition-section/reducer';
import identifierEditorReducer from '../entity-editor/identifier-editor/reducer';
import nameSectionReducer from '../entity-editor/name-section/reducer';
import publisherSectionReducer from '../entity-editor/publisher-section/reducer';
import relationshipSectionReducer from '../entity-editor/relationship-editor/reducer';
import seriesSectionReducer from '../entity-editor/series-section/reducer';
import submissionSectionReducer from '../entity-editor/submission-section/reducer';
import workSectionReducer from '../entity-editor/work-section/reducer';


type ReduxWindow = typeof window & {__REDUX_DEVTOOLS_EXTENSION_COMPOSE__: any};
export function shouldDevToolsBeInjected(): boolean {
    return Boolean(
        typeof window === 'object' &&
        (window as ReduxWindow).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    );
}

function newEditionReducer(state = Immutable.Map({}), action) {
    const {type, payload} = action;
    switch (type) {
        case DUMP_EDITION:
            return state.set(payload.id, Immutable.fromJS(payload.value));
        case LOAD_EDITION:
            return Immutable.Map({});
        default:
            return state;
    }
}
function entityModalIsOpenReducer(state = false, action) {
    const {type} = action;
    switch (type) {
        case OPEN_ENTITY_MODAL:
            return true;
        case CLOSE_ENTITY_MODAL:
            return false;
        default:
            return state;
    }
}
const initialACState = Immutable.fromJS(
    {
        n0: {
            author: null,
            automaticJoinPhrase: true,
            joinPhrase: '',
            name: ''
        }
    }
);
const initialState = Immutable.Map({
    aliasEditor: Immutable.Map({}),
    annotationSection: Immutable.Map({content: ''}),
    buttonBar: Immutable.Map({
        aliasEditorVisible: false,
        identifierEditorVisible: false
    }),
    editionSection: Immutable.Map({
        authorCreditEditorVisible: false,
        authorCreditEnable: true,
        editionGroupVisible: true,
        format: null,
        languages: Immutable.List([]),
        matchingNameEditionGroups: [],
        physicalEnable: true,
        publisher: Immutable.Map({}),
        releaseDate: '',
        status: null
    }),
    identifierEditor: Immutable.OrderedMap(),
    nameSection: Immutable.Map({
        disambiguation: '',
        exactMatches: [],
        language: null,
        name: '',
        searchResults: [],
        sortName: ''
    }),
    relationshipSection: Immutable.Map({
        canEdit: true,
        lastRelationships: null,
        relationshipEditorProps: null,
        relationshipEditorVisible: false,
        relationships: Immutable.OrderedMap()
    }),
    submissionSection: Immutable.Map({
        note: '',
        submitError: '',
        submitted: false
    }),
    workSection: Immutable.Map({
        languages: Immutable.List([]),
        type: null
    })
});

// this reducer act as a intermediary between the entity editor reducers and the UF state
function crossSliceReducer(state:State, action:Action) {
    const {type} = action;
    let intermediateState = state;
    const activeEntityState = {
        aliasEditor: state.get('aliasEditor'),
        annotationSection: state.get('annotationSection'),
        buttonBar: state.get('buttonBar'),
        identifierEditor: state.get('identifierEditor'),
        nameSection: state.get('nameSection'),
        relationshipSection: state.get('relationshipSection'),
        submissionSection: state.get('submissionSection')
    };
    const newEntity = action.payload && action.payload.value;
    // Designed for single edition entity, will need to be modified to support multiple editions
    switch (type) {
        case ADD_AUTHOR:
            // add new author for AC in edition
            intermediateState = intermediateState.setIn(['authorCreditEditor', action.payload.rowId, 'author'], Immutable.Map({
                __isNew__: true,
                id: newEntity.id,
                rowId: action.payload.rowId,
                text: newEntity.text,
                type: 'Author'
            }));
            intermediateState = intermediateState.setIn(
                ['authorCreditEditor', action.payload.rowId, 'name'], newEntity.text
            );
            break;
        case DUMP_EDITION:
            // dump/save current edition state to `Editions`
            action.payload.value = {
                ...activeEntityState,
                authorCreditEditor: state.get('authorCreditEditor'),
                editionSection: state.get('editionSection')
            };
            // reset the state after saving
            intermediateState = intermediateState.merge(initialState);
            // keep the AC modal open after resetting state
            intermediateState = intermediateState.setIn(['editionSection', 'authorCreditEditorVisible'],
                state.getIn(['editionSection', 'authorCreditEditorVisible']));
            if (action.payload.type && camelCase(action.payload.type) === 'editionGroup') {
                // only reset AC state if required
                intermediateState = intermediateState.set('authorCreditEditor', initialACState);
            }
            // default alias language of new entity will be same as of edition
            intermediateState = intermediateState.setIn(['nameSection', 'language'], activeEntityState.nameSection.get('language', null));
            break;
        case ADD_EDITION_GROUP:
            // add new edition group for edition
            intermediateState = intermediateState.setIn(['editionSection', 'editionGroup'], newEntity);
            break;
        case ADD_PUBLISHER: {
            // add new publisher for edition
            // set new publisher in edition state as well
            intermediateState = intermediateState.setIn(
                ['editionSection', 'publisher', newEntity.id]
                , Immutable.Map(newEntity)
            );
            break;
        }
        case DUPLICATE_WORK:
        // copy work state from `Works`
        {
            const fromWork:State = intermediateState.getIn(['Works', action.payload]);
            const defaultAlias = fromWork.get('defaultAlias');
            const relationships = fromWork.getIn(['relationshipSet', 'relationships'], null);
            const identifiers = fromWork.getIn(['identifierSet', 'identifiers'], null);
            const other:any = {};
            if (identifiers) {
                const identifierEditor = identifiers.map((identifier) => Immutable.Map({type: identifier.get('typeId'),
                    value: identifier.get('value')}));
                other.identifierEditor = identifierEditor;
            }
            if (relationships) {
                const rels = relationships.map((rel, key) => {
                    const relationship = convertMapToObject(rel);
                    relationship.rowId = `t-${key}`;
                    relationship.sourceEntity = {
                        bbid: relationship.source.bbid,
                        defaultAlias: {
                            name: relationship.source.name
                        },
                        type: relationship.source.type
                    };
                    relationship.targetEntity = {
                        bbid: relationship.target.bbid,
                        defaultAlias: {
                            name: relationship.target.name
                        },
                        type: relationship.target.type
                    };
                    relationship.relationshipType = relationship.type;
                    return Immutable.fromJS(relationship);
                });
                other.relationshipSection = {
                    canEdit: true,
                    lastRelationships: null,
                    relationshipEditorProps: null,
                    relationshipEditorVisible: false,
                    relationships: rels
                };
            }
            const changedAttributes = Immutable.fromJS({
                nameSection: {
                    language: defaultAlias.get('languageId'),
                    name: defaultAlias.get('name'),
                    sortName: defaultAlias.get('sortName')
                },
                workSection: {
                    type: fromWork.get('typeId')
                },
                ...other
            });
            intermediateState = intermediateState.merge(changedAttributes);
            // get rid of properities that are not needed
            intermediateState = ['text', '__isNew__', 'type', 'id'].reduce((istate, key) => istate.delete(key), intermediateState);
            break;
        }
        case LOAD_EDITION:
        {
            // load old edition state from `Editions`
            intermediateState = intermediateState.merge(intermediateState.getIn(['Editions', action.payload.id]));
            // check whether the new edition group has been added
            const newEditionGroup = intermediateState.getIn(['EditionGroups', 'eg0'], null);
            if (newEditionGroup) {
                intermediateState = intermediateState.setIn(['editionSection', 'editionGroup'], newEditionGroup);
            }
            break;
        }
        default:
            break;
    }
    return intermediateState;
}

export function createRootReducer() {
    return (state: Immutable.Map<string, any>, action) => {
        // first pass the state to our cross slice reducer to handle UF specific actions.
        const intermediateState = crossSliceReducer(state, action);
        return combineReducers({
            Authors: authorsReducer,
            EditionGroups: editionGroupsReducer,
            Editions: newEditionReducer,
            ISBN: ISBNReducer,
            Publishers: publishersReducer,
            Series: seriesReducer,
            Works: worksReducer,
            aliasEditor: aliasEditorReducer,
            annotationSection: annotationSectionReducer,
            authorCreditEditor: authorCreditEditorReducer,
            authorSection: authorSectionReducer,
            autoISBN: autoISBNReducer,
            buttonBar: buttonBarReducer,
            editionGroupSection: editionGroupSectionReducer,
            editionSection: editionSectionReducer,
            entityModalIsOpen: entityModalIsOpenReducer,
            identifierEditor: identifierEditorReducer,
            nameSection: nameSectionReducer,
            publisherSection: publisherSectionReducer,
            relationshipSection: relationshipSectionReducer,
            seriesSection: seriesSectionReducer,
            submissionSection: submissionSectionReducer,
            workSection: workSectionReducer
        })(intermediateState, action);
    };
}