<commit_before>
module.exports = function(grunt) {

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    clean: { dist: ['css', 'js', 'fonts'] },
    less: {
        snisi: {
            options: {
                paths: ["src/less"],
                cleancss: true,
                compress: true
            },
            files: {
                "css/<%= pkg.name %>.css":
                    ["assets/bootstrap.less",
                     "src/less/snisi.less", ]
            }
        },
        map_alone: {
            options: {
                paths: ["src/less"],
                cleancss: true,
                compress: true
            },
            files: {
                "css/map_alone.css":
                    ["assets/bootstrap.less",
                     "src/less/map_alone.less", ]
            }
        }
    },
    concat: {
        js: {
            options: {stripBanners: false},
            src: ['assets/jquery.1.8.2.min.js',
                  'assets/bootstrap-3.1.1/js/tooltip.js',
                  'assets/bootstrap-3.1.1/js/popover.js',
                  'src/js/*.js'],
            dest: 'js/<%= pkg.name %>.js'
        },
        css: {
            src: ['assets/pure-min.css',
                  'css/<%= pkg.name %>.css'],
            dest: 'css/<%= pkg.name %>_all.css'
        },
        mapalonecss: {
            src: ['assets/pure-min.css',
                  'css/map_alone.css'],
            dest: 'css/map_alone_all.css'
        },
        mapcss: {
            src: ['assets/mapbox.2.0.1.css', 'assets/leaflet.label.0.2.1.css'],
            dest: 'css/map.css'
        },
        mapjs: {
            options: {stripBanners: false},
            src: ['assets/mapbox.2.0.1.js', 'assets/leaflet.label.0.2.1.js',
                  'assets/leaflet.spin.js', 'assets/simple_statistics.js',
                  'assets/spin.min.js', 'assets/d3.v3.min.js',
                  'assets/leaflet-image.js'],
            dest: 'js/map.js'
        },
        chartjs: {
            options: {stripBanners: false},
            src: ['assets/highcharts.3.0.10.js', 'assets/exporting.3.0.10.js'],
            dest: 'js/chart.js'
        },
    },
    cssmin: {
        css:{
            src: 'css/<%= pkg.name %>_all.css',
            dest: 'css/<%= pkg.name %>_all.min.css'
        },
        mapcss:{
            src: 'css/map.css',
            dest: 'css/map.min.css'
        }
    },
    uglify: {
        options: {
            banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n',
            mangle: false,
            beautify: false,
            compress: true,
            wrap: false,
            preserveComments: false
        },
        pkg: {
            files: {
                'js/<%= pkg.name %>.min.js': ['<%= concat.js.dest %>']
            }
        },
        // map: {
        //     files: {
        //         'js/map.min.js': ['js/map.js']
        //     }
        // },
        // chart: {
        //     files: {
        //         'js/chart.min.js': ['js/chart.js']
        //     }
        // }
    },
    copy: {
      fonts: {
        files: [
            {expand: true, cwd: 'assets/droidsans/', src: ['**'], dest: 'fonts/', filter: 'isFile'},
            {expand: true, cwd: 'assets/snisi-icon/', src: ['**'], dest: 'fonts/', filter: 'isFile'},
        ]
      }
    },
    jshint: {
      files: ['Gruntfile.js', 'src/**/*.js'],
      options: {
        // options here to override JSHint defaults
        globals: {
          jQuery: true,
          console: true,
          module: true,
          document: true
        }
      }
    },
    watch: {
      files: ['<%= jshint.files %>', 'src/less/*.less', 'src/js/*.js', 'assets/bootstrap.less'],
      tasks: ['dist-css', 'dist-js']
    }
  });

  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-css');

  grunt.registerTask('test', ['jshint']);

  // JS distribution task.
  grunt.registerTask('dist-js', ['concat', 'uglify']);

  // CSS distribution task.
  grunt.registerTask('dist-css', ['copy', 'less', 'concat', 'cssmin']);

  // Fonts distribution task.
  grunt.registerTask('dist-fonts', ['copy']);

  // Full distribution task.
  grunt.registerTask('dist', ['clean', 'dist-css', 'dist-fonts', 'dist-js']);

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

};
<commit_msg>use fontello package<commit_after>
module.exports = function(grunt) {

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    clean: { dist: ['css', 'js', 'fonts'] },
    less: {
        snisi: {
            options: {
                paths: ["src/less"],
                cleancss: true,
                compress: true
            },
            files: {
                "css/<%= pkg.name %>.css":
                    ["assets/bootstrap.less",
                     "src/less/snisi.less", ]
            }
        },
        map_alone: {
            options: {
                paths: ["src/less"],
                cleancss: true,
                compress: true
            },
            files: {
                "css/map_alone.css":
                    ["assets/bootstrap.less",
                     "src/less/map_alone.less", ]
            }
        }
    },
    concat: {
        js: {
            options: {stripBanners: false},
            src: ['assets/jquery.1.8.2.min.js',
                  'assets/bootstrap-3.1.1/js/tooltip.js',
                  'assets/bootstrap-3.1.1/js/popover.js',
                  'src/js/*.js'],
            dest: 'js/<%= pkg.name %>.js'
        },
        css: {
            src: ['assets/pure-min.css',
                  'css/<%= pkg.name %>.css'],
            dest: 'css/<%= pkg.name %>_all.css'
        },
        mapalonecss: {
            src: ['assets/pure-min.css',
                  'css/map_alone.css'],
            dest: 'css/map_alone_all.css'
        },
        mapcss: {
            src: ['assets/mapbox.2.0.1.css', 'assets/leaflet.label.0.2.1.css'],
            dest: 'css/map.css'
        },
        mapjs: {
            options: {stripBanners: false},
            src: ['assets/mapbox.2.0.1.js', 'assets/leaflet.label.0.2.1.js',
                  'assets/leaflet.spin.js', 'assets/simple_statistics.js',
                  'assets/spin.min.js', 'assets/d3.v3.min.js',
                  'assets/leaflet-image.js'],
            dest: 'js/map.js'
        },
        chartjs: {
            options: {stripBanners: false},
            src: ['assets/highcharts.3.0.10.js', 'assets/exporting.3.0.10.js'],
            dest: 'js/chart.js'
        },
    },
    cssmin: {
        css:{
            src: 'css/<%= pkg.name %>_all.css',
            dest: 'css/<%= pkg.name %>_all.min.css'
        },
        mapcss:{
            src: 'css/map.css',
            dest: 'css/map.min.css'
        }
    },
    uglify: {
        options: {
            banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n',
            mangle: false,
            beautify: false,
            compress: true,
            wrap: false,
            preserveComments: false
        },
        pkg: {
            files: {
                'js/<%= pkg.name %>.min.js': ['<%= concat.js.dest %>']
            }
        },
        // map: {
        //     files: {
        //         'js/map.min.js': ['js/map.js']
        //     }
        // },
        // chart: {
        //     files: {
        //         'js/chart.min.js': ['js/chart.js']
        //     }
        // }
    },
    copy: {
      fonts: {
        files: [
            {expand: true, cwd: 'assets/droidsans/', src: ['**'], dest: 'fonts/', filter: 'isFile'},
            {expand: true, cwd: 'assets/fontello/font/', src: ['**'], dest: 'fonts/', filter: 'isFile'},
        ]
      }
    },
    jshint: {
      files: ['Gruntfile.js', 'src/**/*.js'],
      options: {
        // options here to override JSHint defaults
        globals: {
          jQuery: true,
          console: true,
          module: true,
          document: true
        }
      }
    },
    watch: {
      files: ['<%= jshint.files %>', 'src/less/*.less', 'src/js/*.js', 'assets/bootstrap.less'],
      tasks: ['dist-css', 'dist-js']
    }
  });

  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-css');

  grunt.registerTask('test', ['jshint']);

  // JS distribution task.
  grunt.registerTask('dist-js', ['concat', 'uglify']);

  // CSS distribution task.
  grunt.registerTask('dist-css', ['copy', 'less', 'concat', 'cssmin']);

  // Fonts distribution task.
  grunt.registerTask('dist-fonts', ['copy']);

  // Full distribution task.
  grunt.registerTask('dist', ['clean', 'dist-css', 'dist-fonts', 'dist-js']);

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

};
<|endoftext|>


<commit_before>/*jshint asi: true*/

(function() {
  'use strict'
  window.app = angular.module('presenter', []);

  app.config(
    ['$routeProvider', function($routeProvider) {
      return $routeProvider.when('/', {
        templateUrl: 'views/index.html',
        controller: 'MainCtrl'
      }).when('/o/:id', {
        templateUrl: 'views/object.html',
        controller: 'ObjectCtrl'
      }).when('/a/:id', {
        templateUrl: 'views/annotations.html',
        controller: 'notesCtrl'
      }).otherwise({
        redirectTo: '/'
      })
    }]
  )
  app.config(
    ['$httpProvider', function($httpProvider) {
      return delete $httpProvider.defaults.headers.common['X-Requested-With'];
    }]
  )

  app.factory('objects', ['$http', function($http) {
    return function() {
      return $http.get('objects.json').then(function(result) { return result.data; })
    }
  }])
  app.factory('tilesaw', ['$http', function($http) {
    return { get: function(image) {
      return $http.get('//tilesaw.dx.artsmia.org/'+image+'.tif').then(function(result) { return result.data; })
    }}
  }])
  app.factory('notes', ['$http', function($http) {
    return function() {
      // TODO: how do we want to cache/bundle the JSON? WP is slow
      // also cache it within ng so we aren't requesting/parsing it on each request
      return $http.get('http://new.artsmia.org/crashpad/json/').then(function(result) {
        return result.data;
      })
    }
  }])

  app.directive('flatmap', function(tilesaw) {
    return {
      restrict: 'E',
      scope: {
        json: '@',
        image: '@'
      },
      replace: true,
      transclude: true,
      template: '

',
      controller: function($scope) {
        var scope = $scope

        var loadImage = function(image) {
          scope.viewChanging = true
          scope.currentImage = image
          tilesaw.get(image).then(function(tileJson) {
            $('#'+scope.container).find('.leaflet-tile-pane').css('visibility', 'visible') // why is this necessary? when I re-init a zoomer it's visibility is hidden.
            var tileUrl = tileJson.tiles[0].replace('http://0', 'http://{s}')
            scope.zoom = Zoomer.zoom_image({container: scope.container, tileURL: tileUrl, imageWidth: tileJson.width, imageHeight: tileJson.height})
            scope.$broadcast('viewChanged')
          })
        }
        loadImage(scope.image)

        var annotateAndZoom = function(geometry) {
          if(scope.jsonLayer) removeJsonLayer()
          if(geometry) scope.jsonLayer = L.GeoJSON.geometryToLayer(geometry)
          if(scope.viewChanging) return // hold off until the view changes, resulting in `viewChanged` triggering this again
          if(scope.jsonLayer) {
            scope.jsonLayer.setStyle({stroke: true, fill: false, weight: 2, color: '#eee', opacity: '0.5'})
            scope.zoom.map.addLayer(scope.jsonLayer)
            scope.zoom.map.fitBounds(scope.jsonLayer.getBounds())
          }
        }

        var removeJsonLayer = function() {
          scope.zoom.map.removeLayer(scope.jsonLayer)
        }

        scope.$on('changeGeometry', function(event, geometry) { annotateAndZoom(geometry) }, true)
        scope.$on('viewChanged', function(event, message) { scope.viewChanging = false; annotateAndZoom() }, true)
        scope.$on('changeView', function(event, message) {
          if(message.image != scope.currentImage) loadImage(message.image)
        })

        return {
          loadImage: loadImage,
          annotateAndZoom: annotateAndZoom,
          removeJsonLayer: removeJsonLayer,
          scope: scope
        }
      },
      link: function(scope, element, attrs) {
        scope.container = 'zoom-' + scope.image + '-' + new Date().getUTCMilliseconds()
      }
    }
  })

  app.directive('note', function() {
    return {
      restrict: 'E',
      scope: {note: '=', view: '='},
      controller: function($scope) {},
      require: '^flatmap',
      link: function(scope, element, attrs, flatmapCtrl)  {
        window.noteScope = scope
        scope.flatmapCtrl = flatmapCtrl
        scope.map = scope.flatmapCtrl.scope.zoom.map
        scope.jsonLayer = L.GeoJSON.geometryToLayer(scope.note.firebase.geometry)
        scope.marker = L.marker(scope.jsonLayer.getBounds().getCenter())

        var zoomNote = function() {
          flatmapCtrl.scope.$broadcast('changeView', scope.view)
          flatmapCtrl.scope.$broadcast('changeGeometry', scope.note.firebase.geometry)
          scope.$apply(function() { scope.note.active = true })
        }
        var toggleNoteZoom = function() {
          if(scope.note.active) {
            flatmapCtrl.removeJsonLayer()
            // TODO: reset back to main view? Show view changing chrome?
            scope.map.zoomOut(100)
            scope.$apply(function() { scope.note.active = false })
          } else {
            zoomNote()
            var lastNote = flatmapCtrl.scope.lastActiveNote
            if(lastNote) lastNote.active = false
          }
          flatmapCtrl.scope.lastActiveNote = scope.note
        }

        scope.marker.addTo(scope.map).on('click', toggleNoteZoom)
      }
    }
  })

  app.controller('ObjectCtrl', ['$scope', '$routeParams', '$location', 'objects', 'notes',
    function($scope, $routeParams, $location, objects, notes) {
      $scope.id = $routeParams.id
      objects().then(function(data) {
        $scope.json = data[$scope.id]
        $scope.objects = data
      })
      notes().then(function(_wp) {
        $scope.wp = _wp.objects[$scope.id]
        if($scope.wp) {
          $scope.notes = $scope.wp.views
          $scope.$$phase || $scope.$apply()
        }
      })

      $scope.next = function(direction) {
        var next = $scope.objects.ids[$scope.objects.ids.indexOf(parseInt($scope.id))+1]
        if(next) $location.url('/o/'+next)
      }
      $scope.prev = function(direction) {
        var prev = $scope.objects.ids[$scope.objects.ids.indexOf(parseInt($scope.id))-1]
        if(prev) $location.url('/o/'+prev)
      }

      $scope.toggleView = function(about_or_annotations) {
        if(about_or_annotations && about_or_annotations.match('annotations')) {
          $scope.activeSection = 'annotations'
        } else {
          $scope.activeSection = 'about'
        }
      }
      $scope.toggleView()

      function activateAnnotationAndChangeImageIfNeccessary(note, view) {
        note.active = true
        $scope.$broadcast('changeView', view)
        $scope.$broadcast('changeGeometry', note.firebase.geometry)
      }

      $scope.activateNote = function(note, view) {
        // TODO: bubble this up into the directive so it works when clicking markers
        var shouldActivate = !note.active
        angular.forEach(view.annotations, function(ann) { ann.active = false })
        if(shouldActivate) activateAnnotationAndChangeImageIfNeccessary(note, view)
      }

      $scope.toggleSixbar = function(element) {
        $scope.sixBarClosed = !$scope.sixBarClosed
        setTimeout(Zoomer.windowResized, 0)
      }
    }
  ])

  app.controller('notesCtrl', ['$scope', '$routeParams', 'notes',
    function($scope, $routeParams, wp) {
      $scope.id = $routeParams.id
      wp().then(function(_wp) {
        $scope.notes = _wp.objects[$scope.id].views
        $scope.$apply()
      })
    }
  ])

  app.controller('MainCtrl', ['$scope', '$routeParams', 'objects',
    function($scope, $routeParams, objects) {
      objects().then(function(data) {
        $scope.objects = data
      })
    }
  ])
})()
<commit_msg>Open annotations panel when a marker is activated<commit_after>/*jshint asi: true*/

(function() {
  'use strict'
  window.app = angular.module('presenter', []);

  app.config(
    ['$routeProvider', function($routeProvider) {
      return $routeProvider.when('/', {
        templateUrl: 'views/index.html',
        controller: 'MainCtrl'
      }).when('/o/:id', {
        templateUrl: 'views/object.html',
        controller: 'ObjectCtrl'
      }).when('/a/:id', {
        templateUrl: 'views/annotations.html',
        controller: 'notesCtrl'
      }).otherwise({
        redirectTo: '/'
      })
    }]
  )
  app.config(
    ['$httpProvider', function($httpProvider) {
      return delete $httpProvider.defaults.headers.common['X-Requested-With'];
    }]
  )

  app.factory('objects', ['$http', function($http) {
    return function() {
      return $http.get('objects.json').then(function(result) { return result.data; })
    }
  }])
  app.factory('tilesaw', ['$http', function($http) {
    return { get: function(image) {
      return $http.get('//tilesaw.dx.artsmia.org/'+image+'.tif').then(function(result) { return result.data; })
    }}
  }])
  app.factory('notes', ['$http', function($http) {
    return function() {
      // TODO: how do we want to cache/bundle the JSON? WP is slow
      // also cache it within ng so we aren't requesting/parsing it on each request
      return $http.get('http://new.artsmia.org/crashpad/json/').then(function(result) {
        return result.data;
      })
    }
  }])

  app.directive('flatmap', function(tilesaw) {
    return {
      restrict: 'E',
      scope: {
        json: '@',
        image: '@'
      },
      replace: true,
      transclude: true,
      template: '

',
      controller: function($scope) {
        var scope = $scope

        var loadImage = function(image) {
          scope.viewChanging = true
          scope.currentImage = image
          tilesaw.get(image).then(function(tileJson) {
            $('#'+scope.container).find('.leaflet-tile-pane').css('visibility', 'visible') // why is this necessary? when I re-init a zoomer it's visibility is hidden.
            var tileUrl = tileJson.tiles[0].replace('http://0', 'http://{s}')
            scope.zoom = Zoomer.zoom_image({container: scope.container, tileURL: tileUrl, imageWidth: tileJson.width, imageHeight: tileJson.height})
            scope.$broadcast('viewChanged')
          })
        }
        loadImage(scope.image)

        var annotateAndZoom = function(geometry) {
          if(scope.jsonLayer) removeJsonLayer()
          if(geometry) scope.jsonLayer = L.GeoJSON.geometryToLayer(geometry)
          if(scope.viewChanging) return // hold off until the view changes, resulting in `viewChanged` triggering this again
          if(scope.jsonLayer) {
            scope.$parent.$broadcast('showAnnotationsPanel', 'annotations')
            scope.jsonLayer.setStyle({stroke: true, fill: false, weight: 2, color: '#eee', opacity: '0.5'})
            scope.zoom.map.addLayer(scope.jsonLayer)
            scope.zoom.map.fitBounds(scope.jsonLayer.getBounds())
          }
        }

        var removeJsonLayer = function() {
          scope.zoom.map.removeLayer(scope.jsonLayer)
        }

        scope.$on('changeGeometry', function(event, geometry) { annotateAndZoom(geometry) }, true)
        scope.$on('viewChanged', function(event, message) { scope.viewChanging = false; annotateAndZoom() }, true)
        scope.$on('changeView', function(event, message) {
          if(message.image != scope.currentImage) loadImage(message.image)
        })

        return {
          loadImage: loadImage,
          annotateAndZoom: annotateAndZoom,
          removeJsonLayer: removeJsonLayer,
          scope: scope
        }
      },
      link: function(scope, element, attrs) {
        scope.container = 'zoom-' + scope.image + '-' + new Date().getUTCMilliseconds()
      }
    }
  })

  app.directive('note', function() {
    return {
      restrict: 'E',
      scope: {note: '=', view: '='},
      controller: function($scope) {},
      require: '^flatmap',
      link: function(scope, element, attrs, flatmapCtrl)  {
        window.noteScope = scope
        scope.flatmapCtrl = flatmapCtrl
        scope.map = scope.flatmapCtrl.scope.zoom.map
        scope.jsonLayer = L.GeoJSON.geometryToLayer(scope.note.firebase.geometry)
        scope.marker = L.marker(scope.jsonLayer.getBounds().getCenter())

        var zoomNote = function() {
          flatmapCtrl.scope.$broadcast('changeView', scope.view)
          flatmapCtrl.scope.$broadcast('changeGeometry', scope.note.firebase.geometry)
          scope.$apply(function() { scope.note.active = true })
        }
        var toggleNoteZoom = function() {
          if(scope.note.active) {
            flatmapCtrl.removeJsonLayer()
            // TODO: reset back to main view? Show view changing chrome?
            scope.map.zoomOut(100)
            scope.$apply(function() { scope.note.active = false })
          } else {
            zoomNote()
            var lastNote = flatmapCtrl.scope.lastActiveNote
            if(lastNote) lastNote.active = false
          }
          flatmapCtrl.scope.lastActiveNote = scope.note
        }

        scope.marker.addTo(scope.map).on('click', toggleNoteZoom)
      }
    }
  })

  app.controller('ObjectCtrl', ['$scope', '$routeParams', '$location', 'objects', 'notes',
    function($scope, $routeParams, $location, objects, notes) {
      $scope.id = $routeParams.id
      objects().then(function(data) {
        $scope.json = data[$scope.id]
        $scope.objects = data
      })
      notes().then(function(_wp) {
        $scope.wp = _wp.objects[$scope.id]
        if($scope.wp) {
          $scope.notes = $scope.wp.views
          $scope.$$phase || $scope.$apply()
        }
      })

      $scope.next = function(direction) {
        var next = $scope.objects.ids[$scope.objects.ids.indexOf(parseInt($scope.id))+1]
        if(next) $location.url('/o/'+next)
      }
      $scope.prev = function(direction) {
        var prev = $scope.objects.ids[$scope.objects.ids.indexOf(parseInt($scope.id))-1]
        if(prev) $location.url('/o/'+prev)
      }

      $scope.toggleView = function(about_or_annotations) {
        if(about_or_annotations && about_or_annotations.match('annotations')) {
          $scope.activeSection = 'annotations'
        } else {
          $scope.activeSection = 'about'
        }
      }
      $scope.toggleView()
      $scope.$on('showAnnotationsPanel', function(view) {
        $scope.activeSection = 'annotations'
      })

      function activateAnnotationAndChangeImageIfNeccessary(note, view) {
        note.active = true
        $scope.$broadcast('changeView', view)
        $scope.$broadcast('changeGeometry', note.firebase.geometry)
      }

      $scope.activateNote = function(note, view) {
        // TODO: bubble this up into the directive so it works when clicking markers
        var shouldActivate = !note.active
        angular.forEach(view.annotations, function(ann) { ann.active = false })
        if(shouldActivate) activateAnnotationAndChangeImageIfNeccessary(note, view)
      }

      $scope.toggleSixbar = function(element) {
        $scope.sixBarClosed = !$scope.sixBarClosed
        setTimeout(Zoomer.windowResized, 0)
      }
    }
  ])

  app.controller('notesCtrl', ['$scope', '$routeParams', 'notes',
    function($scope, $routeParams, wp) {
      $scope.id = $routeParams.id
      wp().then(function(_wp) {
        $scope.notes = _wp.objects[$scope.id].views
        $scope.$apply()
      })
    }
  ])

  app.controller('MainCtrl', ['$scope', '$routeParams', 'objects',
    function($scope, $routeParams, objects) {
      objects().then(function(data) {
        $scope.objects = data
      })
    }
  ])
})()
<|endoftext|>


<commit_before>import React from 'react';
import { mount } from 'enzyme';
import fetchMock from 'fetch-mock';
import { act } from 'react-test-renderer';

import './helpers/mockAsyncRequest';
import './helpers/miqSparkle';

import ReportDataTable from '../components/report-data-table';

describe('<ReportDataTable />', () => {
  let initialProps;

  beforeEach(() => {
    initialProps = {
      reportResultId: 123,
      perPageDefault: 20,
      perPageOptions: [20, 50, 100],
    };
  });

  afterEach(() => {
    fetchMock.reset();
  });

  it('should fetch report data when instantiated', (done) => {
    const sortBy = '';
    const sortDirection = '';
    const limit = 20;
    const offset = 0;
    const url = `/api/results/${initialProps.reportResultId}?\
expand_value_format=true&\
hash_attribute=result_set&\
sort_by=${sortBy}&sort_order=${sortDirection}&\
limit=${limit}&offset=${offset}`;

    fetchMock.getOnce(url, { report: { col_order: [] }, count: 0, result_set: [] });

    let wrapper;

    act(() => {
      wrapper = mount(<ReportDataTable {...initialProps} />);
    });

    setTimeout(() => {
      expect(fetchMock.calls()).toHaveLength(1);
      expect(wrapper.find('div.table-view-pf-toolbar')).toHaveLength(1);
      expect(wrapper.find('table.table')).toHaveLength(1);
      expect(wrapper.find('form.table-view-pf-pagination')).toHaveLength(1);
      done();
    });
  });
});
<commit_msg>Report data: add EmptyState test, update tests.<commit_after>import React from 'react';
import { mount } from 'enzyme';
import fetchMock from 'fetch-mock';
import { act } from 'react-test-renderer';
import { EmptyState, Paginator, Table } from 'patternfly-react';

import './helpers/mockAsyncRequest';
import './helpers/miqSparkle';

import ReportDataTable from '../components/report-data-table';

describe('<ReportDataTable />', () => {
  let initialProps;

  beforeEach(() => {
    initialProps = {
      reportResultId: 123,
      perPageDefault: 20,
      perPageOptions: [20, 50, 100],
    };
  });

  afterEach(() => {
    fetchMock.reset();
  });

  const requestUrl = () => {
    const sortBy = '';
    const sortDirection = '';
    const limit = 20;
    const offset = 0;
    return `/api/results/${initialProps.reportResultId}?\
expand_value_format=true&\
hash_attribute=result_set&\
sort_by=${sortBy}&sort_order=${sortDirection}&\
limit=${limit}&offset=${offset}`;
  };

  it('should fetch and display report data when instantiated', (done) => {
    fetchMock.getOnce(requestUrl(), { report: { col_order: [] }, count: 1, result_set: [{ foo: 'bar' }] });

    let wrapper;
    act(() => {
      wrapper = mount(<ReportDataTable {...initialProps} />);
    });

    setTimeout(() => {
      wrapper.update();

      expect(fetchMock.calls()).toHaveLength(1);
      expect(wrapper.find('div.table-view-pf-toolbar')).toHaveLength(1);
      expect(wrapper.find(Table.PfProvider)).toHaveLength(1);
      expect(wrapper.find(Paginator)).toHaveLength(1);
      done();
    });
  });

  it('should display empty state when there is no data', (done) => {
    fetchMock.getOnce(requestUrl(), { report: { col_order: [] }, count: 0, result_set: [] });

    let wrapper;
    act(() => {
      wrapper = mount(<ReportDataTable {...initialProps} />);
    });

    setTimeout(() => {
      wrapper.update();

      expect(fetchMock.calls()).toHaveLength(1);
      expect(wrapper.find('div.table-view-pf-toolbar')).toHaveLength(1);
      expect(wrapper.find(Table.PfProvider)).toHaveLength(0);
      expect(wrapper.find(Paginator)).toHaveLength(0);
      expect(wrapper.find(EmptyState)).toHaveLength(1);
      done();
    });
  });
});
<|endoftext|>


<commit_before>'use strict'

var express = require('express');
var FitbitApiClient = require("fitbit-node");
var config = require('./config/dev_config');
var jwt    = require('jsonwebtoken');

var app = express();
var fitbitClient = new FitbitApiClient(config.fitbitClientId, config.fitbitClientSecret);

/** Route Middleware **/

// Verify Token on Requestes
app.use(function(req, res, next) {

  // check header or url parameters or post parameters for token
  var token = req.body.token || req.query.token || req.headers['x-access-token'];

  // decode token
  if (token) {

    // verifies secret and checks exp
    jwt.verify(token, app.get('superSecret'), function(err, decoded) {
      if (err) {
        return res.json({ success: false, message: 'Failed to authenticate token.' });
      } else {
        // if everything is good, save to request for use in other routes
        req.decoded = decoded;
        next();
      }
    });

  } else {

    // if there is no token
    // return an error
    return res.status(403).send({
        success: false,
        message: 'No token provided.'
    });

  }
});

/** Routes **/
var loginApi = require('./routes/login')(app, express, fitbitClient);
app.use('/fitbit', loginApi);

app.listen(config.port, function () {
	console.log('Server started server on localhost: ' + config.port);
});
<commit_msg>*fix(server.js):* updated route middleware to protect 'test api' and not our auth endpoint.<commit_after>'use strict'

var express = require('express');
var FitbitApiClient = require("fitbit-node");
var config = require('./config/dev_config');
var jwt    = require('jsonwebtoken');

var app = express();
var fitbitClient = new FitbitApiClient(config.fitbitClientId, config.fitbitClientSecret);

/** Routes **/
var loginApi = require('./routes/login')(app, express, fitbitClient);
app.use('/fitbit', loginApi);

/** Route Middleware **/
app.use(function(req, res, next) {
    var token = req.headers['x-access-token'];
    if (token) {
        // verifies secret and checks exp
        jwt.verify(token, 'superSecret', function(err, decoded) {
          if (err) {
            return res.json({ success: false, message: 'Failed to authenticate token.' });
          } else {
            // if everything is good, save to request for use in other routes
            req.decoded = decoded;
            next();
          }
        });

      } else {
        // if there is no token, return an error
        return res.status(403).send({
            success: false,
            message: 'No token provided.'
        });
      }
});

var testApi = require('./routes/test')(app, express);
app.use('/test', testApi);

app.listen(config.port, function () {
	console.log('Server started server on localhost: ' + config.port);
});
<|endoftext|>


<commit_before>angular.module('plugins.automove.controllers.upsertRule', [
    'hadouken.jsonrpc'
])
.controller('AutoMove.UpsertRuleController', [
    '$scope', '$modalInstance', 'jsonrpc', 'rule',
    function ($scope, $modalInstance, jsonrpc, rule) {
        $scope.rule = rule;
        $scope.parameters = [];

        function asyncLoop(iterations, func, callback) {
            var index = 0;
            var done = false;
            var loop = {
                next: function () {
                    if (done) {
                        return;
                    }

                    if (index < iterations) {
                        index++;
                        func(loop);

                    } else {
                        done = true;
                        callback();
                    }
                },

                iteration: function () {
                    return index - 1;
                },

                break: function () {
                    done = true;
                    callback();
                }
            };
            loop.next();
            return loop;
        };

        $scope.validate = function () {
            if (rule.Name === '' || rule.TargetPath === '') return false;
            if ($scope.parameters.length === 0) return false;
            return true;
        };

        $scope.addParameter = function () {
            $scope.parameters.push({
                Id: -1,
                Source: "Name",
                Pattern: "^$"
            });
        };

        $scope.removeParameter = function (parameter, index) {
            if (parameter.Id <= 0) {
                $scope.parameters.splice(index, 1);
            } else {
                jsonrpc.request('automove.parameters.delete', {
                    params: [parameter.Id],
                    success: function () {
                        $scope.parameters.splice(index, 1);
                    }
                });
            }
        };

        // Save parameters
        function saveParameters(callback) {
            if ($scope.parameters.length === 0) {
                callback();
                return;
            }

            var length = $scope.parameters.length;

            asyncLoop(length, function (loop) {
                var parameter = $scope.parameters[loop.iteration()];
                parameter.RuleId = $scope.rule.Id;

                var method = parameter.Id <= 0 ? 'automove.parameters.create' : 'automove.parameters.update';

                jsonrpc.request(method, {
                    params: [parameter],
                    success: function () { loop.next(); }
                });

            }, function () { callback(); });
        };

        $scope.save = function () {
            if ($scope.rule.Id <= 0) {
                jsonrpc.request('automove.rules.create', {
                    params: [$scope.rule],
                    success: function (data) {
                        $scope.rule = data.result;

                        saveParameters(function () {
                            $modalInstance.close(data.result);
                        });
                    }
                });
            } else {
                jsonrpc.request('automove.rules.update', {
                    params: [$scope.rule],
                    success: function () {
                        saveParameters(function () {
                            $modalInstance.close($scope.rule);
                        });
                    }
                });
            }
        };

        // Get parameters for this rule
        jsonrpc.request('automove.parameters.getByRuleId', {
            params: [rule.Id],
            success: function (data) {
                $scope.parameters = data.result;

                // Add default parameter
                if ($scope.parameters.length === 0) {
                    $scope.addParameter();
                }
            }
        });
    }
]);<commit_msg>Sensible defaults for AutoMove.<commit_after>angular.module('plugins.automove.controllers.upsertRule', [
    'hadouken.jsonrpc'
])
.controller('AutoMove.UpsertRuleController', [
    '$scope', '$modalInstance', 'jsonrpc', 'rule',
    function ($scope, $modalInstance, jsonrpc, rule) {
        $scope.rule = rule;
        $scope.parameters = [];

        function asyncLoop(iterations, func, callback) {
            var index = 0;
            var done = false;
            var loop = {
                next: function () {
                    if (done) {
                        return;
                    }

                    if (index < iterations) {
                        index++;
                        func(loop);

                    } else {
                        done = true;
                        callback();
                    }
                },

                iteration: function () {
                    return index - 1;
                },

                break: function () {
                    done = true;
                    callback();
                }
            };
            loop.next();
            return loop;
        };

        $scope.validate = function () {
            if (rule.Name === '' || rule.TargetPath === '') return false;
            if ($scope.parameters.length === 0) return false;
            return true;
        };

        $scope.addParameter = function () {
            $scope.parameters.push({
                Id: -1,
                Source: "Name",
                Pattern: ".*"
            });
        };

        $scope.removeParameter = function (parameter, index) {
            if (parameter.Id <= 0) {
                $scope.parameters.splice(index, 1);
            } else {
                jsonrpc.request('automove.parameters.delete', {
                    params: [parameter.Id],
                    success: function () {
                        $scope.parameters.splice(index, 1);
                    }
                });
            }
        };

        // Save parameters
        function saveParameters(callback) {
            if ($scope.parameters.length === 0) {
                callback();
                return;
            }

            var length = $scope.parameters.length;

            asyncLoop(length, function (loop) {
                var parameter = $scope.parameters[loop.iteration()];
                parameter.RuleId = $scope.rule.Id;

                var method = parameter.Id <= 0 ? 'automove.parameters.create' : 'automove.parameters.update';

                jsonrpc.request(method, {
                    params: [parameter],
                    success: function () { loop.next(); }
                });

            }, function () { callback(); });
        };

        $scope.save = function () {
            if ($scope.rule.Id <= 0) {
                jsonrpc.request('automove.rules.create', {
                    params: [$scope.rule],
                    success: function (data) {
                        $scope.rule = data.result;

                        saveParameters(function () {
                            $modalInstance.close(data.result);
                        });
                    }
                });
            } else {
                jsonrpc.request('automove.rules.update', {
                    params: [$scope.rule],
                    success: function () {
                        saveParameters(function () {
                            $modalInstance.close($scope.rule);
                        });
                    }
                });
            }
        };

        // Get parameters for this rule
        jsonrpc.request('automove.parameters.getByRuleId', {
            params: [rule.Id],
            success: function (data) {
                $scope.parameters = data.result;

                // Add default parameter
                if ($scope.parameters.length === 0) {
                    $scope.addParameter();
                }
            }
        });
    }
]);<|endoftext|>


<commit_before>var io;
var Combos = function (theIO) {
  io = theIO;
};

Combos.prototype.randomFlash = function() {
  io.emit('pulse',  {
    name: 'flash',
    r: Math.floor(Math.random() * 255),
    g: Math.floor(Math.random() * 255),
    b: Math.floor(Math.random() * 255)
  });
}

Combos.prototype.flashRow = function(row) {
  io.emit('pulse', {
    name: 'flash',
    target: {
      row: row
    }
  });
}

Combos.prototype.flashColumn = function(column) {
  io.emit('pulse', {
    name: 'flash',
    target: {
      col: column
    }
  });
}

Combos.prototype.flashUp = function(rate) {
  var self = this;
  var march = function(rate) {
    setTimeout(self.flashRow,      0, 1);
    setTimeout(self.flashRow,   rate, 2);
    setTimeout(self.flashRow, 2*rate, 3);
    setTimeout(self.flashRow, 3*rate, 4);
  };
  march(rate);
  setInterval(march, 4*rate, rate);
}

Combos.prototype.atmospheric = function (length) {
  var emitall = function() {
    io.emit('pulse', {
      name: 'fade',
      totalFrames: 12,
      r: Math.ceil(Math.random()*25),
      g: Math.ceil(Math.random()*25),
      b: Math.ceil(Math.random()*120),
      probability: 0.1,
    });
  };
  emitall();
  var atmos = setInterval(emitall, 40)
  setTimeout(clearInterval, length, atmos);
}

Combos.prototype.glock3 = function (note) {
  // note should be number 1 to 3

  var offset = (note - 2) * 0.333333333;

  io.emit('pulse', {
    name: 'bars',
    totalFrames: 8,
    x: 0.5,
    w: 1,
    y: 0.5 + offset,
    h: 0.33333
  });
}

module.exports = Combos;<commit_msg>Add double slide and calm before the storm combos<commit_after>var io;
var Combos = function (theIO) {
  io = theIO;
};

Combos.prototype.randomFlash = function() {
  io.emit('pulse',  {
    name: 'flash',
    r: Math.floor(Math.random() * 255),
    g: Math.floor(Math.random() * 255),
    b: Math.floor(Math.random() * 255)
  });
}

Combos.prototype.flashRow = function(row) {
  io.emit('pulse', {
    name: 'flash',
    target: {
      row: row
    }
  });
}

Combos.prototype.flashColumn = function(column) {
  io.emit('pulse', {
    name: 'flash',
    target: {
      col: column
    }
  });
}

Combos.prototype.flashUp = function(rate) {
  var self = this;
  var march = function(rate) {
    setTimeout(self.flashRow,      0, 1);
    setTimeout(self.flashRow,   rate, 2);
    setTimeout(self.flashRow, 2*rate, 3);
    setTimeout(self.flashRow, 3*rate, 4);
  };
  march(rate);
  setInterval(march, 4*rate, rate);
}

Combos.prototype.atmospheric = function (length) {
  var emitall = function() {
    io.emit('pulse', {
      name: 'fade',
      totalFrames: 12,
      r: Math.ceil(Math.random()*25),
      g: Math.ceil(Math.random()*25),
      b: Math.ceil(Math.random()*120),
      probability: 0.1,
    });
  };
  emitall();
  var atmos = setInterval(emitall, 40)
  setTimeout(clearInterval, length, atmos);
}

Combos.prototype.glock3 = function (note) {
  // note should be number 1 to 3

  var offset = (note - 2) * 0.333333333;

  io.emit('pulse', {
    name: 'bars',
    totalFrames: 8,
    r: (255),
    g: (200 - Math.floor(Math.random()*200)),
    b: (200 - Math.floor(Math.random()*200)),
    x: 0.5,
    w: 1,
    y: 0.5 + offset,
    h: 0.33333,
  });
}

Combos.prototype.slideUpDouble = function () {
  var emitter = function(frames) {
    io.emit('pulse', {
      name: 'slider',
      totalFrames: frames
    });
  }

  emitter(22);

  setTimeout(emitter, 750, 25);
}

Combos.prototype.calmBeforeStorm = function () {
  io.emit('pulse', {
    name: 'strobe',
    totalFrames: 11,
    framesOn: 10,
    probability: 0.5,
    r: 255,
    g: 100,
    b: 100
  });
}

module.exports = Combos;<|endoftext|>


<commit_before>module.exports = Client

var BN = require('bn.js')
var bencode = require('bencode')
var common = require('./lib/common')
var compact2string = require('compact2string')
var concat = require('concat-stream')
var debug = require('debug')('bittorrent-tracker:client')
var dgram = require('dgram')
var EventEmitter = require('events').EventEmitter
var extend = require('extend.js')
var hat = require('hat')
var http = require('http')
var inherits = require('inherits')
var querystring = require('querystring')
var url = require('url')

inherits(Client, EventEmitter)

/**
 * A Client manages tracker connections for a torrent.
 *
 * @param {string} peerId  this peer's id
 * @param {Number} port    port number that the client is listening on
 * @param {Object} torrent parsed torrent
 * @param {Object} opts    optional options
 * @param {Number} opts.numWant    number of peers to request
 * @param {Number} opts.interval   interval in ms to send announce requests to the tracker
 */
function Client (peerId, port, torrent, opts) {

  var self = this
  if (!(self instanceof Client)) return new Client(peerId, port, torrent, opts)
  EventEmitter.call(self)
  self._opts = opts || {}

  // required
  self._peerId = Buffer.isBuffer(peerId)
    ? peerId
    : new Buffer(peerId, 'utf8')
  self._port = port
  self._infoHash = Buffer.isBuffer(torrent.infoHash)
    ? torrent.infoHash
    : new Buffer(torrent.infoHash, 'hex')
  self.torrentLength = torrent.length

  // optional
  self._numWant = self._opts.numWant || 80
  self._intervalMs = self._opts.interval || (30 * 60 * 1000) // default: 30 minutes

  debug('new tracker client for ' + self._infoHash.toString('hex'))

  if (typeof torrent.announce === 'string') {
    // magnet-uri returns a string if the magnet uri only contains one 'tr' parameter
    torrent.announce = [torrent.announce]
  }
  self._trackers = torrent.announce.map(function (announceUrl) {
    return new Tracker(self, announceUrl, self._opts)
  })
}

Client.prototype.start = function (opts) {
  var self = this
  self._trackers.forEach(function (tracker) {
    tracker.start(opts)
  })
}

Client.prototype.stop = function (opts) {
  var self = this
  self._trackers.forEach(function (tracker) {
    tracker.stop(opts)
  })
}

Client.prototype.complete = function (opts) {
  var self = this
  self._trackers.forEach(function (tracker) {
    tracker.complete(opts)
  })
}

Client.prototype.update = function (opts) {
  var self = this
  self._trackers.forEach(function (tracker) {
    tracker.update(opts)
  })
}

Client.prototype.scrape = function (opts) {
  var self = this
  self._trackers.forEach(function (tracker) {
    tracker.scrape(opts)
  })
}

Client.prototype.setInterval = function (intervalMs) {
  var self = this
  self._intervalMs = intervalMs

  self._trackers.forEach(function (tracker) {
    tracker.setInterval(intervalMs)
  })
}

inherits(Tracker, EventEmitter)

/**
 * An individual torrent tracker (used by Client)
 *
 * @param {Client} client       parent bittorrent tracker client
 * @param {string} announceUrl  announce url of tracker
 * @param {Object} opts         optional options
 */
function Tracker (client, announceUrl, opts) {
  var self = this
  EventEmitter.call(self)
  self._opts = opts || {}

  self.client = client

  debug('new tracker for ' + announceUrl)

  self._announceUrl = announceUrl
  self._intervalMs = self.client._intervalMs // use client interval initially
  self._interval = null

  if (self._announceUrl.indexOf('udp:') === 0) {
    self._requestImpl = self._requestUdp
  } else {
    self._requestImpl = self._requestHttp
  }

}

Tracker.prototype.start = function (opts) {
  var self = this
  opts = opts || {}
  opts.event = 'started'

  debug('sent `start` to ' + self._announceUrl)
  self._request(opts)
  self.setInterval(self._intervalMs) // start announcing on intervals
}

Tracker.prototype.stop = function (opts) {
  var self = this
  opts = opts || {}
  opts.event = 'stopped'

  debug('sent `stop` to ' + self._announceUrl)
  self._request(opts)
  self.setInterval(0) // stop announcing on intervals
}

Tracker.prototype.complete = function (opts) {
  var self = this
  opts = opts || {}
  opts.event = 'completed'
  opts.downloaded = opts.downloaded || self.torrentLength || 0

  debug('sent `complete` to ' + self._announceUrl)
  self._request(opts)
}

Tracker.prototype.update = function (opts) {
  var self = this
  opts = opts || {}

  debug('sent `update` to ' + self._announceUrl)
  self._request(opts)
}

Tracker.prototype.scrape = function (opts) {
  var self = this

  if (!self._scrapeUrl) {
    var announce = 'announce'
    var i = self._announceUrl.lastIndexOf('/') + 1

    if (i >= 1 && self._announceUrl.slice(i, i + announce.length) === announce) {
      self._scrapeUrl = self._announceUrl.slice(0, i) + 'scrape' + self._announceUrl.slice(i + announce.length)
    }
  }

  if (!self._scrapeUrl) {
    debug('scrape not supported by ' + self._announceUrl)
    self.client.emit('error', new Error('scrape not supported for announceUrl ' + self._announceUrl))
    return
  }

  debug('sent `scrape` to ' + self._announceUrl)

  opts = extend({
    info_hash: common.bytewiseEncodeURIComponent(self.client._infoHash)
  }, opts)

  self._requestImpl(self._scrapeUrl, opts)
}

Tracker.prototype.setInterval = function (intervalMs) {
  var self = this
  clearInterval(self._interval)

  self._intervalMs = intervalMs
  if (intervalMs) {
    self._interval = setInterval(self.update.bind(self), self._intervalMs)
  }
}

/**
 * Send an announce request to the tracker
 */
Tracker.prototype._request = function (opts) {
  var self = this
  opts = extend({
    info_hash: common.bytewiseEncodeURIComponent(self.client._infoHash),
    peer_id: common.bytewiseEncodeURIComponent(self.client._peerId),
    port: self.client._port,
    compact: 1,
    numwant: self.client._numWant,
    uploaded: 0, // default, user should provide real value
    downloaded: 0 // default, user should provide real value
  }, opts)

  if (self.client.torrentLength !== undefined) {
    opts.left = self.client.torrentLength - (opts.downloaded || 0)
  }

  if (self._trackerId) {
    opts.trackerid = self._trackerId
  }

  self._requestImpl(self._announceUrl, opts)
}

Tracker.prototype._requestHttp = function (requestUrl, opts) {
  var self = this
  var fullUrl = requestUrl + '?' + querystring.stringify(opts)

  var req = http.get(fullUrl, function (res) {
    if (res.statusCode !== 200) {
      res.resume() // consume the whole stream
      self.client.emit('warning', new Error('Invalid response code ' + res.statusCode + ' from tracker ' + requestUrl))
      return
    }
    res.pipe(concat(function (data) {
      if (data && data.length) self._handleResponse(requestUrl, data)
    }))
  })

  req.on('error', function (err) {
    self.client.emit('warning', err)
  })
}

Tracker.prototype._requestUdp = function (requestUrl, opts) {
  var self = this
  var parsedUrl = url.parse(requestUrl)
  var socket = dgram.createSocket('udp4')
  var transactionId = new Buffer(hat(32), 'hex')

  var stopped = opts.event === 'stopped'
  // if we're sending a stopped message, we don't really care if it arrives, so set
  // a short timer and don't call error
  var timeout = setTimeout(function () {
    timeout = null
    cleanup()
    if (!stopped) {
      error('tracker request timed out')
    }
  }, stopped ? 1500 : 15000)

  if (timeout && timeout.unref) {
    timeout.unref()
  }

  send(Buffer.concat([
    common.CONNECTION_ID,
    common.toUInt32(common.ACTIONS.CONNECT),
    transactionId
  ]))

  socket.on('error', error)

  socket.on('message', function (msg, rinfo) {
    if (msg.length < 8 || msg.readUInt32BE(4) !== transactionId.readUInt32BE(0)) {
      return error('tracker sent back invalid transaction id')
    }

    var action = msg.readUInt32BE(0)
    switch (action) {
      case 0: // handshake
        if (msg.length < 16) {
          return error('invalid udp handshake')
        }

        var scrapeStr = 'scrape'
        if (requestUrl.substr(requestUrl.lastIndexOf('/') + 1, scrapeStr.length) === scrapeStr) {
          scrape(msg.slice(8, 16), opts)
        } else {
          announce(msg.slice(8, 16), opts)
        }

        return

      case 1: // announce
        cleanup()
        if (msg.length < 20) {
          return error('invalid announce message')
        }

        var interval = msg.readUInt32BE(8)
        if (interval && !self._opts.interval && self._intervalMs !== 0) {
          // use the interval the tracker recommends, UNLESS the user manually specifies an
          // interval they want to use
          self.setInterval(interval * 1000)
        }

        self.client.emit('update', {
          announce: self._announceUrl,
          complete: msg.readUInt32BE(16),
          incomplete: msg.readUInt32BE(12)
        })

        compact2string.multi(msg.slice(20)).forEach(function (addr) {
          self.client.emit('peer', addr)
        })
        break

      case 2: // scrape
        cleanup()
        if (msg.length < 20) {
          return error('invalid scrape message')
        }
        self.client.emit('scrape', {
          announce: self._announceUrl,
          complete: msg.readUInt32BE(8),
          downloaded: msg.readUInt32BE(12),
          incomplete: msg.readUInt32BE(16)
        })
        break

      case 3: // error
        cleanup()
        if (msg.length < 8) {
          return error('invalid error message')
        }
        self.client.emit('error', new Error(msg.slice(8).toString()))
        break
    }
  })

  function send (message) {
    if (!parsedUrl.port) {
      parsedUrl.port = 80
    }
    socket.send(message, 0, message.length, parsedUrl.port, parsedUrl.hostname)
  }

  function error (message) {
    self.client.emit('error', new Error(message + ' (connecting to tracker ' + requestUrl + ')'))
    cleanup()
  }

  function cleanup () {
    if (timeout) {
      clearTimeout(timeout)
      timeout = null
    }
    try { socket.close() } catch (err) {}
  }

  function genTransactionId () {
    transactionId = new Buffer(hat(32), 'hex')
  }

  function announce (connectionId, opts) {
    opts = opts || {}
    genTransactionId()

    send(Buffer.concat([
      connectionId,
      common.toUInt32(common.ACTIONS.ANNOUNCE),
      transactionId,
      self.client._infoHash,
      self.client._peerId,
      toUInt64(opts.downloaded || 0),
      opts.left ? toUInt64(opts.left) : new Buffer('FFFFFFFFFFFFFFFF', 'hex'),
      toUInt64(opts.uploaded || 0),
      common.toUInt32(common.EVENTS[opts.event] || 0),
      common.toUInt32(0), // ip address (optional)
      common.toUInt32(0), // key (optional)
      common.toUInt32(self.client._numWant),
      toUInt16(self.client._port || 0)
    ]))
  }

  function scrape (connectionId, opts) {
    genTransactionId()

    send(Buffer.concat([
      connectionId,
      common.toUInt32(common.ACTIONS.SCRAPE),
      transactionId,
      self.client._infoHash
    ]))
  }
}

Tracker.prototype._handleResponse = function (requestUrl, data) {
  var self = this

  try {
    data = bencode.decode(data)
  } catch (err) {
    return self.client.emit('error', new Error('Error decoding tracker response: ' + err.message))
  }
  var failure = data['failure reason']
  if (failure) {
    return self.client.emit('warning', new Error(failure))
  }

  var warning = data['warning message']
  if (warning) {
    self.client.emit('warning', new Error(warning))
  }

  if (requestUrl === self._announceUrl) {
    var interval = data.interval || data['min interval']
    if (interval && !self._opts.interval && self._intervalMs !== 0) {
      // use the interval the tracker recommends, UNLESS the user manually specifies an
      // interval they want to use
      self.setInterval(interval * 1000)
    }

    var trackerId = data['tracker id']
    if (trackerId) {
      // If absent, do not discard previous trackerId value
      self._trackerId = trackerId
    }

    self.client.emit('update', {
      announce: self._announceUrl,
      complete: data.complete,
      incomplete: data.incomplete
    })

    if (Buffer.isBuffer(data.peers)) {
      // tracker returned compact response
      compact2string.multi(data.peers).forEach(function (addr) {
        self.client.emit('peer', addr)
      })
    } else if (Array.isArray(data.peers)) {
      // tracker returned normal response
      data.peers.forEach(function (peer) {
        var ip = peer.ip
        var addr = ip[0] + '.' + ip[1] + '.' + ip[2] + '.' + ip[3] + ':' + peer.port
        self.client.emit('peer', addr)
      })
    }
  } else if (requestUrl === self._scrapeUrl) {
    // NOTE: the unofficial spec says to use the 'files' key but i've seen 'host' in practice
    data = data.files || data.host || {}
    data = data[self.client._infoHash.toString('binary')]

    if (!data) {
      self.client.emit('warning', new Error('invalid scrape response'))
    } else {
      // TODO: optionally handle data.flags.min_request_interval (separate from announce interval)
      self.client.emit('scrape', {
        announce: self._announceUrl,
        complete: data.complete,
        incomplete: data.incomplete,
        downloaded: data.downloaded
      })
    }
  }
}

//
// HELPERS
//

function toUInt16 (n) {
  var buf = new Buffer(2)
  buf.writeUInt16BE(n, 0)
  return buf
}

var MAX_UINT = 4294967295

function toUInt64 (n) {
  if (n > MAX_UINT || typeof n === 'string') {
    var bytes = new BN(n).toArray()
    while (bytes.length < 8) {
      bytes.unshift(0)
    }
    return new Buffer(bytes)
  }
  return Buffer.concat([common.toUInt32(0), common.toUInt32(n)])
}
<commit_msg>default numwant to 50 since that's the server default<commit_after>module.exports = Client

var BN = require('bn.js')
var bencode = require('bencode')
var common = require('./lib/common')
var compact2string = require('compact2string')
var concat = require('concat-stream')
var debug = require('debug')('bittorrent-tracker:client')
var dgram = require('dgram')
var EventEmitter = require('events').EventEmitter
var extend = require('extend.js')
var hat = require('hat')
var http = require('http')
var inherits = require('inherits')
var querystring = require('querystring')
var url = require('url')

inherits(Client, EventEmitter)

/**
 * A Client manages tracker connections for a torrent.
 *
 * @param {string} peerId  this peer's id
 * @param {Number} port    port number that the client is listening on
 * @param {Object} torrent parsed torrent
 * @param {Object} opts    optional options
 * @param {Number} opts.numWant    number of peers to request
 * @param {Number} opts.interval   interval in ms to send announce requests to the tracker
 */
function Client (peerId, port, torrent, opts) {

  var self = this
  if (!(self instanceof Client)) return new Client(peerId, port, torrent, opts)
  EventEmitter.call(self)
  self._opts = opts || {}

  // required
  self._peerId = Buffer.isBuffer(peerId)
    ? peerId
    : new Buffer(peerId, 'utf8')
  self._port = port
  self._infoHash = Buffer.isBuffer(torrent.infoHash)
    ? torrent.infoHash
    : new Buffer(torrent.infoHash, 'hex')
  self.torrentLength = torrent.length

  // optional
  self._numWant = self._opts.numWant || 50
  self._intervalMs = self._opts.interval || (30 * 60 * 1000) // default: 30 minutes

  debug('new tracker client for ' + self._infoHash.toString('hex'))

  if (typeof torrent.announce === 'string') {
    // magnet-uri returns a string if the magnet uri only contains one 'tr' parameter
    torrent.announce = [torrent.announce]
  }
  self._trackers = torrent.announce.map(function (announceUrl) {
    return new Tracker(self, announceUrl, self._opts)
  })
}

Client.prototype.start = function (opts) {
  var self = this
  self._trackers.forEach(function (tracker) {
    tracker.start(opts)
  })
}

Client.prototype.stop = function (opts) {
  var self = this
  self._trackers.forEach(function (tracker) {
    tracker.stop(opts)
  })
}

Client.prototype.complete = function (opts) {
  var self = this
  self._trackers.forEach(function (tracker) {
    tracker.complete(opts)
  })
}

Client.prototype.update = function (opts) {
  var self = this
  self._trackers.forEach(function (tracker) {
    tracker.update(opts)
  })
}

Client.prototype.scrape = function (opts) {
  var self = this
  self._trackers.forEach(function (tracker) {
    tracker.scrape(opts)
  })
}

Client.prototype.setInterval = function (intervalMs) {
  var self = this
  self._intervalMs = intervalMs

  self._trackers.forEach(function (tracker) {
    tracker.setInterval(intervalMs)
  })
}

inherits(Tracker, EventEmitter)

/**
 * An individual torrent tracker (used by Client)
 *
 * @param {Client} client       parent bittorrent tracker client
 * @param {string} announceUrl  announce url of tracker
 * @param {Object} opts         optional options
 */
function Tracker (client, announceUrl, opts) {
  var self = this
  EventEmitter.call(self)
  self._opts = opts || {}

  self.client = client

  debug('new tracker for ' + announceUrl)

  self._announceUrl = announceUrl
  self._intervalMs = self.client._intervalMs // use client interval initially
  self._interval = null

  if (self._announceUrl.indexOf('udp:') === 0) {
    self._requestImpl = self._requestUdp
  } else {
    self._requestImpl = self._requestHttp
  }

}

Tracker.prototype.start = function (opts) {
  var self = this
  opts = opts || {}
  opts.event = 'started'

  debug('sent `start` to ' + self._announceUrl)
  self._request(opts)
  self.setInterval(self._intervalMs) // start announcing on intervals
}

Tracker.prototype.stop = function (opts) {
  var self = this
  opts = opts || {}
  opts.event = 'stopped'

  debug('sent `stop` to ' + self._announceUrl)
  self._request(opts)
  self.setInterval(0) // stop announcing on intervals
}

Tracker.prototype.complete = function (opts) {
  var self = this
  opts = opts || {}
  opts.event = 'completed'
  opts.downloaded = opts.downloaded || self.torrentLength || 0

  debug('sent `complete` to ' + self._announceUrl)
  self._request(opts)
}

Tracker.prototype.update = function (opts) {
  var self = this
  opts = opts || {}

  debug('sent `update` to ' + self._announceUrl)
  self._request(opts)
}

Tracker.prototype.scrape = function (opts) {
  var self = this

  if (!self._scrapeUrl) {
    var announce = 'announce'
    var i = self._announceUrl.lastIndexOf('/') + 1

    if (i >= 1 && self._announceUrl.slice(i, i + announce.length) === announce) {
      self._scrapeUrl = self._announceUrl.slice(0, i) + 'scrape' + self._announceUrl.slice(i + announce.length)
    }
  }

  if (!self._scrapeUrl) {
    debug('scrape not supported by ' + self._announceUrl)
    self.client.emit('error', new Error('scrape not supported for announceUrl ' + self._announceUrl))
    return
  }

  debug('sent `scrape` to ' + self._announceUrl)

  opts = extend({
    info_hash: common.bytewiseEncodeURIComponent(self.client._infoHash)
  }, opts)

  self._requestImpl(self._scrapeUrl, opts)
}

Tracker.prototype.setInterval = function (intervalMs) {
  var self = this
  clearInterval(self._interval)

  self._intervalMs = intervalMs
  if (intervalMs) {
    self._interval = setInterval(self.update.bind(self), self._intervalMs)
  }
}

/**
 * Send an announce request to the tracker
 */
Tracker.prototype._request = function (opts) {
  var self = this
  opts = extend({
    info_hash: common.bytewiseEncodeURIComponent(self.client._infoHash),
    peer_id: common.bytewiseEncodeURIComponent(self.client._peerId),
    port: self.client._port,
    compact: 1,
    numwant: self.client._numWant,
    uploaded: 0, // default, user should provide real value
    downloaded: 0 // default, user should provide real value
  }, opts)

  if (self.client.torrentLength !== undefined) {
    opts.left = self.client.torrentLength - (opts.downloaded || 0)
  }

  if (self._trackerId) {
    opts.trackerid = self._trackerId
  }

  self._requestImpl(self._announceUrl, opts)
}

Tracker.prototype._requestHttp = function (requestUrl, opts) {
  var self = this
  var fullUrl = requestUrl + '?' + querystring.stringify(opts)

  var req = http.get(fullUrl, function (res) {
    if (res.statusCode !== 200) {
      res.resume() // consume the whole stream
      self.client.emit('warning', new Error('Invalid response code ' + res.statusCode + ' from tracker ' + requestUrl))
      return
    }
    res.pipe(concat(function (data) {
      if (data && data.length) self._handleResponse(requestUrl, data)
    }))
  })

  req.on('error', function (err) {
    self.client.emit('warning', err)
  })
}

Tracker.prototype._requestUdp = function (requestUrl, opts) {
  var self = this
  var parsedUrl = url.parse(requestUrl)
  var socket = dgram.createSocket('udp4')
  var transactionId = new Buffer(hat(32), 'hex')

  var stopped = opts.event === 'stopped'
  // if we're sending a stopped message, we don't really care if it arrives, so set
  // a short timer and don't call error
  var timeout = setTimeout(function () {
    timeout = null
    cleanup()
    if (!stopped) {
      error('tracker request timed out')
    }
  }, stopped ? 1500 : 15000)

  if (timeout && timeout.unref) {
    timeout.unref()
  }

  send(Buffer.concat([
    common.CONNECTION_ID,
    common.toUInt32(common.ACTIONS.CONNECT),
    transactionId
  ]))

  socket.on('error', error)

  socket.on('message', function (msg, rinfo) {
    if (msg.length < 8 || msg.readUInt32BE(4) !== transactionId.readUInt32BE(0)) {
      return error('tracker sent back invalid transaction id')
    }

    var action = msg.readUInt32BE(0)
    switch (action) {
      case 0: // handshake
        if (msg.length < 16) {
          return error('invalid udp handshake')
        }

        var scrapeStr = 'scrape'
        if (requestUrl.substr(requestUrl.lastIndexOf('/') + 1, scrapeStr.length) === scrapeStr) {
          scrape(msg.slice(8, 16), opts)
        } else {
          announce(msg.slice(8, 16), opts)
        }

        return

      case 1: // announce
        cleanup()
        if (msg.length < 20) {
          return error('invalid announce message')
        }

        var interval = msg.readUInt32BE(8)
        if (interval && !self._opts.interval && self._intervalMs !== 0) {
          // use the interval the tracker recommends, UNLESS the user manually specifies an
          // interval they want to use
          self.setInterval(interval * 1000)
        }

        self.client.emit('update', {
          announce: self._announceUrl,
          complete: msg.readUInt32BE(16),
          incomplete: msg.readUInt32BE(12)
        })

        compact2string.multi(msg.slice(20)).forEach(function (addr) {
          self.client.emit('peer', addr)
        })
        break

      case 2: // scrape
        cleanup()
        if (msg.length < 20) {
          return error('invalid scrape message')
        }
        self.client.emit('scrape', {
          announce: self._announceUrl,
          complete: msg.readUInt32BE(8),
          downloaded: msg.readUInt32BE(12),
          incomplete: msg.readUInt32BE(16)
        })
        break

      case 3: // error
        cleanup()
        if (msg.length < 8) {
          return error('invalid error message')
        }
        self.client.emit('error', new Error(msg.slice(8).toString()))
        break
    }
  })

  function send (message) {
    if (!parsedUrl.port) {
      parsedUrl.port = 80
    }
    socket.send(message, 0, message.length, parsedUrl.port, parsedUrl.hostname)
  }

  function error (message) {
    self.client.emit('error', new Error(message + ' (connecting to tracker ' + requestUrl + ')'))
    cleanup()
  }

  function cleanup () {
    if (timeout) {
      clearTimeout(timeout)
      timeout = null
    }
    try { socket.close() } catch (err) {}
  }

  function genTransactionId () {
    transactionId = new Buffer(hat(32), 'hex')
  }

  function announce (connectionId, opts) {
    opts = opts || {}
    genTransactionId()

    send(Buffer.concat([
      connectionId,
      common.toUInt32(common.ACTIONS.ANNOUNCE),
      transactionId,
      self.client._infoHash,
      self.client._peerId,
      toUInt64(opts.downloaded || 0),
      opts.left ? toUInt64(opts.left) : new Buffer('FFFFFFFFFFFFFFFF', 'hex'),
      toUInt64(opts.uploaded || 0),
      common.toUInt32(common.EVENTS[opts.event] || 0),
      common.toUInt32(0), // ip address (optional)
      common.toUInt32(0), // key (optional)
      common.toUInt32(self.client._numWant),
      toUInt16(self.client._port || 0)
    ]))
  }

  function scrape (connectionId, opts) {
    genTransactionId()

    send(Buffer.concat([
      connectionId,
      common.toUInt32(common.ACTIONS.SCRAPE),
      transactionId,
      self.client._infoHash
    ]))
  }
}

Tracker.prototype._handleResponse = function (requestUrl, data) {
  var self = this

  try {
    data = bencode.decode(data)
  } catch (err) {
    return self.client.emit('error', new Error('Error decoding tracker response: ' + err.message))
  }
  var failure = data['failure reason']
  if (failure) {
    return self.client.emit('warning', new Error(failure))
  }

  var warning = data['warning message']
  if (warning) {
    self.client.emit('warning', new Error(warning))
  }

  if (requestUrl === self._announceUrl) {
    var interval = data.interval || data['min interval']
    if (interval && !self._opts.interval && self._intervalMs !== 0) {
      // use the interval the tracker recommends, UNLESS the user manually specifies an
      // interval they want to use
      self.setInterval(interval * 1000)
    }

    var trackerId = data['tracker id']
    if (trackerId) {
      // If absent, do not discard previous trackerId value
      self._trackerId = trackerId
    }

    self.client.emit('update', {
      announce: self._announceUrl,
      complete: data.complete,
      incomplete: data.incomplete
    })

    if (Buffer.isBuffer(data.peers)) {
      // tracker returned compact response
      compact2string.multi(data.peers).forEach(function (addr) {
        self.client.emit('peer', addr)
      })
    } else if (Array.isArray(data.peers)) {
      // tracker returned normal response
      data.peers.forEach(function (peer) {
        var ip = peer.ip
        var addr = ip[0] + '.' + ip[1] + '.' + ip[2] + '.' + ip[3] + ':' + peer.port
        self.client.emit('peer', addr)
      })
    }
  } else if (requestUrl === self._scrapeUrl) {
    // NOTE: the unofficial spec says to use the 'files' key but i've seen 'host' in practice
    data = data.files || data.host || {}
    data = data[self.client._infoHash.toString('binary')]

    if (!data) {
      self.client.emit('warning', new Error('invalid scrape response'))
    } else {
      // TODO: optionally handle data.flags.min_request_interval (separate from announce interval)
      self.client.emit('scrape', {
        announce: self._announceUrl,
        complete: data.complete,
        incomplete: data.incomplete,
        downloaded: data.downloaded
      })
    }
  }
}

//
// HELPERS
//

function toUInt16 (n) {
  var buf = new Buffer(2)
  buf.writeUInt16BE(n, 0)
  return buf
}

var MAX_UINT = 4294967295

function toUInt64 (n) {
  if (n > MAX_UINT || typeof n === 'string') {
    var bytes = new BN(n).toArray()
    while (bytes.length < 8) {
      bytes.unshift(0)
    }
    return new Buffer(bytes)
  }
  return Buffer.concat([common.toUInt32(0), common.toUInt32(n)])
}
<|endoftext|>


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

  function SearchPanelController(SitesService, PointcloudService, Messagebus) {
    this.pageSize = 2;
    this.currentPage = 1;
    this.SitesService = SitesService;
    this.Messagebus = Messagebus;

    var _currentSite = null;
    Object.defineProperty(this, 'currentSite', {
      get: function() {
        return _currentSite;
      },
      set: function(site) {
        _currentSite = site;
        this.toggleButtons.sitePc = PointcloudService.sitePointcloudId === site.id;
      },
      enumerable: true,
      configurable: true
    });

    this.disabledButtons = {
      sitePc: true,
      siteMesh: true
    };

    this.toggleButtons = {
      _sitePc: false,
      _driveMap: true,
      _siteMesh: false
    };

    Object.defineProperties(this.toggleButtons, {
      sitePc: {
        get: function() {
          return this._sitePc;
        },
        set: function(bool) {
          this._sitePc = bool;
          if(bool){
            PointcloudService.loadSite(_currentSite);
          } else {
            PointcloudService.removeSitePointcloud();
          }
        },
        enumerable: true,
        configurable: true
      },
      driveMap: {
        get: function() {
          return this._driveMap;
        },
        set: function(bool) {
          this._driveMap = bool;
          if(bool){
            console.log('show drive map');
          } else {
            console.log('hide dive map');
          }
        },
        enumerable: true,
        configurable: true
      },
      siteMesh: {
        get: function() {
          return this._siteMesh;
        },
        set: function(bool) {
          this._siteMesh = bool;
          if(bool){
            console.log('show site mesh');
          } else {
            console.log('hide site mesh');
          }
        },
        enumerable: true,
        configurable: true
      }
    });

    this.reconstruction_mesh = 0;

    Messagebus.subscribe('singleSite', function(event, site){
        this.currentSite = site;
        this.disabledButtons.sitePc = !('pointcloud' in site);
        //this.disabledButtons.siteMesh = !('mesh' in site);
    }.bind(this));

    this.lookAtSite = function(site) {
      PointcloudService.lookAtSite(site);
    };
    this.showLabel = function(site) {
      PointcloudService.showLabel(site);
    };
    this.enterOrbitMode = function(site) {
      PointcloudService.enterOrbitMode('event', site);
    };
    /**
     * jump back to first page when query changes
     */
    this.onQueryChange = function() {
      this.currentPage = 1;
    };

  }

  angular.module('pattyApp.searchbox')
    .controller('SearchPanelController', SearchPanelController);
})();
<commit_msg>Add show and hide drive map functionality<commit_after>(function() {
  'use strict';

  function SearchPanelController(SitesService, PointcloudService, Messagebus) {
    this.pageSize = 2;
    this.currentPage = 1;
    this.SitesService = SitesService;
    this.Messagebus = Messagebus;

    var _currentSite = null;
    Object.defineProperty(this, 'currentSite', {
      get: function() {
        return _currentSite;
      },
      set: function(site) {
        _currentSite = site;
        this.toggleButtons.sitePc = PointcloudService.sitePointcloudId === site.id;
      },
      enumerable: true,
      configurable: true
    });

    this.disabledButtons = {
      sitePc: true,
      siteMesh: true
    };

    this.toggleButtons = {
      _sitePc: false,
      _driveMap: true,
      _siteMesh: false
    };

    Object.defineProperties(this.toggleButtons, {
      sitePc: {
        get: function() {
          return this._sitePc;
        },
        set: function(bool) {
          this._sitePc = bool;
          if(bool){
            PointcloudService.loadSite(_currentSite);
          } else {
            PointcloudService.removeSitePointcloud();
          }
        },
        enumerable: true,
        configurable: true
      },
      driveMap: {
        get: function() {
          return this._driveMap;
        },
        set: function(bool) {
          this._driveMap = bool;
          if(bool){
            PointcloudService.enableDrivemap();
          } else {
            PointcloudService.disableDrivemap();
          }
        },
        enumerable: true,
        configurable: true
      },
      siteMesh: {
        get: function() {
          return this._siteMesh;
        },
        set: function(bool) {
          this._siteMesh = bool;
          if(bool){
            console.log('show site mesh');
          } else {
            console.log('hide site mesh');
          }
        },
        enumerable: true,
        configurable: true
      }
    });

    this.reconstruction_mesh = 0;

    Messagebus.subscribe('singleSite', function(event, site){
        this.currentSite = site;
        this.disabledButtons.sitePc = !('pointcloud' in site);
        //this.disabledButtons.siteMesh = !('mesh' in site);
    }.bind(this));

    this.lookAtSite = function(site) {
      PointcloudService.lookAtSite(site);
    };
    this.showLabel = function(site) {
      PointcloudService.showLabel(site);
    };
    this.enterOrbitMode = function(site) {
      PointcloudService.enterOrbitMode('event', site);
    };
    /**
     * jump back to first page when query changes
     */
    this.onQueryChange = function() {
      this.currentPage = 1;
    };

  }

  angular.module('pattyApp.searchbox')
    .controller('SearchPanelController', SearchPanelController);
})();
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2020 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import React, {useState} from 'react'
import ReactDOM from 'react-dom'
import I18n from 'i18n!groups'
import CanvasModal from 'jsx/shared/components/CanvasModal'
import {FileDrop} from '@instructure/ui-forms'
import {Billboard} from '@instructure/ui-billboard'
import {Link} from '@instructure/ui-elements'
import {View} from '@instructure/ui-layout'
import {PresentationContent} from '@instructure/ui-a11y'
import SVGWrapper from 'jsx/shared/SVGWrapper'
import {showFlashAlert} from 'jsx/shared/FlashAlert'
import * as apiClient from '../apiClient'

export default function ImportGroupsModal(props) {
  const [messages, setMessages] = useState([])

  const hide = () => {
    if (props.parent) ReactDOM.unmountComponentAtNode(props.parent)
  }

  const beginUpload = file => {
    if (file !== null) {
      apiClient
        .createImport(props.groupCategoryId, file)
        .then(resp => {
          props.setProgress(resp.data)
        })
        .catch(() => {
          showFlashAlert({
            type: 'error',
            message: I18n.t('There was an error uploading your file. Please try again.')
          })
        })
    }
  }

  const onSelection = (accepted, rejected) => {
    if (accepted.length > 0) {
      beginUpload(accepted[0])
      hide()
    } else if (rejected.length > 0) {
      setMessages([{text: I18n.t('Invalid file type'), type: 'error'}])
    }
  }

  const styles = {
    width: '10rem',
    margin: '0 auto'
  }

  return (
    <CanvasModal size="fullscreen" label={I18n.t('Import Groups')} open onDismiss={hide}>
      <FileDrop
        accept=".csv"
        onDrop={(acceptedFile, rejectedFile) => onSelection(acceptedFile, rejectedFile)}
        messages={messages}
        label={

            <Billboard
              size="medium"
              heading={I18n.t('Upload CSV File')}
              headingLevel="h2"
              message={I18n.t('Drag and drop or click to browse your computer')}
              hero={

                  <PresentationContent>
                    <SVGWrapper url="/images/upload_rocket.svg" />
                  </PresentationContent>

              }
            />

        }
      />
      <View as="div" margin="large auto" textAlign="center">
        <Link href="#">{I18n.t('Download Course Roster CSV')}</Link>
      </View>
      <View as="div" margin="large auto" textAlign="center">
        <Link href="#">{I18n.t('Group Import API Documentation')}</Link>
      </View>
    </CanvasModal>
  )
}
<commit_msg>hook up group csv import modal links<commit_after>/*
 * Copyright (C) 2020 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import React, {useState} from 'react'
import ReactDOM from 'react-dom'
import I18n from 'i18n!groups'
import CanvasModal from 'jsx/shared/components/CanvasModal'
import {FileDrop} from '@instructure/ui-forms'
import {Billboard} from '@instructure/ui-billboard'
import {Link} from '@instructure/ui-elements'
import {View} from '@instructure/ui-layout'
import {PresentationContent} from '@instructure/ui-a11y'
import SVGWrapper from 'jsx/shared/SVGWrapper'
import {showFlashAlert} from 'jsx/shared/FlashAlert'
import * as apiClient from '../apiClient'

export default function ImportGroupsModal(props) {
  const [messages, setMessages] = useState([])

  const hide = () => {
    if (props.parent) ReactDOM.unmountComponentAtNode(props.parent)
  }

  const beginUpload = file => {
    if (file !== null) {
      apiClient
        .createImport(props.groupCategoryId, file)
        .then(resp => {
          props.setProgress(resp.data)
        })
        .catch(() => {
          showFlashAlert({
            type: 'error',
            message: I18n.t('There was an error uploading your file. Please try again.')
          })
        })
    }
  }

  const onSelection = (accepted, rejected) => {
    if (accepted.length > 0) {
      beginUpload(accepted[0])
      hide()
    } else if (rejected.length > 0) {
      setMessages([{text: I18n.t('Invalid file type'), type: 'error'}])
    }
  }

  const styles = {
    width: '10rem',
    margin: '0 auto'
  }

  return (
    <CanvasModal size="fullscreen" label={I18n.t('Import Groups')} open onDismiss={hide}>
      <FileDrop
        accept=".csv"
        onDrop={(acceptedFile, rejectedFile) => onSelection(acceptedFile, rejectedFile)}
        messages={messages}
        label={

            <Billboard
              size="medium"
              heading={I18n.t('Upload CSV File')}
              headingLevel="h2"
              message={I18n.t('Drag and drop or click to browse your computer')}
              hero={

                  <PresentationContent>
                    <SVGWrapper url="/images/upload_rocket.svg" />
                  </PresentationContent>

              }
            />

        }
      />
      <View as="div" margin="large auto" textAlign="center">
        <Link href={`/api/v1/group_categories/${props.groupCategoryId}/export`}>
          {I18n.t('Download Course Roster CSV')}
        </Link>
      </View>
      <View as="div" margin="large auto" textAlign="center">
        <Link href="/doc/api/file.group_category_csv.html" target="_blank">
          {I18n.t('Group Import API Documentation')}
        </Link>
      </View>
    </CanvasModal>
  )
}
<|endoftext|>


<commit_before>'use strict'

const express = require('express')
const Slapp = require('slapp')
const ConvoStore = require('slapp-convo-beepboop')
const Context = require('slapp-context-beepboop')

// use `PORT` env var on Beep Boop - default to 3000 locally
var port = process.env.PORT || 3000

var slapp = Slapp({
  // Beep Boop sets the SLACK_VERIFY_TOKEN env var
  verify_token: process.env.SLACK_VERIFY_TOKEN,
  convo_store: ConvoStore(),
  context: Context(),
  log: true
})

var HELP_TEXT = `
I will respond to the following messages:
\`help\` - to see this message.
\`hi\` - to demonstrate a conversation that tracks state.
\`thanks\` - to demonstrate a simple response.
\`<type-any-other-text>\` - to demonstrate a random emoticon response, some of the time :wink:.
\`attachment\` - to see a Slack attachment message.
`

//*********************************************
// Setup different handlers for messages
//*********************************************

// response to the user typing "help"
slapp.message('help', ['mention', 'direct_message'], (msg) => {
  msg.say(HELP_TEXT)
})

// "Conversation" flow that tracks state - kicks off when user says hi, hello or hey
slapp
  .message('^(hi|hello|hey)$', ['direct_mention', 'direct_message'], (msg, text) => {
    msg
      .say(`${text}, how are you?`)
      // sends next event from user to this route, passing along state
      .route('how-are-you', { greeting: text })
  })
  .route('how-are-you', (msg, state) => {
    var text = (msg.body.event && msg.body.event.text) || ''

    // user may not have typed text as their next action, ask again and re-route
    if (!text) {
      return msg
        .say("Whoops, I'm still waiting to hear how you're doing.")
        .say('How are you?')
        .route('how-are-you', state)
    }

    // add their response to state
    state.status = text

    msg
      .say(`Ok then. What's your favorite color?`)
      .route('color', state)
  })
  .route('color', (msg, state) => {
    var text = (msg.body.event && msg.body.event.text) || ''

    // user may not have typed text as their next action, ask again and re-route
    if (!text) {
      return msg
        .say("I'm eagerly awaiting to hear your favorite color.")
        .route('color', state)
    }

    // add their response to state
    state.color = text

    msg
      .say('Thanks for sharing.')
      .say(`Here's what you've told me so far: \`\`\`${JSON.stringify(state)}\`\`\``)
    // At this point, since we don't route anywhere, the "conversation" is over
  })

// Can use a regex as well
slapp.message(/^(thanks|thank you)/i, ['mention', 'direct_message'], (msg) => {
  // You can provide a list of responses, and a random one will be chosen
  // You can also include slack emoji in your responses
  msg.say([
    "You're welcome :smile:",
    'You bet',
    ':+1: Of course',
    'Anytime :sun_with_face: :full_moon_with_face:'
  ])
})

// demonstrate returning an attachment...
slapp.message('attachment', ['mention', 'direct_message'], (msg) => {
  msg.say({
    text: 'Check out this amazing attachment! :confetti_ball: ',
    attachments: [{
      text: 'Slapp is a robust open source library that sits on top of the Slack APIs',
      title: 'Slapp Library - Open Source',
      image_url: 'https://storage.googleapis.com/beepboophq/_assets/bot-1.22f6fb.png',
      title_link: 'https://beepboophq.com/',
      color: '#7CD197'
    }]
  })
})

// Catch-all for any other responses not handled above
slapp.message('.*', ['direct_mention', 'direct_message'], (msg) => {
  // respond only 40% of the time
  if (Math.random() < 0.4) {
    msg.say([':wave:', ':pray:', ':raised_hands:'])
  }
})

// attach Slapp to express server
var server = slapp.attachToExpress(express())

// start http server
server.listen(port, (err) => {
  if (err) {
    return console.error(err)
  }

  console.log(`Listening on port ${port}`)
})
<commit_msg>Simplify bot for testing<commit_after>'use strict'

const express = require('express')
const Slapp = require('slapp')
const Context = require('slapp-context-beepboop')

var port = process.env.PORT || 3000

var slapp = Slapp({
  //verify_token: process.env.SLACK_VERIFY_TOKEN,
  //convo_store: ConvoStore(),
  context: Context(),
  log: true
})

slapp.message('^(hi|hello|hey).*', ['direct_mention', 'direct_message'], (msg, text, greeting) => {
  msg
    .say(`${greeting}, how are you?`)
    .route('handleHowAreYou')
})

slapp.route('handleHowAreYou', (msg) => {
  msg.say(['Me too', 'Noted', 'That is interesting'])
})

slapp.message('.*', ['direct_mention', 'direct_message'], (msg) => {
  if (Math.random() < 0.4) {
    msg.say([':wave:', ':pray:', ':raised_hands:'])
  }
})

// Start
var server = slapp.attachToExpress(express())
server.listen(port, (err) => {
  if (err) { return console.error(err) }
  console.log(`Listening on port ${port}`)
})
<|endoftext|>