<commit_before>module.exports = function (grunt) {
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-bumpup');
    grunt.loadNpmTasks('grunt-tagrelease');

    // Project configuration.
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        watch: {
            less: {
                files: ['src/styles/**/*.less'],
                tasks: ['less:dev']
            },
            js: {
                files: ['src/scripts/**/*.js'],
                tasks: ['concat', 'uglify']
            }
        },
        bumpup: {
            options: {
                updateProps: {
                    pkg: 'package.json'
                }
            },
            files: ['package.json']
        },
        tagrelease: '<%= pkg.version %>',
        ver: {
            files: ['src/scripts/core/version.js']
        },
        less: {
            dev: {
                files: {
                    'examples/css/contour.css': 'src/styles/contour.less'
                }
            },
            // to generate distribution uncompressed versions
            uncompressed: {
                options: {
                    compress: false,
                    ieCompat: true,
                    yuicompress: false // Off until calc() bug is addressed.  https://github.com/yui/yuicompressor/issues/59
                },
                files: {
                    'dist/contour.css': 'src/styles/contour.less',
                    'examples/css/contour.css': 'src/styles/contour.less'
                }
            },
            production: {
                options: {
                    compress: true,
                    ieCompat: true,
                    yuicompress: false // Off until calc() bug is addressed.  https://github.com/yui/yuicompressor/issues/59
                },
                files: {
                    'dist/contour.min.css': 'src/styles/contour.less',
                    'examples/css/contour.min.css': 'src/styles/contour.less'
                }
            }
        },
        scripts: {
            libs: [
            ],
            core: [
                'src/scripts/core/contour-utils.js',
                'src/scripts/core/contour.js',
                'src/scripts/core/axis/y-axis.js',
                'src/scripts/core/cartesian.js',
                'src/scripts/core/version.js',

                'src/scripts/core/**/*.js',
            ],
            vis: [
                'src/scripts/visualizations/**/*.js'
            ],
            connectors: [
                'src/scripts/connectors/**/*.js'
            ]

        },
        include: {
            core: {
                src: ['<%= scripts.core %>'],
                dest: 'web/include/login/scripts.html'
            }
        },
        concat: {
            all: {
                src: ['src/scripts/header.js', '<%= scripts.core %>', '<%= scripts.vis %>', '<%= scripts.connectors %>', 'src/scripts/footer.js'],
                dest: 'dist/contour.js'
            }
        },
        uglify: {
            options: {
                sourceMap: function (fileName) {
                    return fileName.replace(/\.js$/, '.map');
                },
                banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
                        '<%= grunt.template.today("yyyy-mm-dd") %> */\n'
            },
            production: {
                files: {
                    'dist/contour.min.js': ['dist/contour.js']
                }
            }
        }
    });

    // Default task.
    grunt.registerTask('default', ['less:dev', 'watch:less']);

    grunt.registerTask('production', function (type) {
        type = type ? type : 'patch';
        ['bumpup:' + type, 'ver', 'concat', 'uglify', 'less:production', 'tagrelease'].forEach(function (task) {
            grunt.task.run(task);
        });
    });

    grunt.registerTask('linked', ['concat', 'uglify', 'less:uncompressed', 'less:production', 'watch']);

    grunt.registerMultiTask('ver', 'Update version file with what\'s in package.json', function () {
        var pkg = require('./package.json');
        this.data.forEach(function (f) {
            console.log('Updating ' + f + ' to version ' + pkg.version);
            grunt.file.write(f, "Contour.version = '" + pkg.version + "';");
        });
    });

    grunt.registerMultiTask('templates', 'Compiles underscore templates', function () {
        var _ = require('lodash');
        var options = this.options({
            separator: grunt.util.linefeed,
            templateSettings: {},
            templateNamespace: 'tmpl'
        });

        function getNamespace(ns) {
            var output = [];
            var curPath = 'App';
            if (ns !== 'App') {
                var nsParts = ns.split('.');
                nsParts.forEach(function(curPart, index) {
                    if (curPart !== 'App') {
                        curPath += '[' + JSON.stringify(curPart) + ']';
                        output.push(curPath + ' = ' + curPath + ' || {};');
                    }
                });
            }

            return {
                namespace: curPath,
                declaration: output.join('\n')
            };
        }

        this.files.forEach(function (f) {
            var namespaces = [];
            var output = f.src.filter(function (path) {
                if (!grunt.file.exists(path)) {
                    grunt.log.warn('Source file "' + filepath + '" not found.');
                    return false;
                } else {
                    return true;
                }
            }).map(function (path) {
                var src = grunt.file.read(path);
                var parts = path.match(/.+\/(?:template|tmpl)(?:s)?\/(.+).html/i)[1].split('/');
                var ns = _.initial(parts).join('.').replace(/(-.)/g, function () {
                    return arguments[0].substring(1).toUpperCase();
                });
                var name = _.last(parts).replace(/(-.)/g, function () {
                    return arguments[0].substring(1).toUpperCase();
                });
                var nsInfo = getNamespace([options.templateNamespace, ns].join('.'));
                var compiled;

                try {
                    compiled = _.template(src, false, options.templateSettings).source;
                } catch (e) {
                    grunt.log.error(e);
                    grunt.fail.warn('_.template failed to compile in file "' + filepath + '"');
                }

                if (options.prettify) {
                    compiled = compiled.replace(new RegExp('\n', 'g'), '');
                }

                namespaces = [].concat(namespaces, nsInfo.declaration.split('\n'));

                return nsInfo.namespace + '["' + name + '"] = ' + compiled + ';';
            });

            if (output.length < 1) {
                grunt.log.warn('Destination not written because compiled files were empty.');
            } else {
                if (options.templateNamespace !== false) {
                    output.unshift(_.uniq(namespaces).join('\n'));
                }

                output.unshift('(function (App, undefined) {');
                output.push('})( this["' + options.namespace + '"]);');

                grunt.file.write(f.dest, output.join(grunt.util.normalizelf(options.separator)));
                grunt.log.writeln('File "' + f.dest + '" created.');
            }
        });

    });

    grunt.registerMultiTask('include', 'Inserts all required script tags into the html at a special marked insertion point', function () {
        var _ = require('lodash');
        var options = this.options({
            separator: grunt.util.linefeed,
            attributes: '',
            webRoot: 'web/'
        });
        var reRoot = new RegExp('^' + options.webRoot);

        this.files.forEach(function (f) {
            var output = f.src.filter(function (path) {
                if (!grunt.file.exists(path)) {
                    grunt.log.warn('Source file "' + filepath + '" not found.');
                    return false;
                } else {
                    return true;
                }
            }).map(function (path) {
                return _.compact(['<script', options.attributes, 'src="' + path.replace(reRoot, '') + '"></script>']).join(' ');
            });

            if (output.length < 1) {
                grunt.log.warn('Destination not written because compiled files were empty.');
            } else {
                grunt.file.write(f.dest, output.join(grunt.util.normalizelf(options.separator)));
                grunt.log.writeln('File "' + f.dest + '" created.');
            }
        });

    });

};
<commit_msg>fixed gruntfile to compile uncompressed css file for production releases<commit_after>module.exports = function (grunt) {
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-bumpup');
    grunt.loadNpmTasks('grunt-tagrelease');

    // Project configuration.
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        watch: {
            less: {
                files: ['src/styles/**/*.less'],
                tasks: ['less:dev']
            },
            js: {
                files: ['src/scripts/**/*.js'],
                tasks: ['concat', 'uglify']
            }
        },
        bumpup: {
            options: {
                updateProps: {
                    pkg: 'package.json'
                }
            },
            files: ['package.json']
        },
        tagrelease: '<%= pkg.version %>',
        ver: {
            files: ['src/scripts/core/version.js']
        },
        less: {
            dev: {
                files: {
                    'examples/css/contour.css': 'src/styles/contour.less'
                }
            },
            // to generate distribution uncompressed versions
            uncompressed: {
                options: {
                    compress: false,
                    ieCompat: true,
                    yuicompress: false // Off until calc() bug is addressed.  https://github.com/yui/yuicompressor/issues/59
                },
                files: {
                    'dist/contour.css': 'src/styles/contour.less',
                    'examples/css/contour.css': 'src/styles/contour.less'
                }
            },
            production: {
                options: {
                    compress: true,
                    ieCompat: true,
                    yuicompress: false // Off until calc() bug is addressed.  https://github.com/yui/yuicompressor/issues/59
                },
                files: {
                    'dist/contour.min.css': 'src/styles/contour.less',
                    'examples/css/contour.min.css': 'src/styles/contour.less'
                }
            }
        },
        scripts: {
            libs: [
            ],
            core: [
                'src/scripts/core/contour-utils.js',
                'src/scripts/core/contour.js',
                'src/scripts/core/axis/y-axis.js',
                'src/scripts/core/cartesian.js',
                'src/scripts/core/version.js',

                'src/scripts/core/**/*.js',
            ],
            vis: [
                'src/scripts/visualizations/**/*.js'
            ],
            connectors: [
                'src/scripts/connectors/**/*.js'
            ]

        },
        include: {
            core: {
                src: ['<%= scripts.core %>'],
                dest: 'web/include/login/scripts.html'
            }
        },
        concat: {
            all: {
                src: ['src/scripts/header.js', '<%= scripts.core %>', '<%= scripts.vis %>', '<%= scripts.connectors %>', 'src/scripts/footer.js'],
                dest: 'dist/contour.js'
            }
        },
        uglify: {
            options: {
                sourceMap: function (fileName) {
                    return fileName.replace(/\.js$/, '.map');
                },
                banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
                        '<%= grunt.template.today("yyyy-mm-dd") %> */\n'
            },
            production: {
                files: {
                    'dist/contour.min.js': ['dist/contour.js']
                }
            }
        }
    });

    // Default task.
    grunt.registerTask('default', ['less:dev', 'watch:less']);

    grunt.registerTask('production', function (type) {
        type = type ? type : 'patch';
        ['bumpup:' + type, 'ver', 'concat', 'uglify', 'less:production', 'less:uncompressed', 'tagrelease'].forEach(function (task) {
            grunt.task.run(task);
        });
    });

    grunt.registerTask('linked', ['concat', 'uglify', 'less:uncompressed', 'less:production', 'watch']);

    grunt.registerMultiTask('ver', 'Update version file with what\'s in package.json', function () {
        var pkg = require('./package.json');
        this.data.forEach(function (f) {
            console.log('Updating ' + f + ' to version ' + pkg.version);
            grunt.file.write(f, "Contour.version = '" + pkg.version + "';");
        });
    });

    grunt.registerMultiTask('templates', 'Compiles underscore templates', function () {
        var _ = require('lodash');
        var options = this.options({
            separator: grunt.util.linefeed,
            templateSettings: {},
            templateNamespace: 'tmpl'
        });

        function getNamespace(ns) {
            var output = [];
            var curPath = 'App';
            if (ns !== 'App') {
                var nsParts = ns.split('.');
                nsParts.forEach(function(curPart, index) {
                    if (curPart !== 'App') {
                        curPath += '[' + JSON.stringify(curPart) + ']';
                        output.push(curPath + ' = ' + curPath + ' || {};');
                    }
                });
            }

            return {
                namespace: curPath,
                declaration: output.join('\n')
            };
        }

        this.files.forEach(function (f) {
            var namespaces = [];
            var output = f.src.filter(function (path) {
                if (!grunt.file.exists(path)) {
                    grunt.log.warn('Source file "' + filepath + '" not found.');
                    return false;
                } else {
                    return true;
                }
            }).map(function (path) {
                var src = grunt.file.read(path);
                var parts = path.match(/.+\/(?:template|tmpl)(?:s)?\/(.+).html/i)[1].split('/');
                var ns = _.initial(parts).join('.').replace(/(-.)/g, function () {
                    return arguments[0].substring(1).toUpperCase();
                });
                var name = _.last(parts).replace(/(-.)/g, function () {
                    return arguments[0].substring(1).toUpperCase();
                });
                var nsInfo = getNamespace([options.templateNamespace, ns].join('.'));
                var compiled;

                try {
                    compiled = _.template(src, false, options.templateSettings).source;
                } catch (e) {
                    grunt.log.error(e);
                    grunt.fail.warn('_.template failed to compile in file "' + filepath + '"');
                }

                if (options.prettify) {
                    compiled = compiled.replace(new RegExp('\n', 'g'), '');
                }

                namespaces = [].concat(namespaces, nsInfo.declaration.split('\n'));

                return nsInfo.namespace + '["' + name + '"] = ' + compiled + ';';
            });

            if (output.length < 1) {
                grunt.log.warn('Destination not written because compiled files were empty.');
            } else {
                if (options.templateNamespace !== false) {
                    output.unshift(_.uniq(namespaces).join('\n'));
                }

                output.unshift('(function (App, undefined) {');
                output.push('})( this["' + options.namespace + '"]);');

                grunt.file.write(f.dest, output.join(grunt.util.normalizelf(options.separator)));
                grunt.log.writeln('File "' + f.dest + '" created.');
            }
        });

    });

    grunt.registerMultiTask('include', 'Inserts all required script tags into the html at a special marked insertion point', function () {
        var _ = require('lodash');
        var options = this.options({
            separator: grunt.util.linefeed,
            attributes: '',
            webRoot: 'web/'
        });
        var reRoot = new RegExp('^' + options.webRoot);

        this.files.forEach(function (f) {
            var output = f.src.filter(function (path) {
                if (!grunt.file.exists(path)) {
                    grunt.log.warn('Source file "' + filepath + '" not found.');
                    return false;
                } else {
                    return true;
                }
            }).map(function (path) {
                return _.compact(['<script', options.attributes, 'src="' + path.replace(reRoot, '') + '"></script>']).join(' ');
            });

            if (output.length < 1) {
                grunt.log.warn('Destination not written because compiled files were empty.');
            } else {
                grunt.file.write(f.dest, output.join(grunt.util.normalizelf(options.separator)));
                grunt.log.writeln('File "' + f.dest + '" created.');
            }
        });

    });

};
<|endoftext|>


<commit_before>var http = require('http');
var https = require('https');
var Stream = require('stream').Stream;
var url = require('url');
var Rx = require('Rx');
var Builder = require('./builder');

var Revolt = module.exports = function() {
  if (!(this instanceof Revolt)) {
    return new Revolt();
  }

  this.builder = new Builder();
  this.built = null;
};

Revolt.prototype.use = function(middleware) {
  this.builder.use(middleware);
  return this;
};

Revolt.prototype.build = function() {
  this.built = this.builder.build();
  return this;
};

Revolt.prototype.request = function(options) {
  var self = this;

  options = options || {};
  options.method = options.method || 'GET';
  options.headers = options.headers || {};

  var uri = options.uri || options.url;
  var parsed = url.parse(uri);

  if (uri) {
    options.hostname = parsed.hostname;
    options.port = parsed.port;
    options.path = parsed.path;
    options.auth = parsed.auth;
  };

  var mod = parsed.protocol === 'https:' ? https : http;

  var observable = Rx.Observable.create(function(observer) {
    self.builder.run(function(env, next) {
      var req = env.request = mod.request(env.options);

      req.on('error', function(err) {
        observer.onError(err);
      });

      req.on('response', function(res) {
        res.on('error', function(err) {
          observer.onError(err);
        });

        env.response = res;
        next(env);
      });

      if (env.options.body) {
        if (env.options.body instanceof Stream) {
          env.request.pipe(env.options.body)
        } else {
          env.request.end(env.options.body);
        }
      } else {
        env.request.end();
      }
    });

    self.builder.use(function(handle) {
      handle('response', { affinity: 'sink' }, function(env, next) {
        observer.onNext(env);
        observer.onCompleted();
        next(env);
      });
    });

    self.build();

    var env = {
      request: null,
      options: options,
      response: null
    };

    self.built.flow(env);
  });

  return observable;
};

Revolt.prototype.get = function(requestUrl, options) {
  var options = {
    method: 'GET',
    uri: requestUrl
  };

  return this.request(options);
};

Revolt.prototype.head = function(requestUrl) {
  var options = {
    method: 'HEAD',
    uri: requestUrl
  };

  return this.request(options);
};

Revolt.prototype.post = function(requestUrl, data) {
  var options = {
    method: 'POST',
    uri: requestUrl,
    body: data
  };

  return this.request(options);
};

Revolt.prototype.put = function(requestUrl, data) {
  var options = {
    method: 'PUT',
    uri: requestUrl,
    body: data
  };

  return this.request(options);
};

Revolt.prototype.del = function(requestUrl) {
  var options = {
    method: 'DELETE',
    uri: requestUrl
  };

  return this.request(options);
};

Revolt.prototype.options = function(requestUrl) {
  var options = {
    method: 'OPTIONS',
    uri: requestUrl
  };

  return this.request(options);
};

Revolt.buffer = function(response) {
  return Rx.Node.fromStream(response)
    .reduce(function(acc, data) {
      acc.length += data.length;
      acc.buffers.push(data);

      return acc;
    }, { length: 0, buffers: [] })
    .map(function(body) {
      return Buffer.concat(body.buffers, body.length);
    })
};
<commit_msg>Setting env.response.body.<commit_after>var http = require('http');
var https = require('https');
var Stream = require('stream').Stream;
var url = require('url');
var Rx = require('Rx');
var Builder = require('./builder');

var Revolt = module.exports = function() {
  if (!(this instanceof Revolt)) {
    return new Revolt();
  }

  this.builder = new Builder();
  this.built = null;
};

Revolt.prototype.use = function(middleware) {
  this.builder.use(middleware);
  return this;
};

Revolt.prototype.build = function() {
  this.built = this.builder.build();
  return this;
};

Revolt.prototype.request = function(options) {
  var self = this;

  options = options || {};
  options.method = options.method || 'GET';
  options.headers = options.headers || {};

  var uri = options.uri || options.url;
  var parsed = url.parse(uri);

  if (uri) {
    options.hostname = parsed.hostname;
    options.port = parsed.port;
    options.path = parsed.path;
    options.auth = parsed.auth;
  };

  var mod = parsed.protocol === 'https:' ? https : http;

  var observable = Rx.Observable.create(function(observer) {
    self.builder.run(function(env, next) {
      var req = env.request = mod.request(env.options);

      req.on('error', function(err) {
        observer.onError(err);
      });

      req.on('response', function(res) {
        res.on('error', function(err) {
          observer.onError(err);
        });

        env.response = res;
        env.response.body = res;
        next(env);
      });

      if (env.options.body) {
        if (env.options.body instanceof Stream) {
          env.request.pipe(env.options.body)
        } else {
          env.request.end(env.options.body);
        }
      } else {
        env.request.end();
      }
    });

    self.builder.use(function(handle) {
      handle('response', { affinity: 'sink' }, function(env, next) {
        observer.onNext(env);
        observer.onCompleted();
        next(env);
      });
    });

    self.build();

    var env = {
      request: null,
      options: options,
      response: null
    };

    self.built.flow(env);
  });

  return observable;
};

Revolt.prototype.get = function(requestUrl, options) {
  var options = {
    method: 'GET',
    uri: requestUrl
  };

  return this.request(options);
};

Revolt.prototype.head = function(requestUrl) {
  var options = {
    method: 'HEAD',
    uri: requestUrl
  };

  return this.request(options);
};

Revolt.prototype.post = function(requestUrl, data) {
  var options = {
    method: 'POST',
    uri: requestUrl,
    body: data
  };

  return this.request(options);
};

Revolt.prototype.put = function(requestUrl, data) {
  var options = {
    method: 'PUT',
    uri: requestUrl,
    body: data
  };

  return this.request(options);
};

Revolt.prototype.del = function(requestUrl) {
  var options = {
    method: 'DELETE',
    uri: requestUrl
  };

  return this.request(options);
};

Revolt.prototype.options = function(requestUrl) {
  var options = {
    method: 'OPTIONS',
    uri: requestUrl
  };

  return this.request(options);
};

Revolt.buffer = function(response) {
  return Rx.Node.fromStream(response)
    .reduce(function(acc, data) {
      acc.length += data.length;
      acc.buffers.push(data);

      return acc;
    }, { length: 0, buffers: [] })
    .map(function(body) {
      return Buffer.concat(body.buffers, body.length);
    })
};
<|endoftext|>


<commit_before>// SAMPLE DATA (Can be used for testing!)
// A sample pad
var pad = {
	name: 'Sample Pad',
	color: 'red',
	contents: [
	{
		program: "Firefox",
		files: ["http://www.illinois.edu", "http://www.cnn.com", "http://www.piazza.com"]
	},{
		program: "Evince",
        //Jon's machine
		//files: ["home/jmpierc2/cs233/lab7.pdf", "home/jmpierc2/cs233/lab8.pdf"]
        //Andrew's machine
        files: ["~/research/functional_logic/Curry_report.pdf",
            "~/research/functional_logic/Hanus_Multi-paradigm_Declarative_Languages.pdf"]
	},{
		program: "Calculator",
		files: []
	},{
		program: "gVim",
        //Jon's machine
		//files: ["home/jmpierc2/lilypad/frontend/frontend.js", "home/jmpierc2/lilypad/frontend/lilylibrary.js", "home/jmpierc2/lilypad/backend/backend.py", "home/jmpierc2/lilypad/backend/config.json"]
        //Andrew's machine
		files: ["~/classes/cs465/LilyPad/frontend/frontend.js", "~/classes/cs465/LilyPad/frontend/lilylibrary.js", "~/classes/cs465/LilyPad/backend/backend.py", "~/classes/cs465/LilyPad/backend/config.json"]
	}
	]
};
// A pad delta. Essentially a "contents" array like the one from above.
var paddelta = [
	{
		program: "Firefox",
		files: ["http://www.cnn.com", "http://www.twitter.com", "http://www.facebook.com"]
	},
	{
		program: "Kile",
        //Jon's machine
		//files: ["home/jmpierc2/cs473/hw4.tex", "home/jmpierc2/cs473/hw4.tex"]
        //Andrew's machine
		files: ["~/maude/maude-psl/masters/description.tex",
        "~/maude/maude-psl/masters/thesis.tex"]
	},
	{
		program: "Terminal",
		files: []
	},
	{
		program: "Evince",
		files: ["~/maude/maude-psl/masters/thesis.pdf",
        "~/maude/maude-npa-v2_0/maude-npa-manual-v2_0.pdf"]
	}
];
// END SAMPLE DATA

// Ideally, this should be generated by the backend, and sent to
// generateShellScript. This would allow us to easily different commands for
// different platforms without affecting generateShellScript. However, since
// this thing is a prototype, I don't see
// any need to do whatever contortions are necessary to make that happen.
// You'd only care about that kind of flexibility if you were writing this
// thing for prime time, in which case we wouldn't be using the HTML-5, CSS,
// Python, Javascript contortion that we're using here.
var pgmCmdMap = {}
pgmCmdMap["Firefox"] = "firefox"
pgmCmdMap["Chrome"] = "google-chrome"
pgmCmdMap["Gedit"] = "gedit"
pgmCmdMap["GTKwave"] = "gtkwave"
pgmCmdMap["Adobe Reader"] = "acroread"
pgmCmdMap["Evince"] = "evince"
pgmCmdMap["Okular"] = "okular"
pgmCmdMap["Writer"] = "libreoffice --writer"
pgmCmdMap["Calc"] = "libreoffice --calc"
pgmCmdMap["Impress"] = "libreoffice --impress"
pgmCmdMap["Calculator"] = "gnome-calculator"
pgmCmdMap["Terminal"] = "gnome-terminal"
pgmCmdMap["Kile"] = "kile"
pgmCmdMap["TexWorks"] = "texworks"
pgmCmdMap["Sublime"] = "subl"
pgmCmdMap["gVim"] = "gvim -p"
pgmCmdMap["Emacs"] = "emacs"
pgmCmdMap["Eclipse"] = "eclipse"
pgmCmdMap["Gimp"] = "gimp"
pgmCmdMap["Image Viewer"] = "eog"
pgmCmdMap["RhythmBox"] = "rhythmbox"
pgmCmdMap["VLC"] = "vlc"

// generateShellScript
// input (Object): pad - A pad in the format of the sample one above
// output (String): A shell script in the format defined by the google doc
// author: Andrew
var generateShellScript = function(pad) {
    scriptText = ""
    var programName;
    var programFiles;
    var fileString;
    for (var i=0; i < pad.contents.length; i++){
        programName = pad.contents[i].program;
        programFiles = pad.contents[i].files;
        fileString = "";
        for (var j=0; j < programFiles.length; j++){
            fileString = fileString.concat(" ", programFiles[j]);
        }
        if (pgmCmdMap.hasOwnProperty(programName)){
            scriptText = scriptText.concat("( { ", pgmCmdMap[programName],
                    fileString, " ; } ) &\n");
        }
        else{
            throw(programName.concat( "does not exist in pgmCmdMap."));
        }
    }
    return scriptText
};
console.log(generateShellScript(pad))

// insertIntoPad
// Inserts every element of the delta into the pad, keeping track of paths
// that appear in both pad and delta, and are associated with the same program.
// input (Object): pad - What we are inserting to. A pad like the one above.
// input (Array): delta - Delta array (stuff we're adding), like the one above
// output (Array): Array of paths found in both pad and delta that are
// associated with the same program.
// author: Lorraine
var insertIntoPad = function(pad, delta) {
	var retArr = [];
	//var retCtr = 0;
	//for each program in delta
	for(var i = 0;i<delta.length;i++){
		//see if it is already in pad contents
		var hasProg = false;
		for(var j=0;j<pad.contents.length;j++){
			//if yes then
			if(pad.contents[j].program == delta[i].program){
				hasProg = true;
				//if there are files under that prog in delta
				if(0 != delta[i].files.length){
				  var arrEntry = new Object();
				  arrEntry.program = delta[i].program;
				  arrEntry.files = [];
				  var hasDuplicateFile = false;
				  //then for each of those files
				   for(var k = 0; k<delta[i].files.length;k++){
					var hasFile = false;
					//if in pad add to duplicates array entry
					for(var l = 0; l<pad.contents[j].files.length;l++){
					   if(pad.contents[j].files[l] == delta[i].files[k]){
						arrEntry.files.push(delta[i].files[k]);
						hasFile = true;
						hasDuplicateFile = true;
					   }
					}
					//else add to pad contents
					if(!hasFile){
						pad.contents[j].files.push(delta[i].files[k]);
					}
				   }
				   if(hasDuplicateFile){
					retArr.push(arrEntry);
				   }
				}
				//else add to duplicates
				else{
					retArr.push(delta[i]);
				}
			}
		}
		//if not in pad then add everything
		if(!hasProg){
			pad.contents.push(delta[i]);
		}

	}
	return retArr;
};

// getDefaultPrograms
// Given an array of paths, create a delta object like the example one above.
// Match each path to the appropriate default program from config.json.
// author: John
var getDefaultPrograms = function(paths, config) {

};

// getAlternativePrograms
// Depends on config.json!
// This function should have a way to detect URLs
// input (Array): A array of file/url paths
// output: A list of programs (array of display-name strings) that supports
// every filetype in the list.
// author: John
var getAlternativePrograms = function(paths, config) {

};

// switchToAlternative
// Switches the program associated with certain paths.
// Given a pad as input and a delta , adds the program/file pairings specified
// in delta to pad. Like insertIntoPad, this should return the list of
// paths that appear in both pad and delta and are associated with the same
// program.
// // author: Lorraine
var switchToAlternative = function(pad, delta) {

};

// getConciseProgramList
// Returns an array mapping program display names to the number of files
// associated with said program.
// input (Object): pad - A pad like the example one above
// output (Object array): Array of {name: "program display name", count: int}
// author: Andrew
var getConciseProgramList = function(pad) {
    var array = [];
    for (var i = 0; i < pad.contents.length; i++){
        array.push({name: pad.contents[i].program,
                    count: pad.contents[i].files.length});
    }
    return array
};

/*
var array = getConciseProgramList(pad);
for(var i = 0; i < array.length; i++)
{
    console.log(array[i]);
}
*/

// getStandalonePrograms
// Searches the config object, returns an array of all entries that are standalone programs
// author: Lorraine
var getStandalonePrograms = function(config) {
	var progs = config.programs;
	var ctr = 0;
	var retArr = [];
	for(var i = 0; i<progs.length;i++){
		if(true == progs[i].standalone){
			//add this program to the return array
			retArr[ctr] = progs[i];
			ctr++;
		}
	}
	return retArr;

};

// Given a program name string, returns the corresponding entry from the config.
// author: John
// Yes, I wrote this for you. Don't feel bad. -Jonathan
var getProgramInfo = function(name, config){
	for(var i = 0; i < config.programs.length; i++) {
		if(config.programs[i].display_name.toLowerCase() === name.toLowerCase()) {
			return config.programs[i];
		}
	}
};

//I stole this regex without shame from one searls at:
//https://gist.github.com/searls/1033143
//which is a Javascript form of the regex discussed by one
//John Gruber at:
//http://daringfireball.net/2010/07/improved_regex_for_matching_urls
var urlRegExp = /^((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))$/i;
//...The Internet is awesome.
//returns true if input (a string) is a valid URL.
// author: Andrew
var validateURL = function(input){
    return urlRegExp.test(input)
};

//console.log(validateURL("http://stackoverflow.com/questions/6298566/regex-match-whole-string"));
<commit_msg>Implemented generateShellScript, getConciseProgramList, and validateURL. Also tweaked the examples a little bit, so that the file directories matched files on my computer (I commented out Jon's example directories, so that the sample file directories can be easily swapped).<commit_after>// SAMPLE DATA (Can be used for testing!)
// A sample pad
var pad = {
	name: 'Sample Pad',
	color: 'red',
	contents: [
	{
		program: "Firefox",
		files: ["http://www.illinois.edu", "http://www.cnn.com", "http://www.piazza.com"]
	},{
		program: "Evince",
        //Jon's machine
		//files: ["home/jmpierc2/cs233/lab7.pdf", "home/jmpierc2/cs233/lab8.pdf"]
        //Andrew's machine
        files: ["~/research/functional_logic/Curry_report.pdf",
            "~/research/functional_logic/Hanus_Multi-paradigm_Declarative_Languages.pdf"]
	},{
		program: "Calculator",
		files: []
	},{
		program: "gVim",
        //Jon's machine
		//files: ["home/jmpierc2/lilypad/frontend/frontend.js", "home/jmpierc2/lilypad/frontend/lilylibrary.js", "home/jmpierc2/lilypad/backend/backend.py", "home/jmpierc2/lilypad/backend/config.json"]
        //Andrew's machine
		files: ["~/classes/cs465/LilyPad/frontend/frontend.js", "~/classes/cs465/LilyPad/frontend/lilylibrary.js", "~/classes/cs465/LilyPad/backend/backend.py", "~/classes/cs465/LilyPad/backend/config.json"]
	}
	]
};
// A pad delta. Essentially a "contents" array like the one from above.
var paddelta = [
	{
		program: "Firefox",
		files: ["http://www.cnn.com", "http://www.twitter.com", "http://www.facebook.com"]
	},
	{
		program: "Kile",
        //Jon's machine
		//files: ["home/jmpierc2/cs473/hw4.tex", "home/jmpierc2/cs473/hw4.tex"]
        //Andrew's machine
		files: ["~/maude/maude-psl/masters/description.tex",
        "~/maude/maude-psl/masters/thesis.tex"]
	},
	{
		program: "Terminal",
		files: []
	},
	{
		program: "Evince",
		files: ["~/maude/maude-psl/masters/thesis.pdf",
        "~/maude/maude-npa-v2_0/maude-npa-manual-v2_0.pdf"]
	}
];
// END SAMPLE DATA

// Ideally, this would be generated by the backend, and sent to
// generateShellScript. This would allow us to easily different commands for
// different platforms without affecting generateShellScript (it would also be
// necessary to allow the user to add/remove custom programs). However, since
// this thing is a prototype, I don't see
// any need to do whatever contortions are necessary to make that happen.
// You'd only care about that kind of flexibility if you were writing this
// thing for prime time, in which case we wouldn't be using the HTML-5, CSS,
// Python, Javascript contortion that we're using here.
// --Andrew
var pgmCmdMap = {}
pgmCmdMap["Firefox"] = "firefox"
pgmCmdMap["Chrome"] = "google-chrome"
pgmCmdMap["Gedit"] = "gedit"
pgmCmdMap["GTKwave"] = "gtkwave"
pgmCmdMap["Adobe Reader"] = "acroread"
pgmCmdMap["Evince"] = "evince"
pgmCmdMap["Okular"] = "okular"
pgmCmdMap["Writer"] = "libreoffice --writer"
pgmCmdMap["Calc"] = "libreoffice --calc"
pgmCmdMap["Impress"] = "libreoffice --impress"
pgmCmdMap["Calculator"] = "gnome-calculator"
pgmCmdMap["Terminal"] = "gnome-terminal"
pgmCmdMap["Kile"] = "kile"
pgmCmdMap["TexWorks"] = "texworks"
pgmCmdMap["Sublime"] = "subl"
pgmCmdMap["gVim"] = "gvim -p"
pgmCmdMap["Emacs"] = "emacs"
pgmCmdMap["Eclipse"] = "eclipse"
pgmCmdMap["Gimp"] = "gimp"
pgmCmdMap["Image Viewer"] = "eog"
pgmCmdMap["RhythmBox"] = "rhythmbox"
pgmCmdMap["VLC"] = "vlc"

// generateShellScript
// input (Object): pad - A pad in the format of the sample one above
// output (String): A shell script in the format defined by the google doc
// author: Andrew
var generateShellScript = function(pad) {
    scriptText = ""
    var programName;
    var programFiles;
    var fileString;
    for (var i=0; i < pad.contents.length; i++){
        programName = pad.contents[i].program;
        programFiles = pad.contents[i].files;
        fileString = "";
        for (var j=0; j < programFiles.length; j++){
            fileString = fileString.concat(" ", programFiles[j]);
        }
        if (pgmCmdMap.hasOwnProperty(programName)){
            scriptText = scriptText.concat("( { ", pgmCmdMap[programName],
                    fileString, " ; } ) &\n");
        }
        else{
            throw(programName.concat( "does not exist in pgmCmdMap."));
        }
    }
    return scriptText
};
//console.log(generateShellScript(pad))

// insertIntoPad
// Inserts every element of the delta into the pad, keeping track of paths
// that appear in both pad and delta, and are associated with the same program.
// input (Object): pad - What we are inserting to. A pad like the one above.
// input (Array): delta - Delta array (stuff we're adding), like the one above
// output (Array): Array of paths found in both pad and delta that are
// associated with the same program.
// author: Lorraine
var insertIntoPad = function(pad, delta) {
	var retArr = [];
	//var retCtr = 0;
	//for each program in delta
	for(var i = 0;i<delta.length;i++){
		//see if it is already in pad contents
		var hasProg = false;
		for(var j=0;j<pad.contents.length;j++){
			//if yes then
			if(pad.contents[j].program == delta[i].program){
				hasProg = true;
				//if there are files under that prog in delta
				if(0 != delta[i].files.length){
				  var arrEntry = new Object();
				  arrEntry.program = delta[i].program;
				  arrEntry.files = [];
				  var hasDuplicateFile = false;
				  //then for each of those files
				   for(var k = 0; k<delta[i].files.length;k++){
					var hasFile = false;
					//if in pad add to duplicates array entry
					for(var l = 0; l<pad.contents[j].files.length;l++){
					   if(pad.contents[j].files[l] == delta[i].files[k]){
						arrEntry.files.push(delta[i].files[k]);
						hasFile = true;
						hasDuplicateFile = true;
					   }
					}
					//else add to pad contents
					if(!hasFile){
						pad.contents[j].files.push(delta[i].files[k]);
					}
				   }
				   if(hasDuplicateFile){
					retArr.push(arrEntry);
				   }
				}
				//else add to duplicates
				else{
					retArr.push(delta[i]);
				}
			}
		}
		//if not in pad then add everything
		if(!hasProg){
			pad.contents.push(delta[i]);
		}

	}
	return retArr;
};

// getDefaultPrograms
// Given an array of paths, create a delta object like the example one above.
// Match each path to the appropriate default program from config.json.
// author: John
var getDefaultPrograms = function(paths, config) {

};

// getAlternativePrograms
// Depends on config.json!
// This function should have a way to detect URLs
// input (Array): A array of file/url paths
// output: A list of programs (array of display-name strings) that supports
// every filetype in the list.
// author: John
var getAlternativePrograms = function(paths, config) {

};

// switchToAlternative
// Switches the program associated with certain paths.
// Given a pad as input and a delta , adds the program/file pairings specified
// in delta to pad. Like insertIntoPad, this should return the list of
// paths that appear in both pad and delta and are associated with the same
// program.
// // author: Lorraine
var switchToAlternative = function(pad, delta) {

};

// getConciseProgramList
// Returns an array mapping program display names to the number of files
// associated with said program.
// input (Object): pad - A pad like the example one above
// output (Object array): Array of {name: "program display name", count: int}
// author: Andrew
var getConciseProgramList = function(pad) {
    var array = [];
    for (var i = 0; i < pad.contents.length; i++){
        array.push({name: pad.contents[i].program,
                    count: pad.contents[i].files.length});
    }
    return array
};

/*
var array = getConciseProgramList(pad);
for(var i = 0; i < array.length; i++)
{
    console.log(array[i]);
}
*/

// getStandalonePrograms
// Searches the config object, returns an array of all entries that are standalone programs
// author: Lorraine
var getStandalonePrograms = function(config) {
	var progs = config.programs;
	var ctr = 0;
	var retArr = [];
	for(var i = 0; i<progs.length;i++){
		if(true == progs[i].standalone){
			//add this program to the return array
			retArr[ctr] = progs[i];
			ctr++;
		}
	}
	return retArr;

};

// Given a program name string, returns the corresponding entry from the config.
// author: John
// Yes, I wrote this for you. Don't feel bad. -Jonathan
var getProgramInfo = function(name, config){
	for(var i = 0; i < config.programs.length; i++) {
		if(config.programs[i].display_name.toLowerCase() === name.toLowerCase()) {
			return config.programs[i];
		}
	}
};

//I stole this regex without shame from one searls at:
//https://gist.github.com/searls/1033143
//which is a Javascript form of the regex discussed by one
//John Gruber at:
//http://daringfireball.net/2010/07/improved_regex_for_matching_urls
var urlRegExp = /^((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))$/i;
//...The Internet is awesome.
//returns true if input (a string) is a valid URL.
// author: Andrew
var validateURL = function(input){
    return urlRegExp.test(input)
};

//console.log(validateURL("http://stackoverflow.com/questions/6298566/regex-match-whole-string"));
<|endoftext|>


<commit_before>TileMill.mml = {};
TileMill.mss = {};
TileMill.customSrs = [];

TileMill.mss.generate = function(mss) {
  var output = [];
  for (var i in mss) {
    output.push(i + ' {');
    if (mss[i]) {
      for (var j in mss[i]) {
        output.push('  ' + j + ': ' + mss[i][j] + ';');
      }
    }
    output.push('}');
    output.push('');
  }
  return output.join("\n");
};

TileMill.mml.generate = function(mml) {
  // We can't store the MML in an HTML template because the template engine
  // strips out all whitespace. While this is ok for HTML templates, which just
  // get put in the DOM, we want the MML that we end up with to be readable by
  // humans. Thus, we have to keep it entirely in JavaScript and join the
  // lines together at the end of the script.
  var output = ['<' + '?xml version="1.0" encoding="utf-8"?>',
  '<!DOCTYPE Map[',
  '  <!ENTITY srs900913 "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs">',
  '  <!ENTITY srsWGS84 "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs">',
  ']>'];

  output.push('<Map srs="&srs900913;">');

  // Add metadata to the MML file.
  if (mml.metadata) {
    output.push('  <![CDATA[ ' + JSON.stringify(mml.metadata) + ' ]]>');
  }

  // Add the stylesheets to the MML file.
  if (mml.stylesheets) {
    for (i = 0; i < mml.stylesheets.length; i++) {
      output.push('  <Stylesheet src="' + mml.stylesheets[i] + '" />');
    }
  }

  // And add the layers.
  if (mml.layers) {
    for (i = 0; i < mml.layers.length; i++) {
      var layer = mml.layers[i], layerDef = '  <Layer';
      if (layer.id) {
        layerDef += ' id="' + layer.id + '"';
      }
      if (layer.classes) {
        layerDef += ' class="' + layer.classes + '"';
      }
      if (!layer.srs) {
        // Detect SRS.
        layer.srs = 'WGS84';
      }
      layerDef += ' srs="&srs' + layer.srs + ';"';
      if (mml.layers[i].status != undefined && !mml.layers[i].status) {
        layerDef += ' status="off"';
      }
      layerDef += '>';
      output.push(layerDef);
      output.push('    <Datasource>');
      output.push('      <Parameter name="file">' + layer.file + '</Parameter>');
      if (!layer.type) {
        layer.type = 'shape';
      }
      output.push('      <Parameter name="type">' + layer.type + '</Parameter>');
      if (layer.id) {
        output.push('      <Parameter name="id">' + layer.id + '</Parameter>');
      }
      output.push('    </Datasource>');
      output.push('  </Layer>');
    }
  }
  output.push('</Map>');
  return output.join("\n");
};

TileMill.mml.parseMML = function(mml) {
  var parsed = {
    metadata: {},
    stylesheets: [],
    layers: [],
  };
  // Parse metadata.
  var matches = mml.match(/\<\!\[CDATA\[(.+)\]\]\>/);
  if (matches && matches[1]) {
    parsed.metadata = eval('(' + matches[1] + ')');
  }

  // Parse stylesheets.
  $(mml).find('Stylesheet').each(function() {
    if ($(this).attr('src')) {
      parsed.stylesheets.push($(this).attr('src'));
    }
  });
  // Parse layers.
  $(mml).find('Layer').each(function() {
    var status = $(this).attr('status');
    if (status == 'undefined' || status == undefined || status == 'on') {
      status = true;
    }
    else {
      status = false;
    }
    var classes = '';
    if ($(this).attr('class')) {
      classes = $(this).attr('class');
    }
    var srs = $(this).attr('srs'), parsed_srs = srs.replace(/^&srs(.*);$/, '$1');
    if (parsed_srs == srs) {
      var pass = false;
      for (var key in TileMill.customSrs) {
        if (TileMill.customSrs[key] == srs) {
          pass = true;
          continue;
        }
      }
      if (!pass) {
        TileMill.customSrs.push(srs);
      }
    }
    else {
      srs = parsed_srs;
    }
    parsed.layers.push({
      classes: classes,
      id: $(this).attr('id'),
      status: status,
      file: $(this).find('Datasource Parameter[name=file]').text(),
      srs: srs
    });
  });
  return parsed;
};

TileMill.mml.add = function(options, layers) {
  var name = [];
  if (options.id) {
    name.push('#' + options.id);
  }
  if (options.classes.length) {
    name.push('.' + options.classes.split(' ').join(', .'));
  }
  var status = options.status == 'true' || options.status == true;
  var li = $(TileMill.template('layers-li', {name: name.join(', '), status: status}));

  $('ul.sidebar-content', layers).prepend(li);

  $('a.layer-delete', li).click(function() {
    if (confirm('Are you sure you want to delete this layer?')) {
      $(this).parents('li').hide('fast', function() {
        $(this).remove();
        TileMill.project.changed();
      });
    }
    return false;
  });

  $('a.layer-inspect', li).click(function() {
    // @TODO refactor this out.
    if (!$(this).parents('li').data('tilemill')['id']) {
      alert('You need to add an id to a field and save to inspect it.');
      return false;
    }
    $('#inspector .sidebar-header h2').html('Layers » ' + $(this).parents('li').find('label').text());
    $('#layers').hide();
    $('#inspector').show();
    TileMill.inspector.inspect($(this).parents('li').data('tilemill').id, false);
    TileMill.inspector.page = 0;
    return false;
  });

  $('a.layer-edit', li).click(function() {
    var popup = $(TileMill.template('popup-layer', {submit:'Save'}));

    // Populate form values.
    var options = $(this).parents('li').data('tilemill');
    for (option in options) {
      $('#' + option, popup).val(options[option]).end();
    }

    TileMill.popup.show({content: popup, title: 'Edit layer'});

    // Create reference to layer li for submit handler to find.
    $('form', popup).data('li', $(this).parents('li'));

    // Add submit handler.
    $('form', popup).validate({
      errorLabelContainer: 'form .messages',
      submitHandler: function(form) {
        var layer = {
          classes: $('input#classes', form).val(),
          id: $('input#id', form).val(),
          file: $('input#file', form).val(),
          srs: $('select#srs', form).val(),
          status: 'true'
        };
        var name = [];
        if (layer.id) {
          name.push('#' + layer.id);
        }
        if (layer.classes) {
          name.push('.' + layer.classes.split(' ').join(', .'));
        }
        var li = $(form).data('li');
        $(li)
          .data('tilemill', layer)
          .find('label').text(name.join(', '));
        TileMill.popup.hide();
        TileMill.project.changed();
        return false;
      }
    });
    return false;
  });

  // Attach layer data to li element.
  li.data('tilemill', options);
};

TileMill.mml.save = function(data) {
  filename = [TileMill.settings.type, TileMill.settings.id, TileMill.settings.id + '.mml'].join('/');
  TileMill.backend.post(filename, data);
};

/**
 * Generate the URL of the current project .mml file.
 */
TileMill.mml.url = function(options) {
  if (!options) {
    var options = {};
  }
  options = $.extend({ timestamp: true, encode: true }, options);
  var url = TileMill.backend.url(TileMill.settings.filename);
  if (options.timestamp) {
    url += '&c=' + TileMill.uniq;
  }
  if (options.encode) {
    url = Base64.urlsafe_encode(url);
  }
  return url;
};

// @TODO: Move more of this to the controller.
TileMill.mml.init = function() {
  var layers = $(TileMill.template('layers', {}));
  var l = TileMill.mml.parseMML(TileMill.settings.mml).layers;
  for (var layer in l) {
    TileMill.mml.add(l[layer], layers);
  }
  $('ul.sidebar-content', layers).sortable({ axis: 'y', handle: 'div.handle', change: TileMill.project.changed });

  TileMill.inspector.load();
  for (var i in TileMill.customSrs) {
    var srs = TileMill.customSrs[i];
    if (srs.length > 23) {
      srs = srs.substr(0, 20) + '...';
    }
    $('select#srs').append('<option value="' + TileMill.customSrs[i].replace('"', '\\"') + '">' + srs + "</option>");
  }

  $('a#layers-add', layers).click(function() {
    var popup = $(TileMill.template('popup-layer', {submit: 'Add layer'}));
    TileMill.popup.show({content: popup, title: 'Add layer'});

    // Add submit handler.
    $('form', popup).validate({
      errorLabelContainer: 'form .messages',
      submitHandler: function(form) {
        var layer = {
          classes: $('input#classes', form).val(),
          id: $('input#id', form).val(),
          file: $('input#file', form).val(),
          srs: $('select#srs', form).val(),
          status: 'true'
        };
        TileMill.mml.add(layer, $('#layers'));
        TileMill.popup.hide();
        TileMill.project.changed();
        return false;
      }
    });
    return false;
  });

  return layers;
};
<commit_msg>Bad wipeout of page value.<commit_after>TileMill.mml = {};
TileMill.mss = {};
TileMill.customSrs = [];

TileMill.mss.generate = function(mss) {
  var output = [];
  for (var i in mss) {
    output.push(i + ' {');
    if (mss[i]) {
      for (var j in mss[i]) {
        output.push('  ' + j + ': ' + mss[i][j] + ';');
      }
    }
    output.push('}');
    output.push('');
  }
  return output.join("\n");
};

TileMill.mml.generate = function(mml) {
  // We can't store the MML in an HTML template because the template engine
  // strips out all whitespace. While this is ok for HTML templates, which just
  // get put in the DOM, we want the MML that we end up with to be readable by
  // humans. Thus, we have to keep it entirely in JavaScript and join the
  // lines together at the end of the script.
  var output = ['<' + '?xml version="1.0" encoding="utf-8"?>',
  '<!DOCTYPE Map[',
  '  <!ENTITY srs900913 "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs">',
  '  <!ENTITY srsWGS84 "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs">',
  ']>'];

  output.push('<Map srs="&srs900913;">');

  // Add metadata to the MML file.
  if (mml.metadata) {
    output.push('  <![CDATA[ ' + JSON.stringify(mml.metadata) + ' ]]>');
  }

  // Add the stylesheets to the MML file.
  if (mml.stylesheets) {
    for (i = 0; i < mml.stylesheets.length; i++) {
      output.push('  <Stylesheet src="' + mml.stylesheets[i] + '" />');
    }
  }

  // And add the layers.
  if (mml.layers) {
    for (i = 0; i < mml.layers.length; i++) {
      var layer = mml.layers[i], layerDef = '  <Layer';
      if (layer.id) {
        layerDef += ' id="' + layer.id + '"';
      }
      if (layer.classes) {
        layerDef += ' class="' + layer.classes + '"';
      }
      if (!layer.srs) {
        // Detect SRS.
        layer.srs = 'WGS84';
      }
      layerDef += ' srs="&srs' + layer.srs + ';"';
      if (mml.layers[i].status != undefined && !mml.layers[i].status) {
        layerDef += ' status="off"';
      }
      layerDef += '>';
      output.push(layerDef);
      output.push('    <Datasource>');
      output.push('      <Parameter name="file">' + layer.file + '</Parameter>');
      if (!layer.type) {
        layer.type = 'shape';
      }
      output.push('      <Parameter name="type">' + layer.type + '</Parameter>');
      if (layer.id) {
        output.push('      <Parameter name="id">' + layer.id + '</Parameter>');
      }
      output.push('    </Datasource>');
      output.push('  </Layer>');
    }
  }
  output.push('</Map>');
  return output.join("\n");
};

TileMill.mml.parseMML = function(mml) {
  var parsed = {
    metadata: {},
    stylesheets: [],
    layers: [],
  };
  // Parse metadata.
  var matches = mml.match(/\<\!\[CDATA\[(.+)\]\]\>/);
  if (matches && matches[1]) {
    parsed.metadata = eval('(' + matches[1] + ')');
  }

  // Parse stylesheets.
  $(mml).find('Stylesheet').each(function() {
    if ($(this).attr('src')) {
      parsed.stylesheets.push($(this).attr('src'));
    }
  });
  // Parse layers.
  $(mml).find('Layer').each(function() {
    var status = $(this).attr('status');
    if (status == 'undefined' || status == undefined || status == 'on') {
      status = true;
    }
    else {
      status = false;
    }
    var classes = '';
    if ($(this).attr('class')) {
      classes = $(this).attr('class');
    }
    var srs = $(this).attr('srs'), parsed_srs = srs.replace(/^&srs(.*);$/, '$1');
    if (parsed_srs == srs) {
      var pass = false;
      for (var key in TileMill.customSrs) {
        if (TileMill.customSrs[key] == srs) {
          pass = true;
          continue;
        }
      }
      if (!pass) {
        TileMill.customSrs.push(srs);
      }
    }
    else {
      srs = parsed_srs;
    }
    parsed.layers.push({
      classes: classes,
      id: $(this).attr('id'),
      status: status,
      file: $(this).find('Datasource Parameter[name=file]').text(),
      srs: srs
    });
  });
  return parsed;
};

TileMill.mml.add = function(options, layers) {
  var name = [];
  if (options.id) {
    name.push('#' + options.id);
  }
  if (options.classes.length) {
    name.push('.' + options.classes.split(' ').join(', .'));
  }
  var status = options.status == 'true' || options.status == true;
  var li = $(TileMill.template('layers-li', {name: name.join(', '), status: status}));

  $('ul.sidebar-content', layers).prepend(li);

  $('a.layer-delete', li).click(function() {
    if (confirm('Are you sure you want to delete this layer?')) {
      $(this).parents('li').hide('fast', function() {
        $(this).remove();
        TileMill.project.changed();
      });
    }
    return false;
  });

  $('a.layer-inspect', li).click(function() {
    // @TODO refactor this out.
    if (!$(this).parents('li').data('tilemill')['id']) {
      alert('You need to add an id to a field and save to inspect it.');
      return false;
    }
    $('#inspector .sidebar-header h2').html('Layers » ' + $(this).parents('li').find('label').text());
    $('#layers').hide();
    $('#inspector').show();
    TileMill.inspector.inspect($(this).parents('li').data('tilemill').id, false);
    return false;
  });

  $('a.layer-edit', li).click(function() {
    var popup = $(TileMill.template('popup-layer', {submit:'Save'}));

    // Populate form values.
    var options = $(this).parents('li').data('tilemill');
    for (option in options) {
      $('#' + option, popup).val(options[option]).end();
    }

    TileMill.popup.show({content: popup, title: 'Edit layer'});

    // Create reference to layer li for submit handler to find.
    $('form', popup).data('li', $(this).parents('li'));

    // Add submit handler.
    $('form', popup).validate({
      errorLabelContainer: 'form .messages',
      submitHandler: function(form) {
        var layer = {
          classes: $('input#classes', form).val(),
          id: $('input#id', form).val(),
          file: $('input#file', form).val(),
          srs: $('select#srs', form).val(),
          status: 'true'
        };
        var name = [];
        if (layer.id) {
          name.push('#' + layer.id);
        }
        if (layer.classes) {
          name.push('.' + layer.classes.split(' ').join(', .'));
        }
        var li = $(form).data('li');
        $(li)
          .data('tilemill', layer)
          .find('label').text(name.join(', '));
        TileMill.popup.hide();
        TileMill.project.changed();
        return false;
      }
    });
    return false;
  });

  // Attach layer data to li element.
  li.data('tilemill', options);
};

TileMill.mml.save = function(data) {
  filename = [TileMill.settings.type, TileMill.settings.id, TileMill.settings.id + '.mml'].join('/');
  TileMill.backend.post(filename, data);
};

/**
 * Generate the URL of the current project .mml file.
 */
TileMill.mml.url = function(options) {
  if (!options) {
    var options = {};
  }
  options = $.extend({ timestamp: true, encode: true }, options);
  var url = TileMill.backend.url(TileMill.settings.filename);
  if (options.timestamp) {
    url += '&c=' + TileMill.uniq;
  }
  if (options.encode) {
    url = Base64.urlsafe_encode(url);
  }
  return url;
};

// @TODO: Move more of this to the controller.
TileMill.mml.init = function() {
  var layers = $(TileMill.template('layers', {}));
  var l = TileMill.mml.parseMML(TileMill.settings.mml).layers;
  for (var layer in l) {
    TileMill.mml.add(l[layer], layers);
  }
  $('ul.sidebar-content', layers).sortable({ axis: 'y', handle: 'div.handle', change: TileMill.project.changed });

  TileMill.inspector.load();
  for (var i in TileMill.customSrs) {
    var srs = TileMill.customSrs[i];
    if (srs.length > 23) {
      srs = srs.substr(0, 20) + '...';
    }
    $('select#srs').append('<option value="' + TileMill.customSrs[i].replace('"', '\\"') + '">' + srs + "</option>");
  }

  $('a#layers-add', layers).click(function() {
    var popup = $(TileMill.template('popup-layer', {submit: 'Add layer'}));
    TileMill.popup.show({content: popup, title: 'Add layer'});

    // Add submit handler.
    $('form', popup).validate({
      errorLabelContainer: 'form .messages',
      submitHandler: function(form) {
        var layer = {
          classes: $('input#classes', form).val(),
          id: $('input#id', form).val(),
          file: $('input#file', form).val(),
          srs: $('select#srs', form).val(),
          status: 'true'
        };
        TileMill.mml.add(layer, $('#layers'));
        TileMill.popup.hide();
        TileMill.project.changed();
        return false;
      }
    });
    return false;
  });

  return layers;
};
<|endoftext|>


<commit_before>/**
 * Copyright IBM Corp. 2016, 2018
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { isForwardRef } from 'react-is';
import debounce from 'lodash.debounce';
import classNames from 'classnames';
import { Information16 as Information } from '@carbon/icons-react';
import { settings } from 'carbon-components';
import FloatingMenu, {
  DIRECTION_LEFT,
  DIRECTION_TOP,
  DIRECTION_RIGHT,
  DIRECTION_BOTTOM,
} from '../../internal/FloatingMenu';
import ClickListener from '../../internal/ClickListener';
import mergeRefs from '../../tools/mergeRefs';
import { keys, matches as keyDownMatch } from '../../internal/keyboard';
import isRequiredOneOf from '../../prop-types/isRequiredOneOf';
import requiredIfValueExists from '../../prop-types/requiredIfValueExists';
import { useControlledStateWithValue } from '../../internal/FeatureFlags';

const { prefix } = settings;

/**
 * @param {Element} menuBody The menu body with the menu arrow.
 * @param {string} menuDirection Where the floating menu menu should be placed relative to the trigger button.
 * @returns {FloatingMenu~offset} The adjustment of the floating menu position, upon the position of the menu arrow.
 * @private
 */
const getMenuOffset = (menuBody, menuDirection) => {
  const arrowStyle = menuBody.ownerDocument.defaultView.getComputedStyle(
    menuBody,
    ':before'
  );
  const arrowPositionProp = {
    [DIRECTION_LEFT]: 'right',
    [DIRECTION_TOP]: 'bottom',
    [DIRECTION_RIGHT]: 'left',
    [DIRECTION_BOTTOM]: 'top',
  }[menuDirection];
  const menuPositionAdjustmentProp = {
    [DIRECTION_LEFT]: 'left',
    [DIRECTION_TOP]: 'top',
    [DIRECTION_RIGHT]: 'left',
    [DIRECTION_BOTTOM]: 'top',
  }[menuDirection];
  const values = [arrowPositionProp, 'border-bottom-width'].reduce(
    (o, name) => ({
      ...o,
      [name]: Number(
        (/^([\d-]+)px$/.exec(arrowStyle.getPropertyValue(name)) || [])[1]
      ),
    }),
    {}
  );
  values[arrowPositionProp] = values[arrowPositionProp] || -6; // IE, etc.
  if (Object.keys(values).every((name) => !isNaN(values[name]))) {
    const {
      [arrowPositionProp]: arrowPosition,
      'border-bottom-width': borderBottomWidth,
    } = values;
    return {
      left: 0,
      top: 0,
      [menuPositionAdjustmentProp]:
        Math.sqrt(Math.pow(borderBottomWidth, 2) * 2) - arrowPosition,
    };
  }
};

class Tooltip extends Component {
  constructor(props) {
    super(props);
    this.isControlled = props.open !== undefined;
    if (useControlledStateWithValue && this.isControlled) {
      // Skips the logic of setting initial state if this component is controlled
      return;
    }
    const open = useControlledStateWithValue ? props.defaultOpen : props.open;
    this.state = { open };
  }

  static propTypes = {
    /**
     * Contents to put into the tooltip.
     */
    children: PropTypes.node,

    /**
     * The CSS class names of the tooltip.
     */
    className: PropTypes.string,

    /**
     * Optional starting value for uncontrolled state
     */
    defaultOpen: PropTypes.bool,

    /**
     * Where to put the tooltip, relative to the trigger UI.
     */
    direction: PropTypes.oneOf(['bottom', 'top', 'left', 'right']),

    /**
     * The name of the default tooltip icon.
     */
    iconName: PropTypes.string,

    /**
     * The adjustment of the tooltip position.
     */
    menuOffset: PropTypes.oneOfType([
      PropTypes.shape({
        top: PropTypes.number,
        left: PropTypes.number,
      }),
      PropTypes.func,
    ]),

    /**
     * * the signature of the event handler will be:
     * * `onChange(event, { open })` where:
     *   * `event` is the (React) raw event
     *   * `open` is the new value
     */
    onChange: !useControlledStateWithValue
      ? PropTypes.func
      : requiredIfValueExists(PropTypes.func),

    /**
     * Open/closed state.
     */
    open: PropTypes.bool,

    /**
     * The callback function to optionally render the icon element.
     * It should be a component with React.forwardRef().
     */
    renderIcon: function (props, propName, componentName) {
      if (props[propName] == undefined) {
        return;
      }
      const RefForwardingComponent = props[propName];
      if (!isForwardRef(<RefForwardingComponent />))
        return new Error(`Invalid value of prop '${propName}' supplied to '${componentName}',
                          it should be created/wrapped with React.forwardRef() to have a ref and access the proper
                          DOM node of the element to calculate its position in the viewport.`);
    },

    /**
     * Specify a CSS selector that matches the DOM element that should
     * be focused when the Tooltip opens
     */
    selectorPrimaryFocus: PropTypes.string,

    /**
     * `true` to show the default tooltip icon.
     */
    showIcon: PropTypes.bool,

    /**
     * Optional prop to specify the tabIndex of the Tooltip
     */
    tabIndex: PropTypes.number,

    /**
     * The ID of the tooltip body content.
     */
    tooltipBodyId: PropTypes.string,

    /**
     * The ID of the tooltip content.
     */
    tooltipId: PropTypes.string,

    /**
     * The CSS class names of the trigger UI.
     */
    triggerClassName: PropTypes.string,

    /**
     * The ID of the trigger button.
     */
    triggerId: PropTypes.string,

    ...isRequiredOneOf({
      /**
       * The content to put into the trigger UI, except the (default) tooltip icon.
       */
      triggerText: PropTypes.node,
      /**
       * The description of the default tooltip icon, to be put in its SVG 'aria-label' and 'alt' .
       */
      iconDescription: PropTypes.string,
    }),
  };

  static defaultProps = {
    direction: DIRECTION_BOTTOM,
    renderIcon: Information,
    showIcon: true,
    triggerText: null,
    menuOffset: getMenuOffset,
    selectorPrimaryFocus: '[data-tooltip-primary-focus]',
  };

  /**
   * The element of the tooltip body.
   * @type {Element}
   * @private
   */
  _tooltipEl = null;

  /**
   * The element ref of the tooltip's trigger button.
   * @type {React.RefObject<Element>}
   * @private
   */
  _triggerRef = React.createRef();

  /**
   * Unique tooltip ID that is user-provided or auto-generated
   * Referenced in aria-labelledby attribute
   * @type {string}
   * @private
   */
  _tooltipId =
    this.props.id || `__carbon-tooltip_${Math.random().toString(36).substr(2)}`;

  /**
   * Internal flag for tracking whether or not focusing on the tooltip trigger
   * should automatically display the tooltip body
   */
  _tooltipDismissed = false;

  componentDidMount() {
    if (!this._debouncedHandleFocus) {
      this._debouncedHandleFocus = debounce(this._handleFocus, 200);
    }

    document.addEventListener('keydown', this.handleEscKeyPress, false);
  }

  componentWillUnmount() {
    if (this._debouncedHandleFocus) {
      this._debouncedHandleFocus.cancel();
      this._debouncedHandleFocus = null;
    }

    document.removeEventListener('keydown', this.handleEscKeyPress, false);
  }

  static getDerivedStateFromProps({ open }, state) {
    /**
     * so that tooltip can be controlled programmatically through this `open` prop
     */
    const { prevOpen } = state;
    return prevOpen === open
      ? null
      : {
          open,
          prevOpen: open,
        };
  }

  _handleUserInputOpenClose = (event, { open }) => {
    // capture tooltip body element before it is removed from the DOM
    const tooltipBody = this._tooltipEl;
    this.setState({ open }, () => {
      if (this.props.onChange) {
        this.props.onChange(event, { open });
      }
      if (!open && tooltipBody && tooltipBody.id === this._tooltipId) {
        this._tooltipDismissed = true;
        this._triggerRef?.current.focus();
      }
    });
  };

  /**
   * Handles `focus`/`blur` event.
   * @param {string} state `over` to show the tooltip, `out` to hide the tooltip.
   * @param {Element} [evt] For handing `mouseout` event, indicates where the mouse pointer is gone.
   */
  _handleFocus = (state, evt) => {
    const { relatedTarget } = evt;
    if (state === 'over') {
      if (!this._tooltipDismissed) {
        this._handleUserInputOpenClose(evt, { open: true });
      }
      this._tooltipDismissed = false;
    } else {
      // Note: SVGElement in IE11 does not have `.contains()`
      const { current: triggerEl } = this._triggerRef;
      const shouldPreventClose =
        relatedTarget &&
        ((triggerEl && triggerEl?.contains(relatedTarget)) ||
          (this._tooltipEl && this._tooltipEl.contains(relatedTarget)));
      if (!shouldPreventClose) {
        this._handleUserInputOpenClose(evt, { open: false });
      }
    }
  };

  /**
   * The debounced version of the `focus`/`blur` event handler.
   * @type {Function}
   * @private
   */
  _debouncedHandleFocus = null;

  /**
   * @returns {Element} The DOM element where the floating menu is placed in.
   */
  _getTarget = () => {
    const { current: triggerEl } = this._triggerRef;
    return (
      (triggerEl && triggerEl.closest('[data-floating-menu-container]')) ||
      document.body
    );
  };

  handleMouse = (evt) => {
    evt.persist();
    const state = {
      focus: 'over',
      blur: 'out',
      click: 'click',
    }[evt.type];
    const hadContextMenu = this._hasContextMenu;
    this._hasContextMenu = evt.type === 'contextmenu';
    if (state === 'click') {
      evt.stopPropagation();
      evt.preventDefault();
      const shouldOpen = this.isControlled
        ? !this.props.open
        : !this.state.open;
      this._handleUserInputOpenClose(evt, { open: shouldOpen });
    } else if (
      state &&
      (state !== 'out' || !hadContextMenu) &&
      this._debouncedHandleFocus
    ) {
      this._debouncedHandleFocus(state, evt);
    }
  };

  handleClickOutside = (evt) => {
    const shouldPreventClose =
      evt &&
      evt.target &&
      this._tooltipEl &&
      this._tooltipEl.contains(evt.target);
    if (!shouldPreventClose && this.state.open) {
      this._handleUserInputOpenClose(evt, { open: false });
    }
  };

  handleKeyPress = (event) => {
    if (keyDownMatch(event, [keys.Escape])) {
      event.stopPropagation();
      this._handleUserInputOpenClose(event, { open: false });
    }

    if (keyDownMatch(event, [keys.Enter, keys.Space])) {
      event.stopPropagation();
      event.preventDefault();
      const shouldOpen = this.isControlled
        ? !this.props.open
        : !this.state.open;
      this._handleUserInputOpenClose(event, { open: shouldOpen });
    }
  };

  handleEscKeyPress = (event) => {
    const { open } = this.isControlled ? this.props : this.state;
    if (open && keyDownMatch(event, [keys.Escape])) {
      return this._handleUserInputOpenClose(event, { open: false });
    }
  };

  render() {
    const {
      triggerId = (this.triggerId =
        this.triggerId ||
        `__carbon-tooltip-trigger_${Math.random().toString(36).substr(2)}`),
      tooltipBodyId,
      children,
      className,
      triggerClassName,
      direction,
      triggerText,
      showIcon,
      iconName,
      iconDescription,
      renderIcon: IconCustomElement,
      menuOffset,
      tabIndex = 0,
      innerRef: ref,
      selectorPrimaryFocus, // eslint-disable-line
      ...other
    } = this.props;

    const { open } = this.isControlled ? this.props : this.state;

    const tooltipClasses = classNames(
      `${prefix}--tooltip`,
      { [`${prefix}--tooltip--shown`]: open },
      className
    );

    const triggerClasses = classNames(
      `${prefix}--tooltip__label`,
      triggerClassName
    );

    const refProp = mergeRefs(this._triggerRef, ref);
    const iconProperties = { name: iconName, role: null, description: null };

    const properties = {
      role: 'button',
      tabIndex: tabIndex,
      onClick: this.handleMouse,
      onKeyDown: this.handleKeyPress,
      onMouseOver: this.handleMouse,
      onMouseOut: this.handleMouse,
      onFocus: this.handleMouse,
      onBlur: this.handleMouse,
      'aria-controls': !open ? undefined : this._tooltipId,
      'aria-expanded': open,
      'aria-describedby': open ? this._tooltipId : null,
      // if the user provides property `triggerText`,
      // then the button should use aria-labelledby to point to its id,
      // if the user doesn't provide property `triggerText`,
      // then an aria-label will be provided via the `iconDescription` property.
      ...(triggerText
        ? {
            'aria-labelledby': triggerId,
          }
        : {
            'aria-label': iconDescription,
          }),
    };

    return (
      <>
        <ClickListener onClickOutside={this.handleClickOutside}>
          {showIcon ? (

              {triggerText}
              <div
                className={`${prefix}--tooltip__trigger`}
                {...properties}
                ref={refProp}>
                <IconCustomElement {...iconProperties} />

          ) : (

              {triggerText}

          )}
        </ClickListener>
        {open && (
          <FloatingMenu
            focusTrap
            selectorPrimaryFocus={this.props.selectorPrimaryFocus}
            target={this._getTarget}
            triggerRef={this._triggerRef}
            menuDirection={direction}
            menuOffset={menuOffset}
            menuRef={(node) => {
              this._tooltipEl = node;
            }}>

              <span className={`${prefix}--tooltip__caret`} />
              <div
                className={`${prefix}--tooltip__content`}
                tabIndex="-1"
                role="dialog"
                aria-describedby={tooltipBodyId}
                aria-labelledby={triggerId}>
                {children}

          </FloatingMenu>
        )}
      </>
    );
  }
}

export default (() => {
  const forwardRef = (props, ref) => <Tooltip {...props} innerRef={ref} />;
  forwardRef.displayName = 'Tooltip';
  return React.forwardRef(forwardRef);
})();
<commit_msg>fix(tooltip): correct tooltipId prop name (#6744)<commit_after>/**
 * Copyright IBM Corp. 2016, 2018
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { isForwardRef } from 'react-is';
import debounce from 'lodash.debounce';
import classNames from 'classnames';
import { Information16 as Information } from '@carbon/icons-react';
import { settings } from 'carbon-components';
import FloatingMenu, {
  DIRECTION_LEFT,
  DIRECTION_TOP,
  DIRECTION_RIGHT,
  DIRECTION_BOTTOM,
} from '../../internal/FloatingMenu';
import ClickListener from '../../internal/ClickListener';
import mergeRefs from '../../tools/mergeRefs';
import { keys, matches as keyDownMatch } from '../../internal/keyboard';
import isRequiredOneOf from '../../prop-types/isRequiredOneOf';
import requiredIfValueExists from '../../prop-types/requiredIfValueExists';
import { useControlledStateWithValue } from '../../internal/FeatureFlags';

const { prefix } = settings;

/**
 * @param {Element} menuBody The menu body with the menu arrow.
 * @param {string} menuDirection Where the floating menu menu should be placed relative to the trigger button.
 * @returns {FloatingMenu~offset} The adjustment of the floating menu position, upon the position of the menu arrow.
 * @private
 */
const getMenuOffset = (menuBody, menuDirection) => {
  const arrowStyle = menuBody.ownerDocument.defaultView.getComputedStyle(
    menuBody,
    ':before'
  );
  const arrowPositionProp = {
    [DIRECTION_LEFT]: 'right',
    [DIRECTION_TOP]: 'bottom',
    [DIRECTION_RIGHT]: 'left',
    [DIRECTION_BOTTOM]: 'top',
  }[menuDirection];
  const menuPositionAdjustmentProp = {
    [DIRECTION_LEFT]: 'left',
    [DIRECTION_TOP]: 'top',
    [DIRECTION_RIGHT]: 'left',
    [DIRECTION_BOTTOM]: 'top',
  }[menuDirection];
  const values = [arrowPositionProp, 'border-bottom-width'].reduce(
    (o, name) => ({
      ...o,
      [name]: Number(
        (/^([\d-]+)px$/.exec(arrowStyle.getPropertyValue(name)) || [])[1]
      ),
    }),
    {}
  );
  values[arrowPositionProp] = values[arrowPositionProp] || -6; // IE, etc.
  if (Object.keys(values).every((name) => !isNaN(values[name]))) {
    const {
      [arrowPositionProp]: arrowPosition,
      'border-bottom-width': borderBottomWidth,
    } = values;
    return {
      left: 0,
      top: 0,
      [menuPositionAdjustmentProp]:
        Math.sqrt(Math.pow(borderBottomWidth, 2) * 2) - arrowPosition,
    };
  }
};

class Tooltip extends Component {
  constructor(props) {
    super(props);
    this.isControlled = props.open !== undefined;
    if (useControlledStateWithValue && this.isControlled) {
      // Skips the logic of setting initial state if this component is controlled
      return;
    }
    const open = useControlledStateWithValue ? props.defaultOpen : props.open;
    this.state = { open };
  }

  static propTypes = {
    /**
     * Contents to put into the tooltip.
     */
    children: PropTypes.node,

    /**
     * The CSS class names of the tooltip.
     */
    className: PropTypes.string,

    /**
     * Optional starting value for uncontrolled state
     */
    defaultOpen: PropTypes.bool,

    /**
     * Where to put the tooltip, relative to the trigger UI.
     */
    direction: PropTypes.oneOf(['bottom', 'top', 'left', 'right']),

    /**
     * The name of the default tooltip icon.
     */
    iconName: PropTypes.string,

    /**
     * The adjustment of the tooltip position.
     */
    menuOffset: PropTypes.oneOfType([
      PropTypes.shape({
        top: PropTypes.number,
        left: PropTypes.number,
      }),
      PropTypes.func,
    ]),

    /**
     * * the signature of the event handler will be:
     * * `onChange(event, { open })` where:
     *   * `event` is the (React) raw event
     *   * `open` is the new value
     */
    onChange: !useControlledStateWithValue
      ? PropTypes.func
      : requiredIfValueExists(PropTypes.func),

    /**
     * Open/closed state.
     */
    open: PropTypes.bool,

    /**
     * The callback function to optionally render the icon element.
     * It should be a component with React.forwardRef().
     */
    renderIcon: function (props, propName, componentName) {
      if (props[propName] == undefined) {
        return;
      }
      const RefForwardingComponent = props[propName];
      if (!isForwardRef(<RefForwardingComponent />))
        return new Error(`Invalid value of prop '${propName}' supplied to '${componentName}',
                          it should be created/wrapped with React.forwardRef() to have a ref and access the proper
                          DOM node of the element to calculate its position in the viewport.`);
    },

    /**
     * Specify a CSS selector that matches the DOM element that should
     * be focused when the Tooltip opens
     */
    selectorPrimaryFocus: PropTypes.string,

    /**
     * `true` to show the default tooltip icon.
     */
    showIcon: PropTypes.bool,

    /**
     * Optional prop to specify the tabIndex of the Tooltip
     */
    tabIndex: PropTypes.number,

    /**
     * The ID of the tooltip body content.
     */
    tooltipBodyId: PropTypes.string,

    /**
     * The ID of the tooltip content.
     */
    tooltipId: PropTypes.string,

    /**
     * The CSS class names of the trigger UI.
     */
    triggerClassName: PropTypes.string,

    /**
     * The ID of the trigger button.
     */
    triggerId: PropTypes.string,

    ...isRequiredOneOf({
      /**
       * The content to put into the trigger UI, except the (default) tooltip icon.
       */
      triggerText: PropTypes.node,
      /**
       * The description of the default tooltip icon, to be put in its SVG 'aria-label' and 'alt' .
       */
      iconDescription: PropTypes.string,
    }),
  };

  static defaultProps = {
    direction: DIRECTION_BOTTOM,
    renderIcon: Information,
    showIcon: true,
    triggerText: null,
    menuOffset: getMenuOffset,
    selectorPrimaryFocus: '[data-tooltip-primary-focus]',
  };

  /**
   * The element of the tooltip body.
   * @type {Element}
   * @private
   */
  _tooltipEl = null;

  /**
   * The element ref of the tooltip's trigger button.
   * @type {React.RefObject<Element>}
   * @private
   */
  _triggerRef = React.createRef();

  /**
   * Unique tooltip ID that is user-provided or auto-generated
   * Referenced in aria-labelledby attribute
   * @type {string}
   * @private
   */
  _tooltipId =
    this.props.tooltipId ||
    `__carbon-tooltip_${Math.random().toString(36).substr(2)}`;

  /**
   * Internal flag for tracking whether or not focusing on the tooltip trigger
   * should automatically display the tooltip body
   */
  _tooltipDismissed = false;

  componentDidMount() {
    if (!this._debouncedHandleFocus) {
      this._debouncedHandleFocus = debounce(this._handleFocus, 200);
    }

    document.addEventListener('keydown', this.handleEscKeyPress, false);
  }

  componentWillUnmount() {
    if (this._debouncedHandleFocus) {
      this._debouncedHandleFocus.cancel();
      this._debouncedHandleFocus = null;
    }

    document.removeEventListener('keydown', this.handleEscKeyPress, false);
  }

  static getDerivedStateFromProps({ open }, state) {
    /**
     * so that tooltip can be controlled programmatically through this `open` prop
     */
    const { prevOpen } = state;
    return prevOpen === open
      ? null
      : {
          open,
          prevOpen: open,
        };
  }

  _handleUserInputOpenClose = (event, { open }) => {
    // capture tooltip body element before it is removed from the DOM
    const tooltipBody = this._tooltipEl;
    this.setState({ open }, () => {
      if (this.props.onChange) {
        this.props.onChange(event, { open });
      }
      if (!open && tooltipBody && tooltipBody.id === this._tooltipId) {
        this._tooltipDismissed = true;
        this._triggerRef?.current.focus();
      }
    });
  };

  /**
   * Handles `focus`/`blur` event.
   * @param {string} state `over` to show the tooltip, `out` to hide the tooltip.
   * @param {Element} [evt] For handing `mouseout` event, indicates where the mouse pointer is gone.
   */
  _handleFocus = (state, evt) => {
    const { relatedTarget } = evt;
    if (state === 'over') {
      if (!this._tooltipDismissed) {
        this._handleUserInputOpenClose(evt, { open: true });
      }
      this._tooltipDismissed = false;
    } else {
      // Note: SVGElement in IE11 does not have `.contains()`
      const { current: triggerEl } = this._triggerRef;
      const shouldPreventClose =
        relatedTarget &&
        ((triggerEl && triggerEl?.contains(relatedTarget)) ||
          (this._tooltipEl && this._tooltipEl.contains(relatedTarget)));
      if (!shouldPreventClose) {
        this._handleUserInputOpenClose(evt, { open: false });
      }
    }
  };

  /**
   * The debounced version of the `focus`/`blur` event handler.
   * @type {Function}
   * @private
   */
  _debouncedHandleFocus = null;

  /**
   * @returns {Element} The DOM element where the floating menu is placed in.
   */
  _getTarget = () => {
    const { current: triggerEl } = this._triggerRef;
    return (
      (triggerEl && triggerEl.closest('[data-floating-menu-container]')) ||
      document.body
    );
  };

  handleMouse = (evt) => {
    evt.persist();
    const state = {
      focus: 'over',
      blur: 'out',
      click: 'click',
    }[evt.type];
    const hadContextMenu = this._hasContextMenu;
    this._hasContextMenu = evt.type === 'contextmenu';
    if (state === 'click') {
      evt.stopPropagation();
      evt.preventDefault();
      const shouldOpen = this.isControlled
        ? !this.props.open
        : !this.state.open;
      this._handleUserInputOpenClose(evt, { open: shouldOpen });
    } else if (
      state &&
      (state !== 'out' || !hadContextMenu) &&
      this._debouncedHandleFocus
    ) {
      this._debouncedHandleFocus(state, evt);
    }
  };

  handleClickOutside = (evt) => {
    const shouldPreventClose =
      evt &&
      evt.target &&
      this._tooltipEl &&
      this._tooltipEl.contains(evt.target);
    if (!shouldPreventClose && this.state.open) {
      this._handleUserInputOpenClose(evt, { open: false });
    }
  };

  handleKeyPress = (event) => {
    if (keyDownMatch(event, [keys.Escape])) {
      event.stopPropagation();
      this._handleUserInputOpenClose(event, { open: false });
    }

    if (keyDownMatch(event, [keys.Enter, keys.Space])) {
      event.stopPropagation();
      event.preventDefault();
      const shouldOpen = this.isControlled
        ? !this.props.open
        : !this.state.open;
      this._handleUserInputOpenClose(event, { open: shouldOpen });
    }
  };

  handleEscKeyPress = (event) => {
    const { open } = this.isControlled ? this.props : this.state;
    if (open && keyDownMatch(event, [keys.Escape])) {
      return this._handleUserInputOpenClose(event, { open: false });
    }
  };

  render() {
    const {
      triggerId = (this.triggerId =
        this.triggerId ||
        `__carbon-tooltip-trigger_${Math.random().toString(36).substr(2)}`),
      tooltipBodyId,
      children,
      className,
      triggerClassName,
      direction,
      triggerText,
      showIcon,
      iconName,
      iconDescription,
      renderIcon: IconCustomElement,
      menuOffset,
      tabIndex = 0,
      innerRef: ref,
      selectorPrimaryFocus, // eslint-disable-line
      ...other
    } = this.props;

    const { open } = this.isControlled ? this.props : this.state;

    const tooltipClasses = classNames(
      `${prefix}--tooltip`,
      { [`${prefix}--tooltip--shown`]: open },
      className
    );

    const triggerClasses = classNames(
      `${prefix}--tooltip__label`,
      triggerClassName
    );

    const refProp = mergeRefs(this._triggerRef, ref);
    const iconProperties = { name: iconName, role: null, description: null };

    const properties = {
      role: 'button',
      tabIndex: tabIndex,
      onClick: this.handleMouse,
      onKeyDown: this.handleKeyPress,
      onMouseOver: this.handleMouse,
      onMouseOut: this.handleMouse,
      onFocus: this.handleMouse,
      onBlur: this.handleMouse,
      'aria-controls': !open ? undefined : this._tooltipId,
      'aria-expanded': open,
      'aria-describedby': open ? this._tooltipId : null,
      // if the user provides property `triggerText`,
      // then the button should use aria-labelledby to point to its id,
      // if the user doesn't provide property `triggerText`,
      // then an aria-label will be provided via the `iconDescription` property.
      ...(triggerText
        ? {
            'aria-labelledby': triggerId,
          }
        : {
            'aria-label': iconDescription,
          }),
    };

    return (
      <>
        <ClickListener onClickOutside={this.handleClickOutside}>
          {showIcon ? (

              {triggerText}
              <div
                className={`${prefix}--tooltip__trigger`}
                {...properties}
                ref={refProp}>
                <IconCustomElement {...iconProperties} />

          ) : (

              {triggerText}

          )}
        </ClickListener>
        {open && (
          <FloatingMenu
            focusTrap
            selectorPrimaryFocus={this.props.selectorPrimaryFocus}
            target={this._getTarget}
            triggerRef={this._triggerRef}
            menuDirection={direction}
            menuOffset={menuOffset}
            menuRef={(node) => {
              this._tooltipEl = node;
            }}>

              <span className={`${prefix}--tooltip__caret`} />
              <div
                className={`${prefix}--tooltip__content`}
                tabIndex="-1"
                role="dialog"
                aria-describedby={tooltipBodyId}
                aria-labelledby={triggerId}>
                {children}

          </FloatingMenu>
        )}
      </>
    );
  }
}

export default (() => {
  const forwardRef = (props, ref) => <Tooltip {...props} innerRef={ref} />;
  forwardRef.displayName = 'Tooltip';
  return React.forwardRef(forwardRef);
})();
<|endoftext|>