<commit_before>define([
    'require',
    'jquery',
    'base/js/namespace',
    'base/js/dialog',
    'notebook/js/celltoolbar',
    'base/js/events',
    './resources/diff.min'
], function (require,
             $,
             Jupyter,
             dialog,
             notebook_celltoolbar,
             events, JsDiff) {

    var preset_name = 'Cell Testing';

    var CellToolbar = notebook_celltoolbar.CellToolbar;

    var prepare_data = function(cell) {
        if (cell.nbcelltesting_data === undefined) {
            cell.nbcelltesting_data = {};
        }
        if (cell.nbcelltesting_data.pending === undefined) {
            cell.nbcelltesting_data.pending = false;
        }
    };

    events.on('delete.Cell', function(event, data) {
        update_global_result();
    });

    events.on('create.Cell', function(event, data) {
        update_global_result();
    });

    events.on('selected_cell_type_changed.Notebook', function(event, data) {
        update_global_result();
    });

    events.on('execute.CodeCell', function(event, data) {
        cell = data.cell;
        prepare_data(cell);
        cell.nbcelltesting_data.pending = true;
        delete cell.nbcelltesting_data.result_test;
        cell.celltoolbar.rebuild();
        update_global_result();
    });

    events.on('finished_execute.CodeCell', function(event, data) {
        cell = data.cell;
        prepare_data(cell);
        cell.nbcelltesting_data.pending = false;
        test_output(cell);
        cell.celltoolbar.rebuild();
        update_global_result();
    });

    var prepare_metadata = function(cell) {
        if (cell.metadata.nbcelltesting === undefined) {
            cell.metadata.nbcelltesting = {};
        }
    };

    var cleanup_metadata = function(cell) {
        if (cell.metadata.nbcelltesting !== undefined &&
            Object.keys(cell.metadata.nbcelltesting).length === 0) {
            delete cell.metadata.nbcelltesting
        }
    };

    var cell_output = function(cell) {
        var pre = cell.output_area.element.find('div.output_subarea').last().find('pre');
        return pre.text();
    };

    var _save_desired_output_ = function(cell, output) {
        prepare_metadata(cell);
        cell.metadata.nbcelltesting.desired_output = output;
    };

    var save_current_output = function(cell) {
        return _save_desired_output_(cell, cell_output(cell));
    };

    var desired_output = function(cell) {
        if (cell.metadata.nbcelltesting !== undefined &&
            cell.metadata.nbcelltesting.desired_output !== undefined) {
            return cell.metadata.nbcelltesting.desired_output;
        }
        return null;
    };

    var reset_desired_output = function(cell) {
        if (cell.metadata.nbcelltesting !== undefined &&
            cell.metadata.nbcelltesting.hasOwnProperty("desired_output")) {
            delete cell.metadata.nbcelltesting.desired_output;
            cleanup_metadata(cell);
        }
    };

    var edit_nbcelltesting_metadata = function(cell, celltoolbar=null) {
        var metadata = {};
        if (cell.metadata.nbcelltesting !== undefined) {
            metadata = cell.metadata.nbcelltesting;
        }

        var notebook = Jupyter.notebook;
        dialog.edit_metadata({
            md: metadata,
            callback: function (md) {
                prepare_metadata(cell);
                cell.metadata.nbcelltesting = md;
                test_output(cell);
                if (celltoolbar !== null) {
                    celltoolbar.rebuild();
                }
            },
            name: 'Cell Testing',
            notebook: notebook,
            keyboard_manager: notebook.keyboard_manager});
    };

    var edit_desired_output = function(cell, celltoolbar=null) {
        var output = desired_output(cell);
        if (output === null) { output = ''; }
        var notebook = Jupyter.notebook
        var error_div = $('
').css('color', 'red');
        var message = 'Edit the desired output of the selected cell below.';

        var textarea = $('<textarea/>')
            .attr('rows', '13')
            .attr('cols', '80')
            .attr('name', 'desired-output')
            .text(output);

        var dialogform = $('
').attr('title', 'Edit Desired Output')
            .append(
                $('<form/>').append(
                    $('<fieldset/>').append(
                        $('')
                        .attr('for','desired-output')
                        .text(message)
                        )
                        .append(error_div)
                        .append($('
'))
                        .append(textarea)
                    )
            );

        var editor = CodeMirror.fromTextArea(textarea[0], {
            lineNumbers: true,
            matchBrackets: true,
            indentUnit: 2,
            autoIndent: true,
            mode: 'text/plain',
        });

        var modal_obj = dialog.modal({
            title: 'Edit Desired Output',
            body: dialogform,
            buttons: {
                OK: { class : "btn-primary",
                    click: function() {
                        var new_output = editor.getValue();
                        _save_desired_output_(cell, new_output);
                        test_output(cell);
                        celltoolbar.rebuild();
                        update_global_result();
                    }
                },
                Cancel: {}
            },
            notebook: notebook,
            keyboard_manager: notebook.keyboard_manager,
        });

        modal_obj.on('shown.bs.modal', function(){ editor.refresh(); });
    };

    var compare_output = function(cell) {
        var dout = desired_output(cell);
        if (dout === null) {
            return null;
        }
        var cout = cell_output(cell);
        return cout === dout;
    };

    var status = {'pending': {'text': '',
                              'style': 'info',
                              'cls': 'label-info ct-status-pending',
                              'count': 0,
                              'title': 'Number of pending tests'},
                  'not_available': {'text': 'no output saved',
                                    'style': 'info',
                                    'cls': 'label-info ct-status-not_available',
                                    'count': 0,
                                    'title': 'Number of cells without saved output'},
                  'failed': {'text': 'wrong output',
                             'style': 'danger',
                             'cls': 'label-danger ct-status-failed',
                             'count': 0,
                             'title': 'Number of cells with wrong output'},
                  'passed': {'text': 'correct output',
                             'style': 'success',
                             'cls': 'label-success ct-status-passed',
                             'count': 0,
                             'title': 'Number of cells with correct output'},
                 };

    var test_output = function(cell) {
        prepare_data(cell);
        var result;
        var cls;
        if (cell.nbcelltesting_data.pending === true) {
            result = 'pending';
        } else {
            comparison_result = compare_output(cell);
            if (comparison_result === null) {
                result = 'not_available';
            } else if (comparison_result === false) {
                result = 'failed';
            } else if (comparison_result === true) {
                result = 'passed';
            }
        }

        cell.nbcelltesting_data.result_test = result;
    };

    var result_test = function(cell) {
        if (cell.nbcelltesting_data === undefined ||
            cell.nbcelltesting_data.result_test === undefined) {
            test_output(cell);
        }
        return cell.nbcelltesting_data.result_test;
    };

    var diff_wrong_output = function(cell, celltoolbar=null) {
        var diff = JsDiff.diffChars(desired_output(cell), cell_output(cell));

        var modal_body = $('
').addClass('ct-diff-output');
        diff.forEach(function(part) {
            var colorstyle = part.added ? 'added' : (part.removed ? 'removed' : 'common');
            modal_body.append($('').addClass('ct-diff-output-' + colorstyle)
                              .html(part.value.replace(/\n/g, '
')));
        })

        var notebook = Jupyter.notebook;

        var modal_obj = dialog.modal({
            title: 'Comparison: Desired Output vs. Current Output',
            body: modal_body,
            buttons: {
                OK: {class: 'btn-primary'}
            },
            notebook: notebook,
            keyboard_manager: notebook.keyboard_manager,
        });
    };

    var create_result_test = function(div, cell, celltoolbar) {
        var cell_status = result_test(cell);

        var result = status[cell_status].text;
        var cls = status[cell_status].cls;

        var element = $('').addClass('label result-label').addClass(cls);
        element.append(result);
        element.on('click', function() { on_test_output(cell, celltoolbar); });

        $(div).addClass('ctb-thing result-test')
            .append(element);

        if (cell_status === 'failed') {
            var diffIcon = $('').addClass('fa fa-lg fa-question-circle ct-diff');
            diffIcon.on('click', function() {diff_wrong_output(cell, celltoolbar);});
            $(div).append(diffIcon)
        }
    };

    var create_button = function(name, value, callback) {
        return function(div, cell, celltoolbar) {
            var button = $('<button/>').addClass('btn btn-default btn-xs')
                .addClass('button-' + name)
                .prop('type', 'button')
                .html(value);
            button.name = name;
            button.on('click', function() { callback(cell, celltoolbar) });
            $(div).addClass('ctb-thing').append(button);
        };
    };

    var on_save_current_output = function(cell, celltoolbar) {
        save_current_output(cell);
        test_output(cell);
        celltoolbar.rebuild();
        update_global_result();
    };

    var create_button_save = create_button('save', 'Save Output',
                                           on_save_current_output);

    var on_reset_desired_output = function(cell, celltoolbar) {
        reset_desired_output(cell);
        test_output(cell);
        celltoolbar.rebuild();
        update_global_result();
    };

    var on_edit_nbcelltesting_metadata = edit_nbcelltesting_metadata;

    var on_edit_desired_output = edit_desired_output;

    var on_test_output = function(cell, celltoolbar) {
        test_output(cell);
        celltoolbar.rebuild();
        update_global_result();
    };

    var actions = [{name: 'Reset Output', callback: on_reset_desired_output},
                   {name: 'Edit Output', callback: on_edit_desired_output},
                   {name: 'Test Output', callback: on_test_output},
                   {name: 'break'},
                   {name: 'Edit Metadata', callback: on_edit_nbcelltesting_metadata}];

    var action_callback = function(action, cell, celltoolbar) {
        return function() { action.callback(cell, celltoolbar); }
    }

    var dropdown_factory = function(actions) {
        return function(div, cell, celltoolbar) {
        var dropdownButton = $('<button/>').addClass('btn btn-default btn-xs dropdown-toggle')
            .prop('type', 'button').attr('data-toggle', 'dropdown')
            .html('');

        var options = $('
').addClass('dropdown-menu');

        for (var action of actions) {
            if (action.name === 'break') {
                options.append($('
').attr('role', 'separator').addClass('divider'));
                continue;
            }
            var action_link = $('').prop('href', '#').html(action.name);
            action_link.on('click', action_callback(action, cell, celltoolbar));
            options.append($('
').append(action_link));
        }

        $(div).addClass('ctb-thing dropdown').append(dropdownButton).append(options);
        };
    };

    var create_dropdown_menu = dropdown_factory(actions);

    var global_status = ['passed', 'pending', 'failed', 'not_available'];

    var update_global_result = function() {
        var n = 0;
        for (var s in status) {
            status[s].count = $('.ct-status-' + s).length
            n += status[s].count;
        }
        for (var s in status) {
            $('#nbcelltesting-global-result-' + s)
                .attr('style', 'width: ' + status[s].count / n * 100 + '%')
                .html(status[s].count);
        }
    };

    events.on('preset_activated.CellToolbar', function(event, preset) {
        create_global_result(preset.name === preset_name);
    });

    var create_global_result = function(show=true) {
        var element = $('#nbcelltesting-global-result');
        if (show) {
            if (element.length == 0) {
                var progress = $('
')
                    .attr('id', 'nbcelltesting-global-result')
                    .addClass('progress')
                for (var s of global_status) {
                    progress.append(
                        $('
')
                            .attr('id', 'nbcelltesting-global-result-' + s)
                            .addClass('progress-bar progress-bar-' + status[s].style)
                            .attr('role', 'progressbar')
                            .attr('title', status[s].title));
                }
                $("#maintoolbar-container").append(progress);
            }
            update_global_result();
            element = progress;
            element.show();
        } else {
            element.hide();
        }
    };

    var load_css = function () {
        var link = document.createElement('link');
        link.type = 'text/css';
        link.rel = 'stylesheet';
        link.href = require.toUrl('./nbcelltesting.css');
        document.getElementsByTagName('head')[0].appendChild(link);
    };

    function load_extension(){
        load_css();

        CellToolbar.register_callback('nbcelltesting.result_test',
                                      create_result_test,
                                      ['code']);
        CellToolbar.register_callback('nbcelltesting.button_save',
                                      create_button_save,
                                      ['code']);
        CellToolbar.register_callback('nbcelltesting.dropdown_menu',
                                      create_dropdown_menu,
                                      ['code']);

         var preset = [
            'nbcelltesting.result_test',
            'nbcelltesting.button_save',
            'nbcelltesting.dropdown_menu',
        ];
        CellToolbar.register_preset(preset_name, preset, Jupyter.notebook);
        console.log('nbcelltesting extension loaded.');
    }

    return {
        load_ipython_extension: load_extension,
        load_css: load_css,
        create_button: create_button,
        create_global_result: create_global_result,
        dropdown_factory: dropdown_factory,
        on_reset_desired_output: on_reset_desired_output,
        on_edit_desired_output: on_edit_desired_output,
        on_test_output: on_test_output,
        on_edit_nbcelltesting_metadata: on_edit_nbcelltesting_metadata
    };
});
<commit_msg>minor rewrite of default argument to extend compatibility<commit_after>define([
    'require',
    'jquery',
    'base/js/namespace',
    'base/js/dialog',
    'notebook/js/celltoolbar',
    'base/js/events',
    './resources/diff.min'
], function (require,
             $,
             Jupyter,
             dialog,
             notebook_celltoolbar,
             events, JsDiff) {

    var preset_name = 'Cell Testing';

    var CellToolbar = notebook_celltoolbar.CellToolbar;

    var prepare_data = function(cell) {
        if (cell.nbcelltesting_data === undefined) {
            cell.nbcelltesting_data = {};
        }
        if (cell.nbcelltesting_data.pending === undefined) {
            cell.nbcelltesting_data.pending = false;
        }
    };

    events.on('delete.Cell', function(event, data) {
        update_global_result();
    });

    events.on('create.Cell', function(event, data) {
        update_global_result();
    });

    events.on('selected_cell_type_changed.Notebook', function(event, data) {
        update_global_result();
    });

    events.on('execute.CodeCell', function(event, data) {
        cell = data.cell;
        prepare_data(cell);
        cell.nbcelltesting_data.pending = true;
        delete cell.nbcelltesting_data.result_test;
        cell.celltoolbar.rebuild();
        update_global_result();
    });

    events.on('finished_execute.CodeCell', function(event, data) {
        cell = data.cell;
        prepare_data(cell);
        cell.nbcelltesting_data.pending = false;
        test_output(cell);
        cell.celltoolbar.rebuild();
        update_global_result();
    });

    var prepare_metadata = function(cell) {
        if (cell.metadata.nbcelltesting === undefined) {
            cell.metadata.nbcelltesting = {};
        }
    };

    var cleanup_metadata = function(cell) {
        if (cell.metadata.nbcelltesting !== undefined &&
            Object.keys(cell.metadata.nbcelltesting).length === 0) {
            delete cell.metadata.nbcelltesting
        }
    };

    var cell_output = function(cell) {
        var pre = cell.output_area.element.find('div.output_subarea').last().find('pre');
        return pre.text();
    };

    var _save_desired_output_ = function(cell, output) {
        prepare_metadata(cell);
        cell.metadata.nbcelltesting.desired_output = output;
    };

    var save_current_output = function(cell) {
        return _save_desired_output_(cell, cell_output(cell));
    };

    var desired_output = function(cell) {
        if (cell.metadata.nbcelltesting !== undefined &&
            cell.metadata.nbcelltesting.desired_output !== undefined) {
            return cell.metadata.nbcelltesting.desired_output;
        }
        return null;
    };

    var reset_desired_output = function(cell) {
        if (cell.metadata.nbcelltesting !== undefined &&
            cell.metadata.nbcelltesting.hasOwnProperty("desired_output")) {
            delete cell.metadata.nbcelltesting.desired_output;
            cleanup_metadata(cell);
        }
    };

    var edit_nbcelltesting_metadata = function(cell, celltoolbar=null) {
        var metadata = {};
        if (cell.metadata.nbcelltesting !== undefined) {
            metadata = cell.metadata.nbcelltesting;
        }

        var notebook = Jupyter.notebook;
        dialog.edit_metadata({
            md: metadata,
            callback: function (md) {
                prepare_metadata(cell);
                cell.metadata.nbcelltesting = md;
                test_output(cell);
                if (celltoolbar !== null) {
                    celltoolbar.rebuild();
                }
            },
            name: 'Cell Testing',
            notebook: notebook,
            keyboard_manager: notebook.keyboard_manager});
    };

    var edit_desired_output = function(cell, celltoolbar=null) {
        var output = desired_output(cell);
        if (output === null) { output = ''; }
        var notebook = Jupyter.notebook
        var error_div = $('
').css('color', 'red');
        var message = 'Edit the desired output of the selected cell below.';

        var textarea = $('<textarea/>')
            .attr('rows', '13')
            .attr('cols', '80')
            .attr('name', 'desired-output')
            .text(output);

        var dialogform = $('
').attr('title', 'Edit Desired Output')
            .append(
                $('<form/>').append(
                    $('<fieldset/>').append(
                        $('')
                        .attr('for','desired-output')
                        .text(message)
                        )
                        .append(error_div)
                        .append($('
'))
                        .append(textarea)
                    )
            );

        var editor = CodeMirror.fromTextArea(textarea[0], {
            lineNumbers: true,
            matchBrackets: true,
            indentUnit: 2,
            autoIndent: true,
            mode: 'text/plain',
        });

        var modal_obj = dialog.modal({
            title: 'Edit Desired Output',
            body: dialogform,
            buttons: {
                OK: { class : "btn-primary",
                    click: function() {
                        var new_output = editor.getValue();
                        _save_desired_output_(cell, new_output);
                        test_output(cell);
                        celltoolbar.rebuild();
                        update_global_result();
                    }
                },
                Cancel: {}
            },
            notebook: notebook,
            keyboard_manager: notebook.keyboard_manager,
        });

        modal_obj.on('shown.bs.modal', function(){ editor.refresh(); });
    };

    var compare_output = function(cell) {
        var dout = desired_output(cell);
        if (dout === null) {
            return null;
        }
        var cout = cell_output(cell);
        return cout === dout;
    };

    var status = {'pending': {'text': '',
                              'style': 'info',
                              'cls': 'label-info ct-status-pending',
                              'count': 0,
                              'title': 'Number of pending tests'},
                  'not_available': {'text': 'no output saved',
                                    'style': 'info',
                                    'cls': 'label-info ct-status-not_available',
                                    'count': 0,
                                    'title': 'Number of cells without saved output'},
                  'failed': {'text': 'wrong output',
                             'style': 'danger',
                             'cls': 'label-danger ct-status-failed',
                             'count': 0,
                             'title': 'Number of cells with wrong output'},
                  'passed': {'text': 'correct output',
                             'style': 'success',
                             'cls': 'label-success ct-status-passed',
                             'count': 0,
                             'title': 'Number of cells with correct output'},
                 };

    var test_output = function(cell) {
        prepare_data(cell);
        var result;
        var cls;
        if (cell.nbcelltesting_data.pending === true) {
            result = 'pending';
        } else {
            comparison_result = compare_output(cell);
            if (comparison_result === null) {
                result = 'not_available';
            } else if (comparison_result === false) {
                result = 'failed';
            } else if (comparison_result === true) {
                result = 'passed';
            }
        }

        cell.nbcelltesting_data.result_test = result;
    };

    var result_test = function(cell) {
        if (cell.nbcelltesting_data === undefined ||
            cell.nbcelltesting_data.result_test === undefined) {
            test_output(cell);
        }
        return cell.nbcelltesting_data.result_test;
    };

    var diff_wrong_output = function(cell, celltoolbar=null) {
        var diff = JsDiff.diffChars(desired_output(cell), cell_output(cell));

        var modal_body = $('
').addClass('ct-diff-output');
        diff.forEach(function(part) {
            var colorstyle = part.added ? 'added' : (part.removed ? 'removed' : 'common');
            modal_body.append($('').addClass('ct-diff-output-' + colorstyle)
                              .html(part.value.replace(/\n/g, '
')));
        })

        var notebook = Jupyter.notebook;

        var modal_obj = dialog.modal({
            title: 'Comparison: Desired Output vs. Current Output',
            body: modal_body,
            buttons: {
                OK: {class: 'btn-primary'}
            },
            notebook: notebook,
            keyboard_manager: notebook.keyboard_manager,
        });
    };

    var create_result_test = function(div, cell, celltoolbar) {
        var cell_status = result_test(cell);

        var result = status[cell_status].text;
        var cls = status[cell_status].cls;

        var element = $('').addClass('label result-label').addClass(cls);
        element.append(result);
        element.on('click', function() { on_test_output(cell, celltoolbar); });

        $(div).addClass('ctb-thing result-test')
            .append(element);

        if (cell_status === 'failed') {
            var diffIcon = $('').addClass('fa fa-lg fa-question-circle ct-diff');
            diffIcon.on('click', function() {diff_wrong_output(cell, celltoolbar);});
            $(div).append(diffIcon)
        }
    };

    var create_button = function(name, value, callback) {
        return function(div, cell, celltoolbar) {
            var button = $('<button/>').addClass('btn btn-default btn-xs')
                .addClass('button-' + name)
                .prop('type', 'button')
                .html(value);
            button.name = name;
            button.on('click', function() { callback(cell, celltoolbar) });
            $(div).addClass('ctb-thing').append(button);
        };
    };

    var on_save_current_output = function(cell, celltoolbar) {
        save_current_output(cell);
        test_output(cell);
        celltoolbar.rebuild();
        update_global_result();
    };

    var create_button_save = create_button('save', 'Save Output',
                                           on_save_current_output);

    var on_reset_desired_output = function(cell, celltoolbar) {
        reset_desired_output(cell);
        test_output(cell);
        celltoolbar.rebuild();
        update_global_result();
    };

    var on_edit_nbcelltesting_metadata = edit_nbcelltesting_metadata;

    var on_edit_desired_output = edit_desired_output;

    var on_test_output = function(cell, celltoolbar) {
        test_output(cell);
        celltoolbar.rebuild();
        update_global_result();
    };

    var actions = [{name: 'Reset Output', callback: on_reset_desired_output},
                   {name: 'Edit Output', callback: on_edit_desired_output},
                   {name: 'Test Output', callback: on_test_output},
                   {name: 'break'},
                   {name: 'Edit Metadata', callback: on_edit_nbcelltesting_metadata}];

    var action_callback = function(action, cell, celltoolbar) {
        return function() { action.callback(cell, celltoolbar); }
    }

    var dropdown_factory = function(actions) {
        return function(div, cell, celltoolbar) {
        var dropdownButton = $('<button/>').addClass('btn btn-default btn-xs dropdown-toggle')
            .prop('type', 'button').attr('data-toggle', 'dropdown')
            .html('');

        var options = $('
').addClass('dropdown-menu');

        for (var action of actions) {
            if (action.name === 'break') {
                options.append($('
').attr('role', 'separator').addClass('divider'));
                continue;
            }
            var action_link = $('').prop('href', '#').html(action.name);
            action_link.on('click', action_callback(action, cell, celltoolbar));
            options.append($('
').append(action_link));
        }

        $(div).addClass('ctb-thing dropdown').append(dropdownButton).append(options);
        };
    };

    var create_dropdown_menu = dropdown_factory(actions);

    var global_status = ['passed', 'pending', 'failed', 'not_available'];

    var update_global_result = function() {
        var n = 0;
        for (var s in status) {
            status[s].count = $('.ct-status-' + s).length
            n += status[s].count;
        }
        for (var s in status) {
            $('#nbcelltesting-global-result-' + s)
                .attr('style', 'width: ' + status[s].count / n * 100 + '%')
                .html(status[s].count);
        }
    };

    events.on('preset_activated.CellToolbar', function(event, preset) {
        create_global_result(preset.name === preset_name);
    });

    var create_global_result = function(show) {
        if (typeof show === 'undefined') { show = true; }
        var element = $('#nbcelltesting-global-result');
        if (show) {
            if (element.length == 0) {
                var progress = $('
')
                    .attr('id', 'nbcelltesting-global-result')
                    .addClass('progress')
                for (var s of global_status) {
                    progress.append(
                        $('
')
                            .attr('id', 'nbcelltesting-global-result-' + s)
                            .addClass('progress-bar progress-bar-' + status[s].style)
                            .attr('role', 'progressbar')
                            .attr('title', status[s].title));
                }
                $("#maintoolbar-container").append(progress);
            }
            update_global_result();
            element = progress;
            element.show();
        } else {
            element.hide();
        }
    };

    var load_css = function () {
        var link = document.createElement('link');
        link.type = 'text/css';
        link.rel = 'stylesheet';
        link.href = require.toUrl('./nbcelltesting.css');
        document.getElementsByTagName('head')[0].appendChild(link);
    };

    function load_extension(){
        load_css();

        CellToolbar.register_callback('nbcelltesting.result_test',
                                      create_result_test,
                                      ['code']);
        CellToolbar.register_callback('nbcelltesting.button_save',
                                      create_button_save,
                                      ['code']);
        CellToolbar.register_callback('nbcelltesting.dropdown_menu',
                                      create_dropdown_menu,
                                      ['code']);

         var preset = [
            'nbcelltesting.result_test',
            'nbcelltesting.button_save',
            'nbcelltesting.dropdown_menu',
        ];
        CellToolbar.register_preset(preset_name, preset, Jupyter.notebook);
        console.log('nbcelltesting extension loaded.');
    }

    return {
        load_ipython_extension: load_extension,
        load_css: load_css,
        create_button: create_button,
        create_global_result: create_global_result,
        dropdown_factory: dropdown_factory,
        on_reset_desired_output: on_reset_desired_output,
        on_edit_desired_output: on_edit_desired_output,
        on_test_output: on_test_output,
        on_edit_nbcelltesting_metadata: on_edit_nbcelltesting_metadata
    };
});
<|endoftext|>


<commit_before>#! /usr/bin/env node

"use strict";

var sh = require('shelljs');
var argv = require('minimist')(process.argv.slice(2));

var hostname = sh.exec('hostname', { silent: true }).output;
if (argv._.length < 1) {
    sh.echo('Expect new name!', hostname);
    sh.exit(1);
}
var newname = argv._[0];

sh.echo('Change Hostname: ' + hostname + ' -> ' + newname);
sh.exec('sudo hostname ' + newname);
sh.sed('-i', hostname, newname, '/etc/hosts');<commit_msg>hostname<commit_after>#! /usr/bin/env node

"use strict";

var sh = require('shelljs');
var argv = require('minimist')(process.argv.slice(2));

var hostname = sh.exec('hostname', { silent: true }).output;
hostname = hostname.replace('\n', '');
if (argv._.length < 1) {
    sh.echo('Expect a new name!');
    sh.exit(1);
}
var newname = argv._[0];

sh.echo('Change Hostname: ' + hostname + ' -> ' + newname);
sh.exec('sudo hostname ' + newname);
sh.sed('-i', hostname, newname, '/etc/hosts');<|endoftext|>


<commit_before>// insert service worker scripts here
this.addEventListener("install", function (event) {
      event.waitUntil(
        caches.open('v2').then(function (cache) {
         return cache.addAll(['/html/',
            '/html/index.html',
            'html/js/',
            'html/js/911.js',
            'html/img/',
          	'/html/img/trapped_lafhdy.jpg',
          	'/html/img/screaming.jpg',
          	'/html/img/location4.jpg',
          	'/html/img/embrace3.jpg'
          	]);
        })
        );
    });

this.addEventListener('fetch', function (event) {
	event.respondWith(caches.match(event.request));
});<commit_msg>syntax error<commit_after>// insert service worker scripts here
this.addEventListener("install", function (event) {
      event.waitUntil(
        caches.open('v2').then(function (cache) {
         return cache.addAll(['/html/',
            '/html/index.html',
            'html/js/',
            'html/js/911.js',
            'html/img/',
          	'/html/img/trapped_lady.jpg',
          	'/html/img/screaming.jpg',
          	'/html/img/location4.jpg',
          	'/html/img/embrace3.jpg'
          	]);
        })
        );
    });

this.addEventListener('fetch', function (event) {
	event.respondWith(caches.match(event.request));
});<|endoftext|>


<commit_before>const { outputBanner } = require('ci-utils');
const { tmpdir } = require('os');
const { join } = require('path');
const { readFileSync, writeFileSync } = require('fs');
const sauce = require('./scripts/nightwatch-sauce');

const dataFilePath = join(tmpdir, 'nightwatch-results.json');
const read = () => JSON.parse(readFileSync(dataFilePath, 'utf8'));
const write = data => writeFileSync(dataFilePath, JSON.stringify(data), 'utf8');

console.log({ dataFilePath });

write({
  testCount: 0,
  results: [],
});

const theGlobals = {
  testingUrl: 'https://boltdesignsystem.com',
  results: [],
  testCount: 0,

  afterEach(browser, cb) {
    const data = read();
    data.testCount += 1;
    write(data);
    // theGlobals.testCount += 1;
    outputBanner(`global afterEach called, testCount at ${data.testCount}`);
    sauce(browser, cb);
  },

  reporter(results, cb) {
    // totalReports += 1;
    const data = read();
    data.results.push(results);
    write(data);
    console.log(`global reporter called; have total of ${data.results.length}`);
    console.log(results);
    cb();
  },
};

module.exports = theGlobals;
<commit_msg>chore: fix file path<commit_after>const { outputBanner } = require('ci-utils');
const { tmpdir } = require('os');
const { join } = require('path');
const { readFileSync, writeFileSync } = require('fs');
const sauce = require('./scripts/nightwatch-sauce');

const dataFilePath = join(tmpdir(), 'nightwatch-results.json');
const read = () => JSON.parse(readFileSync(dataFilePath, 'utf8'));
const write = data => writeFileSync(dataFilePath, JSON.stringify(data), 'utf8');

console.log({ dataFilePath });

write({
  testCount: 0,
  results: [],
});

const theGlobals = {
  testingUrl: 'https://boltdesignsystem.com',
  results: [],
  testCount: 0,

  afterEach(browser, cb) {
    const data = read();
    data.testCount += 1;
    write(data);
    // theGlobals.testCount += 1;
    outputBanner(`global afterEach called, testCount at ${data.testCount}`);
    sauce(browser, cb);
  },

  reporter(results, cb) {
    // totalReports += 1;
    const data = read();
    data.results.push(results);
    write(data);
    console.log(`global reporter called; have total of ${data.results.length}`);
    console.log(results);
    cb();
  },
};

module.exports = theGlobals;
<|endoftext|>


<commit_before> var process  = require("./process");
 var formulas = require("./formulas");

// ===========================================
//              Chord Recognition
// ===========================================

/** Identifies the name of a Chord object, returns an array of possible names. */
var chord = function(chord){
    var chordSize    = chord.getSize();
    var permutations = process.buildPermutations(chord);                                   // Array with all the permutations of the chord.
    var formula      = _getFormulas(chordSize);                                             // Determine a formula object to use.
    var returnArray  = [];                                                                 // Array that will be populated with possible names.
    for (var i = 0; i < permutations.length; i++){
        var current = permutations[i].toFormula().slice(0, -1);                            // Last value in formula is not relevant. (1)
        var lowest  = permutations[i].getNotes()[0].getName();                             // Lowest note, to determine root.
        for (var key in formula){                                                          // Loop through chord formulas.
            if (formula.hasOwnProperty(key)){                                              // Checks property doesn't come from prototype.
                if (process.arraysEqual(current, formula[key][0]))                         // Checks for a match.
                    returnArray.push(_determineRoot(lowest, formula[key][1]) + " " + key);  // Add lowest note and key if there is a match.
            }
        }
    }
    return returnArray;
};

// Helper for chord(). Determines formula object based on chord size.
var _getFormulas = function(chordSize){
    if      (chordSize  <  2) throw new Error("chordSize must be bigger than 1");
    else if (chordSize === 2) return formulas.twoNoteChords;
    else if (chordSize === 3) return formulas.threeNoteChords;
    else if (chordSize === 4) return formulas.fourNoteChords;
    else if (chordSize === 5) return formulas.fiveNoteChords;
};

// Helper for chords(). Determines a root note of the formula. It is used for no root chords.
var _determineRoot = function(lowest, offset){
    if (offset === 0) return lowest;
    var pool          = formulas.ET12POOL;
    var indexOfLowest = pool.indexOf(lowest);
    var indexOfRoot   = indexOfLowest + offset;
    if (indexOfRoot < 0) indexOfRoot += pool.length;
    var root          = pool[indexOfRoot];
    return root;
};

// ===========================================
//          Chord Formula Recognition
// ===========================================

// !!! everything
/*
* Identifies a chord based on its formula.
* @param formula a sequence or array of numbers.
*/
var identifyChordFormula = function(formula){
    Array.prototype.slice.call(arguments);  // Cast to array.
};

// ===========================================
//                Node Exports
// ===========================================

exports.chord = chord;

// ===========================================
//                   Notes
// ===========================================

/*
* (1) Last value is not relevant because it's the number of half steps that connect the last note with
* the first note; that value is not used to determine the chord's quality.
*/
<commit_msg>chord can now handle arrays of strings<commit_after> var process  = require("./process");
 var formulas = require("./formulas");
 var sounds   = require("./sounds");

// ===========================================
//              Chord Recognition
// ===========================================

/** Identifies the name of a Chord object, returns an array of possible names. */
var chord = function(chord){
    if      (chord instanceof sounds.Chord) return _chordObject(chord);
    else if (chord instanceof Array){
        if (typeof chord[0] === "string") return _chordStringArray(chord);
    }
};

// Helper for chord(), deals with an array of strings
var _chordStringArray = function(chord){
    var chordSize    = chord.length;
    var permutations = process.permute(chord);                                              // Array with all the permutations of the chord.
    var formula      = _getFormulas(chordSize);                                             // Determine a formula object to use.
    var returnArray  = [];                                                                  // Array that will be populated with possible names.
    for (var i = 0; i < permutations.length; i++){
        var current = process.toFormula(permutations[i]).slice(0, -1);                      // Last value in formula is not relevant. (1)
        var lowest  = permutations[i][0];                                                   // Lowest note, to determine root.
        for (var key in formula){                                                           // Loop through chord formulas.
            if (formula.hasOwnProperty(key))                                                // Checks property doesn't come from prototype.
                if (process.arraysEqual(current, formula[key][0]))                          // Checks for a match.
                    returnArray.push(_determineRoot(lowest, formula[key][1]) + " " + key);  // Add the name of the chord if a match is found.
        }
    }
    return returnArray;
};

// Helper for chord(), deals with Chord object
var _chordObject = function(chord){
    var chordSize    = chord.getSize();
    var permutations = process.buildPermutations(chord);                                    // Array with all the permutations of the chord.
    var formula      = _getFormulas(chordSize);                                             // Determine a formula object to use.
    var returnArray  = [];                                                                  // Array that will be populated with possible names.
    for (var i = 0; i < permutations.length; i++){
        var current = permutations[i].toFormula().slice(0, -1);                             // Last value in formula is not relevant. (1)
        var lowest  = permutations[i].getNotes()[0].getName();                              // Lowest note, to determine root.
        for (var key in formula){                                                           // Loop through chord formulas.
            if (formula.hasOwnProperty(key)){                                               // Checks property doesn't come from prototype.
                if (process.arraysEqual(current, formula[key][0]))                          // Checks for a match.
                    returnArray.push(_determineRoot(lowest, formula[key][1]) + " " + key);  // Add lowest note and key if there is a match.
            }
        }
    }
    return returnArray;
};

// Helper for chord(). Determines formula object based on chord size.
var _getFormulas = function(chordSize){
    if      (chordSize  <  2) throw new Error("chordSize must be bigger than 1");
    else if (chordSize === 2) return formulas.twoNoteChords;
    else if (chordSize === 3) return formulas.threeNoteChords;
    else if (chordSize === 4) return formulas.fourNoteChords;
    else if (chordSize === 5) return formulas.fiveNoteChords;
};

// Helper for chords(). Determines a root note of the formula. It is used for no root chords.
var _determineRoot = function(lowest, offset){
    if (offset === 0) return lowest;
    var pool          = formulas.ET12POOL;
    var indexOfLowest = pool.indexOf(lowest);
    var indexOfRoot   = indexOfLowest + offset;
    if (indexOfRoot < 0) indexOfRoot += pool.length;
    var root          = pool[indexOfRoot];
    return root;
};

// ===========================================
//          Chord Formula Recognition
// ===========================================

// !!! everything
/*
* Identifies a chord based on its formula.
* @param formula a sequence or array of numbers.
*/
var identifyChordFormula = function(formula){
    Array.prototype.slice.call(arguments);  // Cast to array.
};

// ===========================================
//                Node Exports
// ===========================================

exports.chord = chord;

// ===========================================
//                   Notes
// ===========================================

/*
* (1) Last value is not relevant because it's the number of half steps that connect the last note with
* the first note; that value is not used to determine the chord's quality.
*/
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Button, Form, Input, Switch, Select, Icon, Modal, Col, Row, InputNumber, AutoComplete } from 'antd';
import { safeAssign } from '../../../client/common.js';
import mockEditor from '../../../client/containers/Project/Interface/InterfaceList/mockEditor';
import constants from '../../../client/constants/variable.js'
import { httpCodes } from '../index.js'
import { connect } from 'react-redux'

import './CaseDesModal.scss'

const Option = Select.Option;
const FormItem = Form.Item;
// const RadioButton = Radio.Button;
// const RadioGroup = Radio.Group;
const formItemLayout = {
  labelCol: { span: 5 },
  wrapperCol: { span: 12 }
};
const formItemLayoutWithOutLabel = {
  wrapperCol: { span: 12, offset: 5 }
};

@connect(
  state => {
    return {
      currInterface: state.inter.curdata
    }
  }
)
@Form.create()
export default class CaseDesModal extends Component {
  static propTypes = {
    form: PropTypes.object,
    caseData: PropTypes.object,
    onCancel: PropTypes.func,
    onOk: PropTypes.func,
    isAdd: PropTypes.bool,
    visible: PropTypes.bool,
    currInterface: PropTypes.object
  }

  state = {
    headers: [],
    paramsArr: [],
    paramsForm: 'form'
  }

  constructor(props) {
    super(props);
  }

  preProcess = caseData => {
    try {
      caseData = JSON.parse(JSON.stringify(caseData))
    } catch (error) {
      console.log(error)
    }
    // caseModel
    // const a = {
    //   interface_id: { type: Number, required: true },
    //   project_id: {type: Number, required: true},
    //   ip: {type: String},
    //   ip_enable: {type: Boolean,  default: false},
    //   name: {type: String, required: true},
    //   code: {type: Number, default: 200},
    //   deplay: {type: Number,  default: 0},
    //   headers: [{
    //     name: {type: String, required: true},
    //     value: {type: String}
    //   }],
    //   params: mongoose.Schema.Types.Mixed,
    //   uid: String,
    //   up_time: Number,
    //   res_body: {type: String, required: true}
    // }
    const initCaseData = {
      ip: '',
      ip_enable: false,
      name: '',
      code: '200',
      deplay: 0,
      headers: [{name: '', value: ''}],
      paramsArr: [{name: '', value: ''}],
      params: '',
      res_body: ''
    }
    const paramsArr = caseData.params && Object.keys(caseData.params).length ? Object.keys(caseData.params).map(key => {
      return { name: key, value: caseData.params[key] }
    }).filter(item => {
      if (typeof item.value === 'object') {
        this.setState({ paramsForm: 'json' })
      }
      return typeof item.value !== 'object'
    }) : [{name: '', value: ''}];
    const headers = caseData.headers && caseData.headers.length ? caseData.headers : [{name: '', value: ''}];
    caseData.code = ''+caseData.code;
    caseData.params = JSON.stringify(caseData.params, null, 2);
    this.setState({
      headers,
      paramsArr
    })
    caseData = safeAssign(initCaseData, { ...caseData, headers, paramsArr });
    return caseData;
  }

  endProcess = caseData => {
    const headers = [];
    const params = {};
    const { paramsForm } = this.state;
    caseData.headers.forEach(item => {
      if (item.name) {
        headers.push({
          name: item.name,
          value: item.value
        })
      }
    });
    caseData.paramsArr.forEach(item => {
      if (item.name) {
        params[item.name] = item.value
      }
    })
    caseData.headers = headers;
    if (paramsForm === 'form') {
      caseData.params = params;
    } else {
      try {
        caseData.params = JSON.parse(caseData.params)
      } catch (error) {
        console.log(error)
      }
    }
    delete caseData.paramsArr;
    return caseData;
  }

  componentDidMount() {
    this.props.form.setFieldsValue(this.preProcess(this.props.caseData))
    this.shouldLoadEditor = true
  }

  componentDidUpdate() {
    if (this.shouldLoadEditor) {
      this.loadBodyEditor()
      this.loadParamsEditor()
      this.shouldLoadEditor = false
    }
  }

  componentWillReceiveProps(nextProps) {
    if (
      this.props.caseData !== nextProps.caseData ||
      this.props.visible !== nextProps.visible
    ) {
      this.props.form.setFieldsValue(this.preProcess(nextProps.caseData))
      this.shouldLoadEditor = true
    }
  }

  handleOk = () => {
    const form = this.props.form;
    form.validateFieldsAndScroll((err, values) => {
      if (!err) {
        this.props.onOk(this.endProcess(values));
      }
    })
  }

  addValues = (key) => {
    const { setFieldsValue, getFieldValue } = this.props.form;
    let values = getFieldValue(key);
    values = values.concat({ name: '', value: ''});
    this.setState({ [key]: values })
    setFieldsValue({ [key]: values })
  }
  removeValues = (key, index) => {
    const { setFieldsValue, getFieldValue } = this.props.form;
    let values = getFieldValue(key);
    values = values.filter((item, index2) => index !== index2);
    this.setState({ [key]: values })
    setFieldsValue({ [key]: values })
  }

  getParamsKey = () => {
    const { req_query, req_body_form, req_body_type } = this.props.currInterface;
    const keys = [];
    req_query.forEach(item => {
      keys.push(item.name)
    })
    if (req_body_type === 'form') {
      req_body_form.forEach(item => {
        keys.push(item.name)
      })
    }
    return keys
  }

  loadBodyEditor = () => {
    const that = this;
    const { setFieldsValue } = this.props.form;
    this.props.visible && mockEditor({
      container: 'res_body_json',
      data: that.props.caseData.res_body,
      onChange: function (d) {
        if (d.format !== true) return false;
        setFieldsValue({ res_body: d.text })
      }
    });
  }
  loadParamsEditor = () => {
    const that = this;
    const { setFieldsValue } = this.props.form;
    this.props.visible && mockEditor({
      container: 'case_modal_params',
      data: that.props.caseData.params,
      onChange: function (d) {
        if (d.format !== true) return false;
        setFieldsValue({ params: d.text })
      }
    });
  }

  jsonValidator = (rule, value, callback) => {
    try {
      JSON.parse(value)
      callback()
    } catch (error) {
      callback(new Error())
    }
  }

  render() {
    const { getFieldDecorator, getFieldValue } = this.props.form;
    const { isAdd, visible, onCancel } = this.props;
    const { headers, paramsArr, paramsForm } = this.state;

    const valuesTpl = (name, values, title) => {
      getFieldDecorator(name)
      const dataSource = name === 'headers' ? constants.HTTP_REQUEST_HEADER : this.getParamsKey();
      const display = (name === 'paramsArr' && paramsForm === 'json') ? 'none': ''
      return values.map((item, index) => (

          <FormItem
            {...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
            wrapperCol={index === 0 ? { span: 19 } : { span: 19, offset: 5 }}
            label={index ? '' : title}
          >
            <Row gutter={8}>
              <Col span={10}>
                <FormItem>
                  {getFieldDecorator(`${name}[${index}].name`, { initialValue: item.name })(
                    <AutoComplete
                      dataSource={dataSource}
                      filterOption={(inputValue, option) => option.props.children.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1}
                    />
                  )}
                </FormItem>
              </Col>
              <Col span={10}>
                <FormItem>
                  {getFieldDecorator(`${name}[${index}].value`, { initialValue: item.value })(
                    <Input />
                  )}
                </FormItem>
              </Col>
              <Col span={4}>
                {values.length > 1 ? (
                  <Icon
                    className="dynamic-delete-button"
                    type="minus-circle-o"
                    onClick={() => this.removeValues(name, index)}
                  />
                ) : null}
              </Col>
            </Row>
          </FormItem>

      ))
    }
    getFieldDecorator('params')

    return (
      <Modal
        title={isAdd ? '添加期望' : '编辑期望'}
        visible={visible}
        maskClosable={false}
        onOk={this.handleOk}
        width={780}
        onCancel={() => onCancel()}
        afterClose={() => this.setState({paramsForm: 'form'})}
        className="case-des-modal"
      >
        <Form>
          <FormItem
            {...formItemLayout}
            label="期望名称"
          >
            {getFieldDecorator('name', {
              rules: [{ required: true, message: '请输入期望名称！' }]
            })(
              <Input placeholder="请输入期望名称" />
            )}
          </FormItem>

请求

          <FormItem {...formItemLayout} label="IP 过滤" className="ip-filter">
            <Col span={6} className="ip-switch">
              <FormItem>
                {getFieldDecorator('ip_enable', {
                  valuePropName: 'checked',
                  rules: [{ type: 'boolean' }]
                })(
                  <Switch />
                )}
              </FormItem>
            </Col>
            <Col span={18}>

                <FormItem>
                  {getFieldDecorator('ip', getFieldValue('ip_enable') ? {
                    rules: [{ pattern: constants.IP_REGEXP, message: '请填写正确的 IP 地址', required: true }]
                  } : {})(
                    <Input placeholder="请输入过滤的 IP 地址" />
                  )}
                </FormItem>

            </Col>
          </FormItem>

            <FormItem {...formItemLayoutWithOutLabel}>
              <Switch
                checkedChildren="JSON"
                unCheckedChildren="JSON"
                checked={paramsForm === 'json'}
                onChange={bool => {
                  this.setState({ paramsForm: bool ? 'json' : 'form' }, () => {
                    if (paramsForm === 'json') {
                      this.loadParamsEditor()
                    }
                  })
                }}
              />
              {
              // <RadioGroup
              //   value={paramsForm}
              //   size="small"
              //   onChange={e => this.setState({ paramsForm: e.target.value })}
              // >
              //   <RadioButton value="form">Form</RadioButton>
              //   <RadioButton value="json">JSON</RadioButton>
              // </RadioGroup>
              }
            </FormItem>

          {
            valuesTpl('paramsArr', paramsArr, '参数过滤')
          }
          <FormItem wrapperCol={{ span: 6, offset: 5 }} style={{display: paramsForm === 'form' ? '': 'none'}}>
            <Button size="default" type="primary" onClick={() => this.addValues('paramsArr')} style={{ width: '100%' }}>
              <Icon type="plus" /> 添加参数
            </Button>
          </FormItem>
          <FormItem {...formItemLayout} wrapperCol={{ span: 17 }} label="参数过滤" style={{display: paramsForm === 'form' ? 'none': ''}}>

            <FormItem
              {...formItemLayoutWithOutLabel}
            >
              {getFieldDecorator('params', {
                rules: [{ validator: this.jsonValidator, message: '请输入正确的 JSON！' }]
              })(
                <Input style={{display: 'none'}} />
              )}
            </FormItem>
          </FormItem>

响应

          <FormItem
            {...formItemLayout}
            required
            label="HTTP Code"
          >
            {getFieldDecorator('code')(
              <Select showSearch>
                {
                  httpCodes.map(code => <Option key={''+code} value={''+code}>{''+code}</Option>)
                }
              </Select>
            )}
          </FormItem>
          <FormItem
            {...formItemLayout}
            label="延时"
          >
            {getFieldDecorator('deplay', {
              initialValue: 0,
              rules: [{ required: true, message: '请输入延时时间！', type: 'integer' }]
            })(
              <InputNumber placeholder="请输入延时时间" min={0}/>
            )}
            ms
          </FormItem>
          {
            valuesTpl('headers', headers, 'HTTP 头')
          }
          <FormItem wrapperCol={{ span: 6, offset: 5 }}>
            <Button size="default" type="primary" onClick={() => this.addValues('headers')} style={{ width: '100%' }}>
              <Icon type="plus" /> 添加 HTTP 头
            </Button>
          </FormItem>
          <FormItem {...formItemLayout} wrapperCol={{ span: 17 }} label="返回 JSON" required>

            <FormItem
              {...formItemLayoutWithOutLabel}
            >
              {getFieldDecorator('res_body', {
                rules: [{ validator: this.jsonValidator, message: '请输入正确的返回 JSON！' }]
              })(
                <Input style={{display: 'none'}} />
              )}
            </FormItem>
          </FormItem>
        </Form>
      </Modal>
    )
  }
}
<commit_msg>fix: form 格式不验证 params json<commit_after>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Button, Form, Input, Switch, Select, Icon, Modal, Col, Row, InputNumber, AutoComplete } from 'antd';
import { safeAssign } from '../../../client/common.js';
import mockEditor from '../../../client/containers/Project/Interface/InterfaceList/mockEditor';
import constants from '../../../client/constants/variable.js'
import { httpCodes } from '../index.js'
import { connect } from 'react-redux'

import './CaseDesModal.scss'

const Option = Select.Option;
const FormItem = Form.Item;
// const RadioButton = Radio.Button;
// const RadioGroup = Radio.Group;
const formItemLayout = {
  labelCol: { span: 5 },
  wrapperCol: { span: 12 }
};
const formItemLayoutWithOutLabel = {
  wrapperCol: { span: 12, offset: 5 }
};

@connect(
  state => {
    return {
      currInterface: state.inter.curdata
    }
  }
)
@Form.create()
export default class CaseDesModal extends Component {
  static propTypes = {
    form: PropTypes.object,
    caseData: PropTypes.object,
    onCancel: PropTypes.func,
    onOk: PropTypes.func,
    isAdd: PropTypes.bool,
    visible: PropTypes.bool,
    currInterface: PropTypes.object
  }

  state = {
    headers: [],
    paramsArr: [],
    paramsForm: 'form'
  }

  constructor(props) {
    super(props);
  }

  preProcess = caseData => {
    try {
      caseData = JSON.parse(JSON.stringify(caseData))
    } catch (error) {
      console.log(error)
    }
    // caseModel
    // const a = {
    //   interface_id: { type: Number, required: true },
    //   project_id: {type: Number, required: true},
    //   ip: {type: String},
    //   ip_enable: {type: Boolean,  default: false},
    //   name: {type: String, required: true},
    //   code: {type: Number, default: 200},
    //   deplay: {type: Number,  default: 0},
    //   headers: [{
    //     name: {type: String, required: true},
    //     value: {type: String}
    //   }],
    //   params: mongoose.Schema.Types.Mixed,
    //   uid: String,
    //   up_time: Number,
    //   res_body: {type: String, required: true}
    // }
    const initCaseData = {
      ip: '',
      ip_enable: false,
      name: '',
      code: '200',
      deplay: 0,
      headers: [{name: '', value: ''}],
      paramsArr: [{name: '', value: ''}],
      params: '',
      res_body: ''
    }
    const paramsArr = caseData.params && Object.keys(caseData.params).length ? Object.keys(caseData.params).map(key => {
      return { name: key, value: caseData.params[key] }
    }).filter(item => {
      if (typeof item.value === 'object') {
        this.setState({ paramsForm: 'json' })
      }
      return typeof item.value !== 'object'
    }) : [{name: '', value: ''}];
    const headers = caseData.headers && caseData.headers.length ? caseData.headers : [{name: '', value: ''}];
    caseData.code = ''+caseData.code;
    caseData.params = JSON.stringify(caseData.params, null, 2);
    this.setState({
      headers,
      paramsArr
    })
    caseData = safeAssign(initCaseData, { ...caseData, headers, paramsArr });
    return caseData;
  }

  endProcess = caseData => {
    const headers = [];
    const params = {};
    const { paramsForm } = this.state;
    caseData.headers.forEach(item => {
      if (item.name) {
        headers.push({
          name: item.name,
          value: item.value
        })
      }
    });
    caseData.paramsArr.forEach(item => {
      if (item.name) {
        params[item.name] = item.value
      }
    })
    caseData.headers = headers;
    if (paramsForm === 'form') {
      caseData.params = params;
    } else {
      try {
        caseData.params = JSON.parse(caseData.params)
      } catch (error) {
        console.log(error)
      }
    }
    delete caseData.paramsArr;
    return caseData;
  }

  componentDidMount() {
    this.props.form.setFieldsValue(this.preProcess(this.props.caseData))
    this.shouldLoadEditor = true
  }

  componentDidUpdate() {
    if (this.shouldLoadEditor) {
      this.loadBodyEditor()
      this.loadParamsEditor()
      this.shouldLoadEditor = false
    }
  }

  componentWillReceiveProps(nextProps) {
    if (
      this.props.caseData !== nextProps.caseData ||
      this.props.visible !== nextProps.visible
    ) {
      this.props.form.setFieldsValue(this.preProcess(nextProps.caseData))
      this.shouldLoadEditor = true
    }
  }

  handleOk = () => {
    const form = this.props.form;
    form.validateFieldsAndScroll((err, values) => {
      if (!err) {
        this.props.onOk(this.endProcess(values));
      }
    })
  }

  addValues = (key) => {
    const { setFieldsValue, getFieldValue } = this.props.form;
    let values = getFieldValue(key);
    values = values.concat({ name: '', value: ''});
    this.setState({ [key]: values })
    setFieldsValue({ [key]: values })
  }
  removeValues = (key, index) => {
    const { setFieldsValue, getFieldValue } = this.props.form;
    let values = getFieldValue(key);
    values = values.filter((item, index2) => index !== index2);
    this.setState({ [key]: values })
    setFieldsValue({ [key]: values })
  }

  getParamsKey = () => {
    let { req_query, req_body_form, req_body_type } = this.props.currInterface;
    const keys = [];

    req_query.forEach(item => {
      keys.push(item.name)
    })
    if (req_body_type === 'form') {
      req_body_form.forEach(item => {
        keys.push(item.name)
      })
    }
    return keys
  }

  loadBodyEditor = () => {
    const that = this;
    const { setFieldsValue } = this.props.form;
    this.props.visible && mockEditor({
      container: 'res_body_json',
      data: that.props.caseData.res_body,
      onChange: function (d) {
        if (d.format !== true) return false;
        setFieldsValue({ res_body: d.text })
      }
    });
  }
  loadParamsEditor = () => {
    const that = this;
    const { setFieldsValue } = this.props.form;
    this.props.visible && mockEditor({
      container: 'case_modal_params',
      data: that.props.caseData.params,
      onChange: function (d) {
        if (d.format !== true) return false;
        setFieldsValue({ params: d.text })
      }
    });
  }

  jsonValidator = (rule, value, callback) => {
    try {
      JSON.parse(value)
      callback()
    } catch (error) {
      callback(new Error())
    }
  }

  render() {
    const { getFieldDecorator, getFieldValue } = this.props.form;
    const { isAdd, visible, onCancel } = this.props;
    const { headers, paramsArr, paramsForm } = this.state;

    const valuesTpl = (name, values, title) => {
      getFieldDecorator(name)
      const dataSource = name === 'headers' ? constants.HTTP_REQUEST_HEADER : this.getParamsKey();
      const display = (name === 'paramsArr' && paramsForm === 'json') ? 'none': ''
      return values.map((item, index) => (

          <FormItem
            {...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
            wrapperCol={index === 0 ? { span: 19 } : { span: 19, offset: 5 }}
            label={index ? '' : title}
          >
            <Row gutter={8}>
              <Col span={10}>
                <FormItem>
                  {getFieldDecorator(`${name}[${index}].name`, { initialValue: item.name })(
                    <AutoComplete
                      dataSource={dataSource}
                      filterOption={(inputValue, option) => option.props.children.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1}
                    />
                  )}
                </FormItem>
              </Col>
              <Col span={10}>
                <FormItem>
                  {getFieldDecorator(`${name}[${index}].value`, { initialValue: item.value })(
                    <Input />
                  )}
                </FormItem>
              </Col>
              <Col span={4}>
                {values.length > 1 ? (
                  <Icon
                    className="dynamic-delete-button"
                    type="minus-circle-o"
                    onClick={() => this.removeValues(name, index)}
                  />
                ) : null}
              </Col>
            </Row>
          </FormItem>

      ))
    }
    getFieldDecorator('params')

    return (
      <Modal
        title={isAdd ? '添加期望' : '编辑期望'}
        visible={visible}
        maskClosable={false}
        onOk={this.handleOk}
        width={780}
        onCancel={() => onCancel()}
        afterClose={() => this.setState({paramsForm: 'form'})}
        className="case-des-modal"
      >
        <Form>
          <FormItem
            {...formItemLayout}
            label="期望名称"
          >
            {getFieldDecorator('name', {
              rules: [{ required: true, message: '请输入期望名称！' }]
            })(
              <Input placeholder="请输入期望名称" />
            )}
          </FormItem>

请求

          <FormItem {...formItemLayout} label="IP 过滤" className="ip-filter">
            <Col span={6} className="ip-switch">
              <FormItem>
                {getFieldDecorator('ip_enable', {
                  valuePropName: 'checked',
                  rules: [{ type: 'boolean' }]
                })(
                  <Switch />
                )}
              </FormItem>
            </Col>
            <Col span={18}>

                <FormItem>
                  {getFieldDecorator('ip', getFieldValue('ip_enable') ? {
                    rules: [{ pattern: constants.IP_REGEXP, message: '请填写正确的 IP 地址', required: true }]
                  } : {})(
                    <Input placeholder="请输入过滤的 IP 地址" />
                  )}
                </FormItem>

            </Col>
          </FormItem>

            <FormItem {...formItemLayoutWithOutLabel}>
              <Switch
                checkedChildren="JSON"
                unCheckedChildren="JSON"
                checked={paramsForm === 'json'}
                onChange={bool => {
                  this.setState({ paramsForm: bool ? 'json' : 'form' }, () => {
                    if (paramsForm === 'json') {
                      this.loadParamsEditor()
                    }
                  })
                }}
              />
              {
              // <RadioGroup
              //   value={paramsForm}
              //   size="small"
              //   onChange={e => this.setState({ paramsForm: e.target.value })}
              // >
              //   <RadioButton value="form">Form</RadioButton>
              //   <RadioButton value="json">JSON</RadioButton>
              // </RadioGroup>
              }
            </FormItem>

          {
            valuesTpl('paramsArr', paramsArr, '参数过滤')
          }
          <FormItem wrapperCol={{ span: 6, offset: 5 }} style={{display: paramsForm === 'form' ? '': 'none'}}>
            <Button size="default" type="primary" onClick={() => this.addValues('paramsArr')} style={{ width: '100%' }}>
              <Icon type="plus" /> 添加参数
            </Button>
          </FormItem>
          <FormItem {...formItemLayout} wrapperCol={{ span: 17 }} label="参数过滤" style={{display: paramsForm === 'form' ? 'none': ''}}>

            <FormItem
              {...formItemLayoutWithOutLabel}
            >
              {getFieldDecorator('params', paramsForm === 'json' ? {
                rules: [{ validator: this.jsonValidator, message: '请输入正确的 JSON！' }]
              } : {})(
                <Input style={{display: 'none'}} />
              )}
            </FormItem>
          </FormItem>

响应

          <FormItem
            {...formItemLayout}
            required
            label="HTTP Code"
          >
            {getFieldDecorator('code')(
              <Select showSearch>
                {
                  httpCodes.map(code => <Option key={''+code} value={''+code}>{''+code}</Option>)
                }
              </Select>
            )}
          </FormItem>
          <FormItem
            {...formItemLayout}
            label="延时"
          >
            {getFieldDecorator('deplay', {
              initialValue: 0,
              rules: [{ required: true, message: '请输入延时时间！', type: 'integer' }]
            })(
              <InputNumber placeholder="请输入延时时间" min={0}/>
            )}
            ms
          </FormItem>
          {
            valuesTpl('headers', headers, 'HTTP 头')
          }
          <FormItem wrapperCol={{ span: 6, offset: 5 }}>
            <Button size="default" type="primary" onClick={() => this.addValues('headers')} style={{ width: '100%' }}>
              <Icon type="plus" /> 添加 HTTP 头
            </Button>
          </FormItem>
          <FormItem {...formItemLayout} wrapperCol={{ span: 17 }} label="返回 JSON" required>

            <FormItem
              {...formItemLayoutWithOutLabel}
            >
              {getFieldDecorator('res_body', {
                rules: [{ validator: this.jsonValidator, message: '请输入正确的返回 JSON！' }]
              })(
                <Input style={{display: 'none'}} />
              )}
            </FormItem>
          </FormItem>
        </Form>
      </Modal>
    )
  }
}
<|endoftext|>


<commit_before>// index.db.js

;

window.indexedDB = window.indexedDB ||
                   window.mozIndexedDB ||
                   window.webkitIndexedDB ||
                   window.msIndexedDB;

window.IDBTransaction = window.IDBTransaction ||
                   window.webkitIDBTransaction ||
                   window.msIDBTransaction;

window.IDBKeyRange = window.IDBKeyRange ||
                   window.webkitIDBKeyRange ||
                   window.msIDBKeyRange;

(function(window){

    'use strict';

    var db = {

        version: 3, // important: only use whole numbers!

        objectStoreName: 'tasks',

        instance: {},

        upgrade: function (e) {

            var
                _db = e.target.result,
                names = _db.objectStoreNames,
                name = db.objectStoreName;

            if (!names.contains(name)) {

                _db.createObjectStore(
                    db.objectStoreName,
                    {
                        keyPath: 'id',
                        autoIncrement: true
                    });
            }
        },

        errorHandler: function (error) {
            alert('error: ' + error.target.code);
            debugger;
        },

        getObjectStore: function (mode) {

            var
                mode = mode || 'readonly',
                txn, store;

            txn = db.instance.transaction(
                [db.objectStoreName], mode);

            store = txn.objectStore(
                db.objectStoreName);

            return store;
        },

        open: function (callback) {

            var request = window.indexedDB.open(
                db.objectStoreName, db.version);

            request.onerror = db.errorHandler;

            request.onupgradeneeded = db.upgrade;

            request.onsuccess = function (e) {

                db.instance = request.result;

                db.instance.onerror =
                    db.errorHandler;

                callback();
            };
        },

        save: function (note, callback) {

            db.open(function () {

                var store, request,
                    mode = 'readwrite';

                store = db.getObjectStore(mode),

                request = note.id ?
                    store.put(note) :
                    store.add(note);

                request.onsuccess = callback;
            });
        },

        'delete': function (id, callback) {

            id = parseInt(id);

            db.open(function () {

                var
                    mode = 'readwrite',
                    store, request;

                store = db.getObjectStore(mode);

                request = store.delete(id);

                request.onsuccess = callback;
            });
        },

        getAll: function (callback) {

            db.open(function () {

                var
                    store = db.getObjectStore(),
                    cursor = store.openCursor(),
                    data = [];

                cursor.onsuccess = function (e) {

                    var result = e.target.result;

                    if (result &&
                        result !== null) {

                        data.push(result.value);
                        result.continue();

                    } else {

                        callback(data);
                    }
                };

            });
        },

        get: function (id, callback) {

            id = parseInt(id);

            db.open(function () {

                var
                    store = db.getObjectStore(),
                    request = store.get(id);

                request.onsuccess = function (e){
                    callback(e.target.result);
                };
            });
        },

        deleteAll: function (callback) {

            db.open(function () {

                var mode, store, request;

                mode = 'readwrite';
                store = db.getObjectStore(mode);
                request = store.clear();

                request.onsuccess = callback;
            });

        }
    };

    window.app = window.app || {};
    window.app.db = db;

}(window));<commit_msg>fixed variable redefinition<commit_after>// index.db.js

;

window.indexedDB = window.indexedDB ||
                   window.mozIndexedDB ||
                   window.webkitIndexedDB ||
                   window.msIndexedDB;

window.IDBTransaction = window.IDBTransaction ||
                   window.webkitIDBTransaction ||
                   window.msIDBTransaction;

window.IDBKeyRange = window.IDBKeyRange ||
                   window.webkitIDBKeyRange ||
                   window.msIDBKeyRange;

(function(window){

    'use strict';

    var db = {

        version: 3, // important: only use whole numbers!

        objectStoreName: 'tasks',

        instance: {},

        upgrade: function (e) {

            var
                _db = e.target.result,
                names = _db.objectStoreNames,
                name = db.objectStoreName;

            if (!names.contains(name)) {

                _db.createObjectStore(
                    db.objectStoreName,
                    {
                        keyPath: 'id',
                        autoIncrement: true
                    });
            }
        },

        errorHandler: function (error) {
            alert('error: ' + error.target.code);
            debugger;
        },

        getObjectStore: function (mode) {

            var txn, store;

            mode = mode || 'readonly';

            txn = db.instance.transaction(
                [db.objectStoreName], mode);

            store = txn.objectStore(
                db.objectStoreName);

            return store;
        },

        open: function (callback) {

            var request = window.indexedDB.open(
                db.objectStoreName, db.version);

            request.onerror = db.errorHandler;

            request.onupgradeneeded = db.upgrade;

            request.onsuccess = function (e) {

                db.instance = request.result;

                db.instance.onerror =
                    db.errorHandler;

                callback();
            };
        },

        save: function (note, callback) {

            db.open(function () {

                var store, request,
                    mode = 'readwrite';

                store = db.getObjectStore(mode),

                request = note.id ?
                    store.put(note) :
                    store.add(note);

                request.onsuccess = callback;
            });
        },

        'delete': function (id, callback) {

            id = parseInt(id);

            db.open(function () {

                var
                    mode = 'readwrite',
                    store, request;

                store = db.getObjectStore(mode);

                request = store.delete(id);

                request.onsuccess = callback;
            });
        },

        getAll: function (callback) {

            db.open(function () {

                var
                    store = db.getObjectStore(),
                    cursor = store.openCursor(),
                    data = [];

                cursor.onsuccess = function (e) {

                    var result = e.target.result;

                    if (result &&
                        result !== null) {

                        data.push(result.value);
                        result.continue();

                    } else {

                        callback(data);
                    }
                };

            });
        },

        get: function (id, callback) {

            id = parseInt(id);

            db.open(function () {

                var
                    store = db.getObjectStore(),
                    request = store.get(id);

                request.onsuccess = function (e){
                    callback(e.target.result);
                };
            });
        },

        deleteAll: function (callback) {

            db.open(function () {

                var mode, store, request;

                mode = 'readwrite';
                store = db.getObjectStore(mode);
                request = store.clear();

                request.onsuccess = callback;
            });

        }
    };

    window.app = window.app || {};
    window.app.db = db;

}(window));<|endoftext|>


<commit_before>/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4,
maxerr: 50, node: true */
/*global */

(function () {
    "use strict";

    var childProcess = require("child_process"),
        spawn = childProcess.spawn;

    var DOMAIN = "rubocop";

    /**
     * @private
     * Handler function for the rubocop.lint command.
     * @return
     */
    function lint() {
        var proc,
            command = "rubocop",
            error = "",
            output = "foo";

        proc = spawn("rubocop", ['-h']);
//
//        proc.stdout.on("data", function (data) {
//            output += data;
//        });
//
//        // append errors to output instead, so that we get all of the content back in
//        // the output field in the UI.  We don't really need to distinguish.
//        proc.stderr.on("data", function (err) {
//            output += err;
//        });
//
//        proc.on("close", function (code) {
//            output += "\n" + command + " completed with exit code " + code;
//        });

        return output;
    }

    /**
     * Initializes the test domain with several test commands.
     * @param {DomainManager} DomainManager The DomainManager for the server
     */
    function init(DomainManager) {
        if (!DomainManager.hasDomain(DOMAIN)) {
            DomainManager.registerDomain(DOMAIN, {major: 0, minor: 1});
        }
        DomainManager.registerCommand(
            DOMAIN,     // domain name
            "lint",     // command name
            lint,       // command handler function
            false,      // this command is synchronous
            "Lints the current file with Rubocop.",
            [],         // no parameters
            []
        );
    }

    exports.init = init;

}());<commit_msg>Use command output.<commit_after>/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4,
maxerr: 50, node: true */
/*global */

(function () {
    "use strict";

    var childProcess = require("child_process"),
        spawn = childProcess.spawn;

    var DOMAIN = "rubocop";

    /**
     * @private
     * Handler function for the rubocop.lint command.
     * @return
     */
    function lint() {
        var proc,
            command = "rubocop",
            error = "",
            output = "";

        proc = spawn("ls", ['-a']);

        proc.stdout.on("data", function (data) {
            output += data;
        });
//
//        // append errors to output instead, so that we get all of the content back in
//        // the output field in the UI.  We don't really need to distinguish.
//        proc.stderr.on("data", function (err) {
//            output += err;
//        });
//
//        proc.on("close", function (code) {
//            output += "\n" + command + " completed with exit code " + code;
//        });

        return output;
    }

    /**
     * Initializes the test domain with several test commands.
     * @param {DomainManager} DomainManager The DomainManager for the server
     */
    function init(DomainManager) {
        if (!DomainManager.hasDomain(DOMAIN)) {
            DomainManager.registerDomain(DOMAIN, {major: 0, minor: 1});
        }
        DomainManager.registerCommand(
            DOMAIN,     // domain name
            "lint",     // command name
            lint,       // command handler function
            false,      // this command is synchronous
            "Lints the current file with Rubocop.",
            [],         // no parameters
            []
        );
    }

    exports.init = init;

}());<|endoftext|>


<commit_before>import React, { cloneElement } from 'react';
import Field from '../Field';
import { FormField, FormNote } from 'elemental';
import { Button } from '../../../admin/client/App/elemental';
import Lightbox from 'react-images';
import cloudinaryResize from '../../../admin/client/utils/cloudinaryResize';
import Thumbnail from './CloudinaryImagesThumbnail';
import HiddenFileInput from '../../components/HiddenFileInput';
import FileChangeMessage from '../../components/FileChangeMessage';

const SUPPORTED_TYPES = ['image/*', 'application/pdf', 'application/postscript'];
const SUPPORTED_REGEX = new RegExp(/^image\/|application\/pdf|application\/postscript/g);
const RESIZE_DEFAULTS = {
	crop: 'fit',
	format: 'jpg',
};

module.exports = Field.create({
	displayName: 'CloudinaryImagesField',
	statics: {
		type: 'CloudinaryImages',
	},
	getInitialState () {
		const thumbnails = [];

		this.props.value.forEach((item) => {
			this.pushThumbnail({
				imageSourceSmall: cloudinaryResize(item.public_id, {
					...RESIZE_DEFAULTS,
					height: 90,
				}),
				imageSourceLarge: cloudinaryResize(item.public_id, {
					...RESIZE_DEFAULTS,
					height: 600,
					width: 900,
				}),
			}, thumbnails);
		});

		return { thumbnails: thumbnails };
	},

	// ==============================
	// HELPERS
	// ==============================

	triggerFileBrowser () {
		this.refs.fileInput.clickDomNode();
	},
	hasFiles () {
		return this.refs.fileInput && this.refs.fileInput.hasValue();
	},
	openLightbox (event, index) {
		event.preventDefault();
		this.setState({
			lightboxIsVisible: true,
			lightboxImageIndex: index,
		});
	},
	closeLightbox () {
		this.setState({
			lightboxIsVisible: false,
			lightboxImageIndex: null,
		});
	},
	lightboxPrevious () {
		this.setState({
			lightboxImageIndex: this.state.lightboxImageIndex - 1,
		});
	},
	lightboxNext () {
		this.setState({
			lightboxImageIndex: this.state.lightboxImageIndex + 1,
		});
	},

	// ==============================
	// METHODS
	// ==============================

	removeThumbnail (i) {
		const newThumbnails = this.state.thumbnails;
		const target = newThumbnails[i];

		// if the image is just in the queue (and not uploaded yet)
		// remove it from the array
		if (target.props.isQueued) {
			newThumbnails.splice(i, 1);

		// React > 0.14 the props object is frozen
		// https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html#breaking-changes
		// Using splice + clone to toggle target thumb props
		} else {
			newThumbnails.splice(i, 1, cloneElement(target, {
				isDeleted: !target.props.isDeleted,
			}));
		}

		this.setState({ thumbnails: newThumbnails });
	},
	pushThumbnail (args, thumbs = this.state.thumbnails) {
		const i = thumbs.length;

		thumbs.push(
			<Thumbnail
				key={i}
				openLightbox={(e) => this.openLightbox(e, i)}
				shouldRenderActionButton={this.shouldRenderField()}
				toggleDelete={this.removeThumbnail.bind(this, i)}
				{...args}
			/>
		);
	},
	getCount (key) {
		var count = 0;

		this.state.thumbnails.forEach((thumb) => {
			if (thumb && thumb.props[key]) count++;
		});

		return count;
	},
	clearFiles () {
		this.refs.fileInput.clearValue();

		this.setState({
			thumbnails: this.state.thumbnails.filter(function (thumb) {
				return !thumb.props.isQueued;
			}),
		});
	},
	uploadFile (event) {
		if (!window.FileReader) {
			return alert('File reader not supported by browser.');
		}
		const files = event.target.files;

		// FileList not a real Array; forEach not supported
		for (let i = 0; i < files.length; i++) {
			const reader = new FileReader();
			const f = files[i];

			if (!f.type.match(SUPPORTED_REGEX)) {
				return alert('Unsupported file type. Supported formats are: GIF, PNG, JPG, BMP, ICO, PDF, TIFF, EPS, PSD, SVG');
			}

			// TODO use `onloadstart` + `onloadend` to display a loading indicator
			// for each thumbnail when applicable

			reader.readAsDataURL(f);
			reader.onload = (e) => {
				this.pushThumbnail({
					isQueued: true,
					imageSourceSmall: e.target.result,
				});
				this.forceUpdate();
			};
		}
	},

	// ==============================
	// RENDERERS
	// ==============================

	renderFileField () {
		if (!this.shouldRenderField()) return null;

		return (
			<HiddenFileInput
				accept={SUPPORTED_TYPES.join()}
				ref="fileInput"
				name={this.getInputName(this.props.paths.upload)}
				multiple
				onChange={this.uploadFile}
			/>
		);
	},
	renderLightbox () {
		const { value } = this.props;
		if (!value || !value.length) return;

		const images = value.map(image => ({
			src: cloudinaryResize(image.public_id, {
				...RESIZE_DEFAULTS,
				height: 600,
				width: 900,
			}),
		}));

		return (
			<Lightbox
				images={images}
				currentImage={this.state.lightboxImageIndex}
				isOpen={this.state.lightboxIsVisible}
				onClickPrev={this.lightboxPrevious}
				onClickNext={this.lightboxNext}
				onClose={this.closeLightbox}
			/>
		);
	},
	renderToolbar () {
		if (!this.shouldRenderField()) return null;

		const uploadCount = this.getCount('isQueued');
		const deleteCount = this.getCount('isDeleted');

		// provide a gutter for the change message
		// only required when no cancel button, which has equiv. padding
		const uploadButtonStyles = !this.hasFiles()
			? { marginRight: 10 }
			: {};

		// prepare the change message
		const changeMessage = uploadCount || deleteCount ? (
			<FileChangeMessage>
				{uploadCount && deleteCount ? `${uploadCount} added and ${deleteCount} removed` : null}
				{uploadCount && !deleteCount ? `${uploadCount} image added` : null}
				{!uploadCount && deleteCount ? `${deleteCount} image removed` : null}
			</FileChangeMessage>
		) : null;

		// prepare the save message
		const saveMessage = uploadCount || deleteCount ? (
			<FileChangeMessage color={!deleteCount ? 'success' : 'danger'}>
				Save to {!deleteCount ? 'Upload' : 'Confirm'}
			</FileChangeMessage>
		) : null;

		// clear floating images above
		const toolbarStyles = {
			clear: 'both',
		};

		return (

				<Button onClick={this.triggerFileBrowser} style={uploadButtonStyles}>
					Upload Images
				</Button>
				{this.hasFiles() && (
					<Button variant="link" color="cancel" onClick={this.clearFiles}>
						Clear selection
					</Button>
				)}
				{changeMessage}
				{saveMessage}

		);
	},
	renderFieldAction () {
		if (!this.shouldRenderField()) return null;

		var value = '';
		var remove = [];
		this.state.thumbnails.forEach((thumb) => {
			if (thumb && thumb.props.isDeleted) remove.push(thumb.props.public_id);
		});
		if (remove.length) value = 'remove:' + remove.join(',');

		return (
			<input
				className="field-action"
				name={this.getInputName(this.props.paths.action)}
				ref="action"
				type="hidden"
				value={value}
			/>
		);
	},
	renderUploadsField () {
		if (!this.shouldRenderField()) return null;

		return (
			<input
				name={this.getInputName(this.props.paths.uploads)}
				ref="uploads"
				type="hidden"
			/>
		);
	},
	renderUI () {
		const { label, note, path } = this.props;
		const { thumbnails } = this.state;

		return (
			<FormField label={label} className="field-type-cloudinaryimages" htmlFor={path}>
				{this.renderFieldAction()}
				{this.renderUploadsField()}
				{this.renderFileField()}

					{thumbnails}

				{this.renderToolbar()}
				{!!note && <FormNote note={note} />}
				{this.renderLightbox()}
			</FormField>
		);
	},
});
<commit_msg>CloudinaryImagesField: Fixing field paths<commit_after>import React, { cloneElement } from 'react';
import Field from '../Field';
import { FormField, FormNote } from 'elemental';
import { Button } from '../../../admin/client/App/elemental';
import Lightbox from 'react-images';
import cloudinaryResize from '../../../admin/client/utils/cloudinaryResize';
import Thumbnail from './CloudinaryImagesThumbnail';
import HiddenFileInput from '../../components/HiddenFileInput';
import FileChangeMessage from '../../components/FileChangeMessage';

const SUPPORTED_TYPES = ['image/*', 'application/pdf', 'application/postscript'];
const SUPPORTED_REGEX = new RegExp(/^image\/|application\/pdf|application\/postscript/g);
const RESIZE_DEFAULTS = {
	crop: 'fit',
	format: 'jpg',
};

let uploadInc = 1000;

const buildInitialState = (props) => ({
	uploadFieldPath: `CloudinaryImages-${props.path}-${++uploadInc}`,
});

module.exports = Field.create({
	displayName: 'CloudinaryImagesField',
	statics: {
		type: 'CloudinaryImages',
	},
	getInitialState () {
		const thumbnails = [];

		this.props.value.forEach((item) => {
			this.pushThumbnail({
				imageSourceSmall: cloudinaryResize(item.public_id, {
					...RESIZE_DEFAULTS,
					height: 90,
				}),
				imageSourceLarge: cloudinaryResize(item.public_id, {
					...RESIZE_DEFAULTS,
					height: 600,
					width: 900,
				}),
			}, thumbnails);
		});

		return { thumbnails: thumbnails };
	},

	// ==============================
	// HELPERS
	// ==============================

	triggerFileBrowser () {
		this.refs.fileInput.clickDomNode();
	},
	hasFiles () {
		return this.refs.fileInput && this.refs.fileInput.hasValue();
	},
	openLightbox (event, index) {
		event.preventDefault();
		this.setState({
			lightboxIsVisible: true,
			lightboxImageIndex: index,
		});
	},
	closeLightbox () {
		this.setState({
			lightboxIsVisible: false,
			lightboxImageIndex: null,
		});
	},
	lightboxPrevious () {
		this.setState({
			lightboxImageIndex: this.state.lightboxImageIndex - 1,
		});
	},
	lightboxNext () {
		this.setState({
			lightboxImageIndex: this.state.lightboxImageIndex + 1,
		});
	},

	// ==============================
	// METHODS
	// ==============================

	removeThumbnail (i) {
		const newThumbnails = this.state.thumbnails;
		const target = newThumbnails[i];

		// if the image is just in the queue (and not uploaded yet)
		// remove it from the array
		if (target.props.isQueued) {
			newThumbnails.splice(i, 1);

		// React > 0.14 the props object is frozen
		// https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html#breaking-changes
		// Using splice + clone to toggle target thumb props
		} else {
			newThumbnails.splice(i, 1, cloneElement(target, {
				isDeleted: !target.props.isDeleted,
			}));
		}

		this.setState({ thumbnails: newThumbnails });
	},
	pushThumbnail (args, thumbs = this.state.thumbnails) {
		const i = thumbs.length;

		thumbs.push(
			<Thumbnail
				key={i}
				openLightbox={(e) => this.openLightbox(e, i)}
				shouldRenderActionButton={this.shouldRenderField()}
				toggleDelete={this.removeThumbnail.bind(this, i)}
				{...args}
			/>
		);
	},
	getCount (key) {
		var count = 0;

		this.state.thumbnails.forEach((thumb) => {
			if (thumb && thumb.props[key]) count++;
		});

		return count;
	},
	clearFiles () {
		this.refs.fileInput.clearValue();

		this.setState({
			thumbnails: this.state.thumbnails.filter(function (thumb) {
				return !thumb.props.isQueued;
			}),
		});
	},
	uploadFile (event) {
		if (!window.FileReader) {
			return alert('File reader not supported by browser.');
		}
		const files = event.target.files;

		// FileList not a real Array; forEach not supported
		for (let i = 0; i < files.length; i++) {
			const reader = new FileReader();
			const f = files[i];

			if (!f.type.match(SUPPORTED_REGEX)) {
				return alert('Unsupported file type. Supported formats are: GIF, PNG, JPG, BMP, ICO, PDF, TIFF, EPS, PSD, SVG');
			}

			// TODO use `onloadstart` + `onloadend` to display a loading indicator
			// for each thumbnail when applicable

			reader.readAsDataURL(f);
			reader.onload = (e) => {
				this.pushThumbnail({
					isQueued: true,
					imageSourceSmall: e.target.result,
				});
				this.forceUpdate();
			};
		}
	},

	// ==============================
	// RENDERERS
	// ==============================

	renderFileField () {
		if (!this.shouldRenderField()) return null;

		return (
			<HiddenFileInput
				accept={SUPPORTED_TYPES.join()}
				ref="fileInput"
				name={this.state.uploadFieldPath}
				multiple
				onChange={this.uploadFile}
			/>
		);
	},
	renderLightbox () {
		const { value } = this.props;
		if (!value || !value.length) return;

		const images = value.map(image => ({
			src: cloudinaryResize(image.public_id, {
				...RESIZE_DEFAULTS,
				height: 600,
				width: 900,
			}),
		}));

		return (
			<Lightbox
				images={images}
				currentImage={this.state.lightboxImageIndex}
				isOpen={this.state.lightboxIsVisible}
				onClickPrev={this.lightboxPrevious}
				onClickNext={this.lightboxNext}
				onClose={this.closeLightbox}
			/>
		);
	},
	renderToolbar () {
		if (!this.shouldRenderField()) return null;

		const uploadCount = this.getCount('isQueued');
		const deleteCount = this.getCount('isDeleted');

		// provide a gutter for the change message
		// only required when no cancel button, which has equiv. padding
		const uploadButtonStyles = !this.hasFiles()
			? { marginRight: 10 }
			: {};

		// prepare the change message
		const changeMessage = uploadCount || deleteCount ? (
			<FileChangeMessage>
				{uploadCount && deleteCount ? `${uploadCount} added and ${deleteCount} removed` : null}
				{uploadCount && !deleteCount ? `${uploadCount} image added` : null}
				{!uploadCount && deleteCount ? `${deleteCount} image removed` : null}
			</FileChangeMessage>
		) : null;

		// prepare the save message
		const saveMessage = uploadCount || deleteCount ? (
			<FileChangeMessage color={!deleteCount ? 'success' : 'danger'}>
				Save to {!deleteCount ? 'Upload' : 'Confirm'}
			</FileChangeMessage>
		) : null;

		// clear floating images above
		const toolbarStyles = {
			clear: 'both',
		};

		return (

				<Button onClick={this.triggerFileBrowser} style={uploadButtonStyles}>
					Upload Images
				</Button>
				{this.hasFiles() && (
					<Button variant="link" color="cancel" onClick={this.clearFiles}>
						Clear selection
					</Button>
				)}
				{changeMessage}
				{saveMessage}

		);
	},
	renderUploadsField () {
		if (!this.shouldRenderField() || !this.hasFiles()) return null;

		return (
			<input
				name={this.getInputName(this.props.paths.action)}
				value={`upload:${this.state.uploadFieldPath}`}
				type="hidden"
			/>
		);
	},
	renderUI () {
		const { label, note, path } = this.props;
		const { thumbnails } = this.state;

		return (
			<FormField label={label} className="field-type-cloudinaryimages" htmlFor={path}>

					{thumbnails}

				{this.renderFileField()}
				{this.renderUploadsField()}
				{this.renderToolbar()}
				{!!note && <FormNote note={note} />}
				{this.renderLightbox()}
			</FormField>
		);
	},
});
<|endoftext|>