<commit_before>const ytdl = require('..');
const path = require('path');
const fs = require('fs');
const assert = require('assert-diff');
const nock = require('./nock');
const sinon = require('sinon');

describe('ytdl.getInfo()', () => {
  let clock;
  before(() => { clock = sinon.useFakeTimers({ toFake: ['setTimeout'] }); });
  after(() => { clock.restore(); });

  describe('From a regular video', () => {
    const id = 'pJk0p-98Xzc';
    let expectedInfo;
    before(done => {
      fs.readFile(path.resolve(__dirname,
        `files/videos/${id}-vevo/expected-info.json`),
      'utf8', (err, body) => {
        assert.ifError(err);
        expectedInfo = JSON.parse(body);
        done();
      });
    });

    it('Retrieves correct metainfo', async() => {
      const scope = nock(id, {
        type: 'vevo',
        get_video_info: true,
        player: true,
      });

      let info = await ytdl.getInfo(id);
      scope.done();
      assert.ok(info.description.length);
      assert.equal(info.formats.length, expectedInfo.formats.length);
    });

    describe('Use ytdl.getBasicInfo()', () => {
      it('Retrieves just enough metainfo', async() => {
        const scope = nock(id, {
          type: 'vevo',
          get_video_info: true,
        });

        let info = await ytdl.getBasicInfo(id);
        scope.done();
        assert.equal(info.formats.length, expectedInfo.formats.length);
        assert.notEqual(info.formats[0].url, expectedInfo.formats[0].url);
      });

      describe('Followed by ytdl.getInfo()', () => {
        it('Does not make extra requests', async() => {
          const scope = nock(id, {
            type: 'vevo',
            get_video_info: true,
            player: true,
          });

          let info = await ytdl.getBasicInfo(id);
          assert.equal(info.formats.length, expectedInfo.formats.length);
          assert.notEqual(info.formats[0].url, expectedInfo.formats[0].url);
          let info2 = await ytdl.getInfo(id);
          scope.done();
          assert.equal(info2.formats[0].url, expectedInfo.formats[0].url);
        });
      });

      describe('Use `ytdl.downloadFromInfo()`', () => {
        it('Throw error', async() => {
          const scope = nock(id, {
            type: 'vevo',
            get_video_info: true,
          });

          let info = await ytdl.getBasicInfo(id);
          scope.done();
          assert.throws(() => {
            ytdl.downloadFromInfo(info);
          }, /Cannot use/);
        });
      });
    });

    describe('Use `ytdl.downloadFromInfo()`', () => {
      it('Retrives video file', done => {
        const stream = ytdl.downloadFromInfo(expectedInfo);
        let scope;
        stream.on('info', (info, format) => {
          scope = nock.url(format.url).reply(200);
        });
        stream.resume();
        stream.on('error', done);
        stream.on('end', () => {
          scope.done();
          done();
        });
      });
    });

    describe('Pass request options', () => {
      it('Request gets called with more headers', async() => {
        const scope = nock(id, {
          type: 'vevo',
          get_video_info: true,
          player: true,
          headers: { 'X-Hello': '42' },
        });

        await ytdl.getInfo(id, {
          requestOptions: { headers: { 'X-Hello': '42' } },
        });
        scope.done();
      });
    });

    describe('Using the promise API', () => {
      it('Retrieves correct metainfo', done => {
        const scope = nock(id, {
          type: 'vevo',
          get_video_info: true,
          player: true,
        });

        ytdl.getInfo(id)
          .then(info => {
            scope.done();
            assert.ok(info.description.length);
            assert.equal(info.formats.length, expectedInfo.formats.length);
            done();
          })
          .catch(done);
      });

      describe('On a video that fails', () => {
        const testId = '99999999999';

        it('Error is caught', done => {
          const scope = nock(testId, { type: 'nonexistent' });
          const p = ytdl.getInfo(testId);
          p.catch(err => {
            scope.done();
            assert.ok(err);
            assert.equal(err.message, 'This video is unavailable.');
            done();
          });
        });
      });
    });

    describe('Using the callback API', () => {
      it('Retrieves correct metainfo', done => {
        const scope = nock(id, {
          type: 'vevo',
          get_video_info: true,
          player: true,
        });

        ytdl.getInfo(id, (err, info) => {
          assert.ifError(err);
          scope.done();
          assert.ok(info.description.length);
          assert.equal(info.formats.length, expectedInfo.formats.length);
          done();
        });
      });

      describe('On a video that fails', () => {
        const testId = '99999999999';

        it('Error is caught', done => {
          const scope = nock(testId, { type: 'nonexistent' });
          ytdl.getInfo(testId, err => {
            scope.done();
            assert.ok(err);
            assert.equal(err.message, 'This video is unavailable.');
            done();
          });
        });
      });
    });

    describe('Called twice', () => {
      const testId = 'pJk0p-98Xzc';

      it('Makes requests once', async() => {
        const scope = nock(testId, {
          type: 'vevo',
          get_video_info: true,
          player: true,
        });

        let info1 = await ytdl.getInfo(testId);
        assert.ok(info1.description.length);
        assert.equal(info1.formats.length, expectedInfo.formats.length);
        let info2 = await ytdl.getInfo(testId);
        scope.done();
        assert.equal(info2, info1);
      });
    });
  });

  describe('From a non-existant video', () => {
    const id = '99999999999';

    it('Should give an error', async() => {
      const scope = nock(id, { type: 'nonexistent' });
      await assert.rejects(ytdl.getInfo(id), /This video is unavailable./);
      scope.done();
    });
  });

  describe('From an age restricted video', () => {
    const id = 'rIqCiJKWx9I';
    const expectedInfo = require(`./files/videos/${id}-age-restricted/expected-info.json`);

    it('Returns correct video metainfo', async() => {
      const scope = nock(id, {
        type: 'age-restricted',
        embed: true,
        player: true,
        get_video_info: true,
      });
      let info = await ytdl.getInfo(id);
      scope.done();
      assert.equal(info.formats.length, expectedInfo.formats.length);
      assert.ok(info.age_restricted);
    });

    it('Fails gracefully when unable to find config', async() => {
      const scope = nock(id, {
        type: 'age-restricted',
        embed: [true, 200, 'no-config'],
      });
      await assert.rejects(ytdl.getInfo(id), /Could not find player config/);
      scope.done();
    });
  });

  describe('From a video that was live streamed but not currently live', () => {
    it('Returns correct video metainfo', async() => {
      const id = 'nu5uzMXfuLc';
      const scope = nock(id, {
        type: 'streamed',
        player: true,
        get_video_info: true,
      });
      let info = await ytdl.getInfo(id);
      scope.done();
      assert.equal(info.formats.length, 14);
    });
  });

  describe('From a video that is not embeddable outside of YouTube', () => {
    it('Returns correct video metainfo', async() => {
      const id = 'GFg8BP01F5Q';
      const scope = nock(id, {
        type: 'noembed',
        player: true,
        get_video_info: true,
      });
      let info = await ytdl.getInfo(id);
      scope.done();
      assert.equal(info.formats.length, 15);
    });
  });

  describe('From a rental', () => {
    const id = 'SyKPsFRP_Oc';
    it('Returns an error about it', async() => {
      const scope = nock(id, {
        type: 'rental',
        get_video_info: true,
      });
      await assert.rejects(ytdl.getInfo(id), /This video requires payment to watch/);
      scope.done();
    });
  });

  describe('With a bad video ID', () => {
    const id = 'bad';
    it('Returns an error', async() => {
      await assert.rejects(ytdl.getInfo(id), /No video id found: bad/);
    });
  });

  describe('When there is an error requesting one of the pages', () => {
    it('Fails gracefully when unable to get watch page', async() => {
      const id = '_HSylqgVYQI';
      const scope = nock(id, {
        type: 'regular',
        statusCode: 500,
      });
      await assert.rejects(
        ytdl.getInfo(id, { requestOptions: { maxRetries: 0 } }),
        /Status code: 500/,
      );
      scope.done();
    });

    it('Fails gracefully when unable to parse config', async() => {
      const id = '_HSylqgVYQI';
      const scope = nock(id, {
        type: 'regular',
        watch: 'bad-config',
      });
      await assert.rejects(ytdl.getInfo(id), /Error parsing config:/);
      scope.done();
    });

    it('Fails gracefully when unable to get embed page', async() => {
      const id = 'rIqCiJKWx9I';
      const scope = nock(id, {
        type: 'age-restricted',
        embed: [true, 500],
      });
      await assert.rejects(
        ytdl.getInfo(id, { requestOptions: { maxRetries: 0 } }),
        /Status code: 500/,
      );
      scope.done();
    });

    it('Fails gracefully when unable to get video info page', async() => {
      const id = '_HSylqgVYQI';
      const scope = nock(id, {
        type: 'regular',
        get_video_info: [true, 500],
      });
      await assert.rejects(
        ytdl.getInfo(id, { requestOptions: { maxRetries: 0 } }),
        /Status code: 500/,
      );
      scope.done();
    });

    it('Fails gracefully when get video info page errors', async() => {
      const id = 'pJk0p-98Xzc';
      const scope = nock(id, {
        type: 'vevo',
        watch: 'no-formats',
        get_video_info: [true, 200, 'error'],
      });
      await assert.rejects(
        ytdl.getInfo(id),
        /Playback on other websites has been disabled by the video owner./,
      );
      scope.done();
    });

    it('Fails gracefully when unable to get html5player tokens', async() => {
      const id = '_HSylqgVYQI';
      const scope = nock(id, {
        type: 'regular',
        get_video_info: true,
        player: [true, 500],
      });
      await assert.rejects(
        ytdl.getInfo(id, { requestOptions: { maxRetries: 0 } }),
        /Status code: 500/,
      );
      scope.done();
    });

    it('Fails gracefully when unable to get m3u8 playlist', async() => {
      const id = 'hHW1oY26kxQ';
      const scope = nock(id, {
        type: 'live',
        dashmpd: true,
        m3u8: [true, 500],
        get_video_info: true,
        player: true,
      });
      await assert.rejects(
        ytdl.getInfo(id, { requestOptions: { maxRetries: 0 } }),
        /Status code: 500/,
      );
      scope.done();
    });

    it('Fails gracefully when no formats are found', async() => {
      const id = 'pJk0p-98Xzc';
      const scope = nock(id, {
        type: 'vevo',
        watch: 'no-formats',
        get_video_info: [true, 200, 'no-formats'],
      });
      await assert.rejects(
        ytdl.getInfo(id),
        /This video is unavailable/,
      );
      scope.done();
    });

    it('Fails gracefully when unable to parse player_response', async() => {
      const id = '_HSylqgVYQI';
      const scope = nock(id, {
        type: 'regular',
        watch: 'bad-player-response',
        get_video_info: true,
      });
      await assert.rejects(
        ytdl.getInfo(id),
        /Error parsing `player_response`:/,
      );
      scope.done();
    });
  });
});
<commit_msg>test: add test for callback errors<commit_after>const ytdl = require('..');
const path = require('path');
const fs = require('fs');
const assert = require('assert-diff');
const nock = require('./nock');
const sinon = require('sinon');

describe('ytdl.getInfo()', () => {
  let clock;
  before(() => { clock = sinon.useFakeTimers({ toFake: ['setTimeout'] }); });
  after(() => { clock.restore(); });

  describe('From a regular video', () => {
    const id = 'pJk0p-98Xzc';
    let expectedInfo;
    before(done => {
      fs.readFile(path.resolve(__dirname,
        `files/videos/${id}-vevo/expected-info.json`),
      'utf8', (err, body) => {
        assert.ifError(err);
        expectedInfo = JSON.parse(body);
        done();
      });
    });

    it('Retrieves correct metainfo', async() => {
      const scope = nock(id, {
        type: 'vevo',
        get_video_info: true,
        player: true,
      });

      let info = await ytdl.getInfo(id);
      scope.done();
      assert.ok(info.description.length);
      assert.equal(info.formats.length, expectedInfo.formats.length);
    });

    describe('Use ytdl.getBasicInfo()', () => {
      it('Retrieves just enough metainfo', async() => {
        const scope = nock(id, {
          type: 'vevo',
          get_video_info: true,
        });

        let info = await ytdl.getBasicInfo(id);
        scope.done();
        assert.equal(info.formats.length, expectedInfo.formats.length);
        assert.notEqual(info.formats[0].url, expectedInfo.formats[0].url);
      });

      describe('Followed by ytdl.getInfo()', () => {
        it('Does not make extra requests', async() => {
          const scope = nock(id, {
            type: 'vevo',
            get_video_info: true,
            player: true,
          });

          let info = await ytdl.getBasicInfo(id);
          assert.equal(info.formats.length, expectedInfo.formats.length);
          assert.notEqual(info.formats[0].url, expectedInfo.formats[0].url);
          let info2 = await ytdl.getInfo(id);
          scope.done();
          assert.equal(info2.formats[0].url, expectedInfo.formats[0].url);
        });
      });

      describe('Use `ytdl.downloadFromInfo()`', () => {
        it('Throw error', async() => {
          const scope = nock(id, {
            type: 'vevo',
            get_video_info: true,
          });

          let info = await ytdl.getBasicInfo(id);
          scope.done();
          assert.throws(() => {
            ytdl.downloadFromInfo(info);
          }, /Cannot use/);
        });
      });
    });

    describe('Use `ytdl.downloadFromInfo()`', () => {
      it('Retrives video file', done => {
        const stream = ytdl.downloadFromInfo(expectedInfo);
        let scope;
        stream.on('info', (info, format) => {
          scope = nock.url(format.url).reply(200);
        });
        stream.resume();
        stream.on('error', done);
        stream.on('end', () => {
          scope.done();
          done();
        });
      });
    });

    describe('Pass request options', () => {
      it('Request gets called with more headers', async() => {
        const scope = nock(id, {
          type: 'vevo',
          get_video_info: true,
          player: true,
          headers: { 'X-Hello': '42' },
        });

        await ytdl.getInfo(id, {
          requestOptions: { headers: { 'X-Hello': '42' } },
        });
        scope.done();
      });
    });

    describe('Using the promise API', () => {
      it('Retrieves correct metainfo', done => {
        const scope = nock(id, {
          type: 'vevo',
          get_video_info: true,
          player: true,
        });

        ytdl.getInfo(id)
          .then(info => {
            scope.done();
            assert.ok(info.description.length);
            assert.equal(info.formats.length, expectedInfo.formats.length);
            done();
          })
          .catch(done);
      });

      describe('On a video that fails', () => {
        const testId = '99999999999';

        it('Error is caught', done => {
          const scope = nock(testId, { type: 'nonexistent' });
          const p = ytdl.getInfo(testId);
          p.catch(err => {
            scope.done();
            assert.ok(err);
            assert.equal(err.message, 'This video is unavailable.');
            done();
          });
        });
      });
    });

    describe('Using the callback API', () => {
      it('Retrieves correct metainfo', done => {
        const scope = nock(id, {
          type: 'vevo',
          get_video_info: true,
          player: true,
        });

        ytdl.getInfo(id, (err, info) => {
          assert.ifError(err);
          scope.done();
          assert.ok(info.description.length);
          assert.equal(info.formats.length, expectedInfo.formats.length);
          done();
        });
      });

      describe('On a video that fails', () => {
        const testId = '99999999999';

        it('Error is caught', done => {
          const scope = nock(testId, { type: 'nonexistent' });
          ytdl.getInfo(testId, err => {
            scope.done();
            assert.ok(err);
            assert.equal(err.message, 'This video is unavailable.');
            done();
          });
        });
      });
    });

    describe('Called twice', () => {
      const testId = 'pJk0p-98Xzc';

      it('Makes requests once', async() => {
        const scope = nock(testId, {
          type: 'vevo',
          get_video_info: true,
          player: true,
        });

        let info1 = await ytdl.getInfo(testId);
        assert.ok(info1.description.length);
        assert.equal(info1.formats.length, expectedInfo.formats.length);
        let info2 = await ytdl.getInfo(testId);
        scope.done();
        assert.equal(info2, info1);
      });
    });
  });

  describe('From a non-existant video', () => {
    const id = '99999999999';

    it('Should give an error', async() => {
      const scope = nock(id, { type: 'nonexistent' });
      await assert.rejects(ytdl.getInfo(id), /This video is unavailable./);
      scope.done();
    });
  });

  describe('From an age restricted video', () => {
    const id = 'rIqCiJKWx9I';
    const expectedInfo = require(`./files/videos/${id}-age-restricted/expected-info.json`);

    it('Returns correct video metainfo', async() => {
      const scope = nock(id, {
        type: 'age-restricted',
        embed: true,
        player: true,
        get_video_info: true,
      });
      let info = await ytdl.getInfo(id);
      scope.done();
      assert.equal(info.formats.length, expectedInfo.formats.length);
      assert.ok(info.age_restricted);
    });

    it('Fails gracefully when unable to find config', async() => {
      const scope = nock(id, {
        type: 'age-restricted',
        embed: [true, 200, 'no-config'],
      });
      await assert.rejects(ytdl.getInfo(id), /Could not find player config/);
      scope.done();
    });
  });

  describe('From a video that was live streamed but not currently live', () => {
    it('Returns correct video metainfo', async() => {
      const id = 'nu5uzMXfuLc';
      const scope = nock(id, {
        type: 'streamed',
        player: true,
        get_video_info: true,
      });
      let info = await ytdl.getInfo(id);
      scope.done();
      assert.equal(info.formats.length, 14);
    });
  });

  describe('From a video that is not embeddable outside of YouTube', () => {
    it('Returns correct video metainfo', async() => {
      const id = 'GFg8BP01F5Q';
      const scope = nock(id, {
        type: 'noembed',
        player: true,
        get_video_info: true,
      });
      let info = await ytdl.getInfo(id);
      scope.done();
      assert.equal(info.formats.length, 15);
    });
  });

  describe('From a rental', () => {
    const id = 'SyKPsFRP_Oc';
    it('Returns an error about it', async() => {
      const scope = nock(id, {
        type: 'rental',
        get_video_info: true,
      });
      await assert.rejects(ytdl.getInfo(id), /This video requires payment to watch/);
      scope.done();
    });
  });

  describe('With a bad video ID', () => {
    const id = 'bad';
    it('Returns an error', async() => {
      await assert.rejects(ytdl.getInfo(id), /No video id found: bad/);
    });
  });

  describe('When there is an error requesting one of the pages', () => {
    it('Fails gracefully when unable to get watch page', async() => {
      const id = '_HSylqgVYQI';
      const scope = nock(id, {
        type: 'regular',
        statusCode: 500,
      });
      await assert.rejects(
        ytdl.getInfo(id, { requestOptions: { maxRetries: 0 } }),
        /Status code: 500/,
      );
      scope.done();
    });

    it('Fails gracefully when unable to parse config', async() => {
      const id = '_HSylqgVYQI';
      const scope = nock(id, {
        type: 'regular',
        watch: 'bad-config',
      });
      await assert.rejects(ytdl.getInfo(id), /Error parsing config:/);
      scope.done();
    });

    it('Fails gracefully when unable to get embed page', async() => {
      const id = 'rIqCiJKWx9I';
      const scope = nock(id, {
        type: 'age-restricted',
        embed: [true, 500],
      });
      await assert.rejects(
        ytdl.getInfo(id, { requestOptions: { maxRetries: 0 } }),
        /Status code: 500/,
      );
      scope.done();
    });

    it('Fails gracefully when unable to get video info page', async() => {
      const id = '_HSylqgVYQI';
      const scope = nock(id, {
        type: 'regular',
        get_video_info: [true, 500],
      });
      await assert.rejects(
        ytdl.getInfo(id, { requestOptions: { maxRetries: 0 } }),
        /Status code: 500/,
      );
      scope.done();
    });

    it('Fails gracefully when get video info page errors', async() => {
      const id = 'pJk0p-98Xzc';
      const scope = nock(id, {
        type: 'vevo',
        watch: 'no-formats',
        get_video_info: [true, 200, 'error'],
      });
      await assert.rejects(
        ytdl.getInfo(id),
        /Playback on other websites has been disabled by the video owner./,
      );
      scope.done();
    });

    it('Fails gracefully when unable to get html5player tokens', async() => {
      const id = '_HSylqgVYQI';
      const scope = nock(id, {
        type: 'regular',
        get_video_info: true,
        player: [true, 500],
      });
      await assert.rejects(
        ytdl.getInfo(id, { requestOptions: { maxRetries: 0 } }),
        /Status code: 500/,
      );
      scope.done();
    });

    it('Fails gracefully when unable to get m3u8 playlist', async() => {
      const id = 'hHW1oY26kxQ';
      const scope = nock(id, {
        type: 'live',
        dashmpd: true,
        m3u8: [true, 500],
        get_video_info: true,
        player: true,
      });
      await assert.rejects(
        ytdl.getInfo(id, { requestOptions: { maxRetries: 0 } }),
        /Status code: 500/,
      );
      scope.done();
    });

    it('Fails gracefully when no formats are found', async() => {
      const id = 'pJk0p-98Xzc';
      const scope = nock(id, {
        type: 'vevo',
        watch: 'no-formats',
        get_video_info: [true, 200, 'no-formats'],
      });
      await assert.rejects(
        ytdl.getInfo(id),
        /This video is unavailable/,
      );
      scope.done();
    });

    it('Fails gracefully when unable to parse player_response', async() => {
      const id = '_HSylqgVYQI';
      const scope = nock(id, {
        type: 'regular',
        watch: 'bad-player-response',
        get_video_info: true,
      });
      await assert.rejects(
        ytdl.getInfo(id),
        /Error parsing `player_response`:/,
      );
      scope.done();
    });

    it('Able to catch error using callback', () => {
      const id = 'pJk0p-98Xzc';
      const scope = nock(id, {
        type: 'vevo',
        watch: 'no-formats',
        get_video_info: [true, 200, 'no-formats'],
      });
      ytdl.getInfo(id, err => {
        scope.done();
        assert.ok(err);
        assert.ok(/This video is unavailable/.test(err.message));
      });
    });
  });
});
<|endoftext|>


<commit_before><commit_msg>r116dev<commit_after><|endoftext|>


<commit_before>module.exports = miniglob

var fs = require("graceful-fs")
, Minimatch = require("minimatch").Minimatch
, inherits = require("inherits")
, EE = require("events").EventEmitter
, FastList = require("fast-list")
, path = require("path")
, isDir = {}

// Globbing is a *little* bit different than just matching, in some
// key ways.
//
// First, and importantly, it matters a great deal whether a pattern
// is "absolute" or "relative".  Absolute patterns are patterns that
// start with / on unix, or a full device/unc path on windows.
//
// Second, globs interact with the actual filesystem, so being able
// to stop searching as soon as a match is no longer possible is of
// the utmost importance.  It would not do to traverse a large file
// tree, and then eliminate all but one of the options, if it could
// be possible to skip the traversal early.

// Get a Minimatch object from the pattern and options.  Then, starting
// from the options.root or the cwd, read the dir, and do a partial
// match on all the files if it's a dir, or a regular match if it's not.

function miniglob (pattern, options, cb) {
  if (typeof options === "function") cb = options, options = {}
  if (!options) options = {}

  var g = new Miniglob(pattern, options)
  if (typeof cb === "function") {
    g.on("error", cb)
    g.on("end", function (matches) { cb(null, matches) })
  }
  return g
}

miniglob.Miniglob = Miniglob
inherits(Miniglob, EE)
function Miniglob (pattern, options) {
  options = options || {}

  var cwd = this.cwd = options.cwd =
    options.cwd || process.cwd()

  this.root = options.root =
    options.root || path.resolve(cwd, "/")

  if (!pattern) {
    throw new Error("must provide pattern")
  }

  var mm = this.minimatch = new Minimatch(pattern, options)
  options = this.options = mm.options
  pattern = this.pattern = mm.pattern

  this.error = null

  this.matches = new FastList()
  EE.call(this)
  var me = this
  this._process(this.cwd, 1, this._finish.bind(this))
}

Miniglob.prototype._finish = _finish
function _finish () {
  var me = this
  if (me.options.debug) {
    console.error("!!! GLOB top level cb", me)
  }
  if (me.options.nonull && me.matches.length === 0) {
    return me.emit("end", [pattern])
  }

  var found = me.matches.slice()

  found = found.map(function (m) {
    if (m.indexOf(me.options.cwd) === 0) {
      m = m.substr(me.options.cwd.length + 1)
    }
    return m
  })

  if (!me.options.mark) return next()

  // mark all directories with a /.
  // This may involve some stat calls for things that are unknown.
  var needStat = []
  found = found.map(function (f) {
    if (isDir[f] === undefined) needStat.push(f)
    else if (isDir[f] && f.slice(-1) !== "/") f += "/"
    return f
  })
  var c = needStat.length
  if (c === 0) return next()

  var stat = me.options.follow ? "stat" : "lstat"
  needStat.forEach(function (f) {
    fs[stat](f, function (er, st) {
      // ignore errors.  if the user only wants to show
      // existing files, then set options.stat to exclude anything
      // that doesn't exist.
      if (st && st.isDirectory()) {
        found.splice(found.indexOf(f), 1, f + "/")
      }
      if (-- c <= 0) return next()
    })
  })

  function next () {
    if (!me.options.nosort) {
      found = found.sort(function (a, b) {
        return a > b ? 1 : a < b ? -1 : 0
      })
    }
    me.emit("end", found)
  }
}

Miniglob.prototype._process = _process
function _process (f, depth, cb) {
  var pref = depth + new Array(depth + 1).join(" ") + "GLOB "
  var me = this

  // if f matches, then it's a match.  emit it, move on.
  // if it *partially* matches, then it might be a dir.
  //
  // possible optimization: don't just minimatch everything
  // against the full pattern.  if a bit of the pattern is
  // not magical, it'd be good to reduce the number of stats
  // that had to be made.  so, in the pattern: "a/*/b", we could
  // readdir a, then stat a/<child>/b in all of them.
  //
  // however, that'll require a lot of muddying between minimatch
  // and miniglob, and at least for the time being, it's kind of nice to
  // keep them a little bit separate.

  // if this thing is a match, then add to the matches list.
  var match = me.minimatch.match(f)
  if (!match) return me._processPartial(f, depth, cb)

  if (match) {
    if (me.options.debug) {
      console.error(pref + " %s matches %s", f, me.pattern)
    }
    // make sure it exists if asked.
    if (me.options.stat) {
      var stat = me.options.follow ? "stat" : "lstat"
      fs[stat](f, function (er, st) {
        if (er) return cb()
        isDir[f] = st.isDirectory()
        emitMatch()
      })
    } else process.nextTick(emitMatch)

    return

    function emitMatch () {
      if (me.options.debug) {
        console.error(pref, "emitting match", f)
      }
      me.matches.push(f)
      me.emit("match", f)
      // move on, since it might also be a partial match
      // eg, a/**/c matches both a/c and a/c/d/c
      me._processPartial(f, depth, cb)
    }
  }

 }

Miniglob.prototype._processPartial = function _processPartial (f, depth, cb) {

  var me = this
  var pref = depth + new Array(depth + 1).join(" ") + "GLOB "

  var partial = me.minimatch.match(f, true)
  if (!partial) {
    if (me.options.debug) console.error(pref + "not a partial", f)

    // if not a match or partial match, just move on.
    return cb()
  }

  // partial match
  // however, this only matters if it's a dir.
  //if (me.options.debug)
  if (me.options.debug) {
    console.error(pref, "got a partial", f)
  }
  me.emit("partial", f)

  me._processDir(f, depth, cb)
}

Miniglob.prototype._processDir = function _processDir (f, depth, cb) {

  var me = this
  var pref = depth + new Array(depth + 1).join(" ") + "GLOB "

  // now the fun stuff.
  // if it's a dir, then we'll read all the children, and process them.
  // if it's not a dir, or we can't access it, then it'll fail.
  // We log a warning for EACCES and EPERM, but ENOTDIR and ENOENT are
  // expected and fine.

  fs.readdir(f, function (er, children) {
    if (er) switch (er.code) {
      case "ENOENT":
      case "ENOTDIR":
        isDir[f] = false
        return cb()
      default:
        if (!me.options.silent) console.error("miniglob error", er)
        if (me.options.strict) return cb(er)
        return cb()
    }

    // at this point, we know it's a dir, so save a stat later if
    // mark is set.
    isDir[f] = true

    if (-1 === children.indexOf(".")) children.push(".")
    if (-1 === children.indexOf("..")) children.push("..")

    var count = children.length
    if (me.options.debug) {
      console.error(pref + "count=%d %s", count, f, children)
    }

    if (count === 0) {
      //if (me.options.debug)
      if (me.options.debug) {
        console.error("no children?", children, f)
      }
      return then(f)()
    }

    children.forEach(function (c) {
      //if (c === "." || c === "..") {
      //  count --
      //  return
      //}
      if (me.options.debug) {
        console.error(pref + " processing", f + "/" + c)
      }
      me._process(f + "/" + c, depth + 1, then(f + "/" + c))
    })

    function then (f) { return function (er) {
      count --
      if (me.options.debug) {
        console.error("%s THEN %s", pref, f, count, count <= 0 ? "done" : "not done")
      }
      if (me.error) return
      if (er) return me.emit("error", me.error = er)
      if (count <= 0) {
        cb()
      }
    }}
  })
}

<commit_msg>add abortion<commit_after>module.exports = miniglob

var fs = require("graceful-fs")
, Minimatch = require("minimatch").Minimatch
, inherits = require("inherits")
, EE = require("events").EventEmitter
, FastList = require("fast-list")
, path = require("path")
, isDir = {}

// Globbing is a *little* bit different than just matching, in some
// key ways.
//
// First, and importantly, it matters a great deal whether a pattern
// is "absolute" or "relative".  Absolute patterns are patterns that
// start with / on unix, or a full device/unc path on windows.
//
// Second, globs interact with the actual filesystem, so being able
// to stop searching as soon as a match is no longer possible is of
// the utmost importance.  It would not do to traverse a large file
// tree, and then eliminate all but one of the options, if it could
// be possible to skip the traversal early.

// Get a Minimatch object from the pattern and options.  Then, starting
// from the options.root or the cwd, read the dir, and do a partial
// match on all the files if it's a dir, or a regular match if it's not.

function miniglob (pattern, options, cb) {
  if (typeof options === "function") cb = options, options = {}
  if (!options) options = {}

  var g = new Miniglob(pattern, options)
  if (typeof cb === "function") {
    g.on("error", cb)
    g.on("end", function (matches) { cb(null, matches) })
  }
  return g
}

miniglob.Miniglob = Miniglob
inherits(Miniglob, EE)
function Miniglob (pattern, options) {
  options = options || {}

  var cwd = this.cwd = options.cwd =
    options.cwd || process.cwd()

  this.root = options.root =
    options.root || path.resolve(cwd, "/")

  if (!pattern) {
    throw new Error("must provide pattern")
  }

  var mm = this.minimatch = new Minimatch(pattern, options)
  options = this.options = mm.options
  pattern = this.pattern = mm.pattern

  this.error = null
  this.aborted = false

  this.matches = new FastList()
  EE.call(this)
  var me = this
  this._process(this.cwd, 1, this._finish.bind(this))
}

Miniglob.prototype._finish = _finish
function _finish () {
  var me = this
  if (me.options.debug) {
    console.error("!!! GLOB top level cb", me)
  }
  if (me.options.nonull && me.matches.length === 0) {
    return me.emit("end", [pattern])
  }

  var found = me.matches.slice()

  found = found.map(function (m) {
    if (m.indexOf(me.options.cwd) === 0) {
      m = m.substr(me.options.cwd.length + 1)
    }
    return m
  })

  if (!me.options.mark) return next()

  // mark all directories with a /.
  // This may involve some stat calls for things that are unknown.
  var needStat = []
  found = found.map(function (f) {
    if (isDir[f] === undefined) needStat.push(f)
    else if (isDir[f] && f.slice(-1) !== "/") f += "/"
    return f
  })
  var c = needStat.length
  if (c === 0) return next()

  var stat = me.options.follow ? "stat" : "lstat"
  needStat.forEach(function (f) {
    fs[stat](f, function (er, st) {
      // ignore errors.  if the user only wants to show
      // existing files, then set options.stat to exclude anything
      // that doesn't exist.
      if (st && st.isDirectory()) {
        found.splice(found.indexOf(f), 1, f + "/")
      }
      if (-- c <= 0) return next()
    })
  })

  function next () {
    if (!me.options.nosort) {
      found = found.sort(function (a, b) {
        return a > b ? 1 : a < b ? -1 : 0
      })
    }
    me.emit("end", found)
  }
}

Miniglob.prototype.abort = abort
function abort () {
  this.aborted = true
  this.emit("abort")
}

Miniglob.prototype._process = _process
function _process (f, depth, cb) {
  if (this.aborted) return cb()

  var pref = depth + new Array(depth + 1).join(" ") + "GLOB "
  var me = this

  // if f matches, then it's a match.  emit it, move on.
  // if it *partially* matches, then it might be a dir.
  //
  // possible optimization: don't just minimatch everything
  // against the full pattern.  if a bit of the pattern is
  // not magical, it'd be good to reduce the number of stats
  // that had to be made.  so, in the pattern: "a/*/b", we could
  // readdir a, then stat a/<child>/b in all of them.
  //
  // however, that'll require a lot of muddying between minimatch
  // and miniglob, and at least for the time being, it's kind of nice to
  // keep them a little bit separate.

  // if this thing is a match, then add to the matches list.
  var match = me.minimatch.match(f)
  if (!match) return me._processPartial(f, depth, cb)

  if (match) {
    if (me.options.debug) {
      console.error(pref + " %s matches %s", f, me.pattern)
    }
    // make sure it exists if asked.
    if (me.options.stat) {
      var stat = me.options.follow ? "stat" : "lstat"
      fs[stat](f, function (er, st) {
        if (er) return cb()
        isDir[f] = st.isDirectory()
        emitMatch()
      })
    } else process.nextTick(emitMatch)

    return

    function emitMatch () {
      if (me.options.debug) {
        console.error(pref, "emitting match", f)
      }
      me.matches.push(f)
      me.emit("match", f)
      // move on, since it might also be a partial match
      // eg, a/**/c matches both a/c and a/c/d/c
      me._processPartial(f, depth, cb)
    }
  }

 }

Miniglob.prototype._processPartial = _processPartial
function _processPartial (f, depth, cb) {
  if (this.aborted) return cb()

  var me = this
  var pref = depth + new Array(depth + 1).join(" ") + "GLOB "

  var partial = me.minimatch.match(f, true)
  if (!partial) {
    if (me.options.debug) console.error(pref + "not a partial", f)

    // if not a match or partial match, just move on.
    return cb()
  }

  // partial match
  // however, this only matters if it's a dir.
  //if (me.options.debug)
  if (me.options.debug) {
    console.error(pref, "got a partial", f)
  }
  me.emit("partial", f)

  me._processDir(f, depth, cb)
}

Miniglob.prototype._processDir = _processDir
function _processDir (f, depth, cb) {
  if (this.aborted) return cb()

  var me = this
  var pref = depth + new Array(depth + 1).join(" ") + "GLOB "

  // now the fun stuff.
  // if it's a dir, then we'll read all the children, and process them.
  // if it's not a dir, or we can't access it, then it'll fail.
  // We log a warning for EACCES and EPERM, but ENOTDIR and ENOENT are
  // expected and fine.

  fs.readdir(f, function (er, children) {
    if (er) switch (er.code) {
      case "ENOENT":
      case "ENOTDIR":
        isDir[f] = false
        return cb()
      default:
        if (!me.options.silent) console.error("miniglob error", er)
        if (me.options.strict) return cb(er)
        return cb()
    }

    // at this point, we know it's a dir, so save a stat later if
    // mark is set.
    isDir[f] = true

    if (-1 === children.indexOf(".")) children.push(".")
    if (-1 === children.indexOf("..")) children.push("..")

    var count = children.length
    if (me.options.debug) {
      console.error(pref + "count=%d %s", count, f, children)
    }

    if (count === 0) {
      if (me.options.debug) {
        console.error("no children?", children, f)
      }
      return then(f)()
    }

    children.forEach(function (c) {
      if (me.options.debug) {
        console.error(pref + " processing", f + "/" + c)
      }
      me._process(f + "/" + c, depth + 1, then(f + "/" + c))
    })

    function then (f) { return function (er) {
      count --
      if (me.options.debug) {
        console.error("%s THEN %s", pref, f, count, count <= 0 ? "done" : "not done")
      }
      if (me.error) return
      if (er) return me.emit("error", me.error = er)
      if (count <= 0) {
        cb()
      }
    }}
  })
}

<|endoftext|>


<commit_before>$(document).ready(function() {
  var shuffledArray = [];
  var twoDArray = [];
  var tileCoordinates = [];
  var time = 0;
  var timerID;
  var newGame = null;
  var events = null;

  var tileDimensions = {
    '4': 80,
    '5': 64,
    '6': 53
  }

  var gameType = '';

  function checkHighScore() {

    var times = {
      '4' : localStorage.getItem('4'),
      '5' : localStorage.getItem('5'),
      '6' : localStorage.getItem('6')
    }
    console.log('yes',times)
    for(var time in times) {
      if(times[time]) {
        var minutes = Math.floor(times[time] / 60) < 10 ? '0' + String(Math.floor(times[time] / 60)) : Math.floor(times[time] / 60);
        var seconds = (times[time] - minutes * 60) < 10 ? '0' + String(times[time] - minutes * 60) : times[time] - minutes * 60;
        $('.score'+time).text(minutes +':'+seconds);
      } else {
        $('.score'+time).text('--:--');
      }
    }
  }

  checkHighScore();

  function newHighScore() {
    var minutes = parseInt($('.clock').text().split(':')[0]);
    var seconds = parseInt($('.clock').text().split(':')[1]);
    var newScoreSeconds = 0;
    newScoreSeconds+= (minutes * 60) + seconds;
    console.log('set')
    if(localStorage.getItem(gameType) && localStorage.getItem(gameType) > newScoreSeconds ) {
      localStorage.setItem(gameType, newScoreSeconds);
      $('.score'+gameType).text($('.clock').text());
    } else if(!localStorage.getItem(gameType)) {
      localStorage.setItem(gameType, newScoreSeconds);
      $('.score'+gameType).text($('.clock').text());
    }
  }

  function startTimer() {
    var time = 0;
    clearInterval(timerID)
    timerID = setInterval(function() {
      time++;
      var minutes = Math.floor(time / 60) < 10 ? '0' + String(Math.floor(time / 60)) : Math.floor(time / 60);
      var seconds = (time - minutes * 60) < 10 ? '0' + String(time - minutes * 60) : time - minutes * 60;
      //console.log(seconds)
      $('.clock').text(minutes + ':' + seconds);
    }, 1000);
  }

  function stopTimer() {
    clearInterval(timerID);
  }

  function winGame() {
    stopTimer();
    var win = document.createElement('div');
    win.id = "win"
    win.className="winBox"
    win.innerHTML = '
 You Won!
'+
    '
 Play Again
';
    $('.playArea').append(win);
    $('#tiles').css('pointer-events','none');
    $('.playAgain').click(function(){
      $('#tiles').remove();
      $('.winBox').remove();
      $('.playArea').append(newGame);
      $('.click').click(function(){
        startGame($(this))
      });

    });
    newHighScore();
  }

  function shuffle(array,blankIndex, counter) {
    var shuffled = [];
    var newBlankIndex = [];
    var availableShifts = [];
    var shiftIndex;
    if(counter === 1000) {
      return array;
    } else {
      if(blankIndex[0]-1 >= 0) {
        availableShifts.push([blankIndex[0]-1,blankIndex[1]]);
      }
      if(blankIndex[0]+1 < array.length){
        availableShifts.push([blankIndex[0]+1,blankIndex[1]]);
      }
      if(blankIndex[1]-1 >= 0) {
        availableShifts.push([blankIndex[0],blankIndex[1]-1]);
      }
      if(blankIndex[1]+1 < array.length) {
        availableShifts.push([blankIndex[0],blankIndex[1]+1]);
      }
      shiftIndex = Math.floor(Math.random() * availableShifts.length)
      var shift = availableShifts[shiftIndex];
      array[blankIndex[0]][blankIndex[1]] = array[shift[0]][shift[1]];
      array[shift[0]][shift[1]] = undefined;
      blankIndex = availableShifts[shiftIndex];
      return  shuffle(array,blankIndex,counter+=1);
    }
  }

  function checkScore() {
    var q = 1
    for (var i = 0; i < twoDArray.length; i++) {
      for (var j = 0; j < twoDArray[i].length; j++) {
        if (twoDArray[i][j] !== q && twoDArray[i][j] !== undefined) {
          return false;
        } else {
          q++;
        }
      }
    }
    return true;
  }

  function moveTile(tile) {
    var t = tile.attr('id')
    var emptyIndex = 0;
    var tileIndex = 0;
    for (var i = 0; i < twoDArray.length; i++) {
      for (var j = 0; j < twoDArray[i].length; j++) {
        if (twoDArray[i][j] === undefined) {
          emptyIndex = [i, j];
        } else if (twoDArray[i][j] == t) {
          tileIndex = [i, j];
        }
      }
    }
    var numberOfMoves = 0;
    if (tileIndex[0] === emptyIndex[0]) {
      numberOfMoves = Math.abs(emptyIndex[1] - tileIndex[1]);
      if (tileIndex[1] < emptyIndex[1]) {
        console.log('move right')
        for (var i = tileIndex[1]; i <= emptyIndex[1]; i++) {
          $('#' + String(twoDArray[tileIndex[0]][i])).velocity({
            translateX: tileCoordinates[tileIndex[0]][i][0] + tileDimensions[gameType]
          },1)
          tileCoordinates[tileIndex[0]][i] = [tileCoordinates[tileIndex[0]][i][0] + tileDimensions[gameType], tileCoordinates[tileIndex[0]][i][1]];
        }
        var t1 = tileCoordinates[tileIndex[0]].splice(emptyIndex[1], 1);
        tileCoordinates[tileIndex[0]].splice(tileIndex[1], 0, t1[0]);
        var t2 = twoDArray[tileIndex[0]].splice(emptyIndex[1], 1);
        twoDArray[tileIndex[0]].splice(tileIndex[1], 0, t2[0]);
      } else if (tileIndex[1] > emptyIndex[1]) {
        console.log('Move Left')
        for (var i = tileIndex[1]; i >= emptyIndex[1]; i--) {
          $('#' + String(twoDArray[tileIndex[0]][i])).velocity({
            translateX: tileCoordinates[tileIndex[0]][i][0] - tileDimensions[gameType]
          },1)
          tileCoordinates[tileIndex[0]][i] = [tileCoordinates[tileIndex[0]][i][0] - tileDimensions[gameType], tileCoordinates[tileIndex[0]][i][1]]
        }
        var t1 = tileCoordinates[tileIndex[0]].splice(emptyIndex[1], 1);
        tileCoordinates[tileIndex[0]].splice(tileIndex[1], 0, t1[0]);
        var t2 = twoDArray[tileIndex[0]].splice(emptyIndex[1], 1);
        twoDArray[tileIndex[0]].splice(tileIndex[1], 0, t2[0]);
      }
    } else if (tileIndex[1] === emptyIndex[1]) {
      if (tileIndex[0] < emptyIndex[0]) {
        console.log('Move Down');
        var t2 = [];
        var t1 = [];
        for (var i = tileIndex[0]; i <= emptyIndex[0]; i++) {
          $('#' + String(twoDArray[i][tileIndex[1]])).velocity({
            translateY: tileCoordinates[i][tileIndex[1]][1] + tileDimensions[gameType]
          },1)
          tileCoordinates[i][tileIndex[1]] = [tileCoordinates[i][tileIndex[1]][0], tileCoordinates[i][tileIndex[1]][1] + tileDimensions[gameType]];
          t1.push(tileCoordinates[i].splice(tileIndex[1], 1)[0]);
          t2.push(twoDArray[i].splice(tileIndex[1], 1)[0]);
        }
        t1.unshift(t1.pop())
        t2.unshift(t2.pop())
        for (var i = 0, j = tileIndex[0]; i < t2.length; i++, j++) {
          tileCoordinates[j].splice(tileIndex[1], 0, t1[i]);
          twoDArray[j].splice(tileIndex[1], 0, t2[i]);
        }

      } else if (tileIndex[0] > emptyIndex[0]) {
        console.log('Move Up');
        var t2 = [];
        var t1 = [];
        for (var i = tileIndex[0]; i >= emptyIndex[0]; i--) {
          $('#' + String(twoDArray[i][tileIndex[1]])).velocity({
            translateY: tileCoordinates[i][tileIndex[1]][1] - tileDimensions[gameType]
          },1)
          tileCoordinates[i][tileIndex[1]] = [tileCoordinates[i][tileIndex[1]][0], tileCoordinates[i][tileIndex[1]][1] - tileDimensions[gameType]];
          t1.unshift(tileCoordinates[i].splice(tileIndex[1], 1)[0]);
          t2.unshift(twoDArray[i].splice(tileIndex[1], 1)[0]);
        }
        t1.push(t1.shift());
        t2.push(t2.shift());
        for (var i = 0, j = emptyIndex[0]; i < t2.length; i++, j++) {
          tileCoordinates[j].splice(tileIndex[1], 0, t1[i]);
          twoDArray[j].splice(tileIndex[1], 0, t2[i]);
        }
      }
    }

    if (checkScore() === true) {
      winGame();
    }
  }

  function startGame(reference) {
    newGame = $('.newGame');
    $('.newGame').remove();
    var tiles = document.createElement('div');
    tiles.id = "tiles";
    $('.playArea').append(tiles);
    var gameSize = reference.attr('id');
    gameType = gameSize;
    var numbers = [];
    for (var i = 0; i < (gameSize * gameSize) - 1; i++) {
      numbers.push(i + 1);
    }

    twoDArray = [];
    var q = 0;
    for (var i = 0; i < Math.sqrt(numbers.length); i++) {
      twoDArray[i] = [];
      tileCoordinates[i] = [];
      for (var j = 0; j < Math.sqrt(numbers.length); j++) {
        twoDArray[i][j] = numbers[q];
        tileCoordinates[i][j] = [0, 0];
        q++;
      }
    }
    shuffledArray = shuffle(twoDArray,[twoDArray.length-1,twoDArray[twoDArray.length-1].length-1],0);
    for (var i = 0; i < shuffledArray.length; i++) {
      for (var j = 0; j < shuffledArray[i].length; j++) {
        if(shuffledArray[i][j] !== undefined) {
          var tile = document.createElement('div');
          tile.id = String(shuffledArray[i][j]);
          tile.className = "tile tile" + gameSize;
          tile.innerHTML = String(shuffledArray[i][j]);
          $('#tiles').append(tile);
        } else {
          var tile = document.createElement('div');
          tile.id = String(shuffledArray[i][j]);
          tile.className = "blank tile" + gameSize;
          tile.innerHTML = '
';
          $('#tiles').append(tile);
        }
      }

      $('.clock').text('00:00');
      startTimer();
    }
    $('.tile').click(function() {
      moveTile($(this));
    });
  }

  $('.click').click(function() {
    startGame($(this));
  });

  $('.reset').click(function() {
    $('#tiles').remove();
    stopTimer();
    $('.playArea').append(newGame);
    $('.click').click(function() {
      startGame($(this));
    })
    $('.clock').text('00:00');
  })
})
<commit_msg>Added comments for clarity<commit_after>$(document).ready(function() {
  var shuffledArray = [];
  var twoDArray = [];
  var tileCoordinates = [];
  var time = 0;
  var timerID;
  var newGame = null;
  var events = null;

  var tileDimensions = {
    '4': 80,
    '5': 64,
    '6': 53
  }

  var gameType = '';

  function checkHighScore() {

    var times = {
      '4' : localStorage.getItem('4'),
      '5' : localStorage.getItem('5'),
      '6' : localStorage.getItem('6')
    }

    for(var time in times) {
      if(times[time]) {
        //if there is a recorded time in the local storage, set it in the high score
        var minutes = Math.floor(times[time] / 60) < 10 ? '0' + String(Math.floor(times[time] / 60)) : Math.floor(times[time] / 60);
        var seconds = (times[time] - minutes * 60) < 10 ? '0' + String(times[time] - minutes * 60) : times[time] - minutes * 60;
        $('.score'+time).text(minutes +':'+seconds);
      } else {
        //if not, make it an empty high score
        $('.score'+time).text('--:--');
      }
    }
  }
  //check for highscores when loading the page
  checkHighScore();

  function newHighScore() {
    //grab the time from the win
    var minutes = parseInt($('.clock').text().split(':')[0]);
    var seconds = parseInt($('.clock').text().split(':')[1]);
      //change the score to seconds
    var  newScoreSeconds += (minutes * 60) + seconds;
    //if the time is less than the high score set it
    if(localStorage.getItem(gameType) && localStorage.getItem(gameType) > newScoreSeconds ) {
      localStorage.setItem(gameType, newScoreSeconds);
      $('.score'+gameType).text($('.clock').text());
    } else if(!localStorage.getItem(gameType)) {
      //if there's no high score set it
      localStorage.setItem(gameType, newScoreSeconds);
      $('.score'+gameType).text($('.clock').text());
    }
  }

  function startTimer() {
    var time = 0;
    //clear the Timer
    clearInterval(timerID);
    //start a timer and save the id
    timerID = setInterval(function() {
      //second counter
      time++;
      //minute and second conversion
      var minutes = Math.floor(time / 60) < 10 ? '0' + String(Math.floor(time / 60)) : Math.floor(time / 60);
      var seconds = (time - minutes * 60) < 10 ? '0' + String(time - minutes * 60) : time - minutes * 60;
      //change the timer element
      $('.clock').text(minutes + ':' + seconds);
    }, 1000);
  }

  function stopTimer() {
    //clear the timer
    clearInterval(timerID);
  }

  function winGame() {
    //stop the timer when you win the game
    stopTimer();
    //create win box
    var win = document.createElement('div');
    win.id = "win"
    win.className="winBox"
    win.innerHTML = '
 You Won!
'+
    '
 Play Again
';
    $('.playArea').append(win);
    //can't click on tiles when you win
    $('#tiles').css('pointer-events','none');
    //start over
    $('.playAgain').click(function(){
      $('#tiles').remove();
      $('.winBox').remove();
      $('.playArea').append(newGame);
      $('.click').click(function(){
        //call start game
        startGame($(this))
      });

    });
    //change high score if need be
    newHighScore();
  }

  //shuffle function

  function shuffle(array,blankIndex, counter) {
    var availableShifts = [];
    var shiftIndex;
    if(counter === 1000) {
      //do this 1000 times
      return array;
    } else {
      //if there is a number above the blank space save its index
      if(blankIndex[0]-1 >= 0) {
        availableShifts.push([blankIndex[0]-1,blankIndex[1]]);
      }
      //if there is a number below the blank space save its index
      if(blankIndex[0]+1 < array.length){
        availableShifts.push([blankIndex[0]+1,blankIndex[1]]);
      }
      //if there is a number left of the blank space save its index
      if(blankIndex[1]-1 >= 0) {
        availableShifts.push([blankIndex[0],blankIndex[1]-1]);
      }
      //if there is a number right of the blank space save its index
      if(blankIndex[1]+1 < array.length) {
        availableShifts.push([blankIndex[0],blankIndex[1]+1]);
      }
      //randomly pick an available tile
      shiftIndex = Math.floor(Math.random() * availableShifts.length);
      //save the index of the shifting tile
      var shift = availableShifts[shiftIndex];
      //shift tile to blank index
      array[blankIndex[0]][blankIndex[1]] = array[shift[0]][shift[1]];
      //shift blank tile to where tile was
      array[shift[0]][shift[1]] = undefined;
      //change blank index
      blankIndex = availableShifts[shiftIndex];
      //recurse
      return  shuffle(array,blankIndex,counter+=1);
    }
  }
  //check to see if you won
  function checkScore() {
    //array counter needs to be run seperate of the for loop counters
    var q = 1;

    for (var i = 0; i < twoDArray.length; i++) {
      for (var j = 0; j < twoDArray[i].length; j++) {
        //if the numbers aren't in order, return false
        if (twoDArray[i][j] !== q && twoDArray[i][j] !== undefined) {
          return false;
        } else {
          q++;
        }
      }
    }
    //will only be hit if the numbers are in sequential order
    return true;
  }
//move tile function
  function moveTile(tile) {
    //save tile by id
    var tileID = tile.attr('id');
    var emptyIndex = 0;
    var tileIndex = 0;

    //cycle through the array of numbers
    for (var i = 0; i < twoDArray.length; i++) {
      for (var j = 0; j < twoDArray[i].length; j++) {

        //save off blank tile
        if (twoDArray[i][j] === undefined) {
          emptyIndex = [i, j];
        } else if (twoDArray[i][j] == tileID) {
          //save off index of clicked tile in array
          tileIndex = [i, j];
        }
      }
    }
    //keep track of how many tiles to move
    var numberOfMoves = 0;
    //if the tile and blank space are on the same y plane
    if (tileIndex[0] === emptyIndex[0]) {
      //find how many tiles in between the tile and empty index so you can move them all
      numberOfMoves = Math.abs(emptyIndex[1] - tileIndex[1]);
      //if the tile is to the left of the blank space, move it right
      if (tileIndex[1] < emptyIndex[1]) {
        console.log('move right');
        for (var i = tileIndex[1]; i <= emptyIndex[1]; i++) {
          //actually move the elements using velocity
          $('#' + String(twoDArray[tileIndex[0]][i])).velocity({
            translateX: tileCoordinates[tileIndex[0]][i][0] + tileDimensions[gameType]
          },1)
          //keep track of all the tile coordinates for velocity
          tileCoordinates[tileIndex[0]][i] = [tileCoordinates[tileIndex[0]][i][0] + tileDimensions[gameType], tileCoordinates[tileIndex[0]][i][1]];
        }
        //move the tile coordinates in the array to the correct place
        //since this is moving right, we are gonna put the empty index at the front and push the tiles to the right
        var t1 = tileCoordinates[tileIndex[0]].splice(emptyIndex[1], 1);
        tileCoordinates[tileIndex[0]].splice(tileIndex[1], 0, t1[0]);
        //do the same thing to keep track of where the numbers are
        var t2 = twoDArray[tileIndex[0]].splice(emptyIndex[1], 1);
        twoDArray[tileIndex[0]].splice(tileIndex[1], 0, t2[0]);
      } else if (tileIndex[1] > emptyIndex[1]) {
        console.log('Move Left')
        for (var i = tileIndex[1]; i >= emptyIndex[1]; i--) {
          $('#' + String(twoDArray[tileIndex[0]][i])).velocity({
            translateX: tileCoordinates[tileIndex[0]][i][0] - tileDimensions[gameType]
          },1)
          tileCoordinates[tileIndex[0]][i] = [tileCoordinates[tileIndex[0]][i][0] - tileDimensions[gameType], tileCoordinates[tileIndex[0]][i][1]]
        }
        var t1 = tileCoordinates[tileIndex[0]].splice(emptyIndex[1], 1);
        tileCoordinates[tileIndex[0]].splice(tileIndex[1], 0, t1[0]);
        var t2 = twoDArray[tileIndex[0]].splice(emptyIndex[1], 1);
        twoDArray[tileIndex[0]].splice(tileIndex[1], 0, t2[0]);
      }
    } else if (tileIndex[1] === emptyIndex[1]) { //if the tile and blank tile are in the same x plane
      if (tileIndex[0] < emptyIndex[0]) {
        console.log('Move Down');
        var t2 = [];
        var t1 = [];
        for (var i = tileIndex[0]; i <= emptyIndex[0]; i++) {
          $('#' + String(twoDArray[i][tileIndex[1]])).velocity({
            translateY: tileCoordinates[i][tileIndex[1]][1] + tileDimensions[gameType]
          },1)
          tileCoordinates[i][tileIndex[1]] = [tileCoordinates[i][tileIndex[1]][0], tileCoordinates[i][tileIndex[1]][1] + tileDimensions[gameType]];
          t1.push(tileCoordinates[i].splice(tileIndex[1], 1)[0]);
          t2.push(twoDArray[i].splice(tileIndex[1], 1)[0]);
        }
        t1.unshift(t1.pop())
        t2.unshift(t2.pop())
        for (var i = 0, j = tileIndex[0]; i < t2.length; i++, j++) {
          tileCoordinates[j].splice(tileIndex[1], 0, t1[i]);
          twoDArray[j].splice(tileIndex[1], 0, t2[i]);
        }
      } else if (tileIndex[0] > emptyIndex[0]) {
        console.log('Move Up');
        var t2 = [];
        var t1 = [];
        for (var i = tileIndex[0]; i >= emptyIndex[0]; i--) {
          $('#' + String(twoDArray[i][tileIndex[1]])).velocity({
            translateY: tileCoordinates[i][tileIndex[1]][1] - tileDimensions[gameType]
          },1)
          tileCoordinates[i][tileIndex[1]] = [tileCoordinates[i][tileIndex[1]][0], tileCoordinates[i][tileIndex[1]][1] - tileDimensions[gameType]];
          t1.unshift(tileCoordinates[i].splice(tileIndex[1], 1)[0]);
          t2.unshift(twoDArray[i].splice(tileIndex[1], 1)[0]);
        }
        t1.push(t1.shift());
        t2.push(t2.shift());
        for (var i = 0, j = emptyIndex[0]; i < t2.length; i++, j++) {
          tileCoordinates[j].splice(tileIndex[1], 0, t1[i]);
          twoDArray[j].splice(tileIndex[1], 0, t2[i]);
        }
      }
    }
    //if checkscore returns true you win
    if (checkScore() === true) {
      winGame();
    }
  }
//startgame function
  function startGame(reference) {
    //remove new game element
    newGame = $('.newGame');
    $('.newGame').remove();
    //create the tiles container
    var tiles = document.createElement('div');
    tiles.id = "tiles";
    //append it
    $('.playArea').append(tiles);
    //check what size game
    var gameSize = reference.attr('id');
    gameType = gameSize;
    var numbers = [];
    //push numbers into array that is one dimensional
    for (var i = 0; i < (gameSize * gameSize) - 1; i++) {
      numbers.push(i + 1);
    }
    twoDArray = [];
    var q = 0;
    //make it two dimensional array
    for (var i = 0; i < Math.sqrt(numbers.length); i++) {
      twoDArray[i] = [];
      tileCoordinates[i] = [];
      for (var j = 0; j < Math.sqrt(numbers.length); j++) {
        twoDArray[i][j] = numbers[q];
        tileCoordinates[i][j] = [0, 0];
        q++;
      }
    }
    //shuffle the array
    shuffledArray = shuffle(twoDArray,[twoDArray.length-1,twoDArray[twoDArray.length-1].length-1],0);
    for (var i = 0; i < shuffledArray.length; i++) {
      for (var j = 0; j < shuffledArray[i].length; j++) {
        //if the array number isn't the blank space, append the tile
        if(shuffledArray[i][j] !== undefined) {
          var tile = document.createElement('div');
          tile.id = String(shuffledArray[i][j]);
          tile.className = "tile tile" + gameSize;
          tile.innerHTML = String(shuffledArray[i][j]);
          $('#tiles').append(tile);
        } else {
          //if it's undefined change to the blank class
          var tile = document.createElement('div');
          tile.id = String(shuffledArray[i][j]);
          tile.className = "blank tile" + gameSize;
          tile.innerHTML = '
';
          $('#tiles').append(tile);
        }
      }
      //set timer text to 0
      $('.clock').text('00:00');
      //start timer
      startTimer();
    }
    //move tile when clicking
    $('.tile').click(function() {
      moveTile($(this));
    });
  }
  //start game when clicking
  $('.click').click(function() {
    startGame($(this));
  });
  //new game click
  $('.reset').click(function() {
    $('#tiles').remove();
    stopTimer();
    $('.playArea').append(newGame);
    $('.click').click(function() {
      startGame($(this));
    })
    $('.clock').text('00:00');
  })
})
<|endoftext|>


<commit_before> /**
 * The I mazon layer module.
 *
 * @return ImazonLayer class (extends CartoDBLayerClass)
 */
define([
  'moment',
  'uri',
  'abstract/layer/CartoDBLayerClass',
  'text!map/cartocss/Guyra.cartocss',
  'map/presenters/layers/GuyraLayerPresenter'
], function(moment, UriTemplate, CartoDBLayerClass, GuyraCartoCSS, Presenter) {

  'use strict';

  var GuyraLayer = CartoDBLayerClass.extend({
    options: {
      sql:  "SELECT cartodb_id, the_geom_webmercator, date, month, year, '{tableName}' as layer, '{tableName}' AS name FROM {tableName} WHERE date BETWEEN to_date('{startYear}-{startMonth}','YYYY-MM') AND to_date('{endYear}-{endMonth}','YYYY-MM')",
      cartocss: GuyraCartoCSS
    },

    init: function(layer, options, map) {
      var mapObj={month_to_change: moment(layer.maxdate).subtract(3, 'months').utc().format('MM'), year_to_change: moment(layer.maxdate).utc().format('YYYY')}

      this.options.cartocss = GuyraCartoCSS.replace(/month_to_change|year_to_change/g, function(matched){return mapObj[matched]});

      this.presenter = new Presenter(this);
      this.currentDate = options.currentDate || [(layer.mindate), (layer.maxdate)];
      this._super(layer, options, map);
    },

    /**
     * Used by UMDLoassLayerPresenter to set the dates for the tile.
     *
     * @param {Array} date 2D array of moment dates [begin, end]
     */
    setCurrentDate: function(date) {
      this.currentDate = date;
      this.updateTiles();
    },

    /**
     * Get the CartoDB query.
     *
     * @return {string} CartoDB query
     * @override
     */
    getQuery: function() {
      var query = new UriTemplate(this.options.sql).fillFromObject({
        tableName: this.layer.table_name,
        startMonth: this.currentDate[0].format('MM'),
        startYear: this.currentDate[0].format('YYYY'),
        endMonth: this.currentDate[1].format('MM'),
        endYear: this.currentDate[1].format('YYYY')
      });
      return query;
    }

  });

  return GuyraLayer;
});<commit_msg>solved issue with dates<commit_after> /**
 * The I mazon layer module.
 *
 * @return ImazonLayer class (extends CartoDBLayerClass)
 */
define([
  'moment',
  'uri',
  'abstract/layer/CartoDBLayerClass',
  'text!map/cartocss/Guyra.cartocss',
  'map/presenters/layers/GuyraLayerPresenter'
], function(moment, UriTemplate, CartoDBLayerClass, GuyraCartoCSS, Presenter) {

  'use strict';

  var GuyraLayer = CartoDBLayerClass.extend({
    options: {
      sql:  "SELECT cartodb_id, the_geom_webmercator, date, month, year, '{tableName}' as layer, '{tableName}' AS name FROM {tableName} WHERE date BETWEEN to_date('{startYear}-{startMonth}','YYYY-MM') AND to_date('{endYear}-{endMonth}','YYYY-MM')",
      cartocss: GuyraCartoCSS
    },

    init: function(layer, options, map) {
      var mapObj={month_to_change: moment(layer.maxdate).subtract(3, 'months').utc().format('MM'), year_to_change: moment(layer.maxdate).subtract(3, 'months').utc().format('YYYY')}

      this.options.cartocss = GuyraCartoCSS.replace(/month_to_change|year_to_change/g, function(matched){return mapObj[matched]});

      this.presenter = new Presenter(this);
      this.currentDate = options.currentDate || [(layer.mindate), (layer.maxdate)];
      this._super(layer, options, map);
    },

    /**
     * Used by UMDLoassLayerPresenter to set the dates for the tile.
     *
     * @param {Array} date 2D array of moment dates [begin, end]
     */
    setCurrentDate: function(date) {
      this.currentDate = date;
      this.updateTiles();
    },

    /**
     * Get the CartoDB query.
     *
     * @return {string} CartoDB query
     * @override
     */
    getQuery: function() {
      var query = new UriTemplate(this.options.sql).fillFromObject({
        tableName: this.layer.table_name,
        startMonth: this.currentDate[0].format('MM'),
        startYear: this.currentDate[0].format('YYYY'),
        endMonth: this.currentDate[1].format('MM'),
        endYear: this.currentDate[1].format('YYYY')
      });
      return query;
    }

  });

  return GuyraLayer;
});<|endoftext|>


<commit_before>/**
 * Controller to manage the address input form.
 */

var isEmpty = require('lodash.isempty');
var filter = require('lodash.filter');

var AddressFormController = function($scope, $location, dioData, dioAPI, $timeout) {
  var priorAddress = dioData.getCanonicalAddress();

  // See https://developers.google.com/web/fundamentals/input/form/provide-real-time-validation
  $scope.patterns = {
    address: new RegExp(/[a-zA-Z\d\s\-\,\#\.\+]+/),
    city: new RegExp(/[a-zA-Z\d\s\-\,\#\.\+]+/),
    postal: new RegExp(/^\d{5,6}(?:[-\s]\d{4})?$/)
  };
  if (priorAddress.address) {
    $scope.addressData = {
      address: priorAddress.components.primaryNumber + " " + priorAddress.components.streetName + " " + priorAddress.components.streetSuffix,
      city: priorAddress.components.cityName + ', ' + priorAddress.components.stateAbbreviation,
      postal: priorAddress.components.zipcode
    };
  } else {
    $scope.addressData = {
      address: '',
      city: '',
      postal: ''
    };
  }

  $scope.data = {
    address: '',
    verifyingAddress: false
  };

  $scope.verifyAddress = function(address) {
    $scope.error = null;
    var cb = function(err, canonicalAddresses) {
      $scope.data.verifyingAddress = false;
      var addressFound = !isEmpty(canonicalAddresses);
      var serverErr = !isEmpty(err);

      if (addressFound && !serverErr) {
        dioData.clearData();
        // It's possible to get multiple verified addresses for a single source address.
        // We've been unable to find an example of this to test though, so for now just pick
        // the first value and use that.
        dioData.setCanonicalAddress(canonicalAddresses[0]);
        $location.path('/location');
      } else {
        if (serverErr) {
          $scope.error = "There appears to be a problem with the server. Please try again, and if the problem persists, email democracy@eff.org with the address you used so we can try and fix the issue.";
        } else {
          $scope.error = "Your address was not recognized. Please check the address and try again.";
        }
      }

    };

    dioAPI.verifyAddress(address, cb);
  };

  $scope.getAddressString = function() {
     var filteredBits = filter(
      [$scope.addressData.address, $scope.addressData.city, $scope.addressData.postal],
      function(val) {
        return !isEmpty(val);
      }
    );

    return filteredBits.join(', ');
  };

  $scope.validateAddress = function() {
    if ($scope.addressForm.$valid) {
      $scope.data.address = $scope.getAddressString();
      $scope.data.verifyingAddress = true;
      $scope.verifyAddress($scope.data.address);
    }
  };

  $scope.video = {};
  $scope.video.autoplayVideo = function() {
    var vidEl = document.querySelectorAll('#video')[0];
    var contEl = document.querySelectorAll('#video-container')[0];
    angular.element(contEl).addClass('ng-enter');
    $timeout(function() {
      vidEl.play();
    }, 1500);
  };

};

AddressFormController.$inject = [
  '$scope', '$location', 'dioData', 'dioAPI', '$timeout'
];

module.exports = AddressFormController;
<commit_msg>updated prior address validation now checks to make sure fields exist<commit_after>/**
 * Controller to manage the address input form.
 */

var isEmpty = require('lodash.isempty');
var filter = require('lodash.filter');

var AddressFormController = function($scope, $location, dioData, dioAPI, $timeout) {
  var priorCanonicalAddress = dioData.getCanonicalAddress().components;

  var priorAddress = '';
  if (priorCanonicalAddress.primaryNumber) priorAddress += priorCanonicalAddress.primaryNumber + " ";
  if (priorCanonicalAddress.streetName) priorAddress += priorCanonicalAddress.streetName + " ";
  if (priorCanonicalAddress.streetSuffix) priorAddress += priorCanonicalAddress.streetSuffix;

  var priorCity = '';
  if (priorCanonicalAddress.cityName) priorCity += priorCanonicalAddress.cityName;
  if (priorCanonicalAddress.stateAbbreviation) priorCity += ", " + priorCanonicalAddress.stateAbbreviation;

  var priorPostal = '';
  if (priorCanonicalAddress.zipcode) priorPostal = priorCanonicalAddress.zipcode;

  // See https://developers.google.com/web/fundamentals/input/form/provide-real-time-validation
  $scope.patterns = {
    address: new RegExp(/[a-zA-Z\d\s\-\,\#\.\+]+/),
    city: new RegExp(/[a-zA-Z\d\s\-\,\#\.\+]+/),
    postal: new RegExp(/^\d{5,6}(?:[-\s]\d{4})?$/)
  };
  if (priorCanonicalAddress.streetName) {
    $scope.addressData = {
      address: priorAddress,
      city: priorCity,
      postal: priorPostal
    };
  } else {
    $scope.addressData = {
      address: '',
      city: '',
      postal: ''
    };
  }

  $scope.data = {
    address: '',
    verifyingAddress: false
  };

  $scope.verifyAddress = function(address) {
    $scope.error = null;
    var cb = function(err, canonicalAddresses) {
      $scope.data.verifyingAddress = false;
      var addressFound = !isEmpty(canonicalAddresses);
      var serverErr = !isEmpty(err);

      if (addressFound && !serverErr) {
        dioData.clearData();
        // It's possible to get multiple verified addresses for a single source address.
        // We've been unable to find an example of this to test though, so for now just pick
        // the first value and use that.
        dioData.setCanonicalAddress(canonicalAddresses[0]);
        $location.path('/location');
      } else {
        if (serverErr) {
          $scope.error = "There appears to be a problem with the server. Please try again, and if the problem persists, email democracy@eff.org with the address you used so we can try and fix the issue.";
        } else {
          $scope.error = "Your address was not recognized. Please check the address and try again.";
        }
      }

    };

    dioAPI.verifyAddress(address, cb);
  };

  $scope.getAddressString = function() {
     var filteredBits = filter(
      [$scope.addressData.address, $scope.addressData.city, $scope.addressData.postal],
      function(val) {
        return !isEmpty(val);
      }
    );

    return filteredBits.join(', ');
  };

  $scope.validateAddress = function() {
    if ($scope.addressForm.$valid) {
      $scope.data.address = $scope.getAddressString();
      $scope.data.verifyingAddress = true;
      $scope.verifyAddress($scope.data.address);
    }
  };

  $scope.video = {};
  $scope.video.autoplayVideo = function() {
    var vidEl = document.querySelectorAll('#video')[0];
    var contEl = document.querySelectorAll('#video-container')[0];
    angular.element(contEl).addClass('ng-enter');
    $timeout(function() {
      vidEl.play();
    }, 1500);
  };

};

AddressFormController.$inject = [
  '$scope', '$location', 'dioData', 'dioAPI', '$timeout'
];

module.exports = AddressFormController;
<|endoftext|>


<commit_before>/**
* map tab shown in cartodb admin
*/

cdb.admin.MapTab = cdb.core.View.extend({

  events: {
    'click .share a': 'shareMap'
  },

  className: 'map',
  animation_time: 300,

  initialize: function() {
    this.template = this.getTemplate('table/views/maptab');
    this.map = this.model;
    this.map_enabled = false;
    this.featureHovered = null;
    this.infowindowModel = this.options.infowindow;
    this.georeferenced = false;

    //this.add_related_model(this.options.dataLayer);
    this.add_related_model(this.map);
    this.add_related_model(this.options.table);
    this.add_related_model(this.infowindowModel);

    var self = this;

    cdb.god.bind("hidePanel", function(ev) {
      self._moveRightButtons("hide");
    });
    cdb.god.bind("showPanel", function(ev) {
      self._moveRightButtons("show");
    });
    cdb.god.bind("narrowPanel", function(ev) {
      self._moveRightButtons("narrow");
    });

    this.map.bind('change:provider', this.switchMapType, this);
    this.options.table.bind('change:privacy', this._setPrivacy, this);
    this.options.table.bind('change:dataSource', this._hideInfowindow, this);

    this.options.table.bind('data:saved', function() {
      this.updateDataLayerView();
    }, this)

    // menu entries

    this.newGeomDropdown = new cdb.admin.NewGeometryDropdown({
        position: 'position',
        template_base: "table/views/table_new_geom",
        tick: "bottom",
        horizontal_position: "right",
    });
    this.newGeomDropdown.render();

    var addGeom = this.options.menu.addToolButton('add_feature', ['mapLite', 'map']);
    $('body').append(this.newGeomDropdown.el);

    this.newGeomDropdown.bind('newGeometry', function(geomType) {
      this.addGeometry(geomType);
    }, this);

    addGeom.bind('click', function(e) {
      if(this.options.table.isGeoreferenced()) {
        this.addGeometry();
      } else {
        var pos = addGeom.$el.offset()
        this.newGeomDropdown.openAt(pos.left - 165, pos.top - 77);
      }
    }, this);

    _.bindAll(this, 'showNoGeoRefWarning');
  },

  clearMap: function() {

    this.mapView.clean();
    this.zoom.clean();
    this.infowindow.clean();

    delete this.mapView;
    delete this.zoom;
    delete this.infowindow;

    this.map_enabled = false;
  },

  /**
   *  Hide the infowindow when a query is applied or cleared
   */
  _hideInfowindow: function() {
    if(this.infowindow) {
      this.infowindow.model.set('visibility', false);
    }
  },

  _setPrivacy: function() {
    if (this.options.table.get('privacy').toLowerCase() != "public") {
      this.$el.find('div.share a').addClass('disabled');
    } else {
      this.$el.find('div.share a').removeClass('disabled');
    }
  },

  /**
  * this function is used when the map library is changed. Each map library
  * works in different way and need to recreate all the components again
  */
  switchMapType: function() {

    if (this.map_enabled) {
      this.clearMap();
      this.render();
      this.enableMap();
    }
  },

  /**
  * map can't be loaded from the beggining, it needs the DOM to be loaded
  * so we wait until is actually shown to create the mapview and show it
  */
  enableMap: function() {

    this.$('.tipsy').remove();
    var self = this;
    if (!this.map_enabled) {
      var div = this.$('#map')
      , base_maps = this.$('.base_maps');

      if (!this.baseLayerChooser) {

        this.baseLayerChooser = new cdb.admin.BaseMapChooser({
          model: this.map,
          baseLayers: this.options.baseLayers
        });

      }

      base_maps.append(this.baseLayerChooser.render().el);

      var mapViewClass = cdb.geo.LeafletMapView;
      if(this.map.get('provider') === 'googlemaps') {
        mapViewClass = cdb.geo.GoogleMapsMapView;
      }

      this.mapView = new mapViewClass({
        el: div,
        map: this.map
      });
      this.retrigger('editInfoWindow', this.mapView);

      this.clickTimeout = null;
      this._bindMissingClickEvents();

      this.map_enabled = true;

      this.mapView.bind('newLayerView', self._bindDataLayer, self);

      // bind data layer if it is already added to the map
      var dataLayer = this.map.get('dataLayer');
      if(dataLayer) {
        var dl = this.mapView.getLayerByCid(self.map.get('dataLayer').cid);
        this._bindDataLayer(dl);
      }

      // Zoom control
      var zoomControl = new cdb.geo.ui.Zoom({
        model:    this.map,
        template: cdb.templates.getTemplate("table/views/zoom_control")
      });
      this.$el.append(zoomControl.render().$el);
      this.zoom = zoomControl;

      // Search control
      var searchControl = new cdb.geo.ui.Search({
        model:    this.map,
        template: cdb.templates.getTemplate("table/views/search_control")
      });
      this.$el.append(searchControl.render().$el);
      this.search = searchControl;

      // Tiles loader
      var tilesLoader = this.loader = new cdb.geo.ui.TilesLoader({
        template: cdb.templates.getTemplate("table/views/tiles_loader")
      });
      this.$el.append(tilesLoader.render().$el);
      // Create tipsy and set to the tiles loader
      tilesLoader.$el.find("div.loader").tipsy({
        title: function() { return "Loading tiles..." },
        fade: true,
        offset: 3,
        gravity: 'w'
      })

      // Zoom info
      var zoomInfo = this.zoomInfo = new cdb.geo.ui.ZoomInfo({
        model: this.map
      });
      this.$el.append(zoomInfo.render().$el);
      // Create tipsy and set to the zoom info element
      zoomInfo.$el.tipsy({
        title: function() { return "Zoom level" },
        fade: true,
        offset: 3,
        gravity: 'w'
      })

      // Infowindow
      self.infowindow = new cdb.admin.MapInfowindow({
        model: self.infowindowModel,
        //template: cdb.templates.getTemplate('table/views/infowindow'),
        mapView: self.mapView,
        table: self.options.table
      });
      this.mapView.$el.append(self.infowindow.el);
      this.retrigger('editInfoWindow', this.infowindow);

      // Editing geometry
      this.geometryEditor = new cdb.admin.GeometryEditor({
        model: this.options.table
      });
      this.geometryEditor.mapView = this.mapView;
      this.mapView.$el.append(self.geometryEditor.render().el);
      this.geometryEditor.hide();

      this.geometryEditor.bind('editStart', this.hideDataLayer, this);
      this.geometryEditor.bind('editDiscard', this.showDataLayer, this);
      this.geometryEditor.bind('editFinish', this.showDataLayer, this);
      this.geometryEditor.bind('editFinish', this.updateDataLayerView, this);
      this.geometryEditor.bind('geomCreated', function(row) {
        this.options.table.data().add(row);
      }, this);
      self.infowindow.bind('editGeom', this.geometryEditor.editGeom, this.geometryEditor);
      //self.infowindow.bind('removeGeom', this.updateDataLayerView, this);
      this.options.table.bind('remove:row', this.updateDataLayerView, this);

      this.options.table.bind('change:dataSource', function() {
        self.geometryEditor.discard();
      });

      this.bindGeoRefCheck();

      // HACK
      // wait a little bit to give time to the mapview
      // to estabilize
      setTimeout(function() {
        self.mapView.setAutoSaveBounds();
      }, 1000);
    }
  },

  bindGeoRefCheck: function() {
    if(!this.options.table.data().fetched) {
      this.options.table.bind('dataLoaded', function() {
        this.checkGeoRef();
      }, this);
    } else {
      this.checkGeoRef();
    }

  },

  activated: function() {

  },

  checkGeoRef: function() {
    if(this.options && this.options.table) {
      if(!this.options.table.isGeoreferenced()) {
        this.trigger('noGeoRef');
        this.showNoGeoRefWarning();
        this.georeferenced = false;
      } else {
        if(!this.georeferenced) {
          this.trigger('geoRef');
        }
        this.georeferenced = true;
        if(this.noGeoRefDialog) {
          this.noGeoRefDialog.hide();
        }
      }
    }
  },

  /**
  * this function binds click and dblclick events
  * in order to not raise click when user does a dblclick
  *
  * it raises a missingClick when the user clicks on the map
  * but not over a feature or ui component
  */
  _bindMissingClickEvents: function() {
    var self = this;
    this.mapView.bind('click', function(e) {
      if(self.clickTimeout === null) {
        self.clickTimeout = setTimeout(function() {
          self.clickTimeout = null;
          if(!self.featureHovered) {
            self.trigger('missingClick');
          }
        }, 150);
      }
      //google maps does not send an event
      if(!self.featureHovered && e.preventDefault) {
        e.preventDefault();
        e.stopPropagation();
      }
    });

    this.mapView.bind('dblclick', function() {
      if(self.clickTimeout !== null) {
        clearTimeout(self.clickTimeout);
        self.clickTimeout = null;
      }
    });
  },

  /**
  * when the layer view is created this method is called
  * to attach all the click events
  */
  _bindDataLayer: function(layer) {
    var self = this;

    var layerType = layer.model.get('type');

    if (layerType === 'CartoDB') { // unbind previos stuff

      if (self.layerDataView) {
        self.layerDataView.unbind(null, null, this);
      }
      var dataLayer = self.map.get('dataLayer');

      this.options.table.bind('change:schema', function() {
        //dataLayer.invalidate();
      }, this);

      if (dataLayer) {

        var cid = dataLayer.cid;

        self.layerDataView = self.mapView.getLayerByCid(cid);

        if (self.layerDataView) {
          self.layerDataView.bind('featureClick', self.featureClick, self);
          self.layerDataView.bind('featureOut',   self.featureOut,   self);
          self.layerDataView.bind('featureOver',  self.featureOver,  self);
          self.layerDataView.bind('loading',      self.loadingTiles, self);
          self.layerDataView.bind('load',         self.loadTiles,    self);
        }
      }
    }
  },

  loadingTiles: function() {
    this.loader.show()
  },

  loadTiles: function() {
    this.loader.hide()
  },

  featureOver: function(e, latlon, pxPos, data) {
    this.mapView.setCursor('pointer');
    this.featureHovered = data;
  },

  featureOut: function() {
    this.mapView.setCursor('auto');
    this.featureHovered = null;
  },

  featureClick: function(e, latlon, pxPos, data) {
    if(!this.geometryEditor.isEditing()) {
      if(data.cartodb_id) {
        this.infowindow
        .setLatLng(latlon)
        .setFeatureInfo(data.cartodb_id)
        .showInfowindow();
      } else {
        cdb.log.error("can't show infowindow, no cartodb_id on data");
      }
    }
  },

  /**
   *  Move all necessary buttons when panel is openned or closed
   */
  _moveRightButtons: function(type) {
    var right = 0;
    if (type == "show") {
      right = 592;
    } else if (type == "hide") {
      right = 57;
    } else {
      right = 442;
    }

    this.$el.find("div.share")
      .animate({
        right: right
      }, this.animation_time);

    this.$el.find("div.search_box")
      .animate({
        right: right
      }, this.animation_time);
  },

  render: function() {
    this.$el.html('');
    this.$el.append(this.template());
    return this;
  },

  shareMap: function(e) {
    e.preventDefault();

    var privacy = this.options.table.get('privacy').toLowerCase()
      , dlg
      , center = false;

    // If the table is not public, we'll show a warning dialog
    if (privacy != 'public') {
      dlg = new cdb.admin.NoShareMapDialog({
        map: this.model,
        table: this.options.table
      });
    } else {
      dlg = new cdb.admin.ShareMapDialog({
        map: this.model,
        table: this.options.table
      });
      center = true;
    }

    dlg.appendToBody().open({ center: center });

    return false;
  },

  addGeometry: function(type) {
    // row is saved by geometry editor if it is needed
    type = type || this.options.table.geomColumnTypes()[0];
    this.geometryEditor.createGeom(this.options.table.data().newRow(), type);
  },

  showDataLayer: function() {
    this.layerDataView.setInteraction(true);
    this.layerDataView.setOpacity(1.0);
  },

  hideDataLayer: function() {
    //this.layerDataView.hide();
    this.layerDataView.setInteraction(false);
    this.layerDataView.setOpacity(0.5);
  },

  /**
  * reload tiles
  */
  updateDataLayerView: function() {
    if(this.layerDataView) {
      this.layerDataView.reload();
    }
  },
  /**
  * Paints a dialog with a warning when the user hasn't any georeferenced row
  * @method showNoGeorefWarning
  */
  showNoGeoRefWarning: function() {
    if(!this.noGeoRefDialog) {
      this.noGeoRefDialog = new cdb.admin.NoGeoRefDataDialog({
        model: this.options.table
      });
      this.retrigger('georeference', this.noGeoRefDialog);
      this.retrigger('manual', this.noGeoRefDialog);
      this.noGeoRefDialog.render().$el.appendTo(this.$el);
    }
    // if the dialog already has been shown, we don't show it again

  }

});

<commit_msg>now we detect if the user has view the noGeorefernce dialog for this table and we don't show it again<commit_after>/**
* map tab shown in cartodb admin
*/

cdb.admin.MapTab = cdb.core.View.extend({

  events: {
    'click .share a': 'shareMap'
  },

  className: 'map',
  animation_time: 300,

  initialize: function() {
    this.template = this.getTemplate('table/views/maptab');
    this.map = this.model;
    this.map_enabled = false;
    this.featureHovered = null;
    this.infowindowModel = this.options.infowindow;
    this.georeferenced = false;

    //this.add_related_model(this.options.dataLayer);
    this.add_related_model(this.map);
    this.add_related_model(this.options.table);
    this.add_related_model(this.infowindowModel);

    var self = this;

    cdb.god.bind("hidePanel", function(ev) {
      self._moveRightButtons("hide");
    });
    cdb.god.bind("showPanel", function(ev) {
      self._moveRightButtons("show");
    });
    cdb.god.bind("narrowPanel", function(ev) {
      self._moveRightButtons("narrow");
    });

    this.map.bind('change:provider', this.switchMapType, this);
    this.options.table.bind('change:privacy', this._setPrivacy, this);
    this.options.table.bind('change:dataSource', this._hideInfowindow, this);

    this.options.table.bind('data:saved', function() {
      this.updateDataLayerView();
    }, this)

    // menu entries

    this.newGeomDropdown = new cdb.admin.NewGeometryDropdown({
        position: 'position',
        template_base: "table/views/table_new_geom",
        tick: "bottom",
        horizontal_position: "right",
    });
    this.newGeomDropdown.render();

    var addGeom = this.options.menu.addToolButton('add_feature', ['mapLite', 'map']);
    $('body').append(this.newGeomDropdown.el);

    this.newGeomDropdown.bind('newGeometry', function(geomType) {
      this.addGeometry(geomType);
    }, this);

    addGeom.bind('click', function(e) {
      if(this.options.table.isGeoreferenced()) {
        this.addGeometry();
      } else {
        var pos = addGeom.$el.offset()
        this.newGeomDropdown.openAt(pos.left - 165, pos.top - 77);
      }
    }, this);

    _.bindAll(this, 'showNoGeoRefWarning');
  },

  clearMap: function() {

    this.mapView.clean();
    this.zoom.clean();
    this.infowindow.clean();

    delete this.mapView;
    delete this.zoom;
    delete this.infowindow;

    this.map_enabled = false;
  },

  /**
   *  Hide the infowindow when a query is applied or cleared
   */
  _hideInfowindow: function() {
    if(this.infowindow) {
      this.infowindow.model.set('visibility', false);
    }
  },

  _setPrivacy: function() {
    if (this.options.table.get('privacy').toLowerCase() != "public") {
      this.$el.find('div.share a').addClass('disabled');
    } else {
      this.$el.find('div.share a').removeClass('disabled');
    }
  },

  /**
  * this function is used when the map library is changed. Each map library
  * works in different way and need to recreate all the components again
  */
  switchMapType: function() {

    if (this.map_enabled) {
      this.clearMap();
      this.render();
      this.enableMap();
    }
  },

  /**
  * map can't be loaded from the beggining, it needs the DOM to be loaded
  * so we wait until is actually shown to create the mapview and show it
  */
  enableMap: function() {

    this.$('.tipsy').remove();
    var self = this;
    if (!this.map_enabled) {
      var div = this.$('#map')
      , base_maps = this.$('.base_maps');

      if (!this.baseLayerChooser) {

        this.baseLayerChooser = new cdb.admin.BaseMapChooser({
          model: this.map,
          baseLayers: this.options.baseLayers
        });

      }

      base_maps.append(this.baseLayerChooser.render().el);

      var mapViewClass = cdb.geo.LeafletMapView;
      if(this.map.get('provider') === 'googlemaps') {
        mapViewClass = cdb.geo.GoogleMapsMapView;
      }

      this.mapView = new mapViewClass({
        el: div,
        map: this.map
      });
      this.retrigger('editInfoWindow', this.mapView);

      this.clickTimeout = null;
      this._bindMissingClickEvents();

      this.map_enabled = true;

      this.mapView.bind('newLayerView', self._bindDataLayer, self);

      // bind data layer if it is already added to the map
      var dataLayer = this.map.get('dataLayer');
      if(dataLayer) {
        var dl = this.mapView.getLayerByCid(self.map.get('dataLayer').cid);
        this._bindDataLayer(dl);
      }

      // Zoom control
      var zoomControl = new cdb.geo.ui.Zoom({
        model:    this.map,
        template: cdb.templates.getTemplate("table/views/zoom_control")
      });
      this.$el.append(zoomControl.render().$el);
      this.zoom = zoomControl;

      // Search control
      var searchControl = new cdb.geo.ui.Search({
        model:    this.map,
        template: cdb.templates.getTemplate("table/views/search_control")
      });
      this.$el.append(searchControl.render().$el);
      this.search = searchControl;

      // Tiles loader
      var tilesLoader = this.loader = new cdb.geo.ui.TilesLoader({
        template: cdb.templates.getTemplate("table/views/tiles_loader")
      });
      this.$el.append(tilesLoader.render().$el);
      // Create tipsy and set to the tiles loader
      tilesLoader.$el.find("div.loader").tipsy({
        title: function() { return "Loading tiles..." },
        fade: true,
        offset: 3,
        gravity: 'w'
      })

      // Zoom info
      var zoomInfo = this.zoomInfo = new cdb.geo.ui.ZoomInfo({
        model: this.map
      });
      this.$el.append(zoomInfo.render().$el);
      // Create tipsy and set to the zoom info element
      zoomInfo.$el.tipsy({
        title: function() { return "Zoom level" },
        fade: true,
        offset: 3,
        gravity: 'w'
      })

      // Infowindow
      self.infowindow = new cdb.admin.MapInfowindow({
        model: self.infowindowModel,
        //template: cdb.templates.getTemplate('table/views/infowindow'),
        mapView: self.mapView,
        table: self.options.table
      });
      this.mapView.$el.append(self.infowindow.el);
      this.retrigger('editInfoWindow', this.infowindow);

      // Editing geometry
      this.geometryEditor = new cdb.admin.GeometryEditor({
        model: this.options.table
      });
      this.geometryEditor.mapView = this.mapView;
      this.mapView.$el.append(self.geometryEditor.render().el);
      this.geometryEditor.hide();

      this.geometryEditor.bind('editStart', this.hideDataLayer, this);
      this.geometryEditor.bind('editDiscard', this.showDataLayer, this);
      this.geometryEditor.bind('editFinish', this.showDataLayer, this);
      this.geometryEditor.bind('editFinish', this.updateDataLayerView, this);
      this.geometryEditor.bind('geomCreated', function(row) {
        this.options.table.data().add(row);
      }, this);
      self.infowindow.bind('editGeom', this.geometryEditor.editGeom, this.geometryEditor);
      //self.infowindow.bind('removeGeom', this.updateDataLayerView, this);
      this.options.table.bind('remove:row', this.updateDataLayerView, this);

      this.options.table.bind('change:dataSource', function() {
        self.geometryEditor.discard();
      });

      this.bindGeoRefCheck();

      // HACK
      // wait a little bit to give time to the mapview
      // to estabilize
      setTimeout(function() {
        self.mapView.setAutoSaveBounds();
      }, 1000);
    }
  },

  bindGeoRefCheck: function() {
    if(!this.options.table.data().fetched) {
      this.options.table.bind('dataLoaded', function() {
        this.checkGeoRef();
      }, this);
    } else {
      this.checkGeoRef();
    }

  },

  activated: function() {

  },

  checkGeoRef: function() {
    if(this.options && this.options.table) {
      if(!this.options.table.isGeoreferenced()) {
        this.trigger('noGeoRef');
        this.showNoGeoRefWarning();
        this.georeferenced = false;
      } else {
        if(!this.georeferenced) {
          this.trigger('geoRef');
        }
        this.georeferenced = true;
        if(this.noGeoRefDialog) {
          this.noGeoRefDialog.hide();
        }
      }
    }
  },

  /**
  * this function binds click and dblclick events
  * in order to not raise click when user does a dblclick
  *
  * it raises a missingClick when the user clicks on the map
  * but not over a feature or ui component
  */
  _bindMissingClickEvents: function() {
    var self = this;
    this.mapView.bind('click', function(e) {
      if(self.clickTimeout === null) {
        self.clickTimeout = setTimeout(function() {
          self.clickTimeout = null;
          if(!self.featureHovered) {
            self.trigger('missingClick');
          }
        }, 150);
      }
      //google maps does not send an event
      if(!self.featureHovered && e.preventDefault) {
        e.preventDefault();
        e.stopPropagation();
      }
    });

    this.mapView.bind('dblclick', function() {
      if(self.clickTimeout !== null) {
        clearTimeout(self.clickTimeout);
        self.clickTimeout = null;
      }
    });
  },

  /**
  * when the layer view is created this method is called
  * to attach all the click events
  */
  _bindDataLayer: function(layer) {
    var self = this;

    var layerType = layer.model.get('type');

    if (layerType === 'CartoDB') { // unbind previos stuff

      if (self.layerDataView) {
        self.layerDataView.unbind(null, null, this);
      }
      var dataLayer = self.map.get('dataLayer');

      this.options.table.bind('change:schema', function() {
        //dataLayer.invalidate();
      }, this);

      if (dataLayer) {

        var cid = dataLayer.cid;

        self.layerDataView = self.mapView.getLayerByCid(cid);

        if (self.layerDataView) {
          self.layerDataView.bind('featureClick', self.featureClick, self);
          self.layerDataView.bind('featureOut',   self.featureOut,   self);
          self.layerDataView.bind('featureOver',  self.featureOver,  self);
          self.layerDataView.bind('loading',      self.loadingTiles, self);
          self.layerDataView.bind('load',         self.loadTiles,    self);
        }
      }
    }
  },

  loadingTiles: function() {
    this.loader.show()
  },

  loadTiles: function() {
    this.loader.hide()
  },

  featureOver: function(e, latlon, pxPos, data) {
    this.mapView.setCursor('pointer');
    this.featureHovered = data;
  },

  featureOut: function() {
    this.mapView.setCursor('auto');
    this.featureHovered = null;
  },

  featureClick: function(e, latlon, pxPos, data) {
    if(!this.geometryEditor.isEditing()) {
      if(data.cartodb_id) {
        this.infowindow
        .setLatLng(latlon)
        .setFeatureInfo(data.cartodb_id)
        .showInfowindow();
      } else {
        cdb.log.error("can't show infowindow, no cartodb_id on data");
      }
    }
  },

  /**
   *  Move all necessary buttons when panel is openned or closed
   */
  _moveRightButtons: function(type) {
    var right = 0;
    if (type == "show") {
      right = 592;
    } else if (type == "hide") {
      right = 57;
    } else {
      right = 442;
    }

    this.$el.find("div.share")
      .animate({
        right: right
      }, this.animation_time);

    this.$el.find("div.search_box")
      .animate({
        right: right
      }, this.animation_time);
  },

  render: function() {
    this.$el.html('');
    this.$el.append(this.template());
    return this;
  },

  shareMap: function(e) {
    e.preventDefault();

    var privacy = this.options.table.get('privacy').toLowerCase()
      , dlg
      , center = false;

    // If the table is not public, we'll show a warning dialog
    if (privacy != 'public') {
      dlg = new cdb.admin.NoShareMapDialog({
        map: this.model,
        table: this.options.table
      });
    } else {
      dlg = new cdb.admin.ShareMapDialog({
        map: this.model,
        table: this.options.table
      });
      center = true;
    }

    dlg.appendToBody().open({ center: center });

    return false;
  },

  addGeometry: function(type) {
    // row is saved by geometry editor if it is needed
    type = type || this.options.table.geomColumnTypes()[0];
    this.geometryEditor.createGeom(this.options.table.data().newRow(), type);
  },

  showDataLayer: function() {
    this.layerDataView.setInteraction(true);
    this.layerDataView.setOpacity(1.0);
  },

  hideDataLayer: function() {
    //this.layerDataView.hide();
    this.layerDataView.setInteraction(false);
    this.layerDataView.setOpacity(0.5);
  },

  /**
  * reload tiles
  */
  updateDataLayerView: function() {
    if(this.layerDataView) {
      this.layerDataView.reload();
    }
  },
  /**
  * Paints a dialog with a warning when the user hasn't any georeferenced row
  * @method showNoGeorefWarning
  */
  showNoGeoRefWarning: function() {
    var warningStorageName = '';
    if(this.options.table._data.length > 0) {
      warningStorageName = 'georefContentWarningShowed' + this.options.table.id;
    } else {
      warningStorageName = 'georefNoContentWarningShowed' + this.options.table.id;
    }

    if(!this.noGeoRefDialog && (!localStorage[warningStorageName])) {
      localStorage[warningStorageName] = true;
      this.noGeoRefDialog = new cdb.admin.NoGeoRefDataDialog({
        model: this.options.table
      });
      this.retrigger('georeference', this.noGeoRefDialog);
      this.retrigger('manual', this.noGeoRefDialog);
      this.noGeoRefDialog.render().$el.appendTo(this.$el);
    }
    // if the dialog already has been shown, we don't show it again

  }

});

<|endoftext|>