<commit_before>
import moment from "moment"

const PRIVATE = Object.freeze({
  // fields
  apiClient: Symbol("apiClient"),

  // methods
  listAll: Symbol("listAll")
})

export default class YouTubeApiClient {
  constructor(apiClient) {
    this[PRIVATE.apiClient] = apiClient
  }

  /**
   * accountId = null requires authorization
   *
   * @param {?string} accountId
   * @return {Promise<?{id: string, title: string, playlistIds: {watchHistory: ?string, watchLater: ?string}}>}
   */
  getAccountInfo(accountId = null) {
    let parameters = {
      part: "id,snippet,contentDetails",
      fields: "items(id,snippet/title," +
          "contentDetails/relatedPlaylists(watchHistory,watchLater))"
    }
    if (accountId) {
      parameters.id = accountId
    } else {
      parameters.mine = true
    }

    return this[PRIVATE.apiClient].list(
      "channels",
      parameters,
      !accountId
    ).then((response) => {
      if (!response.items.length) {
        return null
      }

      let account = response.items[0]
      let result = {
        id: account.id,
        title: account.snippet.title,
        playlistIds: {
          watchHistory: null,
          watchLater: null
        }
      }
      if (account.contentDetails && account.contentDetails.relatedPlaylists) {
        let playlists = account.contentDetails.relatedPlaylists
        result.playlistIds.watchHistory = playlists.watchHistory || null
        result.playlistIds.watchLater = playlists.watchLater || null
      }

      return result
    })
  }

  /**
   * Require authorization for accessing user's watch history and watch later
   * playlist.
   *
   * @param {string} accountId
   * @param {boolean} authorized
   * @return {Promise<{id: string, title: string, videoCount: number, thumbnails: Object<string, string>}[]>}
   */
  getSubscribedChannels(accountId, authorized) {
    // TODO: paging (response.nextPageToken: string=), post-processing
    return this[PRIVATE.apiClient].list("subscriptions", {
      part: "snippet,contentDetails",
      channelId: accountId,
      maxResults: 50,
      fields: "pageInfo,nextPageToken," +
          "items(snippet(title,resourceId,thumbnails)," +
          "contentDetails/totalItemCount)"
    }, authorized)
    // parameters.pageToken = response.nextPageToken
  }

  /**
   * @param {string} channelId
   * @return {Promise<?string>}
   */
  getUploadsPlaylistId(channelId) {
    return this[PRIVATE.apiClient].list("channels", {
      part: "contentDetails",
      id: channelId,
      fields: "items/contentDetails/relatedPlaylists/uploads"
    }).then((response) => {
      if (response.items.length) {
        return null
      }

      let channelInfo = response.items[0]
      return channelInfo.contentDetails.relatedPlaylists.uploads
    })
  }

  /**
   * @param {?string} playlistId
   * @return {Promise<?{id: string, title: string, description: string, videoCount: number, thumbnails: Object<string, {url: string, width: number, height: number}>}>}
   */
  getPlaylistInfo(playlistId) {
    return this[PRIVATE.apiClient].list("playlists", {
      part: "snippet,contentDetails",
      id: playlistId,
      fields: "items(id,snippet(title,description,thumbnails),contentDetails)"
    }).then((response) => {
      if (!response.items.length) {
        return null
      }

      let playlist = response.items[0]
      return {
        id: playlist.id,
        title: playlist.snippet.title,
        description: playlist.snippet.description,
        videoCount: playlist.contentDetails.itemCount,
        thumbnails: playlist.snippet.thumbnails
      }
    })
  }

  /**
   * @param {string} playlistId
   * @return {Promise<{id: number, title: string, description: string, publishedAt: Date, thumbnails: Object<string, {url: string, width: number, height: number}>}[]>}
   */
  getPlaylistVideos(playlistId) {
    return this[PRIVATE.listAll]("playlistItems", {
      part: "snippet,contentDetails",
      maxResults: 50,
      playlistId,
      fields: "pageInfo,nextPageToken,items/snippet(publishedAt,title," +
      "description,thumbnails,resourceId/videoId)"
    }).then((items) => {
      return items.map((item) => {
        return {
          id: item.resourceId.videoId,
          title: item.snippet.title,
          description: item.snippet.description,
          publishedAt: new Date(item.snippet.publishedAt),
          thumbnails: item.snippet.thumbnails
        }
      })
    })
  }

  /**
   * @param {string} videoId
   * @return {Promise<{id: number, duration: number, viewCount: number}>}
   */
  getVideoMetaData(videoId) {
    return this[PRIVATE.apiClient].list("videos", {
      part: "contentDetails,statistics",
      id: videoId,
      fields: "items(id,contentDetails/duration,statistics/viewCount)"
    }).then((response) => {
      if (!response.items.length) {
        return null
      }

      let video = response.items[0]
      return {
        id: video.id,
        duration: moment.duration(video.contentDetails.duration).asSeconds(),
        viewCount: video.statistics.viewCount
      }
    })
  }

  /**
   * Requires authorization.
   *
   * @param {string} playlistId
   * @param {string} videoId
   * @return {Promise<undefined>}
   */
  addPlaylistItem(playlistId, videoId) {
    return this[PRIVATE.apiClient].insert("playlistItems?part=snippet", {
      snippet: {
        playlistId: playlistId,
        resourceId: {
          kind: "youtube#video",
          videoId: videoId
        }
      }
    }, true).then(() => undefined)
  }

  /**
   * Fetches all pages of items from the REST API that are matching the
   * specified parameters.
   *
   * @param {string} path The path within the REST API denoting the resource or
   *        entity to query.
   * @param {Object<string, ?(boolean|number|string)>} parameters Parameters
   *        restricting the result to return.
   * @param {function(Object[]): boolean} continuationPredicate A callback
   *        executed after each call to the REST API. The callback will receive
   *        the items fetched in the last REST API call and should return
   *        either {@code true} if the method should fetch the next page, or
   *        {@code false} if no additional pages need to be fetched.
   * @param {boolean=} authorized Flag signalling whether the request should be
   *        sent as authorized by user (requires OAuth2 token) or not. Some
   *        REST resources will require authorization.
   * @return {Promise<Object[]>} A promise that will resolve to the items
   *         fetched from the REST API.
   */
  [PRIVATE.listAll](path, parameters, continuationPredicate = () => true,
      authorized = false) {
    let items = []

    return fetchNextPage()

    function fetchNextPage(pageToken) {
      let fetchParams = Object.assign({}, parameters)
      if (pageToken) {
        fetchParams.pageToken = pageToken
      }

      return this[PRIVATE.apiClient].list(
        path,
        fetchParams,
        authorized
      ).then((response) => {
        if (response.items) {
          items = items.concat(response.items)
        }

        if (response.nextPageToken && continuationPredicate(response.items)) {
          return fetchNextPage(response.nextPageToken)
        } else {
          return items
        }
      })
    }
  }
}
<commit_msg>planned further methods<commit_after>
import moment from "moment"

const PRIVATE = Object.freeze({
  // fields
  apiClient: Symbol("apiClient"),

  // methods
  listAll: Symbol("listAll")
})

export default class YouTubeApiClient {
  constructor(apiClient) {
    this[PRIVATE.apiClient] = apiClient
  }

  /**
   * accountId = null requires authorization
   *
   * @param {?string} accountId
   * @return {Promise<?{id: string, title: string, playlistIds: {watchHistory: ?string, watchLater: ?string}}>}
   */
  getAccountInfo(accountId = null) {
    let parameters = {
      part: "id,snippet,contentDetails",
      fields: "items(id,snippet/title," +
          "contentDetails/relatedPlaylists(watchHistory,watchLater))"
    }
    if (accountId) {
      parameters.id = accountId
    } else {
      parameters.mine = true
    }

    return this[PRIVATE.apiClient].list(
      "channels",
      parameters,
      !accountId
    ).then((response) => {
      if (!response.items.length) {
        return null
      }

      let account = response.items[0]
      let result = {
        id: account.id,
        title: account.snippet.title,
        playlistIds: {
          watchHistory: null,
          watchLater: null
        }
      }
      if (account.contentDetails && account.contentDetails.relatedPlaylists) {
        let playlists = account.contentDetails.relatedPlaylists
        result.playlistIds.watchHistory = playlists.watchHistory || null
        result.playlistIds.watchLater = playlists.watchLater || null
      }

      return result
    })
  }

  /**
   * Require authorization for accessing user's watch history and watch later
   * playlist.
   *
   * @param {string} accountId
   * @param {boolean} authorized
   * @return {Promise<{id: string, title: string, videoCount: number, thumbnails: Object<string, string>}[]>}
   */
  getSubscribedChannels(accountId, authorized) {
    // TODO: paging (response.nextPageToken: string=), post-processing
    return this[PRIVATE.apiClient].list("subscriptions", {
      part: "snippet,contentDetails",
      channelId: accountId,
      maxResults: 50,
      fields: "pageInfo,nextPageToken," +
          "items(snippet(title,resourceId,thumbnails)," +
          "contentDetails/totalItemCount)"
    }, authorized)
    // parameters.pageToken = response.nextPageToken
  }

  /**
   * @param {string} channelId
   * @return {Promise<?string>}
   */
  getUploadsPlaylistId(channelId) {
    return this[PRIVATE.apiClient].list("channels", {
      part: "contentDetails",
      id: channelId,
      fields: "items/contentDetails/relatedPlaylists/uploads"
    }).then((response) => {
      if (response.items.length) {
        return null
      }

      let channelInfo = response.items[0]
      return channelInfo.contentDetails.relatedPlaylists.uploads
    })
  }

  // TODO: getUploadsPlaylistIds(channelIds) {}

  /**
   * @param {?string} playlistId
   * @return {Promise<?{id: string, title: string, description: string, videoCount: number, thumbnails: Object<string, {url: string, width: number, height: number}>}>}
   */
  getPlaylistInfo(playlistId) {
    return this[PRIVATE.apiClient].list("playlists", {
      part: "snippet,contentDetails",
      id: playlistId,
      fields: "items(id,snippet(title,description,thumbnails),contentDetails)"
    }).then((response) => {
      if (!response.items.length) {
        return null
      }

      let playlist = response.items[0]
      return {
        id: playlist.id,
        title: playlist.snippet.title,
        description: playlist.snippet.description,
        videoCount: playlist.contentDetails.itemCount,
        thumbnails: playlist.snippet.thumbnails
      }
    })
  }

  // TODO: getPlaylistVideoCounts(playlistIds)

  /**
   * @param {string} playlistId
   * @return {Promise<{id: number, title: string, description: string, publishedAt: Date, thumbnails: Object<string, {url: string, width: number, height: number}>}[]>}
   */
  getPlaylistVideos(playlistId) {
    return this[PRIVATE.listAll]("playlistItems", {
      part: "snippet,contentDetails",
      maxResults: 50,
      playlistId,
      fields: "pageInfo,nextPageToken,items/snippet(publishedAt,title," +
      "description,thumbnails,resourceId/videoId)"
    }).then((items) => {
      return items.map((item) => {
        return {
          id: item.resourceId.videoId,
          title: item.snippet.title,
          description: item.snippet.description,
          publishedAt: new Date(item.snippet.publishedAt),
          thumbnails: item.snippet.thumbnails
        }
      })
    })
  }

  /**
   * @param {string} videoId
   * @return {Promise<{id: number, duration: number, viewCount: number}>}
   */
  getVideoMetaData(videoId) {
    return this[PRIVATE.apiClient].list("videos", {
      part: "contentDetails,statistics",
      id: videoId,
      fields: "items(id,contentDetails/duration,statistics/viewCount)"
    }).then((response) => {
      if (!response.items.length) {
        return null
      }

      let video = response.items[0]
      return {
        id: video.id,
        duration: moment.duration(video.contentDetails.duration).asSeconds(),
        viewCount: video.statistics.viewCount
      }
    })
  }

  // TODO: getVideoMetaData(videoIds)

  /**
   * Requires authorization.
   *
   * @param {string} playlistId
   * @param {string} videoId
   * @return {Promise<undefined>}
   */
  addPlaylistItem(playlistId, videoId) {
    return this[PRIVATE.apiClient].insert("playlistItems?part=snippet", {
      snippet: {
        playlistId: playlistId,
        resourceId: {
          kind: "youtube#video",
          videoId: videoId
        }
      }
    }, true).then(() => undefined)
  }

  /**
   * Fetches all pages of items from the REST API that are matching the
   * specified parameters.
   *
   * @param {string} path The path within the REST API denoting the resource or
   *        entity to query.
   * @param {Object<string, ?(boolean|number|string)>} parameters Parameters
   *        restricting the result to return.
   * @param {function(Object[]): boolean} continuationPredicate A callback
   *        executed after each call to the REST API. The callback will receive
   *        the items fetched in the last REST API call and should return
   *        either {@code true} if the method should fetch the next page, or
   *        {@code false} if no additional pages need to be fetched.
   * @param {boolean=} authorized Flag signalling whether the request should be
   *        sent as authorized by user (requires OAuth2 token) or not. Some
   *        REST resources will require authorization.
   * @return {Promise<Object[]>} A promise that will resolve to the items
   *         fetched from the REST API.
   */
  [PRIVATE.listAll](path, parameters, continuationPredicate = () => true,
      authorized = false) {
    let items = []

    return fetchNextPage()

    function fetchNextPage(pageToken) {
      let fetchParams = Object.assign({}, parameters)
      if (pageToken) {
        fetchParams.pageToken = pageToken
      }

      return this[PRIVATE.apiClient].list(
        path,
        fetchParams,
        authorized
      ).then((response) => {
        if (response.items) {
          items = items.concat(response.items)
        }

        if (response.nextPageToken && continuationPredicate(response.items)) {
          return fetchNextPage(response.nextPageToken)
        } else {
          return items
        }
      })
    }
  }
}
<|endoftext|>


<commit_before>/*global module:false*/
module.exports = function(grunt) {
    var browsers = [{
        browserName: "firefox",
        version: "19",
        platform: "XP"
    }, {
        browserName: "firefox",
        platform: "XP"
    }, {
        browserName: "chrome",
        platform: "XP"
    }, {
        browserName: "internet explorer",
        platform: "XP",
        version: "7.0"
    }, {
        browserName: "internet explorer",
        platform: "XP",
        version: "8.0"
    }, {
        browserName: "chrome",
        platform: "linux"
    }, {
        browserName: "firefox",
        platform: "linux"
    }, {
        browserName: "firefox",
        platform: "OS X 10.10"
    }, {
        browserName: "safari",
        platform: "OS X 10.10"
    }, {
        browserName: "chrome",
        platform: "OS X 10.10"
    }, {
        browserName: "internet explorer",
        platform: "Windows 8",
        version: "10.0"
    }, {
        browserName: "internet explorer",
        platform: "Windows 8.1",
        version: "11.0"
    }, {
        browserName: "internet explorer",
        platform: "Windows 7",
        version: "9.0"
        //}, {
        //browserName: "opera",
        //platform: "Windows 2008",
        //version: "12"
    }];

    var getVersion = function(version) {
        var data = {};
        var match, major, minor, patch;

        match = version.match(/^([0-9]+)\.([0-9]+)\.([0-9]+).*/);

        if (match) {
            data.major = match[1];
            data.minor = match[2];
            data.patch = match[3];
        }

        return data;
    };

    // Project configuration.
    grunt.config.init({
        CDN_URL: 'http://static.woopra.com',
        filename: 'w',
        pkg: grunt.file.readJSON('package.json'),
        options: {
            testFiles: ['<%= pkg.name %>.v<%= pkg.version %>.min.js'],
            specFiles: ['test/spec/*.js']
        },
        connect: {
            dev: {
                options: {
                    port: 4040,
                    hostname: '0.0.0.0',
                    keepalive: true
                }
            },
            test: {
                options: {
                    port: 4040,
                    hostname: '0.0.0.0'
                }
            },
        },

        meta: {
            banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' +
                '<%= grunt.template.today("yyyy-mm-dd") %>\\n' +
                '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' +
                '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' +
                ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */'
        },
        watch: {
            files: '<config:lint.files>',
            tasks: 'lint min',
            test: {
                files: ['<%= options.testFiles>', '<%= options.specFiles %>', 'test/TestRunner.html'],
                tasks: 'mocha'
            }
        },
        jshint: {
            options: {
                curly: true,
                eqeqeq: true,
                immed: true,
                latedef: true,
                newcap: true,
                noarg: true,
                sub: true,
                undef: true,
                boss: true,
                eqnull: true,
                browser: true,
                unused: true,
                globals: {
                    exports: true,
                    jQuery: true
                }
            },
            main: {
                files: [{
                    src: ['grunt.js', '<%= pkg.name %>.js']
                }]
            },
            tests: {
                options: {
                    globals: {
                        exports: true,
                        '$': true,
                        describe: true,
                        expect: true,
                        before: true,
                        beforeEach: true,
                        after: true,
                        afterEach: true,
                        sinon: true,
                        Woopra: true,
                        WoopraTracker: true,
                        it: true
                    }
                },
                files: [{
                    src: ['<%= options.specFiles %>']
                }]
            }
        },
        uglify: {
            options: {
                compressor: {
                    warnings: false
                },
                mangle: true,
                compress: true
            },
            main: {
                options: {
                    banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> */'
                },
                src: ['<%= pkg.name %>.js'],
                dest: '<%= pkg.name %>.min.js'
            },
            plugin: {
                src: ['jquery.woopra.js'],
                dest: 'jquery.woopra.min.js'
            },
            snippet: {
                src: ['snippet.js'],
                dest: 'snippet-min.js'
            }
        },
        upload: {
            tracker: {
                type: 'scp',
                host: 'woopra-cdn',
                cdnRoot: '/var/www/html',
                tasks: [{
                    src: [
                        '<%= uglify.main.dest %>'
                    ],
                    dest: '/js/<%= filename %>.js'
                }]
            },
            dev: {
                type: 'scp',
                host: 'woopra-cdn',
                cdnRoot: '/var/www/html',
                tasks: [{
                    src: [
                        '<%= uglify.main.dest %>'
                    ],
                    dest: '/js/t/<%= pkg.version %>.js'
                }]

            }
        },
        purge: {
            tracker: {
            },
            dev: {
            }
        },
        mocha: {
            tracker: {
                src: ['test/TestRunner.html'],
                options: {
                    mocha: {
                        globals: ['woopra', 'newTracker', 'pingTracker', 'w1', 'w2', 'w3'],
                        ignoreLeaks: false
                    },
                    reporter: 'Spec',
                    run: true,
                    log: true
                }
            },
            snippet: {
                src: ['test/snippet.html'],
                options: {
                    mocha: {
                        globals: ['woopraLoaded', '_w', 'WoopraTracker', 'woopraTracker', 'WoopraLoadScript', 'Woopra', 'woopra', 'woopra_c1', 'woopra_c2', 'woopra_c3'],
                        ignoreLeaks: false
                    },
                    reporter: 'Spec',
                    run: true,
                    log: false
                }
            }
        },
        'saucelabs-mocha': {
            all: {
                options: {
                    urls: ["http://woopra-dev.local:4040/test/TestRunner.html"],
                    tunnelArgs: ['-v', '-t woopra-dev.local'],
                    concurrency: 3,
                    'max-duration': 30,
                    testname: "Woopra tracker tests",
                    tags: ["woopra-tracker"]
                }
            },
            full: {
                options: {
                    browsers: browsers
                }
            },
            quick: {
                options: {
                    browsers: [{
                        browserName: "chrome",
                        platform: "linux"
                    }]
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-mocha');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-connect');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-saucelabs');

    // Default task.
    grunt.registerTask('default', ['test', 'uglify']);

    grunt.task.registerTask('test', ['connect:test', 'saucelabs-mocha:full']);
    grunt.task.registerTask('local-test', ['connect:test', 'mocha']);
    grunt.task.registerTask('quick-test', ['connect:test', 'saucelabs-mocha:quick']);

    grunt.registerTask('deploy', function() {
        grunt.task.run(['local-test', 'version', 'uglify', 'gitTagVersion']);
        grunt.task.run('upload:dev');
        grunt.task.run('purge:dev');
    });

    grunt.registerTask('version', function() {
        var exec = require('child_process').exec;
        var done = this.async();

        var version = grunt.option('ver') || 'patch';
        var old = grunt.config('pkg.version');

        exec('npm version ' + version, function(error, stdout, stderr) {
            grunt.log.writeln(stdout);
            grunt.log.writeln(stderr);

            if (error === null) {
                grunt.config('pkg', grunt.file.readJSON('package.json'));
                grunt.log.writeln('Updated version from ' + old + '->' + grunt.config('pkg.version') + ' (' + version + ')');
                done();
            }
            else {
                grunt.log.error('Error updating version from ' + old + ' (' + version + ')');
                done(false);
            }
        });
    });

    grunt.registerTask('gitTagVersion', function() {
        var exec = require('child_process').exec;
        var _ = require('lodash');
        var done = this.async();
        var version = grunt.config('pkg.version');
        var tasks = [
            'git pull --rebase',
            'git push origin',
            'git fetch --tags',
            'git push origin --tags',
            'git push origin'
        ];

        var doExec = function(index) {
            var task = tasks[index];
            if (task) {
                exec(task, function(error, stdout, stderr) {
                    if (error !== null) {
                        grunt.log.error('Error running: ', task, stderr);
                        done(false);
                    }
                    else {
                        if (tasks.length - 1 === index) {
                            done();
                        }
                        else {
                            doExec(++index);
                        }
                    }
                });
            }
        };

        exec('git diff --shortstat', function(err, stdout, stderr) {
            if (_.isEmpty(stdout)) {
                doExec(0);
            } else {
                // Assume this is safe because `grunt version` will error if there are unstaged changes
                exec('git commit -a -m "Update build to ' + version + '"', function(err, stdout, stderr) {
                    if (!err) {
                        doExec(0);
                    }
                    else {
                        grunt.log.error('Error commiting', stderr);
                        done(false);
                    }
                });
            }
        });
    });

    grunt.registerMultiTask('purge', 'Call EdgeCast API to purge CDN cache', function() {
        var done = this.async();
        var request = require('request');
        var doRequest;
        var upload;
        var doneCounter = 0;
        var doneInc;

        doRequest = function(data) {
            request.put({
                url: 'https://api.edgecast.com/v2/mcc/customers/' + process.env.EDGECAST_ACCOUNT_NUMBER + '/edge/purge',
                json: true,
                body: data,
                headers: {
                    Authorization: 'TOK:' + process.env.EDGECAST_ACCESS_TOKEN,
                    Accept: 'application/json'
                }
            }, function(e, r, body) {
                var json;
                if (!e) {
                    if (body.Id) {
                        // assume only one file gets updated for now
                        grunt.log.writeln('Request sent, id = ' + body.Id);
                        doneInc();
                    }
                    else {
                        grunt.log.error('Error purging CDN cache.', e, body);
                        done(false);
                    }
                }
                else {
                    grunt.log.error('Error purging CDN cache.', e, body);
                    done(false);
                }
            });
        };

        upload = grunt.config('upload.' + this.target);

        doneInc = function() {
            doneCounter++;
            if (doneCounter >= upload.tasks.length) {
                done();
            }
        };

        if (upload && upload.tasks) {
            upload.tasks.forEach(function(task) {
                grunt.log.writeln('Purging...', grunt.config('CDN_URL') + task.dest);
                doRequest({
                    MediaPath: grunt.config('CDN_URL') + task.dest,
                    MediaType: 8
                });
            });
        }
    });

    grunt.registerMultiTask('upload', 'Uploads to CDN', function() {
        var self = this;
        var done = this.async();
        var versions;
        var tasks;
        var uploadTypes = {
            scp: function() {
                var scp = require('scp');

                this.data.tasks.forEach(function(task) {
                    task.src.forEach(function(src) {
                        scp.send({
                            host: self.data.host,
                            path: self.data.cdnRoot + task.dest,
                            file: __dirname + '/' + src
                        }, function(err, stdout, stderr) {
                            grunt.log.writeln(err, stdout, stderr);
                            if (err) done(false);
                            else done();
                        });
                        grunt.log.writeln('Uploading...', task.dest);
                    });
                });
            }
        };

        if (this.target === 'dev') {
            tasks = this.data.tasks;
            versions = getVersion(grunt.config('pkg.version'));
            tasks.push({
                src: [
                    grunt.config('uglify.main.dest')
                ],
                dest: '/js/t/' + versions.major + '.js'
            });
            tasks.push({
                src: [
                    grunt.config('uglify.main.dest')
                ],
                dest: '/js/t/' + versions.major + '.' + versions.minor + '.js'
            });
            grunt.config(this.name + '.' + this.target + '.tasks', tasks);
        }
        uploadTypes[this.data.type].apply(this);
    });
};
<commit_msg>Fix saucelabs config<commit_after>/*global module:false*/
module.exports = function(grunt) {
    var browsers = [{
        browserName: "firefox",
        version: "19",
        platform: "XP"
    }, {
        browserName: "firefox",
        platform: "XP"
    }, {
        browserName: "chrome",
        platform: "XP"
    }, {
        browserName: "internet explorer",
        platform: "XP",
        version: "7.0"
    }, {
        browserName: "internet explorer",
        platform: "XP",
        version: "8.0"
    }, {
        browserName: "chrome",
        platform: "linux"
    }, {
        browserName: "firefox",
        platform: "linux"
    }, {
        browserName: "firefox",
        platform: "OS X 10.10"
    }, {
        browserName: "safari",
        platform: "OS X 10.10"
    }, {
        browserName: "chrome",
        platform: "OS X 10.10"
    }, {
        browserName: "internet explorer",
        platform: "Windows 8",
        version: "10.0"
    }, {
        browserName: "internet explorer",
        platform: "Windows 8.1",
        version: "11.0"
    }, {
        browserName: "internet explorer",
        platform: "Windows 7",
        version: "9.0"
        //}, {
        //browserName: "opera",
        //platform: "Windows 2008",
        //version: "12"
    }];

    var getVersion = function(version) {
        var data = {};
        var match, major, minor, patch;

        match = version.match(/^([0-9]+)\.([0-9]+)\.([0-9]+).*/);

        if (match) {
            data.major = match[1];
            data.minor = match[2];
            data.patch = match[3];
        }

        return data;
    };

    // Project configuration.
    grunt.config.init({
        CDN_URL: 'http://static.woopra.com',
        filename: 'w',
        pkg: grunt.file.readJSON('package.json'),
        options: {
            testFiles: ['<%= pkg.name %>.v<%= pkg.version %>.min.js'],
            specFiles: ['test/spec/*.js']
        },
        connect: {
            dev: {
                options: {
                    port: 4040,
                    hostname: '0.0.0.0',
                    keepalive: true
                }
            },
            test: {
                options: {
                    port: 4040,
                    hostname: '0.0.0.0'
                }
            },
        },

        meta: {
            banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' +
                '<%= grunt.template.today("yyyy-mm-dd") %>\\n' +
                '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' +
                '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' +
                ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */'
        },
        watch: {
            files: '<config:lint.files>',
            tasks: 'lint min',
            test: {
                files: ['<%= options.testFiles>', '<%= options.specFiles %>', 'test/TestRunner.html'],
                tasks: 'mocha'
            }
        },
        jshint: {
            options: {
                curly: true,
                eqeqeq: true,
                immed: true,
                latedef: true,
                newcap: true,
                noarg: true,
                sub: true,
                undef: true,
                boss: true,
                eqnull: true,
                browser: true,
                unused: true,
                globals: {
                    exports: true,
                    jQuery: true
                }
            },
            main: {
                files: [{
                    src: ['grunt.js', '<%= pkg.name %>.js']
                }]
            },
            tests: {
                options: {
                    globals: {
                        exports: true,
                        '$': true,
                        describe: true,
                        expect: true,
                        before: true,
                        beforeEach: true,
                        after: true,
                        afterEach: true,
                        sinon: true,
                        Woopra: true,
                        WoopraTracker: true,
                        it: true
                    }
                },
                files: [{
                    src: ['<%= options.specFiles %>']
                }]
            }
        },
        uglify: {
            options: {
                compressor: {
                    warnings: false
                },
                mangle: true,
                compress: true
            },
            main: {
                options: {
                    banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> */'
                },
                src: ['<%= pkg.name %>.js'],
                dest: '<%= pkg.name %>.min.js'
            },
            plugin: {
                src: ['jquery.woopra.js'],
                dest: 'jquery.woopra.min.js'
            },
            snippet: {
                src: ['snippet.js'],
                dest: 'snippet-min.js'
            }
        },
        upload: {
            tracker: {
                type: 'scp',
                host: 'woopra-cdn',
                cdnRoot: '/var/www/html',
                tasks: [{
                    src: [
                        '<%= uglify.main.dest %>'
                    ],
                    dest: '/js/<%= filename %>.js'
                }]
            },
            dev: {
                type: 'scp',
                host: 'woopra-cdn',
                cdnRoot: '/var/www/html',
                tasks: [{
                    src: [
                        '<%= uglify.main.dest %>'
                    ],
                    dest: '/js/t/<%= pkg.version %>.js'
                }]

            }
        },
        purge: {
            tracker: {
            },
            dev: {
            }
        },
        mocha: {
            tracker: {
                src: ['test/TestRunner.html'],
                options: {
                    mocha: {
                        globals: ['woopra', 'newTracker', 'pingTracker', 'w1', 'w2', 'w3'],
                        ignoreLeaks: false
                    },
                    reporter: 'Spec',
                    run: true,
                    log: true
                }
            },
            snippet: {
                src: ['test/snippet.html'],
                options: {
                    mocha: {
                        globals: ['woopraLoaded', '_w', 'WoopraTracker', 'woopraTracker', 'WoopraLoadScript', 'Woopra', 'woopra', 'woopra_c1', 'woopra_c2', 'woopra_c3'],
                        ignoreLeaks: false
                    },
                    reporter: 'Spec',
                    run: true,
                    log: false
                }
            }
        },
        'saucelabs-mocha': {
            all: {
                options: {
                    urls: ["http://woopra-dev.local:4040/test/TestRunner.html"],
                    tunnelArgs: ['-v', '-t woopra-dev.local'],
                    concurrency: 3,
                    'max-duration': 30,
                    testname: "Woopra tracker tests",
                    tags: ["woopra-tracker"]
                }
            },
            full: {
                options: {
                    urls: ["http://woopra-dev.local:4040/test/TestRunner.html"],
                    tunnelArgs: ['-v', '-t woopra-dev.local'],
                    concurrency: 3,
                    browsers: browsers,
                    'max-duration': 30,
                    testname: "Woopra tracker tests",
                    tags: ["woopra-tracker"]
                }
            },
            quick: {
                options: {
                    browsers: [{
                        browserName: "chrome",
                        platform: "linux"
                    }]
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-mocha');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-connect');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-saucelabs');

    // Default task.
    grunt.registerTask('default', ['test', 'uglify']);

    grunt.task.registerTask('test', ['connect:test', 'saucelabs-mocha:full']);
    grunt.task.registerTask('local-test', ['connect:test', 'mocha']);
    grunt.task.registerTask('quick-test', ['connect:test', 'saucelabs-mocha:quick']);

    grunt.registerTask('deploy', function() {
        grunt.task.run(['local-test', 'version', 'uglify', 'gitTagVersion']);
        grunt.task.run('upload:dev');
        grunt.task.run('purge:dev');
    });

    grunt.registerTask('version', function() {
        var exec = require('child_process').exec;
        var done = this.async();

        var version = grunt.option('ver') || 'patch';
        var old = grunt.config('pkg.version');

        exec('npm version ' + version, function(error, stdout, stderr) {
            grunt.log.writeln(stdout);
            grunt.log.writeln(stderr);

            if (error === null) {
                grunt.config('pkg', grunt.file.readJSON('package.json'));
                grunt.log.writeln('Updated version from ' + old + '->' + grunt.config('pkg.version') + ' (' + version + ')');
                done();
            }
            else {
                grunt.log.error('Error updating version from ' + old + ' (' + version + ')');
                done(false);
            }
        });
    });

    grunt.registerTask('gitTagVersion', function() {
        var exec = require('child_process').exec;
        var _ = require('lodash');
        var done = this.async();
        var version = grunt.config('pkg.version');
        var tasks = [
            'git pull --rebase',
            'git push origin',
            'git fetch --tags',
            'git push origin --tags',
            'git push origin'
        ];

        var doExec = function(index) {
            var task = tasks[index];
            if (task) {
                exec(task, function(error, stdout, stderr) {
                    if (error !== null) {
                        grunt.log.error('Error running: ', task, stderr);
                        done(false);
                    }
                    else {
                        if (tasks.length - 1 === index) {
                            done();
                        }
                        else {
                            doExec(++index);
                        }
                    }
                });
            }
        };

        exec('git diff --shortstat', function(err, stdout, stderr) {
            if (_.isEmpty(stdout)) {
                doExec(0);
            } else {
                // Assume this is safe because `grunt version` will error if there are unstaged changes
                exec('git commit -a -m "Update build to ' + version + '"', function(err, stdout, stderr) {
                    if (!err) {
                        doExec(0);
                    }
                    else {
                        grunt.log.error('Error commiting', stderr);
                        done(false);
                    }
                });
            }
        });
    });

    grunt.registerMultiTask('purge', 'Call EdgeCast API to purge CDN cache', function() {
        var done = this.async();
        var request = require('request');
        var doRequest;
        var upload;
        var doneCounter = 0;
        var doneInc;

        doRequest = function(data) {
            request.put({
                url: 'https://api.edgecast.com/v2/mcc/customers/' + process.env.EDGECAST_ACCOUNT_NUMBER + '/edge/purge',
                json: true,
                body: data,
                headers: {
                    Authorization: 'TOK:' + process.env.EDGECAST_ACCESS_TOKEN,
                    Accept: 'application/json'
                }
            }, function(e, r, body) {
                var json;
                console.log(e, body);
                if (!e) {
                    if (body.Id) {
                        // assume only one file gets updated for now
                        grunt.log.writeln('Request sent, id = ' + body.Id);
                        doneInc();
                    }
                    else {
                        grunt.log.error('Error purging CDN cache.', e, body);
                        done(false);
                    }
                }
                else {
                    grunt.log.error('Error purging CDN cache.', e, body);
                    done(false);
                }
            });
        };

        upload = grunt.config('upload.' + this.target);

        doneInc = function() {
            doneCounter++;
            if (doneCounter >= upload.tasks.length) {
                done();
            }
        };

        if (upload && upload.tasks) {
            upload.tasks.forEach(function(task) {
                grunt.log.writeln('Purging...', grunt.config('CDN_URL') + task.dest);
                doRequest({
                    MediaPath: grunt.config('CDN_URL') + task.dest,
                    MediaType: 8
                });
            });
        }
    });

    grunt.registerMultiTask('upload', 'Uploads to CDN', function() {
        var self = this;
        var done = this.async();
        var versions;
        var tasks;
        var uploadTypes = {
            scp: function() {
                var scp = require('scp');

                this.data.tasks.forEach(function(task) {
                    task.src.forEach(function(src) {
                        scp.send({
                            host: self.data.host,
                            path: self.data.cdnRoot + task.dest,
                            file: __dirname + '/' + src
                        }, function(err, stdout, stderr) {
                            grunt.log.writeln(err, stdout, stderr);
                            if (err) done(false);
                            else done();
                        });
                        grunt.log.writeln('Uploading...', task.dest);
                    });
                });
            }
        };

        if (this.target === 'dev') {
            tasks = this.data.tasks;
            versions = getVersion(grunt.config('pkg.version'));
            tasks.push({
                src: [
                    grunt.config('uglify.main.dest')
                ],
                dest: '/js/t/' + versions.major + '.js'
            });
            tasks.push({
                src: [
                    grunt.config('uglify.main.dest')
                ],
                dest: '/js/t/' + versions.major + '.' + versions.minor + '.js'
            });
            grunt.config(this.name + '.' + this.target + '.tasks', tasks);
        }
        uploadTypes[this.data.type].apply(this);
    });
};
<|endoftext|>


<commit_before>
/**
 * returns a new rendering context
 * @param gl the gl context
 * @param projectionMatrix optional projection Matrix
 * @returns {ISceneGraphContext}
 */
function createSceneGraphContext(gl, shader) {

  //create a default projection matrix
  projectionMatrix = mat4.perspective(mat4.create(), fieldOfViewInRadians, aspectRatio, 0.01, 10);
  //set projection matrix
  gl.uniformMatrix4fv(gl.getUniformLocation(shader, 'u_projection'), false, projectionMatrix);

  return {
    gl: gl,
    sceneMatrix: mat4.create(),
    viewMatrix: calculateViewMatrix(),
    projectionMatrix: projectionMatrix,
    shader: shader
  };
}
/*
function calculateViewMatrix() {
  //compute the camera's matrix
  var eye = [0,3,5];
  var center = [0,0,0];
  var up = [0,1,0];
  viewMatrix = mat4.lookAt(mat4.create(), eye, center, up);
  return viewMatrix;
}
*/
/**
 * base node of the scenegraph
 */
class SceneGraphNode {

  constructor() {
    this.children = [];
  }

  /**
   * appends a new child to this node
   * @param child the child to append
   * @returns {SceneGraphNode} the child
   */
  append(child) {
    this.children.push(child);
    return child;
  }

  /**
   * removes a child from this node
   * @param child
   * @returns {boolean} whether the operation was successful
   */
  remove(child) {
    var i = this.children.indexOf(child);
    if (i >= 0) {
      this.children.splice(i, 1);
    }
    return i >= 0;
  };

  /**
   * render method to render this scengraph
   * @param context
   */
  render(context) {

    //render all children
    this.children.forEach(function (c) {
      return c.render(context);
    });
  };
}

/**
 * a quad node that renders floor plane
 */
class QuadRenderNode extends SceneGraphNode {

  render(context) {

    //setting the model view and projection matrix on shader
    setUpModelViewMatrix(context.sceneMatrix, context.viewMatrix);

    var positionLocation = gl.getAttribLocation(context.shader, 'a_position');
    gl.bindBuffer(gl.ARRAY_BUFFER, quadVertexBuffer);
    gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(positionLocation);

    var colorLocation = gl.getAttribLocation(context.shader, 'a_color');
    gl.bindBuffer(gl.ARRAY_BUFFER, quadColorBuffer);
    gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(colorLocation);

    //set alpha value for blending
    //gl.uniform1f(gl.getUniformLocation(context.shader, 'u_alpha'), 1);

    // draw the bound data as 6 vertices = 2 triangles starting at index 0
    gl.drawArrays(gl.TRIANGLES, 0, 6);

    //render children
    super.render(context);
  }
}

/**
 * a cube node that renders a cube at its local origin
 */
class CubeRenderNode extends SceneGraphNode {

  render(context) {

    //setting the model view and projection matrix on shader
    setUpModelViewMatrix(context.sceneMatrix, context.viewMatrix);

    var positionLocation = gl.getAttribLocation(context.shader, 'a_position');
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexBuffer);
    gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false,0,0) ;
    gl.enableVertexAttribArray(positionLocation);

    var colorLocation = gl.getAttribLocation(context.shader, 'a_color');
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeColorBuffer);
    gl.vertexAttribPointer(colorLocation, 3, gl.FLOAT, false,0,0) ;
    gl.enableVertexAttribArray(colorLocation);

    //set alpha value for blending
    //gl.uniform1f(gl.getUniformLocation(context.shader, 'u_alpha'), 0.5);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeIndexBuffer);
    gl.drawElements(gl.TRIANGLES, cubeIndices.length, gl.UNSIGNED_SHORT, 0); //LINE_STRIP

    //render children
    super.render(context);
  }
}

/**
 * a transformation node, i.e applied a transformation matrix to its successors
 */
class TransformationSceneGraphNode extends SceneGraphNode {
  /**
   * the matrix to apply
   * @param matrix
   */
  constructor(matrix) {
    super();
    this.matrix = matrix || mat4.create();
  }

  render(context) {
    //backup previous one
    var previous = context.sceneMatrix;
    //set current world matrix by multiplying it
    if (previous === null) {
      context.sceneMatrix = mat4.clone(this.matrix);
    }
    else {
      context.sceneMatrix = mat4.multiply(mat4.create(), previous, this.matrix);
    }

    //render children
    super.render(context);
    //restore backup
    context.sceneMatrix = previous;
  }

  setMatrix(matrix) {
    this.matrix = matrix;
  }
}

//TASK 5-0
/**
 * a shader node sets a specific shader for the successors
 */
class ShaderSceneGraphNode extends SceneGraphNode {
  /**
   * constructs a new shader node with the given shader program
   * @param shader the shader program to use
   */
  constructor(shader) {
    super();
    this.shader = shader;
  }

  render(context) {
    //backup prevoius one
    var backup = context.shader;
    //set current shader
    context.shader = this.shader;
    //activate the shader
    context.gl.useProgram(this.shader);
    //set projection matrix
    gl.uniformMatrix4fv(gl.getUniformLocation(context.shader, 'u_projection'),
      false, context.projectionMatrix);
    //render children
    super.render(context);
    //restore backup
    context.shader = backup;
    //activate the shader
    context.gl.useProgram(backup);
  }
};<commit_msg>Refactor Scenegraph Code<commit_after>
/**
 * returns a new rendering context
 * @param gl the gl context
 * @param projectionMatrix optional projection Matrix
 * @returns {ISceneGraphContext}
 */
function createSceneGraphContext(gl, shader) {

  //create a default projection matrix
  projectionMatrix = mat4.perspective(mat4.create(), fieldOfViewInRadians, aspectRatio, 0.01, 10);
  //set projection matrix
  gl.uniformMatrix4fv(gl.getUniformLocation(shader, 'u_projection'), false, projectionMatrix);

  return {
    gl: gl,
    sceneMatrix: mat4.create(),
    viewMatrix: calculateViewMatrix(),
    projectionMatrix: projectionMatrix,
    shader: shader
  };
}
/**
 * a quad node that renders floor plane
 */
class QuadRenderNode extends SGNode {

  render(context) {

    //setting the model view and projection matrix on shader
    setUpModelViewMatrix(context.sceneMatrix, context.viewMatrix);

    var positionLocation = gl.getAttribLocation(context.shader, 'a_position');
    gl.bindBuffer(gl.ARRAY_BUFFER, quadVertexBuffer);
    gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(positionLocation);

    var colorLocation = gl.getAttribLocation(context.shader, 'a_color');
    gl.bindBuffer(gl.ARRAY_BUFFER, quadColorBuffer);
    gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(colorLocation);

    //set alpha value for blending
    //gl.uniform1f(gl.getUniformLocation(context.shader, 'u_alpha'), 1);

    // draw the bound data as 6 vertices = 2 triangles starting at index 0
    gl.drawArrays(gl.TRIANGLES, 0, 6);

    //render children
    super.render(context);
  }
}

/**
 * a cube node that renders a cube at its local origin
 */
class CubeRenderNode extends SGNode {

  render(context) {

    //setting the model view and projection matrix on shader
    setUpModelViewMatrix(context.sceneMatrix, context.viewMatrix);

    var positionLocation = gl.getAttribLocation(context.shader, 'a_position');
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexBuffer);
    gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false,0,0) ;
    gl.enableVertexAttribArray(positionLocation);

    var colorLocation = gl.getAttribLocation(context.shader, 'a_color');
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeColorBuffer);
    gl.vertexAttribPointer(colorLocation, 3, gl.FLOAT, false,0,0) ;
    gl.enableVertexAttribArray(colorLocation);

    //set alpha value for blending
    //gl.uniform1f(gl.getUniformLocation(context.shader, 'u_alpha'), 0.5);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeIndexBuffer);
    gl.drawElements(gl.TRIANGLES, cubeIndices.length, gl.UNSIGNED_SHORT, 0); //LINE_STRIP

    //render children
    super.render(context);
  }
}

/**
 * a sphere node that renders a sphere at its local origin
 */
class SphereRenderNode extends SGNode {

  render(context) {

    //setting the model view and projection matrix on shader
    //setUpModelViewMatrix(context.sceneMatrix, context.viewMatrix);

    /*var positionLocation = gl.getAttribLocation(context.shader, 'a_position');
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexBuffer);
    gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false,0,0) ;
    gl.enableVertexAttribArray(positionLocation);

    var colorLocation = gl.getAttribLocation(context.shader, 'a_color');
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeColorBuffer);
    gl.vertexAttribPointer(colorLocation, 3, gl.FLOAT, false,0,0) ;
    gl.enableVertexAttribArray(colorLocation);
    */
    //set alpha value for blending
    //gl.uniform1f(gl.getUniformLocation(context.shader, 'u_alpha'), 0.5);

    makeSphere();
    //render children
    super.render(context);
  }
}
<|endoftext|>


<commit_before>/* eslint-disable max-lines, max-statements, no-lonely-if */
/* global google */

// LocationMarkers is a marker manager.

var markerStore = tresdb.stores.markers;
var locationsStore = tresdb.stores.locations;
var account = tresdb.stores.account;
var filterStore = tresdb.stores.filter;
var emitter = require('component-emitter');
var rawEventToMarkerLocation = require('./lib/rawEventToMarkerLocation');
var getBoundsDiagonal = require('./lib/getBoundsDiagonal');
var VisitedManager = require('./VisitedManager');
var getGroupRadius = require('./getGroupRadius');
var chooseIcon = require('./chooseIcon');
var labels = require('./lib/labels');

module.exports = function (map) {

  // Init
  var self = this;
  emitter(self);

  // Location markers on the map. A mapping from id to google.maps.Marker
  var _markers = {};

  // Does the map load markers from the locations and display them on the map.
  // Will be set when client is ready to load markers (logged in)
  var _loaderListener = null;

  // True if map is ready for use. Will be switched to true at the first
  // 'idle' event. See addListenerOnce('idle', ...) below.
  // We need to track this for startLoadingMarkers: if map is ready
  // then load the markers immediately when called, otherwise wait for idle.
  var _mapReady = false;

  // Array of ids of locations that the user has visited.
  // Fetch them as soon as possible.
  var _visitedIds = new VisitedManager();

  // Array of ids of locations currently selected ON MAP not in store.
  // The array is used to find the previously selected locations quickly.
  var _selectedId = null;
  // If selected location was hidden, a marker is created for it.
  // To avoid these markers from piling up, remove a deselected marker
  // if it was hidden before.
  var _selectedWasVisible = false;

  // Private methods

  var _chooseIcon = function (mloc) {
    var zoomLevel = map.getZoom();
    var isSelected = locationsStore.isSelected(mloc._id);
    var isVisited = _visitedIds.isVisited(mloc._id);
    return chooseIcon(mloc, zoomLevel, isSelected, isVisited);
  };

  var _updateIcon = function (locId) {
    var m = _markers[locId];
    if (m) {
      m.setIcon(_chooseIcon(m.get('location')));
    }
  };

  var _ensureLabel = function (locId, forceUpdate) {
    // Ensure that label is visible.
    // Parameters:
    //   locId
    //   forceUpdate, optional bool
    //     re-render the label even if visible. Use to update the label text.
    var m = _markers[locId];
    if (m) {
      labels.ensureLabel(m, map.getMapTypeId(), forceUpdate);
    }
  };

  var _addMarker = function (mloc) {
    // Create marker and add it to the map.
    //
    // Parameters:
    //   mloc
    //     a MarkerLocation.
    // Return
    //   the created marker
    var lng, lat, m;

    lng = mloc.geom.coordinates[0];
    lat = mloc.geom.coordinates[1];

    m = new google.maps.Marker({
      position: new google.maps.LatLng(lat, lng),
      icon: _chooseIcon(mloc),
      // Order markers so that northern markers are always behind.
      // eslint-disable-next-line no-magic-numbers
      zIndex: Math.floor((100 - lat) * 1048576),
    });

    // Store the MarkerLocation for _id, name, and layer info.
    m.set('location', mloc);

    // Label only the important, higher markers.
    if (mloc.layer < map.getZoom() - 1) {
      labels.ensureLabel(m, map.getMapTypeId(), true);
    }

    m.setMap(map);
    m.addListener('click', function () {

      if (labels.hasLabel(m)) {
        self.emit('marker_activated', mloc);
      } else {
        // First click shows the label
        labels.ensureLabel(m, map.getMapTypeId(), true);
      }

    });

    m.set('id', mloc._id);
    _markers[mloc._id] = m;

    return m;
  };

  var _removeMarker = function (m) {
    if (m) {
      // Remove from map
      m.setMap(null);

      // Remove click listener
      google.maps.event.clearInstanceListeners(m);

      delete _markers[m.get('id')];
    }
  };

  var _updateMarkers = function (locs) {
    // Add new markers and removes the excess.
    // To speed up things and avoid flicker,
    // only adds those markers on the screen that are not already there.

    // For each location candidate
    locs.forEach(function (l) {
      var m, mloc;
      if (l._id in _markers) {
        // Mark that it does not need to be removed.
        m = _markers[l._id];
        m.set('keep', true);
        // HACK Update dynamic layer and childLayer properties to
        // display child mark properly between updates.
        // In filtered results the layer and childLayer changes constantly.
        mloc = m.get('location');
        if (Math.abs(mloc.layer - l.layer) +
            Math.abs(mloc.childLayer - l.childLayer) > 0) {
          // Change in layer data.
          mloc.layer = l.layer;
          mloc.childLayer = l.childLayer;
          m.setIcon(_chooseIcon(mloc)); // Update accordingly
        }
      } else {
        // otherwise, add it to the map.
        m = _addMarker(l);
        m.set('keep', true);
      }
    });

    // Keep the selected marker, because sometimes the selected is not
    // among the locations from the marker api but hierarchically hidden
    // below them.
    if (_markers[_selectedId]) {
      _markers[_selectedId].set('keep', true);
    }

    // Remove markers that were not marked to be kept.
    // Also, reset keep for next update.
    Object.keys(_markers).forEach(function (k) {
      var m = _markers[k];
      var keep = m.get('keep');
      if (keep) {
        // Reset for next update
        m.set('keep', false);
      } else {
        // Remove
        _removeMarker(m);
      }
    });
  };

  var _loadMarkersThen = function (err, locs) {
    if (err) {
      return console.error(err);
    } // else
    _updateMarkers(locs);
  };

  var _loadMarkers = function () {
    // Load markers for current viewport.
    // Take into consideration the current
    // - map center,
    // - map zoom level
    // - viewport size
    // - filter settings
    var bounds = map.getBounds();
    var zoom = map.getZoom();

    if (filterStore.isDefault()) {
      // No filtration
      var center = map.getCenter();
      var radius = Math.ceil(getBoundsDiagonal(bounds) / 2);
      markerStore.getWithin(center, radius, zoom, _loadMarkersThen);
    } else {
      // Query filtered set of locations.
      var filterState = filterStore.get();
      var boundsLiteral = bounds.toJSON();
      var groupRadius = getGroupRadius(boundsLiteral);
      markerStore.getFilteredWithin({
        east: boundsLiteral.east,
        north: boundsLiteral.north,
        south: boundsLiteral.south,
        west: boundsLiteral.west,
        status: filterState.status,
        type: filterState.type,
        layer: zoom,
        groupRadius: groupRadius,
      }, _loadMarkersThen);
    }
  };

  // Bind

  // Listen for changes in locations so that the markers and labels
  // are up to date.
  markerStore.on('location_name_changed', function (ev) {
    // Parameters
    //   ev
    //     ev.data.newName
    var m, mloc;

    // No need to update if no such marker on the map.
    if (!(ev.locationId in _markers)) {
      return;
    }

    // Update name of markerLocation
    m = _markers[ev.locationId];
    mloc = m.get('location');
    mloc.name = ev.data.newName;

    // Refresh label. Force update even if label already visible
    labels.ensureLabel(m, map.getMapTypeId(), true);
  });

  markerStore.on('location_geom_changed', function (ev) {
    // Parameters
    //   ev
    //     ev.data.newGeom
    var m, g, mloc;

    // No need to update if no such marker on the map.
    if (!(ev.locationId in _markers)) {
      return;
    }

    // Update geom of markerLocation
    g = ev.data.newGeom;
    m = _markers[ev.locationId];
    mloc = m.get('location');
    mloc.geom = g;

    // Ensure coordinates are up to date
    m.setPosition({
      lat: g.coordinates[1],
      lng: g.coordinates[0],
    });
  });

  markerStore.on('location_created', function (ev) {
    var mloc = rawEventToMarkerLocation(ev);
    _addMarker(mloc);
  });

  markerStore.on('location_removed', function (ev) {
    if (_markers[ev.locationId]) {
      var mToRemove = _markers[ev.locationId];
      _removeMarker(mToRemove);
    }
  });

  markerStore.on('location_status_changed', function (ev) {
    // Update icon according to new status.
    // Do this only if the marker has reached the map.
    if (_markers[ev.locationId]) {
      var m = _markers[ev.locationId];
      var mloc = m.get('location');
      // First update the stored MarkerLocation
      mloc.status = ev.data.newStatus;
      // Update icon accordingly
      m.setIcon(_chooseIcon(mloc));
    }
  });

  markerStore.on('location_type_changed', function (ev) {
    // Update icon according to new type.
    // Do this only if the marker has reached the map.
    if (_markers[ev.locationId]) {
      var m = _markers[ev.locationId];
      var mloc = m.get('location');
      // First update the stored MarkerLocation
      mloc.type = ev.data.newType;
      // Update icon accordingly
      m.setIcon(_chooseIcon(mloc));
    }
  });

  markerStore.on('location_entry_created', function (ev) {
    if (ev.data.isVisit && account.isMe(ev.user)) {
      // Add loc among visited locations if not visited before by this user.
      _visitedIds.add(ev.locationId);

      // Update marker icon to visited
      if (_markers[ev.locationId]) {
        var m = _markers[ev.locationId];
        var mloc = m.get('location');
        m.setIcon(_chooseIcon(mloc));
      }
    }
  });

  markerStore.on('location_entry_changed', function (ev) {
    // Change from non-visit to visit
    if (ev.data.newIsVisit && !ev.data.oldIsVisit && account.isMe(ev.user)) {
      // Add loc among visited locations if not visited before by this user.
      _visitedIds.add(ev.locationId);
      // Update marker icon to visited
      _updateIcon(ev.locationId);
    } else {
      // From visit to non-visit
      if (ev.data.oldIsVisit && !ev.data.newIsVisit) {
        _visitedIds.remove(ev.locationId);
        // Update marker icon to unvisited
        _updateIcon(ev.locationId);
      }
    }
  });

  // Listen possible location selections to add or update selected marker.
  locationsStore.on('updated', function (state) {
    if (state.selectedLocationId) {
      if (_selectedId === state.selectedLocationId) {
        // Already selected, do nothing.
      } else {
        // Unstyle the previous selected. Remove the previously selected
        // if it was hidden before.
        if (_selectedWasVisible) {
          _updateIcon(_selectedId);
        } else {
          _removeMarker(_markers[_selectedId]);
        }

        if (_markers[state.selectedLocationId]) {
          // New selection already on map. Restyle the new.
          _updateIcon(state.selectedLocationId);
          _selectedWasVisible = true;
        } else {
          // Marker not on the map. Use state's location if available.
          if (state.selectedMarkerLocation) {
            _addMarker(state.selectedMarkerLocation);
            _selectedWasVisible = false;
          }
        }

        // Show location title
        _ensureLabel(state.selectedLocationId);
        _selectedId = state.selectedLocationId;
      }
    } else {
      // Null selected.
      // Clear select-styled markers.
      if (_selectedWasVisible) {
        _updateIcon(_selectedId);
      } else {
        _removeMarker(_markers[_selectedId]);
      }
      _selectedId = null;
    }
  });

  // Listen map type change to invert label text colors.
  map.addListener('maptypeid_changed', function () {
    labels.updateMarkerLabels(_markers, map.getMapTypeId());
  });

  map.addListener('zoom_changed', function () {
    var z = map.getZoom();

    Object.keys(_markers).forEach(function (k) {
      var m = _markers[k];
      var mloc = m.get('location');

      // Listen zoom level change to only show labels of locations
      // with higher level than current zoom level.
      if (mloc.layer < z - 1) {
        // Ensure that label is visible.
        labels.ensureLabel(m, map.getMapTypeId());
      } else {
        labels.hideLabel(m);
      }

      // Listen zoom level change to update symbols of locations
      // when all their children become visible or hidden.
      // Ensure that the correct marker icon is used.
      // They are zoom-sensitive.
      _updateIcon(k);
    });
  });

  // Track when map becomes usable.
  // See 'var _mapReady = false' above for details.
  google.maps.event.addListenerOnce(map, 'idle', function () {
    _mapReady = true;
  });

  // Public methods

  self.startLoading = function () {
    // Each idle, fetch a new set of locations.
    // The idle is emitted automatically after the initial map load.

    // Prevent duplicate listeners.
    if (_loaderListener !== null) {
      return;
    }

    // Each time map stops, fetch.
    _loaderListener = map.addListener('idle', _loadMarkers);
    // Load the first manually but only if the map is ready.
    // Map emits 'idle' when ready but user might not have been logged in yet.
    if (_mapReady) {
      _loadMarkers();
    }

    // Each time filter changes, fetch.
    filterStore.on('updated', _loadMarkers);

    // Fetch the list of visited locations as soon as possible.
    account.getVisitedLocationIds(function (err, ids) {
      if (err) {
        console.error(err);
        return;
      }

      _visitedIds.set(ids);
    });
  };

  self.stopLoading = function () {
    google.maps.event.removeListener(_loaderListener);
    filterStore.off('updated', _loadMarkers);
    _loaderListener = null;
  };

  self.removeAll = function () {
    // Clear the map.
    Object.keys(_markers).forEach(function (k) {
      _removeMarker(_markers[k]);
    });
  };

};
<commit_msg>Ensure location selection does not hide late markers<commit_after>/* eslint-disable max-lines, max-statements, no-lonely-if */
/* global google */

// LocationMarkers is a marker manager.

var markerStore = tresdb.stores.markers;
var locationsStore = tresdb.stores.locations;
var account = tresdb.stores.account;
var filterStore = tresdb.stores.filter;
var emitter = require('component-emitter');
var rawEventToMarkerLocation = require('./lib/rawEventToMarkerLocation');
var getBoundsDiagonal = require('./lib/getBoundsDiagonal');
var VisitedManager = require('./VisitedManager');
var getGroupRadius = require('./getGroupRadius');
var chooseIcon = require('./chooseIcon');
var labels = require('./lib/labels');

module.exports = function (map) {

  // Init
  var self = this;
  emitter(self);

  // Location markers on the map. A mapping from id to google.maps.Marker
  var _markers = {};

  // Does the map load markers from the locations and display them on the map.
  // Will be set when client is ready to load markers (logged in)
  var _loaderListener = null;

  // True if map is ready for use. Will be switched to true at the first
  // 'idle' event. See addListenerOnce('idle', ...) below.
  // We need to track this for startLoadingMarkers: if map is ready
  // then load the markers immediately when called, otherwise wait for idle.
  var _mapReady = false;

  // Array of ids of locations that the user has visited.
  // Fetch them as soon as possible.
  var _visitedIds = new VisitedManager();

  // Array of ids of locations currently selected ON MAP not in store.
  // The array is used to find the previously selected locations quickly.
  var _selectedId = null;
  // If selected location was hidden, a marker is created for it.
  // To avoid these markers from piling up, remove a deselected marker
  // if it was hidden before.
  var _selectedWasVisible = false;

  // Private methods

  var _chooseIcon = function (mloc) {
    var zoomLevel = map.getZoom();
    var isSelected = locationsStore.isSelected(mloc._id);
    var isVisited = _visitedIds.isVisited(mloc._id);
    return chooseIcon(mloc, zoomLevel, isSelected, isVisited);
  };

  var _updateIcon = function (locId) {
    var m = _markers[locId];
    if (m) {
      m.setIcon(_chooseIcon(m.get('location')));
    }
  };

  var _ensureLabel = function (locId, forceUpdate) {
    // Ensure that label is visible.
    // Parameters:
    //   locId
    //   forceUpdate, optional bool
    //     re-render the label even if visible. Use to update the label text.
    var m = _markers[locId];
    if (m) {
      labels.ensureLabel(m, map.getMapTypeId(), forceUpdate);
    }
  };

  var _addMarker = function (mloc) {
    // Create marker and add it to the map.
    //
    // Parameters:
    //   mloc
    //     a MarkerLocation.
    // Return
    //   the created marker
    var lng, lat, m;

    lng = mloc.geom.coordinates[0];
    lat = mloc.geom.coordinates[1];

    m = new google.maps.Marker({
      position: new google.maps.LatLng(lat, lng),
      icon: _chooseIcon(mloc),
      // Order markers so that northern markers are always behind.
      // eslint-disable-next-line no-magic-numbers
      zIndex: Math.floor((100 - lat) * 1048576),
    });

    // Store the MarkerLocation for _id, name, and layer info.
    m.set('location', mloc);

    // Label only the important, higher markers.
    if (mloc.layer < map.getZoom() - 1) {
      labels.ensureLabel(m, map.getMapTypeId(), true);
    }

    m.setMap(map);
    m.addListener('click', function () {

      if (labels.hasLabel(m)) {
        self.emit('marker_activated', mloc);
      } else {
        // First click shows the label
        labels.ensureLabel(m, map.getMapTypeId(), true);
      }

    });

    m.set('id', mloc._id);
    _markers[mloc._id] = m;

    return m;
  };

  var _removeMarker = function (m) {
    if (m) {
      // Remove from map
      m.setMap(null);

      // Remove click listener
      google.maps.event.clearInstanceListeners(m);

      delete _markers[m.get('id')];
    }
  };

  var _updateMarkers = function (locs) {
    // Add new markers and removes the excess.
    // To speed up things and avoid flicker,
    // only adds those markers on the screen that are not already there.

    // For each location candidate
    locs.forEach(function (l) {
      var m, mloc;
      if (l._id in _markers) {
        // Mark that it does not need to be removed.
        m = _markers[l._id];
        m.set('keep', true);
        // If the marker is selected, ensure it is not hidden after deselect.
        if (l._id === _selectedId) {
          _selectedWasVisible = true;
        }
        // HACK Update dynamic layer and childLayer properties to
        // display child mark properly between updates.
        // In filtered results the layer and childLayer changes constantly.
        mloc = m.get('location');
        if (Math.abs(mloc.layer - l.layer) +
            Math.abs(mloc.childLayer - l.childLayer) > 0) {
          // Change in layer data.
          mloc.layer = l.layer;
          mloc.childLayer = l.childLayer;
          m.setIcon(_chooseIcon(mloc)); // Update accordingly
        }
      } else {
        // otherwise, add it to the map.
        m = _addMarker(l);
        m.set('keep', true);
      }
    });

    // Keep the selected marker, because sometimes the selected is not
    // among the locations from the marker api but hierarchically hidden
    // below them.
    if (_markers[_selectedId]) {
      _markers[_selectedId].set('keep', true);
    }

    // Remove markers that were not marked to be kept.
    // Also, reset keep for next update.
    Object.keys(_markers).forEach(function (k) {
      var m = _markers[k];
      var keep = m.get('keep');
      if (keep) {
        // Reset for next update
        m.set('keep', false);
      } else {
        // Remove
        _removeMarker(m);
      }
    });
  };

  var _loadMarkersThen = function (err, locs) {
    if (err) {
      return console.error(err);
    } // else
    _updateMarkers(locs);
  };

  var _loadMarkers = function () {
    // Load markers for current viewport.
    // Take into consideration the current
    // - map center,
    // - map zoom level
    // - viewport size
    // - filter settings
    var bounds = map.getBounds();
    var zoom = map.getZoom();

    if (filterStore.isDefault()) {
      // No filtration
      var center = map.getCenter();
      var radius = Math.ceil(getBoundsDiagonal(bounds) / 2);
      markerStore.getWithin(center, radius, zoom, _loadMarkersThen);
    } else {
      // Query filtered set of locations.
      var filterState = filterStore.get();
      var boundsLiteral = bounds.toJSON();
      var groupRadius = getGroupRadius(boundsLiteral);
      markerStore.getFilteredWithin({
        east: boundsLiteral.east,
        north: boundsLiteral.north,
        south: boundsLiteral.south,
        west: boundsLiteral.west,
        status: filterState.status,
        type: filterState.type,
        layer: zoom,
        groupRadius: groupRadius,
      }, _loadMarkersThen);
    }
  };

  // Bind

  // Listen for changes in locations so that the markers and labels
  // are up to date.
  markerStore.on('location_name_changed', function (ev) {
    // Parameters
    //   ev
    //     ev.data.newName
    var m, mloc;

    // No need to update if no such marker on the map.
    if (!(ev.locationId in _markers)) {
      return;
    }

    // Update name of markerLocation
    m = _markers[ev.locationId];
    mloc = m.get('location');
    mloc.name = ev.data.newName;

    // Refresh label. Force update even if label already visible
    labels.ensureLabel(m, map.getMapTypeId(), true);
  });

  markerStore.on('location_geom_changed', function (ev) {
    // Parameters
    //   ev
    //     ev.data.newGeom
    var m, g, mloc;

    // No need to update if no such marker on the map.
    if (!(ev.locationId in _markers)) {
      return;
    }

    // Update geom of markerLocation
    g = ev.data.newGeom;
    m = _markers[ev.locationId];
    mloc = m.get('location');
    mloc.geom = g;

    // Ensure coordinates are up to date
    m.setPosition({
      lat: g.coordinates[1],
      lng: g.coordinates[0],
    });
  });

  markerStore.on('location_created', function (ev) {
    var mloc = rawEventToMarkerLocation(ev);
    _addMarker(mloc);
  });

  markerStore.on('location_removed', function (ev) {
    if (_markers[ev.locationId]) {
      var mToRemove = _markers[ev.locationId];
      _removeMarker(mToRemove);
    }
  });

  markerStore.on('location_status_changed', function (ev) {
    // Update icon according to new status.
    // Do this only if the marker has reached the map.
    if (_markers[ev.locationId]) {
      var m = _markers[ev.locationId];
      var mloc = m.get('location');
      // First update the stored MarkerLocation
      mloc.status = ev.data.newStatus;
      // Update icon accordingly
      m.setIcon(_chooseIcon(mloc));
    }
  });

  markerStore.on('location_type_changed', function (ev) {
    // Update icon according to new type.
    // Do this only if the marker has reached the map.
    if (_markers[ev.locationId]) {
      var m = _markers[ev.locationId];
      var mloc = m.get('location');
      // First update the stored MarkerLocation
      mloc.type = ev.data.newType;
      // Update icon accordingly
      m.setIcon(_chooseIcon(mloc));
    }
  });

  markerStore.on('location_entry_created', function (ev) {
    if (ev.data.isVisit && account.isMe(ev.user)) {
      // Add loc among visited locations if not visited before by this user.
      _visitedIds.add(ev.locationId);

      // Update marker icon to visited
      if (_markers[ev.locationId]) {
        var m = _markers[ev.locationId];
        var mloc = m.get('location');
        m.setIcon(_chooseIcon(mloc));
      }
    }
  });

  markerStore.on('location_entry_changed', function (ev) {
    // Change from non-visit to visit
    if (ev.data.newIsVisit && !ev.data.oldIsVisit && account.isMe(ev.user)) {
      // Add loc among visited locations if not visited before by this user.
      _visitedIds.add(ev.locationId);
      // Update marker icon to visited
      _updateIcon(ev.locationId);
    } else {
      // From visit to non-visit
      if (ev.data.oldIsVisit && !ev.data.newIsVisit) {
        _visitedIds.remove(ev.locationId);
        // Update marker icon to unvisited
        _updateIcon(ev.locationId);
      }
    }
  });

  // Listen possible location selections to add or update selected marker.
  locationsStore.on('updated', function (state) {
    if (state.selectedLocationId) {
      if (_selectedId === state.selectedLocationId) {
        // Already selected, do nothing.
      } else {
        // Unstyle the previous selected. Remove the previously selected
        // if it was hidden before.
        if (_selectedWasVisible) {
          _updateIcon(_selectedId);
        } else {
          _removeMarker(_markers[_selectedId]);
        }

        if (_markers[state.selectedLocationId]) {
          // New selection already on map. Restyle the new.
          _updateIcon(state.selectedLocationId);
          _selectedWasVisible = true;
        } else {
          // Marker not on the map. Use state's location if available.
          // If there is an attempt to add the selected marker on the map
          // afterwards, ensure elsewhere that _selectedWasVisible is flipped.
          if (state.selectedMarkerLocation) {
            _addMarker(state.selectedMarkerLocation);
            _selectedWasVisible = false;
          }
        }

        // Show location title
        _ensureLabel(state.selectedLocationId);
        _selectedId = state.selectedLocationId;
      }
    } else {
      // Null selected.
      // Clear select-styled markers.
      if (_selectedWasVisible) {
        _updateIcon(_selectedId);
      } else {
        _removeMarker(_markers[_selectedId]);
      }
      _selectedId = null;
    }
  });

  // Listen map type change to invert label text colors.
  map.addListener('maptypeid_changed', function () {
    labels.updateMarkerLabels(_markers, map.getMapTypeId());
  });

  map.addListener('zoom_changed', function () {
    var z = map.getZoom();

    Object.keys(_markers).forEach(function (k) {
      var m = _markers[k];
      var mloc = m.get('location');

      // Listen zoom level change to only show labels of locations
      // with higher level than current zoom level.
      if (mloc.layer < z - 1) {
        // Ensure that label is visible.
        labels.ensureLabel(m, map.getMapTypeId());
      } else {
        labels.hideLabel(m);
      }

      // Listen zoom level change to update symbols of locations
      // when all their children become visible or hidden.
      // Ensure that the correct marker icon is used.
      // They are zoom-sensitive.
      _updateIcon(k);
    });
  });

  // Track when map becomes usable.
  // See 'var _mapReady = false' above for details.
  google.maps.event.addListenerOnce(map, 'idle', function () {
    _mapReady = true;
  });

  // Public methods

  self.startLoading = function () {
    // Each idle, fetch a new set of locations.
    // The idle is emitted automatically after the initial map load.

    // Prevent duplicate listeners.
    if (_loaderListener !== null) {
      return;
    }

    // Each time map stops, fetch.
    _loaderListener = map.addListener('idle', _loadMarkers);
    // Load the first manually but only if the map is ready.
    // Map emits 'idle' when ready but user might not have been logged in yet.
    if (_mapReady) {
      _loadMarkers();
    }

    // Each time filter changes, fetch.
    filterStore.on('updated', _loadMarkers);

    // Fetch the list of visited locations as soon as possible.
    account.getVisitedLocationIds(function (err, ids) {
      if (err) {
        console.error(err);
        return;
      }

      _visitedIds.set(ids);
    });
  };

  self.stopLoading = function () {
    google.maps.event.removeListener(_loaderListener);
    filterStore.off('updated', _loadMarkers);
    _loaderListener = null;
  };

  self.removeAll = function () {
    // Clear the map.
    Object.keys(_markers).forEach(function (k) {
      _removeMarker(_markers[k]);
    });
  };

};
<|endoftext|>


<commit_before>define(['lib/domReady', 'lib/ajax'], function (domReady, callAjax) {
    var popup,
        overlay,
        closeButton;

    domReady(function () {
        window.addEventListener('resize', centerPopup);
        window.addEventListener('orientationchange', centerPopup);
    });

    function centerPopup() {
        if (typeof popup === 'undefined' || typeof closeButton === 'undefined') {
            return;
        }

        var viewportWidth = Math.floor(document.body.clientWidth / 100 * 85),
            viewportHeight = Math.floor(window.innerHeight / 100 * 85);

        popup.style.top = '';
        popup.style.left = '';

        popup.style.width = viewportWidth > popup.offsetWidth ? '' : viewportWidth + 'px';
        popup.style.marginLeft = popup.offsetWidth / 2 * -1 + 'px';

        popup.style.height = viewportHeight > popup.offsetHeight ? '' : viewportHeight + 'px';
        popup.style.marginTop = popup.offsetHeight / 2 * -1 + 'px';

        popup.style.top = '50%';
        popup.style.left = '50%';

        closeButton.style.top = popup.offsetTop - closeButton.offsetHeight/2 + 'px';
        closeButton.style.left = popup.offsetLeft + popup.offsetWidth - closeButton.offsetWidth/2 + 'px';
    }

    function hidePopupAndOverlayIfShown() {
        if (typeof popup === 'undefined' || typeof overlay === 'undefined' || typeof closeButton === 'undefined') {
            return;
        }

        popup.parentNode.removeChild(popup);
        overlay.parentNode.removeChild(overlay);
        closeButton.parentNode.removeChild(closeButton);
    }

    function processContent(content) {
        if (!content.match(/^https?:\/\//i)) {
            popup.className = popup.className.replace(/\bloading\b/, '');
            popup.innerHTML = content;
            return;
        }

        callAjax(content, function (responseText) {
            popup.className = popup.className.replace(/\bloading\b/, '');
            popup.innerHTML = responseText;
            centerPopup();
        });

        popup.className += ' loading';
    }

    function showCloseButton() {
        closeButton = document.createElement('DIV');
        closeButton.id = 'popup-close';
        closeButton.addEventListener('click', hidePopupAndOverlayIfShown);

        document.body.appendChild(closeButton);
    }

    function showOverlay() {
        overlay = document.createElement('DIV');
        overlay.id = 'modal-box-overlay';

        document.body.appendChild(overlay);
    }

    function showPopup(content) {
        popup = document.createElement('DIV');
        popup.id = 'modal-popup';

        processContent(content);

        document.body.appendChild(popup);
    }

    return function (content) {
        if (typeof content !== 'string' || content.length === 0) {
            return;
        }

        hidePopupAndOverlayIfShown();
        showOverlay();
        showPopup(content);
        showCloseButton();
        centerPopup();
    }
});
<commit_msg>Issue #772: Fix issue with re-opening modal popup<commit_after>define(['lib/domReady', 'lib/ajax'], function (domReady, callAjax) {
    var popup,
        overlay,
        closeButton;

    domReady(function () {
        window.addEventListener('resize', centerPopup);
        window.addEventListener('orientationchange', centerPopup);
    });

    function centerPopup() {
        if (typeof popup === 'undefined' || typeof closeButton === 'undefined') {
            return;
        }

        var viewportWidth = Math.floor(document.body.clientWidth / 100 * 85),
            viewportHeight = Math.floor(window.innerHeight / 100 * 85);

        popup.style.top = '';
        popup.style.left = '';

        popup.style.width = viewportWidth > popup.offsetWidth ? '' : viewportWidth + 'px';
        popup.style.marginLeft = popup.offsetWidth / 2 * -1 + 'px';

        popup.style.height = viewportHeight > popup.offsetHeight ? '' : viewportHeight + 'px';
        popup.style.marginTop = popup.offsetHeight / 2 * -1 + 'px';

        popup.style.top = '50%';
        popup.style.left = '50%';

        closeButton.style.top = popup.offsetTop - closeButton.offsetHeight/2 + 'px';
        closeButton.style.left = popup.offsetLeft + popup.offsetWidth - closeButton.offsetWidth/2 + 'px';
    }

    function hidePopupAndOverlayIfShown() {
        if (typeof popup !== 'undefined' && null !== popup.parentNode) {
            popup.parentNode.removeChild(popup);
        }

        if (typeof overlay !== 'undefined' && null !== overlay.parentNode) {
            overlay.parentNode.removeChild(overlay);
        }

        if (typeof closeButton !== 'undefined' && null !== closeButton.parentNode) {
            closeButton.parentNode.removeChild(closeButton);
        }
    }

    function processContent(content) {
        if (!content.match(/^https?:\/\//i)) {
            popup.className = popup.className.replace(/\bloading\b/, '');
            popup.innerHTML = content;
            return;
        }

        callAjax(content, function (responseText) {
            popup.className = popup.className.replace(/\bloading\b/, '');
            popup.innerHTML = responseText;
            centerPopup();
        });

        popup.className += ' loading';
    }

    function showCloseButton() {
        closeButton = document.createElement('DIV');
        closeButton.id = 'popup-close';
        closeButton.addEventListener('click', hidePopupAndOverlayIfShown);

        document.body.appendChild(closeButton);
    }

    function showOverlay() {
        overlay = document.createElement('DIV');
        overlay.id = 'modal-box-overlay';

        document.body.appendChild(overlay);
    }

    function showPopup(content) {
        popup = document.createElement('DIV');
        popup.id = 'modal-popup';

        processContent(content);

        document.body.appendChild(popup);
    }

    return function (content) {
        if (typeof content !== 'string' || content.length === 0) {
            return;
        }

        hidePopupAndOverlayIfShown();
        showOverlay();
        showPopup(content);
        showCloseButton();
        centerPopup();
    }
});
<|endoftext|>


<commit_before>import Koa from 'koa';
import convert from 'koa-convert';
import json from 'koa-json';
import bodyparser from 'koa-bodyparser';
import config from './config/config';
import {
	orm,
	router
} from './lib/index';
import cacheMiddle from './middleware/cache';
import xload from "./middleware/xload";
import RestQL from 'roas-restql';
import cors from "koa-cors";
import path from "path";
import logMiddle from "./middleware/log";
// import koajwt from "koa-jwt";
import bodyHandlerMiddle from "./middleware/body-handler";
import http from "http";
import Net from "net";
import IO from "koa-socket.io";

const app = new Koa();
const io = new IO({
	namespace: '/'
});

app.config = config;
app.keys = [config.http.secretKeyBase];

// not serve static when deploy
if (config.http.serveStatic) {
	app.use(convert(require('koa-static')(path.join(process.cwd(), '/public'))));
}
app.use(xload(app, {
	path: path.join(process.cwd(), '/public/assets/images/avatar'),
	upload: {
		encoding: 'utf-8',
		maxFieldsSize: 2 * 1024 * 1024,
		maxFields: 1000
	}
}));

app.use(logMiddle());
// body和错误处理必须是第一个use
app.use(bodyHandlerMiddle((err, ctx) => {
	ctx.log.error(err);
}));

//添加rest api支持
const restql = new RestQL(orm.sequelize.models);
app.use(convert(json()));
app.use(bodyparser());

app.use(cacheMiddle());
app.use(cors());

// app.use(koajwt({secret: config.http.secretKeyBase}).unless({path:[/^\/getToken/]}));

app.use(router().routes(), router().allowedMethods());
app.use(restql.routes());

if (process.argv[2] && process.argv[2][0] == 'c') {
	const repl = require('repl');
	repl.start({
		prompt: '> ',
		useGlobal: true
	}).on('exit', () => {
		process.exit();
	});
} else {
	(async() => {
		const connection = await orm.sequelize.sync();
		if (connection) {
			console.log('Connected to the database');

			if (config.http) {
				app.listen(config.http.port, () => {
					console.log('http server listener port:' + config.http.port);
				});
			}
			if (config.ws) {
				const wsServer = http.createServer(app.callback());
				wsServer.listen(config.ws.wsPort, 'localhost', () => {
					console.log('web socket server listener port:' + config.ws.wsPort);
				});
				io.start(wsServer);
				io.use((ctx, next) => {
					router(ctx, next);
				});
			}
			if (config.tcp && config.tcp.provider) {
				const socketServer = Net.createServer((socket) => {
					socket.on('data', async(data) => {
						let parser = require("./api/parsers/" + config.tcp.provider);
						await parser.execute(socket, data.toString());
					})
				});
				socketServer.listen(config.tcp.port, () => {
					console.info('socket server listener port: ' + config.tcp.port);
				});
			}
		}
	})();
}

export default app;<commit_msg>新增TCP Socket和Web Socket协议的支持<commit_after>import Koa from 'koa';
import convert from 'koa-convert';
import json from 'koa-json';
import bodyparser from 'koa-bodyparser';
import config from './config/config';
import {
	orm,
	router
} from './lib/index';
import cacheMiddle from './middleware/cache';
import xload from "./middleware/xload";
import RestQL from 'roas-restql';
import cors from "koa-cors";
import path from "path";
import logMiddle from "./middleware/log";
// import koajwt from "koa-jwt";
import bodyHandlerMiddle from "./middleware/body-handler";
import http from "http";
import Net from "net";
import IO from "koa-socket.io";

const app = new Koa();
const io = new IO({
	namespace: '/'
});

app.config = config;
app.keys = [config.http.secretKeyBase];

// not serve static when deploy
if (config.http.serveStatic) {
	app.use(convert(require('koa-static')(path.join(process.cwd(), '/public'))));
}
app.use(xload(app, {
	path: path.join(process.cwd(), '/public/assets/images/avatar'),
	upload: {
		encoding: 'utf-8',
		maxFieldsSize: 2 * 1024 * 1024,
		maxFields: 1000
	}
}));

app.use(logMiddle());
// body和错误处理必须是第一个use
app.use(bodyHandlerMiddle((err, ctx) => {
	ctx.log.error(err);
}));

//添加rest api支持
const restql = new RestQL(orm.sequelize.models);
app.use(convert(json()));
app.use(bodyparser());

app.use(cacheMiddle());
app.use(cors());

// app.use(koajwt({secret: config.http.secretKeyBase}).unless({path:[/^\/getToken/]}));

app.use(router().routes(), router().allowedMethods());
app.use(restql.routes());

if (process.argv[2] && process.argv[2][0] == 'c') {
	const repl = require('repl');
	repl.start({
		prompt: '> ',
		useGlobal: true
	}).on('exit', () => {
		process.exit();
	});
} else {
	(async() => {
		const connection = await orm.sequelize.sync();
		if (connection) {
			console.log('Connected to the database');

			if (config.http && config.http.isEnable) {
				app.listen(config.http.port, () => {
					console.log('http server listener port:' + config.http.port);
				});
			}
			if (config.ws && config.ws.isEnable) {
				const wsServer = http.createServer(app.callback());
				wsServer.listen(config.ws.wsPort, 'localhost', () => {
					console.log('web socket server listener port:' + config.ws.wsPort);
				});
				io.start(wsServer);
				io.use((ctx, next) => {
					router(ctx, next);
				});
			}
			if (config.tcp && config.tcp.isEnable && config.tcp.provider) {
				const socketServer = Net.createServer((socket) => {
					socket.on('data', async(data) => {
						let parser = require("./api/parsers/" + config.tcp.provider);
						await parser.execute(socket, data.toString());
					})
				});
				socketServer.listen(config.tcp.port, () => {
					console.info('socket server listener port: ' + config.tcp.port);
				});
			}
		}
	})();
}

export default app;<|endoftext|>


<commit_before>'use strict';

angular.module('myApp.addPost', ['ngRoute'])

.config(['$routeProvider', function($routeProvider) {
  $routeProvider.when('/addPost', {
    templateUrl: 'addPost/addPost.html',
    controller: 'AddPostCtrl'
  });
}])

.controller('AddPostCtrl', ['$scope', '$firebase', function($scope, $firebase) {
 	$scope.AddPost = function() {
 		var firebaseObj = new Firebase("https://blogz-on-fire.firebaseio.com/");
 		var fb = $firebase(firebaseObj);

 		var title = $scope.article.title;
 		var post = $scope.article.post;
 	}
}]);<commit_msg>using the Firebase API to save data<commit_after>'use strict';

angular.module('myApp.addPost', ['ngRoute'])

.config(['$routeProvider', function($routeProvider) {
  $routeProvider.when('/addPost', {
    templateUrl: 'addPost/addPost.html',
    controller: 'AddPostCtrl'
  });
}])

.controller('AddPostCtrl', ['$scope', '$firebase', function($scope, $firebase) {
 	$scope.AddPost = function() {
 		var firebaseObj = new Firebase("https://blogz-on-fire.firebaseio.com/");
 		var fb = $firebase(firebaseObj);

 		var title = $scope.article.title;
 		var post = $scope.article.post;

 		fb.$push({
    	title: title,
	    post: post
		}).then(function(ref) {
		    console.log(ref);
		}, function(error) {
		    console.log("Error:", error);
		});
 	}
}]);<|endoftext|>