<commit_before>/* eslint-disable max-len */
exports.extensions = {
  supported: [
    { icon: 'actionscript', extensions: ['as'] },
    { icon: 'angular', extensions: [] },
    { icon: 'apache', extensions: ['htaccess', 'htpasswd'] },
    { icon: 'apib', extensions: ['apib'] },
    { icon: 'applescript', extensions: ['app'] },
    { icon: 'appveyor', extensions: ['appveyor.yml'], special: 'yml' },
    { icon: 'asp', extensions: ['asp'] },
    { icon: 'assembly', extensions: ['s', 'asm'] },
    { icon: 'autohotkey', extensions: ['ahk'] },
    { icon: 'babel', extensions: ['babelrc'] },
    { icon: 'binary', extensions: ['bin', 'o', 'a', 'exe', 'obj', 'lib', 'dll', 'so', 'pyc', 'pyd', 'pyo', 'n', 'ndll', 'pdb', 'cmo', 'cmx', 'cma', 'cmxa', 'cmi' ] }, // http://www.file-extensions.org/filetype/extension/name/binary-files
    { icon: 'blade', extensions: ['.blade.php'], special: 'php' },
    { icon: 'bower', extensions: ['bowerrc'] },
    { icon: 'bower', extensions: ['bower'], special: 'json' },
    { icon: 'c++', extensions: ['cpp', 'hpp', 'cc', 'cxx'] },
    { icon: 'c', extensions: ['c'] },
    { icon: 'cake', extensions: ['cake'] },
    { icon: 'cfm', extensions: ['cfm', 'cfc', 'lucee'] },
    { icon: 'cheader', extensions: ['h'] },
    { icon: 'clojure', extensions: ['clojure', 'cjm', 'clj', 'cljs', 'cljc', 'edn'] },
    { icon: 'codeclimate', extensions: ['codeclimate.yml'], special: 'yml' },
    { icon: 'coffeescript', extensions: ['coffee'] },
    { icon: 'config', extensions: ['env', 'ini', 'makefile', 'config'] },
    { icon: 'compass', extensions: [] },
    { icon: 'composer', extensions: ['composer.json'], special: 'json' },
    { icon: 'composer', extensions: ['composer.lock'], special: 'lock' },
    { icon: 'cs', extensions: ['cs'] },
    { icon: 'cshtml', extensions: ['cshtml'] },
    { icon: 'css', extensions: ['css'] },
    { icon: 'csslint', extensions: ['csslintrc'] },
    { icon: 'cucumber', extensions: ['feature'] },
    { icon: 'dartlang', extensions: ['dart'] },
    { icon: 'dlang', extensions: ['d'] },
    { icon: 'docker', extensions: ['dockerfile'] },
    { icon: 'editorconfig', extensions: ['editorconfig'] },
    { icon: 'ejs', extensions: ['ejs'] },
    { icon: 'elixir', extensions: ['ex', 'exs', 'eex'] },
    { icon: 'elm', extensions: ['elm'] },
    { icon: 'erb', extensions: ['rhtml', 'erb'] },
    { icon: 'erlang', extensions: ['erl', 'hrl', 'emakefile', 'emakerfile'] },
    { icon: 'eslint', extensions: ['eslintrc', 'eslintignore'] },
    { icon: 'eslint', extensions: ['.eslintrc.js'], special: 'js' },
    { icon: 'eslint', extensions: ['.eslintrc.json'], special: 'json' },
    { icon: 'eslint', extensions: ['.eslintrc.yaml'], special: 'yaml' },
    { icon: 'eslint', extensions: ['.eslintrc.yml'], special: 'yml' },
    { icon: 'excel', extensions: ['xls', 'xlsx', 'csv', 'ods'] },
    { icon: 'favicon', extensions: ['favicon'], special: 'ico' },
    { icon: 'font', extensions: ['woff', 'woff2', 'ttf', 'otf', 'eot', 'pfa', 'pfb', 'sfd'] },
    { icon: 'flash', extensions: ['swf', 'swc', 'sol'] },
    { icon: 'fsharp', extensions: ['fs', 'fsx', 'fsi'] },
    { icon: 'git', extensions: ['gitattributes', 'gitignore', 'gitmodules', 'gitkeep'] },
    { icon: 'go', extensions: ['go'] },
    { icon: 'gradle', extensions: ['gradle'] },
    { icon: 'graphviz', extensions: [] },
    { icon: 'groovy', extensions: ['groovy'] },
    { icon: 'gruntfile', extensions: ['gruntfile'], special: 'js' },
    { icon: 'gulpfile', extensions: ['gulpfile'], special: 'js' },
    { icon: 'haml', extensions: ['haml'] },
    { icon: 'handlebars', extensions: ['hbs', 'handlebars'] },
    { icon: 'haskell', extensions: ['has', 'hs', 'lhs', 'lit', 'gf'] },
    { icon: 'haxe', extensions: ['hx', 'hxml'] },
    { icon: 'haxe', extensions: ['haxelib'], special: 'json' },
    { icon: 'haxecheckstyle', extensions: ['checkstyle.json'], special: 'json' },
    { icon: 'haxedevelop', extensions: ['hxproj'] },
    { icon: 'html', extensions: ['htm', 'html'] },
    { icon: 'image', extensions: ['jpeg', 'jpg', 'gif', 'png', 'bmp', 'ico'] },
    { icon: 'ionic', extensions: ['ionic'], special: 'project' },
    { icon: 'ionic', extensions: ['ionic.config'], special: 'json' },
    { icon: 'jade', extensions: ['jade', 'pug'] },
    { icon: 'java', extensions: ['java'] },
    { icon: 'js', extensions: ['js'] },
    { icon: 'jshintrc', extensions: ['jshintrc'] },
    { icon: 'jsmap', extensions: ['.js.map'], special: 'map' },
    { icon: 'jsp', extensions: ['jsp'] },
    { icon: 'julia', extensions: ['jl'] },
    { icon: 'log', extensions: ['log'] },
    { icon: 'less', extensions: ['less'] },
    { icon: 'license', extensions: ['license'] },
    { icon: 'lisp', extensions: ['bil'] },
    { icon: 'lime', extensions: ['hxp'] },
    { icon: 'lime', extensions: ['include.xml'], special: 'xml' },
    { icon: 'lsl', extensions: ['lsl'] },
    { icon: 'lua', extensions: ['lua'] },
    { icon: 'm', extensions: ['m'] },
    { icon: 'markdown', extensions: ['md', 'markdown'] },
    { icon: 'marko', extensions: ['marko'] },
    { icon: 'markojs', extensions: ['.marko.js'], special: 'js' },
    { icon: 'markup', extensions: [] },
    { icon: 'matlab', extensions: ['fig', 'mat', 'mex', 'mexn', 'mexrs6', 'mn', 'mum', 'mx', 'mx3', 'rwd', 'slx', 'slddc', 'smv', 'tikz', 'xvc', 'xvc'] },
    { icon: 'mustache', extensions: ['mustache', 'mst'] },
    { icon: 'nim', extensions: ['nim', 'nims', 'cfg'] },
    { icon: 'node', extensions: ['json'] },
    { icon: 'node2', extensions: ['nvmrc'] },
    { icon: 'npm', extensions: ['npmignore'] },
    { icon: 'npm', extensions: ['package'], special: 'json' },
    { icon: 'nunjucks', extensions: ['njk', 'nunjucks', 'nunjs', 'nunj', 'njs', 'nj'] },
    { icon: 'ocaml', extensions: ['ml', 'mll', 'mli', 'mly', 'ocamlmakefile', 'merlin'] },
    { icon: 'paket', extensions: ['paket.dependencies'], special: 'dependencies' },
    { icon: 'paket', extensions: ['paket.lock'], special: 'lock' },
    { icon: 'paket', extensions: ['paket.references'], special: 'references' },
    { icon: 'paket', extensions: ['paket.template'], special: 'template' },
    { icon: 'paket', extensions: ['paket.local'], special: 'local' },
    { icon: 'patch', extensions: ['patch'] },
    { icon: 'perl', extensions: ['perl'] },
    { icon: 'poedit', extensions: ['po', 'mo'] },
    { icon: 'photoshop', extensions: ['psd'] },
    { icon: 'php', extensions: ['php', 'php1', 'php2', 'php3', 'php4', 'php5', 'php6', 'phps', 'phpsa', 'phpt', 'phtml', 'phar'] },
    { icon: 'procfile', extensions: ['procfile'] },
    { icon: 'postcss', extensions: ['pcss', 'postcss'] },
    { icon: 'powershell', extensions: ['ps1', 'psm1', 'psd1'] },
    { icon: 'puppet', extensions: ['epp'] },
    { icon: 'python', extensions: ['py', 'pyw'] },
    { icon: 'r', extensions: ['r'] },
    { icon: 'rails', extensions: [] },
    { icon: 'reactjs', extensions: ['jsx'] },
    { icon: 'reacttemplate', extensions: ['rt'] },
    { icon: 'reactts', extensions: ['tsx'] },
    { icon: 'riot', extensions: ['tag'] },
    { icon: 'robotframework', extensions: ['robot'] },
    { icon: 'ruby', extensions: ['rb', 'gemfile'] },
    { icon: 'ruby', extensions: ['gemfile'], special: 'lock' },
    { icon: 'rust', extensions: ['rs'] },
    { icon: 'sass', extensions: ['sass'] },
    { icon: 'scala', extensions: ['scala'] },
    { icon: 'scss', extensions: ['scss'] },
    { icon: 'settings', extensions: [] },
    { icon: 'shell', extensions: ['bat', 'sh', 'cmd', 'bash', 'zsh', 'fish'] },
    { icon: 'slim', extensions: [] },
    { icon: 'source', extensions: [] },
    { icon: 'sql', extensions: ['sql'] },
    { icon: 'sqlite', extensions: ['sqlite', 'db3'] },
    { icon: 'smarty', extensions: ['tpl', 'swig'] },
    { icon: 'stylelint', extensions: ['stylelintrc'] },
    { icon: 'stylelint', extensions: ['stylelint.config'], special: 'js' },
    { icon: 'stylus', extensions: ['styl'] },
    { icon: 'svg', extensions: ['svg'] },
    { icon: 'swift', extensions: ['swift'] },
    { icon: 'tcl', extensions: ['tcl'] },
    { icon: 'tex', extensions: ['texi', 'tex'] },
    { icon: 'text', extensions: ['txt'] },
    { icon: 'textile', extensions: ['textile'] },
    { icon: 'todo', extensions: ['todo'] },
    { icon: 'travis', extensions: ['travis.yml'], special: 'yml' },
    { icon: 'twig', extensions: ['twig'] },
    { icon: 'typescript', extensions: ['ts'] },
    { icon: 'typescriptdef', extensions: ['.d.ts'], special: 'ts' },
    { icon: 'volt', extensions: ['volt'] },
    { icon: 'vbhtml', extensions: ['vbhtml'] },
    { icon: 'vue', extensions: ['vue'] },
    { icon: 'vscode', extensions: ['vscodeignore', 'launch', 'jsconfig', 'tsconfig'], special: 'json' },
    { icon: 'xml', extensions: ['xml', 'axml', 'xaml'] },
    { icon: 'yaml', extensions: ['yml', 'yaml'] },
    { icon: 'zip', extensions: ['zip', 'rar', '7z', 'tar', 'gz', 'bzip2', 'xz', 'bz2'] }
  ],
  parse: function () {
    var s = this.replace(/\./g, '_');
    if ((/^\d/).test(s)) return 'n' + s;
    return s;
  }
};
<commit_msg>Use the key icon for .enc<commit_after>/* eslint-disable max-len */
exports.extensions = {
  supported: [
    { icon: 'actionscript', extensions: ['as'] },
    { icon: 'angular', extensions: [] },
    { icon: 'apache', extensions: ['htaccess', 'htpasswd'] },
    { icon: 'apib', extensions: ['apib'] },
    { icon: 'applescript', extensions: ['app'] },
    { icon: 'appveyor', extensions: ['appveyor.yml'], special: 'yml' },
    { icon: 'asp', extensions: ['asp'] },
    { icon: 'assembly', extensions: ['s', 'asm'] },
    { icon: 'autohotkey', extensions: ['ahk'] },
    { icon: 'babel', extensions: ['babelrc'] },
    { icon: 'binary', extensions: ['bin', 'o', 'a', 'exe', 'obj', 'lib', 'dll', 'so', 'pyc', 'pyd', 'pyo', 'n', 'ndll', 'pdb', 'cmo', 'cmx', 'cma', 'cmxa', 'cmi' ] }, // http://www.file-extensions.org/filetype/extension/name/binary-files
    { icon: 'blade', extensions: ['.blade.php'], special: 'php' },
    { icon: 'bower', extensions: ['bowerrc'] },
    { icon: 'bower', extensions: ['bower'], special: 'json' },
    { icon: 'c++', extensions: ['cpp', 'hpp', 'cc', 'cxx'] },
    { icon: 'c', extensions: ['c'] },
    { icon: 'cake', extensions: ['cake'] },
    { icon: 'cfm', extensions: ['cfm', 'cfc', 'lucee'] },
    { icon: 'cheader', extensions: ['h'] },
    { icon: 'clojure', extensions: ['clojure', 'cjm', 'clj', 'cljs', 'cljc', 'edn'] },
    { icon: 'codeclimate', extensions: ['codeclimate.yml'], special: 'yml' },
    { icon: 'coffeescript', extensions: ['coffee'] },
    { icon: 'config', extensions: ['env', 'ini', 'makefile', 'config'] },
    { icon: 'compass', extensions: [] },
    { icon: 'composer', extensions: ['composer.json'], special: 'json' },
    { icon: 'composer', extensions: ['composer.lock'], special: 'lock' },
    { icon: 'cs', extensions: ['cs'] },
    { icon: 'cshtml', extensions: ['cshtml'] },
    { icon: 'css', extensions: ['css'] },
    { icon: 'csslint', extensions: ['csslintrc'] },
    { icon: 'cucumber', extensions: ['feature'] },
    { icon: 'dartlang', extensions: ['dart'] },
    { icon: 'dlang', extensions: ['d'] },
    { icon: 'docker', extensions: ['dockerfile'] },
    { icon: 'editorconfig', extensions: ['editorconfig'] },
    { icon: 'ejs', extensions: ['ejs'] },
    { icon: 'elixir', extensions: ['ex', 'exs', 'eex'] },
    { icon: 'elm', extensions: ['elm'] },
    { icon: 'erb', extensions: ['rhtml', 'erb'] },
    { icon: 'erlang', extensions: ['erl', 'hrl', 'emakefile', 'emakerfile'] },
    { icon: 'eslint', extensions: ['eslintrc', 'eslintignore'] },
    { icon: 'eslint', extensions: ['.eslintrc.js'], special: 'js' },
    { icon: 'eslint', extensions: ['.eslintrc.json'], special: 'json' },
    { icon: 'eslint', extensions: ['.eslintrc.yaml'], special: 'yaml' },
    { icon: 'eslint', extensions: ['.eslintrc.yml'], special: 'yml' },
    { icon: 'excel', extensions: ['xls', 'xlsx', 'csv', 'ods'] },
    { icon: 'favicon', extensions: ['favicon'], special: 'ico' },
    { icon: 'font', extensions: ['woff', 'woff2', 'ttf', 'otf', 'eot', 'pfa', 'pfb', 'sfd'] },
    { icon: 'flash', extensions: ['swf', 'swc', 'sol'] },
    { icon: 'fsharp', extensions: ['fs', 'fsx', 'fsi'] },
    { icon: 'git', extensions: ['gitattributes', 'gitignore', 'gitmodules', 'gitkeep'] },
    { icon: 'go', extensions: ['go'] },
    { icon: 'gradle', extensions: ['gradle'] },
    { icon: 'graphviz', extensions: [] },
    { icon: 'groovy', extensions: ['groovy'] },
    { icon: 'gruntfile', extensions: ['gruntfile'], special: 'js' },
    { icon: 'gulpfile', extensions: ['gulpfile'], special: 'js' },
    { icon: 'haml', extensions: ['haml'] },
    { icon: 'handlebars', extensions: ['hbs', 'handlebars'] },
    { icon: 'haskell', extensions: ['has', 'hs', 'lhs', 'lit', 'gf'] },
    { icon: 'haxe', extensions: ['hx', 'hxml'] },
    { icon: 'haxe', extensions: ['haxelib'], special: 'json' },
    { icon: 'haxecheckstyle', extensions: ['checkstyle.json'], special: 'json' },
    { icon: 'haxedevelop', extensions: ['hxproj'] },
    { icon: 'html', extensions: ['htm', 'html'] },
    { icon: 'image', extensions: ['jpeg', 'jpg', 'gif', 'png', 'bmp', 'ico'] },
    { icon: 'ionic', extensions: ['ionic'], special: 'project' },
    { icon: 'ionic', extensions: ['ionic.config'], special: 'json' },
    { icon: 'jade', extensions: ['jade', 'pug'] },
    { icon: 'java', extensions: ['java'] },
    { icon: 'js', extensions: ['js'] },
    { icon: 'jshintrc', extensions: ['jshintrc'] },
    { icon: 'jsmap', extensions: ['.js.map'], special: 'map' },
    { icon: 'jsp', extensions: ['jsp'] },
    { icon: 'julia', extensions: ['jl'] },
    { icon: 'log', extensions: ['log'] },
    { icon: 'less', extensions: ['less'] },
    { icon: 'license', extensions: ['license', 'enc'] },
    { icon: 'lisp', extensions: ['bil'] },
    { icon: 'lime', extensions: ['hxp'] },
    { icon: 'lime', extensions: ['include.xml'], special: 'xml' },
    { icon: 'lsl', extensions: ['lsl'] },
    { icon: 'lua', extensions: ['lua'] },
    { icon: 'm', extensions: ['m'] },
    { icon: 'markdown', extensions: ['md', 'markdown'] },
    { icon: 'marko', extensions: ['marko'] },
    { icon: 'markojs', extensions: ['.marko.js'], special: 'js' },
    { icon: 'markup', extensions: [] },
    { icon: 'matlab', extensions: ['fig', 'mat', 'mex', 'mexn', 'mexrs6', 'mn', 'mum', 'mx', 'mx3', 'rwd', 'slx', 'slddc', 'smv', 'tikz', 'xvc', 'xvc'] },
    { icon: 'mustache', extensions: ['mustache', 'mst'] },
    { icon: 'nim', extensions: ['nim', 'nims', 'cfg'] },
    { icon: 'node', extensions: ['json'] },
    { icon: 'node2', extensions: ['nvmrc'] },
    { icon: 'npm', extensions: ['npmignore'] },
    { icon: 'npm', extensions: ['package'], special: 'json' },
    { icon: 'nunjucks', extensions: ['njk', 'nunjucks', 'nunjs', 'nunj', 'njs', 'nj'] },
    { icon: 'ocaml', extensions: ['ml', 'mll', 'mli', 'mly', 'ocamlmakefile', 'merlin'] },
    { icon: 'paket', extensions: ['paket.dependencies'], special: 'dependencies' },
    { icon: 'paket', extensions: ['paket.lock'], special: 'lock' },
    { icon: 'paket', extensions: ['paket.references'], special: 'references' },
    { icon: 'paket', extensions: ['paket.template'], special: 'template' },
    { icon: 'paket', extensions: ['paket.local'], special: 'local' },
    { icon: 'patch', extensions: ['patch'] },
    { icon: 'perl', extensions: ['perl'] },
    { icon: 'poedit', extensions: ['po', 'mo'] },
    { icon: 'photoshop', extensions: ['psd'] },
    { icon: 'php', extensions: ['php', 'php1', 'php2', 'php3', 'php4', 'php5', 'php6', 'phps', 'phpsa', 'phpt', 'phtml', 'phar'] },
    { icon: 'procfile', extensions: ['procfile'] },
    { icon: 'postcss', extensions: ['pcss', 'postcss'] },
    { icon: 'powershell', extensions: ['ps1', 'psm1', 'psd1'] },
    { icon: 'puppet', extensions: ['epp'] },
    { icon: 'python', extensions: ['py', 'pyw'] },
    { icon: 'r', extensions: ['r'] },
    { icon: 'rails', extensions: [] },
    { icon: 'reactjs', extensions: ['jsx'] },
    { icon: 'reacttemplate', extensions: ['rt'] },
    { icon: 'reactts', extensions: ['tsx'] },
    { icon: 'riot', extensions: ['tag'] },
    { icon: 'robotframework', extensions: ['robot'] },
    { icon: 'ruby', extensions: ['rb', 'gemfile'] },
    { icon: 'ruby', extensions: ['gemfile'], special: 'lock' },
    { icon: 'rust', extensions: ['rs'] },
    { icon: 'sass', extensions: ['sass'] },
    { icon: 'scala', extensions: ['scala'] },
    { icon: 'scss', extensions: ['scss'] },
    { icon: 'settings', extensions: [] },
    { icon: 'shell', extensions: ['bat', 'sh', 'cmd', 'bash', 'zsh', 'fish'] },
    { icon: 'slim', extensions: [] },
    { icon: 'source', extensions: [] },
    { icon: 'sql', extensions: ['sql'] },
    { icon: 'sqlite', extensions: ['sqlite', 'db3'] },
    { icon: 'smarty', extensions: ['tpl', 'swig'] },
    { icon: 'stylelint', extensions: ['stylelintrc'] },
    { icon: 'stylelint', extensions: ['stylelint.config'], special: 'js' },
    { icon: 'stylus', extensions: ['styl'] },
    { icon: 'svg', extensions: ['svg'] },
    { icon: 'swift', extensions: ['swift'] },
    { icon: 'tcl', extensions: ['tcl'] },
    { icon: 'tex', extensions: ['texi', 'tex'] },
    { icon: 'text', extensions: ['txt'] },
    { icon: 'textile', extensions: ['textile'] },
    { icon: 'todo', extensions: ['todo'] },
    { icon: 'travis', extensions: ['travis.yml'], special: 'yml' },
    { icon: 'twig', extensions: ['twig'] },
    { icon: 'typescript', extensions: ['ts'] },
    { icon: 'typescriptdef', extensions: ['.d.ts'], special: 'ts' },
    { icon: 'volt', extensions: ['volt'] },
    { icon: 'vbhtml', extensions: ['vbhtml'] },
    { icon: 'vue', extensions: ['vue'] },
    { icon: 'vscode', extensions: ['vscodeignore', 'launch', 'jsconfig', 'tsconfig'], special: 'json' },
    { icon: 'xml', extensions: ['xml', 'axml', 'xaml'] },
    { icon: 'yaml', extensions: ['yml', 'yaml'] },
    { icon: 'zip', extensions: ['zip', 'rar', '7z', 'tar', 'gz', 'bzip2', 'xz', 'bz2'] }
  ],
  parse: function () {
    var s = this.replace(/\./g, '_');
    if ((/^\d/).test(s)) return 'n' + s;
    return s;
  }
};
<|endoftext|>


<commit_before>describe('dc.bubbleChart', function() {
    var id, chart, data;
    var dateFixture;
    var dimension, group;
    var countryDimension;
    var width = 900, height = 350;

    beforeEach(function() {
        dateFixture = loadDateFixture();
        data = crossfilter(dateFixture);
        dimension = data.dimension(function(d) {
            return d.status;
        });
        group = dimension.group()
            .reduce(
                //add
                function(p, v) {
                    ++p.count;
                    p.value += +v.value;
                    return p;
                },
                //remove
                function(p, v) {
                    --p.count;
                    p.value -= +v.value;
                    return p;
                },
                //init
                function() {
                    return {count:0, value:0};
                }
            )
            .order(function(d){return d.value;});
        countryDimension = data.dimension(function(d) {
            return d.countrycode;
        });

        id = 'bubble-chart';
        appendChartID(id);

        chart = dc.bubbleChart('#' + id);
        chart.dimension(dimension).group(group)
            .width(width).height(height)
            .colors(["#a60000", "#ff0000", "#ff4040", "#ff7373", "#67e667", "#39e639", "#00cc00"])
            .colorDomain([0,220])
            .colorAccessor(function(p) {
                return p.value.value;
            })
            .keyAccessor(function (p) {
                return p.value.value;
            })
            .valueAccessor(function (p) {
                return p.value.count;
            })
            .radiusValueAccessor(function (p) {
                return p.value.count;
            })
            .x(d3.scale.linear().domain([0, 300]))
            .y(d3.scale.linear().domain([0, 10]))
            .r(d3.scale.linear().domain([0, 30]))
            .maxBubbleRelativeSize(0.3)
            .transitionDuration(0)
            .renderLabel(true)
            .renderTitle(true)
            .title(function (p) {
                return p.key + ": {count:" + p.value.count + ",value:" + p.value.value + "}";
            });
    });

    it("should produce a chart", function() {
        expect(chart).not.toBeNull();
    });
    it('should register the chart with DC', function () {
        expect(dc.hasChart(chart)).toBeTruthy();
    });
    it('should set the requested dimension', function () {
        expect(chart.dimension()).toBe(dimension);
    });
    it('should set the requested group', function () {
        expect(chart.group()).toBe(group);
    });

    it('should assign colors', function () {
        expect(chart.colors()).not.toBeNull();
    });
    it('should set the width', function () {
        expect(chart.width()).toBe(width);
    });
    it('should set the height', function () {
        expect(chart.height()).toBe(height);
    });
    it('should set the transition duration', function () {
        expect(chart.transitionDuration()).toBe(0);
    });
    it('should set the margin', function () {
        expect(chart.margins()).not.toBeNull();
    });
    it('should set the x scale', function () {
        expect(chart.x()).toBeDefined();
    });
    it('should set the x scale domain', function () {
        expect(chart.x().domain()[0]).toBe(0);
        expect(chart.x().domain()[1]).toBe(300);
    });
    it('should set the y scale', function () {
        expect(chart.y()).toBeDefined();
    });
    it('should set the y scale domain', function () {
        expect(chart.y().domain()[0]).toBe(0);
        expect(chart.y().domain()[1]).toBe(10);
    });
    it('should set the radius scale', function () {
        expect(chart.r()).not.toBeNull();
    });
    it('should set the radius value accessor', function () {
        expect(chart.radiusValueAccessor()).not.toBeNull();
    });
    it('should set the x units', function () {
        expect(chart.xUnits()).toBe(dc.units.integers);
    });
    it('should create the x axis', function () {
        expect(chart.xAxis()).not.toBeNull();
    });
    it('should create the y axis', function () {
        expect(chart.yAxis()).not.toBeNull();
    });
    it('should create the brush', function () {
        expect(chart.select("g.brush")).not.toBeNull();
    });
    it('should leave round off by default', function () {
        expect(chart.round()).toBeUndefined();
    });
    it('allow round to be changed', function () {
        chart.round(d3.time.day.round);
        expect(chart.round()).not.toBeNull();
    });

    describe('render', function() {
        beforeEach(function() {
            chart.render();
        });
        it('should create the svg', function () {
            expect(chart.select("svg").empty()).toBeFalsy();
        });
        it('should use the height for svg', function () {
            expect(Number(chart.select("svg").attr("height"))).toEqual(height);
        });
        it('should auto calculate the x range based on width', function () {
            expect(chart.x().range()[0]).toBe(0);
            expect(chart.x().range()[1]).toBe(820);
        });
        it('should auto calculate the y range round based on height', function () {
            expect(chart.y().range()[0]).toBe(310);
            expect(chart.y().range()[1]).toBe(0);
        });
        it('should create the root g', function () {
            expect(chart.select("svg g").empty()).toBeFalsy();
        });
        it('should place the x axis at the bottom', function () {
            expect(chart.select("svg g g.x").attr("transform")).toBe("translate(30,320)");
        });
        it('should place the y axis on the left', function () {
            expect(chart.select("svg g g.y").attr("transform")).toBe("translate(30,10)");
        });

        // bubbles
        it('should generate right number of bubbles', function () {
            expect(chart.selectAll("circle.bubble")[0].length).toBe(2);
        });
        it('should calculate right cx for each bubble', function () {
            chart.selectAll("g.node").each(function (d, i) {
                if (i === 0)
                    expect(d3.select(this).attr("transform")).toBe("translate(601.3333333333334,155)");
                if (i === 1)
                    expect(d3.select(this).attr("transform")).toBe("translate(541.2,155)");
            });
        });
        it('should generate opaque groups and circles for each bubble', function () {
            chart.selectAll("g.node").each(function (d, i) {
                expect(d3.select(this).attr("opacity")).toBeNull();
                expect(d3.select(this).select('circle').attr("opacity")).toBe('1');
            });
        });
        it('should calculate right r for each bubble', function () {
            chart.selectAll("circle.bubble").each(function (d, i) {
                if (i === 0)
                    expect(Number(d3.select(this).attr("r"))).toBe(49.33333333333333);
                if (i === 1)
                    expect(Number(d3.select(this).attr("r"))).toBe(49.33333333333333);
            });
        });
        it('should attach each bubble with index based class', function () {
            chart.selectAll("circle.bubble").each(function (d, i) {
                if (i === 0)
                    expect(d3.select(this).attr("class")).toBe("bubble _0");
                if (i === 1)
                    expect(d3.select(this).attr("class")).toBe("bubble _1");
            });
        });
        it('should generate right number of labels', function () {
            expect(chart.selectAll("g.node text")[0].length).toBe(2);
        });
        it('should create correct label for each bubble', function () {
            chart.selectAll("g.node text").each(function (d, i) {
                if (i === 0)
                    expect(d3.select(this).text()).toBe("F");
                if (i === 1)
                    expect(d3.select(this).text()).toBe("T");
            });
        });
        it('should generate right number of titles', function () {
            expect(chart.selectAll("g.node title")[0].length).toBe(2);
        });
        it('should create correct title for each bubble', function () {
            chart.selectAll("g.node title").each(function (d, i) {
                if (i === 0)
                    expect(d3.select(this).text()).toBe("F: {count:5,value:220}");
                if (i === 1)
                    expect(d3.select(this).text()).toBe("T: {count:5,value:198}");
            });
        });
        it('should fill bubbles with correct colors', function () {
            chart.selectAll("circle.bubble").each(function (d, i) {
                if (i === 0)
                    expect(d3.select(this).attr("fill")).toBe("#00cc00");
                if (i === 1)
                    expect(d3.select(this).attr("fill")).toBe("#00cc00");
            });
        });
    });

    describe('bubble chart w/o label & title', function() {
        beforeEach(function () {
            chart.renderLabel(false);
            chart.renderTitle(false);
            chart.render();
        });
        it('should generate right number of labels', function () {
            expect(chart.selectAll("g.node text")[0].length).toBe(0);
        });
        it('should generate right number of titles', function () {
            expect(chart.selectAll("g.node title")[0].length).toBe(0);
        });
    });

    describe('with filter', function() {
        beforeEach(function () {
            chart.filter("F");
            chart.render();
        });
        it('should deselect bubble based on filter value', function () {
            chart.selectAll("g.node").each(function (d, i) {
                if (i === 0)
                    expect(d3.select(this).attr("class")).toBe("node selected");
                if (i === 1)
                    expect(d3.select(this).attr("class")).toBe("node deselected");
            });
        });
        it('should handle multi-selection highlight', function () {
            chart.filter("T");
            chart.redraw();
            chart.selectAll("g.node").each(function (d, i) {
                expect(d3.select(this).attr("class")).toBe("node selected");
            });
        });
    });

    describe('update', function() {
        beforeEach(function () {
            chart.render();
            countryDimension.filter("CA");
            chart.redraw();
        });
        it('should create correct label for each bubble', function () {
            chart.selectAll("g.node title").each(function (d, i) {
                if (i === 0)
                    expect(d3.select(this).text()).toBe("F: {count:0,value:0}");
                if (i === 1)
                    expect(d3.select(this).text()).toBe("T: {count:2,value:77}");
            });
        });
        it('should fill bubbles with correct colors', function () {
            chart.selectAll("circle.bubble").each(function (d, i) {
                if (i === 0)
                    expect(d3.select(this).attr("fill")).toBe("#a60000");
                if (i === 1)
                    expect(d3.select(this).attr("fill")).toBe("#ff4040");
            });
        });
    });

    describe('with no filter', function() {
        beforeEach(function () {
            countryDimension.filter("ZZ");
            chart.render();
        });
       it('should set invisible if bubble has 0 r', function () {
            chart.selectAll("g.node text").each(function (d, i) {
                expect(Number(d3.select(this).attr("opacity"))).toBe(0);
            });
        });
    });

   describe('with elastic axises', function() {
        beforeEach(function () {
            chart.elasticY(true)
                .yAxisPadding(3)
                .elasticX(true)
                .xAxisPadding(20);
            chart.render();
        });
       it('should auto calculate x range based on width', function () {
            expect(chart.x().range()[0]).toBe(0);
            expect(chart.x().range()[1]).toBe(820);
        });
       it('should set the x domain', function () {
            expect(chart.x().domain()[0]).toBe(178);
            expect(chart.x().domain()[1]).toBe(240);
        });
       it('should auto calculate y range based on height', function () {
            expect(chart.y().range()[0]).toBe(310);
            expect(chart.y().range()[1]).toBe(0);
        });
       it('should set the y domain', function () {
            expect(chart.y().domain()[0]).toBe(2);
            expect(chart.y().domain()[1]).toBe(8);
        });
   });

   describe('renderlet', function() {
       var derlet;
       beforeEach(function () {
           // spyOn doesn't seem to work with plain functions
           derlet = jasmine.createSpy('renderlet', function () {
               chart.selectAll("circle").attr("fill", "red");
           });
           derlet.and.callThrough();
           chart.renderlet(derlet);
       });
       it('should be invoked with render', function () {
           chart.render();
           expect(chart.selectAll("circle").attr("fill")).toBe("red");
           expect(derlet).toHaveBeenCalled();
       });
       it('should be invoked with redraw', function () {
           chart.render().redraw();
           expect(chart.selectAll("circle").attr("fill")).toBe("red");
           expect(derlet.calls.count()).toEqual(2);
        });
    });

    describe('non-unique keys', function() {
        // plot all rows as (value, nvalue) - a common scatterplot scenario
        beforeEach(function() {
            var rowDimension = data.dimension(function(d, i) {
                return i;
            });
            var rowGroup = rowDimension.group();

            chart.dimension(rowDimension).group(rowGroup)
                .keyAccessor(function(kv) {
                    return +dateFixture[kv.key].value;
                })
                .valueAccessor(function(kv) {
                    return +dateFixture[kv.key].nvalue;
                })
                .elasticY(true)
                .yAxisPadding(2)
                .elasticX(true)
                .xAxisPadding(2);

            chart.render();
        });
        it('should generate right number of bubbles', function () {
            expect(chart.selectAll("circle.bubble")[0].length).toBe(10);
        });
        it('should auto calculate x range based on width', function () {
            expect(chart.x().range()[0]).toBe(0);
            expect(chart.x().range()[1]).toBe(820);
        });
        it('should set the x domain', function () {
            expect(chart.x().domain()[0]).toBe(20);
            expect(chart.x().domain()[1]).toBe(68);
        });
        it('should auto calculate y range based on height', function () {
            expect(chart.y().range()[0]).toBe(310);
            expect(chart.y().range()[1]).toBe(0);
        });
        it('should set the y domain', function () {
            expect(chart.y().domain()[0]).toBe(-7);
            expect(chart.y().domain()[1]).toBe(12);
        });

    });
});
<commit_msg>missing parameter<commit_after>describe('dc.bubbleChart', function() {
    var id, chart, data;
    var dateFixture;
    var dimension, group;
    var countryDimension;
    var width = 900, height = 350;

    beforeEach(function() {
        dateFixture = loadDateFixture();
        data = crossfilter(dateFixture);
        dimension = data.dimension(function(d) {
            return d.status;
        });
        group = dimension.group()
            .reduce(
                //add
                function(p, v) {
                    ++p.count;
                    p.value += +v.value;
                    return p;
                },
                //remove
                function(p, v) {
                    --p.count;
                    p.value -= +v.value;
                    return p;
                },
                //init
                function() {
                    return {count:0, value:0};
                }
            )
            .order(function(d){return d.value;});
        countryDimension = data.dimension(function(d) {
            return d.countrycode;
        });

        id = 'bubble-chart';
        appendChartID(id);

        chart = dc.bubbleChart('#' + id);
        chart.dimension(dimension).group(group)
            .width(width).height(height)
            .colors(["#a60000", "#ff0000", "#ff4040", "#ff7373", "#67e667", "#39e639", "#00cc00"])
            .colorDomain([0,220])
            .colorAccessor(function(p) {
                return p.value.value;
            })
            .keyAccessor(function (p) {
                return p.value.value;
            })
            .valueAccessor(function (p) {
                return p.value.count;
            })
            .radiusValueAccessor(function (p) {
                return p.value.count;
            })
            .x(d3.scale.linear().domain([0, 300]))
            .y(d3.scale.linear().domain([0, 10]))
            .r(d3.scale.linear().domain([0, 30]))
            .maxBubbleRelativeSize(0.3)
            .transitionDuration(0)
            .renderLabel(true)
            .renderTitle(true)
            .title(function (p) {
                return p.key + ": {count:" + p.value.count + ",value:" + p.value.value + "}";
            });
    });

    it("should produce a chart", function() {
        expect(chart).not.toBeNull();
    });
    it('should register the chart with DC', function () {
        expect(dc.hasChart(chart)).toBeTruthy();
    });
    it('should set the requested dimension', function () {
        expect(chart.dimension()).toBe(dimension);
    });
    it('should set the requested group', function () {
        expect(chart.group()).toBe(group);
    });

    it('should assign colors', function () {
        expect(chart.colors()).not.toBeNull();
    });
    it('should set the width', function () {
        expect(chart.width()).toBe(width);
    });
    it('should set the height', function () {
        expect(chart.height()).toBe(height);
    });
    it('should set the transition duration', function () {
        expect(chart.transitionDuration()).toBe(0);
    });
    it('should set the margin', function () {
        expect(chart.margins()).not.toBeNull();
    });
    it('should set the x scale', function () {
        expect(chart.x()).toBeDefined();
    });
    it('should set the x scale domain', function () {
        expect(chart.x().domain()[0]).toBe(0);
        expect(chart.x().domain()[1]).toBe(300);
    });
    it('should set the y scale', function () {
        expect(chart.y()).toBeDefined();
    });
    it('should set the y scale domain', function () {
        expect(chart.y().domain()[0]).toBe(0);
        expect(chart.y().domain()[1]).toBe(10);
    });
    it('should set the radius scale', function () {
        expect(chart.r()).not.toBeNull();
    });
    it('should set the radius value accessor', function () {
        expect(chart.radiusValueAccessor()).not.toBeNull();
    });
    it('should set the x units', function () {
        expect(chart.xUnits()).toBe(dc.units.integers);
    });
    it('should create the x axis', function () {
        expect(chart.xAxis()).not.toBeNull();
    });
    it('should create the y axis', function () {
        expect(chart.yAxis()).not.toBeNull();
    });
    it('should create the brush', function () {
        expect(chart.select("g.brush")).not.toBeNull();
    });
    it('should leave round off by default', function () {
        expect(chart.round()).toBeUndefined();
    });
    it('allow round to be changed', function () {
        chart.round(d3.time.day.round);
        expect(chart.round()).not.toBeNull();
    });

    describe('render', function() {
        beforeEach(function() {
            chart.render();
        });
        it('should create the svg', function () {
            expect(chart.select("svg").empty()).toBeFalsy();
        });
        it('should use the height for svg', function () {
            expect(Number(chart.select("svg").attr("height"))).toEqual(height);
        });
        it('should auto calculate the x range based on width', function () {
            expect(chart.x().range()[0]).toBe(0);
            expect(chart.x().range()[1]).toBe(820);
        });
        it('should auto calculate the y range round based on height', function () {
            expect(chart.y().range()[0]).toBe(310);
            expect(chart.y().range()[1]).toBe(0);
        });
        it('should create the root g', function () {
            expect(chart.select("svg g").empty()).toBeFalsy();
        });
        it('should place the x axis at the bottom', function () {
            expect(chart.select("svg g g.x").attr("transform")).toBe("translate(30,320)");
        });
        it('should place the y axis on the left', function () {
            expect(chart.select("svg g g.y").attr("transform")).toBe("translate(30,10)");
        });

        // bubbles
        it('should generate right number of bubbles', function () {
            expect(chart.selectAll("circle.bubble")[0].length).toBe(2);
        });
        it('should calculate right cx for each bubble', function () {
            chart.selectAll("g.node").each(function (d, i) {
                if (i === 0)
                    expect(d3.select(this).attr("transform")).toBe("translate(601.3333333333334,155)");
                if (i === 1)
                    expect(d3.select(this).attr("transform")).toBe("translate(541.2,155)");
            });
        });
        it('should generate opaque groups and circles for each bubble', function () {
            chart.selectAll("g.node").each(function (d, i) {
                expect(d3.select(this).attr("opacity")).toBeNull();
                expect(d3.select(this).select('circle').attr("opacity")).toBe('1');
            });
        });
        it('should calculate right r for each bubble', function () {
            chart.selectAll("circle.bubble").each(function (d, i) {
                if (i === 0)
                    expect(Number(d3.select(this).attr("r"))).toBe(49.33333333333333);
                if (i === 1)
                    expect(Number(d3.select(this).attr("r"))).toBe(49.33333333333333);
            });
        });
        it('should attach each bubble with index based class', function () {
            chart.selectAll("circle.bubble").each(function (d, i) {
                if (i === 0)
                    expect(d3.select(this).attr("class")).toBe("bubble _0");
                if (i === 1)
                    expect(d3.select(this).attr("class")).toBe("bubble _1");
            });
        });
        it('should generate right number of labels', function () {
            expect(chart.selectAll("g.node text")[0].length).toBe(2);
        });
        it('should create correct label for each bubble', function () {
            chart.selectAll("g.node text").each(function (d, i) {
                if (i === 0)
                    expect(d3.select(this).text()).toBe("F");
                if (i === 1)
                    expect(d3.select(this).text()).toBe("T");
            });
        });
        it('should generate right number of titles', function () {
            expect(chart.selectAll("g.node title")[0].length).toBe(2);
        });
        it('should create correct title for each bubble', function () {
            chart.selectAll("g.node title").each(function (d, i) {
                if (i === 0)
                    expect(d3.select(this).text()).toBe("F: {count:5,value:220}");
                if (i === 1)
                    expect(d3.select(this).text()).toBe("T: {count:5,value:198}");
            });
        });
        it('should fill bubbles with correct colors', function () {
            chart.selectAll("circle.bubble").each(function (d, i) {
                if (i === 0)
                    expect(d3.select(this).attr("fill")).toBe("#00cc00");
                if (i === 1)
                    expect(d3.select(this).attr("fill")).toBe("#00cc00");
            });
        });
    });

    describe('bubble chart w/o label & title', function() {
        beforeEach(function () {
            chart.renderLabel(false);
            chart.renderTitle(false);
            chart.render();
        });
        it('should generate right number of labels', function () {
            expect(chart.selectAll("g.node text")[0].length).toBe(0);
        });
        it('should generate right number of titles', function () {
            expect(chart.selectAll("g.node title")[0].length).toBe(0);
        });
    });

    describe('with filter', function() {
        beforeEach(function () {
            chart.filter("F");
            chart.render();
        });
        it('should deselect bubble based on filter value', function () {
            chart.selectAll("g.node").each(function (d, i) {
                if (i === 0)
                    expect(d3.select(this).attr("class")).toBe("node selected");
                if (i === 1)
                    expect(d3.select(this).attr("class")).toBe("node deselected");
            });
        });
        it('should handle multi-selection highlight', function () {
            chart.filter("T");
            chart.redraw();
            chart.selectAll("g.node").each(function (d, i) {
                expect(d3.select(this).attr("class")).toBe("node selected");
            });
        });
    });

    describe('update', function() {
        beforeEach(function () {
            chart.render();
            countryDimension.filter("CA");
            chart.redraw();
        });
        it('should create correct label for each bubble', function () {
            chart.selectAll("g.node title").each(function (d, i) {
                if (i === 0)
                    expect(d3.select(this).text()).toBe("F: {count:0,value:0}");
                if (i === 1)
                    expect(d3.select(this).text()).toBe("T: {count:2,value:77}");
            });
        });
        it('should fill bubbles with correct colors', function () {
            chart.selectAll("circle.bubble").each(function (d, i) {
                if (i === 0)
                    expect(d3.select(this).attr("fill")).toBe("#a60000");
                if (i === 1)
                    expect(d3.select(this).attr("fill")).toBe("#ff4040");
            });
        });
    });

    describe('with no filter', function() {
        beforeEach(function () {
            countryDimension.filter("ZZ");
            chart.render();
        });
       it('should set invisible if bubble has 0 r', function () {
            chart.selectAll("g.node text").each(function (d, i) {
                expect(Number(d3.select(this).attr("opacity"))).toBe(0);
            });
        });
    });

   describe('with elastic axises', function() {
        beforeEach(function () {
            chart.elasticY(true)
                .yAxisPadding(3)
                .elasticX(true)
                .xAxisPadding(20);
            chart.render();
        });
       it('should auto calculate x range based on width', function () {
            expect(chart.x().range()[0]).toBe(0);
            expect(chart.x().range()[1]).toBe(820);
        });
       it('should set the x domain', function () {
            expect(chart.x().domain()[0]).toBe(178);
            expect(chart.x().domain()[1]).toBe(240);
        });
       it('should auto calculate y range based on height', function () {
            expect(chart.y().range()[0]).toBe(310);
            expect(chart.y().range()[1]).toBe(0);
        });
       it('should set the y domain', function () {
            expect(chart.y().domain()[0]).toBe(2);
            expect(chart.y().domain()[1]).toBe(8);
        });
   });

   describe('renderlet', function() {
       var derlet;
       beforeEach(function () {
           // spyOn doesn't seem to work with plain functions
           derlet = jasmine.createSpy('renderlet', function (chart) {
               chart.selectAll("circle").attr("fill", "red");
           });
           derlet.and.callThrough();
           chart.renderlet(derlet);
       });
       it('should be invoked with render', function () {
           chart.render();
           expect(chart.selectAll("circle").attr("fill")).toBe("red");
           expect(derlet).toHaveBeenCalled();
       });
       it('should be invoked with redraw', function () {
           chart.render().redraw();
           expect(chart.selectAll("circle").attr("fill")).toBe("red");
           expect(derlet.calls.count()).toEqual(2);
        });
    });

    describe('non-unique keys', function() {
        // plot all rows as (value, nvalue) - a common scatterplot scenario
        beforeEach(function() {
            var rowDimension = data.dimension(function(d, i) {
                return i;
            });
            var rowGroup = rowDimension.group();

            chart.dimension(rowDimension).group(rowGroup)
                .keyAccessor(function(kv) {
                    return +dateFixture[kv.key].value;
                })
                .valueAccessor(function(kv) {
                    return +dateFixture[kv.key].nvalue;
                })
                .elasticY(true)
                .yAxisPadding(2)
                .elasticX(true)
                .xAxisPadding(2);

            chart.render();
        });
        it('should generate right number of bubbles', function () {
            expect(chart.selectAll("circle.bubble")[0].length).toBe(10);
        });
        it('should auto calculate x range based on width', function () {
            expect(chart.x().range()[0]).toBe(0);
            expect(chart.x().range()[1]).toBe(820);
        });
        it('should set the x domain', function () {
            expect(chart.x().domain()[0]).toBe(20);
            expect(chart.x().domain()[1]).toBe(68);
        });
        it('should auto calculate y range based on height', function () {
            expect(chart.y().range()[0]).toBe(310);
            expect(chart.y().range()[1]).toBe(0);
        });
        it('should set the y domain', function () {
            expect(chart.y().domain()[0]).toBe(-7);
            expect(chart.y().domain()[1]).toBe(12);
        });

    });
});
<|endoftext|>


<commit_before>import classNames from 'classnames'
import PropTypes from 'prop-types'
import React from 'react'
import CloseIcon from 'part:@sanity/base/close-icon'
import styles from 'part:@sanity/components/dialogs/default-style'
import Button from 'part:@sanity/components/buttons/default'
import ButtonGrid from 'part:@sanity/components/buttons/button-grid'
import {partition, debounce} from 'lodash'
import {Portal} from '../utilities/Portal'
import Escapable from '../utilities/Escapable'
import CaptureOutsideClicks from '../utilities/CaptureOutsideClicks'
import Stacked from '../utilities/Stacked'

const noop = () => {}

export default class DefaultDialog extends React.PureComponent {
  static propTypes = {
    color: PropTypes.oneOf(['default', 'warning', 'success', 'danger', 'info']),
    className: PropTypes.string,
    title: PropTypes.string,
    children: PropTypes.node,
    onOpen: PropTypes.func,
    onClose: PropTypes.func,
    onEscape: PropTypes.func,
    onClickOutside: PropTypes.func,
    onAction: PropTypes.func,
    showCloseButton: PropTypes.bool,
    actionsAlign: PropTypes.oneOf(['start', 'end']),
    actions: PropTypes.arrayOf(
      PropTypes.shape({
        title: PropTypes.string.isRequired,
        icon: PropTypes.func,
        tooltip: PropTypes.string,
        kind: PropTypes.string,
        autoFocus: PropTypes.bool
      })
    )
  }

  static defaultProps = {
    showCloseButton: true,
    actionsAlign: 'end',
    onAction() {},
    onOpen() {},
    onClose() {},
    actions: [],
    color: 'default'
  }

  openDialogElement() {
    this.props.onOpen()
  }

  handleDialogClick = event => {
    event.stopPropagation()
  }

  setDialogElement = element => {
    this.dialog = element
  }

  setContentElement = element => {
    this.contentElement = element
  }

  createButtonFromAction = (action, i) => {
    return (
      <Button
        key={i}
        onClick={() => this.props.onAction(action)}
        data-action-index={i}
        color={action.color}
        disabled={action.disabled}
        kind={action.kind}
        inverted={action.inverted}
        autoFocus={action.autoFocus}
        icon={action.icon}
        className={action.secondary ? styles.actionSecondary : ''}
      >
        {action.title}
      </Button>
    )
  }

  renderActions = actions => {
    if (!actions || actions.length === 0) {
      return null
    }

    const [secondary, primary] = partition(actions, action => action.secondary)

    return (
      <ButtonGrid
        align={this.props.actionsAlign}
        secondary={secondary.map(this.createButtonFromAction)}
      >
        {primary.map(this.createButtonFromAction)}
      </ButtonGrid>
    )
  }

  render() {
    const {
      title,
      actions,
      color,
      onClose,
      onClickOutside,
      onEscape,
      className: classNameProp,
      showCloseButton
    } = this.props

    const className = classNames(
      styles.root,
      styles[color],
      actions && actions.length > 0 && styles.hasFunctions,
      classNameProp
    )

    const handleEscape = onEscape || onClose || noop

    return (
      <Portal>
        <Stacked>
          {isActive => (

                <Escapable
                  onEscape={event => (isActive || event.shiftKey) && handleEscape(event)}
                />
                <CaptureOutsideClicks
                  onClickOutside={isActive ? onClickOutside : undefined}
                  className={styles.inner}
                >
                  {!title && onClose && showCloseButton && (

                      <Button
                        icon={CloseIcon}
                        kind="simple"
                        onClick={onClose}
                        padding="small"
                        title="Close"
                      />

                  )}
                  {title && (

{title}

                      {onClose && showCloseButton && (

                          <Button
                            className={styles.closeButton}
                            color={['danger', 'success', 'warning'].includes(color) && 'white'}
                            icon={CloseIcon}
                            kind="simple"
                            onClick={onClose}
                            padding="small"
                            title="Close"
                          />

                      )}

                  )}

 0 ? styles.content : styles.contentWithoutFooter
                    }
                  >
                    {this.props.children}

                  {actions && actions.length > 0 && (

{this.renderActions(actions)}

                  )}
                </CaptureOutsideClicks>

          )}
        </Stacked>
      </Portal>
    )
  }
}
<commit_msg>[components] Fix incorrect color proptype for close button in portal<commit_after>import classNames from 'classnames'
import PropTypes from 'prop-types'
import React from 'react'
import CloseIcon from 'part:@sanity/base/close-icon'
import styles from 'part:@sanity/components/dialogs/default-style'
import Button from 'part:@sanity/components/buttons/default'
import ButtonGrid from 'part:@sanity/components/buttons/button-grid'
import {partition, debounce} from 'lodash'
import {Portal} from '../utilities/Portal'
import Escapable from '../utilities/Escapable'
import CaptureOutsideClicks from '../utilities/CaptureOutsideClicks'
import Stacked from '../utilities/Stacked'

const noop = () => {}

export default class DefaultDialog extends React.PureComponent {
  static propTypes = {
    color: PropTypes.oneOf(['default', 'warning', 'success', 'danger', 'info']),
    className: PropTypes.string,
    title: PropTypes.string,
    children: PropTypes.node,
    onOpen: PropTypes.func,
    onClose: PropTypes.func,
    onEscape: PropTypes.func,
    onClickOutside: PropTypes.func,
    onAction: PropTypes.func,
    showCloseButton: PropTypes.bool,
    actionsAlign: PropTypes.oneOf(['start', 'end']),
    actions: PropTypes.arrayOf(
      PropTypes.shape({
        title: PropTypes.string.isRequired,
        icon: PropTypes.func,
        tooltip: PropTypes.string,
        kind: PropTypes.string,
        autoFocus: PropTypes.bool
      })
    )
  }

  static defaultProps = {
    showCloseButton: true,
    actionsAlign: 'end',
    onAction() {},
    onOpen() {},
    onClose() {},
    actions: [],
    color: 'default'
  }

  openDialogElement() {
    this.props.onOpen()
  }

  handleDialogClick = event => {
    event.stopPropagation()
  }

  setDialogElement = element => {
    this.dialog = element
  }

  setContentElement = element => {
    this.contentElement = element
  }

  createButtonFromAction = (action, i) => {
    return (
      <Button
        key={i}
        onClick={() => this.props.onAction(action)}
        data-action-index={i}
        color={action.color}
        disabled={action.disabled}
        kind={action.kind}
        inverted={action.inverted}
        autoFocus={action.autoFocus}
        icon={action.icon}
        className={action.secondary ? styles.actionSecondary : ''}
      >
        {action.title}
      </Button>
    )
  }

  renderActions = actions => {
    if (!actions || actions.length === 0) {
      return null
    }

    const [secondary, primary] = partition(actions, action => action.secondary)

    return (
      <ButtonGrid
        align={this.props.actionsAlign}
        secondary={secondary.map(this.createButtonFromAction)}
      >
        {primary.map(this.createButtonFromAction)}
      </ButtonGrid>
    )
  }

  render() {
    const {
      title,
      actions,
      color,
      onClose,
      onClickOutside,
      onEscape,
      className: classNameProp,
      showCloseButton
    } = this.props

    const className = classNames(
      styles.root,
      styles[color],
      actions && actions.length > 0 && styles.hasFunctions,
      classNameProp
    )

    const handleEscape = onEscape || onClose || noop

    return (
      <Portal>
        <Stacked>
          {isActive => (

                <Escapable
                  onEscape={event => (isActive || event.shiftKey) && handleEscape(event)}
                />
                <CaptureOutsideClicks
                  onClickOutside={isActive ? onClickOutside : undefined}
                  className={styles.inner}
                >
                  {!title && onClose && showCloseButton && (

                      <Button
                        icon={CloseIcon}
                        kind="simple"
                        onClick={onClose}
                        padding="small"
                        title="Close"
                      />

                  )}
                  {title && (

{title}

                      {onClose && showCloseButton && (

                          <Button
                            className={styles.closeButton}
                            color={
                              ['danger', 'success', 'warning'].includes(color) ? 'white' : undefined
                            }
                            icon={CloseIcon}
                            kind="simple"
                            onClick={onClose}
                            padding="small"
                            title="Close"
                          />

                      )}

                  )}

 0 ? styles.content : styles.contentWithoutFooter
                    }
                  >
                    {this.props.children}

                  {actions && actions.length > 0 && (

{this.renderActions(actions)}

                  )}
                </CaptureOutsideClicks>

          )}
        </Stacked>
      </Portal>
    )
  }
}
<|endoftext|>


<commit_before>var connection
var editor
var editorDirty = false
var editorShare
var output
var status = 'nothing' // reflects what status the buttons show: 'running' or 'nothing'
var changing ='' // for starting/stopping states
var state // various things shared with share.js, including group consideration of the running status
var doneLoad = false

// Set up editor whenever we have a good share.js connection
var load_and_wire_up_editor = function() {
  async.parallel( [
    // Wire up shared document on the connection
    function(callback) {
      console.log("sharing doc...")
      // XXX need a better token in here. API key?
      var docName = 'scraperwiki-' + scraperwiki.box + '-doc005'
      connection.open(docName, 'text', function(error, doc) {
        if (error) {
          console.log("sharing doc error", error)
          scraperwiki.alert("Trouble setting up pair editing!", error, true)
          callback(true, null)
        }
        console.log("...shared doc")
        editorShare = doc
        callback(null, doc)
      })
    },
    // Wire up shared state on the connection
    function(callback) {
      console.log("sharing state...")
      // XXX need a better token in here. API key?
      var docName = 'scraperwiki-' + scraperwiki.box + '-state005'
      connection.open(docName, 'json', function(error, doc) {
        if (error) {
          console.log("sharing state error", error)
          scraperwiki.alert("Trouble setting up pair state!", error, true)
          callback(true, null)
        }
        state = doc
        // Start to share status
        if (state.created) {
          console.log("first time this state connection has been used, initialising")
          state.submitOp([{p:[],od:null,oi:{status:'nothing'}}])
        }
        state.on('change', function (op) {
          shared_state_update(op)
        })
        console.log("...shared state")
        callback(null, doc)
      })
    }
  ], function(err, results) {
    if (err) {
      scraperwiki.alert("Gave up setup of pair stuff!", err, true)
      return
    }

    // Load code from file
    console.log("loading...")
    scraperwiki.exec('mkdir -p code && touch code/scraper && cat code/scraper && echo -n swinternalGOTCODEOFSCRAPER', function(data) {
      if (data.indexOf("swinternalGOTCODEOFSCRAPER") == -1) {
        scraperwiki.alert("Trouble loading code!", data, true)
        return
      }
      data = data.replace("swinternalGOTCODEOFSCRAPER", "")

      // If nothing there, set some default content to get people going
      if (data.match(/^\s*$/)) {
        data = "#!/usr/bin/python\n\nimport scraperwiki\n\n"
      }
      console.log("...loaded")

      // Connect editor window to the doc
      editorShare.attach_ace(editor)
      set_editor_mode(data)
      editor.setValue(data) // XXX this overrides what is in filesystem on top of what is in sharej
      editor.moveCursorTo(0, 0)
      editor.focus()
      editor.setReadOnly(false)
      doneLoad = true

      update_dirty(false)
      editor.on('change', function() {
        update_dirty(true)
      })

      poll_output()
    }, handle_exec_error)
  });
}

// Handle error
var handle_exec_error = function(jqXHR, textStatus, errorThrown) {
    console.log("got an error:", errorThrown, jqXHR, textStatus)
    // Handle special case of the browser not being connected to the net
    // http://stackoverflow.com/questions/10026204/dialog-box-if-there-is-no-internet-connection-using-jquery-or-ajax
    if (jqXHR.status == 0) {
      // Wait half a second before error - otherwise they show on page refresh,
      // we only want to show if the browser is disconneted from the network.
      setTimeout(function() {
        clear_alerts()
        scraperwiki.alert("No connection to Internet!", "", false)
      } , 500)
    } else {
      scraperwiki.alert(errorThrown, $(jqXHR.responseText).text(), "error")
    }
}

// Display whether we have unsaved edits
var update_dirty = function(value) {
  clearTimeout(save_code)
  editorDirty = value
  if (editorDirty) {
    // Wait three seconds and then save. If we get another change
    // in those three seconds, reset that timer to avoid excess saves.
    $("#saved").text("Saving...")
    setTimeout(save_code, 3000)
  } else {
    $("#saved").text("All changes saved")
  }
}

// Prevent navigation away if not saved
// XXX this doesn't seem to work for close events in chrome in the frame
$(window).on('beforeunload', function() {
  if (editorDirty) {
    return "You've made changes that haven't been saved yet."
  }
})

// Work out language we're in from the shebang line
var set_editor_mode = function(code) {
  var first = code.split("\n")[0]
  if (first.substr(0,2) != "#!") {
    scraperwiki.alert("Specify language in the first line!", "For example, put #!/usr/bin/ruby, #!/usr/bin/R or #!/usr/bin/python.", false)
    return false
  }
  // Please add more as you need them and send us a pull request!
  if (first.indexOf("python") != -1) {
    editor.getSession().setMode("ace/mode/python")
  } else if (first.indexOf("ruby") != -1) {
    editor.getSession().setMode("ace/mode/ruby")
  } else if (first.indexOf("perl") != -1) {
    editor.getSession().setMode("ace/mode/perl")
  } else if (first.indexOf("node") != -1) {
    editor.getSession().setMode("ace/mode/javascript")
  } else if (first.indexOf("coffee") != -1) {
    editor.getSession().setMode("ace/mode/coffee")
  } else if (first.indexOf("clojure") != -1) {
    editor.getSession().setMode("ace/mode/clojure")
  } else if (first.indexOf("tcc") != -1) {
    editor.getSession().setMode("ace/mode/c_cpp")
  } else if (first.indexOf("R") != -1) {
    editor.getSession().setMode("ace/mode/r")
  } else if (first.indexOf("sh") != -1) {
    editor.getSession().setMode("ace/mode/sh")
  } else {
    editor.getSession().setMode("ace/mode/text")
  }
  return true
}

// Got a new state over ShareJS (from ourselves or remote clients)
var shared_state_update = function(op) {
  console.log("shared_state_update", state.snapshot)

  // Respond to the status change
  var new_status = state.snapshot.status
  if (new_status != status) {
    console.log("status change", status, "===>", new_status)
    if (new_status == "running") {
      poll_output()
    }
    status = new_status
    changing = ""
  }
  update_display_from_status(new_status)
}

// Show status in buttons - we have this as we can call it directly
// to make the run button seem more responsive
var update_display_from_status = function(use_status) {
  $("#run").removeClass("btn-primary").removeClass("btn-danger").removeClass("btn-warning").removeClass("btn-success")
  if (changing == "starting") {
    $("#run").text("Starting...").addClass("btn-warning")
  } else if (changing == "stopping") {
    $("#run").text("Stopping...").addClass("btn-danger")
  } else if (use_status == "running") {
    $("#run").text("Running...").addClass("btn-success")
  } else if (use_status == "nothing") {
    $("#run").text("Run!").addClass("btn-primary")
  }
}

// Show/hide things according to current state
var set_status = function(new_status) {
  if (new_status != "running" && new_status != "nothing") {
    scraperwiki.alert("Unknown new status!", new_status, true)
    return
  }

  // Tell other ShareJS clients the status has changed
  try {
    state.submitOp( {p:['status'],od:status,oi:new_status}) // XXX state.status for od?
  } catch (e) {
    scraperwiki.alert("Error saving to ShareJS!", e, true)
  }
}

// Console get more output
var poll_output = function() {
  scraperwiki.exec("./tool/enrunerate", function(text) {
    console.log("enrunerate:", text, "len:", text.length)
    set_status(text)

    // we poll one last time either way to be sure we get end of output...
    var again = false
    if (text == "running") {
      // ... but if script is still "running" we'll trigger the timer to do it again
      again = true
    }
    scraperwiki.exec("cat logs/out", function(text) {
      // XXX detect no file a better way
      if (text != "cat: logs/out: No such file or directory\n") {
        output.setValue(text)
      }
      output.clearSelection()
      if (again) {
        setTimeout(poll_output, 10)
      }
    }, handle_exec_error)
  }, handle_exec_error)
}

// Clear any errors
var clear_alerts = function() {
  $(".alert").remove()
}

// Save the code - optionally takes text of extra commands to also
// in the same "exec" and a callback to run when done
var save_code = function(callback) {
  clearTimeout(save_code) // stop any already scheduled timed saves
  var code = editor.getValue()
  if (code.length == 0 || code.charAt(code.length - 1) != "\n") {
    code += "\n" // we need a separator \n at the end of the file for the ENDOFSCRAPER heredoc below
  }
  var cmd = "cat >code/scraper.new.$$ <<ENDOFSCRAPER &&\n" + code + "ENDOFSCRAPER\n"
  cmd = cmd + "chmod a+x code/scraper.new.$$ && mv code/scraper.new.$$ code/scraper"
  scraperwiki.exec(cmd, function(text) {
    if (text != "") {
        scraperwiki.alert("Trouble saving code!", text, true)
        return
    }

    // Check actual content against saved - in case there was a change while we executed
    if (editor.getValue() == code) {
      console.log("Saved fine without interference")
      update_dirty(false)
    } else {
      console.log("Ooops, it got dirty while saving")
      update_dirty(true)
    }

    if (callback) {
      callback(text)
    }
  }, handle_exec_error)
}

// When the "documentation" button is pressed
var do_docs = function() {
  window.open("https://scraperwiki.com/docs/", "_blank")
}

// When the "bugs" button is pressed
var do_bugs = function() {
  window.open("https://github.com/frabcus/code-scraper-in-browser-tool/issues", "_blank")
}

// When the "keys" button is pressed
var do_keys = function() {
  window.open("https://github.com/ajaxorg/ace/wiki/Default-Keyboard-Shortcuts", "_blank")
}

// When the "run" button is pressed
var do_run = function() {
  // force a check that we have a shebang (#!) line
  clear_alerts()
  var code = editor.getValue()
  if (!set_editor_mode(code)) {
    return
  }

  // if it is already running, stop instead
  if (status == "running") {
    // make button show what we're doing
    changing = "stopping"
    update_display_from_status(status)

    scraperwiki.exec("./tool/enrunerate stop", function(text) {
      console.log("enrunerate stop:", text)
      // And tell all clients that we're now not running code (if we're not!)
      set_status(text)
    }, handle_exec_error)
    return
  }

  // to make button feel responsibe, temporarily show the wrong status
  // (until next operation makes it right)
  changing = "starting"
  update_display_from_status(status)
  output.setValue("")

  // Save code
  save_code(function (text) {
    // Then run it
    scraperwiki.exec("./tool/enrunerate run", function(text) {
      console.log("enrunerate run:", text)
      // And tell all clients that we're now running code (if we are!)
      set_status(text)
    }, handle_exec_error)
  })
}

// Main entry point
$(document).ready(function() {
  settings = scraperwiki.readSettings()
  $('#apikey').val(settings.source.apikey)

  // Create editor window, read only until it is ready
  editor = ace.edit("editor")
  editor.getSession().setUseSoftTabs(true)
  editor.setTheme("ace/theme/monokai")
  editor.setReadOnly(true)

  // Connect to sharejs server
  console.log("connecting...")
  connection = new sharejs.Connection('http://seagrass.goatchurch.org.uk/sharejs/channel')
  connection.on("error", function(e) {
      console.log("sharejs connection: error")
      clear_alerts()
      scraperwiki.alert("Editor is offline!", e, false)
      doneLoad = false
      editor.setReadOnly(true)
  })
  connection.on("ok", function(e) {
      console.log("sharejs connection: ok")
      clear_alerts()
      load_and_wire_up_editor()
      if (doneLoad) {
        editor.setReadOnly(false)
      }
  })
  console.log("...connected")

  // Create the console output window
  output = ace.edit("output")
  output.setTheme("ace/theme/monokai")
  // ... we use /bin/sh syntax highlighting, the only other at all
  // credible option for such varied output is plain text, which is dull.
  output.getSession().setMode("ace/mode/sh")
  output.setReadOnly(true)

  // Bind all the buttons to do something
  $('#docs').on('click', do_docs)
  $('#bugs').on('click', do_bugs)
  $('#keys').on('click', do_keys)
  $('#run').on('click', do_run)
  $(document).bind('keydown', 'ctrl+r', do_run)
})

<commit_msg>Say where source is<commit_after>// Source code repository: https://github.com/frabcus/code-scraper-in-browser-tool/

var connection
var editor
var editorDirty = false
var editorShare
var output
var status = 'nothing' // reflects what status the buttons show: 'running' or 'nothing'
var changing ='' // for starting/stopping states
var state // various things shared with share.js, including group consideration of the running status
var doneLoad = false

// Set up editor whenever we have a good share.js connection
var load_and_wire_up_editor = function() {
  async.parallel( [
    // Wire up shared document on the connection
    function(callback) {
      console.log("sharing doc...")
      // XXX need a better token in here. API key?
      var docName = 'scraperwiki-' + scraperwiki.box + '-doc005'
      connection.open(docName, 'text', function(error, doc) {
        if (error) {
          console.log("sharing doc error", error)
          scraperwiki.alert("Trouble setting up pair editing!", error, true)
          callback(true, null)
        }
        console.log("...shared doc")
        editorShare = doc
        callback(null, doc)
      })
    },
    // Wire up shared state on the connection
    function(callback) {
      console.log("sharing state...")
      // XXX need a better token in here. API key?
      var docName = 'scraperwiki-' + scraperwiki.box + '-state005'
      connection.open(docName, 'json', function(error, doc) {
        if (error) {
          console.log("sharing state error", error)
          scraperwiki.alert("Trouble setting up pair state!", error, true)
          callback(true, null)
        }
        state = doc
        // Start to share status
        if (state.created) {
          console.log("first time this state connection has been used, initialising")
          state.submitOp([{p:[],od:null,oi:{status:'nothing'}}])
        }
        state.on('change', function (op) {
          shared_state_update(op)
        })
        console.log("...shared state")
        callback(null, doc)
      })
    }
  ], function(err, results) {
    if (err) {
      scraperwiki.alert("Gave up setup of pair stuff!", err, true)
      return
    }

    // Load code from file
    console.log("loading...")
    scraperwiki.exec('mkdir -p code && touch code/scraper && cat code/scraper && echo -n swinternalGOTCODEOFSCRAPER', function(data) {
      if (data.indexOf("swinternalGOTCODEOFSCRAPER") == -1) {
        scraperwiki.alert("Trouble loading code!", data, true)
        return
      }
      data = data.replace("swinternalGOTCODEOFSCRAPER", "")

      // If nothing there, set some default content to get people going
      if (data.match(/^\s*$/)) {
        data = "#!/usr/bin/python\n\nimport scraperwiki\n\n"
      }
      console.log("...loaded")

      // Connect editor window to the doc
      editorShare.attach_ace(editor)
      set_editor_mode(data)
      editor.setValue(data) // XXX this overrides what is in filesystem on top of what is in sharej
      editor.moveCursorTo(0, 0)
      editor.focus()
      editor.setReadOnly(false)
      doneLoad = true

      update_dirty(false)
      editor.on('change', function() {
        update_dirty(true)
      })

      poll_output()
    }, handle_exec_error)
  });
}

// Handle error
var handle_exec_error = function(jqXHR, textStatus, errorThrown) {
    console.log("got an error:", errorThrown, jqXHR, textStatus)
    // Handle special case of the browser not being connected to the net
    // http://stackoverflow.com/questions/10026204/dialog-box-if-there-is-no-internet-connection-using-jquery-or-ajax
    if (jqXHR.status == 0) {
      // Wait half a second before error - otherwise they show on page refresh,
      // we only want to show if the browser is disconneted from the network.
      setTimeout(function() {
        clear_alerts()
        scraperwiki.alert("No connection to Internet!", "", false)
      } , 500)
    } else {
      scraperwiki.alert(errorThrown, $(jqXHR.responseText).text(), "error")
    }
}

// Display whether we have unsaved edits
var update_dirty = function(value) {
  clearTimeout(save_code)
  editorDirty = value
  if (editorDirty) {
    // Wait three seconds and then save. If we get another change
    // in those three seconds, reset that timer to avoid excess saves.
    $("#saved").text("Saving...")
    setTimeout(save_code, 3000)
  } else {
    $("#saved").text("All changes saved")
  }
}

// Prevent navigation away if not saved
// XXX this doesn't seem to work for close events in chrome in the frame
$(window).on('beforeunload', function() {
  if (editorDirty) {
    return "You've made changes that haven't been saved yet."
  }
})

// Work out language we're in from the shebang line
var set_editor_mode = function(code) {
  var first = code.split("\n")[0]
  if (first.substr(0,2) != "#!") {
    scraperwiki.alert("Specify language in the first line!", "For example, put #!/usr/bin/ruby, #!/usr/bin/R or #!/usr/bin/python.", false)
    return false
  }
  // Please add more as you need them and send us a pull request!
  if (first.indexOf("python") != -1) {
    editor.getSession().setMode("ace/mode/python")
  } else if (first.indexOf("ruby") != -1) {
    editor.getSession().setMode("ace/mode/ruby")
  } else if (first.indexOf("perl") != -1) {
    editor.getSession().setMode("ace/mode/perl")
  } else if (first.indexOf("node") != -1) {
    editor.getSession().setMode("ace/mode/javascript")
  } else if (first.indexOf("coffee") != -1) {
    editor.getSession().setMode("ace/mode/coffee")
  } else if (first.indexOf("clojure") != -1) {
    editor.getSession().setMode("ace/mode/clojure")
  } else if (first.indexOf("tcc") != -1) {
    editor.getSession().setMode("ace/mode/c_cpp")
  } else if (first.indexOf("R") != -1) {
    editor.getSession().setMode("ace/mode/r")
  } else if (first.indexOf("sh") != -1) {
    editor.getSession().setMode("ace/mode/sh")
  } else {
    editor.getSession().setMode("ace/mode/text")
  }
  return true
}

// Got a new state over ShareJS (from ourselves or remote clients)
var shared_state_update = function(op) {
  console.log("shared_state_update", state.snapshot)

  // Respond to the status change
  var new_status = state.snapshot.status
  if (new_status != status) {
    console.log("status change", status, "===>", new_status)
    if (new_status == "running") {
      poll_output()
    }
    status = new_status
    changing = ""
  }
  update_display_from_status(new_status)
}

// Show status in buttons - we have this as we can call it directly
// to make the run button seem more responsive
var update_display_from_status = function(use_status) {
  $("#run").removeClass("btn-primary").removeClass("btn-danger").removeClass("btn-warning").removeClass("btn-success")
  if (changing == "starting") {
    $("#run").text("Starting...").addClass("btn-warning")
  } else if (changing == "stopping") {
    $("#run").text("Stopping...").addClass("btn-danger")
  } else if (use_status == "running") {
    $("#run").text("Running...").addClass("btn-success")
  } else if (use_status == "nothing") {
    $("#run").text("Run!").addClass("btn-primary")
  }
}

// Show/hide things according to current state
var set_status = function(new_status) {
  if (new_status != "running" && new_status != "nothing") {
    scraperwiki.alert("Unknown new status!", new_status, true)
    return
  }

  // Tell other ShareJS clients the status has changed
  try {
    state.submitOp( {p:['status'],od:status,oi:new_status}) // XXX state.status for od?
  } catch (e) {
    scraperwiki.alert("Error saving to ShareJS!", e, true)
  }
}

// Console get more output
var poll_output = function() {
  scraperwiki.exec("./tool/enrunerate", function(text) {
    console.log("enrunerate:", text, "len:", text.length)
    set_status(text)

    // we poll one last time either way to be sure we get end of output...
    var again = false
    if (text == "running") {
      // ... but if script is still "running" we'll trigger the timer to do it again
      again = true
    }
    scraperwiki.exec("cat logs/out", function(text) {
      // XXX detect no file a better way
      if (text != "cat: logs/out: No such file or directory\n") {
        output.setValue(text)
      }
      output.clearSelection()
      if (again) {
        setTimeout(poll_output, 10)
      }
    }, handle_exec_error)
  }, handle_exec_error)
}

// Clear any errors
var clear_alerts = function() {
  $(".alert").remove()
}

// Save the code - optionally takes text of extra commands to also
// in the same "exec" and a callback to run when done
var save_code = function(callback) {
  clearTimeout(save_code) // stop any already scheduled timed saves
  var code = editor.getValue()
  if (code.length == 0 || code.charAt(code.length - 1) != "\n") {
    code += "\n" // we need a separator \n at the end of the file for the ENDOFSCRAPER heredoc below
  }
  var cmd = "cat >code/scraper.new.$$ <<ENDOFSCRAPER &&\n" + code + "ENDOFSCRAPER\n"
  cmd = cmd + "chmod a+x code/scraper.new.$$ && mv code/scraper.new.$$ code/scraper"
  scraperwiki.exec(cmd, function(text) {
    if (text != "") {
        scraperwiki.alert("Trouble saving code!", text, true)
        return
    }

    // Check actual content against saved - in case there was a change while we executed
    if (editor.getValue() == code) {
      console.log("Saved fine without interference")
      update_dirty(false)
    } else {
      console.log("Ooops, it got dirty while saving")
      update_dirty(true)
    }

    if (callback) {
      callback(text)
    }
  }, handle_exec_error)
}

// When the "documentation" button is pressed
var do_docs = function() {
  window.open("https://scraperwiki.com/docs/", "_blank")
}

// When the "bugs" button is pressed
var do_bugs = function() {
  window.open("https://github.com/frabcus/code-scraper-in-browser-tool/issues", "_blank")
}

// When the "keys" button is pressed
var do_keys = function() {
  window.open("https://github.com/ajaxorg/ace/wiki/Default-Keyboard-Shortcuts", "_blank")
}

// When the "run" button is pressed
var do_run = function() {
  // force a check that we have a shebang (#!) line
  clear_alerts()
  var code = editor.getValue()
  if (!set_editor_mode(code)) {
    return
  }

  // if it is already running, stop instead
  if (status == "running") {
    // make button show what we're doing
    changing = "stopping"
    update_display_from_status(status)

    scraperwiki.exec("./tool/enrunerate stop", function(text) {
      console.log("enrunerate stop:", text)
      // And tell all clients that we're now not running code (if we're not!)
      set_status(text)
    }, handle_exec_error)
    return
  }

  // to make button feel responsibe, temporarily show the wrong status
  // (until next operation makes it right)
  changing = "starting"
  update_display_from_status(status)
  output.setValue("")

  // Save code
  save_code(function (text) {
    // Then run it
    scraperwiki.exec("./tool/enrunerate run", function(text) {
      console.log("enrunerate run:", text)
      // And tell all clients that we're now running code (if we are!)
      set_status(text)
    }, handle_exec_error)
  })
}

// Main entry point
$(document).ready(function() {
  settings = scraperwiki.readSettings()
  $('#apikey').val(settings.source.apikey)

  // Create editor window, read only until it is ready
  editor = ace.edit("editor")
  editor.getSession().setUseSoftTabs(true)
  editor.setTheme("ace/theme/monokai")
  editor.setReadOnly(true)

  // Connect to sharejs server
  console.log("connecting...")
  connection = new sharejs.Connection('http://seagrass.goatchurch.org.uk/sharejs/channel')
  connection.on("error", function(e) {
      console.log("sharejs connection: error")
      clear_alerts()
      scraperwiki.alert("Editor is offline!", e, false)
      doneLoad = false
      editor.setReadOnly(true)
  })
  connection.on("ok", function(e) {
      console.log("sharejs connection: ok")
      clear_alerts()
      load_and_wire_up_editor()
      if (doneLoad) {
        editor.setReadOnly(false)
      }
  })
  console.log("...connected")

  // Create the console output window
  output = ace.edit("output")
  output.setTheme("ace/theme/monokai")
  // ... we use /bin/sh syntax highlighting, the only other at all
  // credible option for such varied output is plain text, which is dull.
  output.getSession().setMode("ace/mode/sh")
  output.setReadOnly(true)

  // Bind all the buttons to do something
  $('#docs').on('click', do_docs)
  $('#bugs').on('click', do_bugs)
  $('#keys').on('click', do_keys)
  $('#run').on('click', do_run)
  $(document).bind('keydown', 'ctrl+r', do_run)
})

<|endoftext|>