<commit_before>var exec = require('child_process').exec;
var fs =  require('fs');
var _ = require('underscore');
var Q = require('q');
// TODO : major refactor needed
var date_opts =  {
  year: 'numeric', month: 'numeric', day: 'numeric',
  hour: 'numeric', minute: 'numeric', second: 'numeric'
};

var gulpmap = {
  'scripts': 'browserify_bundle',
  'styles' : 'build_style',
  'images' : 'build_image',
  'locales': 'json_move'
}

var gulptasks = [];

var headCommit;

function ci(req, res, next){
  var body = req.body;
  if (body.commits && body.commits.length){
    console.log(new Date().toLocaleString('en', date_opts), 'GITHUB : changes to repository detected by :', body.pusher.email);
    // what have to be done to ensure latest steady version on test
    // we return success status only if all steps passed
    Q.fcall(pullUpdates, body)
      .then(updateNpm)
      .then(rebuildApp)
      .then(function(data){
        res.status(200).send('[BUILD SUCCESS] :: \n' + data.log );
        console.log('res status 200 - Build success');
      }, function(err){
        res.status(500).send(err);
        console.log('Build failure :: \n', err);
      })
      // .then(updateAppEnvData)
  } else {
    res.status(501).send('What is that? no commits, meh :/')
  }
}

// functions to be run on git push notification
function pullUpdates(body){
  return new Promise(function(resolve, reject){
    exec('git pull', function(err, stdout, stderr){
      if (err){
        reject(err);
      } else {
        headCommit = JSON.stringify(_.pick(body.head_commit, 'message', 'timestamp', 'url', 'author'));
        resolve({ body: body, log: '[pullUpdates] :: \n' + stdout.trim() });
      }
      console.log('[pullUpdates] ::', stdout, stderr);
    });
  });
};
function updateNpm(data){
  return new Promise(function(resolve, reject){
    var modified = _.pluck(data.body.commits, 'modified');
    var isUpdateNeed;
    // iterate to look for changes in package.json
    modified.forEach(function(commit){
      commit.forEach(function(file){
        if (file === 'package.json'){
          isUpdateNeed = true;
        }
      });
    });
    // if found - update packages
    if (isUpdateNeed) {
      exec('npm install', function(err, stdout, stderr){
        if (err){
          reject(err)
        } else {
          resolve({ body: data.body, log: data.log + '\n[updateNpm] ::\n' + stdout });
        }
        console.log('[updateNpm] ::', stdout, stderr);
      });
    } else {
      console.log('[updateNpm] :: no need to update')
      resolve({ body: data.body, log: data.log+'\n[updateNpm] :: \nNo need to update' });
    }
  });
}

function rebuildApp(data){
  var changedFiles = _.uniq( _.flatten( data.body.commits.map(function(commit){
    return _.union(commit.added, commit.removed, commit.modified);
  })));

  var types = _.uniq( changedFiles.map(function(file){
    if (file.match(/\/[a-z]\w+\//)){
      // [1] returns group match in parenthesis
      return /\/([a-z]\w+)\//.exec(file)[1];
    }
  }));

  var tasks = types.map(function(type){
    return gulpmap[type];
  }).join(' ').trim();

  var gulpCommand = 'gulp ' + tasks;

  return new Promise(function(resolve, reject){
    if ( tasks ){
      exec(gulpCommand, function(err, stdout, stderr){
        if (err) {
          reject(err);
        } else {
          resolve({ body: data.body, log: data.log +'\n[rebuildApp] ::\n' + stdout });
        }
        console.log('[rebuildApp] :: \n', stdout, stderr);
      });
    } else {
      resolve({body: data.body, log: data.log + '\n[rebuildApp] ::\nNo need to rebuild'});
    }
  });
}
// todo : pass updated data (e.g. version) into html for convenience of tracking
// function updateAppEnvData(){
//   return new Promise(function(resolve, reject){
//     fs.readFile('./package.json', 'utf-8', function(err, data){
//       if (err){
//         reject(err);
//       } else {
//         appEnvData.version = JSON.parse(data).version;
//         resolve();
//       }
//       console.log('[updateAppEnvData] :: ', appEnvData.version);
//     });
//   });
// }

module.exports = ci;
<commit_msg>#cm_43, #cm_64 restart server after rebuild<commit_after>var exec = require('child_process').exec;
var fs =  require('fs');
var _ = require('underscore');
var Q = require('q');
// TODO : major refactor needed
var date_opts =  {
  year: 'numeric', month: 'numeric', day: 'numeric',
  hour: 'numeric', minute: 'numeric', second: 'numeric'
};

var gulpmap = {
  'scripts': 'browserify_bundle',
  'styles' : 'build_style',
  'images' : 'build_image',
  'locales': 'json_move'
}

var gulptasks = [];

var headCommit;

function ci(req, res, next){
  var body = req.body;
  if (body.commits && body.commits.length){
    console.log(new Date().toLocaleString('en', date_opts), 'GITHUB : changes to repository detected by :', body.pusher.email);
    // what have to be done to ensure latest steady version on test
    // we return success status only if all steps passed
    Q.fcall(pullUpdates, body)
      .then(updateNpm)
      .then(rebuildApp)
      .then(function(data){
        res.status(200).send('[BUILD SUCCESS] :: \n' + data.log );
        console.log('res status 200 - Build success');
      }, function(err){
        res.status(500).send(err);
        console.log('Build failure :: \n', err);
      })
      .then(restartServer);
      // .then(updateAppEnvData)
  } else {
    res.status(501).send('What is that? no commits, meh :/')
  }
}

// functions to be run on git push notification
function pullUpdates(body){
  return new Promise(function(resolve, reject){
    exec('git pull', function(err, stdout, stderr){
      if (err){
        reject(err);
      } else {
        headCommit = JSON.stringify(_.pick(body.head_commit, 'message', 'timestamp', 'url', 'author'));
        resolve({ body: body, log: '[pullUpdates] :: \n' + stdout.trim() });
      }
      console.log('[pullUpdates] ::', stdout, stderr);
    });
  });
};
function updateNpm(data){
  return new Promise(function(resolve, reject){
    var modified = _.pluck(data.body.commits, 'modified');
    var isUpdateNeed;
    // iterate to look for changes in package.json
    modified.forEach(function(commit){
      commit.forEach(function(file){
        if (file === 'package.json'){
          isUpdateNeed = true;
        }
      });
    });
    // if found - update packages
    if (isUpdateNeed) {
      exec('npm install', function(err, stdout, stderr){
        if (err){
          reject(err)
        } else {
          resolve({ body: data.body, log: data.log + '\n[updateNpm] ::\n' + stdout });
        }
        console.log('[updateNpm] ::', stdout, stderr);
      });
    } else {
      console.log('[updateNpm] :: no need to update')
      resolve({ body: data.body, log: data.log+'\n[updateNpm] :: \nNo need to update' });
    }
  });
}

function rebuildApp(data){
  var changedFiles = _.uniq( _.flatten( data.body.commits.map(function(commit){
    return _.union(commit.added, commit.removed, commit.modified);
  })));

  var types = _.uniq( changedFiles.map(function(file){
    if (file.match(/\/[a-z]\w+\//)){
      // [1] returns group match in parenthesis
      return /\/([a-z]\w+)\//.exec(file)[1];
    }
  }));

  var tasks = types.map(function(type){
    return gulpmap[type];
  }).join(' ').trim();

  var gulpCommand = 'gulp ' + tasks;

  return new Promise(function(resolve, reject){
    if ( tasks ){
      exec(gulpCommand, function(err, stdout, stderr){
        if (err) {
          reject(err);
        } else {
          resolve({ body: data.body, log: data.log +'\n[rebuildApp] ::\n' + stdout });
        }
        console.log('[rebuildApp] :: \n', stdout, stderr);
      });
    } else {
      resolve({body: data.body, log: data.log + '\n[rebuildApp] ::\nNo need to rebuild'});
    }
  });
}

function restartServer(data){
  return new Promise(function(resolve, reject){
    exec('forever restart server.js', function(err, stdout, stderr){
      if(err) {
        reject(err);
      } else {
        resolve({body: data.body, log: data.log + '\n[restartServer] :: restarted server'})
      }
    });
  });
}

module.exports = ci;
<|endoftext|>


<commit_before>$( "#addition" ).click(function() {
    /*
    $.post( "addExpenses",
    {
        description: $('#description').val(),
        amount: $('#amount').val(),
        category: $('#category').val(),
        merchant: $('#merchant').val(),
        location: $('#location').val(),
    });
    */

    var description = 'description=' . $('#description').val();
    var amount = 'amount=' . $('#amount').val();
    var category = 'category=' . $('#category').val();
    var merchant = 'merchant=' . $('#merchant').val();
    var location = 'location=' . $('#location').val();
    var myExpenses = new Array(description, location, category, merchant, location);

    $.ajax({
        type: "GET",
        url: "https://gorilla.borke.us/index.php/user/addExpenses",
        data: myExpenses.join('&'),
        success: function()
        {
          console.log("AJAX request was successfull");
        },
          error:function()
        {
          console.log("AJAX request was a failure");
        }
    });

/*
         $.ajax(
        {
            url: "send_sms.php",
            type: "GET",
            data: "from=6504378632&body="+smsMessage+"&to="+smsTo, //The data your sending to some-page.php

        success: function()
        {
          console.log("AJAX request was successfull");
        },
          error:function()
        {
          console.log("AJAX request was a failure");
        }
    });
*/

});<commit_msg>adding javascript<commit_after>$( "#addition" ).click(function() {
    /*
    $.post( "addExpenses",
    {
        description: $('#description').val(),
        amount: $('#amount').val(),
        category: $('#category').val(),
        merchant: $('#merchant').val(),
        location: $('#location').val(),
    });
    */

    var description = 'description=' . $('#description').val();
    var amount = 'amount=' . $('#amount').val();
    var category = 'category=' . $('#category').val();
    var merchant = 'merchant=' . $('#merchant').val();
    var location = 'location=' . $('#location').val();
    var myExpenses = new Array(description, location, category, merchant, location);

    $.post("/index.php/user/addExpenses",
    {
        description: $('#description').val(),
        amount: $('#amount').val(),
        category: $('#category').val(),
        merchant: $('#merchant').val(),
        location: $('#location').val(),
    },
    function(data, textStatus, jqXHR)
    {
        //data - response from server
    }).fail(function(jqXHR, textStatus, errorThrown)
    {
        alert(textStatus);
    });

/*
         $.ajax(
        {
            url: "send_sms.php",
            type: "GET",
            data: "from=6504378632&body="+smsMessage+"&to="+smsTo, //The data your sending to some-page.php

        success: function()
        {
          console.log("AJAX request was successfull");
        },
          error:function()
        {
          console.log("AJAX request was a failure");
        }
    });
*/

});<|endoftext|>


<commit_before>import gulp     from 'gulp';
import plugins  from 'gulp-load-plugins';
import browser  from 'browser-sync';
import rimraf   from 'rimraf';
import panini   from 'panini';
import yargs    from 'yargs';
import lazypipe from 'lazypipe';
import inky     from 'inky';
import fs       from 'fs';
import siphon   from 'siphon-media-query';
import path     from 'path';
import merge    from 'merge-stream';
import beep     from 'beepbeep';
import colors   from 'colors';

const $ = plugins();

// Look for the --production flag
const PRODUCTION = !!(yargs.argv.production);

// Declar var so that both AWS and Litmus task can use it.
var CONFIG;

// Build the "dist" folder by running all of the above tasks
gulp.task('build',
  gulp.series(clean, pages, sass, images, inline));

// Build emails, run the server, and watch for file changes
gulp.task('default',
  gulp.series('build', server, watch));

// Build emails, then send to litmus
gulp.task('litmus',
  gulp.series('build', creds, aws, litmus));

// Build emails, then zip
gulp.task('zip',
  gulp.series('build', zip));

// Delete the "dist" folder
// This happens every time a build starts
function clean(done) {
  rimraf('dist', done);
}

// Compile layouts, pages, and partials into flat HTML files
// Then parse using Inky templates
function pages() {
  return gulp.src('src/pages/**/*.html')
    .pipe(panini({
      root: 'src/pages',
      layouts: 'src/layouts',
      partials: 'src/partials',
      helpers: 'src/helpers'
    }))
    .pipe(inky())
    .pipe(gulp.dest('dist'));
}

// Reset Panini's cache of layouts and partials
function resetPages(done) {
  panini.refresh();
  done();
}

// Compile Sass into CSS
function sass() {
  return gulp.src('src/assets/scss/app.scss')
    .pipe($.if(!PRODUCTION, $.sourcemaps.init()))
    .pipe($.sass({
      includePaths: ['node_modules/foundation-emails/scss']
    }).on('error', $.sass.logError))
    .pipe($.if(PRODUCTION, $.uncss(
      {
        html: ['dist/*.html']
      })))
    .pipe($.if(!PRODUCTION, $.sourcemaps.write()))
    .pipe(gulp.dest('dist/css'));
}

// Copy and compress images
function images() {
  return gulp.src('src/assets/img/**/*')
    .pipe($.imagemin())
    .pipe(gulp.dest('./dist/assets/img'));
}

// Inline CSS and minify HTML
function inline() {
  return gulp.src('dist/**/*.html')
    .pipe($.if(PRODUCTION, inliner('dist/css/app.css')))
    .pipe(gulp.dest('dist'));
}

// Start a server with LiveReload to preview the site in
function server(done) {
  browser.init({
    server: 'dist'
  });
  done();
}

// Watch for file changes
function watch() {
  gulp.watch('src/pages/**/*.html').on('change', gulp.series(pages, inline, browser.reload));
  gulp.watch(['src/layouts/**/*', 'src/partials/**/*']).on('change', gulp.series(resetPages, pages, inline, browser.reload));
  gulp.watch(['../scss/**/*.scss', 'src/assets/scss/**/*.scss']).on('change', gulp.series(resetPages, sass, pages, inline, browser.reload));
  gulp.watch('src/assets/img/**/*').on('change', gulp.series(images, browser.reload));
}

// Inlines CSS into HTML, adds media query CSS into the <style> tag of the email, and compresses the HTML
function inliner(css) {
  var css = fs.readFileSync(css).toString();
  var mqCss = siphon(css);

  var pipe = lazypipe()
    .pipe($.inlineCss, {
      applyStyleTags: false,
      removeStyleTags: true,
      preserveMediaQueries: true,
      removeLinkTags: false
    })
    .pipe($.replace, '<!-- <style> -->', `<style>${mqCss}</style>`)
    .pipe($.replace, '<link rel="stylesheet" type="text/css" href="css/app.css">', '')
    .pipe($.htmlmin, {
      collapseWhitespace: true,
      minifyCSS: true
    });

  return pipe();
}

// Ensure creds for Litmus are at least there.
function creds(done) {
  var configPath = './config.json';
  try { CONFIG = JSON.parse(fs.readFileSync(configPath)); }
  catch(e) {
    beep();
    console.log('[AWS]'.bold.red + ' Sorry, there was an issue locating your config.json. Please see README.md');
    process.exit();
  }
  done();
}

// Post images to AWS S3 so they are accessible to Litmus test
function aws() {
  var publisher = !!CONFIG.aws ? $.awspublish.create(CONFIG.aws) : $.awspublish.create();
  var headers = {
    'Cache-Control': 'max-age=315360000, no-transform, public'
  };

  return gulp.src('./dist/assets/img/*')
    // publisher will add Content-Length, Content-Type and headers specified above
    // If not specified it will set x-amz-acl to public-read by default
    .pipe(publisher.publish(headers))

    // create a cache file to speed up consecutive uploads
    //.pipe(publisher.cache())

    // print upload updates to console
    .pipe($.awspublish.reporter());
}

// Send email to Litmus for testing. If no AWS creds then do not replace img urls.
function litmus() {
  var awsURL = !!CONFIG && !!CONFIG.aws && !!CONFIG.aws.url ? CONFIG.aws.url : false;

  return gulp.src('dist/**/*.html')
    .pipe($.if(!!awsURL, $.replace(/=('|")(\/?assets\/img)/g, "=$1"+ awsURL)))
    .pipe($.litmus(CONFIG.litmus))
    .pipe(gulp.dest('dist'));
}

// Copy and compress into Zip
function zip() {
  var dist = 'dist';
  var ext = '.html';

  function getHtmlFiles(dir) {
    return fs.readdirSync(dir)
      .filter(function(file) {
        var fileExt = path.join(dir, file);
        var isHtml = path.extname(fileExt) == ext;
        return fs.statSync(fileExt).isFile() && isHtml;
      });
  }

  var htmlFiles = getHtmlFiles(dist);

  var moveTasks = htmlFiles.map(function(file){
    var sourcePath = path.join(dist, file);
    var fileName = path.basename(sourcePath, ext);

    var moveHTML = gulp.src(sourcePath)
      .pipe($.rename(function (path) {
        path.dirname = fileName;
        return path;
      }));

    var moveImages = gulp.src(sourcePath)
      .pipe($.htmlSrc({ selector: 'img'}))
      .pipe($.rename(function (path) {
        path.dirname = fileName + '/assets/img';
        return path;
      }));

    return merge(moveHTML, moveImages)
      .pipe($.zip(fileName+ '.zip'))
      .pipe(gulp.dest('dist'));
  });

  return merge(moveTasks);
}

<commit_msg>Pass on fixing uncss selector<commit_after>import gulp     from 'gulp';
import plugins  from 'gulp-load-plugins';
import browser  from 'browser-sync';
import rimraf   from 'rimraf';
import panini   from 'panini';
import yargs    from 'yargs';
import lazypipe from 'lazypipe';
import inky     from 'inky';
import fs       from 'fs';
import siphon   from 'siphon-media-query';
import path     from 'path';
import merge    from 'merge-stream';
import beep     from 'beepbeep';
import colors   from 'colors';

const $ = plugins();

// Look for the --production flag
const PRODUCTION = !!(yargs.argv.production);

// Declar var so that both AWS and Litmus task can use it.
var CONFIG;

// Build the "dist" folder by running all of the above tasks
gulp.task('build',
  gulp.series(clean, pages, sass, images, inline));

// Build emails, run the server, and watch for file changes
gulp.task('default',
  gulp.series('build', server, watch));

// Build emails, then send to litmus
gulp.task('litmus',
  gulp.series('build', creds, aws, litmus));

// Build emails, then zip
gulp.task('zip',
  gulp.series('build', zip));

// Delete the "dist" folder
// This happens every time a build starts
function clean(done) {
  rimraf('dist', done);
}

// Compile layouts, pages, and partials into flat HTML files
// Then parse using Inky templates
function pages() {
  return gulp.src('src/pages/**/*.html')
    .pipe(panini({
      root: 'src/pages',
      layouts: 'src/layouts',
      partials: 'src/partials',
      helpers: 'src/helpers'
    }))
    .pipe(inky())
    .pipe(gulp.dest('dist'));
}

// Reset Panini's cache of layouts and partials
function resetPages(done) {
  panini.refresh();
  done();
}

// Compile Sass into CSS
function sass() {
  return gulp.src('src/assets/scss/app.scss')
    .pipe($.if(!PRODUCTION, $.sourcemaps.init()))
    .pipe($.sass({
      includePaths: ['node_modules/foundation-emails/scss']
    }).on('error', $.sass.logError))
    .pipe($.if(PRODUCTION, $.uncss(
      {
        html: ['dist/**/*.html']
      })))
    .pipe($.if(!PRODUCTION, $.sourcemaps.write()))
    .pipe(gulp.dest('dist/css'));
}

// Copy and compress images
function images() {
  return gulp.src('src/assets/img/**/*')
    .pipe($.imagemin())
    .pipe(gulp.dest('./dist/assets/img'));
}

// Inline CSS and minify HTML
function inline() {
  return gulp.src('dist/**/*.html')
    .pipe($.if(PRODUCTION, inliner('dist/css/app.css')))
    .pipe(gulp.dest('dist'));
}

// Start a server with LiveReload to preview the site in
function server(done) {
  browser.init({
    server: 'dist'
  });
  done();
}

// Watch for file changes
function watch() {
  gulp.watch('src/pages/**/*.html').on('change', gulp.series(pages, inline, browser.reload));
  gulp.watch(['src/layouts/**/*', 'src/partials/**/*']).on('change', gulp.series(resetPages, pages, inline, browser.reload));
  gulp.watch(['../scss/**/*.scss', 'src/assets/scss/**/*.scss']).on('change', gulp.series(resetPages, sass, pages, inline, browser.reload));
  gulp.watch('src/assets/img/**/*').on('change', gulp.series(images, browser.reload));
}

// Inlines CSS into HTML, adds media query CSS into the <style> tag of the email, and compresses the HTML
function inliner(css) {
  var css = fs.readFileSync(css).toString();
  var mqCss = siphon(css);

  var pipe = lazypipe()
    .pipe($.inlineCss, {
      applyStyleTags: false,
      removeStyleTags: true,
      preserveMediaQueries: true,
      removeLinkTags: false
    })
    .pipe($.replace, '<!-- <style> -->', `<style>${mqCss}</style>`)
    .pipe($.replace, '<link rel="stylesheet" type="text/css" href="css/app.css">', '')
    .pipe($.htmlmin, {
      collapseWhitespace: true,
      minifyCSS: true
    });

  return pipe();
}

// Ensure creds for Litmus are at least there.
function creds(done) {
  var configPath = './config.json';
  try { CONFIG = JSON.parse(fs.readFileSync(configPath)); }
  catch(e) {
    beep();
    console.log('[AWS]'.bold.red + ' Sorry, there was an issue locating your config.json. Please see README.md');
    process.exit();
  }
  done();
}

// Post images to AWS S3 so they are accessible to Litmus test
function aws() {
  var publisher = !!CONFIG.aws ? $.awspublish.create(CONFIG.aws) : $.awspublish.create();
  var headers = {
    'Cache-Control': 'max-age=315360000, no-transform, public'
  };

  return gulp.src('./dist/assets/img/*')
    // publisher will add Content-Length, Content-Type and headers specified above
    // If not specified it will set x-amz-acl to public-read by default
    .pipe(publisher.publish(headers))

    // create a cache file to speed up consecutive uploads
    //.pipe(publisher.cache())

    // print upload updates to console
    .pipe($.awspublish.reporter());
}

// Send email to Litmus for testing. If no AWS creds then do not replace img urls.
function litmus() {
  var awsURL = !!CONFIG && !!CONFIG.aws && !!CONFIG.aws.url ? CONFIG.aws.url : false;

  return gulp.src('dist/**/*.html')
    .pipe($.if(!!awsURL, $.replace(/=('|")(\/?assets\/img)/g, "=$1"+ awsURL)))
    .pipe($.litmus(CONFIG.litmus))
    .pipe(gulp.dest('dist'));
}

// Copy and compress into Zip
function zip() {
  var dist = 'dist';
  var ext = '.html';

  function getHtmlFiles(dir) {
    return fs.readdirSync(dir)
      .filter(function(file) {
        var fileExt = path.join(dir, file);
        var isHtml = path.extname(fileExt) == ext;
        return fs.statSync(fileExt).isFile() && isHtml;
      });
  }

  var htmlFiles = getHtmlFiles(dist);

  var moveTasks = htmlFiles.map(function(file){
    var sourcePath = path.join(dist, file);
    var fileName = path.basename(sourcePath, ext);

    var moveHTML = gulp.src(sourcePath)
      .pipe($.rename(function (path) {
        path.dirname = fileName;
        return path;
      }));

    var moveImages = gulp.src(sourcePath)
      .pipe($.htmlSrc({ selector: 'img'}))
      .pipe($.rename(function (path) {
        path.dirname = fileName + '/assets/img';
        return path;
      }));

    return merge(moveHTML, moveImages)
      .pipe($.zip(fileName+ '.zip'))
      .pipe(gulp.dest('dist'));
  });

  return merge(moveTasks);
}

<|endoftext|>


<commit_before>var hotspots = (function () {

var exports = {};

// icon placements (relative to element):
var TOP_LEFT = 'TOP_LEFT';
var TOP_RIGHT = 'TOP_RIGHT';
var BOTTOM_RIGHT = 'BOTTOM_RIGHT';
var BOTTOM_LEFT = 'BOTTOM_LEFT';
var CENTER = 'CENTER';

// popover orientations
var TOP = 'top';
var LEFT = 'left';
var RIGHT = 'right';
var BOTTOM = 'bottom';
var VIEWPORT_CENTER = 'viewport_center';

// popover orientation can optionally be fixed here (property: popover),
// otherwise popovers.compute_placement is used to compute orientation
var HOTSPOT_LOCATIONS = {
    click_to_reply: {
        element: '.selected_message .messagebox-content',
        icon: CENTER,
    },
    new_topic_button: {
        element: '#left_bar_compose_stream_button_big',
        icon: TOP_LEFT,
    },
    stream_settings: {
        element: '#streams_inline_cog',
        icon: CENTER,
    },
};

// popover illustration url(s)
var WHALE = '/static/images/hotspots/whale.svg';

exports.map_hotspots_to_DOM = function (hotspots, locations) {
    hotspots.forEach(function (hotspot) {
        hotspot.location = locations[hotspot.name];
    });
};

exports.post_hotspot_as_read = function (hotspot_name) {
    channel.post({
        url: '/json/users/me/hotspots',
        data: { hotspot: JSON.stringify(hotspot_name) },
        error: function (err) {
            blueslip.error(err.responseText);
        },
    });
};

function place_icon(hotspot) {
    if (!$(hotspot.location.element).length === 0) {
        $('#hotspot_' + hotspot.name + '_icon').css('display', 'none');
        return;
    }

    var el_width = $(hotspot.location.element).outerWidth();
    var el_height = $(hotspot.location.element).outerHeight();

    var offset;
    switch (hotspot.location.icon) {
        case TOP_LEFT:
            offset = {
                top: 0,
                left: 0,
            };
            break;

        case TOP_RIGHT:
            offset = {
                top: 0,
                left: el_width,
            };
            break;

        case BOTTOM_RIGHT:
            offset = {
                top: el_height,
                left: el_width,
            };
            break;

        case BOTTOM_LEFT:
            offset = {
                top: el_height,
                left: 0,
            };
            break;

        case CENTER:
            offset = {
                top: (el_height / 2),
                left: (el_width / 2),
            };
            break;

        default:
            blueslip.error(
                'Invalid icon placement value for hotspot \'' +
                hotspot.name + '\''
            );
            break;
    }

    var client_rect = $(hotspot.location.element).get(0).getBoundingClientRect();
    var placement = {
        top: client_rect.top + offset.top,
        left: client_rect.left + offset.left,
    };

    if ($(hotspot.location.element).css('display') === 'none' ||
        !$(hotspot.location.element).is(':visible') ||
        $(hotspot.location.element).is(':hidden')) {
        $('#hotspot_' + hotspot.name + '_icon').css('display', 'none');
    } else {
        $('#hotspot_' + hotspot.name + '_icon').css('display', 'block');
        $('#hotspot_' + hotspot.name + '_icon').css(placement);
    }
}

function place_popover(hotspot) {
    if (!hotspot.location.element) {
        return;
    }

    var popover_width = $('#hotspot_' + hotspot.name + '_overlay .hotspot-popover').outerWidth();
    var popover_height = $('#hotspot_' + hotspot.name + '_overlay .hotspot-popover').outerHeight();
    var el_width = $(hotspot.location.element).outerWidth();
    var el_height = $(hotspot.location.element).outerHeight();
    var arrow_offset = 20;

    var popover_offset;
    var arrow_placement;
    var orientation = hotspot.location.popover ||
        popovers.compute_placement($(hotspot.location.element));

    switch (orientation) {
        case TOP:
            popover_offset = {
                top: -(popover_height + arrow_offset),
                left: (el_width / 2) - (popover_width / 2),
            };
            arrow_placement = 'bottom';
            break;

        case LEFT:
            popover_offset = {
                top: (el_height / 2) - (popover_height / 2),
                left: -(popover_width + arrow_offset),
            };
            arrow_placement = 'right';
            break;

        case BOTTOM:
            popover_offset = {
                top: el_height + arrow_offset,
                left: (el_width / 2) - (popover_width / 2),
            };
            arrow_placement = 'top';
            break;

        case RIGHT:
            popover_offset = {
                top: (el_height / 2) - (popover_height / 2),
                left: el_width + arrow_offset,
            };
            arrow_placement = 'left';
            break;

        case VIEWPORT_CENTER:
            popover_offset = {
                top: el_height / 2,
                left: el_width / 2,
            };
            arrow_placement = '';
            break;

        default:
            blueslip.error(
                'Invalid popover placement value for hotspot \'' +
                hotspot.name + '\''
            );
            break;
    }

    // position arrow
    arrow_placement = 'arrow-' + arrow_placement;
    $('#hotspot_' + hotspot.name + '_overlay .hotspot-popover')
        .removeClass('arrow-top arrow-left arrow-bottom arrow-right')
        .addClass(arrow_placement);

    // position popover
    var popover_placement;
    if (orientation === VIEWPORT_CENTER) {
        popover_placement = {
            top: '45%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
        };
    } else {
        var client_rect = $(hotspot.location.element).get(0).getBoundingClientRect();
        popover_placement = {
            top: client_rect.top + popover_offset.top,
            left: client_rect.left + popover_offset.left,
            transform: '',
        };
    }

    $('#hotspot_' + hotspot.name + '_overlay .hotspot-popover')
        .css(popover_placement);
}

function insert_hotspot_into_DOM(hotspot) {
    var hotspot_overlay_HTML = templates.render('hotspot_overlay', {
        name: hotspot.name,
        title: hotspot.title,
        description: hotspot.description,
        img: WHALE,
    });

    var hotspot_icon_HTML =
        '
' +
            '' +
            '' +
        '
';

    setTimeout(function () {
        $('body').prepend(hotspot_icon_HTML);
        place_icon(hotspot);

        $('body').prepend(hotspot_overlay_HTML);
        place_popover(hotspot);

        // reposition on any event that might update the UI
        ['resize', 'scroll', 'onkeydown', 'click']
        .forEach(function (event_name) {
            window.addEventListener(event_name, function () {
                place_icon(hotspot);
                place_popover(hotspot);
            }, true);
        });
    }, (hotspot.delay * 100));
}

exports.load_new = function (new_hotspots) {
    exports.map_hotspots_to_DOM(new_hotspots, HOTSPOT_LOCATIONS);
    new_hotspots.forEach(insert_hotspot_into_DOM);
};

exports.initialize = function () {
    exports.load_new(page_params.hotspots);
};

return exports;
}());
if (typeof module !== 'undefined') {
    module.exports = hotspots;
}
<commit_msg>hotspots: Debounce hotspot placement on UI updates by 10ms.<commit_after>var hotspots = (function () {

var exports = {};

// icon placements (relative to element):
var TOP_LEFT = 'TOP_LEFT';
var TOP_RIGHT = 'TOP_RIGHT';
var BOTTOM_RIGHT = 'BOTTOM_RIGHT';
var BOTTOM_LEFT = 'BOTTOM_LEFT';
var CENTER = 'CENTER';

// popover orientations
var TOP = 'top';
var LEFT = 'left';
var RIGHT = 'right';
var BOTTOM = 'bottom';
var VIEWPORT_CENTER = 'viewport_center';

// popover orientation can optionally be fixed here (property: popover),
// otherwise popovers.compute_placement is used to compute orientation
var HOTSPOT_LOCATIONS = {
    click_to_reply: {
        element: '.selected_message .messagebox-content',
        icon: CENTER,
    },
    new_topic_button: {
        element: '#left_bar_compose_stream_button_big',
        icon: TOP_LEFT,
    },
    stream_settings: {
        element: '#streams_inline_cog',
        icon: CENTER,
    },
};

// popover illustration url(s)
var WHALE = '/static/images/hotspots/whale.svg';

exports.map_hotspots_to_DOM = function (hotspots, locations) {
    hotspots.forEach(function (hotspot) {
        hotspot.location = locations[hotspot.name];
    });
};

exports.post_hotspot_as_read = function (hotspot_name) {
    channel.post({
        url: '/json/users/me/hotspots',
        data: { hotspot: JSON.stringify(hotspot_name) },
        error: function (err) {
            blueslip.error(err.responseText);
        },
    });
};

function place_icon(hotspot) {
    if (!$(hotspot.location.element).length === 0) {
        $('#hotspot_' + hotspot.name + '_icon').css('display', 'none');
        return;
    }

    var el_width = $(hotspot.location.element).outerWidth();
    var el_height = $(hotspot.location.element).outerHeight();

    var offset;
    switch (hotspot.location.icon) {
        case TOP_LEFT:
            offset = {
                top: 0,
                left: 0,
            };
            break;

        case TOP_RIGHT:
            offset = {
                top: 0,
                left: el_width,
            };
            break;

        case BOTTOM_RIGHT:
            offset = {
                top: el_height,
                left: el_width,
            };
            break;

        case BOTTOM_LEFT:
            offset = {
                top: el_height,
                left: 0,
            };
            break;

        case CENTER:
            offset = {
                top: (el_height / 2),
                left: (el_width / 2),
            };
            break;

        default:
            blueslip.error(
                'Invalid icon placement value for hotspot \'' +
                hotspot.name + '\''
            );
            break;
    }

    var client_rect = $(hotspot.location.element).get(0).getBoundingClientRect();
    var placement = {
        top: client_rect.top + offset.top,
        left: client_rect.left + offset.left,
    };

    if ($(hotspot.location.element).css('display') === 'none' ||
        !$(hotspot.location.element).is(':visible') ||
        $(hotspot.location.element).is(':hidden')) {
        $('#hotspot_' + hotspot.name + '_icon').css('display', 'none');
    } else {
        $('#hotspot_' + hotspot.name + '_icon').css('display', 'block');
        $('#hotspot_' + hotspot.name + '_icon').css(placement);
    }
}

function place_popover(hotspot) {
    if (!hotspot.location.element) {
        return;
    }

    var popover_width = $('#hotspot_' + hotspot.name + '_overlay .hotspot-popover').outerWidth();
    var popover_height = $('#hotspot_' + hotspot.name + '_overlay .hotspot-popover').outerHeight();
    var el_width = $(hotspot.location.element).outerWidth();
    var el_height = $(hotspot.location.element).outerHeight();
    var arrow_offset = 20;

    var popover_offset;
    var arrow_placement;
    var orientation = hotspot.location.popover ||
        popovers.compute_placement($(hotspot.location.element));

    switch (orientation) {
        case TOP:
            popover_offset = {
                top: -(popover_height + arrow_offset),
                left: (el_width / 2) - (popover_width / 2),
            };
            arrow_placement = 'bottom';
            break;

        case LEFT:
            popover_offset = {
                top: (el_height / 2) - (popover_height / 2),
                left: -(popover_width + arrow_offset),
            };
            arrow_placement = 'right';
            break;

        case BOTTOM:
            popover_offset = {
                top: el_height + arrow_offset,
                left: (el_width / 2) - (popover_width / 2),
            };
            arrow_placement = 'top';
            break;

        case RIGHT:
            popover_offset = {
                top: (el_height / 2) - (popover_height / 2),
                left: el_width + arrow_offset,
            };
            arrow_placement = 'left';
            break;

        case VIEWPORT_CENTER:
            popover_offset = {
                top: el_height / 2,
                left: el_width / 2,
            };
            arrow_placement = '';
            break;

        default:
            blueslip.error(
                'Invalid popover placement value for hotspot \'' +
                hotspot.name + '\''
            );
            break;
    }

    // position arrow
    arrow_placement = 'arrow-' + arrow_placement;
    $('#hotspot_' + hotspot.name + '_overlay .hotspot-popover')
        .removeClass('arrow-top arrow-left arrow-bottom arrow-right')
        .addClass(arrow_placement);

    // position popover
    var popover_placement;
    if (orientation === VIEWPORT_CENTER) {
        popover_placement = {
            top: '45%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
        };
    } else {
        var client_rect = $(hotspot.location.element).get(0).getBoundingClientRect();
        popover_placement = {
            top: client_rect.top + popover_offset.top,
            left: client_rect.left + popover_offset.left,
            transform: '',
        };
    }

    $('#hotspot_' + hotspot.name + '_overlay .hotspot-popover')
        .css(popover_placement);
}

function insert_hotspot_into_DOM(hotspot) {
    var hotspot_overlay_HTML = templates.render('hotspot_overlay', {
        name: hotspot.name,
        title: hotspot.title,
        description: hotspot.description,
        img: WHALE,
    });

    var hotspot_icon_HTML =
        '
' +
            '' +
            '' +
        '
';

    setTimeout(function () {
        $('body').prepend(hotspot_icon_HTML);
        place_icon(hotspot);

        $('body').prepend(hotspot_overlay_HTML);
        place_popover(hotspot);

        // reposition on any event that might update the UI
        ['resize', 'scroll', 'onkeydown', 'click']
        .forEach(function (event_name) {
            window.addEventListener(event_name, _.debounce(function () {
                place_icon(hotspot);
                place_popover(hotspot);
            }, 10), true);
        });
    }, (hotspot.delay * 100));
}

exports.load_new = function (new_hotspots) {
    exports.map_hotspots_to_DOM(new_hotspots, HOTSPOT_LOCATIONS);
    new_hotspots.forEach(insert_hotspot_into_DOM);
};

exports.initialize = function () {
    exports.load_new(page_params.hotspots);
};

return exports;
}());
if (typeof module !== 'undefined') {
    module.exports = hotspots;
}
<|endoftext|>


<commit_before>import 'core-js/fn/object/entries' // shim Object.entries
import fs from 'fs'
import { exec as nodeExec } from 'child_process'
import pify from 'pify'
import streamToPromise from 'stream-to-promise'
import gulp from 'gulp'
import uglify from 'gulp-uglify'
import identity from 'gulp-identity'
import source from 'vinyl-source-stream'
import buffer from 'vinyl-buffer'
import stylelint from 'gulp-stylelint'
import browserify from 'browserify'
import watchify from 'watchify'
import babelify from 'babelify'
import envify from 'loose-envify/custom'
import eslint from 'gulp-eslint'
import path from 'path'
import cssModulesify from 'css-modulesify'
import cssnext from 'postcss-cssnext'

const exec = pify(nodeExec)

// === Tasks for building the source code; result is put into ./extension ===

const staticFiles = {
    'src/manifest.json': 'extension',
    'src/**/*.html': 'extension',
    'src/local-page/local-page.css': 'extension/local-page',
    'img/*': 'extension/img',
    'node_modules/webextension-polyfill/dist/browser-polyfill.js': 'extension/lib',
    'node_modules/pdfjs-dist/build/pdf.worker.min.js': 'extension/lib',
    'node_modules/material-design-icons/iconfont/*.{eot,ttf,woff,woff2,css}': 'extension/fonts/material-icons',
}

const commonUIEntry = './src/common-ui/components/index.js'

const sourceFiles = [
    {
        entries: ['./src/background.js'],
        output: 'background.js',
        destination: './extension',
    },
    {
        entries: ['./src/content_script.js'],
        output: 'content_script.js',
        destination: './extension',
    },
    {
        entries: ['./src/overview/overview.jsx', commonUIEntry],
        output: 'overview.js',
        destination: './extension/overview',
        cssOutput: 'overview.css',
    },
    {
        entries: ['./src/options/options.jsx', commonUIEntry],
        output: 'options.js',
        destination: './extension/options',
        cssOutput: 'options.css',
    },
    {
        entries: ['./src/local-page/local-page.js'],
        output: 'local-page.js',
        destination: './extension/local-page',
    },
    {
        entries: ['./src/popup/popup.js'],
        output: 'popup.js',
        destination: './extension/popup',
        cssOutput: 'popup.css',
    },
]

const browserifySettings = {
    debug: true,
    extensions: ['.jsx', '.css'],
    paths: ['.'],
}

async function createBundle({entries, output, destination, cssOutput}, {watch = false, production = false}) {
    let b = watch
        ? watchify(browserify({...watchify.args, ...browserifySettings, entries}))
            .on('update', bundle)
        : browserify({...browserifySettings, entries})
    b.transform(babelify)
    b.transform(envify({
        NODE_ENV: production ? 'production' : 'development',
    }), {global: true})

    if (cssOutput) {
        b.plugin(cssModulesify, {
            global: true,
            output: path.join(destination, cssOutput),
            postcssBefore: [
                cssnext,
            ],
        })
    }

    function bundle(callback) {
        let startTime = Date.now()
        b.bundle()
            .on('error', console.error)
            .pipe(source(output))
            .pipe(buffer())
            .pipe(production ? uglify({output: {ascii_only: true}}) : identity())
            .pipe(gulp.dest(destination))
            .on('end', () => {
                let time = (Date.now() - startTime) / 1000
                console.log(`Bundled ${output} in ${time}s.`)
                if (!watch) {
                    callback()
                }
            })
    }

    await pify(bundle)()
}

gulp.task('copyStaticFiles', () => {
    for (let filename in staticFiles) {
        console.log(`Copying '${filename}' to '${staticFiles[filename]}'..`)
        gulp.src(filename)
            .pipe(gulp.dest(staticFiles[filename]))
    }
})

gulp.task('copyStaticFiles-watch', ['copyStaticFiles'], () => {
    Object.entries(staticFiles).forEach(([filename, destination]) => {
        gulp.watch(filename)
            .on('change', event => {
                console.log(`Copying '${filename}' to '${staticFiles[filename]}'..`)
                return gulp.src(filename)
                    .pipe(gulp.dest(staticFiles[filename]))
            })
    })
})

gulp.task('build-prod', ['copyStaticFiles'], async () => {
    const ps = sourceFiles.map(bundle => createBundle(bundle, {watch: false, production: true}))
    await Promise.all(ps)
})

gulp.task('build', ['copyStaticFiles'], async () => {
    const ps = sourceFiles.map(bundle => createBundle(bundle, {watch: false}))
    await Promise.all(ps)
})

gulp.task('build-watch', ['copyStaticFiles-watch'], async () => {
    const ps = sourceFiles.map(bundle => createBundle(bundle, {watch: true}))
    await Promise.all(ps)
})

// === Tasks for linting the source code ===

const stylelintOptions = {
    failAfterError: false,
    reporters: [
        {formatter: 'string', console: true},
    ],
}

gulp.task('lint', async () => {
    const eslintStream = gulp.src(['src/**/*.js', 'src/**/*.jsx'])
        .pipe(eslint())
        .pipe(eslint.format())
        .pipe(eslint.results(results => {
            // For clarity, also give some output when there are no errors.
            if (results.errorCount === 0) {
                console.log(`No eslint errors.\n`)
            }
        }))
    await streamToPromise(eslintStream)

    const stylelintStream = gulp.src(['src/**/*.css'])
        .pipe(stylelint(stylelintOptions))
    await streamToPromise(stylelintStream)
})

gulp.task('lint-watch', ['lint'], callback => {
    gulp.watch(['src/**/*.js', 'src/**/*.jsx'])
        .on('change', event => {
            return gulp.src(event.path)
                .pipe(eslint())
                .pipe(eslint.format())
        })

    gulp.watch(['src/**/*.css'])
        .on('change', event => {
            return gulp.src(event.path)
                .pipe(stylelint(stylelintOptions))
        })

    // Don't call callback, to wait forever.
})

gulp.task('watch', ['build-watch', 'lint-watch'])

// === Tasks for packaging the extension; results go into ./dist/{browser} ===

function getManifest() {
    const manifest = JSON.parse(fs.readFileSync('./extension/manifest.json'))
    return manifest
}

function getFilename() {
    const { name, version } = getManifest()
    const filename = `${name.toLowerCase()}-${version}`
    return filename
}

gulp.task('package-firefox', async () => {
    const filename = getFilename()
    const buildXpiCommand = `web-ext -s ./extension -a ./dist/firefox build`
    await exec(buildXpiCommand)
    // web-ext will have named the file ${filename}.zip. Change .zip to .xpi.
    await exec(`rename -f "s/\\.zip$/.xpi/" dist/firefox/${filename}.zip`)
})

gulp.task('package-chromium', async () => {
    const filename = getFilename()
    const buildCrxCommand = (
        `crx pack ./extension`
        + ` -o ./dist/chromium/${filename}.crx`
        + ` -p .chrome-extension-key.pem`
    )
    await exec(buildCrxCommand)
})

gulp.task('package', ['package-firefox', 'package-chromium'])
<commit_msg>Small fixes to packaging. - Swap rename for mv. - Ensure dist/chromium exists.<commit_after>import 'core-js/fn/object/entries' // shim Object.entries
import fs from 'fs'
import { exec as nodeExec } from 'child_process'
import pify from 'pify'
import streamToPromise from 'stream-to-promise'
import gulp from 'gulp'
import uglify from 'gulp-uglify'
import identity from 'gulp-identity'
import source from 'vinyl-source-stream'
import buffer from 'vinyl-buffer'
import stylelint from 'gulp-stylelint'
import browserify from 'browserify'
import watchify from 'watchify'
import babelify from 'babelify'
import envify from 'loose-envify/custom'
import eslint from 'gulp-eslint'
import path from 'path'
import cssModulesify from 'css-modulesify'
import cssnext from 'postcss-cssnext'

const exec = pify(nodeExec)

// === Tasks for building the source code; result is put into ./extension ===

const staticFiles = {
    'src/manifest.json': 'extension',
    'src/**/*.html': 'extension',
    'src/local-page/local-page.css': 'extension/local-page',
    'img/*': 'extension/img',
    'node_modules/webextension-polyfill/dist/browser-polyfill.js': 'extension/lib',
    'node_modules/pdfjs-dist/build/pdf.worker.min.js': 'extension/lib',
    'node_modules/material-design-icons/iconfont/*.{eot,ttf,woff,woff2,css}': 'extension/fonts/material-icons',
}

const commonUIEntry = './src/common-ui/components/index.js'

const sourceFiles = [
    {
        entries: ['./src/background.js'],
        output: 'background.js',
        destination: './extension',
    },
    {
        entries: ['./src/content_script.js'],
        output: 'content_script.js',
        destination: './extension',
    },
    {
        entries: ['./src/overview/overview.jsx', commonUIEntry],
        output: 'overview.js',
        destination: './extension/overview',
        cssOutput: 'overview.css',
    },
    {
        entries: ['./src/options/options.jsx', commonUIEntry],
        output: 'options.js',
        destination: './extension/options',
        cssOutput: 'options.css',
    },
    {
        entries: ['./src/local-page/local-page.js'],
        output: 'local-page.js',
        destination: './extension/local-page',
    },
    {
        entries: ['./src/popup/popup.js'],
        output: 'popup.js',
        destination: './extension/popup',
        cssOutput: 'popup.css',
    },
]

const browserifySettings = {
    debug: true,
    extensions: ['.jsx', '.css'],
    paths: ['.'],
}

async function createBundle({entries, output, destination, cssOutput}, {watch = false, production = false}) {
    let b = watch
        ? watchify(browserify({...watchify.args, ...browserifySettings, entries}))
            .on('update', bundle)
        : browserify({...browserifySettings, entries})
    b.transform(babelify)
    b.transform(envify({
        NODE_ENV: production ? 'production' : 'development',
    }), {global: true})

    if (cssOutput) {
        b.plugin(cssModulesify, {
            global: true,
            output: path.join(destination, cssOutput),
            postcssBefore: [
                cssnext,
            ],
        })
    }

    function bundle(callback) {
        let startTime = Date.now()
        b.bundle()
            .on('error', console.error)
            .pipe(source(output))
            .pipe(buffer())
            .pipe(production ? uglify({output: {ascii_only: true}}) : identity())
            .pipe(gulp.dest(destination))
            .on('end', () => {
                let time = (Date.now() - startTime) / 1000
                console.log(`Bundled ${output} in ${time}s.`)
                if (!watch) {
                    callback()
                }
            })
    }

    await pify(bundle)()
}

gulp.task('copyStaticFiles', () => {
    for (let filename in staticFiles) {
        console.log(`Copying '${filename}' to '${staticFiles[filename]}'..`)
        gulp.src(filename)
            .pipe(gulp.dest(staticFiles[filename]))
    }
})

gulp.task('copyStaticFiles-watch', ['copyStaticFiles'], () => {
    Object.entries(staticFiles).forEach(([filename, destination]) => {
        gulp.watch(filename)
            .on('change', event => {
                console.log(`Copying '${filename}' to '${staticFiles[filename]}'..`)
                return gulp.src(filename)
                    .pipe(gulp.dest(staticFiles[filename]))
            })
    })
})

gulp.task('build-prod', ['copyStaticFiles'], async () => {
    const ps = sourceFiles.map(bundle => createBundle(bundle, {watch: false, production: true}))
    await Promise.all(ps)
})

gulp.task('build', ['copyStaticFiles'], async () => {
    const ps = sourceFiles.map(bundle => createBundle(bundle, {watch: false}))
    await Promise.all(ps)
})

gulp.task('build-watch', ['copyStaticFiles-watch'], async () => {
    const ps = sourceFiles.map(bundle => createBundle(bundle, {watch: true}))
    await Promise.all(ps)
})

// === Tasks for linting the source code ===

const stylelintOptions = {
    failAfterError: false,
    reporters: [
        {formatter: 'string', console: true},
    ],
}

gulp.task('lint', async () => {
    const eslintStream = gulp.src(['src/**/*.js', 'src/**/*.jsx'])
        .pipe(eslint())
        .pipe(eslint.format())
        .pipe(eslint.results(results => {
            // For clarity, also give some output when there are no errors.
            if (results.errorCount === 0) {
                console.log(`No eslint errors.\n`)
            }
        }))
    await streamToPromise(eslintStream)

    const stylelintStream = gulp.src(['src/**/*.css'])
        .pipe(stylelint(stylelintOptions))
    await streamToPromise(stylelintStream)
})

gulp.task('lint-watch', ['lint'], callback => {
    gulp.watch(['src/**/*.js', 'src/**/*.jsx'])
        .on('change', event => {
            return gulp.src(event.path)
                .pipe(eslint())
                .pipe(eslint.format())
        })

    gulp.watch(['src/**/*.css'])
        .on('change', event => {
            return gulp.src(event.path)
                .pipe(stylelint(stylelintOptions))
        })

    // Don't call callback, to wait forever.
})

gulp.task('watch', ['build-watch', 'lint-watch'])

// === Tasks for packaging the extension; results go into ./dist/{browser} ===

function getManifest() {
    const manifest = JSON.parse(fs.readFileSync('./extension/manifest.json'))
    return manifest
}

function getFilename() {
    const { name, version } = getManifest()
    const filename = `${name.toLowerCase()}-${version}`
    return filename
}

gulp.task('package-firefox', async () => {
    const filename = getFilename()
    const buildXpiCommand = `web-ext -s ./extension -a ./dist/firefox build`
    await exec(buildXpiCommand)
    // web-ext will have named the file ${filename}.zip. Change .zip to .xpi.
    await exec(`mv dist/firefox/${filename}.zip dist/firefox/${filename}.xpi`)
})

gulp.task('package-chromium', async () => {
    const filename = getFilename()
    const buildCrxCommand = (
        `crx pack ./extension`
        + ` -o ./dist/chromium/${filename}.crx`
        + ` -p .chrome-extension-key.pem`
    )
    // crx fails if the output directory is not there.
    await exec(`mkdir -p dist/chromium`)
    await exec(buildCrxCommand)
})

gulp.task('package', ['package-firefox', 'package-chromium'])
<|endoftext|>


<commit_before>var ws;

var exp_num = 0;

function WebSocketTest(server) {
    var ws;
    if ("WebSocket" in window) {
        ws = new WebSocket("ws://" + server + "/ws/");
        ws.onopen = function() {
            toggle_status('on');
            console.log("Connection established");
        };
        ws.onmessage = function (evt) {
            var channel = evt.data.split(' ', 1)[0];
            var received_msg = evt.data.substring(evt.data.indexOf(' ') + 1)

            var msg = jQuery.parseJSON(received_msg);

            switch(channel.toUpperCase()){
                case 'STATE':
                    change_state(msg['state']);
                    break;
                case 'OBSERVER':
                    update_info(msg['observer']);
                    break;
                case 'FIELD':
                    update_info(msg['field']);
                    break;
                case 'STATUS':
                    change_state(msg['state']);

                    var observer = msg['observatory']['observer'];
                    var observation = msg['observatory']['observation'];

                    observer['local_evening_astro_time'] = trim_time(observer['local_evening_astro_time']);
                    observer['local_morning_astro_time'] = trim_time(observer['local_morning_astro_time']);

                    observer['local_moon_illumination'] = pretty_number(observer['local_moon_illumination'] * 100);
                    observer['local_moon_alt'] = pretty_number(observer['local_moon_alt']);

                    // Update parts of the page

                    if (observation){
                        update_info(observation);
                        if (observation['current_exp'] != exp_num){
                            // $('#observation_info .timer').timer('reset');
                            exp_num = observation['current_exp'];
                        }
                    }

                    update_info(observer);
                    // $('#system_panel .timer').timer('reset');

                    update_info(msg['observatory']['mount']);
                    // $('#mount_panel .timer').timer('reset');

                    refresh_images();
                    break;
                case 'ENVIRONMENT':
                    update_environment(msg['data']);
                    break;
                case 'WEATHER':
                    update_info(msg['data']);
                    update_safety(msg['data']['safe']);

                    // $('#weather_panel .timer').timer('reset');
                    break;
                case 'CAMERA':
                    update_cameras(msg);
                    break;
                case 'PANBOT':
                case 'PANCHAT':
                case 'PEAS_SHELL':
                case 'POCS_SHELL':
                    add_chat_item(channel, msg.message, msg.timestamp);
                    break;
                default:
                    break;
            }

        };
        ws.onclose = function() {
            toggle_status('off');
        };
    } else {
        toggle_status('error');
    }

    return ws;
}

function update_safety(is_safe){
    if(is_safe){
        $('.safe_condition').html('Safe').removeClass('text-warning').addClass('text-success');
        toggle_status('on');
    } else {
        $('.safe_condition').html('Unsafe').removeClass('text-success').addClass('text-warning');
        toggle_status('unsafe');
    }
}

function toggle_status(status){
    var safety_cls, border_cls;

    // Update depending on status
    if (status == 'on'){
        safety_cls = 'success';
        border_cls = 'safe_borders';
    } else if (status == 'off'){
        safety_cls = 'warning';
        border_cls = 'warning_borders';
    } else {
        safety_cls = 'danger';
        border_cls = 'unsafe_borders';
    }

    $('.safety-border').removeClass('safe_borders warning_borders unsafe_borders').addClass(border_cls);
}

/******************** Update Info Methods ****************************/

function change_state(state){
    var icon = $('.current_state i');
    var text = $('.current_state span');

    icon.removeClass().addClass('fa').addClass('success');
    text.removeClass('warning danger').html(state.toUpperCase());
    switch(state) {
        case 'analyzing':
            icon.addClass('fa-calculator');
            break;
        case 'tracking':
            icon.addClass('fa-binoculars');
            break;
        case 'observing':
            icon.addClass('fa-camera');
            break;
        case 'pointing':
            icon.addClass('fa-bullseye');
            break;
        case 'slewing':
            icon.addClass('fa-cog fa-spin');
            break;
        case 'scheduling':
            icon.addClass('fa-tasks');
            break;
        case 'ready':
            icon.addClass('fa-thumbs-o-up');
            break;
        case 'parking':
        case 'parked':
            icon.addClass('fa-car');
            break;
        case 'sleeping':
            icon.addClass('fa-check-circle');
            break;
        default:
            icon.addClass('fa-circle');
    }
    reload_img($('.state_img'));
}

// Find all the elements with the class that matches a return value
// and update their html
function update_info(status){
    $.each(status, function(key, val){
        $('.' + key).each(function(idx, elem){
            $(elem).html(val);
        })
    });
}

function update_cameras(cameras){
    $.each(cameras, function(cam_name, props){
        $.each(props, function(prop, val){
            $('#' + cam_name + ' .' + prop).each(function(idx, elem){
                $(elem).html(val);
            });
            if (prop == 'exptime'){
                // Start the progress bar
                var pb = $('#' + cam_name + ' .progress-bar');
                pb.width('100%');

                var exptime = $('#' + cam_name + ' .exptime');
                exptime.html(val);

                var total_time = val;
                var count_time = val;

                var exp_count = $('#' + cam_name + ' .exp_count');
                // exp_count.timer('remove');
                exp_count.timer({
                    duration: '2s',
                    callback: function(){
                        count_time = count_time - 2;
                        var perc = (count_time/total_time) * 100;
                        console.log(perc);

                        if (perc < 0){
                            pb.width('0%');
                            exp_count.timer('remove');
                        } else {
                            pb.width(perc + '%');
                        }

                    },
                    repeat: true,
                });

                var pb_count = function(cb, count){
                    var width_perc = (count / val) * 100;
                    pb.width(width_perc + '%');
                    setTimeout(cb, 1000, cb, --count);
                };

                setTimeout(pb_count, 1000, pb_count, val);
            }
        });

    });

}

function update_environment(info){
    try {
        var camera_info = info['camera_box'];
        $('.camera_box_humidity_00').html(pretty_number(camera_info['humidity']));
        $('.camera_box_temp_00').html(pretty_number(camera_info['temp_01']));
    } catch(err) {
        console.log(err);
    }

    try {
        var computer_info = info['computer_box'];
        $('.computer_box_humidity_00').html(pretty_number(computer_info['humidity']));
        $('.computer_box_temp_00').html(pretty_number(computer_info['temp_00']));
        $('.computer_box_temp_01').html(pretty_number(computer_info['temp_01']));
        $('.computer_box_temp_02').html(pretty_number(computer_info['temp_02']));
        $('.computer_box_temp_03').html(pretty_number(computer_info['temp_03']));
    } catch(err) {
        console.log(err);
    }
}

function add_chat_item(name, msg, time){

    item = '
';
    item = item + '
';
    item = item + ' ';
    item = item + '
';
    item = item + '
';
    item = item + '  ' + time +' UTC';
    item = item + '
';
    item = item + '    ';
    item = item + name;
    item = item + '    ';
    item = item + msg;
    item = item + '  ';
    item = item + '
';

    $('#bot_chat').prepend(item);
}

/******************** Convenience Methods ****************************/

// Refresh all images with `img_refresh` container class
function refresh_images(){
    $.each($('.img_refresh img'), function(idx, img){
        reload_img(img);
    });
}

// Reload individual image
function reload_img(img){
    base = $(img).attr('src').split('?')[0];
    // console.log("Reloading image: " + $(img).attr('src'));

    // Hack for others
    if(base.startsWith('http')){
        new_src = $(img).attr('src');
    } else {
        new_src = base + '?' + new Date().getTime();
    }

    $(img).attr('src', new_src);

    // Update the link so not pointing at thumbnail
    // $(img.parentlEement).attr('href', new_src.replace('tn_', ''));
}

function trim_time(t){
    var time = t;
    if (typeof(t) == 'string'){
        time = t.split(':').slice(0,-1).join(':');
    }
    return time;
}

function pretty_number(num){
    return parseFloat(num).toFixed(2);
}
<commit_msg>* Fixing avatar<commit_after>var ws;

var exp_num = 0;

function WebSocketTest(server) {
    var ws;
    if ("WebSocket" in window) {
        ws = new WebSocket("ws://" + server + "/ws/");
        ws.onopen = function() {
            toggle_status('on');
            console.log("Connection established");
        };
        ws.onmessage = function (evt) {
            var channel = evt.data.split(' ', 1)[0];
            var received_msg = evt.data.substring(evt.data.indexOf(' ') + 1)

            var msg = jQuery.parseJSON(received_msg);

            switch(channel.toUpperCase()){
                case 'STATE':
                    change_state(msg['state']);
                    break;
                case 'OBSERVER':
                    update_info(msg['observer']);
                    break;
                case 'FIELD':
                    update_info(msg['field']);
                    break;
                case 'STATUS':
                    change_state(msg['state']);

                    var observer = msg['observatory']['observer'];
                    var observation = msg['observatory']['observation'];

                    observer['local_evening_astro_time'] = trim_time(observer['local_evening_astro_time']);
                    observer['local_morning_astro_time'] = trim_time(observer['local_morning_astro_time']);

                    observer['local_moon_illumination'] = pretty_number(observer['local_moon_illumination'] * 100);
                    observer['local_moon_alt'] = pretty_number(observer['local_moon_alt']);

                    // Update parts of the page

                    if (observation){
                        update_info(observation);
                        if (observation['current_exp'] != exp_num){
                            // $('#observation_info .timer').timer('reset');
                            exp_num = observation['current_exp'];
                        }
                    }

                    update_info(observer);
                    // $('#system_panel .timer').timer('reset');

                    update_info(msg['observatory']['mount']);
                    // $('#mount_panel .timer').timer('reset');

                    refresh_images();
                    break;
                case 'ENVIRONMENT':
                    update_environment(msg['data']);
                    break;
                case 'WEATHER':
                    update_info(msg['data']);
                    update_safety(msg['data']['safe']);

                    // $('#weather_panel .timer').timer('reset');
                    break;
                case 'CAMERA':
                    update_cameras(msg);
                    break;
                case 'PANBOT':
                case 'PANCHAT':
                case 'PEAS_SHELL':
                case 'POCS_SHELL':
                    add_chat_item(channel, msg.message, msg.timestamp);
                    break;
                default:
                    break;
            }

        };
        ws.onclose = function() {
            toggle_status('off');
        };
    } else {
        toggle_status('error');
    }

    return ws;
}

function update_safety(is_safe){
    if(is_safe){
        $('.safe_condition').html('Safe').removeClass('text-warning').addClass('text-success');
        toggle_status('on');
    } else {
        $('.safe_condition').html('Unsafe').removeClass('text-success').addClass('text-warning');
        toggle_status('unsafe');
    }
}

function toggle_status(status){
    var safety_cls, border_cls;

    // Update depending on status
    if (status == 'on'){
        safety_cls = 'success';
        border_cls = 'safe_borders';
    } else if (status == 'off'){
        safety_cls = 'warning';
        border_cls = 'warning_borders';
    } else {
        safety_cls = 'danger';
        border_cls = 'unsafe_borders';
    }

    $('.safety-border').removeClass('safe_borders warning_borders unsafe_borders').addClass(border_cls);
}

/******************** Update Info Methods ****************************/

function change_state(state){
    var icon = $('.current_state i');
    var text = $('.current_state span');

    icon.removeClass().addClass('fa').addClass('success');
    text.removeClass('warning danger').html(state.toUpperCase());
    switch(state) {
        case 'analyzing':
            icon.addClass('fa-calculator');
            break;
        case 'tracking':
            icon.addClass('fa-binoculars');
            break;
        case 'observing':
            icon.addClass('fa-camera');
            break;
        case 'pointing':
            icon.addClass('fa-bullseye');
            break;
        case 'slewing':
            icon.addClass('fa-cog fa-spin');
            break;
        case 'scheduling':
            icon.addClass('fa-tasks');
            break;
        case 'ready':
            icon.addClass('fa-thumbs-o-up');
            break;
        case 'parking':
        case 'parked':
            icon.addClass('fa-car');
            break;
        case 'sleeping':
            icon.addClass('fa-check-circle');
            break;
        default:
            icon.addClass('fa-circle');
    }
    reload_img($('.state_img'));
}

// Find all the elements with the class that matches a return value
// and update their html
function update_info(status){
    $.each(status, function(key, val){
        $('.' + key).each(function(idx, elem){
            $(elem).html(val);
        })
    });
}

function update_cameras(cameras){
    $.each(cameras, function(cam_name, props){
        $.each(props, function(prop, val){
            $('#' + cam_name + ' .' + prop).each(function(idx, elem){
                $(elem).html(val);
            });
            if (prop == 'exptime'){
                // Start the progress bar
                var pb = $('#' + cam_name + ' .progress-bar');
                pb.width('100%');

                var exptime = $('#' + cam_name + ' .exptime');
                exptime.html(val);

                var total_time = val;
                var count_time = val;

                var exp_count = $('#' + cam_name + ' .exp_count');
                // exp_count.timer('remove');
                exp_count.timer({
                    duration: '2s',
                    callback: function(){
                        count_time = count_time - 2;
                        var perc = (count_time/total_time) * 100;
                        console.log(perc);

                        if (perc < 0){
                            pb.width('0%');
                            exp_count.timer('remove');
                        } else {
                            pb.width(perc + '%');
                        }

                    },
                    repeat: true,
                });

                var pb_count = function(cb, count){
                    var width_perc = (count / val) * 100;
                    pb.width(width_perc + '%');
                    setTimeout(cb, 1000, cb, --count);
                };

                setTimeout(pb_count, 1000, pb_count, val);
            }
        });

    });

}

function update_environment(info){
    try {
        var camera_info = info['camera_box'];
        $('.camera_box_humidity_00').html(pretty_number(camera_info['humidity']));
        $('.camera_box_temp_00').html(pretty_number(camera_info['temp_01']));
    } catch(err) {
        console.log(err);
    }

    try {
        var computer_info = info['computer_box'];
        $('.computer_box_humidity_00').html(pretty_number(computer_info['humidity']));
        $('.computer_box_temp_00').html(pretty_number(computer_info['temp_00']));
        $('.computer_box_temp_01').html(pretty_number(computer_info['temp_01']));
        $('.computer_box_temp_02').html(pretty_number(computer_info['temp_02']));
        $('.computer_box_temp_03').html(pretty_number(computer_info['temp_03']));
    } catch(err) {
        console.log(err);
    }
}

function add_chat_item(name, msg, time){

    item = '
';
    item = item + '
';
    item = item + ' ';
    item = item + '
';
    item = item + '
';
    item = item + '  ' + time +' UTC';
    item = item + '
';
    item = item + '    ';
    item = item + name;
    item = item + '    ';
    item = item + msg;
    item = item + '  ';
    item = item + '
';

    $('#bot_chat').prepend(item);
}

/******************** Convenience Methods ****************************/

// Refresh all images with `img_refresh` container class
function refresh_images(){
    $.each($('.img_refresh img'), function(idx, img){
        reload_img(img);
    });
}

// Reload individual image
function reload_img(img){
    base = $(img).attr('src').split('?')[0];
    // console.log("Reloading image: " + $(img).attr('src'));

    // Hack for others
    if(base.startsWith('http')){
        new_src = $(img).attr('src');
    } else {
        new_src = base + '?' + new Date().getTime();
    }

    $(img).attr('src', new_src);

    // Update the link so not pointing at thumbnail
    // $(img.parentlEement).attr('href', new_src.replace('tn_', ''));
}

function trim_time(t){
    var time = t;
    if (typeof(t) == 'string'){
        time = t.split(':').slice(0,-1).join(':');
    }
    return time;
}

function pretty_number(num){
    return parseFloat(num).toFixed(2);
}
<|endoftext|>


<commit_before>/**
 *
 *  Web Starter Kit
 *  Copyright 2015 Google Inc. All rights reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License
 *
 */

'use strict';

// This gulpfile makes use of new JavaScript features.
// Babel handles this without us having to do anything. It just works.
// You can read more about the new JavaScript features here:
// https://babeljs.io/docs/learn-es2015/

import path from 'path';
import gulp from 'gulp';
import del from 'del';
import runSequence from 'run-sequence';
import browserSync from 'browser-sync';
import swPrecache from 'sw-precache';
import gulpLoadPlugins from 'gulp-load-plugins';
import {output as pagespeed} from 'psi';
import pkg from './package.json';

const $ = gulpLoadPlugins();
const reload = browserSync.reload;

// Lint JavaScript
gulp.task('lint', () =>
  gulp.src('app/scripts/**/*.js')
    .pipe($.eslint())
    .pipe($.eslint.format())
    .pipe($.if(!browserSync.active, $.eslint.failOnError()))
);

// Optimize images
gulp.task('images', () =>
  gulp.src('app/images/**/*')
    .pipe($.cache($.imagemin({
      progressive: true,
      interlaced: true
    })))
    .pipe(gulp.dest('dist/images'))
    .pipe($.size({title: 'images'}))
);

// Copy all files at the root level (app)
gulp.task('copy', () =>
  gulp.src([
    'app/*',
    '!app/*.html',
    'node_modules/apache-server-configs/dist/.htaccess'
  ], {
    dot: true
  }).pipe(gulp.dest('dist'))
    .pipe($.size({title: 'copy'}))
);

// Compile and automatically prefix stylesheets
gulp.task('styles', () => {
  const AUTOPREFIXER_BROWSERS = [
    'ie >= 10',
    'ie_mob >= 10',
    'ff >= 30',
    'chrome >= 34',
    'safari >= 7',
    'opera >= 23',
    'ios >= 7',
    'android >= 4.4',
    'bb >= 10'
  ];

  // For best performance, don't add Sass partials to `gulp.src`
  return gulp.src([
    'app/styles/**/*.scss',
    'app/styles/**/*.css'
  ])
    .pipe($.newer('.tmp/styles'))
    .pipe($.sourcemaps.init())
    .pipe($.sass({
      precision: 10
    }).on('error', $.sass.logError))
    .pipe($.autoprefixer(AUTOPREFIXER_BROWSERS))
    .pipe(gulp.dest('.tmp/styles'))
    // Concatenate and minify styles
    .pipe($.if('*.css', $.cssnano()))
    .pipe($.size({title: 'styles'}))
    .pipe($.sourcemaps.write('./'))
    .pipe(gulp.dest('dist/styles'));
});

// Concatenate and minify JavaScript. Optionally transpiles ES2015 code to ES5.
// to enables ES2015 support remove the line `"only": "gulpfile.babel.js",` in the
// `.babelrc` file.
gulp.task('scripts', () =>
    gulp.src([
      // Note: Since we are not using useref in the scripts build pipeline,
      //       you need to explicitly list your scripts here in the right order
      //       to be correctly concatenated
      './app/scripts/main.js'
      // Other scripts
    ])
      .pipe($.newer('.tmp/scripts'))
      .pipe($.sourcemaps.init())
      .pipe($.babel())
      .pipe($.sourcemaps.write())
      .pipe(gulp.dest('.tmp/scripts'))
      .pipe($.concat('main.min.js'))
      .pipe($.uglify({preserveComments: 'some'}))
      // Output files
      .pipe($.size({title: 'scripts'}))
      .pipe($.sourcemaps.write('.'))
      .pipe(gulp.dest('dist/scripts'))
);

// Scan your HTML for assets & optimize them
gulp.task('html', () => {
  return gulp.src('app/**/*.html')
    .pipe($.useref({searchPath: '{.tmp,app}'}))
    // Remove any unused CSS
    .pipe($.if('*.css', $.uncss({
      html: [
        'app/index.html'
      ],
      // CSS Selectors for UnCSS to ignore
      ignore: []
    })))

    // Concatenate and minify styles
    // In case you are still using useref build blocks
    .pipe($.if('*.css', $.cssnano()))

    // Minify any HTML
    .pipe($.if('*.html', $.htmlmin({
      removeComments: true,
      collapseWhitespace: true,
      collapseBooleanAttributes: true,
      removeAttributeQuotes: true,
      removeRedundantAttributes: true,
      removeEmptyAttributes: true,
      removeScriptTypeAttributes: true,
      removeStyleLinkTypeAttributes: true,
      removeOptionalTags: true
    })))
    // Output files
    .pipe($.if('*.html', $.size({title: 'html', showFiles: true})))
    .pipe(gulp.dest('dist'));
});

// Clean output directory
gulp.task('clean', () => del(['.tmp', 'dist/*', '!dist/.git'], {dot: true}));

// Watch files for changes & reload
gulp.task('serve', ['scripts', 'styles'], () => {
  browserSync({
    notify: false,
    // Customize the Browsersync console logging prefix
    logPrefix: 'WSK',
    // Allow scroll syncing across breakpoints
    scrollElementMapping: ['main', '.mdl-layout'],
    // Run as an https by uncommenting 'https: true'
    // Note: this uses an unsigned certificate which on first access
    //       will present a certificate warning in the browser.
    // https: true,
    server: ['.tmp', 'app'],
    host: '192.168.0.199',
    port: 3000
  });

  gulp.watch(['app/**/*.html'], reload);
  gulp.watch(['app/styles/**/*.{scss,css}'], ['styles', reload]);
  gulp.watch(['app/scripts/**/*.js'], ['lint', 'scripts']);
  gulp.watch(['app/images/**/*'], reload);
});

// Build and serve the output from the dist build
gulp.task('serve:dist', ['default'], () =>
  browserSync({
    notify: false,
    logPrefix: 'WSK',
    // Allow scroll syncing across breakpoints
    scrollElementMapping: ['main', '.mdl-layout'],
    // Run as an https by uncommenting 'https: true'
    // Note: this uses an unsigned certificate which on first access
    //       will present a certificate warning in the browser.
    // https: true,
    server: 'dist',
    host: '192.168.0.199',
    port: 3001
  })
);

// Build production files, the default task
gulp.task('default', ['clean'], cb =>
  runSequence(
    'styles',
    ['lint', 'html', 'scripts', 'images', 'copy'],
    'generate-service-worker',
    cb
  )
);

// Run PageSpeed Insights
gulp.task('pagespeed', cb =>
  // Update the below URL to the public URL of your site
  pagespeed('example.com', {
    strategy: 'mobile'
    // By default we use the PageSpeed Insights free (no API key) tier.
    // Use a Google Developer API key if you have one: http://goo.gl/RkN0vE
    // key: 'YOUR_API_KEY'
  }, cb)
);

// Copy over the scripts that are used in importScripts as part of the generate-service-worker task.
gulp.task('copy-sw-scripts', () => {
  return gulp.src(['node_modules/sw-toolbox/sw-toolbox.js', 'app/scripts/sw/runtime-caching.js'])
    .pipe(gulp.dest('dist/scripts/sw'));
});

// See http://www.html5rocks.com/en/tutorials/service-worker/introduction/ for
// an in-depth explanation of what service workers are and why you should care.
// Generate a service worker file that will provide offline functionality for
// local resources. This should only be done for the 'dist' directory, to allow
// live reload to work as expected when serving from the 'app' directory.
gulp.task('generate-service-worker', ['copy-sw-scripts'], () => {
  const rootDir = 'dist';
  const filepath = path.join(rootDir, 'service-worker.js');

  return swPrecache.write(filepath, {
    // Used to avoid cache conflicts when serving on localhost.
    cacheId: pkg.name || 'web-starter-kit',
    // sw-toolbox.js needs to be listed first. It sets up methods used in runtime-caching.js.
    importScripts: [
      'scripts/sw/sw-toolbox.js',
      'scripts/sw/runtime-caching.js'
    ],
    staticFileGlobs: [
      // Add/remove glob patterns to match your directory setup.
      `${rootDir}/images/**/*`,
      `${rootDir}/scripts/**/*.js`,
      `${rootDir}/styles/**/*.css`,
      `${rootDir}/*.{html,json}`
    ],
    // Translates a static file path to the relative URL that it's served from.
    stripPrefix: path.join(rootDir, path.sep)
  });
});

// Deploy to GH Pages with Gulp
var ghPages = require('gulp-gh-pages');

gulp.task('deploy', function() {
  return gulp.src('./dist/**/*')
    .pipe(ghPages());
});

// Load custom tasks from the `tasks` directory
// Run: `npm install --save-dev require-dir` from the command-line
// try { require('require-dir')('tasks'); } catch (err) { console.error(err); }
<commit_msg>Update required scripts<commit_after>/**
 *
 *  Web Starter Kit
 *  Copyright 2015 Google Inc. All rights reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License
 *
 */

'use strict';

// This gulpfile makes use of new JavaScript features.
// Babel handles this without us having to do anything. It just works.
// You can read more about the new JavaScript features here:
// https://babeljs.io/docs/learn-es2015/

import path from 'path';
import gulp from 'gulp';
import del from 'del';
import runSequence from 'run-sequence';
import browserSync from 'browser-sync';
import swPrecache from 'sw-precache';
import gulpLoadPlugins from 'gulp-load-plugins';
import {output as pagespeed} from 'psi';
import pkg from './package.json';

const $ = gulpLoadPlugins();
const reload = browserSync.reload;

// Lint JavaScript
gulp.task('lint', () =>
  gulp.src('app/scripts/**/*.js')
    .pipe($.eslint())
    .pipe($.eslint.format())
    .pipe($.if(!browserSync.active, $.eslint.failOnError()))
);

// Optimize images
gulp.task('images', () =>
  gulp.src('app/images/**/*')
    .pipe($.cache($.imagemin({
      progressive: true,
      interlaced: true
    })))
    .pipe(gulp.dest('dist/images'))
    .pipe($.size({title: 'images'}))
);

// Copy all files at the root level (app)
gulp.task('copy', () =>
  gulp.src([
    'app/*',
    '!app/*.html',
    'node_modules/apache-server-configs/dist/.htaccess'
  ], {
    dot: true
  }).pipe(gulp.dest('dist'))
    .pipe($.size({title: 'copy'}))
);

// Compile and automatically prefix stylesheets
gulp.task('styles', () => {
  const AUTOPREFIXER_BROWSERS = [
    'ie >= 10',
    'ie_mob >= 10',
    'ff >= 30',
    'chrome >= 34',
    'safari >= 7',
    'opera >= 23',
    'ios >= 7',
    'android >= 4.4',
    'bb >= 10'
  ];

  // For best performance, don't add Sass partials to `gulp.src`
  return gulp.src([
    'app/styles/**/*.scss',
    'app/styles/**/*.css'
  ])
    .pipe($.newer('.tmp/styles'))
    .pipe($.sourcemaps.init())
    .pipe($.sass({
      precision: 10
    }).on('error', $.sass.logError))
    .pipe($.autoprefixer(AUTOPREFIXER_BROWSERS))
    .pipe(gulp.dest('.tmp/styles'))
    // Concatenate and minify styles
    .pipe($.if('*.css', $.cssnano()))
    .pipe($.size({title: 'styles'}))
    .pipe($.sourcemaps.write('./'))
    .pipe(gulp.dest('dist/styles'));
});

// Concatenate and minify JavaScript. Optionally transpiles ES2015 code to ES5.
// to enables ES2015 support remove the line `"only": "gulpfile.babel.js",` in the
// `.babelrc` file.
gulp.task('scripts', () =>
    gulp.src([
      // Note: Since we are not using useref in the scripts build pipeline,
      //       you need to explicitly list your scripts here in the right order
      //       to be correctly concatenated
      './app/scripts/main.js',
      // Other scripts
      './app/scripts/perfmatters.js'
    ])
      .pipe($.newer('.tmp/scripts'))
      .pipe($.sourcemaps.init())
      .pipe($.babel())
      .pipe($.sourcemaps.write())
      .pipe(gulp.dest('.tmp/scripts'))
      .pipe($.concat('main.min.js'))
      .pipe($.uglify({preserveComments: 'some'}))
      // Output files
      .pipe($.size({title: 'scripts'}))
      .pipe($.sourcemaps.write('.'))
      .pipe(gulp.dest('dist/scripts'))
);

// Scan your HTML for assets & optimize them
gulp.task('html', () => {
  return gulp.src('app/**/*.html')
    .pipe($.useref({searchPath: '{.tmp,app}'}))
    // Remove any unused CSS
    .pipe($.if('*.css', $.uncss({
      html: [
        'app/index.html'
      ],
      // CSS Selectors for UnCSS to ignore
      ignore: []
    })))

    // Concatenate and minify styles
    // In case you are still using useref build blocks
    .pipe($.if('*.css', $.cssnano()))

    // Minify any HTML
    .pipe($.if('*.html', $.htmlmin({
      removeComments: true,
      collapseWhitespace: true,
      collapseBooleanAttributes: true,
      removeAttributeQuotes: true,
      removeRedundantAttributes: true,
      removeEmptyAttributes: true,
      removeScriptTypeAttributes: true,
      removeStyleLinkTypeAttributes: true,
      removeOptionalTags: true
    })))
    // Output files
    .pipe($.if('*.html', $.size({title: 'html', showFiles: true})))
    .pipe(gulp.dest('dist'));
});

// Clean output directory
gulp.task('clean', () => del(['.tmp', 'dist/*', '!dist/.git'], {dot: true}));

// Watch files for changes & reload
gulp.task('serve', ['scripts', 'styles'], () => {
  browserSync({
    notify: false,
    // Customize the Browsersync console logging prefix
    logPrefix: 'WSK',
    // Allow scroll syncing across breakpoints
    scrollElementMapping: ['main', '.mdl-layout'],
    // Run as an https by uncommenting 'https: true'
    // Note: this uses an unsigned certificate which on first access
    //       will present a certificate warning in the browser.
    // https: true,
    server: ['.tmp', 'app'],
    host: '192.168.0.199',
    port: 3000
  });

  gulp.watch(['app/**/*.html'], reload);
  gulp.watch(['app/styles/**/*.{scss,css}'], ['styles', reload]);
  gulp.watch(['app/scripts/**/*.js'], ['lint', 'scripts']);
  gulp.watch(['app/images/**/*'], reload);
});

// Build and serve the output from the dist build
gulp.task('serve:dist', ['default'], () =>
  browserSync({
    notify: false,
    logPrefix: 'WSK',
    // Allow scroll syncing across breakpoints
    scrollElementMapping: ['main', '.mdl-layout'],
    // Run as an https by uncommenting 'https: true'
    // Note: this uses an unsigned certificate which on first access
    //       will present a certificate warning in the browser.
    // https: true,
    server: 'dist',
    host: '192.168.0.199',
    port: 3001
  })
);

// Build production files, the default task
gulp.task('default', ['clean'], cb =>
  runSequence(
    'styles',
    ['lint', 'html', 'scripts', 'images', 'copy'],
    'generate-service-worker',
    cb
  )
);

// Run PageSpeed Insights
gulp.task('pagespeed', cb =>
  // Update the below URL to the public URL of your site
  pagespeed('http://petarmihaylov.github.io/UFEWDN-P4-Website-Optimization/', {
    strategy: 'mobile'
    // By default we use the PageSpeed Insights free (no API key) tier.
    // Use a Google Developer API key if you have one: http://goo.gl/RkN0vE
    // key: 'YOUR_API_KEY'
  }, cb)
);

// Copy over the scripts that are used in importScripts as part of the generate-service-worker task.
gulp.task('copy-sw-scripts', () => {
  return gulp.src(['node_modules/sw-toolbox/sw-toolbox.js', 'app/scripts/sw/runtime-caching.js'])
    .pipe(gulp.dest('dist/scripts/sw'));
});

// See http://www.html5rocks.com/en/tutorials/service-worker/introduction/ for
// an in-depth explanation of what service workers are and why you should care.
// Generate a service worker file that will provide offline functionality for
// local resources. This should only be done for the 'dist' directory, to allow
// live reload to work as expected when serving from the 'app' directory.
gulp.task('generate-service-worker', ['copy-sw-scripts'], () => {
  const rootDir = 'dist';
  const filepath = path.join(rootDir, 'service-worker.js');

  return swPrecache.write(filepath, {
    // Used to avoid cache conflicts when serving on localhost.
    cacheId: pkg.name || 'web-starter-kit',
    // sw-toolbox.js needs to be listed first. It sets up methods used in runtime-caching.js.
    importScripts: [
      'scripts/sw/sw-toolbox.js',
      'scripts/sw/runtime-caching.js'
    ],
    staticFileGlobs: [
      // Add/remove glob patterns to match your directory setup.
      `${rootDir}/images/**/*`,
      `${rootDir}/scripts/**/*.js`,
      `${rootDir}/styles/**/*.css`,
      `${rootDir}/*.{html,json}`
    ],
    // Translates a static file path to the relative URL that it's served from.
    stripPrefix: path.join(rootDir, path.sep)
  });
});

// Deploy to GH Pages with Gulp
var ghPages = require('gulp-gh-pages');

gulp.task('deploy', function() {
  return gulp.src('./dist/**/*')
    .pipe(ghPages());
});

// Load custom tasks from the `tasks` directory
// Run: `npm install --save-dev require-dir` from the command-line
// try { require('require-dir')('tasks'); } catch (err) { console.error(err); }
<|endoftext|>


<commit_before>/**
 * starter-vanilla
 * gulpfile.js
 */

import gulp from 'gulp';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
import del from 'del';
import {argv} from 'yargs';
import browserify from 'browserify';
import watchify from 'watchify';
import babelify from 'babelify';
import browserSync from 'browser-sync';

const $ = require('gulp-load-plugins')();

/**
 * Config
 *
 * @type {Object}
 */
const config = {
    dev: !argv.production,
    bsBrowser: 'google chrome canary',
    bsProxyServer: null
};

/**
 * Default Task
 * Alias for "watch" task.
 */
gulp.task('default', ['watch']);

/**
 * Build Task
 * Build app.
 */
gulp.task('build', ['clean', 'browserify', 'sass', 'assets', 'index']);

/**
 * Watch Task
 * Build app, open browser with browser-sync, watch for changes
 * to source files and reload browser.
 */
gulp.task('watch', ['clean', 'watch:task', 'watchify', 'sass', 'assets', 'index'], browserSyncTask);

/**
 * Browserify Task
 * Run browserify to compile javascript.
 */
gulp.task('browserify', () => {
    let bundler = browserify('./src/app.js', { debug: config.dev });

    // Transforms
    bundler.transform(babelify);

    return bundler.bundle()
        .pipe($.plumber({ errorHandler: onError }))
        .pipe(source('bundle.js'))
        .pipe(buffer())
        .pipe($.if(config.dev, $.sourcemaps.init({ loadMaps: true })))
            .pipe($.uglify())
        .pipe($.if(config.dev, $.sourcemaps.write('./')))
        .pipe(gulp.dest('./dist'));
});

/**
 * Watchify Task
 * Run browserify to compile javascriptand watch for
 * changes using watchify to reduce compile time.
 */
gulp.task('watchify', () => {
    let options = watchify.args;
    options.debug = true;

    var bundler = watchify(browserify('./src/app.js', options));

    // Transforms
    bundler.transform(babelify);

    function rebundle () {
        return bundler.bundle()
            .on('error', onError)
            .pipe(source('bundle.js'))
            .pipe(buffer())
            .pipe($.sourcemaps.init({ loadMaps: true }))
            .pipe($.sourcemaps.write('./'))
            .pipe(gulp.dest('./dist'))
            .pipe(browserSync.stream());
    }

    bundler.on('update', rebundle);
    return rebundle();
});

/**
 * Watch Task
 * Start watching for changesto relevant files.
 */
gulp.task('watch:task', () => {
    gulp.watch('./src/index.html', ['index']);
    gulp.watch('./src/assets/**/*.*', ['assets']);
    gulp.watch('./src/sass/**/*.scss', ['sass']);
});

/**
 * Sass Task
 * Process sass und inject changes into browser via  browser-sync.
 */
gulp.task('sass', () => {
    return gulp.src('src/sass/*.scss', {})
        .pipe($.if(config.dev, $.sourcemaps.init()))
            .pipe($.plumber({ errorHandler: onError }))
            .pipe($.sass({ outputStyle: 'compressed' }))
            .pipe($.autoprefixer())
            .pipe($.concat('app.css'))
        .pipe($.if(config.dev, $.sourcemaps.write('./')))
        .pipe(gulp.dest('./dist'))
        .pipe(browserSync.stream({ match: '**/*.css' }));
});

/**
 * Assets Task
 * Optimize assets and copy them to the "dist" directory.
 */
gulp.task('assets', () => {
    return gulp.src('./src/assets/**/*.*')
        .pipe($.plumber({ errorHandler: onError }))
        .pipe($.imagemin({ optimizationLevel: 4, progressive: true }))
        .pipe(gulp.dest('./dist/assets'))
        .pipe(browserSync.stream());
});

/**
 * Index Task
 * Copy the index.html file to the "dist" directory.
 */
gulp.task('index', () => {
    return gulp.src('src/index.html')
        .pipe(gulp.dest('dist'))
        .pipe(browserSync.stream());
});

/**
 * Prepare Revision Task
 * Create versioned versions of js / css files in "dist" directory
 * and create a "rev-manifest.json" file.
 */
gulp.task('revision:prepare', () => {
    return gulp.src(['dist/*.css', 'dist/*.js'])
        .pipe($.rev())
        .pipe(gulp.dest('dist'))
        .pipe($.rev.manifest())
        .pipe(gulp.dest('dist'));
});

/**
 * Revision Task
 * Replace mentions of static assets that are present in the
 * "rev-manifest.json" file.
 */
gulp.task('revision', ['revision:prepare'], () => {
    let manifest = gulp.src('dist/rev-manifest.json');

    return gulp.src('dist/index.html')
        .pipe($.revReplace({ manifest: manifest }))
        .pipe(gulp.dest('dist'));
});

/**
 * Clean Task
 * Remove files / folders in "dist" directory.
 */
gulp.task('clean', (cb) => {
    del.sync(['dist']);
    cb();
});

/**
 * List Task
 * Liast all tasks.
 */
gulp.task('list', $.taskListing);

/**
 * Start browser-sync
 *
 * @return {void}
 */
function browserSyncTask () {
    var bsOptions = {
        browser: config.bsBrowser || 'google chrome',
        online: false,
        logSnippet: false,
        notify: false,
        ghostMode: false,
        server: {
            baseDir: './dist'
        }
    };

    if (config.bsProxyServer) {
        bsOptions.proxy = config.bsProxyServer;
        delete bsOptions.server;
    }

    browserSync(bsOptions);
}

/**
 * gulp-plumber error handler
 *
 * @param  {string} err
 *
 * @return {this}
 */
function onError (err) {
    $.util.beep();
    $.util.log($.util.colors.red(err));
    this.emit('end');
    return this;
}<commit_msg>Build: Use google chrome by default with browser-sync<commit_after>/**
 * starter-vanilla
 * gulpfile.js
 */

import gulp from 'gulp';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
import del from 'del';
import {argv} from 'yargs';
import browserify from 'browserify';
import watchify from 'watchify';
import babelify from 'babelify';
import browserSync from 'browser-sync';

const $ = require('gulp-load-plugins')();

/**
 * Config
 *
 * @type {Object}
 */
const config = {
    dev: !argv.production,
    bsBrowser: 'google chrome',
    bsProxyServer: null
};

/**
 * Default Task
 * Alias for "watch" task.
 */
gulp.task('default', ['watch']);

/**
 * Build Task
 * Build app.
 */
gulp.task('build', ['clean', 'browserify', 'sass', 'assets', 'index']);

/**
 * Watch Task
 * Build app, open browser with browser-sync, watch for changes
 * to source files and reload browser.
 */
gulp.task('watch', ['clean', 'watch:task', 'watchify', 'sass', 'assets', 'index'], browserSyncTask);

/**
 * Browserify Task
 * Run browserify to compile javascript.
 */
gulp.task('browserify', () => {
    let bundler = browserify('./src/app.js', { debug: config.dev });

    // Transforms
    bundler.transform(babelify);

    return bundler.bundle()
        .pipe($.plumber({ errorHandler: onError }))
        .pipe(source('bundle.js'))
        .pipe(buffer())
        .pipe($.if(config.dev, $.sourcemaps.init({ loadMaps: true })))
            .pipe($.uglify())
        .pipe($.if(config.dev, $.sourcemaps.write('./')))
        .pipe(gulp.dest('./dist'));
});

/**
 * Watchify Task
 * Run browserify to compile javascriptand watch for
 * changes using watchify to reduce compile time.
 */
gulp.task('watchify', () => {
    let options = watchify.args;
    options.debug = true;

    var bundler = watchify(browserify('./src/app.js', options));

    // Transforms
    bundler.transform(babelify);

    function rebundle () {
        return bundler.bundle()
            .on('error', onError)
            .pipe(source('bundle.js'))
            .pipe(buffer())
            .pipe($.sourcemaps.init({ loadMaps: true }))
            .pipe($.sourcemaps.write('./'))
            .pipe(gulp.dest('./dist'))
            .pipe(browserSync.stream());
    }

    bundler.on('update', rebundle);
    return rebundle();
});

/**
 * Watch Task
 * Start watching for changesto relevant files.
 */
gulp.task('watch:task', () => {
    gulp.watch('./src/index.html', ['index']);
    gulp.watch('./src/assets/**/*.*', ['assets']);
    gulp.watch('./src/sass/**/*.scss', ['sass']);
});

/**
 * Sass Task
 * Process sass und inject changes into browser via  browser-sync.
 */
gulp.task('sass', () => {
    return gulp.src('src/sass/*.scss', {})
        .pipe($.if(config.dev, $.sourcemaps.init()))
            .pipe($.plumber({ errorHandler: onError }))
            .pipe($.sass({ outputStyle: 'compressed' }))
            .pipe($.autoprefixer())
            .pipe($.concat('app.css'))
        .pipe($.if(config.dev, $.sourcemaps.write('./')))
        .pipe(gulp.dest('./dist'))
        .pipe(browserSync.stream({ match: '**/*.css' }));
});

/**
 * Assets Task
 * Optimize assets and copy them to the "dist" directory.
 */
gulp.task('assets', () => {
    return gulp.src('./src/assets/**/*.*')
        .pipe($.plumber({ errorHandler: onError }))
        .pipe($.imagemin({ optimizationLevel: 4, progressive: true }))
        .pipe(gulp.dest('./dist/assets'))
        .pipe(browserSync.stream());
});

/**
 * Index Task
 * Copy the index.html file to the "dist" directory.
 */
gulp.task('index', () => {
    return gulp.src('src/index.html')
        .pipe(gulp.dest('dist'))
        .pipe(browserSync.stream());
});

/**
 * Prepare Revision Task
 * Create versioned versions of js / css files in "dist" directory
 * and create a "rev-manifest.json" file.
 */
gulp.task('revision:prepare', () => {
    return gulp.src(['dist/*.css', 'dist/*.js'])
        .pipe($.rev())
        .pipe(gulp.dest('dist'))
        .pipe($.rev.manifest())
        .pipe(gulp.dest('dist'));
});

/**
 * Revision Task
 * Replace mentions of static assets that are present in the
 * "rev-manifest.json" file.
 */
gulp.task('revision', ['revision:prepare'], () => {
    let manifest = gulp.src('dist/rev-manifest.json');

    return gulp.src('dist/index.html')
        .pipe($.revReplace({ manifest: manifest }))
        .pipe(gulp.dest('dist'));
});

/**
 * Clean Task
 * Remove files / folders in "dist" directory.
 */
gulp.task('clean', (cb) => {
    del.sync(['dist']);
    cb();
});

/**
 * List Task
 * Liast all tasks.
 */
gulp.task('list', $.taskListing);

/**
 * Start browser-sync
 *
 * @return {void}
 */
function browserSyncTask () {
    var bsOptions = {
        browser: config.bsBrowser || 'google chrome',
        online: false,
        logSnippet: false,
        notify: false,
        ghostMode: false,
        server: {
            baseDir: './dist'
        }
    };

    if (config.bsProxyServer) {
        bsOptions.proxy = config.bsProxyServer;
        delete bsOptions.server;
    }

    browserSync(bsOptions);
}

/**
 * gulp-plumber error handler
 *
 * @param  {string} err
 *
 * @return {this}
 */
function onError (err) {
    $.util.beep();
    $.util.log($.util.colors.red(err));
    this.emit('end');
    return this;
}
<|endoftext|>