<commit_before>/*
 (c) 2014, Vladimir Agafonkin
 simpleheat, a tiny JavaScript library for drawing heatmaps with Canvas
 https://github.com/mourner/simpleheat
*/

(function () { 'use strict';

function simpleheat(canvas, radius, gradient) {
    // jshint newcap: false, validthis: true
    if (!(this instanceof simpleheat)) { return new simpleheat(canvas); }

    this._canvas = canvas = typeof canvas === 'string' ? document.getElementById(canvas) : canvas;

    this._ctx = canvas.getContext('2d');
    this._width = canvas.width;
    this._height = canvas.height;

    this.radius(radius || 25);

    this.gradient(gradient || {
        0.00: 'rgba(0,0,255,0)',
        0.45: 'rgb(0,0,255)',
        0.55: 'rgb(0,255,255)',
        0.65: 'rgb(0,255,0)',
        0.95: 'yellow',
        1.00: 'rgb(255,0,0)'
    });
}

simpleheat.prototype = {

    data: function (data, max) {
        this._data = data;
        this._max = max;
        return this;
    },

    radius: function (r, blur) {
        this._circle = document.createElement('canvas');

        blur = blur || 15;

        var ctx = this._circle.getContext('2d'),
            r2 = this._r = r + blur;

        this._circle.width = this._circle.height = r2 * 2;

        ctx.shadowOffsetX = ctx.shadowOffsetY = 1000;
        ctx.shadowBlur = blur;
        ctx.shadowColor = 'black';

        ctx.beginPath();
        ctx.arc(r2 - 1000, r2 - 1000, r, 0, Math.PI * 2, true);
        ctx.closePath();
        ctx.fill();

        return this;
    },

    gradient: function (grad) {
        var canvas = document.createElement('canvas'),
            ctx = canvas.getContext('2d');

        canvas.width = 1;
        canvas.height = 256;

        var gradient = ctx.createLinearGradient(0, 0, 0, 256);

        for (var i in grad) {
            gradient.addColorStop(i, grad[i]);
        }

        ctx.fillStyle = gradient;
        ctx.fillRect(0, 0, 1, 256);

        this._gradient = ctx.getImageData(0, 0, 1, 256).data;

        return this;
    },

    draw: function () {
        var ctx = this._ctx;

        ctx.clearRect(0, 0, this._width, this._height);

        for (var i = 0, len = this._data.length, p; i < len; i++) {
            p = this._data[i];

            ctx.globalAlpha = p[2] ? p[2] / this._max : 0.1;
            ctx.drawImage(this._circle, p[0] - this._r, p[1] - this._r);
        }

        var colored = ctx.getImageData(0, 0, this._width, this._height);
        this._colorize(colored.data, this._gradient);
        ctx.putImageData(colored, 0, 0);

        return this;
    },

    _colorize: function (pixels, gradient) {
        for (var i = 3, len = pixels.length, j; i < len; i += 4) {
            j = pixels[i] * 4;

            if (j) {
                pixels[i - 3] = gradient[j];
                pixels[i - 2] = gradient[j + 1];
                pixels[i - 1] = gradient[j + 2];
            }
        }
    }
};

window.simpleheat = simpleheat;

})();
<commit_msg>init radius/gradient later<commit_after>/*
 (c) 2014, Vladimir Agafonkin
 simpleheat, a tiny JavaScript library for drawing heatmaps with Canvas
 https://github.com/mourner/simpleheat
*/

(function () { 'use strict';

function simpleheat(canvas) {
    // jshint newcap: false, validthis: true
    if (!(this instanceof simpleheat)) { return new simpleheat(canvas); }

    this._canvas = canvas = typeof canvas === 'string' ? document.getElementById(canvas) : canvas;

    this._ctx = canvas.getContext('2d');
    this._width = canvas.width;
    this._height = canvas.height;
}

simpleheat.prototype = {

    data: function (data, max) {
        this._data = data;
        this._max = max;
        return this;
    },

    radius: function (r, blur) {
        this._circle = document.createElement('canvas');

        blur = blur || 15;

        var ctx = this._circle.getContext('2d'),
            r2 = this._r = r + blur;

        this._circle.width = this._circle.height = r2 * 2;

        ctx.shadowOffsetX = ctx.shadowOffsetY = 1000;
        ctx.shadowBlur = blur;
        ctx.shadowColor = 'black';

        ctx.beginPath();
        ctx.arc(r2 - 1000, r2 - 1000, r, 0, Math.PI * 2, true);
        ctx.closePath();
        ctx.fill();

        return this;
    },

    gradient: function (grad) {
        var canvas = document.createElement('canvas'),
            ctx = canvas.getContext('2d');

        canvas.width = 1;
        canvas.height = 256;

        var gradient = ctx.createLinearGradient(0, 0, 0, 256);

        for (var i in grad) {
            gradient.addColorStop(i, grad[i]);
        }

        ctx.fillStyle = gradient;
        ctx.fillRect(0, 0, 1, 256);

        this._gradient = ctx.getImageData(0, 0, 1, 256).data;

        return this;
    },

    draw: function () {
        if (!this._circle) {
            this.radius(25).gradient({
                0.00: 'rgba(0,0,255,0)',
                0.45: 'rgb(0,0,255)',
                0.55: 'rgb(0,255,255)',
                0.65: 'rgb(0,255,0)',
                0.95: 'yellow',
                1.00: 'rgb(255,0,0)'
            });
        }

        var ctx = this._ctx;

        ctx.clearRect(0, 0, this._width, this._height);

        for (var i = 0, len = this._data.length, p; i < len; i++) {
            p = this._data[i];

            ctx.globalAlpha = p[2] ? p[2] / this._max : 0.1;
            ctx.drawImage(this._circle, p[0] - this._r, p[1] - this._r);
        }

        var colored = ctx.getImageData(0, 0, this._width, this._height);
        this._colorize(colored.data, this._gradient);
        ctx.putImageData(colored, 0, 0);

        return this;
    },

    _colorize: function (pixels, gradient) {
        for (var i = 3, len = pixels.length, j; i < len; i += 4) {
            j = pixels[i] * 4;

            if (j) {
                pixels[i - 3] = gradient[j];
                pixels[i - 2] = gradient[j + 1];
                pixels[i - 1] = gradient[j + 2];
            }
        }
    }
};

window.simpleheat = simpleheat;

})();
<|endoftext|>


<commit_before>'use strict';
const dayjs = require('dayjs');
const max = Math.max;

const {
  calculateRequestHeaderSize,
  calculateResponseHeaderSize,
  getHeaderValue,
  parseHeaders
} = require('./headers');

const { parseRequestCookies, parseResponseCookies } = require('./cookies');
const { isHttp1x, formatMillis } = require('./util');
function firstNonNegative(values) {
  for (let i = 0; i < values.length; ++i) {
    if (values[i] >= 0) return values[i];
  }
  return -1;
}

function formatIP(ipAddress) {
  if (typeof ipAddress !== 'string') {
    return undefined;
  }
  // IPv6 addresses are listed as [2a00:1450:400f:80a::2003]
  return ipAddress.replace(/^\[|]$/g, '');
}

module.exports = function(entry, response, page, options) {
  const responseHeaders = response.headers;
  const cookieHeader = getHeaderValue(responseHeaders, 'Set-Cookie');

  let cookies = parseResponseCookies(cookieHeader);
  let headers = parseHeaders(responseHeaders);

  if (entry.extraResponseInfo) {
    if (entry.extraResponseInfo.headers) {
      headers = entry.extraResponseInfo.headers.concat(
        headers.filter(
          ({ name }) => !headers.find(header => header.name === name)
        )
      );
    }

    if (entry.extraResponseInfo.blockedCookies) {
      cookies = cookies.filter(
        ({ name }) =>
          !entry.extraResponseInfo.blockedCookies.find(blockedCookie => {
            if (blockedCookie.cookie) {
              return blockedCookie.cookie.name === name;
            } else if (blockedCookie.cookieLine) {
              const cookie = parseResponseCookies(blockedCookie.cookieLine)[0];
              if (cookie) {
                return cookie.name === name;
              }
            }

            return false;
          })
      );
    }

    // Remove extra info once it has been added to the response
    delete entry.extraResponseInfo;
  }

  // response.body must be set by the library user, by either calling
  // Network.getResponseBody or Network.getResponseBodyForInterception as it is
  // not part of the Chrome DevTools Protocol specification.
  // See https://chromedevtools.github.io/devtools-protocol/tot/Network#type-Response
  const text =
    options != null && options.includeTextFromResponseBody
      ? response.body
      : undefined;

  entry.response = {
    httpVersion: response.protocol,
    redirectURL: '',
    status: response.status,
    statusText: response.statusText,
    content: {
      mimeType: response.mimeType,
      size: 0,
      text: text
    },
    headersSize: -1,
    bodySize: -1,
    cookies,
    headers,
    _transferSize: response.encodedDataLength
  };

  const locationHeaderValue = getHeaderValue(responseHeaders, 'Location');
  if (locationHeaderValue) {
    entry.response.redirectURL = locationHeaderValue;
  }

  entry.request.httpVersion = response.protocol;

  if (response.fromDiskCache === true) {
    if (isHttp1x(response.protocol)) {
      // In http2 headers are compressed, so calculating size from headers text wouldn't be correct.
      entry.response.headersSize = calculateResponseHeaderSize(response);
    }

    // h2 push might cause resource to be received before parser sees and requests it.
    if (response.timing && !(response.timing.pushStart > 0)) {
      entry.cache.beforeRequest = {
        lastAccess: '',
        eTag: '',
        hitCount: 0
      };
    }
  } else {
    if (response.requestHeaders) {
      entry.request.headers = parseHeaders(response.requestHeaders);

      const cookieHeader = getHeaderValue(response.requestHeaders, 'Cookie');
      entry.request.cookies = parseRequestCookies(cookieHeader);
    }

    if (isHttp1x(response.protocol)) {
      if (response.headersText) {
        entry.response.headersSize = response.headersText.length;
      } else {
        entry.response.headersSize = calculateResponseHeaderSize(response);
      }

      entry.response.bodySize =
        response.encodedDataLength - entry.response.headersSize;

      if (response.requestHeadersText) {
        entry.request.headersSize = response.requestHeadersText.length;
      } else {
        // Since entry.request.httpVersion is now set, we can calculate header size.
        entry.request.headersSize = calculateRequestHeaderSize(entry.request);
      }
    }
  }

  entry.connection = response.connectionId.toString();
  entry.serverIPAddress = formatIP(response.remoteIPAddress);

  function parseOptionalTime(timing, start, end) {
    if (timing[start] >= 0) {
      return formatMillis(timing[end] - timing[start]);
    }
    return -1;
  }

  const timing = response.timing;
  if (timing) {
    const blocked = formatMillis(
      firstNonNegative([timing.dnsStart, timing.connectStart, timing.sendStart])
    );

    const dns = parseOptionalTime(timing, 'dnsStart', 'dnsEnd');
    const connect = parseOptionalTime(timing, 'connectStart', 'connectEnd');
    const send = formatMillis(timing.sendEnd - timing.sendStart);
    const wait = formatMillis(timing.receiveHeadersEnd - timing.sendEnd);
    const receive = 0;

    const ssl = parseOptionalTime(timing, 'sslStart', 'sslEnd');

    entry.timings = {
      blocked,
      dns,
      connect,
      send,
      wait,
      receive,
      ssl
    };

    entry._requestTime = timing.requestTime;
    entry.__receiveHeadersEnd = timing.receiveHeadersEnd;
    if (timing.pushStart > 0) {
      // use the same extended field as WebPageTest
      entry._was_pushed = 1;
    }

    entry.time =
      max(0, blocked) + max(0, dns) + max(0, connect) + send + wait + receive;

    // Some cached responses generate a Network.requestServedFromCache event,
    // but fromDiskCache is still set to false.
    if (!entry.__servedFromCache) {
      // wallTime is not necessarily monotonic, timestamp is. So calculate startedDateTime from timestamp diffs.
      // (see https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/network/ResourceLoadTiming.h?q=requestTime+package:%5Echromium$&dr=CSs&l=84)
      const entrySecs =
        page.__wallTime + (timing.requestTime - page.__timestamp);
      entry.startedDateTime = dayjs.unix(entrySecs).toISOString();

      const queuedMillis =
        (timing.requestTime - entry.__requestWillBeSentTime) * 1000;
      if (queuedMillis > 0) {
        entry.timings._queued = formatMillis(queuedMillis);
      }
    }

    if (entry.cache && entry.cache.beforeRequest) {
      // lastAccess needs to be a valid date
      entry.cache.beforeRequest.lastAccess = entry.startedDateTime;
    }
  } else {
    entry.timings = {
      blocked: -1,
      dns: -1,
      connect: -1,
      send: 0,
      wait: 0,
      receive: 0,
      ssl: -1,
      comment: 'No timings available from Chrome'
    };
    entry.time = 0;
  }
};
<commit_msg>Propagation of response.encoding (#79)<commit_after>'use strict';
const dayjs = require('dayjs');
const max = Math.max;

const {
  calculateRequestHeaderSize,
  calculateResponseHeaderSize,
  getHeaderValue,
  parseHeaders
} = require('./headers');

const { parseRequestCookies, parseResponseCookies } = require('./cookies');
const { isHttp1x, formatMillis } = require('./util');
function firstNonNegative(values) {
  for (let i = 0; i < values.length; ++i) {
    if (values[i] >= 0) return values[i];
  }
  return -1;
}

function formatIP(ipAddress) {
  if (typeof ipAddress !== 'string') {
    return undefined;
  }
  // IPv6 addresses are listed as [2a00:1450:400f:80a::2003]
  return ipAddress.replace(/^\[|]$/g, '');
}

module.exports = function(entry, response, page, options) {
  const responseHeaders = response.headers;
  const cookieHeader = getHeaderValue(responseHeaders, 'Set-Cookie');

  let cookies = parseResponseCookies(cookieHeader);
  let headers = parseHeaders(responseHeaders);

  if (entry.extraResponseInfo) {
    if (entry.extraResponseInfo.headers) {
      headers = entry.extraResponseInfo.headers.concat(
        headers.filter(
          ({ name }) => !headers.find(header => header.name === name)
        )
      );
    }

    if (entry.extraResponseInfo.blockedCookies) {
      cookies = cookies.filter(
        ({ name }) =>
          !entry.extraResponseInfo.blockedCookies.find(blockedCookie => {
            if (blockedCookie.cookie) {
              return blockedCookie.cookie.name === name;
            } else if (blockedCookie.cookieLine) {
              const cookie = parseResponseCookies(blockedCookie.cookieLine)[0];
              if (cookie) {
                return cookie.name === name;
              }
            }

            return false;
          })
      );
    }

    // Remove extra info once it has been added to the response
    delete entry.extraResponseInfo;
  }

  // response.body must be set by the library user, by either calling
  // Network.getResponseBody or Network.getResponseBodyForInterception as it is
  // not part of the Chrome DevTools Protocol specification.
  // See https://chromedevtools.github.io/devtools-protocol/tot/Network#type-Response
  const text =
    options != null && options.includeTextFromResponseBody
      ? response.body
      : undefined;

  entry.response = {
    httpVersion: response.protocol,
    redirectURL: '',
    status: response.status,
    statusText: response.statusText,
    content: {
      encodig: response.encoding,
      mimeType: response.mimeType,
      size: text != null ? text.length : 0,
      text: text
    },
    headersSize: -1,
    bodySize: -1,
    cookies,
    headers,
    _transferSize: response.encodedDataLength
  };

  const locationHeaderValue = getHeaderValue(responseHeaders, 'Location');
  if (locationHeaderValue) {
    entry.response.redirectURL = locationHeaderValue;
  }

  entry.request.httpVersion = response.protocol;

  if (response.fromDiskCache === true) {
    if (isHttp1x(response.protocol)) {
      // In http2 headers are compressed, so calculating size from headers text wouldn't be correct.
      entry.response.headersSize = calculateResponseHeaderSize(response);
    }

    // h2 push might cause resource to be received before parser sees and requests it.
    if (response.timing && !(response.timing.pushStart > 0)) {
      entry.cache.beforeRequest = {
        lastAccess: '',
        eTag: '',
        hitCount: 0
      };
    }
  } else {
    if (response.requestHeaders) {
      entry.request.headers = parseHeaders(response.requestHeaders);

      const cookieHeader = getHeaderValue(response.requestHeaders, 'Cookie');
      entry.request.cookies = parseRequestCookies(cookieHeader);
    }

    if (isHttp1x(response.protocol)) {
      if (response.headersText) {
        entry.response.headersSize = response.headersText.length;
      } else {
        entry.response.headersSize = calculateResponseHeaderSize(response);
      }

      entry.response.bodySize =
        response.encodedDataLength - entry.response.headersSize;

      if (response.requestHeadersText) {
        entry.request.headersSize = response.requestHeadersText.length;
      } else {
        // Since entry.request.httpVersion is now set, we can calculate header size.
        entry.request.headersSize = calculateRequestHeaderSize(entry.request);
      }
    }
  }

  entry.connection = response.connectionId.toString();
  entry.serverIPAddress = formatIP(response.remoteIPAddress);

  function parseOptionalTime(timing, start, end) {
    if (timing[start] >= 0) {
      return formatMillis(timing[end] - timing[start]);
    }
    return -1;
  }

  const timing = response.timing;
  if (timing) {
    const blocked = formatMillis(
      firstNonNegative([timing.dnsStart, timing.connectStart, timing.sendStart])
    );

    const dns = parseOptionalTime(timing, 'dnsStart', 'dnsEnd');
    const connect = parseOptionalTime(timing, 'connectStart', 'connectEnd');
    const send = formatMillis(timing.sendEnd - timing.sendStart);
    const wait = formatMillis(timing.receiveHeadersEnd - timing.sendEnd);
    const receive = 0;

    const ssl = parseOptionalTime(timing, 'sslStart', 'sslEnd');

    entry.timings = {
      blocked,
      dns,
      connect,
      send,
      wait,
      receive,
      ssl
    };

    entry._requestTime = timing.requestTime;
    entry.__receiveHeadersEnd = timing.receiveHeadersEnd;
    if (timing.pushStart > 0) {
      // use the same extended field as WebPageTest
      entry._was_pushed = 1;
    }

    entry.time =
      max(0, blocked) + max(0, dns) + max(0, connect) + send + wait + receive;

    // Some cached responses generate a Network.requestServedFromCache event,
    // but fromDiskCache is still set to false.
    if (!entry.__servedFromCache) {
      // wallTime is not necessarily monotonic, timestamp is. So calculate startedDateTime from timestamp diffs.
      // (see https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/network/ResourceLoadTiming.h?q=requestTime+package:%5Echromium$&dr=CSs&l=84)
      const entrySecs =
        page.__wallTime + (timing.requestTime - page.__timestamp);
      entry.startedDateTime = dayjs.unix(entrySecs).toISOString();

      const queuedMillis =
        (timing.requestTime - entry.__requestWillBeSentTime) * 1000;
      if (queuedMillis > 0) {
        entry.timings._queued = formatMillis(queuedMillis);
      }
    }

    if (entry.cache && entry.cache.beforeRequest) {
      // lastAccess needs to be a valid date
      entry.cache.beforeRequest.lastAccess = entry.startedDateTime;
    }
  } else {
    entry.timings = {
      blocked: -1,
      dns: -1,
      connect: -1,
      send: 0,
      wait: 0,
      receive: 0,
      ssl: -1,
      comment: 'No timings available from Chrome'
    };
    entry.time = 0;
  }
};
<|endoftext|>


<commit_before><commit_msg>Added email module to log in page<commit_after><|endoftext|>


<commit_before>/* eslint-disable no-process-exit */
require('babel-register');
require('dotenv').load();
const adler32 = require('adler32');

const Rx = require('rx');
const _ = require('lodash');
const createDebugger = require('debug');
const utils = require('../server/utils');
const getChallenges = require('./getChallenges');
const { validateChallenge } = require(
  './schema/challengeSchema'
);
const app = require('../server/server');

const log = createDebugger('fcc:seed');
// force logger to always output
// this may be brittle
log.enabled = true;

const dasherize = utils.dasherize;
const nameify = utils.nameify;
const Observable = Rx.Observable;
const Challenge = app.models.Challenge;

const destroyChallenges =
  Observable.fromNodeCallback(Challenge.destroyAll, Challenge);
const createChallenges =
  Observable.fromNodeCallback(Challenge.create, Challenge);

const Block = app.models.Block;
const destroyBlocks = Observable.fromNodeCallback(Block.destroyAll, Block);
const createBlocks = Observable.fromNodeCallback(Block.create, Block);
const arrToString = arr =>
  Array.isArray(arr) ? arr.join('\n') : _.toString(arr);

Observable.combineLatest(
  destroyChallenges(),
  destroyBlocks()
)
  .last()
  .flatMap(function() { return Observable.from(getChallenges()); })
  .flatMap(function(challengeSpec) {
    const order = challengeSpec.order;
    const blockName = challengeSpec.name;
    const superBlock = challengeSpec.superBlock;
    const superOrder = challengeSpec.superOrder;
    const isBeta = !!challengeSpec.isBeta;
    const isComingSoon = !!challengeSpec.isComingSoon;
    const fileName = challengeSpec.fileName;
    const helpRoom = challengeSpec.helpRoom || 'Help';
    const time = challengeSpec.time;
    const isLocked = !!challengeSpec.isLocked;
    const message = challengeSpec.message;
    const required = challengeSpec.required || [];
    const template = challengeSpec.template;
    const isPrivate = !!challengeSpec.isPrivate;

    log('parsed %s successfully', blockName);

    // challenge file has no challenges...
    if (challengeSpec.challenges.length === 0) {
      return Rx.Observable.just([{ block: 'empty ' + blockName }]);
    }

    const block = {
      title: blockName,
      name: nameify(blockName),
      dashedName: dasherize(blockName),
      superOrder,
      superBlock,
      superBlockMessage: message,
      order,
      time,
      isLocked,
      isPrivate
    };

    return createBlocks(block)
      .map(block => {
        log('successfully created %s block', block.name);

        return challengeSpec.challenges
          .map(function(challenge, index) {
            challenge.name = nameify(challenge.title);

            challenge.dashedName = dasherize(challenge.name);

            challenge.checksum = adler32.sum(
              Buffer(
                challenge.title +
                JSON.stringify(challenge.description) +
                JSON.stringify(challenge.challengeSeed) +
                JSON.stringify(challenge.tests)
              )
            );

            if (challenge.files) {
              challenge.files = _.reduce(challenge.files, (map, file) => {
                map[file.key] = {
                  ...file,
                  head: arrToString(file.head),
                  contents: arrToString(file.contents),
                  tail: arrToString(file.tail)
                };
                return map;
              }, {});
            }
            challenge.fileName = fileName;
            challenge.helpRoom = helpRoom;
            challenge.order = order;
            challenge.suborder = index + 1;
            challenge.block = dasherize(blockName);
            challenge.blockId = '' + block.id;
            challenge.isBeta = challenge.isBeta || isBeta;
            challenge.isComingSoon = challenge.isComingSoon || isComingSoon;
            challenge.isLocked = challenge.isLocked || isLocked;
            challenge.isPrivate = challenge.isPrivate || isPrivate;
            challenge.time = challengeSpec.time;
            challenge.superOrder = superOrder;
            challenge.superBlock = superBlock
              .split('-')
              .map(function(word) {
                return _.capitalize(word);
              })
              .join(' ');
            challenge.required = (challenge.required || []).concat(required);
            challenge.template = challenge.template || template;
            return _.omit(
              challenge,
              [
                'betaSolutions',
                'betaTests',
                'hints',
                'MDNlinks',
                'null',
                'rawSolutions',
                'react',
                'reactRedux',
                'redux',
                'releasedOn',
                'translations',
                'type'
              ]
            );
          });
      })
      .flatMap(challenges => {
        challenges.forEach(challenge => {
          const result = validateChallenge(challenge);
          if (result.error) {
            console.log(result.value);
            throw new Error(result.error);
          }
        });
        return createChallenges(challenges);
      });
  })
  .subscribe(
    function(challenges) {
      log('%s successfully saved', challenges[0].block);
    },
    function(err) { throw err; },
    function() {
      log('challenge seed completed');
      process.exit(0);
    }
  );
<commit_msg>fix: current-challenge redirect (#17528)<commit_after>/* eslint-disable no-process-exit */
require('babel-register');
require('dotenv').load();
const adler32 = require('adler32');

const Rx = require('rx');
const _ = require('lodash');
const createDebugger = require('debug');
const utils = require('../server/utils');
const getChallenges = require('./getChallenges');
const { validateChallenge } = require(
  './schema/challengeSchema'
);
const app = require('../server/server');

const log = createDebugger('fcc:seed');
// force logger to always output
// this may be brittle
log.enabled = true;

const dasherize = utils.dasherize;
const nameify = utils.nameify;
const Observable = Rx.Observable;
const Challenge = app.models.Challenge;

const destroyChallenges =
  Observable.fromNodeCallback(Challenge.destroyAll, Challenge);
const createChallenges =
  Observable.fromNodeCallback(Challenge.create, Challenge);

const Block = app.models.Block;
const destroyBlocks = Observable.fromNodeCallback(Block.destroyAll, Block);
const createBlocks = Observable.fromNodeCallback(Block.create, Block);
const arrToString = arr =>
  Array.isArray(arr) ? arr.join('\n') : _.toString(arr);

Observable.combineLatest(
  destroyChallenges(),
  destroyBlocks()
)
  .last()
  .flatMap(function() { return Observable.from(getChallenges()); })
  .flatMap(function(challengeSpec) {
    const order = challengeSpec.order;
    const blockName = challengeSpec.name;
    const superBlock = challengeSpec.superBlock;
    const superOrder = challengeSpec.superOrder;
    const isBeta = !!challengeSpec.isBeta;
    const isComingSoon = !!challengeSpec.isComingSoon;
    const fileName = challengeSpec.fileName;
    const helpRoom = challengeSpec.helpRoom || 'Help';
    const time = challengeSpec.time;
    const isLocked = !!challengeSpec.isLocked;
    const message = challengeSpec.message;
    const required = challengeSpec.required || [];
    const template = challengeSpec.template;
    const isPrivate = !!challengeSpec.isPrivate;

    log('parsed %s successfully', blockName);

    // challenge file has no challenges...
    if (challengeSpec.challenges.length === 0) {
      return Rx.Observable.just([{ block: 'empty ' + blockName }]);
    }

    const block = {
      title: blockName,
      name: nameify(blockName),
      dashedName: dasherize(blockName),
      superOrder,
      superBlock,
      superBlockMessage: message,
      order,
      time,
      isLocked,
      isPrivate
    };

    return createBlocks(block)
      .map(block => {
        log('successfully created %s block', block.name);

        return challengeSpec.challenges
          .map(function(challenge, index) {
            challenge.name = nameify(challenge.title);

            challenge.dashedName = dasherize(challenge.title);

            challenge.checksum = adler32.sum(
              Buffer(
                challenge.title +
                JSON.stringify(challenge.description) +
                JSON.stringify(challenge.challengeSeed) +
                JSON.stringify(challenge.tests)
              )
            );

            if (challenge.files) {
              challenge.files = _.reduce(challenge.files, (map, file) => {
                map[file.key] = {
                  ...file,
                  head: arrToString(file.head),
                  contents: arrToString(file.contents),
                  tail: arrToString(file.tail)
                };
                return map;
              }, {});
            }
            challenge.fileName = fileName;
            challenge.helpRoom = helpRoom;
            challenge.order = order;
            challenge.suborder = index + 1;
            challenge.block = dasherize(blockName);
            challenge.blockId = '' + block.id;
            challenge.isBeta = challenge.isBeta || isBeta;
            challenge.isComingSoon = challenge.isComingSoon || isComingSoon;
            challenge.isLocked = challenge.isLocked || isLocked;
            challenge.isPrivate = challenge.isPrivate || isPrivate;
            challenge.time = challengeSpec.time;
            challenge.superOrder = superOrder;
            challenge.superBlock = superBlock
              .split('-')
              .map(function(word) {
                return _.capitalize(word);
              })
              .join(' ');
            challenge.required = (challenge.required || []).concat(required);
            challenge.template = challenge.template || template;
            return _.omit(
              challenge,
              [
                'betaSolutions',
                'betaTests',
                'hints',
                'MDNlinks',
                'null',
                'rawSolutions',
                'react',
                'reactRedux',
                'redux',
                'releasedOn',
                'translations',
                'type'
              ]
            );
          });
      })
      .flatMap(challenges => {
        challenges.forEach(challenge => {
          const result = validateChallenge(challenge);
          if (result.error) {
            console.log(result.value);
            throw new Error(result.error);
          }
        });
        return createChallenges(challenges);
      });
  })
  .subscribe(
    function(challenges) {
      log('%s successfully saved', challenges[0].block);
    },
    function(err) { throw err; },
    function() {
      log('challenge seed completed');
      process.exit(0);
    }
  );
<|endoftext|>


<commit_before>var debug = require('debug')('instruction');
var runner = require('./runner')();

var inst = runner.inst;
var before = runner.before;
var after = runner.after;

// Docs
//
// - http://www.z80.info/zip/z80cpu_um.pdf
// - http://www.z80.info/z80gboy.txt
// - http://gbdev.gg8.se/wiki/articles/Gameboy_Bootstrap_ROM#Contents_of_the_ROM

before(function (spec, cpu) {

    debug('next %s %s', spec.mnemonic, spec.operands);
    debug('next ZNHC flags [%s]', spec.flags_znhc.join(' '));

    debug('current ZNHC flags %s', cpu.f.toString(2));

    // Previous state

    this.pc = cpu.pc;
});

after(function (spec, cpu) {

    // Reset flags when possible

    var flags = spec.flags_znhc;

    [7, 6, 5, 4].forEach(function (pos, i) {

        var reset = parseInt(flags[i], 10);
        if (!isNaN(reset)) {
            var flag = 1 << pos;
            debug('reset 0x%s flag = %d', flag.toString(16), reset);
            reset === 1 ? cpu.f |= flag : cpu.f &= ~flag;
        }
    });

    debug('updated ZNHC flags %s', cpu.f.toString(2));

    // Next cycle

    cpu.t += spec.cycles;

    // Do not override PC value if has been changed in a jump instruction

    if (cpu.pc === this.pc) cpu.pc += spec.bytes;
});

// Instructions
//
// - http://www.z80.info/z80gboy.txt
// - http://www.z80.info/zip/z80cpu_um.pdf

// Unprefixed:
//
// ADD, ADC, AND, CPL, CCF, CP, CALL, DEC, DAA, DI, EI, HALT, INC, JR, JP, LD,
// LDH, NOP, OR, POP, PUSH, PREFIX, RLCA, RRCA, RLA, RRA, RET, RST, RETI, STOP,
// SCF, SUB, SBC, XOR

// CALL cc, nn
// CALL nn
//
// Description:
//
// If condition cc is true, this instruction pushes the current contents of the
// Program Counter (PC) onto the top of the external memory stack, then loads
// the operands nn to PC to point to the address in memory where the first
// Op Code of a subroutine is to be fetched. At the end of the subroutine, a
// RETurn instruction can be used to return to the original program flow by
// popping the top of the stack back to PC. If condition cc is false, the
// Program Counter is incremented as usual, and the program continues with the
// next sequential instruction. The stack push is accomplished by first
// decrementing the current contents of the Stack Pointer (SP), loading the
// high-order byte of the PC contents to the memory address now pointed to by
// SP; then decrementing SP again, and loading the low order byte of the PC
// contents to the top of the stack.
//
// Because this is a 3-byte instruction, the Program Counter was incremented by
// three before the push is executed.
//
// Condition Bits Affected: None

inst('call', function (op1, op2, cpu) {

    var jump = function (addr) {
        cpu.sp -= 2;
        cpu.mmu.writeWord(cpu.sp, cpu.pc >> 8 | (cpu.pc << 8) & 0xFF00);
        return cpu.pc = addr;
    }

    if (!op2) return jump(op1())
    else if (op1() == 1) return jump(op2());
    return cpu.pc;
});

// INC r
// INC (HL)
//
// Description:
//
// Register r is incremented and register r identifies any of the registers
// A, B, C, D, E, H, or L, assembled as follows in the object code.
//
// Condition Bits Affected:
//
// S is set if result is negative; reset otherwise
// Z is set if result is zero; reset otherwise
// H is set if carry from bit 3; reset otherwise
// P/V is set if r was 7FH before operation; reset otherwise N is reset
// C is not affected
//
// Example:
//
// If the contents of register D are 28H, at execution of INC D the contents of
// register D are 29H.

inst('inc', function (op1, op2, cpu) {

    var value = op1() + 1;

    cpu.f |= value === 0 ? 0x80 : cpu.f;

    if (op1.indirect) return op1(value);
    return cpu[op1.operand] = value;
});

// JR Z, e
// JR NZ, e
//
// Description:
//
// This instruction provides for conditional branching to other segments of a
// program depending on the results of a test on the Zero Flag. If the flag is
// equal to a 1, the value of the displacement e is added to the Program
// Counter (PC) and the next instruction is fetched from the location designated
// by the new contents of the PC. The jump is measured from the address of the
// instruction Op Code and has a range of -126 to +129 bytes. The assembler
// automatically adjusts for the twice incremented PC.
//
// Condition Bits Affected: None

inst('jr', function (op1, op2, cpu) {

    if (op1 && op2 && op1() === 1) return cpu.pc += op2();
    else if (op1) return cpu.pc += op1();
    return cpu.pc;
});

// LD (BC),A    LD (DE),A   LD (HL),n   LD (HL),r   LD (nn),A
// LD (nn),dd   LD (nn),HL  LD A,(BC)   LD A,(DE)   LD A,(nn)
// LD A,I       LD A,R9     LD dd,(nn)  LD dd,nn    LD HL,(nn)
// LD I,A       LD r,(HL)   LD R,A1     LD r,r'     LD r,n
// LD SP,HL
//
// Description:
//
// The contents of the Accumulator are loaded to the memory location specified
// by the contents of the register pair BC.
//
// Condition Bits Affected: None
//
// Example:
//
// If the Accumulator contains 7AH and the BC register pair contains 1212H the
// instruction LD (BC), A results in 7AH in memory location 1212H.

inst('ld', function (op1, op2, cpu) {

    if (op1.indirect) return op1(op2);
    return cpu[op1.operand] = op2();
});

// LD (byte),A
// LD A,(byte)
//
// Description: Save A at (FF00+byte)

inst('ldh', function (op1, op2, cpu) {

    if (!op1.indirect) return cpu[op1.operand] = op2();
    return op1(op2);
});

// PUSH qq
// PUSH IX
// PUSH IY
//
// Description:
//
// The contents of the register pair qq are pushed to the external memory
// LIFO (last-in, first-out) Stack. The Stack Pointer (SP) register pair holds
// the 16-bit address of the current top of the Stack. This instruction first
// decrements SP and loads the high order byte of register pair qq to the memory
// address specified by the SP. The SP is decremented again and loads the low
// order byte of qq to the memory location corresponding to this new address in
// the SP.
//
// Condition Bits Affected: None
//
// Example:
//
// If the AF register pair contains 2233H and the Stack Pointer contains 1007H,
// at instruction PUSH AF memory address 1006H contains 22H, memory address
// 1005H contains 33H, and the Stack Pointer contains 1005H.

inst('push', function (op1, op2, cpu) {

    cpu.sp -= 2;

    var operand = op1();
    var value = operand >> 8 | (operand << 8) & 0xFF00;

    return cpu.mmu.writeWord(cpu.sp, value);
});

// RLA
//
// Description:
//
// The contents of the Accumulator (register A) are rotated left 1-bit position
// through the Carry flag. The previous content of the Carry flag is copied to
// bit 0. Bit 0 is the least-significant bit.
//
// Condition Bits Affected:
//
// S is not affected
// Z is not affected
// H is reset
// P/V is not affected
// N is reset
// C is data from bit 7 of Accumulator

inst('rla', function (op1, op2, cpu) {

    var shifted = (cpu.a << 1) + (cpu.f & 0x10 > 0 ? 1 : 0);

    if (shifted >> 8 > 0) cpu.f |= 0x10;
    else cpu.f &= ~(1 << 0x10);

    return cpu.a = shifted & 0xFF;
});

// XOR s
//
// Description:
//
// The logical exclusive-OR operation is performed between the byte specified by
// the s operand and the byte contained in the Accumulator; the result is stored
// in the Accumulator.
//
// Conditions Bits Affected:
//
// S is set if result is negative; reset otherwise
// Z is set if result is zero; reset otherwise H is reset
// H is reset
// P/V is set if parity even; reset otherwise N is reset
// N is reset
// C is reset
//
// Example:
//
// If the Accumulator contains 96H (1001 0110), at execution of XOR
// 5DH(5DH = 0101 1101) the Accumulator contains CBH (1100 1011).

inst('xor', function (op1, op2, cpu) {

    cpu.a ^= op1();
    return cpu.f |= cpu.a === 0 ? 0x80 : cpu.f;
});

// 0xCB Prefixed
//
// BIT, RLC, RRC, RL, RR, RES, SLA, SRA, SWAP, SRL, SET

// BIT b,r  BIT b,(HL)
//
// Description:
//
// This instruction tests bit b in register r and sets the Z flag accordingly.
//
// Condition Bits Affected:
//
// S is unknown
// Z is set if specified bit is 0; reset otherwise
// H is set
// P/V is unknown
// N is reset
// C is not affected
//
// Example:
//
// If bit 2 in register B contains 0, at execution of BIT 2, B the Z flag in the
// F register contains 1, and bit 2 in register B remains 0. Bit 0 in register B
// is the least-significant bit.

inst('bit', function (op1, op2, cpu) {

    if (op1() & op2()) return cpu.f |= 0x80;
    return cpu.f;
});

// RL m
//
// Description:
//
// The contents of the m operand are rotated left 1-bit position. The content of
// bit 7 is copied to the Carry flag and the previous content of the Carry flag
// is copied to bit 0.
//
// Condition Bits Affected:
//
// S is set if result is negative; reset otherwise
// Z is set if result is zero; reset otherwise
// H is reset
// P/V is set if parity even; reset otherwise
// N is reset
// C is data from bit 7 of source register

inst('rl', function (op1, op2, cpu) {

    var shifted = (op1() << 1) + (cpu.f & 0x10 > 0 ? 1 : 0);

    if (shifted >> 8 > 0) cpu.f |= 0x10;
    else cpu.f &= ~(1 << 0x10);

    var value = shifted & 0xFF;

    if (value === 0) cpu.f |= 0x80;

    if (op1.indirect) return op1(value);
    return cpu[op1.operand] = value;
});

// Exports

module.exports = runner.prebake;
<commit_msg>flags<commit_after>var debug = require('debug')('instruction');
var runner = require('./runner')();

var inst = runner.inst;
var before = runner.before;
var after = runner.after;

// Docs
//
// - http://www.z80.info/zip/z80cpu_um.pdf
// - http://www.z80.info/z80gboy.txt
// - http://gbdev.gg8.se/wiki/articles/Gameboy_Bootstrap_ROM#Contents_of_the_ROM

before(function (spec, cpu) {

    debug('next %s %s', spec.mnemonic, spec.operands);
    debug('next ZNHC flags [%s]', spec.flags_znhc.join(' '));

    debug('current ZNHC flags %s', cpu.f.toString(2));

    // Previous state

    this.pc = cpu.pc;
});

after(function (spec, cpu) {

    // Reset flags when possible

    var flags = spec.flags_znhc;

    [7, 6, 5, 4].forEach(function (pos, i) {

        var reset = parseInt(flags[i], 10);
        if (!isNaN(reset)) {
            var flag = 1 << pos;
            debug('reset 0x%s flag = %d', flag.toString(16), reset);
            reset === 1 ? cpu.f |= flag : cpu.f &= ~flag;
        }
    });

    debug('updated ZNHC flags %s', cpu.f.toString(2));

    // Next cycle

    cpu.t += spec.cycles;

    // Do not override PC value if has been changed in a jump instruction

    if (cpu.pc === this.pc) cpu.pc += spec.bytes;
});

// Instructions
//
// - http://www.z80.info/z80gboy.txt
// - http://www.z80.info/zip/z80cpu_um.pdf

// Unprefixed:
//
// ADD, ADC, AND, CPL, CCF, CP, CALL, DEC, DAA, DI, EI, HALT, INC, JR, JP, LD,
// LDH, NOP, OR, POP, PUSH, PREFIX, RLCA, RRCA, RLA, RRA, RET, RST, RETI, STOP,
// SCF, SUB, SBC, XOR

// CALL cc, nn
// CALL nn
//
// Description:
//
// If condition cc is true, this instruction pushes the current contents of the
// Program Counter (PC) onto the top of the external memory stack, then loads
// the operands nn to PC to point to the address in memory where the first
// Op Code of a subroutine is to be fetched. At the end of the subroutine, a
// RETurn instruction can be used to return to the original program flow by
// popping the top of the stack back to PC. If condition cc is false, the
// Program Counter is incremented as usual, and the program continues with the
// next sequential instruction. The stack push is accomplished by first
// decrementing the current contents of the Stack Pointer (SP), loading the
// high-order byte of the PC contents to the memory address now pointed to by
// SP; then decrementing SP again, and loading the low order byte of the PC
// contents to the top of the stack.
//
// Because this is a 3-byte instruction, the Program Counter was incremented by
// three before the push is executed.
//
// Condition Bits Affected: None

inst('call', function (op1, op2, cpu) {

    var jump = function (addr) {
        cpu.sp -= 2;
        cpu.mmu.writeWord(cpu.sp, cpu.pc >> 8 | (cpu.pc << 8) & 0xFF00);
        return cpu.pc = addr;
    }

    if (!op2) return jump(op1())
    else if (op1() == 1) return jump(op2());
    return cpu.pc;
});

// INC r
// INC (HL)
//
// Description:
//
// Register r is incremented and register r identifies any of the registers
// A, B, C, D, E, H, or L, assembled as follows in the object code.
//
// Condition Bits Affected:
//
// S is set if result is negative; reset otherwise
// Z is set if result is zero; reset otherwise
// H is set if carry from bit 3; reset otherwise
// P/V is set if r was 7FH before operation; reset otherwise
// N is reset
// C is not affected
//
// Example:
//
// If the contents of register D are 28H, at execution of INC D the contents of
// register D are 29H.

inst('inc', function (op1, op2, cpu) {

    var value = op1() + 1;

    value === 0 ? cpu.f |= 0x80 : cpu.f &= ~(1 << 0x80);

    if (op1.indirect) return op1(value);
    return cpu[op1.operand] = value;
});

// JR Z, e
// JR NZ, e
//
// Description:
//
// This instruction provides for conditional branching to other segments of a
// program depending on the results of a test on the Zero Flag. If the flag is
// equal to a 1, the value of the displacement e is added to the Program
// Counter (PC) and the next instruction is fetched from the location designated
// by the new contents of the PC. The jump is measured from the address of the
// instruction Op Code and has a range of -126 to +129 bytes. The assembler
// automatically adjusts for the twice incremented PC.
//
// Condition Bits Affected: None

inst('jr', function (op1, op2, cpu) {

    if (op1 && op2 && op1() === 1) return cpu.pc += op2();
    else if (op1) return cpu.pc += op1();
    return cpu.pc;
});

// LD (BC),A    LD (DE),A   LD (HL),n   LD (HL),r   LD (nn),A
// LD (nn),dd   LD (nn),HL  LD A,(BC)   LD A,(DE)   LD A,(nn)
// LD A,I       LD A,R9     LD dd,(nn)  LD dd,nn    LD HL,(nn)
// LD I,A       LD r,(HL)   LD R,A1     LD r,r'     LD r,n
// LD SP,HL
//
// Description:
//
// The contents of the Accumulator are loaded to the memory location specified
// by the contents of the register pair BC.
//
// Condition Bits Affected: None
//
// Example:
//
// If the Accumulator contains 7AH and the BC register pair contains 1212H the
// instruction LD (BC), A results in 7AH in memory location 1212H.

inst('ld', function (op1, op2, cpu) {

    if (op1.indirect) return op1(op2);
    return cpu[op1.operand] = op2();
});

// LD (byte),A
// LD A,(byte)
//
// Description: Save A at (FF00+byte)

inst('ldh', function (op1, op2, cpu) {

    if (!op1.indirect) return cpu[op1.operand] = op2();
    return op1(op2);
});

// PUSH qq
// PUSH IX
// PUSH IY
//
// Description:
//
// The contents of the register pair qq are pushed to the external memory
// LIFO (last-in, first-out) Stack. The Stack Pointer (SP) register pair holds
// the 16-bit address of the current top of the Stack. This instruction first
// decrements SP and loads the high order byte of register pair qq to the memory
// address specified by the SP. The SP is decremented again and loads the low
// order byte of qq to the memory location corresponding to this new address in
// the SP.
//
// Condition Bits Affected: None
//
// Example:
//
// If the AF register pair contains 2233H and the Stack Pointer contains 1007H,
// at instruction PUSH AF memory address 1006H contains 22H, memory address
// 1005H contains 33H, and the Stack Pointer contains 1005H.

inst('push', function (op1, op2, cpu) {

    cpu.sp -= 2;

    var operand = op1();
    var value = operand >> 8 | (operand << 8) & 0xFF00;

    return cpu.mmu.writeWord(cpu.sp, value);
});

// RLA
//
// Description:
//
// The contents of the Accumulator (register A) are rotated left 1-bit position
// through the Carry flag. The previous content of the Carry flag is copied to
// bit 0. Bit 0 is the least-significant bit.
//
// Condition Bits Affected:
//
// S is not affected
// Z is not affected
// H is reset
// P/V is not affected
// N is reset
// C is data from bit 7 of Accumulator

inst('rla', function (op1, op2, cpu) {

    var shifted = (cpu.a << 1) + (cpu.f & 0x10 > 0 ? 1 : 0);

    if (shifted >> 8 > 0) cpu.f |= 0x10;
    else cpu.f &= ~(1 << 0x10);

    return cpu.a = shifted & 0xFF;
});

// XOR s
//
// Description:
//
// The logical exclusive-OR operation is performed between the byte specified by
// the s operand and the byte contained in the Accumulator; the result is stored
// in the Accumulator.
//
// Conditions Bits Affected:
//
// S is set if result is negative; reset otherwise
// Z is set if result is zero; reset otherwise
// H is reset
// P/V is set if parity even; reset otherwise
// N is reset
// C is reset
//
// Example:
//
// If the Accumulator contains 96H (1001 0110), at execution of XOR
// 5DH(5DH = 0101 1101) the Accumulator contains CBH (1100 1011).

inst('xor', function (op1, op2, cpu) {

    cpu.a ^= op1();
    return cpu.a === 0 ? cpu.f |= 0x80 : cpu.f &= ~(1 << 0x80);
});

// 0xCB Prefixed
//
// BIT, RLC, RRC, RL, RR, RES, SLA, SRA, SWAP, SRL, SET

// BIT b,r  BIT b,(HL)
//
// Description:
//
// This instruction tests bit b in register r and sets the Z flag accordingly.
//
// Condition Bits Affected:
//
// S is unknown
// Z is set if specified bit is 0; reset otherwise
// H is set
// P/V is unknown
// N is reset
// C is not affected
//
// Example:
//
// If bit 2 in register B contains 0, at execution of BIT 2, B the Z flag in the
// F register contains 1, and bit 2 in register B remains 0. Bit 0 in register B
// is the least-significant bit.

inst('bit', function (op1, op2, cpu) {

    if (op1() & op2()) return cpu.f |= 0x80;
    return cpu.f &= ~(1 << 0x80);
});

// RL m
//
// Description:
//
// The contents of the m operand are rotated left 1-bit position. The content of
// bit 7 is copied to the Carry flag and the previous content of the Carry flag
// is copied to bit 0.
//
// Condition Bits Affected:
//
// S is set if result is negative; reset otherwise
// Z is set if result is zero; reset otherwise
// H is reset
// P/V is set if parity even; reset otherwise
// N is reset
// C is data from bit 7 of source register

inst('rl', function (op1, op2, cpu) {

    var shifted = (op1() << 1) + (cpu.f & 0x10 > 0 ? 1 : 0);

    if (shifted >> 8 > 0) cpu.f |= 0x10;
    else cpu.f &= ~(1 << 0x10);

    var value = shifted & 0xFF;

    value === 0 ? cpu.f |= 0x80 : cpu.f &= ~(1 << 0x80);

    if (op1.indirect) return op1(value);
    return cpu[op1.operand] = value;
});

// Exports

module.exports = runner.prebake;
<|endoftext|>


<commit_before>// selectbox1.js
//
// This is a simple test script that does the following tests:
//  open a website
//  validate title
//  selects checked selectbox option
//
//

// required libraries
var webdriverio = require('webdriverio'),
  should = require('should');

// a test script block or suite
describe('Selectbox Test for Web Driver IO - Tutorial Test Page Website', function() {

  // set timeout to 10 seconds
	this.timeout(10000);
  var driver = {};

  // hook to run before tests
  before( function (done) {
    // check for global browser (grunt + grunt-webdriver)
    if(typeof browser === "undefined") {
      // load the driver for browser
      driver = webdriverio.remote({ desiredCapabilities: {browserName: 'firefox'} });
      driver.init(done);
    } else {
      // grunt will load the browser driver
      driver = browser;
      done();
    }
  });

  it('should be load correct page and title', function () {
    // load page, then call function()
    return driver
      .url('file:///Users/tkeith/Testing/Tutorial/HTML/WebDriverIOTutorialTest.html')
      // get title, then pass title to function()
      .getTitle().then( function (title) {
        // verify title
        (title).should.be.equal("Web Driver IO - Tutorial Test Page");
        // uncomment for console debug
        // console.log('Current Page Title: ' + title);
      });
  });

  it('should select checked options from selectbox', function () {
    return driver
      .getText('#selectbox option:checked').then(function(value) {
        console.log('Selected Text: ' + value);
        (value).should.be.equal("Los Angeles Kings");
    })
    .selectByVisibleText('#selectbox', 'San Jose Sharks')
    .getText('#selectbox option:checked').then(function(value) {
      console.log('Selected Text: ' + value);
      (value).should.be.equal("San Jose Sharks");
    })
    .selectByVisibleText('#selectbox', 'Pittsburgh Penguins')
    .getText('#selectbox option:checked').then(function(value) {
      console.log('Selected Text: ' + value);
      (value).should.be.equal("Pittsburgh Penguins");
    });
  });

  // a "hook" to run after all tests in this block
	after(function(done) {
    if(typeof browser === "undefined") {
      driver.end(done);
    } else {
      done();
    }
  });
});<commit_msg>Fixed URL to public instead of local<commit_after>// selectbox1.js
//
// This is a simple test script that does the following tests:
//  open a website
//  validate title
//  selects checked selectbox option
//
//

// required libraries
var webdriverio = require('webdriverio'),
  should = require('should');

// a test script block or suite
describe('Selectbox Test for Web Driver IO - Tutorial Test Page Website', function() {

  // set timeout to 10 seconds
	this.timeout(10000);
  var driver = {};

  // hook to run before tests
  before( function (done) {
    // check for global browser (grunt + grunt-webdriver)
    if(typeof browser === "undefined") {
      // load the driver for browser
      driver = webdriverio.remote({ desiredCapabilities: {browserName: 'firefox'} });
      driver.init(done);
    } else {
      // grunt will load the browser driver
      driver = browser;
      done();
    }
  });

  it('should be load correct page and title', function () {
    // load page, then call function()
    return driver
      .url('http://www.tlkeith.com/WebDriverIOTutorialTest.html')
      // get title, then pass title to function()
      .getTitle().then( function (title) {
        // verify title
        (title).should.be.equal("Web Driver IO - Tutorial Test Page");
        // uncomment for console debug
        // console.log('Current Page Title: ' + title);
      });
  });

  it('should select checked options from selectbox', function () {
    return driver
      .getText('#selectbox option:checked').then(function(value) {
        console.log('Selected Text: ' + value);
        (value).should.be.equal("Los Angeles Kings");
    })
    .selectByVisibleText('#selectbox', 'San Jose Sharks')
    .getText('#selectbox option:checked').then(function(value) {
      console.log('Selected Text: ' + value);
      (value).should.be.equal("San Jose Sharks");
    })
    .selectByVisibleText('#selectbox', 'Pittsburgh Penguins')
    .getText('#selectbox option:checked').then(function(value) {
      console.log('Selected Text: ' + value);
      (value).should.be.equal("Pittsburgh Penguins");
    });
  });

  // a "hook" to run after all tests in this block
	after(function(done) {
    if(typeof browser === "undefined") {
      driver.end(done);
    } else {
      done();
    }
  });
});<|endoftext|>


<commit_before>/** @flow */

import type { Alignment, CellSizeGetter, VisibleCellRange } from "../types";

type CellSizeAndPositionManagerParams = {
  batchAllCells: boolean,
  cellCount: number,
  cellSizeGetter: CellSizeGetter,
  estimatedCellSize: number
};

type ConfigureParams = {
  cellCount: number,
  estimatedCellSize: number
};

type GetUpdatedOffsetForIndex = {
  align: Alignment,
  containerSize: number,
  currentOffset: number,
  targetIndex: number
};

type GetVisibleCellRangeParams = {
  containerSize: number,
  offset: number
};

type SizeAndPositionData = {
  offset: number,
  size: number
};

/**
 * Just-in-time calculates and caches size and position information for a collection of cells.
 */

export default class CellSizeAndPositionManager {
  // Cache of size and position data for cells, mapped by cell index.
  // Note that invalid values may exist in this map so only rely on cells up to this._lastMeasuredIndex
  _cellSizeAndPositionData = {};

  // Measurements for cells up to this index can be trusted; cells afterward should be estimated.
  _lastMeasuredIndex = -1;

  // Used in deferred mode to track which cells have been queued for measurement.
  _lastBatchedIndex = -1;

  _batchAllCells: boolean;
  _cellCount: number;
  _cellSizeGetter: CellSizeGetter;
  _estimatedCellSize: number;

  constructor({
    batchAllCells = false,
    cellCount,
    cellSizeGetter,
    estimatedCellSize
  }: CellSizeAndPositionManagerParams) {
    this._batchAllCells = batchAllCells;
    this._cellSizeGetter = cellSizeGetter;
    this._cellCount = cellCount;
    this._estimatedCellSize = estimatedCellSize;
  }

  areOffsetsAdjusted() {
    return false;
  }

  configure({ cellCount, estimatedCellSize }: ConfigureParams) {
    this._cellCount = cellCount;
    this._estimatedCellSize = estimatedCellSize;
  }

  getCellCount(): number {
    return this._cellCount;
  }

  getEstimatedCellSize(): number {
    return this._estimatedCellSize;
  }

  getLastMeasuredIndex(): number {
    return this._lastMeasuredIndex;
  }

  getOffsetAdjustment() {
    return 0;
  }

  /**
   * This method returns the size and position for the cell at the specified index.
   * It just-in-time calculates (or used cached values) for cells leading up to the index.
   */
  getSizeAndPositionOfCell(index: number): SizeAndPositionData {
    if (index < 0 || index >= this._cellCount) {
      throw Error(
        `Requested index ${index} is outside of range 0..${this._cellCount}`
      );
    }

    if (index > this._lastMeasuredIndex) {
      let lastMeasuredCellSizeAndPosition = this.getSizeAndPositionOfLastMeasuredCell();
      let offset =
        lastMeasuredCellSizeAndPosition.offset +
        lastMeasuredCellSizeAndPosition.size;

      for (var i = this._lastMeasuredIndex + 1; i <= index; i++) {
        let size = this._cellSizeGetter({ index: i });

        // undefined or NaN probably means a logic error in the size getter.
        // null means we're using CellMeasurer and haven't yet measured a given index.
        if (size === undefined || isNaN(size)) {
          throw Error(`Invalid size returned for cell ${i} of value ${size}`);
        } else if (size === null) {
          this._cellSizeAndPositionData[i] = {
            offset,
            size: 0
          };

          this._lastBatchedIndex = index;
        } else {
          this._cellSizeAndPositionData[i] = {
            offset,
            size
          };

          offset += size;

          this._lastMeasuredIndex = index;
        }
      }
    }

    return this._cellSizeAndPositionData[index];
  }

  getSizeAndPositionOfLastMeasuredCell(): SizeAndPositionData {
    return this._lastMeasuredIndex >= 0
      ? this._cellSizeAndPositionData[this._lastMeasuredIndex]
      : {
          offset: 0,
          size: 0
        };
  }

  /**
   * Total size of all cells being measured.
   * This value will be completely estimated initially.
   * As cells as measured the estimate will be updated.
   */
  getTotalSize(): number {
    const lastMeasuredCellSizeAndPosition = this.getSizeAndPositionOfLastMeasuredCell();

    return (
      lastMeasuredCellSizeAndPosition.offset +
      lastMeasuredCellSizeAndPosition.size +
      (this._cellCount - this._lastMeasuredIndex - 1) * this._estimatedCellSize
    );
  }

  /**
   * Determines a new offset that ensures a certain cell is visible, given the current offset.
   * If the cell is already visible then the current offset will be returned.
   * If the current offset is too great or small, it will be adjusted just enough to ensure the specified index is visible.
   *
   * @param align Desired alignment within container; one of "auto" (default), "start", or "end"
   * @param containerSize Size (width or height) of the container viewport
   * @param currentOffset Container's current (x or y) offset
   * @param totalSize Total size (width or height) of all cells
   * @return Offset to use to ensure the specified cell is visible
   */
  getUpdatedOffsetForIndex({
    align = "auto",
    containerSize,
    currentOffset,
    targetIndex
  }: GetUpdatedOffsetForIndex): number {
    if (containerSize <= 0) {
      return 0;
    }

    const datum = this.getSizeAndPositionOfCell(targetIndex);
    const maxOffset = datum.offset;
    const minOffset = maxOffset - containerSize + datum.size;

    let idealOffset;

    switch (align) {
      case "start":
        idealOffset = maxOffset;
        break;
      case "end":
        idealOffset = minOffset;
        break;
      case "center":
        idealOffset = maxOffset - (containerSize - datum.size) / 2;
        break;
      default:
        idealOffset = Math.max(minOffset, Math.min(maxOffset, currentOffset));
        break;
    }

    const totalSize = this.getTotalSize();

    return Math.max(0, Math.min(totalSize - containerSize, idealOffset));
  }

  getVisibleCellRange(params: GetVisibleCellRangeParams): VisibleCellRange {
    // Advanced use-cases (eg CellMeasurer) require batched measurements to determine accurate sizes.
    // eg we can't know a row's height without measuring the height of all columns within that row.
    if (this._batchAllCells) {
      return {
        start: 0,
        stop: this._cellCount - 1
      };
    }

    let { containerSize, offset } = params;

    const totalSize = this.getTotalSize();

    if (totalSize === 0) {
      return {};
    }

    const maxOffset = offset + containerSize;
    const start = this._findNearestCell(offset);

    const datum = this.getSizeAndPositionOfCell(start);
    offset = datum.offset + datum.size;

    let stop = start;

    while (offset < maxOffset && stop < this._cellCount - 1) {
      stop++;

      offset += this.getSizeAndPositionOfCell(stop).size;
    }

    return {
      start,
      stop
    };
  }

  /**
   * Clear all cached values for cells after the specified index.
   * This method should be called for any cell that has changed its size.
   * It will not immediately perform any calculations; they'll be performed the next time getSizeAndPositionOfCell() is called.
   */
  resetCell(index: number): void {
    this._lastMeasuredIndex = Math.min(this._lastMeasuredIndex, index - 1);
  }

  _binarySearch(high: number, low: number, offset: number): number {
    while (low <= high) {
      const middle = low + Math.floor((high - low) / 2);
      const currentOffset = this.getSizeAndPositionOfCell(middle).offset;

      if (currentOffset === offset) {
        return middle;
      } else if (currentOffset < offset) {
        low = middle + 1;
      } else if (currentOffset > offset) {
        high = middle - 1;
      }
    }

    if (low > 0) {
      return low - 1;
    } else {
      return 0;
    }
  }

  _exponentialSearch(index: number, offset: number): number {
    let interval = 1;

    while (
      index < this._cellCount &&
      this.getSizeAndPositionOfCell(index).offset < offset
    ) {
      index += interval;
      interval *= 2;
    }

    return this._binarySearch(
      Math.min(index, this._cellCount - 1),
      Math.floor(index / 2),
      offset
    );
  }

  /**
   * Searches for the cell (index) nearest the specified offset.
   *
   * If no exact match is found the next lowest cell index will be returned.
   * This allows partially visible cells (with offsets just before/above the fold) to be visible.
   */
  _findNearestCell(offset: number): number {
    if (isNaN(offset)) {
      throw Error(`Invalid offset ${offset} specified`);
    }

    // Our search algorithms find the nearest match at or below the specified offset.
    // So make sure the offset is at least 0 or no match will be found.
    offset = Math.max(0, offset);

    const lastMeasuredCellSizeAndPosition = this.getSizeAndPositionOfLastMeasuredCell();
    const lastMeasuredIndex = Math.max(0, this._lastMeasuredIndex);

    if (lastMeasuredCellSizeAndPosition.offset >= offset) {
      // If we've already measured cells within this range just use a binary search as it's faster.
      return this._binarySearch(lastMeasuredIndex, 0, offset);
    } else {
      // If we haven't yet measured this high, fallback to an exponential search with an inner binary search.
      // The exponential search avoids pre-computing sizes for the full set of cells as a binary search would.
      // The overall complexity for this approach is O(log n).
      return this._exponentialSearch(lastMeasuredIndex, offset);
    }
  }
}
<commit_msg>Make CellSizeAndPositionManager.getTotalSize() more explicit so it's easier to understand.<commit_after>/** @flow */

import type { Alignment, CellSizeGetter, VisibleCellRange } from "../types";

type CellSizeAndPositionManagerParams = {
  batchAllCells: boolean,
  cellCount: number,
  cellSizeGetter: CellSizeGetter,
  estimatedCellSize: number
};

type ConfigureParams = {
  cellCount: number,
  estimatedCellSize: number
};

type GetUpdatedOffsetForIndex = {
  align: Alignment,
  containerSize: number,
  currentOffset: number,
  targetIndex: number
};

type GetVisibleCellRangeParams = {
  containerSize: number,
  offset: number
};

type SizeAndPositionData = {
  offset: number,
  size: number
};

/**
 * Just-in-time calculates and caches size and position information for a collection of cells.
 */

export default class CellSizeAndPositionManager {
  // Cache of size and position data for cells, mapped by cell index.
  // Note that invalid values may exist in this map so only rely on cells up to this._lastMeasuredIndex
  _cellSizeAndPositionData = {};

  // Measurements for cells up to this index can be trusted; cells afterward should be estimated.
  _lastMeasuredIndex = -1;

  // Used in deferred mode to track which cells have been queued for measurement.
  _lastBatchedIndex = -1;

  _batchAllCells: boolean;
  _cellCount: number;
  _cellSizeGetter: CellSizeGetter;
  _estimatedCellSize: number;

  constructor({
    batchAllCells = false,
    cellCount,
    cellSizeGetter,
    estimatedCellSize
  }: CellSizeAndPositionManagerParams) {
    this._batchAllCells = batchAllCells;
    this._cellSizeGetter = cellSizeGetter;
    this._cellCount = cellCount;
    this._estimatedCellSize = estimatedCellSize;
  }

  areOffsetsAdjusted() {
    return false;
  }

  configure({ cellCount, estimatedCellSize }: ConfigureParams) {
    this._cellCount = cellCount;
    this._estimatedCellSize = estimatedCellSize;
  }

  getCellCount(): number {
    return this._cellCount;
  }

  getEstimatedCellSize(): number {
    return this._estimatedCellSize;
  }

  getLastMeasuredIndex(): number {
    return this._lastMeasuredIndex;
  }

  getOffsetAdjustment() {
    return 0;
  }

  /**
   * This method returns the size and position for the cell at the specified index.
   * It just-in-time calculates (or used cached values) for cells leading up to the index.
   */
  getSizeAndPositionOfCell(index: number): SizeAndPositionData {
    if (index < 0 || index >= this._cellCount) {
      throw Error(
        `Requested index ${index} is outside of range 0..${this._cellCount}`
      );
    }

    if (index > this._lastMeasuredIndex) {
      let lastMeasuredCellSizeAndPosition = this.getSizeAndPositionOfLastMeasuredCell();
      let offset =
        lastMeasuredCellSizeAndPosition.offset +
        lastMeasuredCellSizeAndPosition.size;

      for (var i = this._lastMeasuredIndex + 1; i <= index; i++) {
        let size = this._cellSizeGetter({ index: i });

        // undefined or NaN probably means a logic error in the size getter.
        // null means we're using CellMeasurer and haven't yet measured a given index.
        if (size === undefined || isNaN(size)) {
          throw Error(`Invalid size returned for cell ${i} of value ${size}`);
        } else if (size === null) {
          this._cellSizeAndPositionData[i] = {
            offset,
            size: 0
          };

          this._lastBatchedIndex = index;
        } else {
          this._cellSizeAndPositionData[i] = {
            offset,
            size
          };

          offset += size;

          this._lastMeasuredIndex = index;
        }
      }
    }

    return this._cellSizeAndPositionData[index];
  }

  getSizeAndPositionOfLastMeasuredCell(): SizeAndPositionData {
    return this._lastMeasuredIndex >= 0
      ? this._cellSizeAndPositionData[this._lastMeasuredIndex]
      : {
          offset: 0,
          size: 0
        };
  }

  /**
   * Total size of all cells being measured.
   * This value will be completely estimated initially.
   * As cells are measured, the estimate will be updated.
   */
  getTotalSize(): number {
    const lastMeasuredCellSizeAndPosition = this.getSizeAndPositionOfLastMeasuredCell();
    const numUnmeasuredCells = this._cellCount - this._lastMeasuredIndex - 1;

    const totalSizeOfMeasuredCells = (
        lastMeasuredCellSizeAndPosition.offset +
        lastMeasuredCellSizeAndPosition.size
    );
    const totalSizeOfUnmeasuredCells = numUnmeasuredCells * this._estimatedCellSize;

    return totalSizeOfMeasuredCells + totalSizeOfUnmeasuredCells;
  }

  /**
   * Determines a new offset that ensures a certain cell is visible, given the current offset.
   * If the cell is already visible then the current offset will be returned.
   * If the current offset is too great or small, it will be adjusted just enough to ensure the specified index is visible.
   *
   * @param align Desired alignment within container; one of "auto" (default), "start", or "end"
   * @param containerSize Size (width or height) of the container viewport
   * @param currentOffset Container's current (x or y) offset
   * @param totalSize Total size (width or height) of all cells
   * @return Offset to use to ensure the specified cell is visible
   */
  getUpdatedOffsetForIndex({
    align = "auto",
    containerSize,
    currentOffset,
    targetIndex
  }: GetUpdatedOffsetForIndex): number {
    if (containerSize <= 0) {
      return 0;
    }

    const datum = this.getSizeAndPositionOfCell(targetIndex);
    const maxOffset = datum.offset;
    const minOffset = maxOffset - containerSize + datum.size;

    let idealOffset;

    switch (align) {
      case "start":
        idealOffset = maxOffset;
        break;
      case "end":
        idealOffset = minOffset;
        break;
      case "center":
        idealOffset = maxOffset - (containerSize - datum.size) / 2;
        break;
      default:
        idealOffset = Math.max(minOffset, Math.min(maxOffset, currentOffset));
        break;
    }

    const totalSize = this.getTotalSize();

    return Math.max(0, Math.min(totalSize - containerSize, idealOffset));
  }

  getVisibleCellRange(params: GetVisibleCellRangeParams): VisibleCellRange {
    // Advanced use-cases (eg CellMeasurer) require batched measurements to determine accurate sizes.
    // eg we can't know a row's height without measuring the height of all columns within that row.
    if (this._batchAllCells) {
      return {
        start: 0,
        stop: this._cellCount - 1
      };
    }

    let { containerSize, offset } = params;

    const totalSize = this.getTotalSize();

    if (totalSize === 0) {
      return {};
    }

    const maxOffset = offset + containerSize;
    const start = this._findNearestCell(offset);

    const datum = this.getSizeAndPositionOfCell(start);
    offset = datum.offset + datum.size;

    let stop = start;

    while (offset < maxOffset && stop < this._cellCount - 1) {
      stop++;

      offset += this.getSizeAndPositionOfCell(stop).size;
    }

    return {
      start,
      stop
    };
  }

  /**
   * Clear all cached values for cells after the specified index.
   * This method should be called for any cell that has changed its size.
   * It will not immediately perform any calculations; they'll be performed the next time getSizeAndPositionOfCell() is called.
   */
  resetCell(index: number): void {
    this._lastMeasuredIndex = Math.min(this._lastMeasuredIndex, index - 1);
  }

  _binarySearch(high: number, low: number, offset: number): number {
    while (low <= high) {
      const middle = low + Math.floor((high - low) / 2);
      const currentOffset = this.getSizeAndPositionOfCell(middle).offset;

      if (currentOffset === offset) {
        return middle;
      } else if (currentOffset < offset) {
        low = middle + 1;
      } else if (currentOffset > offset) {
        high = middle - 1;
      }
    }

    if (low > 0) {
      return low - 1;
    } else {
      return 0;
    }
  }

  _exponentialSearch(index: number, offset: number): number {
    let interval = 1;

    while (
      index < this._cellCount &&
      this.getSizeAndPositionOfCell(index).offset < offset
    ) {
      index += interval;
      interval *= 2;
    }

    return this._binarySearch(
      Math.min(index, this._cellCount - 1),
      Math.floor(index / 2),
      offset
    );
  }

  /**
   * Searches for the cell (index) nearest the specified offset.
   *
   * If no exact match is found the next lowest cell index will be returned.
   * This allows partially visible cells (with offsets just before/above the fold) to be visible.
   */
  _findNearestCell(offset: number): number {
    if (isNaN(offset)) {
      throw Error(`Invalid offset ${offset} specified`);
    }

    // Our search algorithms find the nearest match at or below the specified offset.
    // So make sure the offset is at least 0 or no match will be found.
    offset = Math.max(0, offset);

    const lastMeasuredCellSizeAndPosition = this.getSizeAndPositionOfLastMeasuredCell();
    const lastMeasuredIndex = Math.max(0, this._lastMeasuredIndex);

    if (lastMeasuredCellSizeAndPosition.offset >= offset) {
      // If we've already measured cells within this range just use a binary search as it's faster.
      return this._binarySearch(lastMeasuredIndex, 0, offset);
    } else {
      // If we haven't yet measured this high, fallback to an exponential search with an inner binary search.
      // The exponential search avoids pre-computing sizes for the full set of cells as a binary search would.
      // The overall complexity for this approach is O(log n).
      return this._exponentialSearch(lastMeasuredIndex, offset);
    }
  }
}
<|endoftext|>


<commit_before>import Router from "routes"
import {Observable as O} from "rx"
import {createHashHistory} from "history"

const isObj = x => typeof x === "object" && x.constructor === Object

function makeRouterDriver(h = createHashHistory()) {
  return function RouterDriver({run, extract, compose}) {

    const location$ = O.create(o => {
      const dispose = h.listen(loc => o.onNext(loc))
      return {dispose}
    }).shareReplay(1)

    function route(in$, driverName, spec) {
      const r = new Router()
      Object.keys(spec).forEach(route => {
        r.addRoute(route, spec[route])
      })

      const path$ = extract(in$, driverName).map(l => l.pathname)
      return path$.flatMapLatest(path => {
        const res = r.match(path)
        if (!res || !res.fn) {
          return O.throw(new Error("No route found for path: " + path))
        }
        const main = res.fn
        const params$ = compose({[`${driverName}.params`]: O.just(res.params)})
        return run(in$.merge(params$), main)
      })
    }

    function executor(location$) {
      return location$.subscribe(newPath => {
        console.log(newPath)
        h.push(isObj(newPath) ? newPath : {
          pathname: newPath
        })
      })
    }

    const transducers = {
      route
    }

    return {
      signals: location$,
      transducers,
      executor
    }
  }

}

export default makeRouterDriver
<commit_msg>Follow @tsers/core 0.7.0 driver spec <commit_after>import Router from "routes"
import {Observable as O} from "rx"
import {createHashHistory} from "history"

const isObj = x => typeof x === "object" && x.constructor === Object

function makeRouterDriver(h = createHashHistory()) {
  return function RouterDriver({run, extract, compose}) {
    function route(in$, driverName, spec) {
      const r = new Router()
      Object.keys(spec).forEach(route => {
        r.addRoute(route, spec[route])
      })

      const path$ = extract(in$, driverName).map(l => l.pathname.replace(/^#/, ""))
      return path$.flatMapLatest(path => {
        const res = r.match(path)
        if (!res || !res.fn) {
          return O.throw(new Error("No route found for path: " + path))
        }
        const main = res.fn
        const params$ = compose({[`${driverName}.params`]: O.just(res.params)})
        return run(in$.merge(params$), main)
      })
    }

    const Transducers = {
      route
    }

    const location$ = O.create(o => {
      const dispose = h.listen(loc => o.onNext(loc))
      return {dispose}
    }).shareReplay(1)

    function executor(location$) {
      return location$.subscribe(newPath => {
        h.push(isObj(newPath) ? newPath : {
          pathname: newPath
        })
      })
    }

    return [Transducers, location$, executor]
  }

}

export default makeRouterDriver
<|endoftext|>


<commit_before>
cdb.admin.mod = cdb.admin.mod || {};
cdb.admin.mod.CartoCSSEditor = cdb.core.View.extend({

  buttonClass: 'cartocss_mod',
  type: 'tool',

  events: {
    'click .actions button':  'applyStyle',
    'click .actions a.next':  '_do',
    'click .actions a.back':  '_undo',
    'click .doc_info':    '_showDoc'
  },

  initialize: function() {
    this.template = this.getTemplate('table/menu_modules/views/carto_editor');

    this.model.bind('change',      this._updateStyle, this);
    this.add_related_model(this.model);
    this.add_related_model(this.model.table);

    // Set query position from history array and last sql applied
    var history   = this.model.get('tile_style_history')
      , position  = this.model.tile_style_history_position
      , style     = this.model.get('tile_style');

    // Model doesn't persist last change, let's add in the history
    if (style && style != "" && history && _.indexOf(history, style) === -1) {
      history.push(style);
      this.model.set({ "tile_style_history": history }, { silent:true });
    }

    // Get history position
    this.model.tile_style_history_position =
      _.indexOf(history, style) !== -1
      ? _.indexOf(history, style)
      : 0;

    this.model.bind('parseError', this._showErrorFromServer, this);
    /*
    this.model.bind('tileError', this._renderError, this);
    */
    this.model.bind('tileOk', this._checkLocalErrors, this);
    this.model.table.bind('change:schema', this._checkLocalErrors, this);
  },

  activated: function() {
    if(this.codeEditor) {
      this.codeEditor.refresh();
      this.codeEditor.focus();
    }
  },

  render: function() {
    this.$el.append(this.template({}));
    this.codeEditor = CodeMirror.fromTextArea(this.$('textarea')[0], {
      mode: "text/x-carto",
      tabMode: "indent",
      matchBrackets: true,
      lineNumbers: true,
      lineWrapping: true,
      extraKeys: {
       "Ctrl-Space": function(cm) {
         CodeMirror.showHint(cm, CodeMirror.hint['custom-list'], {
           word: /[\w\-$]+/,
           range: 50
         });
       }
      }
    });
    this._updateStyle();

    // Add tooltip for undo/redo buttons
    this.$el.find("a.next,a.back").tipsy({
      gravity: "s",
      fade: true
    });

    //TODO: move this to common/hotkeys.js
    // Codemirror extrakey
    // Add save keymap
    // PC & LINUX -> Ctrl + s
    // MAC        -> Cmd + s
    var ua      = navigator.userAgent.toLowerCase()
      , so      = "rest"
      , keymap  = "ctrl+s"
      , self    = this;

    if (/mac os/.test(ua)) {
      keymap = "meta+s";
      so = "mac";
    }

    this.$el.bind('keydown', keymap, function(ev) {
      if (((so === "mac" && ev.metaKey) || (so === "rest" && ev.ctrlKey)) && ev.keyCode == 83 ) {
        ev.preventDefault();
        self.applyStyle();
      }
    });

    return this;
  },

  /** hack used to format the old styles transformed to cartodb 2.0*/
  formatStyle: function(s) {
    try {
      if (s && s.length) {
        s = s.replace(/{/g,'{\n')
            .replace(/}/g,'}\n')
            .replace(/;/g,';\n')
        var t = s.split('\n');
        var lines = [];
        var c = 0;
        for(var i = 0; i < t.length; ++i) {
          lines.push(c);
          if (t[i].indexOf('{')  != -1) {
            ++c;
          }
          if (t[i].indexOf('}')  != -1) {
            --c;
          }
        }
        var r = [];
        for(var i = 0; i < t.length; ++i) {
          var spaces = '';
          if(t[i].indexOf('}') >= 0) lines[i]-=1;
          for(var j = 0; j < lines[i]; ++j) {
            spaces = spaces + '  ';
          }
          r.push(spaces + t[i]);
        }
        return '/** this cartoCSS has been processed in order to be compatible with the new cartodb 2.0 */\n\n' + r.join('\n');
      }
    } catch(e) {
    }
    return s;
  },

  _updateStyle: function(){
    var st        = this.model.get('tile_style')
      , editor_st = this.codeEditor.getValue();

      if(this.codeEditor && st && st != editor_st) {
        if(st.indexOf('\n') === -1) {
          st = this.formatStyle(st);
        }
        this.codeEditor.setValue(st);
        this.codeEditor.refresh();
      }

    // If model is using history, check buttons
    if (this.model.get('tile_style_history'))
      this._checkDoButtons();
  },

  /**
   * gets an array of parse errors from windshaft
   * and returns an array of {line:1, error: 'string'] with user friendly
   * strings. Parses errors in format:
   *
   *  'style.mss:7:2 Invalid code: asdasdasda'
   */
  _parseError: function(errors) {
    var parsedErrors = [];
    for(var i in errors) {
      var err = errors[i];
      if(err && err.length > 0) {
        var g = err.match(/.*:(\d+):(\d+)\s*(.*)/);
        if(g) {
          parsedErrors.push({
            line: parseInt(g[1], 10),
            message: g[3]
          });
        } else {
          parsedErrors.push({
            line: null,
            message: err
          })
        }
      }
    }
    // sort by line
    parsedErrors.sort(function(a, b) { return a.line - b.line; });
    parsedErrors = _.uniq(parsedErrors, true, function(a) { return a.line + a.message; });
    return parsedErrors;
  },

  _showErrorFromServer: function(err) {
    this._showError(this._parseError(err));
  },

  _showError: function(err) {
    var parsedErrors = err;
    if(parsedErrors.length > 0) {
      var errors = _(parsedErrors).map(function(e) {
        if(e.line) {
          return "line " + e.line + ": " + e.message;
        }
        return e.error || e.message;
      })
      this._renderError(errors.join('
'));
    }
  },

  _renderError: function(err) {
    this.trigger('hasErrors');

    // Show the error, positionate it
    this.$('.info')
      .html("
" + err + "
")
      .addClass('error')
      .show();

    this._adjustCodeEditorSize();

    // Adjust Codemirror editor
    //var info_height = this.$('.info').outerHeight();
    //this.$el.find(".CodeMirror-wrap")
      //.css({ bottom: info_height + 88 [> it has some bottom due to the action bottoms <]})
  },

  _adjustCodeEditorSize: function() {
    var info_height = this.$('.info').outerHeight();
    var panel_height = this.$el.height();
    this.$el.find(".CodeMirror-wrap")
      .css({ height: panel_height - info_height });
  },

  _checkLocalErrors: function() {
    var style = this.model.get('tile_style');
    var cartoParser = new cdb.admin.CartoParser(style);
    if(cartoParser.errors().length) {
      this._showError(cartoParser.errors())
    } else {
      // check variables used
      var err = this.checkVariables(cartoParser.variablesUsed());
      if(err.length) {
        this._showError(err)
        return;
      }
    }
    if(style && style.indexOf(this.model.get('table_name')) == -1) {
      this._renderError("style name should be #" + this.model.get('table_name') + " otherwise all tiles will be blank");
    } else {
      this._clearErrors();
    }
  },

  _clearErrors: function() {
    this.trigger('clearError');

    // Hide info
    this.$('.info')
      .html('')
      .removeClass('error')
      .hide();

    this._adjustCodeEditorSize();
    // Adjust again the editor
    //this.$el.find(".CodeMirror-wrap")
      //.css({ bottom: 88 })
  },

  _do: function(e) {
    e.preventDefault();
    var newCarto = this.model.redoHistory('tile_style');
    if(this.codeEditor) this.codeEditor.setValue(newCarto);
    this._checkDoButtons();
    return false;
  },

  _undo: function(e) {
    e.preventDefault();
    var newCarto = this.model.undoHistory('tile_style');
    if(this.codeEditor) this.codeEditor.setValue(newCarto);
    this._checkDoButtons();
    return false;
  },

  /**
   * checks variabels used in cartocss are in the schem
   */
  checkVariables: function(vars) {
    var columns = this.model.table.columnNames();
    var err = [];
    for(var i in vars) {
      if(!_.contains(columns, vars[i])) {
        err.push({
          error: "sql/table must contain " + vars[i] + " variable"
        });
      }
    }
    return err;
  },

  applyStyle: function() {
    this._clearErrors();
    var style = this.codeEditor.getValue();
    var cartoParser = new cdb.admin.CartoParser(style);
    if(cartoParser.errors().length) {
      this._showError(cartoParser.errors())
    } else {
      // check variables used
      var err = this.checkVariables(cartoParser.variablesUsed());
      if(err.length) {
        this._showError(err)
        return;
      }
      this.model.addToHistory('tile_style', style);
      //TODO: check if the style has been changed
      this.model.save({
        tile_style: style,
        tile_style_custom: true
      });

      // we save the new applied query on the history array
      this.trigger('applyStyle', style);
    }
  },

  /**
   * Check if the editor is different from the saved value
   * @return {Boolean}
   */
  hasChanges: function() {
    return this.model.get('tile_style') != this.codeEditor.getValue();
  },

  _checkDoButtons: function() {
      // Redo
      if (!this.model.isHistoryAtLastPosition('tile_style')) {
        this.$el.find('a.next').removeClass("disabled")
      } else {
        this.$el.find('a.next').addClass("disabled")
      }
      // Undo
      if (!this.model.isHistoryAtFirstPosition('tile_style')) {
        this.$el.find('a.back').removeClass("disabled")
      } else {
        this.$el.find('a.back').addClass("disabled")
      }
  },

  _showDoc: function(ev) {
    ev.preventDefault();
    var dialog = new cdb.admin.CartoCSSInfo();
    $("body").append(dialog.render().el);
    dialog.open();
  }
});
<commit_msg>fixed function when adjust carto editor<commit_after>
cdb.admin.mod = cdb.admin.mod || {};
cdb.admin.mod.CartoCSSEditor = cdb.core.View.extend({

  buttonClass: 'cartocss_mod',
  type: 'tool',

  events: {
    'click .actions button':  'applyStyle',
    'click .actions a.next':  '_do',
    'click .actions a.back':  '_undo',
    'click .doc_info':    '_showDoc'
  },

  initialize: function() {
    this.template = this.getTemplate('table/menu_modules/views/carto_editor');

    this.model.bind('change',      this._updateStyle, this);
    this.add_related_model(this.model);
    this.add_related_model(this.model.table);

    // Set query position from history array and last sql applied
    var history   = this.model.get('tile_style_history')
      , position  = this.model.tile_style_history_position
      , style     = this.model.get('tile_style');

    // Model doesn't persist last change, let's add in the history
    if (style && style != "" && history && _.indexOf(history, style) === -1) {
      history.push(style);
      this.model.set({ "tile_style_history": history }, { silent:true });
    }

    // Get history position
    this.model.tile_style_history_position =
      _.indexOf(history, style) !== -1
      ? _.indexOf(history, style)
      : 0;

    this.model.bind('parseError', this._showErrorFromServer, this);
    /*
    this.model.bind('tileError', this._renderError, this);
    */
    this.model.bind('tileOk', this._checkLocalErrors, this);
    this.model.table.bind('change:schema', this._checkLocalErrors, this);
  },

  activated: function() {
    if(this.codeEditor) {
      this.codeEditor.refresh();
      this.codeEditor.focus();
    }
  },

  render: function() {
    this.$el.append(this.template({}));
    this.codeEditor = CodeMirror.fromTextArea(this.$('textarea')[0], {
      mode: "text/x-carto",
      tabMode: "indent",
      matchBrackets: true,
      lineNumbers: true,
      lineWrapping: true,
      extraKeys: {
       "Ctrl-Space": function(cm) {
         CodeMirror.showHint(cm, CodeMirror.hint['custom-list'], {
           word: /[\w\-$]+/,
           range: 50
         });
       }
      }
    });
    this._updateStyle();

    // Add tooltip for undo/redo buttons
    this.$el.find("a.next,a.back").tipsy({
      gravity: "s",
      fade: true
    });

    //TODO: move this to common/hotkeys.js
    // Codemirror extrakey
    // Add save keymap
    // PC & LINUX -> Ctrl + s
    // MAC        -> Cmd + s
    var ua      = navigator.userAgent.toLowerCase()
      , so      = "rest"
      , keymap  = "ctrl+s"
      , self    = this;

    if (/mac os/.test(ua)) {
      keymap = "meta+s";
      so = "mac";
    }

    this.$el.bind('keydown', keymap, function(ev) {
      if (((so === "mac" && ev.metaKey) || (so === "rest" && ev.ctrlKey)) && ev.keyCode == 83 ) {
        ev.preventDefault();
        self.applyStyle();
      }
    });

    return this;
  },

  /** hack used to format the old styles transformed to cartodb 2.0*/
  formatStyle: function(s) {
    try {
      if (s && s.length) {
        s = s.replace(/{/g,'{\n')
            .replace(/}/g,'}\n')
            .replace(/;/g,';\n')
        var t = s.split('\n');
        var lines = [];
        var c = 0;
        for(var i = 0; i < t.length; ++i) {
          lines.push(c);
          if (t[i].indexOf('{')  != -1) {
            ++c;
          }
          if (t[i].indexOf('}')  != -1) {
            --c;
          }
        }
        var r = [];
        for(var i = 0; i < t.length; ++i) {
          var spaces = '';
          if(t[i].indexOf('}') >= 0) lines[i]-=1;
          for(var j = 0; j < lines[i]; ++j) {
            spaces = spaces + '  ';
          }
          r.push(spaces + t[i]);
        }
        return '/** this cartoCSS has been processed in order to be compatible with the new cartodb 2.0 */\n\n' + r.join('\n');
      }
    } catch(e) {
    }
    return s;
  },

  _updateStyle: function(){
    var st        = this.model.get('tile_style')
      , editor_st = this.codeEditor.getValue();

      if(this.codeEditor && st && st != editor_st) {
        if(st.indexOf('\n') === -1) {
          st = this.formatStyle(st);
        }
        this.codeEditor.setValue(st);
        this.codeEditor.refresh();
      }

    // If model is using history, check buttons
    if (this.model.get('tile_style_history'))
      this._checkDoButtons();
  },

  /**
   * gets an array of parse errors from windshaft
   * and returns an array of {line:1, error: 'string'] with user friendly
   * strings. Parses errors in format:
   *
   *  'style.mss:7:2 Invalid code: asdasdasda'
   */
  _parseError: function(errors) {
    var parsedErrors = [];
    for(var i in errors) {
      var err = errors[i];
      if(err && err.length > 0) {
        var g = err.match(/.*:(\d+):(\d+)\s*(.*)/);
        if(g) {
          parsedErrors.push({
            line: parseInt(g[1], 10),
            message: g[3]
          });
        } else {
          parsedErrors.push({
            line: null,
            message: err
          })
        }
      }
    }
    // sort by line
    parsedErrors.sort(function(a, b) { return a.line - b.line; });
    parsedErrors = _.uniq(parsedErrors, true, function(a) { return a.line + a.message; });
    return parsedErrors;
  },

  _showErrorFromServer: function(err) {
    this._showError(this._parseError(err));
  },

  _showError: function(err) {
    var parsedErrors = err;
    if(parsedErrors.length > 0) {
      var errors = _(parsedErrors).map(function(e) {
        if(e.line) {
          return "line " + e.line + ": " + e.message;
        }
        return e.error || e.message;
      })
      this._renderError(errors.join('
'));
    }
  },

  _renderError: function(err) {
    this.trigger('hasErrors');

    // Show the error, positionate it
    this.$('.info')
      .html("
" + err + "
")
      .addClass('error')
      .show();

    this._adjustCodeEditorSize();
  },

  _adjustCodeEditorSize: function() {
    var h = this.$('.info').is(':visible') ? this.$('.info').outerHeight() : 0;
    this.$('.CodeMirror-wrap').css({bottom: h + 80 /* the space we need to show the action buttons */});
  },

  _checkLocalErrors: function() {
    var style = this.model.get('tile_style');
    var cartoParser = new cdb.admin.CartoParser(style);
    if(cartoParser.errors().length) {
      this._showError(cartoParser.errors())
    } else {
      // check variables used
      var err = this.checkVariables(cartoParser.variablesUsed());
      if(err.length) {
        this._showError(err)
        return;
      }
    }
    if(style && style.indexOf(this.model.get('table_name')) == -1) {
      this._renderError("style name should be #" + this.model.get('table_name') + " otherwise all tiles will be blank");
    } else {
      this._clearErrors();
    }
  },

  _clearErrors: function() {
    this.trigger('clearError');

    // Hide info
    this.$('.info')
      .html('')
      .removeClass('error')
      .hide();

    this._adjustCodeEditorSize();
  },

  _do: function(e) {
    e.preventDefault();
    var newCarto = this.model.redoHistory('tile_style');
    if(this.codeEditor) this.codeEditor.setValue(newCarto);
    this._checkDoButtons();
    return false;
  },

  _undo: function(e) {
    e.preventDefault();
    var newCarto = this.model.undoHistory('tile_style');
    if(this.codeEditor) this.codeEditor.setValue(newCarto);
    this._checkDoButtons();
    return false;
  },

  /**
   * checks variabels used in cartocss are in the schem
   */
  checkVariables: function(vars) {
    var columns = this.model.table.columnNames();
    var err = [];
    for(var i in vars) {
      if(!_.contains(columns, vars[i])) {
        err.push({
          error: "sql/table must contain " + vars[i] + " variable"
        });
      }
    }
    return err;
  },

  applyStyle: function() {
    this._clearErrors();
    var style = this.codeEditor.getValue();
    var cartoParser = new cdb.admin.CartoParser(style);
    if(cartoParser.errors().length) {
      this._showError(cartoParser.errors())
    } else {
      // check variables used
      var err = this.checkVariables(cartoParser.variablesUsed());
      if(err.length) {
        this._showError(err)
        return;
      }
      this.model.addToHistory('tile_style', style);
      //TODO: check if the style has been changed
      this.model.save({
        tile_style: style,
        tile_style_custom: true
      });

      // we save the new applied query on the history array
      this.trigger('applyStyle', style);
    }
  },

  /**
   * Check if the editor is different from the saved value
   * @return {Boolean}
   */
  hasChanges: function() {
    return this.model.get('tile_style') != this.codeEditor.getValue();
  },

  _checkDoButtons: function() {
      // Redo
      if (!this.model.isHistoryAtLastPosition('tile_style')) {
        this.$el.find('a.next').removeClass("disabled")
      } else {
        this.$el.find('a.next').addClass("disabled")
      }
      // Undo
      if (!this.model.isHistoryAtFirstPosition('tile_style')) {
        this.$el.find('a.back').removeClass("disabled")
      } else {
        this.$el.find('a.back').addClass("disabled")
      }
  },

  _showDoc: function(ev) {
    ev.preventDefault();
    var dialog = new cdb.admin.CartoCSSInfo();
    $("body").append(dialog.render().el);
    dialog.open();
  }
});
<|endoftext|>