<commit_before>/*
 * Generated on 2014-03-26
 * generator-assemble v0.4.11
 * https://github.com/assemble/generator-assemble
 *
 * Copyright (c) 2014 Hariadi Hinta
 * Licensed under the MIT license.
 */

'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// '<%= config.src %>/templates/pages/{,*/}*.hbs'
// use this if you want to match all subfolders:
// '<%= config.src %>/templates/pages/**/*.hbs'

module.exports = function(grunt) {

  require('time-grunt')(grunt);
  //jit-grunt loads only the npm tasks required for the grunt task.
  //makes livereload much faster.
  require('jit-grunt')(grunt, {
    replace: 'grunt-text-replace'
  });

  // Project configuration.
  grunt.initConfig({
    config: {
      preview: {
        options: {
          variables: {
            aws: grunt.file.readJSON('configs/s3Config.json'),
            environment: 'preview',
            environmentData: 'website-guts/data/environments/production/environmentVariables.json',
            assets_dir: '/assets',
            link_path: '',
            sassSourceMap: false,
            sassImagePath: '/assets/img',
            compress_js: true,
            concat_banner: '(function($){ \n\n' +
                           '  window.optly = window.optly || {}; \n\n' +
                           '  window.optly.mrkt = window.optly.mrkt || {}; \n\n' +
                           '  try { \n\n',
            concat_footer: '  } catch(error){ \n\n' +
                           '  //report errors to GA \n\n' +
                           '  window.console.log("js error: " + error);' +
                           '  } \n' +
                           '})(jQuery);'
          }
        }
      },
      dev: {
        options: {
          variables: {
            environment: 'dev',
            environmentData: 'website-guts/data/environments/development/environmentVariables.json',
            assets_dir: '/dist/assets',
            link_path: '/dist',
            sassSourceMap: true,
            sassImagePath: '/dist/assets/img',
            compress_js: false,
            concat_banner: '(function($){ \n\n' +
                           '  window.optly = window.optly || {}; \n\n' +
                           '  window.optly.mrkt = window.optly.mrkt || {}; \n\n',
            concat_footer: '})(jQuery);'
          }
        }
      },
      content: 'website',
      guts: 'website-guts',
      dist: 'dist',
      temp: 'temp',
      helpers: 'website-guts/helpers',
      bowerDir: 'bower_components'
    },
    watch: {
      assemble: {
        files: [
          '<%= config.content %>/{,*/}*.{md,hbs,yml,json}',
          '<%= config.guts %>/templates/**/*.hbs',
          '<%= config.content %>/**/*.hbs'
        ],
        tasks: ['config:dev', 'assemble']
      },
      sass: {
        files: '<%= config.guts %>/assets/css/**/*.scss',
        tasks: ['config:dev', 'sass', 'replace', 'autoprefixer', 'clean:postBuild']
      },
      img: {
        files: ['<%= config.guts %>/assets/img/*.{png,jpg,svg}'],
        tasks: ['copy:img']
      },
      js: {
        files: ['<%= config.guts %>/assets/js/**/*.js', '<%= config.temp %>/assets/js/**/*.js'],
        tasks: ['config:dev', 'jshint', 'concat', 'uglify', 'clean:postBuild']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= config.dist %>/**/*.html',
          '<%= config.dist %>/assets/css/**/*.css',
          '<%= config.dist %>/assets/js/**/*.js'
        ]
      }
    },
    connect: {
      options: {
        port: 9000,
        livereload: 35729,
        // change this to '0.0.0.0' to access the server from outside
        hostname: '0.0.0.0',
        middleware: function(connect, options, middlewares){
          middlewares.push(function(req, res, next){
            if(req.url === '/account/free_trial_landing'){

              res.writeHead(200, {'Content-Type': 'application/json'});
              res.end( grunt.file.read('website-guts/endpoint-mocks/formSuccess.json') );

            } else if(req.url === '/account/info') {

              res.writeHead(200, {'Content-Type': 'application/json'});
              res.end( grunt.file.read('website-guts/endpoint-mocks/accountInfo.json') );

            }
            else{

              return next();

            }

          });
          return middlewares;
        }
      },
      livereload: {
        options: {
          open: {
            target: 'http://0.0.0.0:9000/dist',
            base: '.'
          }
        }
      }
    },
    assemble: {
      options: {
        customVar: "Hello I'm a custom var",
        layoutdir: '<%= config.guts %>/templates/layouts/',
        assetsDir: '<%= grunt.config.get("assets_dir") %>',
        linkPath: '<%= grunt.config.get("link_path") %>',
        sassImagePath: '<%= grunt.config.get("sassImagePath") %>',
        environmentIsProduction: '<%= grunt.config.get("environmentIsProduction") %>',
        environmentIsDev: '<%= grunt.config.get("environmentIsDev") %>',
        data: ['<%= config.content %>/**/*.json', '<%= config.content %>/**/*.yml', '<%= grunt.config.get("environmentData") %>'],
        helpers: ['<%= config.helpers %>/helper-*.js']
      },
      pages: {
        files: [
          {
            src: ['**/*.hbs'],
            dest: '<%= config.dist %>/',
            cwd: '<%= config.content %>/',
            expand: true
          }
        ]
      }
    },
    sass: {
      styles: {
        options: {
          sourceMap: true,
          imagePath: '<%= grunt.config.get("sassImagePath") %>'
        },
        files: [
          {
            src: '<%= config.guts %>/assets/css/styles.scss',
            dest: '<%= config.temp %>/css/styles.css'
          }
        ]
      }
    },
    replace: {
      cssSourceMap: {
        src: '<%= config.temp %>/css/styles.css.map',
        overwrite: true,
        replacements: [
          {
            from: 'website-guts/',
            to: '../../../website-guts/'
          }
        ]
      }
    },
    autoprefixer: {
      options: {
        options: ['last 2 versions', "Firefox ESR"]
      },
      files: {
        flatten: true,
        src: '<%= config.temp %>/css/styles.css',
        dest: '<%= config.dist %>/assets/css/styles.css'
      }
    },
    copy: {
      cssSourceMap: {
        src: '<%= config.temp %>/css/styles.css.map',
        dest: '<%= config.dist %>/assets/css/styles.css.map'
      },
      cssFontFile: {
        src: ['<%= config.guts %>/assets/css/fonts.css'],
        dest: '<%= config.dist %>/assets/css/fonts.css'
      },
      js: {
        files: [
          {
            src: '<%= config.bowerDir %>/jquery/jquery.js',
            dest: '<%= config.dist %>/assets/js/libraries/jquery.js',
            flatten: true,
            filter: 'isFile'
          }
        ]
      },
      img: {
        files: [
          {
            cwd: '<%= config.guts %>/assets/img/',
            src: '**',
            dest: '<%= config.dist %>/assets/img/',
            expand: true
          }
        ]
      }
    },
    clean: {
      preBuild: ['<%= config.dist %>/'],
      postBuild: ['<%= config.temp %>']
    },
    s3: {
      options: {
        key: '<%= grunt.config.get("aws.key") %>',
        secret: '<%= grunt.config.get("aws.secret") %>',
        bucket: '<%= grunt.config.get("aws.bucket") %>',
        access: 'public-read',
      },
      dev: {
        upload: [
          {
            src: '<%= config.dist %>/**/*',
            dest: '/',
            rel: '<%= config.dist %>'
          }
        ]
      }
    },
    jshint: {
      options: {
        trailing: true,
        curly: true,
        eqeqeq: true,
        indent: 4,
        latedef: true,
        noempty: true,
        nonbsp: true,
        undef: true,
        unused: true,
        quotmark: 'single',
        browser: true,
        globals: {
          jQuery: true,
          $: true,
          console: false,
          Handlebars: false,
          moment: false,
          _gaq: false
        },
        '-W087': (function() {
          if(grunt.config.get("environment") == "dev") {
            return true;
          } else {
            return false;
          }
        }())
      },
      files: ['<%= config.guts %>/assets/js/**/*.js', '!<%= config.guts %>/assets/js/libraries/**/*.js']
    },
    concat: {
      modernizrYep: {
        files: {
          '<%= config.dist %>/assets/js/libraries/modernizr-yepnope.js': ['<%= config.guts %>/assets/js/libraries/modernizr-2.8.2.min.js','<%= config.bowerDir %>/yepnope/yepnope.1.5.4-min.js']
        }
      },
      namespace: {
        options: {
          banner: '<%= grunt.config.get("concat_banner") %>',
          footer: '<%= grunt.config.get("concat_footer") %>'
        },
        src: ['**/*.js', '!libraries/**/*.js'],
        expand: true,
        cwd: '<%= config.guts %>/assets/js/',
        dest: '<%= config.dist %>/assets/js/'
      }
    },
    uglify: {
      options: {
        mangle: false,
        compress: false,
        beautify: false
      },
      globalJS: {
        files: {
          '<%= config.dist %>/assets/js/libraries/fastclick.js': ['<%= config.bowerDir %>/fastclick/lib/fastclick.js'],
          '<%= config.dist %>/assets/js/libraries/jquery.js': ['<%= config.dist %>/assets/js/libraries/jquery.js'],
          '<%= config.dist %>/assets/js/bundle.js': [
            '<%= config.bowerDir %>/jquery-cookie/jquery.cookie.js',
            '<%= config.guts %>/assets/js/libraries/handlebars-v1.3.0.js',
            '<%= config.bowerDir %>/momentjs/moment.js',
            '<%= config.guts %>/assets/js/libraries/oForm/oForm.js',
            '<%= config.temp %>/assets/js/global.js'
          ]
        }
      },
      pageFiles: {
        files: [
          {
            expand: true,
            cwd: '<%= config.temp %>/assets/js/pages',
            src: '**/*.js',
            dest: '<%= config.dist %>/assets/js/pages'
          }
        ]
      }
    }
  });

/*
  grunt.loadNpmTasks('assemble');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-connect');
  grunt.loadNpmTasks('grunt-autoprefixer');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-sass');
  grunt.loadNpmTasks('grunt-contrib-copy');
  //do not load the npm grunt-s3 task when using 'grunt server' per: https://github.com/pifantastic/grunt-s3/issues/68
  //do load it for running 'grunt preview'
  //grunt.loadNpmTasks('grunt-s3');
  grunt.loadNpmTasks('grunt-config');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-text-replace');
*/

  grunt.registerTask('server', [
    'config:dev',
    'jshint',
    'clean:preBuild',
    'assemble',
    'concat',
    'sass',
    'replace',
    'autoprefixer',
    'copy',
    'uglify:globalJS',
    //'clean:postBuild',
    'connect:livereload',
    'watch'
  ]);

  grunt.registerTask('build', [
    'config:dev',
    'jshint',
    'clean:preBuild',
    'assemble',
    'concat',
    'uglify:pageFiles',
    'sass',
    'replace',
    'autoprefixer',
    'copy',
    'clean:postBuild'
  ]);

  grunt.registerTask('preview', [
    'config:preview',
    'jshint',
    'clean:preBuild',
    'assemble',
    'concat',
    'uglify',
    'sass',
    'autoprefixer',
    'copy',
    's3',
    'clean:postBuild'
  ]);

  grunt.registerTask('default', [
    'build'
  ]);

};
<commit_msg>add modified gruntfile<commit_after>/*
 * Generated on 2014-03-26
 * generator-assemble v0.4.11
 * https://github.com/assemble/generator-assemble
 *
 * Copyright (c) 2014 Hariadi Hinta
 * Licensed under the MIT license.
 */

'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// '<%= config.src %>/templates/pages/{,*/}*.hbs'
// use this if you want to match all subfolders:
// '<%= config.src %>/templates/pages/**/*.hbs'

module.exports = function(grunt) {

  require('time-grunt')(grunt);
  //jit-grunt loads only the npm tasks required for the grunt task.
  //makes livereload much faster.
  require('jit-grunt')(grunt, {
    replace: 'grunt-text-replace'
  });

  // Project configuration.
  grunt.initConfig({
    config: {
      preview: {
        options: {
          variables: {
            aws: grunt.file.readJSON('configs/s3Config.json'),
            environment: 'preview',
            environmentData: 'website-guts/data/environments/production/environmentVariables.json',
            assets_dir: '/assets',
            link_path: '',
            sassSourceMap: false,
            sassImagePath: '/assets/img',
            compress_js: true,
            concat_banner: '(function($){ \n\n' +
                           '  window.optly = window.optly || {}; \n\n' +
                           '  window.optly.mrkt = window.optly.mrkt || {}; \n\n' +
                           '  try { \n\n',
            concat_footer: '  } catch(error){ \n\n' +
                           '  //report errors to GA \n\n' +
                           '  window.console.log("js error: " + error);' +
                           '  } \n' +
                           '})(jQuery);'
          }
        }
      },
      dev: {
        options: {
          variables: {
            environment: 'dev',
            environmentData: 'website-guts/data/environments/development/environmentVariables.json',
            assets_dir: '/dist/assets',
            link_path: '/dist',
            sassSourceMap: true,
            sassImagePath: '/dist/assets/img',
            compress_js: false,
            concat_banner: '(function($){ \n\n' +
                           '  window.optly = window.optly || {}; \n\n' +
                           '  window.optly.mrkt = window.optly.mrkt || {}; \n\n',
            concat_footer: '})(jQuery);'
          }
        }
      },
      content: 'website',
      guts: 'website-guts',
      dist: 'dist',
      temp: 'temp',
      helpers: 'website-guts/helpers',
      bowerDir: 'bower_components'
    },
    watch: {
      assemble: {
        files: [
          '<%= config.content %>/{,*/}*.{md,hbs,yml,json}',
          '<%= config.guts %>/templates/**/*.hbs',
          '<%= config.content %>/**/*.hbs'
        ],
        tasks: ['config:dev', 'assemble']
      },
      sass: {
        files: '<%= config.guts %>/assets/css/**/*.scss',
        tasks: ['config:dev', 'sass', 'replace', 'autoprefixer', 'clean:postBuild']
      },
      img: {
        files: ['<%= config.guts %>/assets/img/*.{png,jpg,svg}'],
        tasks: ['copy:img']
      },
      js: {
        files: ['<%= config.guts %>/assets/js/**/*.js', '<%= config.temp %>/assets/js/**/*.js'],
        tasks: ['config:dev', 'jshint', 'concat', 'uglify', 'clean:postBuild']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= config.dist %>/**/*.html',
          '<%= config.dist %>/assets/css/**/*.css',
          '<%= config.dist %>/assets/js/**/*.js'
        ]
      }
    },
    connect: {
      options: {
        port: 9000,
        livereload: 35729,
        // change this to '0.0.0.0' to access the server from outside
        hostname: '0.0.0.0',
        middleware: function(connect, options, middlewares){
          middlewares.push(function(req, res, next){
            if(req.url === '/account/free_trial_landing'){

              res.writeHead(200, {'Content-Type': 'application/json'});
              res.end( grunt.file.read('website-guts/endpoint-mocks/formSuccess.json') );

            } else if(req.url === '/account/info') {

              res.writeHead(200, {'Content-Type': 'application/json'});
              res.end( grunt.file.read('website-guts/endpoint-mocks/accountInfo.json') );

            }
            else{

              return next();

            }

          });
          return middlewares;
        }
      },
      livereload: {
        options: {
          open: {
            target: 'http://0.0.0.0:9000/dist',
            base: '.'
          }
        }
      }
    },
    assemble: {
      options: {
        layoutdir: '<%= config.guts %>/templates/layouts/',
        assetsDir: '<%= grunt.config.get("assets_dir") %>',
        linkPath: '<%= grunt.config.get("link_path") %>',
        sassImagePath: '<%= grunt.config.get("sassImagePath") %>',
        environmentIsProduction: '<%= grunt.config.get("environmentIsProduction") %>',
        environmentIsDev: '<%= grunt.config.get("environmentIsDev") %>',
        data: ['<%= config.content %>/**/*.json', '<%= config.content %>/**/*.yml', '<%= grunt.config.get("environmentData") %>'],
        helpers: ['<%= config.helpers %>/helper-*.js']
      },
      pages: {
        files: [
          {
            src: ['**/*.hbs'],
            dest: '<%= config.dist %>/',
            cwd: '<%= config.content %>/',
            expand: true
          }
        ]
      }
    },
    sass: {
      styles: {
        options: {
          sourceMap: true,
          imagePath: '<%= grunt.config.get("sassImagePath") %>'
        },
        files: [
          {
            src: '<%= config.guts %>/assets/css/styles.scss',
            dest: '<%= config.temp %>/css/styles.css'
          }
        ]
      }
    },
    replace: {
      cssSourceMap: {
        src: '<%= config.temp %>/css/styles.css.map',
        overwrite: true,
        replacements: [
          {
            from: 'website-guts/',
            to: '../../../website-guts/'
          }
        ]
      }
    },
    autoprefixer: {
      options: {
        options: ['last 2 versions', "Firefox ESR"]
      },
      files: {
        flatten: true,
        src: '<%= config.temp %>/css/styles.css',
        dest: '<%= config.dist %>/assets/css/styles.css'
      }
    },
    copy: {
      cssSourceMap: {
        src: '<%= config.temp %>/css/styles.css.map',
        dest: '<%= config.dist %>/assets/css/styles.css.map'
      },
      cssFontFile: {
        src: ['<%= config.guts %>/assets/css/fonts.css'],
        dest: '<%= config.dist %>/assets/css/fonts.css'
      },
      js: {
        files: [
          {
            src: '<%= config.bowerDir %>/jquery/jquery.js',
            dest: '<%= config.dist %>/assets/js/libraries/jquery.js',
            flatten: true,
            filter: 'isFile'
          },
          {
            '<%= config.dist %>/assets/js/libraries/fastclick.js': ['<%= config.bowerDir %>/fastclick/lib/fastclick.js']
          }
        ]
      },
      img: {
        files: [
          {
            cwd: '<%= config.guts %>/assets/img/',
            src: '**',
            dest: '<%= config.dist %>/assets/img/',
            expand: true
          }
        ]
      }
    },
    clean: {
      preBuild: ['<%= config.dist %>/'],
      postBuild: ['<%= config.temp %>']
    },
    s3: {
      options: {
        key: '<%= grunt.config.get("aws.key") %>',
        secret: '<%= grunt.config.get("aws.secret") %>',
        bucket: '<%= grunt.config.get("aws.bucket") %>',
        access: 'public-read',
      },
      dev: {
        upload: [
          {
            src: '<%= config.dist %>/**/*',
            dest: '/',
            rel: '<%= config.dist %>'
          }
        ]
      }
    },
    jshint: {
      options: {
        trailing: true,
        curly: true,
        eqeqeq: true,
        indent: 4,
        latedef: true,
        noempty: true,
        nonbsp: true,
        undef: true,
        unused: true,
        quotmark: 'single',
        browser: true,
        globals: {
          jQuery: true,
          $: true,
          console: false,
          Handlebars: false,
          moment: false,
          _gaq: false
        },
        '-W087': (function() {
          if(grunt.config.get("environment") == "dev") {
            return true;
          } else {
            return false;
          }
        }())
      },
      files: ['<%= config.guts %>/assets/js/**/*.js', '!<%= config.guts %>/assets/js/libraries/**/*.js']
    },
    concat: {
      modernizrYep: {
        files: {
          '<%= config.dist %>/assets/js/libraries/modernizr-yepnope.js': ['<%= config.guts %>/assets/js/libraries/modernizr-2.8.2.min.js','<%= config.bowerDir %>/yepnope/yepnope.1.5.4-min.js']
        }
      },
      namespacePages: {
        options: {
          banner: '<%= grunt.config.get("concat_banner") %>',
          footer: '<%= grunt.config.get("concat_footer") %>'
        },
        src: ['pages/*.js', 'layouts/*.js'],
        expand: true,
        cwd: '<%= config.guts %>/assets/js/',
        dest: '<%= config.dist %>/assets/js/'
      },
      namespaceGlobal: {
        options: {
          banner: '<%= grunt.config.get("concat_banner") %>',
          footer: '<%= grunt.config.get("concat_footer") %>'
        },
        src: ['global.js'],
        expand: true,
        cwd: '<%= config.guts %>/assets/js/',
        dest: '<%= config.temp %>/assets/js/'
      },
      concatBundle: {
        files: {
          '<%= config.dist %>/assets/js/bundle.js': [
            '<%= config.bowerDir %>/jquery-cookie/jquery.cookie.js',
            '<%= config.guts %>/assets/js/libraries/handlebars-v1.3.0.js',
            '<%= config.bowerDir %>/momentjs/moment.js',
            '<%= config.guts %>/assets/js/libraries/oForm/oForm.js',
            '<%= config.temp %>/assets/js/global.js'
          ]
        }
      }
    },
    uglify: {
      options: {
        mangle: false,
        compress: false,
        beautify: false
      },
      globalJS: {
        files: {
          '<%= config.dist %>/assets/js/libraries/fastclick.js': ['<%= config.dist %>/assets/js/libraries/fastclick.js'],
          '<%= config.dist %>/assets/js/libraries/jquery.js': ['<%= config.dist %>/assets/js/libraries/jquery.js'],
          '<%= config.dist %>/assets/js/bundle.js': ['<%= config.dist %>/assets/js/bundle.js']
        }
      },
      pageFiles: {
        files: [
          {
            expand: true,
            cwd: '<%= config.dist %>/assets/js/',
            src: 'pages/*.js',
            dest: '<%= config.dist %>/assets/js/pages',
            flatten: true
          }
        ]
      }
    }
  });

/*
  grunt.loadNpmTasks('assemble');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-connect');
  grunt.loadNpmTasks('grunt-autoprefixer');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-sass');
  grunt.loadNpmTasks('grunt-contrib-copy');
  //do not load the npm grunt-s3 task when using 'grunt server' per: https://github.com/pifantastic/grunt-s3/issues/68
  //do load it for running 'grunt preview'
  //grunt.loadNpmTasks('grunt-s3');
  grunt.loadNpmTasks('grunt-config');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-text-replace');
*/

  grunt.registerTask('server', [
    'config:dev',
    'jshint',
    'clean:preBuild',
    'assemble',
    'concat',
    'sass',
    'replace',
    'autoprefixer',
    'copy',
    'clean:postBuild',
    'connect:livereload',
    'watch'
  ]);

  grunt.registerTask('build', [
    'config:dev',
    'jshint',
    'clean:preBuild',
    'assemble',
    'concat',
    'copy',
    'uglify',
    'sass',
    'replace',
    'autoprefixer',
    'clean:postBuild'
  ]);

  grunt.registerTask('preview', [
    'config:preview',
    'jshint',
    'clean:preBuild',
    'assemble',
    'concat',
    'uglify',
    'sass',
    'autoprefixer',
    'copy',
    's3',
    'clean:postBuild'
  ]);

  grunt.registerTask('default', [
    'build'
  ]);

};
<|endoftext|>


<commit_before>(function() {
  'use strict';

  var timeinterval = 0;
  var timerType;
  var status = document.getElementById('status');

  function getTimeRemaining(endtime) {
    var t = Date.parse(endtime) - Date.parse(new Date());
    var seconds = Math.floor((t / 1000) % 60);
    var minutes = Math.floor((t / 1000 / 60) % 60);
    var hours = Math.floor((t / (1000 * 60 * 60)) % 24);
    var days = Math.floor(t / (1000 * 60 * 60 * 24));
    return {
      'total': t,
      'days': days,
      'hours': hours,
      'minutes': minutes,
      'seconds': seconds
    };
  }

  function initializeClock(id, endtime) {
    var clock = document.getElementById(id);
    var minutesSpan = clock.querySelector('.minutes');
    var secondsSpan = clock.querySelector('.seconds');

    function updateClock() {
      var t = getTimeRemaining(endtime);

      minutesSpan.innerHTML = ('0' + t.minutes).slice(-2);
      secondsSpan.innerHTML = ('0' + t.seconds).slice(-2);

      if (t.total <= 0) {
        // TODO: add animations
        clearInterval(timeinterval);
        onTimerFinish();
        let volume = localStorage.getItem('volume');
        volume === 'true' ? alarm.play() : console.log('Volume muted');
        timerType === 'work' ? status.innerHTML = 'Get ready to work it' : status.innerHTML = 'Chill out mate!';
      }
    }

    updateClock();
    timeinterval = setInterval(updateClock, 1000);
  }

  // var deadline = new Date(Date.parse(new Date()) + 15 * 24 * 60 * 60 * 1000);

  function setTimer(duration) { // Duration in seconds
    timeinterval ? clearInterval(timeinterval) : console.log('Work Work Work Work Work');
    let deadline = new Date(Date.parse(new Date()) + duration * 1000);
    initializeClock('clockdiv', deadline);
  }

  function doSomeWork(workit) {
    let workButton = document.getElementById('tomato');
    let breakButtons = document.getElementById('break-buttons');

    if (workit) { // If you're doing work, break buttons are hidden
      status.innerHTML = 'Work it';
      breakButtons.className = 'hidden';
      workButton.className = 'visible';
      timerType = 'work';
    } else {
      status.innerHTML = 'Chill out time';
      breakButtons.className = 'visible';
      workButton.className = 'hidden';
      timerType = 'break';
    }
  }

  function onTimerFinish() {
    timerType === 'work' ? doSomeWork(false) : doSomeWork(true);
    displayNotification();
  }

  // Event listeners
  var workButton = document.querySelector('#tomato');
  var fiveMinButton = document.querySelector('#five-min');
  var tenMinButton = document.querySelector('#ten-min');
  var breakButtons = document.getElementById('break-buttons');
  var volumeButton = document.getElementById('volume');

  workButton.addEventListener('click', function() {
    status.innerHTML = '';
    setTimer(25*60);
    doSomeWork(true);
  });

  fiveMinButton.addEventListener('click', function() {
    setTimer(5*60);
    doSomeWork(false);
  });

  tenMinButton.addEventListener('click', function() {
    setTimer(10*60);
    doSomeWork(false);
  });

  volumeButton.addEventListener('click', function(){
    if (localStorage.getItem('volume') === 'true') {
      volumeButton.children[0].setAttribute('src','assets/volume_muted.svg');
      localStorage.setItem('volume',false);
    }
    else {
      volumeButton.children[0].setAttribute('src','assets/volume_on.svg');
      localStorage.setItem('volume',true);
    }

  });

  // Add to homescreen event
  window.addEventListener('beforeinstallprompt', function(e) {

    e.userChoice.then(function(choiceResult) {

      console.log(choiceResult.outcome);

      if(choiceResult.outcome == 'dismissed') {
        console.log('User cancelled home screen install');
      }
      else {
        console.log('User added to home screen');
      }
    });
  });

  function sound(src) {
      var self = this;
      self.sound = document.createElement("audio");
      self.sound.src = src;
      self.sound.setAttribute("preload", "auto");
      self.sound.setAttribute("controls", "none");
      self.sound.style.display = "none";
      document.body.appendChild(self.sound);
      self.play = function(){
          self.sound.play();
      };
      self.stop = function(){
          self.sound.pause();
      };
  }

  var alarm = new sound('assets/alarm.mp3');

  // Desktop notifications
  function displayNotification() {
    // Check if the browser supports notifications
    if (!("Notification" in window)) {
      alert("This browser does not support desktop notification");
    }
    else if (Notification.permission === "granted") {
      var notification = new Notification("Timer finished!");
    }
    else if (Notification.permission !== 'denied') {
      Notification.requestPermission(function (permission) {
        // If the user accepts, let's create a notification
        if (permission === "granted") {
          var notification = new Notification("Timer finished!");
        }
      });
    }
  }

  function checkVolume(){
    let vol = localStorage.getItem('volume');
    if (vol === 'true' || vol === '') {
      volumeButton.children[0].setAttribute('src','assets/volume_on.svg');
      localStorage.setItem('volume',true);
    } else {
      volumeButton.children[0].setAttribute('src','assets/volume_muted.svg');
      localStorage.setItem('volume',false);
    }
  }

  // Request desktop permissions
  Notification.requestPermission();
  checkVolume();

}());
<commit_msg>Volume on as default<commit_after>(function() {
  'use strict';

  var timeinterval = 0;
  var timerType;
  var status = document.getElementById('status');

  function getTimeRemaining(endtime) {
    var t = Date.parse(endtime) - Date.parse(new Date());
    var seconds = Math.floor((t / 1000) % 60);
    var minutes = Math.floor((t / 1000 / 60) % 60);
    var hours = Math.floor((t / (1000 * 60 * 60)) % 24);
    var days = Math.floor(t / (1000 * 60 * 60 * 24));
    return {
      'total': t,
      'days': days,
      'hours': hours,
      'minutes': minutes,
      'seconds': seconds
    };
  }

  function initializeClock(id, endtime) {
    var clock = document.getElementById(id);
    var minutesSpan = clock.querySelector('.minutes');
    var secondsSpan = clock.querySelector('.seconds');

    function updateClock() {
      var t = getTimeRemaining(endtime);

      minutesSpan.innerHTML = ('0' + t.minutes).slice(-2);
      secondsSpan.innerHTML = ('0' + t.seconds).slice(-2);

      if (t.total <= 0) {
        // TODO: add animations
        clearInterval(timeinterval);
        onTimerFinish();
        let volume = localStorage.getItem('volume');
        volume === 'true' ? alarm.play() : console.log('Volume muted');
        timerType === 'work' ? status.innerHTML = 'Get ready to work it' : status.innerHTML = 'Chill out mate!';
      }
    }

    updateClock();
    timeinterval = setInterval(updateClock, 1000);
  }

  // var deadline = new Date(Date.parse(new Date()) + 15 * 24 * 60 * 60 * 1000);

  function setTimer(duration) { // Duration in seconds
    timeinterval ? clearInterval(timeinterval) : console.log('Work Work Work Work Work');
    let deadline = new Date(Date.parse(new Date()) + duration * 1000);
    initializeClock('clockdiv', deadline);
  }

  function doSomeWork(workit) {
    let workButton = document.getElementById('tomato');
    let breakButtons = document.getElementById('break-buttons');

    if (workit) { // If you're doing work, break buttons are hidden
      status.innerHTML = 'Work it';
      breakButtons.className = 'hidden';
      workButton.className = 'visible';
      timerType = 'work';
    } else {
      status.innerHTML = 'Chill out time';
      breakButtons.className = 'visible';
      workButton.className = 'hidden';
      timerType = 'break';
    }
  }

  function onTimerFinish() {
    timerType === 'work' ? doSomeWork(false) : doSomeWork(true);
    displayNotification();
  }

  // Event listeners
  var workButton = document.querySelector('#tomato');
  var fiveMinButton = document.querySelector('#five-min');
  var tenMinButton = document.querySelector('#ten-min');
  var breakButtons = document.getElementById('break-buttons');
  var volumeButton = document.getElementById('volume');

  workButton.addEventListener('click', function() {
    status.innerHTML = '';
    setTimer(25*60);
    doSomeWork(true);
  });

  fiveMinButton.addEventListener('click', function() {
    setTimer(5*60);
    doSomeWork(false);
  });

  tenMinButton.addEventListener('click', function() {
    setTimer(10*60);
    doSomeWork(false);
  });

  volumeButton.addEventListener('click', function(){
    if (localStorage.getItem('volume') === 'true') {
      volumeButton.children[0].setAttribute('src','assets/volume_muted.svg');
      localStorage.setItem('volume',false);
    }
    else {
      volumeButton.children[0].setAttribute('src','assets/volume_on.svg');
      localStorage.setItem('volume',true);
    }

  });

  // Add to homescreen event
  window.addEventListener('beforeinstallprompt', function(e) {

    e.userChoice.then(function(choiceResult) {

      console.log(choiceResult.outcome);

      if(choiceResult.outcome == 'dismissed') {
        console.log('User cancelled home screen install');
      }
      else {
        console.log('User added to home screen');
      }
    });
  });

  function sound(src) {
      var self = this;
      self.sound = document.createElement("audio");
      self.sound.src = src;
      self.sound.setAttribute("preload", "auto");
      self.sound.setAttribute("controls", "none");
      self.sound.style.display = "none";
      document.body.appendChild(self.sound);
      self.play = function(){
          self.sound.play();
      };
      self.stop = function(){
          self.sound.pause();
      };
  }

  var alarm = new sound('assets/alarm.mp3');

  // Desktop notifications
  function displayNotification() {
    // Check if the browser supports notifications
    if (!("Notification" in window)) {
      alert("This browser does not support desktop notification");
    }
    else if (Notification.permission === "granted") {
      var notification = new Notification("Timer finished!");
    }
    else if (Notification.permission !== 'denied') {
      Notification.requestPermission(function (permission) {
        // If the user accepts, let's create a notification
        if (permission === "granted") {
          var notification = new Notification("Timer finished!");
        }
      });
    }
  }

  function checkVolume(){
    let vol = localStorage.getItem('volume');
    if (vol === 'false' || vol === '') {
      volumeButton.children[0].setAttribute('src','assets/volume_muted.svg');
      localStorage.setItem('volume',false);
    } else {
      volumeButton.children[0].setAttribute('src','assets/volume_on.svg');
      localStorage.setItem('volume',true);
    }
  }

  // Request desktop permissions
  Notification.requestPermission();
  checkVolume();

}());
<|endoftext|>


<commit_before>goog.provide('clulib.cm.ComponentManager');

goog.require('clulib.cm.NodeTree');

goog.require('goog.structs.Map');

/**
 * TODO: docs
 *
 * @constructor
 */
clulib.cm.ComponentManager = function () {
  /**
   * @type {string}
   * @private
   */
  this.typeAttribute_ = 'data-cmp';

  /**
   * @type {string}
   * @private
   */
  this.idAttribute_ = 'data-cmp-id';

  /**
   * @type {string}
   * @private
   */
  this.configAttribute_ = 'data-cmp-cfg';

  /**
   * @type {goog.structs.Map<string, Function>}
   * @private
   */
  this.registry_ = new goog.structs.Map();

  /**
   * @type {clulib.cm.NodeTree}
   * @private
   */
  this.nodeTree_ = new clulib.cm.NodeTree(this);
};

/**
 * @returns {string}
 */
clulib.cm.ComponentManager.prototype.getTypeAttribute = function () {
  return this.typeAttribute_;
};

/**
 * @returns {string}
 */
clulib.cm.ComponentManager.prototype.getIdAttribute = function () {
  return this.idAttribute_;
};

/**
 * @returns {string}
 */
clulib.cm.ComponentManager.prototype.getConfigAttribute = function () {
  return this.configAttribute_;
};

/**
 * @returns {goog.structs.Map<string, Function>}
 */
clulib.cm.ComponentManager.prototype.getRegistry = function () {
  return this.registry_;
};

/**
 * @param {Element} element
 * @returns {?clulib.cm.Component}
 */
clulib.cm.ComponentManager.prototype.getComponentForElement = function (element) {
  if (element == null || element.hasAttribute(this.idAttribute_) == false)
    return null;

  const id = element.getAttribute(this.idAttribute_);

  return this.nodeTree_.getComponent(id);
};

/**
 * @param {Array<Element>} elementArray
 * @returns {Array<clulib.cm.Component>}
 */
clulib.cm.ComponentManager.prototype.getComponentsForElementArray = function (elementArray) {
  return this.nodeTree_.getComponentArray(
      elementArray.map(element => element.getAttribute(this.idAttribute_))
  );
};

/**
 * @param {string} id
 */
clulib.cm.ComponentManager.prototype.disposeNode = function (id) {
  this.nodeTree_.disposeNode(id);
};

/**
 * @param {string} type
 * @param {function(new:clulib.cm.Component)} constructor
 */
clulib.cm.ComponentManager.prototype.addComponent = function (type, constructor) {
  this.registry_.set(type, constructor);
};

/**
 * @param {Element=} opt_rootElement
 * @returns {Promise}
 */
clulib.cm.ComponentManager.prototype.decorate = function (opt_rootElement) {
  const rootElement = opt_rootElement || document.body;
  return this.nodeTree_.createTree(rootElement);
};

clulib.cm.ComponentManager.prototype.disposeAll = function () {
  this.nodeTree_.disposeAll();
};<commit_msg>add assertion to check if component with specified type was already registered<commit_after>goog.provide('clulib.cm.ComponentManager');

goog.require('clulib.cm.NodeTree');

goog.require('goog.asserts');
goog.require('goog.structs.Map');

/**
 * TODO: docs
 *
 * @constructor
 */
clulib.cm.ComponentManager = function () {
  /**
   * @type {string}
   * @private
   */
  this.typeAttribute_ = 'data-cmp';

  /**
   * @type {string}
   * @private
   */
  this.idAttribute_ = 'data-cmp-id';

  /**
   * @type {string}
   * @private
   */
  this.configAttribute_ = 'data-cmp-cfg';

  /**
   * @type {goog.structs.Map<string, Function>}
   * @private
   */
  this.registry_ = new goog.structs.Map();

  /**
   * @type {clulib.cm.NodeTree}
   * @private
   */
  this.nodeTree_ = new clulib.cm.NodeTree(this);
};

/**
 * @returns {string}
 */
clulib.cm.ComponentManager.prototype.getTypeAttribute = function () {
  return this.typeAttribute_;
};

/**
 * @returns {string}
 */
clulib.cm.ComponentManager.prototype.getIdAttribute = function () {
  return this.idAttribute_;
};

/**
 * @returns {string}
 */
clulib.cm.ComponentManager.prototype.getConfigAttribute = function () {
  return this.configAttribute_;
};

/**
 * @returns {goog.structs.Map<string, Function>}
 */
clulib.cm.ComponentManager.prototype.getRegistry = function () {
  return this.registry_;
};

/**
 * @param {Element} element
 * @returns {?clulib.cm.Component}
 */
clulib.cm.ComponentManager.prototype.getComponentForElement = function (element) {
  if (element == null || element.hasAttribute(this.idAttribute_) == false)
    return null;

  const id = element.getAttribute(this.idAttribute_);

  return this.nodeTree_.getComponent(id);
};

/**
 * @param {Array<Element>} elementArray
 * @returns {Array<clulib.cm.Component>}
 */
clulib.cm.ComponentManager.prototype.getComponentsForElementArray = function (elementArray) {
  return this.nodeTree_.getComponentArray(
      elementArray.map(element => element.getAttribute(this.idAttribute_))
  );
};

/**
 * @param {string} id
 */
clulib.cm.ComponentManager.prototype.disposeNode = function (id) {
  this.nodeTree_.disposeNode(id);
};

/**
 * @param {string} type
 * @param {function(new:clulib.cm.Component)} constructor
 */
clulib.cm.ComponentManager.prototype.addComponent = function (type, constructor) {
  goog.asserts.assert(this.registry_.containsKey(type) == false, `Component with type '${type}' already registered.`);
  this.registry_.set(type, constructor);
};

/**
 * @param {Element=} opt_rootElement
 * @returns {Promise}
 */
clulib.cm.ComponentManager.prototype.decorate = function (opt_rootElement) {
  const rootElement = opt_rootElement || document.body;
  return this.nodeTree_.createTree(rootElement);
};

clulib.cm.ComponentManager.prototype.disposeAll = function () {
  this.nodeTree_.disposeAll();
};<|endoftext|>


<commit_before>import $ from 'jquery';
import _ from 'underscore';
import Backbone from 'backbone';

import events from 'girder/events';
import eventStream from 'girder/utilities/EventStream';
import LayoutFooterView from 'girder/views/layout/FooterView';
import LayoutGlobalNavView from 'girder/views/layout/GlobalNavView';
import LayoutHeaderView from 'girder/views/layout/HeaderView';
import LoginView from 'girder/views/layout/LoginView';
import ProgressListView from 'girder/views/layout/ProgressListView';
import RegisterView from 'girder/views/layout/RegisterView';
import ResetPasswordView from 'girder/views/layout/ResetPasswordView';
import router from 'girder/router';
import UserModel from 'girder/models/UserModel';
import View from 'girder/views/View';
import { fetchCurrentUser, setCurrentUser, getCurrentUser } from 'girder/auth';
import { Layout } from 'girder/constants';
import { splitRoute } from 'girder/misc';

import AlertTemplate from 'girder/templates/layout/alert.pug';
import LayoutTemplate from 'girder/templates/layout/layout.pug';

import 'girder/routes';

import 'girder/stylesheets/layout/global.styl';
import 'girder/stylesheets/layout/layout.styl';

import 'girder/utilities/jquery/girderModal';

import 'bootstrap/dist/css/bootstrap.css';

var App = View.extend({
    /**
     * @param {object} [settings]
     * @param {bool} [settings.start=true] Run start after initialization
     */
    initialize: function (settings) {
        this._started = false;
        settings = settings || {};
        if (settings.start === undefined || settings.start) {
            this.start();
        }
    },

    /**
     * Start the application with optional components.
     * @param {object} [settings]
     * @param {bool} [settings.fetch=true] Fetch the current user modal
     * @param {bool} [settings.render=true] Render the layout after starting
     * @param {bool} [settings.history=true] Start backbone's history api
     * @returns {$.Deferred} A promise-like object that resolves when the app is ready
     */
    start: function (settings) {
        // start is a noop if the app is already running
        var promise = new $.Deferred().resolve(null).promise();
        if (this._started) {
            return promise;
        }
        this._started = true;

        // set defaults
        settings = _.defaults(settings || {}, {
            fetch: true,
            render: true,
            history: true
        });

        // define a function to be run after fetching the user model
        var afterFetch = _.bind(function (user) {
            // TODO: this below is the old, pre-webpack code. eventStream is now a singleton
            // imported from 'girder/utilities/EventStream'. This mean it is not "re-newed" if
            // start is called another time (is it, ever)
            // girder.eventStream = new girder.EventStream({
            //     timeout: girder.sseTimeout || null
            // });

            this._createLayout();

            if (user) {
                setCurrentUser(new UserModel(user));
                eventStream.open();
            }

            if (settings.render) {
                this.render();
            }

            if (settings.history) {
                Backbone.history.stop();
                Backbone.history.start({
                    pushState: false
                });
            }
        }, this);

        // If fetching the user from the server then we return the jqxhr object
        // from the request, otherwise just call the callback.
        if (settings.fetch) {
            promise = fetchCurrentUser()
                .done(afterFetch);
        } else {
            afterFetch(null);
        }

        this.bindGirderEvents();
        return promise;
    },

    /**
     * Bind the application to the global event object.
     */
    bindGirderEvents: function () {
        // Unbind any current handlers in case this happens to be called twice.
        events.off(null, null, this);

        events.on('g:navigateTo', this.navigateTo, this);
        events.on('g:loginUi', this.loginDialog, this);
        events.on('g:registerUi', this.registerDialog, this);
        events.on('g:resetPasswordUi', this.resetPasswordDialog, this);
        events.on('g:alert', this.alert, this);
        events.on('g:login', this.login, this);
    },

    /**
     * Create the main layout views.
     * @private
     */
    _createLayout: function () {
        this.headerView = new LayoutHeaderView({
            parentView: this
        });

        this.globalNavView = new LayoutGlobalNavView({
            parentView: this
        });

        this.footerView = new LayoutFooterView({
            parentView: this
        });

        this.progressListView = new ProgressListView({
            eventStream: eventStream,
            parentView: this
        });

        this.layoutRenderMap = {};
        this.layoutRenderMap[Layout.DEFAULT] = this._defaultLayout;
        this.layoutRenderMap[Layout.EMPTY] = this._emptyLayout;
    },

    _layout: 'default',

    _defaultLayout: {
        show: function () {
            this.$('#g-app-header-container,#g-global-nav-container,#g-app-footer-container').show();
            this.$('#g-app-body-container').addClass('g-default-layout');
        },

        hide: function () {
            this.$('#g-app-header-container,#g-global-nav-container,#g-app-footer-container').hide();
            this.$('#g-app-body-container').removeClass('g-default-layout');
        }
    },

    _emptyLayout: {
        show: function () {
            this.$('#g-app-body-container').addClass('g-empty-layout');
        },

        hide: function () {
            this.$('#g-app-body-container').removeClass('g-empty-layout');
        }
    },

    render: function () {
        if (!this._started) {
            return;
        }
        this.$el.html(LayoutTemplate());

        this.globalNavView.setElement(this.$('#g-global-nav-container')).render();
        this.headerView.setElement(this.$('#g-app-header-container')).render();
        this.footerView.setElement(this.$('#g-app-footer-container')).render();
        this.progressListView.setElement(this.$('#g-app-progress-container')).render();

        return this;
    },

    /**
     * Changes the current body view to the view class specified by view.
     * @param view The view to display in the body.
     * @param [settings={}] Settings to pass to the view initialize() method.
     * @param opts Additional options for navigateTo, if any.
     */
    navigateTo: function (view, settings, opts) {
        this.globalNavView.deactivateAll();

        settings = settings || {};
        opts = opts || {};

        if (opts.layout) {
            if (this._layout !== opts.layout) {
                if (_.has(this.layoutRenderMap, opts.layout)) {
                    // set a layout if opts specifies one different from current
                    this.layoutRenderMap[this._layout].hide.call(this, opts);
                    this._layout = opts.layout;
                    this.layoutRenderMap[this._layout].show.call(this, opts);
                } else {
                    console.error('Attempting to set unknown layout type: ' + opts.layout);
                }
            }
        } else if (this._layout !== Layout.DEFAULT) {
            // reset to default as needed when nothing specified in opts
            this.layoutRenderMap[this._layout].hide.call(this, opts);
            this._layout = Layout.DEFAULT;
            this.layoutRenderMap[this._layout].show.call(this, opts);
        }

        if (view) {
            if (this.bodyView) {
                this.bodyView.destroy();
            }

            settings = _.extend(settings, {
                el: this.$('#g-app-body-container'),
                parentView: this
            });

            /* We let the view be created in this way even though it is
             * normally against convention.
             */
            this.bodyView = new view(settings); // eslint-disable-line new-cap
        } else {
            console.error('Undefined page.');
        }
        return this;
    },

    /**
     * Close any open dialog if we are already logged in.
     * :returns: true if we have a current user.
     */
    closeDialogIfUser: function () {
        if (getCurrentUser()) {
            $('.modal').girderModal('close');
            return true;
        }
        return false;
    },

    /**
     * Show a dialog allowing a user to login or register.
     */
    loginDialog: function () {
        if (this.closeDialogIfUser()) {
            return;
        }
        if (!this.loginView) {
            this.loginView = new LoginView({
                el: this.$('#g-dialog-container'),
                parentView: this
            });
        }
        this.loginView.render();
    },

    registerDialog: function () {
        if (this.closeDialogIfUser()) {
            return;
        }
        if (!this.registerView) {
            this.registerView = new RegisterView({
                el: this.$('#g-dialog-container'),
                parentView: this
            });
        }
        this.registerView.render();
    },

    resetPasswordDialog: function () {
        if (this.closeDialogIfUser()) {
            return;
        }
        if (!this.resetPasswordView) {
            this.resetPasswordView = new ResetPasswordView({
                el: this.$('#g-dialog-container'),
                parentView: this
            });
        }
        this.resetPasswordView.render();
    },

    /**
     * Display a brief alert on the screen with the following options:
     *   - text: The text to be displayed
     *   - [type]: The alert class ('info', 'warning', 'success', 'danger').
     *             Default is 'info'.
     *   - [icon]: An optional icon to display in the alert.
     *   - [timeout]: How long before the alert should automatically disappear.
     *                Default is 6000ms. Set to <= 0 to have no timeout.
     */
    alert: function (options) {
        var el = $(AlertTemplate({
            text: options.text,
            type: options.type || 'info',
            icon: options.icon
        }));
        $('#g-alerts-container').append(el);
        el.fadeIn(500);

        if (options.timeout === undefined) {
            options.timeout = 6000;
        }
        if (options.timeout > 0) {
            window.setTimeout(function () {
                el.fadeOut(750, function () {
                    $(this).remove();
                });
            }, options.timeout);
        }
    },

    /**
     * On login we re-render the current body view; whereas on
     * logout, we redirect to the front page.
     */
    login: function () {
        var route = splitRoute(Backbone.history.fragment).base;
        Backbone.history.fragment = null;
        eventStream.close();

        if (getCurrentUser()) {
            eventStream.open();
            router.navigate(route, {trigger: true});
        } else {
            router.navigate('/', {trigger: true});
        }
    }
});

export default App;
<commit_msg>Fix error in console during startup<commit_after>import $ from 'jquery';
import _ from 'underscore';
import Backbone from 'backbone';

import events from 'girder/events';
import eventStream from 'girder/utilities/EventStream';
import LayoutFooterView from 'girder/views/layout/FooterView';
import LayoutGlobalNavView from 'girder/views/layout/GlobalNavView';
import LayoutHeaderView from 'girder/views/layout/HeaderView';
import LoginView from 'girder/views/layout/LoginView';
import ProgressListView from 'girder/views/layout/ProgressListView';
import RegisterView from 'girder/views/layout/RegisterView';
import ResetPasswordView from 'girder/views/layout/ResetPasswordView';
import router from 'girder/router';
import UserModel from 'girder/models/UserModel';
import View from 'girder/views/View';
import { fetchCurrentUser, setCurrentUser, getCurrentUser } from 'girder/auth';
import { Layout } from 'girder/constants';
import { splitRoute } from 'girder/misc';

import AlertTemplate from 'girder/templates/layout/alert.pug';
import LayoutTemplate from 'girder/templates/layout/layout.pug';

import 'girder/routes';

import 'girder/stylesheets/layout/global.styl';
import 'girder/stylesheets/layout/layout.styl';

import 'girder/utilities/jquery/girderModal';

import 'bootstrap/dist/css/bootstrap.css';

var App = View.extend({
    /**
     * @param {object} [settings]
     * @param {bool} [settings.start=true] Run start after initialization
     */
    initialize: function (settings) {
        this._started = false;
        settings = settings || {};
        if (settings.start === undefined || settings.start) {
            this.start();
        }
    },

    /**
     * Start the application with optional components.
     * @param {object} [settings]
     * @param {bool} [settings.fetch=true] Fetch the current user modal
     * @param {bool} [settings.render=true] Render the layout after starting
     * @param {bool} [settings.history=true] Start backbone's history api
     * @returns {$.Deferred} A promise-like object that resolves when the app is ready
     */
    start: function (settings) {
        // start is a noop if the app is already running
        var promise = new $.Deferred().resolve(null).promise();
        if (this._started) {
            return promise;
        }

        // set defaults
        settings = _.defaults(settings || {}, {
            fetch: true,
            render: true,
            history: true
        });

        // define a function to be run after fetching the user model
        var afterFetch = _.bind(function (user) {
            // TODO: this below is the old, pre-webpack code. eventStream is now a singleton
            // imported from 'girder/utilities/EventStream'. This mean it is not "re-newed" if
            // start is called another time (is it, ever)
            // girder.eventStream = new girder.EventStream({
            //     timeout: girder.sseTimeout || null
            // });

            this._createLayout();

            if (user) {
                setCurrentUser(new UserModel(user));
                eventStream.open();
            }

            this._started = true;

            if (settings.render) {
                this.render();
            }

            if (settings.history) {
                Backbone.history.stop();
                Backbone.history.start({
                    pushState: false
                });
            }
        }, this);

        // If fetching the user from the server then we return the jqxhr object
        // from the request, otherwise just call the callback.
        if (settings.fetch) {
            promise = fetchCurrentUser()
                .done(afterFetch);
        } else {
            afterFetch(null);
        }

        this.bindGirderEvents();
        return promise;
    },

    /**
     * Bind the application to the global event object.
     */
    bindGirderEvents: function () {
        // Unbind any current handlers in case this happens to be called twice.
        events.off(null, null, this);

        events.on('g:navigateTo', this.navigateTo, this);
        events.on('g:loginUi', this.loginDialog, this);
        events.on('g:registerUi', this.registerDialog, this);
        events.on('g:resetPasswordUi', this.resetPasswordDialog, this);
        events.on('g:alert', this.alert, this);
        events.on('g:login', this.login, this);
    },

    /**
     * Create the main layout views.
     * @private
     */
    _createLayout: function () {
        this.headerView = new LayoutHeaderView({
            parentView: this
        });

        this.globalNavView = new LayoutGlobalNavView({
            parentView: this
        });

        this.footerView = new LayoutFooterView({
            parentView: this
        });

        this.progressListView = new ProgressListView({
            eventStream: eventStream,
            parentView: this
        });

        this.layoutRenderMap = {};
        this.layoutRenderMap[Layout.DEFAULT] = this._defaultLayout;
        this.layoutRenderMap[Layout.EMPTY] = this._emptyLayout;
    },

    _layout: 'default',

    _defaultLayout: {
        show: function () {
            this.$('#g-app-header-container,#g-global-nav-container,#g-app-footer-container').show();
            this.$('#g-app-body-container').addClass('g-default-layout');
        },

        hide: function () {
            this.$('#g-app-header-container,#g-global-nav-container,#g-app-footer-container').hide();
            this.$('#g-app-body-container').removeClass('g-default-layout');
        }
    },

    _emptyLayout: {
        show: function () {
            this.$('#g-app-body-container').addClass('g-empty-layout');
        },

        hide: function () {
            this.$('#g-app-body-container').removeClass('g-empty-layout');
        }
    },

    render: function () {
        if (!this._started) {
            return;
        }
        this.$el.html(LayoutTemplate());

        this.globalNavView.setElement(this.$('#g-global-nav-container')).render();
        this.headerView.setElement(this.$('#g-app-header-container')).render();
        this.footerView.setElement(this.$('#g-app-footer-container')).render();
        this.progressListView.setElement(this.$('#g-app-progress-container')).render();

        return this;
    },

    /**
     * Changes the current body view to the view class specified by view.
     * @param view The view to display in the body.
     * @param [settings={}] Settings to pass to the view initialize() method.
     * @param opts Additional options for navigateTo, if any.
     */
    navigateTo: function (view, settings, opts) {
        this.globalNavView.deactivateAll();

        settings = settings || {};
        opts = opts || {};

        if (opts.layout) {
            if (this._layout !== opts.layout) {
                if (_.has(this.layoutRenderMap, opts.layout)) {
                    // set a layout if opts specifies one different from current
                    this.layoutRenderMap[this._layout].hide.call(this, opts);
                    this._layout = opts.layout;
                    this.layoutRenderMap[this._layout].show.call(this, opts);
                } else {
                    console.error('Attempting to set unknown layout type: ' + opts.layout);
                }
            }
        } else if (this._layout !== Layout.DEFAULT) {
            // reset to default as needed when nothing specified in opts
            this.layoutRenderMap[this._layout].hide.call(this, opts);
            this._layout = Layout.DEFAULT;
            this.layoutRenderMap[this._layout].show.call(this, opts);
        }

        if (view) {
            if (this.bodyView) {
                this.bodyView.destroy();
            }

            settings = _.extend(settings, {
                el: this.$('#g-app-body-container'),
                parentView: this
            });

            /* We let the view be created in this way even though it is
             * normally against convention.
             */
            this.bodyView = new view(settings); // eslint-disable-line new-cap
        } else {
            console.error('Undefined page.');
        }
        return this;
    },

    /**
     * Close any open dialog if we are already logged in.
     * :returns: true if we have a current user.
     */
    closeDialogIfUser: function () {
        if (getCurrentUser()) {
            $('.modal').girderModal('close');
            return true;
        }
        return false;
    },

    /**
     * Show a dialog allowing a user to login or register.
     */
    loginDialog: function () {
        if (this.closeDialogIfUser()) {
            return;
        }
        if (!this.loginView) {
            this.loginView = new LoginView({
                el: this.$('#g-dialog-container'),
                parentView: this
            });
        }
        this.loginView.render();
    },

    registerDialog: function () {
        if (this.closeDialogIfUser()) {
            return;
        }
        if (!this.registerView) {
            this.registerView = new RegisterView({
                el: this.$('#g-dialog-container'),
                parentView: this
            });
        }
        this.registerView.render();
    },

    resetPasswordDialog: function () {
        if (this.closeDialogIfUser()) {
            return;
        }
        if (!this.resetPasswordView) {
            this.resetPasswordView = new ResetPasswordView({
                el: this.$('#g-dialog-container'),
                parentView: this
            });
        }
        this.resetPasswordView.render();
    },

    /**
     * Display a brief alert on the screen with the following options:
     *   - text: The text to be displayed
     *   - [type]: The alert class ('info', 'warning', 'success', 'danger').
     *             Default is 'info'.
     *   - [icon]: An optional icon to display in the alert.
     *   - [timeout]: How long before the alert should automatically disappear.
     *                Default is 6000ms. Set to <= 0 to have no timeout.
     */
    alert: function (options) {
        var el = $(AlertTemplate({
            text: options.text,
            type: options.type || 'info',
            icon: options.icon
        }));
        $('#g-alerts-container').append(el);
        el.fadeIn(500);

        if (options.timeout === undefined) {
            options.timeout = 6000;
        }
        if (options.timeout > 0) {
            window.setTimeout(function () {
                el.fadeOut(750, function () {
                    $(this).remove();
                });
            }, options.timeout);
        }
    },

    /**
     * On login we re-render the current body view; whereas on
     * logout, we redirect to the front page.
     */
    login: function () {
        var route = splitRoute(Backbone.history.fragment).base;
        Backbone.history.fragment = null;
        eventStream.close();

        if (getCurrentUser()) {
            eventStream.open();
            router.navigate(route, {trigger: true});
        } else {
            router.navigate('/', {trigger: true});
        }
    }
});

export default App;
<|endoftext|>


<commit_before>import _ from 'lodash'; // eslint-disable-line
import React, { PropTypes, Component } from 'react'; // eslint-disable-line
import Match from 'react-router/Match'; // eslint-disable-line
import {Row, Col} from 'react-bootstrap'; // eslint-disable-line

import Pane from '@folio/stripes-components/lib/Pane'; // eslint-disable-line
import Paneset from '@folio/stripes-components/lib/Paneset'; // eslint-disable-line
import PaneMenu from '@folio/stripes-components/lib/PaneMenu'; // eslint-disable-line
import Button from '@folio/stripes-components/lib/Button'; // eslint-disable-line
import Icon from '@folio/stripes-components/lib/Icon'; // eslint-disable-line
import Checkbox from '@folio/stripes-components/lib/Checkbox'; // eslint-disable-line
import TextField from '@folio/stripes-components/lib/TextField'; // eslint-disable-line
import MultiColumnList from '@folio/stripes-components/lib/MultiColumnList'; // eslint-disable-line
import KeyValue from '@folio/stripes-components/lib/KeyValue'; // eslint-disable-line
import FilterPaneSearch from '@folio/stripes-components/lib/FilterPaneSearch'; // eslint-disable-line
import FilterControlGroup from '@folio/stripes-components/lib/FilterControlGroup'; // eslint-disable-line
import Select from '@folio/stripes-components/lib/Select'; // eslint-disable-line
import Layer from '@folio/stripes-components/lib/Layer'; // eslint-disable-line

import UserForm from './UserForm';
import ViewUser from './ViewUser';

class Users extends Component {
  static contextTypes = {
    router: PropTypes.object.isRequired,
    store: PropTypes.object,
  };

  static propTypes = {
    data: PropTypes.object.isRequired,
    pathname: PropTypes.string.isRequired,
    location: PropTypes.shape({
      pathname: PropTypes.string.isRequired,
      query: PropTypes.object, // object of key=value pairs
      search: PropTypes.string, // string combining all parts of query
    }).isRequired,
    mutator: PropTypes.object,
  };

  static manifest = Object.freeze({
    addUserMode: { },
    users: {
      type: 'okapi',
      records: 'users',
      path: (queryParams, _pathComponents, _resourceValues) => {
        // console.log('Users manifest "users" path function, queryParams = ', queryParams);
        const { query, filterActive, filterInactive, sort } = queryParams || {};

        let cql;
        if (query) {
          cql = `username="${query}*" or personal.first_name="${query}*" or personal.last_name="${query}*"`;
        }

        let filterCql;
        if (filterActive && !filterInactive) {
          filterCql = 'active=true';
        } else if (filterInactive && !filterActive) {
          filterCql = 'active=false';
        } else if (!filterActive && !filterInactive) {
          // Technically, we should force this configuration to find
          // no records; but it probably makes more sense to do
          // nothing, and allow both active AND inactive records.
        }

        if (filterCql) {
          if (cql) {
            cql = `(${cql}) and ${filterCql}`;
          } else {
            cql = filterCql;
          }
        }

        if (sort) {
          const sortMap = {
            Active: 'active',
            Name: 'personal.last_name personal.first_name',
            Username: 'username',
            Email: 'personal.email',
          };
          const sortIndex = sortMap[sort];
          if (sortIndex) {
            if (cql === undefined) cql = 'username=*';
            cql += ` sortby ${sortIndex}`;
          }
        }

        let path = 'users';
        if (cql) path += `?query=${encodeURIComponent(cql)}`;

        console.log(`query=${query} active=${filterActive} inactive=${filterInactive} sort=${sort} -> ${path}`);
        return path;
      },
      staticFallback: { path: 'users' },
    }
  });

  constructor(props, context) {
    super(props);

    const query = props.location.query || {};
    this.state = {
      filter: {
        active: query.filterActive || false,
        inactive: query.filterInactive || false,
      },
      selectedItem: {},
      searchTerm: query.query || '',
      sortOrder: query.sort || '',
    };
    props.mutator.addUserMode.replace({ mode: false });

    this.okapi = context.store.getState().okapi;

    this.onClickAddNewUser = this.onClickAddNewUser.bind(this);
    this.onClickCloseNewUser = this.onClickCloseNewUser.bind(this);
    this.onChangeFilter = this.onChangeFilter.bind(this);
    //this.performSearch = _.debounce(this.performSearch.bind(this), 250);
    this.performSearch = this.performSearch.bind(this); // For now, prefer instant response
    this.onChangeSearch = this.onChangeSearch.bind(this);
    this.onClearSearch = this.onClearSearch.bind(this);
    this.onSort = this.onSort.bind(this);
    this.onSelectRow = this.onSelectRow.bind(this);
  }

  // search Handlers...
  onChangeFilter(e) {
    const filter = this.state.filter;
    filter[e.target.id] = !filter[e.target.id];
    console.log('onChangeFilter setting state', filter);
    this.setState({ filter });
    this.updateSearch(this.state.searchTerm, this.state.sortOrder, filter);
  }

  onChangeSearch(e) {
    const term = e.target.value;
    this.setState({ searchTerm: term });
    this.performSearch(term);
  }

  performSearch(term) {
    console.log('User searched:', term, 'at', this.props.location.pathname);
    const transitionPath = term === "" ?  this.props.location.pathname : `${this.props.location.pathname}?query=${term}`;
    this.context.router.transitionTo(transitionPath);
  }

  onClearSearch() {
    console.log('User cleared search');
    this.setState({ searchTerm: '' });
    this.context.router.transitionTo(this.props.location.pathname);
  }

  onSort(e, meta) {
    const sortOrder = meta.name;
    console.log('User sorted by', sortOrder);
    this.setState({ sortOrder });
    this.updateSearch(this.state.searchTerm, sortOrder, this.state.filter);
  }

  onSelectRow(e, meta) {
    const userId = meta.id;
    console.log('User clicked', userId, 'location = ', this.props.location);
    this.setState({ selectedItem: meta });
    this.context.router.transitionTo(`/users/view/${userId}${this.props.location.search}`);
  }

  // end search Handlers

  // AddUser Handlers
  onClickAddNewUser(e) {
    if (e) e.preventDefault();
    this.props.mutator.addUserMode.replace({ mode: true })
  }

  onClickCloseNewUser(e) {
    if (e) e.preventDefault();
    this.props.mutator.addUserMode.replace({ mode: false })
  }
  // end AddUser Handlers

  // We need to explicitly pass changed values into this function,
  // as state-change only happens after event is handled.
  updateSearch(query, sortOrder, filter) {
    console.log(`updateSearch('${query}', '${sortOrder}',`, filter, ')');
    let transitionLoc = this.props.location.pathname;
    const params = {};
    if (query) params.query = query;
    if (sortOrder) params.sort = sortOrder;
    if (filter.active) params.filterActive = true;
    if (filter.inactive) params.filterInactive = true;
    const keys = Object.keys(params);
    if (keys.length) {
      // eslint-disable-next-line prefer-template
      transitionLoc += '?' + keys.map(key => `${key}=${encodeURIComponent(params[key])}`).join('&');
    }
    this.context.router.transitionTo(transitionLoc);
  }

  create(data) {
    // extract creds object from user object
    const creds = Object.assign({}, data.creds, { username: data.username });
    if (data.creds) delete data.creds;
    // POST user record
    this.props.mutator.users.POST(data);
    // POST credentials, permission-user, permissions;
    this.postCreds(data.username, { credentials: creds });
    this.onClickCloseNewUser();
  }

  postCreds(username, creds) {
    fetch(`${this.okapi.url}/authn/users`, {
      method: 'POST',
      headers: Object.assign({}, { 'X-Okapi-Tenant': this.okapi.tenant, 'X-Okapi-Token': this.okapi.token }),
      body: JSON.stringify(creds),
    }).then((response) => {
      if (response.status >= 400) {
        console.log("Users. POST of creds failed.");
      } else {
        this.postPerms(username, 'users.super');
      }
    });
  }

  postPerms (username, perms) {
    fetch(`${this.okapi.url}/perms/users`, {
      method: 'POST',
      headers: Object.assign({}, { 'X-Okapi-Tenant': this.okapi.tenant, 'X-Okapi-Token': this.okapi.token }),
      body: username,
    }).then((response) => {
      if (response.status >= 400) {
        console.log("Users. POST of username failed.");
      } else {
        this.postUsersPerms(username, perms);
      }
    });
  }

  postUsersPerms(username, perm) {
    fetch(`${this.okapi.url}/perms/users/${username}/permissions`, {
      method: 'POST',
      headers: Object.assign({}, { 'X-Okapi-Tenant': this.okapi.tenant, 'X-Okapi-Token': this.okapi.token }),
      body: JSON.stringify({ permission_name: perm }),
    }).then((response) => {
      if (response.status >= 400) {
        console.log("Users. POST of user's perms failed.");
      } else {
      }
    });

  }

  render() {
    const { data, pathname } = this.props;
    const users = data.users || [];

    /* searchHeader is a 'custom pane header'*/
    const searchHeader = <FilterPaneSearch id="SearchField" onChange={this.onChangeSearch} onClear={this.onClearSearch} value={this.state.searchTerm} />;
    const resultMenu = <PaneMenu><button><Icon icon="bookmark" /></button></PaneMenu>;
    const fineHistory = [{ 'Due Date': '11/12/2014', 'Amount': '34.23', 'Status': 'Unpaid' }]; // eslint-disable-line

    const resultsFormatter = {
      Active: user => user.active,
      Name: user => `${_.get(user, ['personal', 'last_name'], '')}, ${_.get(user, ['personal', 'first_name'], '')}`,
      Username: user => user.username,
      Email: user => _.get(user, ['personal', 'email']),
    };

    return (
      <Paneset>
        {/* Filter Pane */}
        <Pane defaultWidth="16%" header={searchHeader}>
          <FilterControlGroup label="Filters">
            <Checkbox
              id="active"
              label="Active"
              checked={this.state.filter.active}
              onChange={this.onChangeFilter}
              marginBottom0
              hover
              fullWidth
              checkedIcon={<Icon icon="eye" />}
            />
            <Checkbox
              id="inactive"
              label="Inactive"
              checked={this.state.filter.inactive}
              onChange={this.onChangeFilter}
              marginBottom0
              hover
              fullWidth
              checkedIcon={<Icon icon="eye" />}
            />
          </FilterControlGroup>
          <FilterControlGroup label="Actions">
            <Button fullWidth onClick={this.onClickAddNewUser}>New user</Button>
          </FilterControlGroup>
        </Pane>
        {/* Results Pane */}
        <Pane
          defaultWidth="40%"
          paneTitle={

              Results

                {users.length} Result{users.length === 1 ? '' : 's'} Found

          }
          lastMenu={resultMenu}
        >
          <MultiColumnList
            contentData={users}
            selectedRow={this.state.selectedItem}
            rowMetadata={['id']}
            formatter={resultsFormatter}
            onRowClick={this.onSelectRow}
            onHeaderClick={this.onSort}
            visibleColumns={['Active', 'Name', 'Username', 'Email']}
            fullWidth
            sortOrder={this.state.sortOrder}
            isEmptyMessage={`No results found for "${this.state.searchTerm}". Please check your spelling and filters.`}
          />
        </Pane>

        {/* Details Pane */}
        <Match pattern={`${pathname}/view/:userid`} render={props => <ViewUser placeholder={'placeholder'} {...props} />} />
        <Layer isOpen={data.addUserMode ? data.addUserMode.mode : false } label="Add New User Dialog">
          <UserForm
            onSubmit={(record) => { this.create(record); }}
            onCancel={this.onClickCloseNewUser}
          />
        </Layer>
      </Paneset>
    );
  }
}

export default Users;
<commit_msg>Added username to the view path pattern<commit_after>import _ from 'lodash'; // eslint-disable-line
import React, { PropTypes, Component } from 'react'; // eslint-disable-line
import Match from 'react-router/Match'; // eslint-disable-line
import {Row, Col} from 'react-bootstrap'; // eslint-disable-line

import Pane from '@folio/stripes-components/lib/Pane'; // eslint-disable-line
import Paneset from '@folio/stripes-components/lib/Paneset'; // eslint-disable-line
import PaneMenu from '@folio/stripes-components/lib/PaneMenu'; // eslint-disable-line
import Button from '@folio/stripes-components/lib/Button'; // eslint-disable-line
import Icon from '@folio/stripes-components/lib/Icon'; // eslint-disable-line
import Checkbox from '@folio/stripes-components/lib/Checkbox'; // eslint-disable-line
import TextField from '@folio/stripes-components/lib/TextField'; // eslint-disable-line
import MultiColumnList from '@folio/stripes-components/lib/MultiColumnList'; // eslint-disable-line
import KeyValue from '@folio/stripes-components/lib/KeyValue'; // eslint-disable-line
import FilterPaneSearch from '@folio/stripes-components/lib/FilterPaneSearch'; // eslint-disable-line
import FilterControlGroup from '@folio/stripes-components/lib/FilterControlGroup'; // eslint-disable-line
import Select from '@folio/stripes-components/lib/Select'; // eslint-disable-line
import Layer from '@folio/stripes-components/lib/Layer'; // eslint-disable-line

import UserForm from './UserForm';
import ViewUser from './ViewUser';

class Users extends Component {
  static contextTypes = {
    router: PropTypes.object.isRequired,
    store: PropTypes.object,
  };

  static propTypes = {
    data: PropTypes.object.isRequired,
    pathname: PropTypes.string.isRequired,
    location: PropTypes.shape({
      pathname: PropTypes.string.isRequired,
      query: PropTypes.object, // object of key=value pairs
      search: PropTypes.string, // string combining all parts of query
    }).isRequired,
    mutator: PropTypes.object,
  };

  static manifest = Object.freeze({
    addUserMode: { },
    users: {
      type: 'okapi',
      records: 'users',
      path: (queryParams, _pathComponents, _resourceValues) => {
        // console.log('Users manifest "users" path function, queryParams = ', queryParams);
        const { query, filterActive, filterInactive, sort } = queryParams || {};

        let cql;
        if (query) {
          cql = `username="${query}*" or personal.first_name="${query}*" or personal.last_name="${query}*"`;
        }

        let filterCql;
        if (filterActive && !filterInactive) {
          filterCql = 'active=true';
        } else if (filterInactive && !filterActive) {
          filterCql = 'active=false';
        } else if (!filterActive && !filterInactive) {
          // Technically, we should force this configuration to find
          // no records; but it probably makes more sense to do
          // nothing, and allow both active AND inactive records.
        }

        if (filterCql) {
          if (cql) {
            cql = `(${cql}) and ${filterCql}`;
          } else {
            cql = filterCql;
          }
        }

        if (sort) {
          const sortMap = {
            Active: 'active',
            Name: 'personal.last_name personal.first_name',
            Username: 'username',
            Email: 'personal.email',
          };
          const sortIndex = sortMap[sort];
          if (sortIndex) {
            if (cql === undefined) cql = 'username=*';
            cql += ` sortby ${sortIndex}`;
          }
        }

        let path = 'users';
        if (cql) path += `?query=${encodeURIComponent(cql)}`;

        console.log(`query=${query} active=${filterActive} inactive=${filterInactive} sort=${sort} -> ${path}`);
        return path;
      },
      staticFallback: { path: 'users' },
    }
  });

  constructor(props, context) {
    super(props);

    const query = props.location.query || {};
    this.state = {
      filter: {
        active: query.filterActive || false,
        inactive: query.filterInactive || false,
      },
      selectedItem: {},
      searchTerm: query.query || '',
      sortOrder: query.sort || '',
    };
    props.mutator.addUserMode.replace({ mode: false });

    this.okapi = context.store.getState().okapi;

    this.onClickAddNewUser = this.onClickAddNewUser.bind(this);
    this.onClickCloseNewUser = this.onClickCloseNewUser.bind(this);
    this.onChangeFilter = this.onChangeFilter.bind(this);
    //this.performSearch = _.debounce(this.performSearch.bind(this), 250);
    this.performSearch = this.performSearch.bind(this); // For now, prefer instant response
    this.onChangeSearch = this.onChangeSearch.bind(this);
    this.onClearSearch = this.onClearSearch.bind(this);
    this.onSort = this.onSort.bind(this);
    this.onSelectRow = this.onSelectRow.bind(this);
  }

  // search Handlers...
  onChangeFilter(e) {
    const filter = this.state.filter;
    filter[e.target.id] = !filter[e.target.id];
    console.log('onChangeFilter setting state', filter);
    this.setState({ filter });
    this.updateSearch(this.state.searchTerm, this.state.sortOrder, filter);
  }

  onChangeSearch(e) {
    const term = e.target.value;
    this.setState({ searchTerm: term });
    this.performSearch(term);
  }

  performSearch(term) {
    console.log('User searched:', term, 'at', this.props.location.pathname);
    const transitionPath = term === "" ?  this.props.location.pathname : `${this.props.location.pathname}?query=${term}`;
    this.context.router.transitionTo(transitionPath);
  }

  onClearSearch() {
    console.log('User cleared search');
    this.setState({ searchTerm: '' });
    this.context.router.transitionTo(this.props.location.pathname);
  }

  onSort(e, meta) {
    const sortOrder = meta.name;
    console.log('User sorted by', sortOrder);
    this.setState({ sortOrder });
    this.updateSearch(this.state.searchTerm, sortOrder, this.state.filter);
  }

  onSelectRow(e, meta) {
    const userId = meta.id;
    const username = meta.username;
    console.log('User clicked', userId, 'location = ', this.props.location);
    this.setState({ selectedItem: meta });
    this.context.router.transitionTo(`/users/view/${userId}/${username}${this.props.location.search}`);
  }

  // end search Handlers

  // AddUser Handlers
  onClickAddNewUser(e) {
    if (e) e.preventDefault();
    this.props.mutator.addUserMode.replace({ mode: true })
  }

  onClickCloseNewUser(e) {
    if (e) e.preventDefault();
    this.props.mutator.addUserMode.replace({ mode: false })
  }
  // end AddUser Handlers

  // We need to explicitly pass changed values into this function,
  // as state-change only happens after event is handled.
  updateSearch(query, sortOrder, filter) {
    console.log(`updateSearch('${query}', '${sortOrder}',`, filter, ')');
    let transitionLoc = this.props.location.pathname;
    const params = {};
    if (query) params.query = query;
    if (sortOrder) params.sort = sortOrder;
    if (filter.active) params.filterActive = true;
    if (filter.inactive) params.filterInactive = true;
    const keys = Object.keys(params);
    if (keys.length) {
      // eslint-disable-next-line prefer-template
      transitionLoc += '?' + keys.map(key => `${key}=${encodeURIComponent(params[key])}`).join('&');
    }
    this.context.router.transitionTo(transitionLoc);
  }

  create(data) {
    // extract creds object from user object
    const creds = Object.assign({}, data.creds, { username: data.username });
    if (data.creds) delete data.creds;
    // POST user record
    this.props.mutator.users.POST(data);
    // POST credentials, permission-user, permissions;
    this.postCreds(data.username, { credentials: creds });
    this.onClickCloseNewUser();
  }

  postCreds(username, creds) {
    fetch(`${this.okapi.url}/authn/users`, {
      method: 'POST',
      headers: Object.assign({}, { 'X-Okapi-Tenant': this.okapi.tenant, 'X-Okapi-Token': this.okapi.token }),
      body: JSON.stringify(creds),
    }).then((response) => {
      if (response.status >= 400) {
        console.log("Users. POST of creds failed.");
      } else {
        this.postPerms(username, 'users.super');
      }
    });
  }

  postPerms (username, perms) {
    fetch(`${this.okapi.url}/perms/users`, {
      method: 'POST',
      headers: Object.assign({}, { 'X-Okapi-Tenant': this.okapi.tenant, 'X-Okapi-Token': this.okapi.token }),
      body: username,
    }).then((response) => {
      if (response.status >= 400) {
        console.log("Users. POST of username failed.");
      } else {
        this.postUsersPerms(username, perms);
      }
    });
  }

  postUsersPerms(username, perm) {
    fetch(`${this.okapi.url}/perms/users/${username}/permissions`, {
      method: 'POST',
      headers: Object.assign({}, { 'X-Okapi-Tenant': this.okapi.tenant, 'X-Okapi-Token': this.okapi.token }),
      body: JSON.stringify({ permission_name: perm }),
    }).then((response) => {
      if (response.status >= 400) {
        console.log("Users. POST of user's perms failed.");
      } else {
      }
    });

  }

  render() {
    const { data, pathname } = this.props;
    const users = data.users || [];

    /* searchHeader is a 'custom pane header'*/
    const searchHeader = <FilterPaneSearch id="SearchField" onChange={this.onChangeSearch} onClear={this.onClearSearch} value={this.state.searchTerm} />;
    const resultMenu = <PaneMenu><button><Icon icon="bookmark" /></button></PaneMenu>;
    const fineHistory = [{ 'Due Date': '11/12/2014', 'Amount': '34.23', 'Status': 'Unpaid' }]; // eslint-disable-line

    const resultsFormatter = {
      Active: user => user.active,
      Name: user => `${_.get(user, ['personal', 'last_name'], '')}, ${_.get(user, ['personal', 'first_name'], '')}`,
      Username: user => user.username,
      Email: user => _.get(user, ['personal', 'email']),
    };

    return (
      <Paneset>
        {/* Filter Pane */}
        <Pane defaultWidth="16%" header={searchHeader}>
          <FilterControlGroup label="Filters">
            <Checkbox
              id="active"
              label="Active"
              checked={this.state.filter.active}
              onChange={this.onChangeFilter}
              marginBottom0
              hover
              fullWidth
              checkedIcon={<Icon icon="eye" />}
            />
            <Checkbox
              id="inactive"
              label="Inactive"
              checked={this.state.filter.inactive}
              onChange={this.onChangeFilter}
              marginBottom0
              hover
              fullWidth
              checkedIcon={<Icon icon="eye" />}
            />
          </FilterControlGroup>
          <FilterControlGroup label="Actions">
            <Button fullWidth onClick={this.onClickAddNewUser}>New user</Button>
          </FilterControlGroup>
        </Pane>
        {/* Results Pane */}
        <Pane
          defaultWidth="40%"
          paneTitle={

              Results

                {users.length} Result{users.length === 1 ? '' : 's'} Found

          }
          lastMenu={resultMenu}
        >
          <MultiColumnList
            contentData={users}
            selectedRow={this.state.selectedItem}
            rowMetadata={['id', 'username']}
            formatter={resultsFormatter}
            onRowClick={this.onSelectRow}
            onHeaderClick={this.onSort}
            visibleColumns={['Active', 'Name', 'Username', 'Email']}
            fullWidth
            sortOrder={this.state.sortOrder}
            isEmptyMessage={`No results found for "${this.state.searchTerm}". Please check your spelling and filters.`}
          />
        </Pane>

        {/* Details Pane */}
        <Match pattern={`${pathname}/view/:userid/:username`} render={props => <ViewUser placeholder={'placeholder'} {...props} />} />
        <Layer isOpen={data.addUserMode ? data.addUserMode.mode : false } label="Add New User Dialog">
          <UserForm
            onSubmit={(record) => { this.create(record); }}
            onCancel={this.onClickCloseNewUser}
          />
        </Layer>
      </Paneset>
    );
  }
}

export default Users;
<|endoftext|>


<commit_before>var gulp = require('gulp'),
  connect = require('gulp-connect'),
  $ = require('gulp-load-plugins')();

var iconfont = {
  stylusFileName: '_font.styl'
};

var path = (function () {
  var _path = {
    source: 'app/',
    build: '_publish/'
  };

  _path.sourceHtml = _path.source + 'html/';
  _path.sourceStyle = _path.source + 'style/';
  _path.sourceScript = _path.source + 'script/';
  _path.sourceImage = _path.source + 'images/';

  return _path;
})();

gulp.task('html', function () {
  return gulp.src(path.sourceHtml + '*.html')
    .pipe($.plumber())
    .pipe($.minifyHtml({
      quotes: true
    }))
    .pipe(gulp.dest(path.build))
    .pipe(connect.reload());
});

gulp.task('css', function () {
  return gulp.src(path.sourceStyle + 'style.styl')
    .pipe($.plumber())
    .pipe($.stylus({
      import: ['tmp/' + iconfont.stylusFileName]
    }))
    .pipe($.autoprefixer())
    .pipe($.minifyCss())
    .pipe(gulp.dest(path.build + 'css/'))
    .pipe(connect.reload());
});

gulp.task('js', function () {
  return gulp.src(path.sourceScript + '**/*.js')
    .pipe($.plumber())
    .pipe($.concat('script.js'))
    .pipe($.jshint())
    .pipe($.jshint.reporter('jshint-stylish'))
    .pipe($.uglify())
    .pipe(gulp.dest(path.build + 'js/'))
    .pipe(connect.reload());
});

gulp.task('png-min', ['png-clone-no-compress'],function () {
  return gulp.src([path.sourceImage + '**/*.png', '!' + path.sourceImage + '**/*-no-compress.png'])
    .pipe($.optipng(['-o2', '-strip all']))
    .pipe(gulp.dest(path.build + 'img/'));
});

gulp.task('png-clone-no-compress', function () {
  return gulp.src([path.sourceImage + '**/*-no-compress.png'])
    .pipe(gulp.dest(path.build + 'img/'));
});

gulp.task('iconfont', function () {
  return gulp.src(path.source + 'font/svg/*.svg')
    .pipe($.iconfont({
      fontName: 'myfont',
      // centerHorizontally: true,
      // appendCodepoints: true,
      fixedWidth: true
    }))
    .on('codepoints', function(codepoints, options) {
      // console.log(codepoints, options);
      gulp.src(path.sourceStyle + '_font.styl-temp')
        .pipe($.consolidate('lodash', {
          glyphs: codepoints,
          fontName: 'myfont',
          fontPath: '../font/',
          className: 'appicon'
        }))
        .pipe($.rename(iconfont.stylusFileName))
        .pipe(gulp.dest(path.sourceStyle + 'tmp'));
    })
    .pipe(gulp.dest(path.build + 'font/'));
});

gulp.task('server', function () {
  connect.server({
    livereload: true
  });
});

gulp.task('watch', ['build'], function () {
  gulp.watch(path.sourceHtml + '*.html', ['html']);
  gulp.watch(path.sourceScript + '*.js', ['js']);
  gulp.watch(path.sourceStyle + '**/*.styl', ['css']);
});

gulp.task('deploy', ['build', 'png-min'], function () {
  gulp.src(path.build + '**/*')
    .pipe($.ghPages());
});

gulp.task('build', ['iconfont', 'html', 'css', 'js']);
gulp.task('dev', ['server', 'watch']);
gulp.task('default', ['build']);
<commit_msg>re-style, 加大 task 間距<commit_after>var gulp = require('gulp'),
  connect = require('gulp-connect'),
  $ = require('gulp-load-plugins')();

var iconfont = {
  stylusFileName: '_font.styl'
};

var path = (function () {
  var _path = {
    source: 'app/',
    build: '_publish/'
  };

  _path.sourceHtml = _path.source + 'html/';
  _path.sourceStyle = _path.source + 'style/';
  _path.sourceScript = _path.source + 'script/';
  _path.sourceImage = _path.source + 'images/';

  return _path;
})();

gulp.task('html', function () {
  return gulp.src(path.sourceHtml + '*.html')
    .pipe($.plumber())
    .pipe($.minifyHtml({
      quotes: true
    }))
    .pipe(gulp.dest(path.build))
    .pipe(connect.reload());
});

gulp.task('css', function () {
  return gulp.src(path.sourceStyle + 'style.styl')
    .pipe($.plumber())
    .pipe($.stylus({
      import: ['tmp/' + iconfont.stylusFileName]
    }))
    .pipe($.autoprefixer())
    .pipe($.minifyCss())
    .pipe(gulp.dest(path.build + 'css/'))
    .pipe(connect.reload());
});

gulp.task('js', function () {
  return gulp.src(path.sourceScript + '**/*.js')
    .pipe($.plumber())
    .pipe($.concat('script.js'))
    .pipe($.jshint())
    .pipe($.jshint.reporter('jshint-stylish'))
    .pipe($.uglify())
    .pipe(gulp.dest(path.build + 'js/'))
    .pipe(connect.reload());
});

gulp.task('png-min', ['png-clone-no-compress'],function () {
  return gulp.src([path.sourceImage + '**/*.png', '!' + path.sourceImage + '**/*-no-compress.png'])
    .pipe($.optipng(['-o2', '-strip all']))
    .pipe(gulp.dest(path.build + 'img/'));
});

gulp.task('png-clone-no-compress', function () {
  return gulp.src([path.sourceImage + '**/*-no-compress.png'])
    .pipe(gulp.dest(path.build + 'img/'));
});

gulp.task('iconfont', function () {
  return gulp.src(path.source + 'font/svg/*.svg')
    .pipe($.iconfont({
      fontName: 'myfont',
      // centerHorizontally: true,
      // appendCodepoints: true,
      fixedWidth: true
    }))
    .on('codepoints', function(codepoints, options) {
      // console.log(codepoints, options);
      gulp.src(path.sourceStyle + '_font.styl-temp')
        .pipe($.consolidate('lodash', {
          glyphs: codepoints,
          fontName: 'myfont',
          fontPath: '../font/',
          className: 'appicon'
        }))
        .pipe($.rename(iconfont.stylusFileName))
        .pipe(gulp.dest(path.sourceStyle + 'tmp'));
    })
    .pipe(gulp.dest(path.build + 'font/'));
});

gulp.task('server', function () {
  connect.server({
    livereload: true
  });
});

gulp.task('watch', ['build'], function () {
  gulp.watch(path.sourceHtml + '*.html', ['html']);
  gulp.watch(path.sourceScript + '*.js', ['js']);
  gulp.watch(path.sourceStyle + '**/*.styl', ['css']);
});

gulp.task('deploy', ['build', 'png-min'], function () {
  gulp.src(path.build + '**/*')
    .pipe($.ghPages());
});

gulp.task('build', ['iconfont', 'html', 'css', 'js']);
gulp.task('dev', ['server', 'watch']);
gulp.task('default', ['build']);
<|endoftext|>