<commit_before>import m from 'mithril';
import prop from 'mithril/stream';
import _ from 'underscore';
import { catarse } from '../api';
import models from '../models';
import h from '../h';
import railsErrorsVM from '../vms/rails-errors-vm';
import projectBasicsVM from '../vms/project-basics-vm';
import popNotification from './pop-notification';
import inputCard from './input-card';
import projectEditSaveBtn from './project-edit-save-btn';

const I18nScope = _.partial(h.i18nScope, 'projects.dashboard_basics');

const projectBasicsEdit = {
    oninit: function (vnode) {
        const vm = projectBasicsVM,
            mapErrors = [
                ['name', ['name']],
                ['public_tags', ['public_tags']],
                ['content_rating', ['content_rating']],
                ['permalink', ['permalink']],
                ['category_id', ['category']],
                ['city_id', ['city']],
            ],
            loading = prop(false),
            cities = prop(),
            categories = prop([]),
            isAdultContent = prop(null),
            showSuccess = h.toggleProp(false, true),
            showError = h.toggleProp(false, true),
            selectedTags = prop([]),
            tagOptions = prop([]),
            isEditingTags = prop(false),
            tagEditingLoading = prop(false),
            onSubmit = () => {
                if (isEditingTags()) {
                    return false;
                }

                loading(true);
                m.redraw();
                const tagString = _.pluck(selectedTags(), 'name').join(',');
                vm.fields.public_tags(tagString);
                vm.updateProject(vnode.attrs.projectId)
                    .then(() => {
                        loading(false);
                        vm.e.resetFieldErrors();
                        showSuccess(true);
                        showError(false);
                        vnode.attrs.reloadProject(vm.fillFields);
                    })
                    .catch(err => {
                        if (err.errors_json) {
                            railsErrorsVM.mapRailsErrors(err.errors_json, mapErrors, vm.e);
                        }
                        loading(false);
                        showSuccess(false);
                        showError(true);
                    });

                return false;
            };
        if (railsErrorsVM.railsErrors()) {
            railsErrorsVM.mapRailsErrors(railsErrorsVM.railsErrors(), mapErrors, vm.e);
        }
        vm.fillFields(vnode.attrs.project);

        if (vm.fields.public_tags()) {
            selectedTags(_.map(vm.fields.public_tags().split(','), name => ({ name })));
        }

        vm.loadCategoriesOptionsTo(categories, vm.fields.category_id());
        const addTag = tag => () => {
            tagOptions([]);

            if (selectedTags().length >= 5) {
                vm.e('public_tags', window.I18n.t('tags_max_error', I18nScope()));
                vm.e.inlineError('public_tags', true);
                m.redraw();

                return false;
            }
            selectedTags().push(tag);
            isEditingTags(false);

            m.redraw();

            return false;
        };

        const removeTag = tagToRemove => () => {
            const updatedTags = _.reject(selectedTags(), tag => tag === tagToRemove);

            selectedTags(updatedTags);

            return false;
        };
        const tagString = prop('');
        const transport = prop({ abort: Function.prototype });
        const searchTagsUrl = `${h.getApiHost()}/rpc/tag_search`;
        const searchTags = () => m.request({ method: 'POST', background: true, config: transport, data: { query: tagString(), count: 3 }, url: searchTagsUrl });
        const triggerTagSearch = e => {
            tagString(e.target.value);

            isEditingTags(true);
            tagOptions([]);

            const keyCode = e.keyCode;

            if (keyCode === 188 || keyCode === 13) {
                const tag = tagString().charAt(tagString().length - 1) === ',' ? tagString().substr(0, tagString().length - 1) : tagString();

                addTag({ name: tag.toLowerCase() }).call();
                e.target.value = '';
                return false;
            }

            tagEditingLoading(true);
            transport().abort();
            searchTags().then(data => {
                tagOptions(data);
                tagEditingLoading(false);
                m.redraw(true);
            });

            return false;
        };

        const editTag = event => {
            return triggerTagSearch(event);
        };

        vnode.state = {
            vm,
            isAdultContent,
            onSubmit,
            loading,
            categories,
            cities,
            showSuccess,
            showError,
            tagOptions,
            editTag,
            addTag,
            removeTag,
            isEditingTags,
            triggerTagSearch,
            selectedTags,
            tagEditingLoading,
        };
    },
    view: function ({ state, attrs }) {
        const vm = state.vm;

        return m('#basics-tab', [
            state.showSuccess()
                ? m(popNotification, {
                    message: window.I18n.t('shared.successful_update'),
                    toggleOpt: state.showSuccess,
                })
                : '',
            state.showError()
                ? m(popNotification, {
                    message: window.I18n.t('shared.failed_update'),
                    toggleOpt: state.showError,
                    error: true,
                })
                : '',
            // add pop notifications here
            m('form.w-form', { onsubmit: state.onSubmit }, [
                m('.w-container', [
                    // admin fields
                    attrs.user.is_admin
                        ? m('.w-row', [
                            m('.w-col.w-col-10.w-col-push-1', [
                                m(inputCard, {
                                    label: window.I18n.t('tracker_snippet_html', I18nScope()),
                                    children: [
                                        m('textarea.text.optional.w-input.text-field.positive.medium', {
                                            value: vm.fields.tracker_snippet_html(),
                                            onchange: m.withAttr('value', vm.fields.tracker_snippet_html),
                                        }),
                                    ],
                                }),
                                m(inputCard, {
                                    label: window.I18n.t('user_id', I18nScope()),
                                    children: [
                                        m('input.string.optional.w-input.text-field.positive.medium[type="text"]', {
                                            value: vm.fields.user_id(),
                                            onchange: m.withAttr('value', vm.fields.user_id),
                                        }),
                                    ],
                                }),
                                m(inputCard, {
                                    label: window.I18n.t('admin_tags', I18nScope()),
                                    label_hint: window.I18n.t('admin_tags_hint', I18nScope()),
                                    children: [
                                        m('input.string.optional.w-input.text-field.positive.medium[type="text"]', {
                                            value: vm.fields.admin_tags(),
                                            onchange: m.withAttr('value', vm.fields.admin_tags),
                                        }),
                                    ],
                                }),
                                m(inputCard, {
                                    label: window.I18n.t('service_fee', I18nScope()),
                                    children: [
                                        m('input.string.optional.w-input.text-field.positive.medium[type="number"]', {
                                            value: vm.fields.service_fee(),
                                            onchange: m.withAttr('value', vm.fields.service_fee),
                                        }),
                                    ],
                                }),
                            ]),
                        ])
                        : '',
                    m('.w-row', [
                        m('.w-col.w-col-10.w-col-push-1', [
                            m(inputCard, {
                                label: window.I18n.t('name', I18nScope()),
                                label_hint: window.I18n.t('name_hint', I18nScope()),
                                children: [
                                    m('input.string.required.w-input.text-field.positive.medium[type="text"][maxlength="50"]', {
                                        value: vm.fields.name(),
                                        class: vm.e.hasError('name') ? 'error' : '',
                                        onchange: m.withAttr('value', vm.fields.name),
                                    }),
                                    vm.e.inlineError('name'),
                                ],
                            }),

                            m(inputCard, {
                                label: window.I18n.t('adult_content', I18nScope()),
                                label_hint: window.I18n.t('adult_content_hint', I18nScope()),
                                children: [
                                    m(
                                        'select.required.w-input.text-field.w-select.positive.medium',
                                        {
                                            value: vm.fields.content_rating(),
                                            class: vm.e.hasError('content_rating') ? 'error' : '',
                                            onchange: (event) => {
                                                try {
                                                    const content_rating_value = JSON.parse(event.target.value);
                                                    vm.fields.content_rating(content_rating_value);
                                                    vm.fields.show_cans_and_cants(content_rating_value === 18);
                                                    vm.fields.force_show_cans_and_cants(false);
                                                } catch (e) {
                                                    console.log('Error setting content rating:', e);
                                                    h.captureException(e);
                                                }
                                            },
                                        },
                                        m(`option[value=null]`, {
                                            selected: vm.fields.content_rating() === null
                                        }, I18n.t('is_adult_content_answer_choose', I18nScope())),
                                        m(`option[value=18]`, {
                                            selected: vm.fields.content_rating() === 18
                                        }, I18n.t('is_adult_content_answer_yes', I18nScope())),
                                        m(`option[value=0]`, {
                                            selected: vm.fields.content_rating() === 0
                                        }, I18n.t('is_adult_content_answer_no', I18nScope()))
                                    ),
                                    vm.e.inlineError('content_rating'),

                                    m('div.fontsize-smaller.fontweight-light.fontcolor-secondary', [
                                        window.I18n.t('adult_content_description', I18nScope()),
                                        m('a.alt-link.fontweight-semibold', {
                                            onclick: () => vm.fields.force_show_cans_and_cants.toggle()
                                        }, window.I18n.t('adult_content_description_click_more', I18nScope()) ),
                                        '.'
                                    ])
                                ],
                                belowChildren: (vm.fields.show_cans_and_cants() || vm.fields.force_show_cans_and_cants()) && [
                                    m('div.card.u-margintop-30', [
                                        m('.w-row', [
                                            m('div.w-sub-col.w-col.w-col-6', [
                                                m('div.fontsize-small.fontweight-semibold.u-marginbottom-10', I18n.t('adult_content_can_title', I18nScope())),
                                                m('div.fontsize-smaller', I18n.t('adult_content_can_text', I18nScope()))
                                            ]),
                                            m('div.w-col.w-col-6', [
                                                m('div.fontsize-small.fontweight-semibold.u-marginbottom-10', I18n.t('adult_content_cant_title', I18nScope())),
                                                m('div.fontsize-smaller', [
                                                    m.trust(I18n.t('adult_content_cant_text', I18nScope()))
                                                ])
                                            ])
                                        ]),
                                        m('div.fontsize-small.u-text-center.u-margintop-30.u-marginbottom-20',
                                            m(`a.alt-link.fontweight-semibold[href="${I18n.t('adult_content_support_url', I18nScope())}"]`,
                                                I18n.t('adult_content_more_info_link_text', I18nScope())
                                            )
                                        )
                                    ])
                                ]
                            }),

                            m(inputCard, {
                                label: window.I18n.t('tags', I18nScope()),
                                label_hint: window.I18n.t('tags_hint', I18nScope()),
                                onclick: () => state.isEditingTags(false),
                                children: [
                                    m('input.string.optional.w-input.text-field.positive.medium[type="text"]', {
                                        onkeyup: event => state.editTag(event),
                                        class: vm.e.hasError('public_tags') ? 'error' : '',
                                        onfocus: () => vm.e.inlineError('public_tags', false),
                                    }),
                                    state.isEditingTags()
                                        ? m(
                                            '.options-list.table-outer',
                                            state.tagEditingLoading()
                                                ? m('.dropdown-link', m('.fontsize-smallest', 'Carregando...'))
                                                : state.tagOptions().length
                                                    ? _.map(state.tagOptions(), tag =>
                                                        m('.dropdown-link', { onclick: state.addTag(tag) }, m('.fontsize-smaller', tag.name))
                                                    )
                                                    : m('.dropdown-link', m('.fontsize-smallest', 'Nenhuma tag relacionada...'))
                                        )
                                        : '',
                                    vm.e.inlineError('public_tags'),
                                    m(
                                        'div.tag-choices',
                                        _.map(state.selectedTags(), choice =>
                                            m(
                                                '.tag-div',
                                                m('div', [m('a.tag-close-btn.fa.fa-times-circle', { onclick: state.removeTag(choice) }), ` ${choice.name}`])
                                            )
                                        )
                                    ),
                                ],
                            }),
                            m(inputCard, {
                                label: window.I18n.t('permalink', I18nScope()),
                                label_hint: window.I18n.t('permalink_hint', I18nScope()),
                                children: [
                                    m('.w-row', [
                                        m(
                                            '.w-col.w-col-4.w-col-small-6.w-col-tiny6.text-field.prefix.no-hover.medium.prefix-permalink',
                                            {
                                                class: vm.e.hasError('permalink') ? 'error' : '',
                                            },
                                            m('.fontcolor-secondary.u-text-center.fontcolor-secondary.u-text-center.fontsize-smallest', 'www.catarse.me/')
                                        ),
                                        m('.w-col.w-col-8.w-col-small-6.w-col-tiny-6', [
                                            m('input.string.required.w-input.text-field.postfix.positive.medium[type="text"]', {
                                                value: vm.fields.permalink(),
                                                class: vm.e.hasError('permalink') ? 'error' : '',
                                                onchange: m.withAttr('value', vm.fields.permalink),
                                            }),
                                        ]),
                                    ]),
                                    m('.w-row', vm.e.inlineError('permalink')),
                                ],
                            }),
                            m(inputCard, {
                                label: window.I18n.t('category', I18nScope()),
                                label_hint: window.I18n.t('category_hint', I18nScope()),
                                children: [
                                    m(
                                        'select.required.w-input.text-field.w-select.positive.medium',
                                        {
                                            value: vm.fields.category_id(),
                                            class: vm.e.hasError('category_id') ? 'error' : '',
                                            onchange: m.withAttr('value', vm.fields.category_id),
                                        },
                                        state.categories()
                                    ),
                                    vm.e.inlineError('category_id'),
                                ],
                            }),
                            m(inputCard, {
                                label: window.I18n.t('city', I18nScope()),
                                label_hint: window.I18n.t('city_hint', I18nScope()),
                                children: [
                                    m('input.string.required.w-input.text-field.positive.medium[type="text"]', {
                                        value: vm.fields.city_name(),
                                        class: vm.e.hasError('city_id') ? 'error' : '',
                                        onkeyup: vm.generateSearchCity(state.cities),
                                    }),
                                    vm.e.inlineError('city_id'),
                                    state.cities(),
                                ],
                            }),
                        ]),
                    ]),
                ]),
                m(projectEditSaveBtn, { loading: state.loading, onSubmit: state.onSubmit }),
            ]),
        ]);
    },
};

export default projectBasicsEdit;
<commit_msg>tests for projects controller<commit_after>import m from 'mithril';
import prop from 'mithril/stream';
import _ from 'underscore';
import { catarse } from '../api';
import models from '../models';
import h from '../h';
import railsErrorsVM from '../vms/rails-errors-vm';
import projectBasicsVM from '../vms/project-basics-vm';
import popNotification from './pop-notification';
import inputCard from './input-card';
import projectEditSaveBtn from './project-edit-save-btn';

const I18nScope = _.partial(h.i18nScope, 'projects.dashboard_basics');

const projectBasicsEdit = {
    oninit: function (vnode) {
        const vm = projectBasicsVM,
            mapErrors = [
                ['name', ['name']],
                ['public_tags', ['public_tags']],
                ['content_rating', ['content_rating']],
                ['permalink', ['permalink']],
                ['category_id', ['category']],
                ['city_id', ['city']],
            ],
            loading = prop(false),
            cities = prop(),
            categories = prop([]),
            isAdultContent = prop(null),
            showSuccess = h.toggleProp(false, true),
            showError = h.toggleProp(false, true),
            selectedTags = prop([]),
            tagOptions = prop([]),
            isEditingTags = prop(false),
            tagEditingLoading = prop(false),
            onSubmit = () => {
                if (isEditingTags()) {
                    return false;
                }

                loading(true);
                m.redraw();
                const tagString = _.pluck(selectedTags(), 'name').join(',');
                vm.fields.public_tags(tagString);
                vm.updateProject(vnode.attrs.projectId)
                    .then(() => {
                        loading(false);
                        vm.e.resetFieldErrors();
                        showSuccess(true);
                        showError(false);
                        vnode.attrs.reloadProject(vm.fillFields);
                    })
                    .catch(err => {
                        if (err.errors_json) {
                            railsErrorsVM.mapRailsErrors(err.errors_json, mapErrors, vm.e);
                        }
                        loading(false);
                        showSuccess(false);
                        showError(true);
                    });

                return false;
            };
        if (railsErrorsVM.railsErrors()) {
            railsErrorsVM.mapRailsErrors(railsErrorsVM.railsErrors(), mapErrors, vm.e);
        }
        vm.fillFields(vnode.attrs.project);

        if (vm.fields.public_tags()) {
            selectedTags(_.map(vm.fields.public_tags().split(','), name => ({ name })));
        }

        vm.loadCategoriesOptionsTo(categories, vm.fields.category_id());
        const addTag = tag => () => {
            tagOptions([]);

            if (selectedTags().length >= 5) {
                vm.e('public_tags', window.I18n.t('tags_max_error', I18nScope()));
                vm.e.inlineError('public_tags', true);
                m.redraw();

                return false;
            }
            selectedTags().push(tag);
            isEditingTags(false);

            m.redraw();

            return false;
        };

        const removeTag = tagToRemove => () => {
            const updatedTags = _.reject(selectedTags(), tag => tag === tagToRemove);

            selectedTags(updatedTags);

            return false;
        };
        const tagString = prop('');
        const transport = prop({ abort: Function.prototype });
        const searchTagsUrl = `${h.getApiHost()}/rpc/tag_search`;
        const searchTags = () => m.request({ method: 'POST', background: true, config: transport, data: { query: tagString(), count: 3 }, url: searchTagsUrl });
        const triggerTagSearch = e => {
            tagString(e.target.value);

            isEditingTags(true);
            tagOptions([]);

            const keyCode = e.keyCode;

            if (keyCode === 188 || keyCode === 13) {
                const tag = tagString().charAt(tagString().length - 1) === ',' ? tagString().substr(0, tagString().length - 1) : tagString();

                addTag({ name: tag.toLowerCase() }).call();
                e.target.value = '';
                return false;
            }

            tagEditingLoading(true);
            transport().abort();
            searchTags().then(data => {
                tagOptions(data);
                tagEditingLoading(false);
                m.redraw(true);
            });

            return false;
        };

        const editTag = event => {
            return triggerTagSearch(event);
        };

        vnode.state = {
            vm,
            isAdultContent,
            onSubmit,
            loading,
            categories,
            cities,
            showSuccess,
            showError,
            tagOptions,
            editTag,
            addTag,
            removeTag,
            isEditingTags,
            triggerTagSearch,
            selectedTags,
            tagEditingLoading,
        };
    },
    view: function ({ state, attrs }) {
        const vm = state.vm;

        return m('#basics-tab', [
            state.showSuccess()
                ? m(popNotification, {
                    message: window.I18n.t('shared.successful_update'),
                    toggleOpt: state.showSuccess,
                })
                : '',
            state.showError()
                ? m(popNotification, {
                    message: window.I18n.t('shared.failed_update'),
                    toggleOpt: state.showError,
                    error: true,
                })
                : '',
            // add pop notifications here
            m('form.w-form', { onsubmit: state.onSubmit }, [
                m('.w-container', [
                    // admin fields
                    attrs.user.is_admin
                        ? m('.w-row', [
                            m('.w-col.w-col-10.w-col-push-1', [
                                m(inputCard, {
                                    label: window.I18n.t('tracker_snippet_html', I18nScope()),
                                    children: [
                                        m('textarea.text.optional.w-input.text-field.positive.medium', {
                                            value: vm.fields.tracker_snippet_html(),
                                            onchange: m.withAttr('value', vm.fields.tracker_snippet_html),
                                        }),
                                    ],
                                }),
                                m(inputCard, {
                                    label: window.I18n.t('user_id', I18nScope()),
                                    children: [
                                        m('input.string.optional.w-input.text-field.positive.medium[type="text"]', {
                                            value: vm.fields.user_id(),
                                            onchange: m.withAttr('value', vm.fields.user_id),
                                        }),
                                    ],
                                }),
                                m(inputCard, {
                                    label: window.I18n.t('admin_tags', I18nScope()),
                                    label_hint: window.I18n.t('admin_tags_hint', I18nScope()),
                                    children: [
                                        m('input.string.optional.w-input.text-field.positive.medium[type="text"]', {
                                            value: vm.fields.admin_tags(),
                                            onchange: m.withAttr('value', vm.fields.admin_tags),
                                        }),
                                    ],
                                }),
                                m(inputCard, {
                                    label: window.I18n.t('service_fee', I18nScope()),
                                    children: [
                                        m('input.string.optional.w-input.text-field.positive.medium[type="number"]', {
                                            value: vm.fields.service_fee(),
                                            onchange: m.withAttr('value', vm.fields.service_fee),
                                        }),
                                    ],
                                }),
                            ]),
                        ])
                        : '',
                    m('.w-row', [
                        m('.w-col.w-col-10.w-col-push-1', [
                            m(inputCard, {
                                label: window.I18n.t('name', I18nScope()),
                                label_hint: window.I18n.t('name_hint', I18nScope()),
                                children: [
                                    m('input.string.required.w-input.text-field.positive.medium[type="text"][maxlength="50"]', {
                                        value: vm.fields.name(),
                                        class: vm.e.hasError('name') ? 'error' : '',
                                        onchange: m.withAttr('value', vm.fields.name),
                                    }),
                                    vm.e.inlineError('name'),
                                ],
                            }),
                            m(inputCard, {
                                label: window.I18n.t('adult_content', I18nScope()),
                                label_hint: window.I18n.t('adult_content_hint', I18nScope()),
                                children: [
                                    m(
                                        'select.required.w-input.text-field.w-select.positive.medium[id="content_rating_id"]',
                                        {
                                            value: vm.fields.content_rating(),
                                            class: vm.e.hasError('content_rating') ? 'error' : '',
                                            onchange: (event) => {
                                                try {
                                                    const content_rating_value = JSON.parse(event.target.value);
                                                    vm.fields.content_rating(content_rating_value);
                                                    vm.fields.show_cans_and_cants(content_rating_value === 18);
                                                    vm.fields.force_show_cans_and_cants(false);
                                                } catch (e) {
                                                    console.log('Error setting content rating:', e);
                                                    h.captureException(e);
                                                }
                                            },
                                        },
                                        m(`option[value=null]`, {
                                            selected: vm.fields.content_rating() === null
                                        }, I18n.t('is_adult_content_answer_choose', I18nScope())),
                                        m(`option[value=18]`, {
                                            selected: vm.fields.content_rating() === 18
                                        }, I18n.t('is_adult_content_answer_yes', I18nScope())),
                                        m(`option[value=0]`, {
                                            selected: vm.fields.content_rating() === 0
                                        }, I18n.t('is_adult_content_answer_no', I18nScope()))
                                    ),
                                    vm.e.inlineError('content_rating'),

                                    m('div.fontsize-smaller.fontweight-light.fontcolor-secondary', [
                                        window.I18n.t('adult_content_description', I18nScope()),
                                        m('a.alt-link.fontweight-semibold', {
                                            onclick: () => vm.fields.force_show_cans_and_cants.toggle()
                                        }, window.I18n.t('adult_content_description_click_more', I18nScope()) ),
                                        '.'
                                    ])
                                ],
                                belowChildren: (vm.fields.show_cans_and_cants() || vm.fields.force_show_cans_and_cants()) && [
                                    m('div.card.u-margintop-30', [
                                        m('.w-row', [
                                            m('div.w-sub-col.w-col.w-col-6', [
                                                m('div.fontsize-small.fontweight-semibold.u-marginbottom-10', I18n.t('adult_content_can_title', I18nScope())),
                                                m('div.fontsize-smaller', I18n.t('adult_content_can_text', I18nScope()))
                                            ]),
                                            m('div.w-col.w-col-6', [
                                                m('div.fontsize-small.fontweight-semibold.u-marginbottom-10', I18n.t('adult_content_cant_title', I18nScope())),
                                                m('div.fontsize-smaller', [
                                                    m.trust(I18n.t('adult_content_cant_text', I18nScope()))
                                                ])
                                            ])
                                        ]),
                                        m('div.fontsize-small.u-text-center.u-margintop-30.u-marginbottom-20',
                                            m(`a.alt-link.fontweight-semibold[href="${I18n.t('adult_content_support_url', I18nScope())}"]`,
                                                I18n.t('adult_content_more_info_link_text', I18nScope())
                                            )
                                        )
                                    ])
                                ]
                            }),
                            m(inputCard, {
                                label: window.I18n.t('tags', I18nScope()),
                                label_hint: window.I18n.t('tags_hint', I18nScope()),
                                onclick: () => state.isEditingTags(false),
                                children: [
                                    m('input.string.optional.w-input.text-field.positive.medium[type="text"]', {
                                        onkeyup: event => state.editTag(event),
                                        class: vm.e.hasError('public_tags') ? 'error' : '',
                                        onfocus: () => vm.e.inlineError('public_tags', false),
                                    }),
                                    state.isEditingTags()
                                        ? m(
                                            '.options-list.table-outer',
                                            state.tagEditingLoading()
                                                ? m('.dropdown-link', m('.fontsize-smallest', 'Carregando...'))
                                                : state.tagOptions().length
                                                    ? _.map(state.tagOptions(), tag =>
                                                        m('.dropdown-link', { onclick: state.addTag(tag) }, m('.fontsize-smaller', tag.name))
                                                    )
                                                    : m('.dropdown-link', m('.fontsize-smallest', 'Nenhuma tag relacionada...'))
                                        )
                                        : '',
                                    vm.e.inlineError('public_tags'),
                                    m(
                                        'div.tag-choices',
                                        _.map(state.selectedTags(), choice =>
                                            m(
                                                '.tag-div',
                                                m('div', [m('a.tag-close-btn.fa.fa-times-circle', { onclick: state.removeTag(choice) }), ` ${choice.name}`])
                                            )
                                        )
                                    ),
                                ],
                            }),
                            m(inputCard, {
                                label: window.I18n.t('permalink', I18nScope()),
                                label_hint: window.I18n.t('permalink_hint', I18nScope()),
                                children: [
                                    m('.w-row', [
                                        m(
                                            '.w-col.w-col-4.w-col-small-6.w-col-tiny6.text-field.prefix.no-hover.medium.prefix-permalink',
                                            {
                                                class: vm.e.hasError('permalink') ? 'error' : '',
                                            },
                                            m('.fontcolor-secondary.u-text-center.fontcolor-secondary.u-text-center.fontsize-smallest', 'www.catarse.me/')
                                        ),
                                        m('.w-col.w-col-8.w-col-small-6.w-col-tiny-6', [
                                            m('input.string.required.w-input.text-field.postfix.positive.medium[type="text"]', {
                                                value: vm.fields.permalink(),
                                                class: vm.e.hasError('permalink') ? 'error' : '',
                                                onchange: m.withAttr('value', vm.fields.permalink),
                                            }),
                                        ]),
                                    ]),
                                    m('.w-row', vm.e.inlineError('permalink')),
                                ],
                            }),
                            m(inputCard, {
                                label: window.I18n.t('category', I18nScope()),
                                label_hint: window.I18n.t('category_hint', I18nScope()),
                                children: [
                                    m(
                                        'select.required.w-input.text-field.w-select.positive.medium',
                                        {
                                            value: vm.fields.category_id(),
                                            class: vm.e.hasError('category_id') ? 'error' : '',
                                            onchange: m.withAttr('value', vm.fields.category_id),
                                        },
                                        state.categories()
                                    ),
                                    vm.e.inlineError('category_id'),
                                ],
                            }),
                            m(inputCard, {
                                label: window.I18n.t('city', I18nScope()),
                                label_hint: window.I18n.t('city_hint', I18nScope()),
                                children: [
                                    m('input.string.required.w-input.text-field.positive.medium[type="text"]', {
                                        value: vm.fields.city_name(),
                                        class: vm.e.hasError('city_id') ? 'error' : '',
                                        onkeyup: vm.generateSearchCity(state.cities),
                                    }),
                                    vm.e.inlineError('city_id'),
                                    state.cities(),
                                ],
                            }),
                        ]),
                    ]),
                ]),
                m(projectEditSaveBtn, { loading: state.loading, onSubmit: state.onSubmit }),
            ]),
        ]);
    },
};

export default projectBasicsEdit;
<|endoftext|>


<commit_before>(function (context) {
    var channel = require("cordova/channel"),
        _self = {
            boot: function () {
                /**
                 * Create all cordova objects once page has fully loaded and native side is ready.
                 */
                channel.join(function() {
                    var builder = require('cordova/builder'),
                        base = require('cordova/common'),
                        platform = require('cordova/platform');

                    // Drop the common globals into the window object, but be nice and don't overwrite anything.
                    builder.build(base.objects).intoButDontClobber(window);

                    // Drop the platform-specific globals into the window object
                    // and clobber any existing object.
                    builder.build(platform.objects).intoAndClobber(window);

                    // Merge the platform-specific overrides/enhancements into
                    // the window object.
                    if (typeof platform.merges !== 'undefined') {
                        builder.build(platform.merges).intoAndMerge(window);
                    }

                    // Call the platform-specific initialization
                    platform.initialize();

                    // Fire event to notify that all objects are created
                    channel.onCordovaReady.fire();

                    // Fire onDeviceReady event once all constructors have run and
                    // cordova info has been received from native side.
                    channel.join(function() {
                        channel.onDeviceReady.fire();
                    }, channel.deviceReadyChannelsArray);

                }, [ channel.onDOMContentLoaded, channel.onNativeReady ]);
            }
        };

    // boot up once native side is ready
    channel.onNativeReady.subscribeOnce(_self.boot);

    // _nativeReady is global variable that the native side can set
    // to signify that the native code is ready. It is a global since
    // it may be called before any cordova JS is ready.
    if (window._nativeReady) {
        channel.onNativeReady.fire();
    }

}(window));<commit_msg>[CB-683] also firing deviceready event through fireDocumentEvent, so we get an event object passed into the handler<commit_after>(function (context) {
    var channel = require("cordova/channel"),
        _self = {
            boot: function () {
                /**
                 * Create all cordova objects once page has fully loaded and native side is ready.
                 */
                channel.join(function() {
                    var builder = require('cordova/builder'),
                        base = require('cordova/common'),
                        platform = require('cordova/platform');

                    // Drop the common globals into the window object, but be nice and don't overwrite anything.
                    builder.build(base.objects).intoButDontClobber(window);

                    // Drop the platform-specific globals into the window object
                    // and clobber any existing object.
                    builder.build(platform.objects).intoAndClobber(window);

                    // Merge the platform-specific overrides/enhancements into
                    // the window object.
                    if (typeof platform.merges !== 'undefined') {
                        builder.build(platform.merges).intoAndMerge(window);
                    }

                    // Call the platform-specific initialization
                    platform.initialize();

                    // Fire event to notify that all objects are created
                    channel.onCordovaReady.fire();

                    // Fire onDeviceReady event once all constructors have run and
                    // cordova info has been received from native side.
                    channel.join(function() {
                        require('cordova').fireDocumentEvent('deviceready');
                    }, channel.deviceReadyChannelsArray);

                }, [ channel.onDOMContentLoaded, channel.onNativeReady ]);
            }
        };

    // boot up once native side is ready
    channel.onNativeReady.subscribeOnce(_self.boot);

    // _nativeReady is global variable that the native side can set
    // to signify that the native code is ready. It is a global since
    // it may be called before any cordova JS is ready.
    if (window._nativeReady) {
        channel.onNativeReady.fire();
    }

}(window));
<|endoftext|>


<commit_before>
const feathersErrors = require('@feathersjs/errors');

const errors = feathersErrors.errors;

module.exports = function (...providers) {
  return context => {
    const hookProvider = (context.params || {}).provider;

    const anyProvider = providers.length === 0;
    const thisProvider = providers.some(provider =>
      provider === hookProvider ||
      (provider === 'server' && !hookProvider) ||
      (provider === 'external' && !!hookProvider)
    );

    if (anyProvider || thisProvider) {
      throw new errors.MethodNotAllowed(
        `Provider '${context.params.provider}' can not call '${context.method}'. (disallow)`
      );
    }
  };
};
<commit_msg>Fix feathersErrors.errors === undefined error<commit_after>
const feathersErrors = require('@feathersjs/errors');

const errors = feathersErrors;

module.exports = function (...providers) {
  return context => {
    const hookProvider = (context.params || {}).provider;

    const anyProvider = providers.length === 0;
    const thisProvider = providers.some(provider =>
      provider === hookProvider ||
      (provider === 'server' && !hookProvider) ||
      (provider === 'external' && !!hookProvider)
    );

    if (anyProvider || thisProvider) {
      throw new errors.MethodNotAllowed(
        `Provider '${context.params.provider}' can not call '${context.method}'. (disallow)`
      );
    }
  };
};
<|endoftext|>


<commit_before>define(['js/core/Component', 'underscore'], function (Component, _) {

    var exceptionSizes = [120, 178],
        PRODUCT = "product",
        COMPOSITION = "composition";

    var ImageService = Component.inherit('sprd.data.ImageService', {

        defaults: {
            subDomainCount: 10,
            endPoint: '//image.spreadshirt.net/image-server/v1',
            gateway: '/image-server/v1'
        },

        virtualProductImage: function(product, vpString, viewId, options) {
            return this.buildUrl([
                'products',
                vpString,
                'views',
                viewId
            ], ImageService.getImageSizeParameter(options), parseInt(product ? product.$.id : 0));
        },

        productTypeImage: function (productTypeId, viewId, appearanceId, options) {
            return this.buildUrl(['productTypes', productTypeId, 'views', viewId, 'appearances', appearanceId],
                ImageService.getImageSizeParameter(options), parseInt(productTypeId || 0) + parseInt(viewId || 0) + parseInt(appearanceId || 0));
        },

        productTypeSizeImage: function (productTypeId, options) {
            return this.buildUrl(["productTypes", productTypeId, "variants", "size"], ImageService.getImageSizeParameter(options), productTypeId);
        },

        designImage: function (designId, options) {
            options = options || {};
            var parameter = ImageService.getImageSizeParameter(options) || {};

            var printColors = options.printColors;
            if (printColors) {
                for (var i = 0; i < printColors.length; i++) {
                    parameter["colors[" + i + "]"] = printColors[i];
                }
            }

            var cacheId = options.cacheId || (designId || "").replace(/^.*?(\d+).*/, "$1");
            return this.buildUrl(['designs', designId], parameter, cacheId);
        },

        appearanceImage: function (appearanceId, options) {
            return this.buildUrl(['appearances', appearanceId], ImageService.getImageSizeParameter(options), appearanceId);
        },

        printColorImage: function (printColorId, options) {
            return this.buildUrl(['printColors', printColorId], ImageService.getImageSizeParameter(options), printColorId);
        },

        emptyImage: function () {
            return "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==";
        },

        fontUrl: function (font, extension) {
            extension = extension || "woff" || "svg#font";
            return this.buildUrl(['fontFamilies', font.getFontFamily().$.id, 'fonts', font.$.id + "." + extension]);
        },

        buildUrl: function (url, parameter, cacheId) {
            var imgServer,
                subDomainCount = this.$.subDomainCount;

            if (!isNaN(parseInt(cacheId))) {
                // use the full qualified endpoint
                imgServer = this.$.endPoint;
                imgServer = imgServer.replace(/(\/\/image)\./, '$1' + (cacheId % subDomainCount) + ".");
            } else {
                imgServer = this.$.gateway;
            }

            url = url || [];
            url.unshift(imgServer);

            return ImageService.buildUrl(url, parameter);
        }
    });

    ImageService.exceptionalSizes = [120, 178];

    ImageService.normalizeImageSize = function (size) {

        if (_.indexOf(exceptionSizes, size) === -1) {
            // normalize size
            size = Math.ceil(size / 50) * 50;
        }

        // keep range
        return Math.max(50, Math.min(1200, size));
    };

    ImageService.getImageSizeParameter = function (options) {

        var ret = {};

        if (options.width) {
            ret.width = ImageService.normalizeImageSize(options.width);
        }

        if (options.height) {
            ret.height = ImageService.normalizeImageSize(options.height);
        }

        return ret;
    };

    ImageService.buildUrl = function (url, parameter) {
        var queryString = ImageService.buildQueryString(parameter);
        return url.join('/') + (queryString ? '?' + queryString : '');
    };

    ImageService.buildQueryString = function (parameter) {
        var ret = [];

        for (var key in parameter) {
            if (parameter.hasOwnProperty(key)) {
                ret.push(key + '=' + encodeURIComponent(parameter[key]));
            }
        }

        if (ret.length) {
            return ret.join('&');
        }
    };

    ImageService.ProductImageType = {
        PRODUCT: PRODUCT,
        COMPOSITION: COMPOSITION
    };

    return ImageService;

});
<commit_msg>Chris - Added code which was removed before by me.<commit_after>define(['js/core/Component', 'underscore'], function (Component, _) {

    var exceptionSizes = [120, 178],
        PRODUCT = "product",
        COMPOSITION = "composition";

    var ImageService = Component.inherit('sprd.data.ImageService', {

        defaults: {
            subDomainCount: 10,
            endPoint: '//image.spreadshirt.net/image-server/v1',
            gateway: '/image-server/v1'
        },

        virtualProductImage: function(product, vpString, viewId, options) {
            return this.buildUrl([
                'products',
                vpString,
                'views',
                viewId
            ], ImageService.getImageSizeParameter(options), parseInt(product ? product.$.id : 0));
        },

        productImage: function (productId, viewId, appearanceId, type, options) {
            return this.buildUrl([
                type === PRODUCT ? "products" : "compositions",
                productId,
                "views",
                viewId
            ],
            _.extend({ appearanceId: appearanceId}, ImageService.getImageSizeParameter(options)),
            parseInt(productId || 0) + parseInt(viewId || 0) + parseInt(appearanceId || 0));
        },

        productTypeImage: function (productTypeId, viewId, appearanceId, options) {
            return this.buildUrl(['productTypes', productTypeId, 'views', viewId, 'appearances', appearanceId],
                ImageService.getImageSizeParameter(options), parseInt(productTypeId || 0) + parseInt(viewId || 0) + parseInt(appearanceId || 0));
        },

        productTypeSizeImage: function (productTypeId, options) {
            return this.buildUrl(["productTypes", productTypeId, "variants", "size"], ImageService.getImageSizeParameter(options), productTypeId);
        },

        designImage: function (designId, options) {
            options = options || {};
            var parameter = ImageService.getImageSizeParameter(options) || {};

            var printColors = options.printColors;
            if (printColors) {
                for (var i = 0; i < printColors.length; i++) {
                    parameter["colors[" + i + "]"] = printColors[i];
                }
            }

            var cacheId = options.cacheId || (designId || "").replace(/^.*?(\d+).*/, "$1");
            return this.buildUrl(['designs', designId], parameter, cacheId);
        },

        appearanceImage: function (appearanceId, options) {
            return this.buildUrl(['appearances', appearanceId], ImageService.getImageSizeParameter(options), appearanceId);
        },

        printColorImage: function (printColorId, options) {
            return this.buildUrl(['printColors', printColorId], ImageService.getImageSizeParameter(options), printColorId);
        },

        emptyImage: function () {
            return "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==";
        },

        fontUrl: function (font, extension) {
            extension = extension || "woff" || "svg#font";
            return this.buildUrl(['fontFamilies', font.getFontFamily().$.id, 'fonts', font.$.id + "." + extension]);
        },

        buildUrl: function (url, parameter, cacheId) {
            var imgServer,
                subDomainCount = this.$.subDomainCount;

            if (!isNaN(parseInt(cacheId))) {
                // use the full qualified endpoint
                imgServer = this.$.endPoint;
                imgServer = imgServer.replace(/(\/\/image)\./, '$1' + (cacheId % subDomainCount) + ".");
            } else {
                imgServer = this.$.gateway;
            }

            url = url || [];
            url.unshift(imgServer);

            return ImageService.buildUrl(url, parameter);
        }
    });

    ImageService.exceptionalSizes = [120, 178];

    ImageService.normalizeImageSize = function (size) {

        if (_.indexOf(exceptionSizes, size) === -1) {
            // normalize size
            size = Math.ceil(size / 50) * 50;
        }

        // keep range
        return Math.max(50, Math.min(1200, size));
    };

    ImageService.getImageSizeParameter = function (options) {

        var ret = {};

        if (options.width) {
            ret.width = ImageService.normalizeImageSize(options.width);
        }

        if (options.height) {
            ret.height = ImageService.normalizeImageSize(options.height);
        }

        return ret;
    };

    ImageService.buildUrl = function (url, parameter) {
        var queryString = ImageService.buildQueryString(parameter);
        return url.join('/') + (queryString ? '?' + queryString : '');
    };

    ImageService.buildQueryString = function (parameter) {
        var ret = [];

        for (var key in parameter) {
            if (parameter.hasOwnProperty(key)) {
                ret.push(key + '=' + encodeURIComponent(parameter[key]));
            }
        }

        if (ret.length) {
            return ret.join('&');
        }
    };

    ImageService.ProductImageType = {
        PRODUCT: PRODUCT,
        COMPOSITION: COMPOSITION
    };

    return ImageService;

});
<|endoftext|>


<commit_before>var _each = require("lodash-node/modern/collections/forEach");

var StoreWatchMixin = function() {
  var storeNames = Array.prototype.slice.call(arguments);
  return {
    componentWillMount: function() {
      var flux = this.props.flux || this.context.flux;
      _each(storeNames, function(store) {
        flux.store(store).on("change", this._setStateFromFlux);
      }, this);
    },

    componentWillUnmount: function() {
      var flux = this.props.flux || this.context.flux;
      _each(storeNames, function(store) {
        flux.store(store).removeListener("change", this._setStateFromFlux);
      }, this);
    },

    _setStateFromFlux: function() {
      if(this.isMounted()) {
        this.setState(this.getStateFromFlux());
      }
    },

    getInitialState: function() {
      return this.getStateFromFlux();
    }
  };
};

StoreWatchMixin.componentWillMount = function() {
  throw new Error("Fluxxor.StoreWatchMixin is a function that takes one or more " +
    "store names as parameters and returns the mixin, e.g.: " +
    "mixins[Fluxxor.StoreWatchMixin(\"Store1\", \"Store2\")]");
};

module.exports = StoreWatchMixin;
<commit_msg>Change StoreWatchMixin to watch in componentDidMount<commit_after>var _each = require("lodash-node/modern/collections/forEach");

var StoreWatchMixin = function() {
  var storeNames = Array.prototype.slice.call(arguments);
  return {
    componentDidMount: function() {
      var flux = this.props.flux || this.context.flux;
      _each(storeNames, function(store) {
        flux.store(store).on("change", this._setStateFromFlux);
      }, this);
    },

    componentWillUnmount: function() {
      var flux = this.props.flux || this.context.flux;
      _each(storeNames, function(store) {
        flux.store(store).removeListener("change", this._setStateFromFlux);
      }, this);
    },

    _setStateFromFlux: function() {
      if(this.isMounted()) {
        this.setState(this.getStateFromFlux());
      }
    },

    getInitialState: function() {
      return this.getStateFromFlux();
    }
  };
};

StoreWatchMixin.componentWillMount = function() {
  throw new Error("Fluxxor.StoreWatchMixin is a function that takes one or more " +
    "store names as parameters and returns the mixin, e.g.: " +
    "mixins[Fluxxor.StoreWatchMixin(\"Store1\", \"Store2\")]");
};

module.exports = StoreWatchMixin;
<|endoftext|>


<commit_before>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow
 */

'use strict';

const React = require('React');
const ReactNative = require('ReactNative');
const UIManager = require('UIManager');

const dismissKeyboard = require('dismissKeyboard');
const requireNativeComponent = require('requireNativeComponent');

const NativeAndroidViewPager = requireNativeComponent('AndroidViewPager');

import type {ViewStyleProp} from 'StyleSheet';

const VIEWPAGER_REF = 'viewPager';

type Event = Object;

export type ViewPagerScrollState = $Enum<{
  idle: string,
  dragging: string,
  settling: string,
}>;

type Props = $ReadOnly<{|
  /**
   * Index of initial page that should be selected. Use `setPage` method to
   * update the page, and `onPageSelected` to monitor page changes
   */
  initialPage?: ?number,

  /**
   * Executed when transitioning between pages (ether because of animation for
   * the requested page change or when user is swiping/dragging between pages)
   * The `event.nativeEvent` object for this callback will carry following data:
   *  - position - index of first page from the left that is currently visible
   *  - offset - value from range [0,1) describing stage between page transitions.
   *    Value x means that (1 - x) fraction of the page at "position" index is
   *    visible, and x fraction of the next page is visible.
   */
  onPageScroll?: ?Function,

  /**
   * Function called when the page scrolling state has changed.
   * The page scrolling state can be in 3 states:
   * - idle, meaning there is no interaction with the page scroller happening at the time
   * - dragging, meaning there is currently an interaction with the page scroller
   * - settling, meaning that there was an interaction with the page scroller, and the
   *   page scroller is now finishing it's closing or opening animation
   */
  onPageScrollStateChanged?: ?Function,

  /**
   * This callback will be called once ViewPager finish navigating to selected page
   * (when user swipes between pages). The `event.nativeEvent` object passed to this
   * callback will have following fields:
   *  - position - index of page that has been selected
   */
  onPageSelected?: ?Function,

  /**
   * Blank space to show between pages. This is only visible while scrolling, pages are still
   * edge-to-edge.
   */
  pageMargin?: ?number,

  /**
   * Whether enable showing peekFraction or not. If this is true, the preview of
   * last and next page will show in current screen. Defaults to false.
   */

  peekEnabled?: ?boolean,

  /**
   * Determines whether the keyboard gets dismissed in response to a drag.
   *   - 'none' (the default), drags do not dismiss the keyboard.
   *   - 'on-drag', the keyboard is dismissed when a drag begins.
   */
  keyboardDismissMode?: ?('none' | 'on-drag'),

  /**
   * When false, the content does not scroll.
   * The default value is true.
   */
  scrollEnabled?: ?boolean,

  children?: React.Node,

  style?: ?ViewStyleProp,
|}>;

/**
 * Container that allows to flip left and right between child views. Each
 * child view of the `ViewPagerAndroid` will be treated as a separate page
 * and will be stretched to fill the `ViewPagerAndroid`.
 *
 * It is important all children are `<View>`s and not composite components.
 * You can set style properties like `padding` or `backgroundColor` for each
 * child. It is also important that each child have a `key` prop.
 *
 * Example:
 *
 * ```
 * render: function() {
 *   return (
 *     <ViewPagerAndroid
 *       style={styles.viewPager}
 *       initialPage={0}>
 *       <View style={styles.pageStyle} key="1">
 *         <Text>First page</Text>
 *       </View>
 *       <View style={styles.pageStyle} key="2">
 *         <Text>Second page</Text>
 *       </View>
 *     </ViewPagerAndroid>
 *   );
 * }
 *
 * ...
 *
 * var styles = {
 *   ...
 *   viewPager: {
 *     flex: 1
 *   },
 *   pageStyle: {
 *     alignItems: 'center',
 *     padding: 20,
 *   }
 * }
 * ```
 */

class ViewPagerAndroid extends React.Component<Props> {
  componentDidMount() {
    if (this.props.initialPage != null) {
      this.setPageWithoutAnimation(this.props.initialPage);
    }
  }

  /* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue was found
   * when making Flow check .android.js files. */
  getInnerViewNode = (): ReactComponent => {
    return this.refs[VIEWPAGER_REF].getInnerViewNode();
  };

  /* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue was found
   * when making Flow check .android.js files. */
  _childrenWithOverridenStyle = (): Array => {
    // Override styles so that each page will fill the parent. Native component
    // will handle positioning of elements, so it's not important to offset
    // them correctly.
    return React.Children.map(this.props.children, function(child) {
      if (!child) {
        return null;
      }
      const newProps = {
        ...child.props,
        style: [
          child.props.style,
          {
            position: 'absolute',
            left: 0,
            top: 0,
            right: 0,
            bottom: 0,
            width: undefined,
            height: undefined,
          },
        ],
        collapsable: false,
      };
      if (
        child.type &&
        child.type.displayName &&
        child.type.displayName !== 'RCTView' &&
        child.type.displayName !== 'View'
      ) {
        console.warn(
          'Each ViewPager child must be a <View>. Was ' +
            child.type.displayName,
        );
      }
      return React.createElement(child.type, newProps);
    });
  };

  _onPageScroll = (e: Event) => {
    if (this.props.onPageScroll) {
      this.props.onPageScroll(e);
    }
    if (this.props.keyboardDismissMode === 'on-drag') {
      dismissKeyboard();
    }
  };

  _onPageScrollStateChanged = (e: Event) => {
    if (this.props.onPageScrollStateChanged) {
      this.props.onPageScrollStateChanged(e.nativeEvent.pageScrollState);
    }
  };

  _onPageSelected = (e: Event) => {
    if (this.props.onPageSelected) {
      this.props.onPageSelected(e);
    }
  };

  /**
   * A helper function to scroll to a specific page in the ViewPager.
   * The transition between pages will be animated.
   */
  setPage = (selectedPage: number) => {
    UIManager.dispatchViewManagerCommand(
      ReactNative.findNodeHandle(this),
      UIManager.getViewManagerConfig('AndroidViewPager').Commands.setPage,
      [selectedPage],
    );
  };

  /**
   * A helper function to scroll to a specific page in the ViewPager.
   * The transition between pages will *not* be animated.
   */
  setPageWithoutAnimation = (selectedPage: number) => {
    UIManager.dispatchViewManagerCommand(
      ReactNative.findNodeHandle(this),
      UIManager.getViewManagerConfig('AndroidViewPager').Commands
        .setPageWithoutAnimation,
      [selectedPage],
    );
  };

  render() {
    return (
      <NativeAndroidViewPager
        {...this.props}
        ref={VIEWPAGER_REF}
        style={this.props.style}
        onPageScroll={this._onPageScroll}
        onPageScrollStateChanged={this._onPageScrollStateChanged}
        onPageSelected={this._onPageSelected}
        children={this._childrenWithOverridenStyle()}
      />
    );
  }
}

module.exports = ViewPagerAndroid;
<commit_msg>Flow strict in ViewPagerAndroid.android.js (#22134)<commit_after>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow strict-local
 */

'use strict';

const React = require('React');
const ReactNative = require('ReactNative');
const UIManager = require('UIManager');

const dismissKeyboard = require('dismissKeyboard');
const requireNativeComponent = require('requireNativeComponent');

const NativeAndroidViewPager = requireNativeComponent('AndroidViewPager');

import type {SyntheticEvent} from 'CoreEventTypes';
import type {ViewStyleProp} from 'StyleSheet';

const VIEWPAGER_REF = 'viewPager';

type PageScrollState = 'idle' | 'dragging' | 'settling';

type PageScrollEvent = SyntheticEvent<
  $ReadOnly<{|
    position: number,
    offset: number,
  |}>,
>;

type PageScrollStateChangedEvent = SyntheticEvent<
  $ReadOnly<{|
    pageScrollState: PageScrollState,
  |}>,
>;

type PageSelectedEvent = SyntheticEvent<
  $ReadOnly<{|
    position: number,
  |}>,
>;

export type ViewPagerScrollState = $Enum<{
  idle: string,
  dragging: string,
  settling: string,
}>;

type Props = $ReadOnly<{|
  /**
   * Index of initial page that should be selected. Use `setPage` method to
   * update the page, and `onPageSelected` to monitor page changes
   */
  initialPage?: ?number,

  /**
   * Executed when transitioning between pages (ether because of animation for
   * the requested page change or when user is swiping/dragging between pages)
   * The `event.nativeEvent` object for this callback will carry following data:
   *  - position - index of first page from the left that is currently visible
   *  - offset - value from range [0,1) describing stage between page transitions.
   *    Value x means that (1 - x) fraction of the page at "position" index is
   *    visible, and x fraction of the next page is visible.
   */
  onPageScroll?: ?(e: PageScrollEvent) => void,

  /**
   * Function called when the page scrolling state has changed.
   * The page scrolling state can be in 3 states:
   * - idle, meaning there is no interaction with the page scroller happening at the time
   * - dragging, meaning there is currently an interaction with the page scroller
   * - settling, meaning that there was an interaction with the page scroller, and the
   *   page scroller is now finishing it's closing or opening animation
   */
  onPageScrollStateChanged?: ?(e: PageScrollState) => void,

  /**
   * This callback will be called once ViewPager finish navigating to selected page
   * (when user swipes between pages). The `event.nativeEvent` object passed to this
   * callback will have following fields:
   *  - position - index of page that has been selected
   */
  onPageSelected?: ?(e: PageSelectedEvent) => void,

  /**
   * Blank space to show between pages. This is only visible while scrolling, pages are still
   * edge-to-edge.
   */
  pageMargin?: ?number,

  /**
   * Whether enable showing peekFraction or not. If this is true, the preview of
   * last and next page will show in current screen. Defaults to false.
   */

  peekEnabled?: ?boolean,

  /**
   * Determines whether the keyboard gets dismissed in response to a drag.
   *   - 'none' (the default), drags do not dismiss the keyboard.
   *   - 'on-drag', the keyboard is dismissed when a drag begins.
   */
  keyboardDismissMode?: ?('none' | 'on-drag'),

  /**
   * When false, the content does not scroll.
   * The default value is true.
   */
  scrollEnabled?: ?boolean,

  children?: React.Node,

  style?: ?ViewStyleProp,
|}>;

/**
 * Container that allows to flip left and right between child views. Each
 * child view of the `ViewPagerAndroid` will be treated as a separate page
 * and will be stretched to fill the `ViewPagerAndroid`.
 *
 * It is important all children are `<View>`s and not composite components.
 * You can set style properties like `padding` or `backgroundColor` for each
 * child. It is also important that each child have a `key` prop.
 *
 * Example:
 *
 * ```
 * render: function() {
 *   return (
 *     <ViewPagerAndroid
 *       style={styles.viewPager}
 *       initialPage={0}>
 *       <View style={styles.pageStyle} key="1">
 *         <Text>First page</Text>
 *       </View>
 *       <View style={styles.pageStyle} key="2">
 *         <Text>Second page</Text>
 *       </View>
 *     </ViewPagerAndroid>
 *   );
 * }
 *
 * ...
 *
 * var styles = {
 *   ...
 *   viewPager: {
 *     flex: 1
 *   },
 *   pageStyle: {
 *     alignItems: 'center',
 *     padding: 20,
 *   }
 * }
 * ```
 */

class ViewPagerAndroid extends React.Component<Props> {
  componentDidMount() {
    if (this.props.initialPage != null) {
      this.setPageWithoutAnimation(this.props.initialPage);
    }
  }

  /* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue was found
   * when making Flow check .android.js files. */
  getInnerViewNode = (): ReactComponent => {
    return this.refs[VIEWPAGER_REF].getInnerViewNode();
  };

  /* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue was found
   * when making Flow check .android.js files. */
  _childrenWithOverridenStyle = (): Array => {
    // Override styles so that each page will fill the parent. Native component
    // will handle positioning of elements, so it's not important to offset
    // them correctly.
    return React.Children.map(this.props.children, function(child) {
      if (!child) {
        return null;
      }
      const newProps = {
        ...child.props,
        style: [
          child.props.style,
          {
            position: 'absolute',
            left: 0,
            top: 0,
            right: 0,
            bottom: 0,
            width: undefined,
            height: undefined,
          },
        ],
        collapsable: false,
      };
      if (
        child.type &&
        child.type.displayName &&
        child.type.displayName !== 'RCTView' &&
        child.type.displayName !== 'View'
      ) {
        console.warn(
          'Each ViewPager child must be a <View>. Was ' +
            child.type.displayName,
        );
      }
      return React.createElement(child.type, newProps);
    });
  };

  _onPageScroll = (e: PageScrollEvent) => {
    if (this.props.onPageScroll) {
      this.props.onPageScroll(e);
    }
    if (this.props.keyboardDismissMode === 'on-drag') {
      dismissKeyboard();
    }
  };

  _onPageScrollStateChanged = (e: PageScrollStateChangedEvent) => {
    if (this.props.onPageScrollStateChanged) {
      this.props.onPageScrollStateChanged(e.nativeEvent.pageScrollState);
    }
  };

  _onPageSelected = (e: PageSelectedEvent) => {
    if (this.props.onPageSelected) {
      this.props.onPageSelected(e);
    }
  };

  /**
   * A helper function to scroll to a specific page in the ViewPager.
   * The transition between pages will be animated.
   */
  setPage = (selectedPage: number) => {
    UIManager.dispatchViewManagerCommand(
      ReactNative.findNodeHandle(this),
      UIManager.getViewManagerConfig('AndroidViewPager').Commands.setPage,
      [selectedPage],
    );
  };

  /**
   * A helper function to scroll to a specific page in the ViewPager.
   * The transition between pages will *not* be animated.
   */
  setPageWithoutAnimation = (selectedPage: number) => {
    UIManager.dispatchViewManagerCommand(
      ReactNative.findNodeHandle(this),
      UIManager.getViewManagerConfig('AndroidViewPager').Commands
        .setPageWithoutAnimation,
      [selectedPage],
    );
  };

  render() {
    return (
      <NativeAndroidViewPager
        {...this.props}
        ref={VIEWPAGER_REF}
        style={this.props.style}
        onPageScroll={this._onPageScroll}
        onPageScrollStateChanged={this._onPageScrollStateChanged}
        onPageSelected={this._onPageSelected}
        children={this._childrenWithOverridenStyle()}
      />
    );
  }
}

module.exports = ViewPagerAndroid;
<|endoftext|>


<commit_before>/* This file is part of Indico.
 * Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN).
 *
 * Indico is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 3 of the
 * License, or (at your option) any later version.
 *
 * Indico is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Indico; if not, see <http://www.gnu.org/licenses/>.
 */

import {connect} from 'react-redux';
import {reduxForm, reset} from 'redux-form';

import ErrorDialog from './component';
import {clearError, showReportForm} from './actions';

const mapStateToProps = ({errors: {errorList, formVisible}}) => ({
    errorData: errorList[0],
    remainingErrors: errorList.length ? errorList.length - 1 : 0,
    formVisible,
    dialogVisible: !!errorList.length,
});

const mapDispatchToProps = (dispatch) => ({
    showReportForm() {
        dispatch(showReportForm());
    },
    clearError() {
        dispatch(clearError());
        dispatch(reset('reportError'));
    },
});

const container = connect(mapStateToProps, mapDispatchToProps)(ErrorDialog);
export default reduxForm({form: 'reportError'})(container);
<commit_msg>Do not validate error form on blur<commit_after>/* This file is part of Indico.
 * Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN).
 *
 * Indico is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 3 of the
 * License, or (at your option) any later version.
 *
 * Indico is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Indico; if not, see <http://www.gnu.org/licenses/>.
 */

import {connect} from 'react-redux';
import {reduxForm, reset} from 'redux-form';

import ErrorDialog from './component';
import {clearError, showReportForm} from './actions';

const mapStateToProps = ({errors: {errorList, formVisible}}) => ({
    errorData: errorList[0],
    remainingErrors: errorList.length ? errorList.length - 1 : 0,
    formVisible,
    dialogVisible: !!errorList.length,
});

const mapDispatchToProps = (dispatch) => ({
    showReportForm() {
        dispatch(showReportForm());
    },
    clearError() {
        dispatch(clearError());
        dispatch(reset('reportError'));
    },
});

const container = connect(mapStateToProps, mapDispatchToProps)(ErrorDialog);
export default reduxForm({form: 'reportError', touchOnBlur: false})(container);
<|endoftext|>


<commit_before>import { formatNumber, formatPercentage } from 'common/format';

import Status from './Modules/Status';
import HealingDone from './Modules/HealingDone';
import DamageDone from './Modules/DamageDone';
import DamageTaken from './Modules/DamageTaken';

import Combatants from './Modules/Combatants';
import AbilityTracker from './Modules/AbilityTracker';
import Haste from './Modules/Haste';
import AlwaysBeCasting from './Modules/AlwaysBeCasting';
import Enemies from './Modules/Enemies';
import Pets from './Modules/Pets';
import HealEventTracker from './Modules/HealEventTracker';
import ManaValues from './Modules/ManaValues';
import SpellManaCost from './Modules/SpellManaCost';

import CritEffectBonus from './Modules/Helpers/CritEffectBonus';
import Bloodlust from './Modules/Helpers/Bloodlust';

// Shared Legendaries
import Prydaz from './Modules/Items/Prydaz';
import Velens from './Modules/Items/Velens';
import SephuzsSecret from './Modules/Items/SephuzsSecret';
import KiljaedensBurningWish from './Modules/Items/KiljaedensBurningWish';
import ArchimondesHatredReborn from './Modules/Items/ArchimondesHatredReborn';
// Shared Epics
import DrapeOfShame from './Modules/Items/DrapeOfShame';
import DarkmoonDeckPromises from './Modules/Items/DarkmoonDeckPromises';
import AmalgamsSeventhSpine from './Modules/Items/AmalgamsSeventhSpine';
import ArchiveOfFaith from './Modules/Items/ArchiveOfFaith';
import BarbaricMindslaver from './Modules/Items/BarbaricMindslaver';
import SeaStar from './Modules/Items/SeaStarOfTheDepthmother';
import DeceiversGrandDesign from './Modules/Items/DeceiversGrandDesign';
import PrePotion from './Modules/Items/PrePotion';
import GnawedThumbRing from './Modules/Items/GnawedThumbRing';
import VialOfCeaselessToxins from './Modules/Items/VialOfCeaselessToxins';
import SpecterOfBetrayal from './Modules/Items/SpecterOfBetrayal';
import EngineOfEradication from './Modules/Items/EngineOfEradication';
import TarnishedSentinelMedallion from './Modules/Items/TarnishedSentinelMedallion';
import SpectralThurible from './Modules/Items/SpectralThurible';
import TerrorFromBelow from './Modules/Items/TerrorFromBelow';
import TomeOfUnravelingSanity from './Modules/Items/TomeOfUnravelingSanity';

// Shared Buffs
import VantusRune from './Modules/VantusRune';

import ParseResults from './ParseResults';

const debug = false;

let _modulesDeprectatedWarningSent = false;

class CombatLogParser {
  static abilitiesAffectedByHealingIncreases = [];

  static defaultModules = {
    status: Status,
    healingDone: HealingDone,
    damageDone: DamageDone,
    damageTaken: DamageTaken,

    combatants: Combatants,
    enemies: Enemies,
    pets: Pets,
    spellManaCost: SpellManaCost,
    abilityTracker: AbilityTracker,
    healEventTracker: HealEventTracker,
    haste: Haste,
    alwaysBeCasting: AlwaysBeCasting,
    manaValues: ManaValues,
    vantusRune: VantusRune,

    critEffectBonus: CritEffectBonus,
    bloodlust: Bloodlust,

    // Items:
    // Legendaries:
    prydaz: Prydaz,
    velens: Velens,
    sephuzsSecret: SephuzsSecret,
    kiljaedensBurningWish: KiljaedensBurningWish,
    archimondesHatredReborn: ArchimondesHatredReborn,
    // Epics:
    drapeOfShame: DrapeOfShame,
    amalgamsSeventhSpine: AmalgamsSeventhSpine,
    darkmoonDeckPromises: DarkmoonDeckPromises,
    prePotion: PrePotion,
    gnawedThumbRing: GnawedThumbRing,
    // Tomb trinkets:
    archiveOfFaith: ArchiveOfFaith,
    barbaricMindslaver: BarbaricMindslaver,
    seaStar: SeaStar,
    deceiversGrandDesign: DeceiversGrandDesign,
    vialCeaslessToxins: VialOfCeaselessToxins,
    specterOfBetrayal: SpecterOfBetrayal,
    engineOfEradication: EngineOfEradication,
    tarnishedSentinelMedallion: TarnishedSentinelMedallion,
    spectralThurible: SpectralThurible,
    terrorFromBelow: TerrorFromBelow,
    tomeOfUnravelingSanity: TomeOfUnravelingSanity,
  };
  // Override this with spec specific modules
  static specModules = {};

  report = null;
  player = null;
  playerPets = null;
  fight = null;

  _modules = {};
  get modules() {
    if (!_modulesDeprectatedWarningSent) {
      console.error('Using `this.owner.modules` is deprectated. You should add the module you want to use as a dependency and use the property that\'s added to your module instead.');
      _modulesDeprectatedWarningSent = true;
    }
    return this._modules;
  }
  get activeModules() {
    return Object.keys(this._modules)
      .map(key => this._modules[key])
      .filter(module => module.active);
  }

  get playerId() {
    return this.player.id;
  }

  _timestamp = null;
  get currentTimestamp() {
    return this.finished ? this.fight.end_time : this._timestamp;
  }
  get fightDuration() {
    return this.currentTimestamp - this.fight.start_time;
  }
  get finished() {
    return this._modules.status.finished;
  }

  get playersById() {
    return this.report.friendlies.reduce((obj, player) => {
      obj[player.id] = player;
      return obj;
    }, {});
  }

  constructor(report, player, playerPets, fight) {
    this.report = report;
    this.player = player;
    this.playerPets = playerPets;
    this.fight = fight;

    this.initializeModules({
      ...this.constructor.defaultModules,
      ...this.constructor.specModules,
    });
  }

  initializeModules(modules) {
    const failedModules = [];
    Object.keys(modules).forEach((desiredModuleName) => {
      const moduleClass = modules[desiredModuleName];
      if (!moduleClass) {
        return;
      }

      const availableDependencies = {};
      const missingDependencies = [];
      if (moduleClass.dependencies) {
        Object.keys(moduleClass.dependencies).forEach((desiredDependencyName) => {
          const dependencyClass = moduleClass.dependencies[desiredDependencyName];

          const dependencyModule = this.findModule(dependencyClass);
          if (dependencyModule) {
            availableDependencies[desiredDependencyName] = dependencyModule;
          } else {
            missingDependencies.push(dependencyClass);
          }
        });
      }

      if (missingDependencies.length === 0) {
        if (debug) {
          if (Object.keys(availableDependencies).length === 0) {
            console.log('Loading', moduleClass.name);
          } else {
            console.log('Loading', moduleClass.name, 'with dependencies:', Object.keys(availableDependencies));
          }
        }
        // eslint-disable-next-line new-cap
        this._modules[desiredModuleName] = new moduleClass(this, availableDependencies, Object.keys(this._modules).length);
      } else {
        debug && console.warn(moduleClass.name, 'could not be loaded, missing dependencies:', missingDependencies.map(d => d.name));
        failedModules.push(desiredModuleName);
      }
    });

    if (failedModules.length !== 0) {
      debug && console.warn(`${failedModules.length} modules failed to load, trying again:`, failedModules.map(key => modules[key].name));
      const newBatch = {};
      failedModules.forEach((key) => {
        newBatch[key] = modules[key];
      });
      this.initializeModules(newBatch);
    }
  }
  findModule(type) {
    return Object.keys(this._modules)
      .map(key => this._modules[key])
      .find(module => module instanceof type);
  }

  _debugEventHistory = [];
  parseEvents(events) {
    if (process.env.NODE_ENV === 'development') {
      this._debugEventHistory = [
        ...this._debugEventHistory,
        ...events,
      ];
    }
    return new Promise((resolve, reject) => {
      events.forEach((event) => {
        if (this.error) {
          throw new Error(this.error);
        }
        this._timestamp = event.timestamp;

        // Triggering a lot of events here for development pleasure; does this have a significant performance impact?
        this.triggerEvent(event.type, event);
      });

      resolve(events.length);
    });
  }

  reorderEvents(events) {
    this.activeModules
      .sort((a, b) => a.priority - b.priority) // lowest should go first, as `priority = 0` will have highest prio
      .forEach((module) => {
        if (module.reorderEvents) {
          events = module.reorderEvents(events);
        }
      });
    return events;
  }

  triggerEvent(eventType, event, ...args) {
    this.activeModules
      .sort((a, b) => a.priority - b.priority) // lowest should go first, as `priority = 0` will have highest prio
      .forEach(module => module.triggerEvent(eventType, event, ...args));
  }

  byPlayer(event, playerId = this.player.id) {
    return (event.sourceID === playerId);
  }
  toPlayer(event, playerId = this.player.id) {
    return (event.targetID === playerId);
  }
  byPlayerPet(event) {
    return this.playerPets.some(pet => pet.id === event.sourceID);
  }
  toPlayerPet(event) {
    return this.playerPets.some(pet => pet.id === event.targetID);
  }

  // TODO: Damage taken from LOTM

  getPercentageOfTotalHealingDone(healingDone) {
    return healingDone / this._modules.healingDone.total.effective;
  }
  formatItemHealingDone(healingDone) {
    return `${formatPercentage(this.getPercentageOfTotalHealingDone(healingDone))} % / ${formatNumber(healingDone / this.fightDuration * 1000)} HPS`;
  }
  formatItemAbsorbDone(absorbDone) {
    return `${formatNumber(absorbDone)}`;
  }
  getPercentageOfTotalDamageDone(damageDone) {
    return damageDone / this._modules.damageDone.total.effective;
  }
  formatItemDamageDone(damageDone) {
    return `${formatPercentage(this.getPercentageOfTotalDamageDone(damageDone))} % / ${formatNumber(damageDone / this.fightDuration * 1000)} DPS`;
  }

  generateResults() {
    const results = new ParseResults();

    this.activeModules
      .sort((a, b) => b.priority - a.priority)
      .forEach((module) => {
        if (module.statistic) {
          const statistic = module.statistic();
          if (statistic) {
            results.statistics.push({
              statistic,
              order: module.statisticOrder,
            });
          }
        }
        if (module.item) {
          const item = module.item();
          if (item) {
            results.items.push(item);
          }
        }
        if (module.tab) {
          const tab = module.tab();
          if (tab) {
            results.tabs.push(tab);
          }
        }
        if (module.suggestions) {
          module.suggestions(results.suggestions.when);
        }
      });

    return results;
  }
}

export default CombatLogParser;
<commit_msg>Allow setting class properties when defining a spec module<commit_after>import { formatNumber, formatPercentage } from 'common/format';

import Status from './Modules/Status';
import HealingDone from './Modules/HealingDone';
import DamageDone from './Modules/DamageDone';
import DamageTaken from './Modules/DamageTaken';

import Combatants from './Modules/Combatants';
import AbilityTracker from './Modules/AbilityTracker';
import Haste from './Modules/Haste';
import AlwaysBeCasting from './Modules/AlwaysBeCasting';
import Enemies from './Modules/Enemies';
import Pets from './Modules/Pets';
import HealEventTracker from './Modules/HealEventTracker';
import ManaValues from './Modules/ManaValues';
import SpellManaCost from './Modules/SpellManaCost';

import CritEffectBonus from './Modules/Helpers/CritEffectBonus';
import Bloodlust from './Modules/Helpers/Bloodlust';

// Shared Legendaries
import Prydaz from './Modules/Items/Prydaz';
import Velens from './Modules/Items/Velens';
import SephuzsSecret from './Modules/Items/SephuzsSecret';
import KiljaedensBurningWish from './Modules/Items/KiljaedensBurningWish';
import ArchimondesHatredReborn from './Modules/Items/ArchimondesHatredReborn';
// Shared Epics
import DrapeOfShame from './Modules/Items/DrapeOfShame';
import DarkmoonDeckPromises from './Modules/Items/DarkmoonDeckPromises';
import AmalgamsSeventhSpine from './Modules/Items/AmalgamsSeventhSpine';
import ArchiveOfFaith from './Modules/Items/ArchiveOfFaith';
import BarbaricMindslaver from './Modules/Items/BarbaricMindslaver';
import SeaStar from './Modules/Items/SeaStarOfTheDepthmother';
import DeceiversGrandDesign from './Modules/Items/DeceiversGrandDesign';
import PrePotion from './Modules/Items/PrePotion';
import GnawedThumbRing from './Modules/Items/GnawedThumbRing';
import VialOfCeaselessToxins from './Modules/Items/VialOfCeaselessToxins';
import SpecterOfBetrayal from './Modules/Items/SpecterOfBetrayal';
import EngineOfEradication from './Modules/Items/EngineOfEradication';
import TarnishedSentinelMedallion from './Modules/Items/TarnishedSentinelMedallion';
import SpectralThurible from './Modules/Items/SpectralThurible';
import TerrorFromBelow from './Modules/Items/TerrorFromBelow';
import TomeOfUnravelingSanity from './Modules/Items/TomeOfUnravelingSanity';

// Shared Buffs
import VantusRune from './Modules/VantusRune';

import ParseResults from './ParseResults';

const debug = false;

let _modulesDeprectatedWarningSent = false;

class CombatLogParser {
  static abilitiesAffectedByHealingIncreases = [];

  static defaultModules = {
    status: Status,
    healingDone: HealingDone,
    damageDone: DamageDone,
    damageTaken: DamageTaken,

    combatants: Combatants,
    enemies: Enemies,
    pets: Pets,
    spellManaCost: SpellManaCost,
    abilityTracker: AbilityTracker,
    healEventTracker: HealEventTracker,
    haste: Haste,
    alwaysBeCasting: AlwaysBeCasting,
    manaValues: ManaValues,
    vantusRune: VantusRune,

    critEffectBonus: CritEffectBonus,
    bloodlust: Bloodlust,

    // Items:
    // Legendaries:
    prydaz: Prydaz,
    velens: Velens,
    sephuzsSecret: SephuzsSecret,
    kiljaedensBurningWish: KiljaedensBurningWish,
    archimondesHatredReborn: ArchimondesHatredReborn,
    // Epics:
    drapeOfShame: DrapeOfShame,
    amalgamsSeventhSpine: AmalgamsSeventhSpine,
    darkmoonDeckPromises: DarkmoonDeckPromises,
    prePotion: PrePotion,
    gnawedThumbRing: GnawedThumbRing,
    // Tomb trinkets:
    archiveOfFaith: ArchiveOfFaith,
    barbaricMindslaver: BarbaricMindslaver,
    seaStar: SeaStar,
    deceiversGrandDesign: DeceiversGrandDesign,
    vialCeaslessToxins: VialOfCeaselessToxins,
    specterOfBetrayal: SpecterOfBetrayal,
    engineOfEradication: EngineOfEradication,
    tarnishedSentinelMedallion: TarnishedSentinelMedallion,
    spectralThurible: SpectralThurible,
    terrorFromBelow: TerrorFromBelow,
    tomeOfUnravelingSanity: TomeOfUnravelingSanity,
  };
  // Override this with spec specific modules
  static specModules = {};

  report = null;
  player = null;
  playerPets = null;
  fight = null;

  _modules = {};
  get modules() {
    if (!_modulesDeprectatedWarningSent) {
      console.error('Using `this.owner.modules` is deprectated. You should add the module you want to use as a dependency and use the property that\'s added to your module instead.');
      _modulesDeprectatedWarningSent = true;
    }
    return this._modules;
  }
  get activeModules() {
    return Object.keys(this._modules)
      .map(key => this._modules[key])
      .filter(module => module.active);
  }

  get playerId() {
    return this.player.id;
  }

  _timestamp = null;
  get currentTimestamp() {
    return this.finished ? this.fight.end_time : this._timestamp;
  }
  get fightDuration() {
    return this.currentTimestamp - this.fight.start_time;
  }
  get finished() {
    return this._modules.status.finished;
  }

  get playersById() {
    return this.report.friendlies.reduce((obj, player) => {
      obj[player.id] = player;
      return obj;
    }, {});
  }

  constructor(report, player, playerPets, fight) {
    this.report = report;
    this.player = player;
    this.playerPets = playerPets;
    this.fight = fight;

    this.initializeModules({
      ...this.constructor.defaultModules,
      ...this.constructor.specModules,
    });
  }

  initializeModules(modules) {
    const failedModules = [];
    Object.keys(modules).forEach((desiredModuleName) => {
      const moduleConfig = modules[desiredModuleName];
      if (!moduleConfig) {
        return;
      }
      let moduleClass;
      let options;
      if (moduleConfig instanceof Array) {
        moduleClass = moduleConfig[0];
        options = moduleConfig[1];
      } else {
        moduleClass = moduleConfig;
        options = null;
      }

      const availableDependencies = {};
      const missingDependencies = [];
      if (moduleClass.dependencies) {
        Object.keys(moduleClass.dependencies).forEach((desiredDependencyName) => {
          const dependencyClass = moduleClass.dependencies[desiredDependencyName];

          const dependencyModule = this.findModule(dependencyClass);
          if (dependencyModule) {
            availableDependencies[desiredDependencyName] = dependencyModule;
          } else {
            missingDependencies.push(dependencyClass);
          }
        });
      }

      if (missingDependencies.length === 0) {
        if (debug) {
          if (Object.keys(availableDependencies).length === 0) {
            console.log('Loading', moduleClass.name);
          } else {
            console.log('Loading', moduleClass.name, 'with dependencies:', Object.keys(availableDependencies));
          }
        }
        // eslint-disable-next-line new-cap
        const module = new moduleClass(this, availableDependencies, Object.keys(this._modules).length);
        // We can't set the options via the constructor since a parent constructor can't override the values of a child's class properties.
        // See https://github.com/Microsoft/TypeScript/issues/6110 for more info
        if (options) {
          Object.keys(options).forEach(key => module[key] = options[key]);
        }
        this._modules[desiredModuleName] = module;
      } else {
        debug && console.warn(moduleClass.name, 'could not be loaded, missing dependencies:', missingDependencies.map(d => d.name));
        failedModules.push(desiredModuleName);
      }
    });

    if (failedModules.length !== 0) {
      debug && console.warn(`${failedModules.length} modules failed to load, trying again:`, failedModules.map(key => modules[key].name));
      const newBatch = {};
      failedModules.forEach((key) => {
        newBatch[key] = modules[key];
      });
      this.initializeModules(newBatch);
    }
  }
  findModule(type) {
    return Object.keys(this._modules)
      .map(key => this._modules[key])
      .find(module => module instanceof type);
  }

  _debugEventHistory = [];
  parseEvents(events) {
    if (process.env.NODE_ENV === 'development') {
      this._debugEventHistory = [
        ...this._debugEventHistory,
        ...events,
      ];
    }
    return new Promise((resolve, reject) => {
      events.forEach((event) => {
        if (this.error) {
          throw new Error(this.error);
        }
        this._timestamp = event.timestamp;

        // Triggering a lot of events here for development pleasure; does this have a significant performance impact?
        this.triggerEvent(event.type, event);
      });

      resolve(events.length);
    });
  }

  reorderEvents(events) {
    this.activeModules
      .sort((a, b) => a.priority - b.priority) // lowest should go first, as `priority = 0` will have highest prio
      .forEach((module) => {
        if (module.reorderEvents) {
          events = module.reorderEvents(events);
        }
      });
    return events;
  }

  triggerEvent(eventType, event, ...args) {
    this.activeModules
      .sort((a, b) => a.priority - b.priority) // lowest should go first, as `priority = 0` will have highest prio
      .forEach(module => module.triggerEvent(eventType, event, ...args));
  }

  byPlayer(event, playerId = this.player.id) {
    return (event.sourceID === playerId);
  }
  toPlayer(event, playerId = this.player.id) {
    return (event.targetID === playerId);
  }
  byPlayerPet(event) {
    return this.playerPets.some(pet => pet.id === event.sourceID);
  }
  toPlayerPet(event) {
    return this.playerPets.some(pet => pet.id === event.targetID);
  }

  // TODO: Damage taken from LOTM

  getPercentageOfTotalHealingDone(healingDone) {
    return healingDone / this._modules.healingDone.total.effective;
  }
  formatItemHealingDone(healingDone) {
    return `${formatPercentage(this.getPercentageOfTotalHealingDone(healingDone))} % / ${formatNumber(healingDone / this.fightDuration * 1000)} HPS`;
  }
  formatItemAbsorbDone(absorbDone) {
    return `${formatNumber(absorbDone)}`;
  }
  getPercentageOfTotalDamageDone(damageDone) {
    return damageDone / this._modules.damageDone.total.effective;
  }
  formatItemDamageDone(damageDone) {
    return `${formatPercentage(this.getPercentageOfTotalDamageDone(damageDone))} % / ${formatNumber(damageDone / this.fightDuration * 1000)} DPS`;
  }

  generateResults() {
    const results = new ParseResults();

    this.activeModules
      .sort((a, b) => b.priority - a.priority)
      .forEach((module) => {
        if (module.statistic) {
          const statistic = module.statistic();
          if (statistic) {
            results.statistics.push({
              statistic,
              order: module.statisticOrder,
            });
          }
        }
        if (module.item) {
          const item = module.item();
          if (item) {
            results.items.push(item);
          }
        }
        if (module.tab) {
          const tab = module.tab();
          if (tab) {
            results.tabs.push(tab);
          }
        }
        if (module.suggestions) {
          module.suggestions(results.suggestions.when);
        }
      });

    return results;
  }
}

export default CombatLogParser;
<|endoftext|>