<commit_before>'use strict';

let log = require('intel'),
  Cookie = require('tough-cookie').Cookie,
  urlParser = require('url'),
  util = require('util'),
  moment = require('moment');

module.exports = {
  eventFromLogEntry: function(logEntry) {
    const logContent = JSON.parse(logEntry.message);

    return {
      timestamp: logEntry.timestamp,
      method: logContent.message.method,
      data: logContent.message.params,
      webview: logContent.webview
    }
  },
  harFromEvents: function(events) {
    let pages = [],
      entries = [],
      currentPageId,
      ongoingDataRequests = new Set();

    for (let event of events) {
      const data = event.data;
      switch (event.method) {
        case 'Page.frameStartedLoading':
        {
          currentPageId = 'page_' + (pages.length + 1);
          let page = {
            id: currentPageId,
            startedDateTime: moment(event.timestamp).toISOString(),
            title: '',
            pageTimings: {}
          };
          pages.push(page);
        }
          break;

        case 'Network.requestWillBeSent':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          const request = data.request;
          if (request.url.startsWith('data:')) {
            ongoingDataRequests.add(data.requestId);
            continue;
          }

          const cookieHeader = getHeaderValue(request.headers, 'Cookie');
          const cookies = parseCookies(cookieHeader.split(';'));

          let req = {
            method: request.method,
            url: request.url,
            _timestamp: data.timestamp,
            queryString: parsePostParams(request.url),
            headersSize: -1,
            bodySize: -1,
            cookies: cookies,
            headers: parseHeaders(request.headers)
          };

          let entry = {
            cache: {},
            startedDateTime: moment.unix(data.wallTime).toISOString(), //epoch float64, eg 1440589909.59248
            _wallTime: data.wallTime,
            _requestId: data.requestId,
            pageref: currentPageId,
            request: req,
            time: 0
          };

          if (data.redirectResponse) {
            let previousEntry = entries.find((entry) => {
              return entry._requestId === data.requestId;
            });
            previousEntry._requestId = previousEntry._requestId + 'r';
            populateEntryFromResponse(previousEntry, data.redirectResponse, data.timestamp);
            previousEntry.response.redirectURL = request.url;
          }

          entries.push(entry);

          let page = pages[pages.length - 1];
          // this is the first request for this page, so set timestamp of page.
          if (!page._timestamp) {
            page._wallTime = data.wallTime;
            page._timestamp = data.timestamp;
            page.startedDateTime = entry.startedDateTime;
          }
        }
          break;

        case 'Network.responseReceived':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (data.response.url.startsWith('data:')) {
            continue;
          }

          let entry = entries.find((entry) => {
            return entry._requestId === data.requestId;
          });
          if (!entry) {
            log.warn('Recieved network response for requestId ' + data.requestId + ' with no matching request.');
            continue;
          }

          let page = pages.find((page) => {
            return page.id === currentPageId;
          });
          if (!page.title || page.title === '') {
            // URL is better than blank, and it's what devtools uses.
            page.title = data.response.url;
          }

          try {
            populateEntryFromResponse(entry, data.response, data.timestamp);
          } catch (e) {
            log.error('Error parsing response: ' + JSON.stringify(data, null, 2));
            throw e;
          }
        }
          break;

        case 'Network.dataReceived':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (ongoingDataRequests.has(data.requestId)) {
            continue;
          }

          let entry = entries.find((entry) => {
            return entry._requestId === data.requestId;
          });
          if (!entry) {
            log.warn('Recieved network data for requestId ' + data.requestId + ' with no matching request.');
            continue;
          }

          entry.response.content.size += data.dataLength;
        }
          break;

        case 'Network.loadingFinished':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (ongoingDataRequests.has(data.requestId)) {
            ongoingDataRequests.delete(data.requestId);
            continue;
          }

          let entry = entries.find((entry) => {
            return entry._requestId === data.requestId;
          });
          if (!entry) {
            log.warn('Network loading finished for requestId ' + data.requestId + ' with no matching request.');
            continue;
          }

          entry.response.bodySize = entry.response.content.size;
          //if (entry.response.headersSize > -1) {
          //  entry.response.bodySize -= entry.response.headersSize;
          //}

          // encodedDataLength will be -1 sometimes
          if (data.encodedDataLength > 0) {
            // encodedDataLength seems to be larger than body size sometimes. Perhaps it's because full packets are
            // listed even though the actual data might be very small.
            // I've seen dataLength: 416, encodedDataLength: 1016,

            const compression = Math.max(0, entry.response.bodySize - data.encodedDataLength);
            if (compression > 0) {
              entry.response.content.compression = compression;
            }
          }
          entry.time = (data.timestamp - entry.request._timestamp) * 1000;
          //entry.timings.receive = (entry.timings.receive + data.timestamp*1000);
        }
          break;

        case 'Page.loadEventFired':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue;
          }

          let page = pages[pages.length - 1];
          page.pageTimings.onLoad = Number((data.timestamp - page._timestamp) * 1000)
        }
          break;

        case 'Page.domContentEventFired':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue;
          }

          let page = pages[pages.length - 1];
          page.pageTimings.onContentLoad = Number((data.timestamp - page._timestamp) * 1000)
        }
          break;

        case 'Page.frameAttached':
          // FIXME should check for attachments to the main frame (e.g. fonts on izettle.com)
          // and not create a new page for those.
          break;

        case 'Page.frameScheduledNavigation':
        case 'Page.frameNavigated':
        case 'Page.frameStoppedLoading':
        case 'Page.frameClearedScheduledNavigation':
        case 'Page.frameDetached':
          // ignore
          break;

        case 'Network.requestServedFromCache':
        case 'Network.loadingFailed':
          // ignore
          break;

        default:
          log.warn('Unhandled event: ' + event.method);
          break;
      }
    }

    entries = entries.filter((entry) => {
      // Page doesn't wait for favicon to load, and that's ok (for now).
      if (!entry.response && !entry.request.url.endsWith('.ico')) {
        log.warn('Entry without response!: ' + JSON.stringify(entry));
      }
      return entry.response;
    });

    return {
      log: {
        version: "1.2",
        creator: {"name": "Browsertime", "version": "1.0"},
        pages,
        entries
      }
    }
  }
};

function calculateRequestHeaderSize(harRequest) {
  let buffer = '';
  buffer = buffer.concat(util.format('%s %s %s\r\n', harRequest.method, harRequest.url, harRequest.httpVersion));

  const headerLines = harRequest.headers.map((header) => util.format('%s: %s\r\n', header.name, header.value));
  buffer = buffer.concat(headerLines.join(''));
  buffer = buffer.concat('\r\n');

  return buffer.length;
}

function calculateResponseHeaderSize(perflogResponse) {
  let buffer = '';
  buffer = buffer.concat(util.format('%s %d %s\r\n', perflogResponse.protocol, perflogResponse.status, perflogResponse.statusText));
  Object.keys(perflogResponse.headers).forEach((key) => {
    buffer = buffer.concat(util.format('%s: %s\r\n', key, perflogResponse.headers[key]));
  });
  buffer = buffer.concat('\r\n');

  return buffer.length;
}

function populateEntryFromResponse(entry, response, timestamp) {
  entry.response = {
    httpVersion: response.protocol,
    redirectURL: '',
    status: response.status,
    statusText: response.statusText,
    content: {
      mimeType: response.mimeType,
      size: 0
    },
    headersSize: -1,
    bodySize: -1,
    cookies: [],
    headers: parseHeaders(response.headers)
  };

  entry.request.httpVersion = response.protocol;

  if (response.fromDiskCache === true && isHttp1x(response.protocol)) {
    entry.response.headersSize = calculateResponseHeaderSize(response);
  } else {
    if (response.requestHeaders) {
      entry.request.headers = parseHeaders(response.requestHeaders);

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

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

      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);
      }
    }
  }

  let blocked = response.timing["dnsStart"];
  if (blocked < 0.0) {
    blocked = 0.0
  }
  let dns = response.timing["dnsEnd"] - response.timing["dnsStart"];
  if (dns < 0.0) {
    dns = 0.0
  }
  let connect = response.timing["connectEnd"] - response.timing["connectStart"];
  if (connect < 0.0) {
    connect = 0.0
  }
  let send = response.timing["sendEnd"] - response.timing["sendStart"];
  if (send < 0.0) {
    send = 0.0
  }
  let wait = response.timing["receiveHeadersEnd"] - response.timing["sendEnd"];
  if (wait < 0.0) {
    wait = 0.0
  }
  let ssl = response.timing["sslEnd"] - response.timing["sslStart"];
  if (ssl < 0.0) {
    ssl = 0.0
  }

  entry.timings = {
    blocked,
    dns,
    connect,
    send,
    wait,
    receive: timestamp - response.timing["requestTime"],
    ssl
  };

  entry._requestTime = response.timing["requestTime"];

  entry.time = (timestamp - response.timing.requestTime) * 1000;

}

function parseCookies(cookieStrings) {
  return cookieStrings.filter(Boolean).map((cookieString) => {
    let cookie = Cookie.parse(cookieString);
    return {
      'name': cookie.key,
      'value': cookie.value,
      'path': cookie.path || undefined, // must be undefined, not null, to exclude empty path
      'domain': cookie.domain || undefined,  // must be undefined, not null, to exclude empty domain
      'expires': cookie.expires === 'Infinity' ? undefined : moment(cookie.expires).toISOString(),
      'httpOnly': cookie.httpOnly,
      'secure': cookie.secure
    };
  });
}

function parseHeaders(headers) {
  if (!headers) {
    return [];
  }
  return Object.keys(headers).map((key) => {
    return {
      name: key,
      value: headers[key]
    }
  });
}

function getHeaderValue(headers, header) {
  if (!headers) {
    return '';
  }
  // http header names are case insensitive
  const lowerCaseHeader = header.toLowerCase();
  const headerNames = Object.keys(headers);
  return headerNames.filter((key) => key.toLowerCase() === lowerCaseHeader)
      .map((key) => headers[key]).shift() || '';
}

function parsePostParams(text) {
  var paramList = [];
  var queryString = urlParser.parse(text, true).query;
  Object.keys(queryString).forEach(function(name) {
    var value = queryString[name];
    if (!Array.isArray(value))
      value = [value];
    value.forEach(function(v) {
      paramList.push(
        {
          "name": name,
          "value": v
          //                    "fileName": "example.pdf",
          //                    "contentType": "application/pdf",
        });
    });
  });
  return paramList;
}

function isHttp1x(version) {
  return version.toLowerCase().startsWith('http/1.')
}<commit_msg>lets pickup the initialPriority<commit_after>'use strict';

let log = require('intel'),
  Cookie = require('tough-cookie').Cookie,
  urlParser = require('url'),
  util = require('util'),
  moment = require('moment');

module.exports = {
  eventFromLogEntry: function(logEntry) {
    const logContent = JSON.parse(logEntry.message);

    return {
      timestamp: logEntry.timestamp,
      method: logContent.message.method,
      data: logContent.message.params,
      webview: logContent.webview
    }
  },
  harFromEvents: function(events) {
    let pages = [],
      entries = [],
      currentPageId,
      ongoingDataRequests = new Set();

    for (let event of events) {
      const data = event.data;
      switch (event.method) {
        case 'Page.frameStartedLoading':
        {
          currentPageId = 'page_' + (pages.length + 1);
          let page = {
            id: currentPageId,
            startedDateTime: moment(event.timestamp).toISOString(),
            title: '',
            pageTimings: {}
          };
          pages.push(page);
        }
          break;

        case 'Network.requestWillBeSent':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          const request = data.request;
          if (request.url.startsWith('data:')) {
            ongoingDataRequests.add(data.requestId);
            continue;
          }

          const cookieHeader = getHeaderValue(request.headers, 'Cookie');
          const cookies = parseCookies(cookieHeader.split(';'));

          let req = {
            method: request.method,
            url: request.url,
            _timestamp: data.timestamp,
            queryString: parsePostParams(request.url),
            headersSize: -1,
            bodySize: -1,
            _initialPriority: request.initialPriority,
            cookies: cookies,
            headers: parseHeaders(request.headers)
          };

          let entry = {
            cache: {},
            startedDateTime: moment.unix(data.wallTime).toISOString(), //epoch float64, eg 1440589909.59248
            _wallTime: data.wallTime,
            _requestId: data.requestId,
            pageref: currentPageId,
            request: req,
            time: 0
          };

          if (data.redirectResponse) {
            let previousEntry = entries.find((entry) => {
              return entry._requestId === data.requestId;
            });
            previousEntry._requestId = previousEntry._requestId + 'r';
            populateEntryFromResponse(previousEntry, data.redirectResponse, data.timestamp);
            previousEntry.response.redirectURL = request.url;
          }

          entries.push(entry);

          let page = pages[pages.length - 1];
          // this is the first request for this page, so set timestamp of page.
          if (!page._timestamp) {
            page._wallTime = data.wallTime;
            page._timestamp = data.timestamp;
            page.startedDateTime = entry.startedDateTime;
          }
        }
          break;

        case 'Network.responseReceived':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (data.response.url.startsWith('data:')) {
            continue;
          }

          let entry = entries.find((entry) => {
            return entry._requestId === data.requestId;
          });
          if (!entry) {
            log.warn('Recieved network response for requestId ' + data.requestId + ' with no matching request.');
            continue;
          }

          let page = pages.find((page) => {
            return page.id === currentPageId;
          });
          if (!page.title || page.title === '') {
            // URL is better than blank, and it's what devtools uses.
            page.title = data.response.url;
          }

          try {
            populateEntryFromResponse(entry, data.response, data.timestamp);
          } catch (e) {
            log.error('Error parsing response: ' + JSON.stringify(data, null, 2));
            throw e;
          }
        }
          break;

        case 'Network.dataReceived':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (ongoingDataRequests.has(data.requestId)) {
            continue;
          }

          let entry = entries.find((entry) => {
            return entry._requestId === data.requestId;
          });
          if (!entry) {
            log.warn('Recieved network data for requestId ' + data.requestId + ' with no matching request.');
            continue;
          }

          entry.response.content.size += data.dataLength;
        }
          break;

        case 'Network.loadingFinished':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (ongoingDataRequests.has(data.requestId)) {
            ongoingDataRequests.delete(data.requestId);
            continue;
          }

          let entry = entries.find((entry) => {
            return entry._requestId === data.requestId;
          });
          if (!entry) {
            log.warn('Network loading finished for requestId ' + data.requestId + ' with no matching request.');
            continue;
          }

          entry.response.bodySize = entry.response.content.size;
          //if (entry.response.headersSize > -1) {
          //  entry.response.bodySize -= entry.response.headersSize;
          //}

          // encodedDataLength will be -1 sometimes
          if (data.encodedDataLength > 0) {
            // encodedDataLength seems to be larger than body size sometimes. Perhaps it's because full packets are
            // listed even though the actual data might be very small.
            // I've seen dataLength: 416, encodedDataLength: 1016,

            const compression = Math.max(0, entry.response.bodySize - data.encodedDataLength);
            if (compression > 0) {
              entry.response.content.compression = compression;
            }
          }
          entry.time = (data.timestamp - entry.request._timestamp) * 1000;
          //entry.timings.receive = (entry.timings.receive + data.timestamp*1000);
        }
          break;

        case 'Page.loadEventFired':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue;
          }

          let page = pages[pages.length - 1];
          page.pageTimings.onLoad = Number((data.timestamp - page._timestamp) * 1000)
        }
          break;

        case 'Page.domContentEventFired':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue;
          }

          let page = pages[pages.length - 1];
          page.pageTimings.onContentLoad = Number((data.timestamp - page._timestamp) * 1000)
        }
          break;

        case 'Page.frameAttached':
          // FIXME should check for attachments to the main frame (e.g. fonts on izettle.com)
          // and not create a new page for those.
          break;

        case 'Page.frameScheduledNavigation':
        case 'Page.frameNavigated':
        case 'Page.frameStoppedLoading':
        case 'Page.frameClearedScheduledNavigation':
        case 'Page.frameDetached':
          // ignore
          break;

        case 'Network.requestServedFromCache':
        case 'Network.loadingFailed':
          // ignore
          break;

        default:
          log.warn('Unhandled event: ' + event.method);
          break;
      }
    }

    entries = entries.filter((entry) => {
      // Page doesn't wait for favicon to load, and that's ok (for now).
      if (!entry.response && !entry.request.url.endsWith('.ico')) {
        log.warn('Entry without response!: ' + JSON.stringify(entry));
      }
      return entry.response;
    });

    return {
      log: {
        version: "1.2",
        creator: {"name": "Browsertime", "version": "1.0"},
        pages,
        entries
      }
    }
  }
};

function calculateRequestHeaderSize(harRequest) {
  let buffer = '';
  buffer = buffer.concat(util.format('%s %s %s\r\n', harRequest.method, harRequest.url, harRequest.httpVersion));

  const headerLines = harRequest.headers.map((header) => util.format('%s: %s\r\n', header.name, header.value));
  buffer = buffer.concat(headerLines.join(''));
  buffer = buffer.concat('\r\n');

  return buffer.length;
}

function calculateResponseHeaderSize(perflogResponse) {
  let buffer = '';
  buffer = buffer.concat(util.format('%s %d %s\r\n', perflogResponse.protocol, perflogResponse.status, perflogResponse.statusText));
  Object.keys(perflogResponse.headers).forEach((key) => {
    buffer = buffer.concat(util.format('%s: %s\r\n', key, perflogResponse.headers[key]));
  });
  buffer = buffer.concat('\r\n');

  return buffer.length;
}

function populateEntryFromResponse(entry, response, timestamp) {
  entry.response = {
    httpVersion: response.protocol,
    redirectURL: '',
    status: response.status,
    statusText: response.statusText,
    content: {
      mimeType: response.mimeType,
      size: 0
    },
    headersSize: -1,
    bodySize: -1,
    cookies: [],
    headers: parseHeaders(response.headers)
  };

  entry.request.httpVersion = response.protocol;

  if (response.fromDiskCache === true && isHttp1x(response.protocol)) {
    entry.response.headersSize = calculateResponseHeaderSize(response);
  } else {
    if (response.requestHeaders) {
      entry.request.headers = parseHeaders(response.requestHeaders);

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

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

      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);
      }
    }
  }

  let blocked = response.timing["dnsStart"];
  if (blocked < 0.0) {
    blocked = 0.0
  }
  let dns = response.timing["dnsEnd"] - response.timing["dnsStart"];
  if (dns < 0.0) {
    dns = 0.0
  }
  let connect = response.timing["connectEnd"] - response.timing["connectStart"];
  if (connect < 0.0) {
    connect = 0.0
  }
  let send = response.timing["sendEnd"] - response.timing["sendStart"];
  if (send < 0.0) {
    send = 0.0
  }
  let wait = response.timing["receiveHeadersEnd"] - response.timing["sendEnd"];
  if (wait < 0.0) {
    wait = 0.0
  }
  let ssl = response.timing["sslEnd"] - response.timing["sslStart"];
  if (ssl < 0.0) {
    ssl = 0.0
  }

  entry.timings = {
    blocked,
    dns,
    connect,
    send,
    wait,
    receive: timestamp - response.timing["requestTime"],
    ssl
  };

  entry._requestTime = response.timing["requestTime"];

  entry.time = (timestamp - response.timing.requestTime) * 1000;

}

function parseCookies(cookieStrings) {
  return cookieStrings.filter(Boolean).map((cookieString) => {
    let cookie = Cookie.parse(cookieString);
    return {
      'name': cookie.key,
      'value': cookie.value,
      'path': cookie.path || undefined, // must be undefined, not null, to exclude empty path
      'domain': cookie.domain || undefined,  // must be undefined, not null, to exclude empty domain
      'expires': cookie.expires === 'Infinity' ? undefined : moment(cookie.expires).toISOString(),
      'httpOnly': cookie.httpOnly,
      'secure': cookie.secure
    };
  });
}

function parseHeaders(headers) {
  if (!headers) {
    return [];
  }
  return Object.keys(headers).map((key) => {
    return {
      name: key,
      value: headers[key]
    }
  });
}

function getHeaderValue(headers, header) {
  if (!headers) {
    return '';
  }
  // http header names are case insensitive
  const lowerCaseHeader = header.toLowerCase();
  const headerNames = Object.keys(headers);
  return headerNames.filter((key) => key.toLowerCase() === lowerCaseHeader)
      .map((key) => headers[key]).shift() || '';
}

function parsePostParams(text) {
  var paramList = [];
  var queryString = urlParser.parse(text, true).query;
  Object.keys(queryString).forEach(function(name) {
    var value = queryString[name];
    if (!Array.isArray(value))
      value = [value];
    value.forEach(function(v) {
      paramList.push(
        {
          "name": name,
          "value": v
          //                    "fileName": "example.pdf",
          //                    "contentType": "application/pdf",
        });
    });
  });
  return paramList;
}

function isHttp1x(version) {
  return version.toLowerCase().startsWith('http/1.')
}
<|endoftext|>


<commit_before>// Dependencies
var request = require('request');

// Define
module.exports = function(jar,group,callbacks) {
  request.get('http://www.roblox.com/My/Groups.aspx?gid=' + group,{jar: jar},function(err,res,body) {
    if (callbacks.always)
      callbacks.always();
    if (err) {
      if (callbacks.failure)
        callbacks.failure(err);
      return console.error(err);
    }
    if (res.responseCode == 200) {
      if (callbacks.success)
        callbacks.success(parseInt(body.match(/InitializeGlobalVars\((.*), /)[1]));
    } else {
      var msg = 'Exile failed, response code ' + res.responseCode;
      if (callbacks.failure)
        callbacks.failure(msg);
      return console.error(msg);
    }
  });
};
<commit_msg>Fix<commit_after>// Dependencies
var request = require('request');

// Define
module.exports = function(jar,group,callbacks) {
  request.get('http://www.roblox.com/My/Groups.aspx?gid=' + group,{jar: jar},function(err,res,body) {
    if (callbacks.always)
      callbacks.always();
    if (err) {
      if (callbacks.failure)
        callbacks.failure(err);
      return console.error(err);
    }
    if (res.statusCode == 200) {
      if (callbacks.success)
        callbacks.success(parseInt(body.match(/InitializeGlobalVars\((.*), /)[1],10));
    } else {
      var msg = 'Exile failed, response code ' + res.responseCode;
      if (callbacks.failure)
        callbacks.failure(msg);
      return console.error(msg);
    }
  });
};
<|endoftext|>


<commit_before>"use strict";

//TODO:
// Remove Dict and use native Map as much as possible here
//Use ObjectChangeDescriptor to avoid creating useless arrays and benefit from similar gains made in property-changes

var WeakMap = require("weak-map"),
    Map = require("../_map"),
    ChangeDescriptor = require("./change-descriptor"),
    ObjectChangeDescriptor = ChangeDescriptor.ObjectChangeDescriptor,
    ChangeListenersRecord = ChangeDescriptor.ChangeListenersRecord,
    ListenerGhost = ChangeDescriptor.ListenerGhost;

var rangeChangeDescriptors = new WeakMap(); // {isActive, willChangeListeners, changeListeners}

//
function RangeChangeDescriptor(name) {
    this.name = name;
    this.isActive = false;
    this._willChangeListeners = null;
    this._changeListeners = null;
};

RangeChangeDescriptor.prototype = new ObjectChangeDescriptor();
RangeChangeDescriptor.prototype.constructor = RangeChangeDescriptor;

RangeChangeDescriptor.prototype.changeListenersRecordConstructor = RangeChangeListenersRecord;
RangeChangeDescriptor.prototype.willChangeListenersRecordConstructor = RangeWillChangeListenersRecord;
Object.defineProperty(RangeChangeDescriptor.prototype,"active",{
    get: function() {
        return this._active || (this._active = this._current ? this._current.slice():[]);
    }
});

var RangeChangeListenersSpecificHandlerMethodName = new Map();

function RangeChangeListenersRecord(name) {
    var specificHandlerMethodName = RangeChangeListenersSpecificHandlerMethodName.get(name);
    if(!specificHandlerMethodName) {
        specificHandlerMethodName = "handle";
        specificHandlerMethodName += name.slice(0, 1).toUpperCase();
        specificHandlerMethodName += name.slice(1);
        specificHandlerMethodName += "RangeChange";
        RangeChangeListenersSpecificHandlerMethodName.set(name,specificHandlerMethodName);
    }
    this.specificHandlerMethodName = specificHandlerMethodName;
	return this;
}
RangeChangeListenersRecord.prototype = new ChangeListenersRecord();
RangeChangeListenersRecord.prototype.constructor = RangeChangeListenersRecord;

var RangeWillChangeListenersSpecificHandlerMethodName = new Map();

function RangeWillChangeListenersRecord(name) {
    var specificHandlerMethodName = RangeWillChangeListenersSpecificHandlerMethodName.get(name);
    if(!specificHandlerMethodName) {
        specificHandlerMethodName = "handle";
        specificHandlerMethodName += name.slice(0, 1).toUpperCase();
        specificHandlerMethodName += name.slice(1);
        specificHandlerMethodName += "RangeWillChange";
        RangeWillChangeListenersSpecificHandlerMethodName.set(name,specificHandlerMethodName);
    }
    this.specificHandlerMethodName = specificHandlerMethodName;
    return this;
}
RangeWillChangeListenersRecord.prototype = new ChangeListenersRecord();
RangeWillChangeListenersRecord.prototype.constructor = RangeWillChangeListenersRecord;

module.exports = RangeChanges;
function RangeChanges() {
    throw new Error("Can't construct. RangeChanges is a mixin.");
}

RangeChanges.prototype.getAllRangeChangeDescriptors = function () {
    if (!rangeChangeDescriptors.has(this)) {
        rangeChangeDescriptors.set(this, new Map());
    }
    return rangeChangeDescriptors.get(this);
};

RangeChanges.prototype.getRangeChangeDescriptor = function (token) {
    var tokenChangeDescriptors = this.getAllRangeChangeDescriptors();
    token = token || "";
    if (!tokenChangeDescriptors.has(token)) {
        tokenChangeDescriptors.set(token, new RangeChangeDescriptor(token));
    }
    return tokenChangeDescriptors.get(token);
};

var ObjectsDispatchesRangeChanges = new WeakMap(),
    dispatchesRangeChangesGetter = function() {
        return ObjectsDispatchesRangeChanges.get(this);
    },
    dispatchesRangeChangesSetter = function(value) {
        return ObjectsDispatchesRangeChanges.set(this,value);
    },
    dispatchesChangesMethodName = "dispatchesRangeChanges",
    dispatchesChangesPropertyDescriptor = {
        get: dispatchesRangeChangesGetter,
        set: dispatchesRangeChangesSetter,
        configurable: true,
        enumerable: false
    };

RangeChanges.prototype.addRangeChangeListener = function addRangeChangeListener(listener, token, beforeChange) {
    // a concession for objects like Array that are not inherently observable
    if (!this.isObservable && this.makeObservable) {
        this.makeObservable();
    }

    var descriptor = this.getRangeChangeDescriptor(token);

    var listeners;
    if (beforeChange) {
        listeners = descriptor.willChangeListeners;
    } else {
        listeners = descriptor.changeListeners;
    }

    // even if already registered
    if(!listeners._current) {
        listeners._current = listener;
    }
    else if(!Array.isArray(listeners._current)) {
        listeners._current = [listeners._current,listener]
    }
    else {
        listeners._current.push(listener);
    }

    if(Object.getOwnPropertyDescriptor((this.__proto__||Object.getPrototypeOf(this)),dispatchesChangesMethodName) === void 0) {
        Object.defineProperty((this.__proto__||Object.getPrototypeOf(this)), dispatchesChangesMethodName, dispatchesChangesPropertyDescriptor);
    }
    this.dispatchesRangeChanges = true;

    var self = this;
    return function cancelRangeChangeListener() {
        if (!self) {
            // TODO throw new Error("Range change listener " + JSON.stringify(token) + " has already been canceled");
            return;
        }
        self.removeRangeChangeListener(listener, token, beforeChange);
        self = null;
    };
};

RangeChanges.prototype.removeRangeChangeListener = function (listener, token, beforeChange) {
    var descriptor = this.getRangeChangeDescriptor(token);

    var listeners;
    if (beforeChange) {
        listeners = descriptor._willChangeListeners;
    } else {
        listeners = descriptor._changeListeners;
    }

    if(listeners._current) {
        if(listeners._current === listener) {
            listeners._current = null;
        }
        else {
            var index = listeners._current.lastIndexOf(listener);
            if (index === -1) {
                throw new Error("Can't remove range change listener: does not exist: token " + JSON.stringify(token));
            }
            else {
                if(descriptor.isActive) {
                    listeners.ghostCount = listeners.ghostCount+1
                    listeners._current[index]=ListenerGhost
                }
                else {
                    listeners._current.spliceOne(index);
                }
            }
        }
    }

};

RangeChanges.prototype.dispatchRangeChange = function (plus, minus, index, beforeChange) {
    var descriptors = this.getAllRangeChangeDescriptors(),
        descriptor,
        mapIter  = descriptors.values();

    descriptors.dispatchBeforeChange = beforeChange;

     while (descriptor = mapIter.next().value) {

        if (descriptor.isActive) {
            return;
        }

        // before or after
        var listeners = beforeChange ? descriptor._willChangeListeners : descriptor._changeListeners;
        if(listeners && listeners._current) {
            var tokenName = listeners.specificHandlerMethodName;
            if(Array.isArray(listeners._current)) {
                if(listeners._current.length) {
                    // notably, defaults to "handleRangeChange" or "handleRangeWillChange"
                    // if token is "" (the default)

                    descriptor.isActive = true;
                    // dispatch each listener
                    try {
                        var i,
                            countI,
                            listener,
                            //removeGostListenersIfNeeded returns listeners.current or a new filtered one when conditions are met
                            currentListeners = listeners.removeCurrentGostListenersIfNeeded(),
                            Ghost = ListenerGhost;
                        for(i=0, countI = currentListeners.length;i<countI;i++) {
                            if ((listener = currentListeners[i]) !== Ghost) {
                                if (listener[tokenName]) {
                                    listener[tokenName](plus, minus, index, this, beforeChange);
                                } else if (listener.call) {
                                    listener.call(this, plus, minus, index, this, beforeChange);
                                } else {
                                    throw new Error("Handler " + listener + " has no method " + tokenName + " and is not callable");
                                }
                            }
                        }
                    } finally {
                        descriptor.isActive = false;
                    }
                }
            }
            else {
                descriptor.isActive = true;
                // dispatch each listener
                try {
                    listener = listeners._current;
                    if (listener[tokenName]) {
                        listener[tokenName](plus, minus, index, this, beforeChange);
                    } else if (listener.call) {
                        listener.call(this, plus, minus, index, this, beforeChange);
                    } else {
                        throw new Error("Handler " + listener + " has no method " + tokenName + " and is not callable");
                    }
                } finally {
                    descriptor.isActive = false;
                }

            }
        }

    }
};

RangeChanges.prototype.addBeforeRangeChangeListener = function (listener, token) {
    return this.addRangeChangeListener(listener, token, true);
};

RangeChanges.prototype.removeBeforeRangeChangeListener = function (listener, token) {
    return this.removeRangeChangeListener(listener, token, true);
};

RangeChanges.prototype.dispatchBeforeRangeChange = function (plus, minus, index) {
    return this.dispatchRangeChange(plus, minus, index, true);
};
<commit_msg>Removes var creation in a loop and clean up var declarations<commit_after>"use strict";

//TODO:
// Remove Dict and use native Map as much as possible here
//Use ObjectChangeDescriptor to avoid creating useless arrays and benefit from similar gains made in property-changes

var WeakMap = require("weak-map"),
    Map = require("../_map"),
    ChangeDescriptor = require("./change-descriptor"),
    ObjectChangeDescriptor = ChangeDescriptor.ObjectChangeDescriptor,
    ChangeListenersRecord = ChangeDescriptor.ChangeListenersRecord,
    ListenerGhost = ChangeDescriptor.ListenerGhost;

var rangeChangeDescriptors = new WeakMap(); // {isActive, willChangeListeners, changeListeners}

//
function RangeChangeDescriptor(name) {
    this.name = name;
    this.isActive = false;
    this._willChangeListeners = null;
    this._changeListeners = null;
};

RangeChangeDescriptor.prototype = new ObjectChangeDescriptor();
RangeChangeDescriptor.prototype.constructor = RangeChangeDescriptor;

RangeChangeDescriptor.prototype.changeListenersRecordConstructor = RangeChangeListenersRecord;
RangeChangeDescriptor.prototype.willChangeListenersRecordConstructor = RangeWillChangeListenersRecord;
Object.defineProperty(RangeChangeDescriptor.prototype,"active",{
    get: function() {
        return this._active || (this._active = this._current ? this._current.slice():[]);
    }
});

var RangeChangeListenersSpecificHandlerMethodName = new Map();

function RangeChangeListenersRecord(name) {
    var specificHandlerMethodName = RangeChangeListenersSpecificHandlerMethodName.get(name);
    if(!specificHandlerMethodName) {
        specificHandlerMethodName = "handle";
        specificHandlerMethodName += name.slice(0, 1).toUpperCase();
        specificHandlerMethodName += name.slice(1);
        specificHandlerMethodName += "RangeChange";
        RangeChangeListenersSpecificHandlerMethodName.set(name,specificHandlerMethodName);
    }
    this.specificHandlerMethodName = specificHandlerMethodName;
	return this;
}
RangeChangeListenersRecord.prototype = new ChangeListenersRecord();
RangeChangeListenersRecord.prototype.constructor = RangeChangeListenersRecord;

var RangeWillChangeListenersSpecificHandlerMethodName = new Map();

function RangeWillChangeListenersRecord(name) {
    var specificHandlerMethodName = RangeWillChangeListenersSpecificHandlerMethodName.get(name);
    if(!specificHandlerMethodName) {
        specificHandlerMethodName = "handle";
        specificHandlerMethodName += name.slice(0, 1).toUpperCase();
        specificHandlerMethodName += name.slice(1);
        specificHandlerMethodName += "RangeWillChange";
        RangeWillChangeListenersSpecificHandlerMethodName.set(name,specificHandlerMethodName);
    }
    this.specificHandlerMethodName = specificHandlerMethodName;
    return this;
}
RangeWillChangeListenersRecord.prototype = new ChangeListenersRecord();
RangeWillChangeListenersRecord.prototype.constructor = RangeWillChangeListenersRecord;

module.exports = RangeChanges;
function RangeChanges() {
    throw new Error("Can't construct. RangeChanges is a mixin.");
}

RangeChanges.prototype.getAllRangeChangeDescriptors = function () {
    if (!rangeChangeDescriptors.has(this)) {
        rangeChangeDescriptors.set(this, new Map());
    }
    return rangeChangeDescriptors.get(this);
};

RangeChanges.prototype.getRangeChangeDescriptor = function (token) {
    var tokenChangeDescriptors = this.getAllRangeChangeDescriptors();
    token = token || "";
    if (!tokenChangeDescriptors.has(token)) {
        tokenChangeDescriptors.set(token, new RangeChangeDescriptor(token));
    }
    return tokenChangeDescriptors.get(token);
};

var ObjectsDispatchesRangeChanges = new WeakMap(),
    dispatchesRangeChangesGetter = function() {
        return ObjectsDispatchesRangeChanges.get(this);
    },
    dispatchesRangeChangesSetter = function(value) {
        return ObjectsDispatchesRangeChanges.set(this,value);
    },
    dispatchesChangesMethodName = "dispatchesRangeChanges",
    dispatchesChangesPropertyDescriptor = {
        get: dispatchesRangeChangesGetter,
        set: dispatchesRangeChangesSetter,
        configurable: true,
        enumerable: false
    };

RangeChanges.prototype.addRangeChangeListener = function addRangeChangeListener(listener, token, beforeChange) {
    // a concession for objects like Array that are not inherently observable
    if (!this.isObservable && this.makeObservable) {
        this.makeObservable();
    }

    var descriptor = this.getRangeChangeDescriptor(token);

    var listeners;
    if (beforeChange) {
        listeners = descriptor.willChangeListeners;
    } else {
        listeners = descriptor.changeListeners;
    }

    // even if already registered
    if(!listeners._current) {
        listeners._current = listener;
    }
    else if(!Array.isArray(listeners._current)) {
        listeners._current = [listeners._current,listener]
    }
    else {
        listeners._current.push(listener);
    }

    if(Object.getOwnPropertyDescriptor((this.__proto__||Object.getPrototypeOf(this)),dispatchesChangesMethodName) === void 0) {
        Object.defineProperty((this.__proto__||Object.getPrototypeOf(this)), dispatchesChangesMethodName, dispatchesChangesPropertyDescriptor);
    }
    this.dispatchesRangeChanges = true;

    var self = this;
    return function cancelRangeChangeListener() {
        if (!self) {
            // TODO throw new Error("Range change listener " + JSON.stringify(token) + " has already been canceled");
            return;
        }
        self.removeRangeChangeListener(listener, token, beforeChange);
        self = null;
    };
};

RangeChanges.prototype.removeRangeChangeListener = function (listener, token, beforeChange) {
    var descriptor = this.getRangeChangeDescriptor(token);

    var listeners;
    if (beforeChange) {
        listeners = descriptor._willChangeListeners;
    } else {
        listeners = descriptor._changeListeners;
    }

    if(listeners._current) {
        if(listeners._current === listener) {
            listeners._current = null;
        }
        else {
            var index = listeners._current.lastIndexOf(listener);
            if (index === -1) {
                throw new Error("Can't remove range change listener: does not exist: token " + JSON.stringify(token));
            }
            else {
                if(descriptor.isActive) {
                    listeners.ghostCount = listeners.ghostCount+1
                    listeners._current[index]=ListenerGhost
                }
                else {
                    listeners._current.spliceOne(index);
                }
            }
        }
    }

};

RangeChanges.prototype.dispatchRangeChange = function (plus, minus, index, beforeChange) {
    var descriptors = this.getAllRangeChangeDescriptors(),
        descriptor,
        mapIter  = descriptors.values(),
        listeners,
        tokenName,
        i,
        countI,
        listener,
        currentListeners,
        Ghost;

    descriptors.dispatchBeforeChange = beforeChange;

     while (descriptor = mapIter.next().value) {

        if (descriptor.isActive) {
            return;
        }

        // before or after
        listeners = beforeChange ? descriptor._willChangeListeners : descriptor._changeListeners;
        if(listeners && listeners._current) {
            tokenName = listeners.specificHandlerMethodName;
            if(Array.isArray(listeners._current)) {
                if(listeners._current.length) {
                    // notably, defaults to "handleRangeChange" or "handleRangeWillChange"
                    // if token is "" (the default)

                    descriptor.isActive = true;
                    // dispatch each listener
                    try {
                            //removeGostListenersIfNeeded returns listeners.current or a new filtered one when conditions are met
                            currentListeners = listeners.removeCurrentGostListenersIfNeeded();
                            Ghost = ListenerGhost;
                        for(i=0, countI = currentListeners.length;i<countI;i++) {
                            if ((listener = currentListeners[i]) !== Ghost) {
                                if (listener[tokenName]) {
                                    listener[tokenName](plus, minus, index, this, beforeChange);
                                } else if (listener.call) {
                                    listener.call(this, plus, minus, index, this, beforeChange);
                                } else {
                                    throw new Error("Handler " + listener + " has no method " + tokenName + " and is not callable");
                                }
                            }
                        }
                    } finally {
                        descriptor.isActive = false;
                    }
                }
            }
            else {
                descriptor.isActive = true;
                // dispatch each listener
                try {
                    listener = listeners._current;
                    if (listener[tokenName]) {
                        listener[tokenName](plus, minus, index, this, beforeChange);
                    } else if (listener.call) {
                        listener.call(this, plus, minus, index, this, beforeChange);
                    } else {
                        throw new Error("Handler " + listener + " has no method " + tokenName + " and is not callable");
                    }
                } finally {
                    descriptor.isActive = false;
                }

            }
        }

    }
};

RangeChanges.prototype.addBeforeRangeChangeListener = function (listener, token) {
    return this.addRangeChangeListener(listener, token, true);
};

RangeChanges.prototype.removeBeforeRangeChangeListener = function (listener, token) {
    return this.removeRangeChangeListener(listener, token, true);
};

RangeChanges.prototype.dispatchBeforeRangeChange = function (plus, minus, index) {
    return this.dispatchRangeChange(plus, minus, index, true);
};
<|endoftext|>


<commit_before>/*
 * jQuery Touch Gestures
 * @version 1.1
 * @ported > QuoJS
 * @copyright QuoJS 2.2.0 (c) 2011, 2012 Javi Jiménez Villar (@soyjavi)
 * @see http://quojs.tapquo.com
 */
(function($) {
    var CURRENT_TOUCH, FIRST_TOUCH, GESTURE, GESTURES, HOLD_DELAY, TOUCH_TIMEOUT, _angle, _capturePinch, _captureRotation, _cleanGesture, _distance, _fingersPosition, _getTouches, _hold, _isSwipe, _listenTouches, _onTouchEnd, _onTouchMove, _onTouchStart, _parentIfText, _swipeDirection, _trigger, _current;

    _current = null;
    GESTURE = {};
    FIRST_TOUCH = [];
    CURRENT_TOUCH = [];
    TOUCH_TIMEOUT = void 0;
    HOLD_DELAY = 650;
    GESTURES = ["singleTap", "doubleTap", "hold", "swipe", "swiping", "swipeLeft", "swipeRight", "swipeUp", "swipeDown", "rotate", "rotating", "rotateLeft", "rotateRight", "pinch", "pinching", "pinchIn", "pinchOut", "drag", "dragLeft", "dragRight", "dragUp", "dragDown"];

    $.each({ touch: "touchstart", tap: "tap" }, function(event, shortcut) {
        $.fn[event] = function(callback) {
            return $(document.body).on(shortcut, this, callback);
        };
    });

    $.each(GESTURES, function(k, event) {
        $.fn[event] = function (callback) {
            return this.on(event, this.selector, callback);
        };
    });

    $(document).ready(function () {
        return _listenTouches();
    });

    _listenTouches = function () {
        return $(document.body)
            .on("touchstart", _onTouchStart)
            .on("touchmove", _onTouchMove)
            .on("touchend", _onTouchEnd)
            .on("touchcancel", _cleanGesture);
    };

    _onTouchStart = function (event) {
        var delta, fingers, now, touches;
        now = Date.now();
        delta = now - (GESTURE.last || now);
        TOUCH_TIMEOUT && clearTimeout(TOUCH_TIMEOUT);
        touches = _getTouches(event);
        fingers = touches.length;
        FIRST_TOUCH = _fingersPosition(touches, fingers);
        GESTURE.el = $(_parentIfText(touches[0].target));
        GESTURE.fingers = fingers;
        GESTURE.last = now;
        if (fingers === 1) {
            GESTURE.isDoubleTap = delta > 0 && delta <= 250;
            return setTimeout(_hold, HOLD_DELAY);
        } else if (fingers === 2) {
            GESTURE.initial_angle = parseInt(_angle(FIRST_TOUCH), 10);
            GESTURE.initial_distance = parseInt(_distance(FIRST_TOUCH), 10);
            GESTURE.angle_difference = 0;
            return GESTURE.distance_difference = 0;
        }
    };

    _onTouchMove = function (event) {
        var fingers, touches;
        if (GESTURE.el) {
            touches = _getTouches(event);
            fingers = touches.length;
            if (fingers === GESTURE.fingers) {
                CURRENT_TOUCH = _fingersPosition(touches, fingers);
                if (_isSwipe(event)) {
                    _trigger("swiping");
                }
                if (fingers === 2) {
                    _captureRotation();
                    _capturePinch();
                    event.preventDefault();
                }
            } else {
                _cleanGesture();
            }
        }
        return true;
    };

    _isSwipe = function () {
        var move_horizontal, move_vertical, ret;
        ret = false;
        if (CURRENT_TOUCH[0]) {
            move_horizontal = Math.abs(FIRST_TOUCH[0].x - CURRENT_TOUCH[0].x) > 30;
            move_vertical = Math.abs(FIRST_TOUCH[0].y - CURRENT_TOUCH[0].y) > 30;
            ret = GESTURE.el && (move_horizontal || move_vertical);
        }
        return ret;
    };

    _onTouchEnd = function () {
        var anyevent, drag_direction, pinch_direction, rotation_direction, swipe_direction;
        if (GESTURE.fingers === 1) {
            if (_isSwipe()) {
                _trigger("swipe");
                swipe_direction = _swipeDirection(FIRST_TOUCH[0].x, CURRENT_TOUCH[0].x, FIRST_TOUCH[0].y, CURRENT_TOUCH[0].y);
                _trigger("swipe" + swipe_direction);
                return _cleanGesture();
            } else {
                if (GESTURE.isDoubleTap) {
                    _trigger("doubleTap");
                    return _cleanGesture();
                } else {
                    TOUCH_TIMEOUT = setTimeout(function () {
                        _trigger("singleTap");
                        _cleanGesture();
                    }, 250);
                    return TOUCH_TIMEOUT;
                }
            }
        } else if (GESTURE.fingers === 2) {
            anyevent = false;

            if (GESTURE.angle_difference !== 0) {
                _trigger("rotate", {
                    angle: GESTURE.angle_difference
                });
                rotation_direction = GESTURE.angle_difference > 0 ? "rotateRight" : "rotateLeft";
                _trigger(rotation_direction, {
                    angle: GESTURE.angle_difference
                });
                anyevent = true;
            }

            if (GESTURE.distance_difference !== 0) {
                _trigger("pinch", {
                    angle: GESTURE.distance_difference
                });
                pinch_direction = GESTURE.distance_difference > 0 ? "pinchOut" : "pinchIn";
                _trigger(pinch_direction, {
                    distance: GESTURE.distance_difference
                });
                anyevent = true;
            }

            if (!anyevent && CURRENT_TOUCH[0]) {
                if (Math.abs(FIRST_TOUCH[0].x - CURRENT_TOUCH[0].x) > 10 || Math.abs(FIRST_TOUCH[0].y - CURRENT_TOUCH[0].y) > 10) {
                    _trigger("drag");
                    drag_direction = _swipeDirection(FIRST_TOUCH[0].x, CURRENT_TOUCH[0].x, FIRST_TOUCH[0].y, CURRENT_TOUCH[0].y);
                    _trigger("drag" + drag_direction);
                }
            }
            return _cleanGesture();
        }
    };

    _fingersPosition = function (touches, fingers) {
        var result = [],
            i = 0;
        touches = touches[0].targetTouches ? touches[0].targetTouches : touches;
        while (i < fingers) {
            result.push({
                x: touches[i].pageX,
                y: touches[i].pageY
            });
            i++;
        }
        return result;
    };

    _captureRotation = function () {
        var angle, diff, i, symbol;
        angle = parseInt(_angle(CURRENT_TOUCH), 10);
        diff = parseInt(GESTURE.initial_angle - angle, 10);
        if (Math.abs(diff) > 20 || GESTURE.angle_difference !== 0) {
            i = 0;
            symbol = GESTURE.angle_difference < 0 ? "-" : "+";
            while (Math.abs(diff - GESTURE.angle_difference) > 90 && i++ < 10) {
                eval("diff " + symbol + "= 180;");
            }
            GESTURE.angle_difference = parseInt(diff, 10);
            return _trigger("rotating", {
                angle: GESTURE.angle_difference
            });
        }
    };

    _capturePinch = function () {
        var diff, distance;
        distance = parseInt(_distance(CURRENT_TOUCH), 10);
        diff = GESTURE.initial_distance - distance;
        if (Math.abs(diff) > 10) {
            GESTURE.distance_difference = diff;
            return _trigger("pinching", {
                distance: diff
            });
        }
    };

    _trigger = function (type, params) {
        if (GESTURE.el) {
            params = params || {};
            if (CURRENT_TOUCH[0]) {
                params.iniTouch = (GESTURE.fingers > 1 ? FIRST_TOUCH : FIRST_TOUCH[0]);
                params.currentTouch = (GESTURE.fingers > 1 ? CURRENT_TOUCH : CURRENT_TOUCH[0]);
            }
            return GESTURE.el.trigger(type, params);
        }
    };

    _cleanGesture = function () {
        FIRST_TOUCH = [];
        CURRENT_TOUCH = [];
        GESTURE = {};
        return clearTimeout(TOUCH_TIMEOUT);
    };

    _angle = function (touches_data) {
        var A = touches_data[0],
            B = touches_data[1],
            angle = Math.atan((B.y - A.y) * -1 / (B.x - A.x)) * (180 / Math.PI);
        return (angle < 0) ? angle + 180 : angle;
    };

    _distance = function (touches_data) {
        var A = touches_data[0],
            B = touches_data[1];
        return Math.sqrt((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y)) * -1;
    };

    _getTouches = function (event) {
        var e = event.originalEvent || event;
        return e.touches || [e];
    };

    _parentIfText = function (node) {
        return ("tagName" in node) ? node : node.parentNode;
    };

    _swipeDirection = function (x1, x2, y1, y2) {
        if (Math.abs(x1 - x2) >= Math.abs(y1 - y2)) {
            return (x1 - x2 > 0) ? "Left" : "Right";
        } else {
            return (y1 - y2 > 0) ? "Up" : "Down";
        }
    };

    _hold = function() {
        if (GESTURE.last && (Date.now() - GESTURE.last >= HOLD_DELAY)) {
            _trigger("hold");
            return _cleanGesture();
        }
    };
}(jQuery));
<commit_msg>Added x and y coordinates for doubleTap and singleTap events<commit_after>/*
 * jQuery Touch Gestures
 * @version 1.1
 * @ported > QuoJS
 * @copyright QuoJS 2.2.0 (c) 2011, 2012 Javi Jiménez Villar (@soyjavi)
 * @see http://quojs.tapquo.com
 */
(function($) {
    var CURRENT_TOUCH, FIRST_TOUCH, GESTURE, GESTURES, HOLD_DELAY, TOUCH_TIMEOUT, _angle, _capturePinch, _captureRotation, _cleanGesture, _distance, _fingersPosition, _getTouches, _hold, _isSwipe, _listenTouches, _onTouchEnd, _onTouchMove, _onTouchStart, _parentIfText, _swipeDirection, _trigger, _current;

    _current = null;
    GESTURE = {};
    FIRST_TOUCH = [];
    CURRENT_TOUCH = [];
    TOUCH_TIMEOUT = void 0;
    HOLD_DELAY = 650;
    GESTURES = ["singleTap", "doubleTap", "hold", "swipe", "swiping", "swipeLeft", "swipeRight", "swipeUp", "swipeDown", "rotate", "rotating", "rotateLeft", "rotateRight", "pinch", "pinching", "pinchIn", "pinchOut", "drag", "dragLeft", "dragRight", "dragUp", "dragDown"];

    $.each({ touch: "touchstart", tap: "tap" }, function(event, shortcut) {
        $.fn[event] = function(callback) {
            return $(document.body).on(shortcut, this, callback);
        };
    });

    $.each(GESTURES, function(k, event) {
        $.fn[event] = function (callback) {
            return this.on(event, this.selector, callback);
        };
    });

    $(document).ready(function () {
        return _listenTouches();
    });

    _listenTouches = function () {
        return $(document.body)
            .on("touchstart", _onTouchStart)
            .on("touchmove", _onTouchMove)
            .on("touchend", _onTouchEnd)
            .on("touchcancel", _cleanGesture);
    };

    _onTouchStart = function (event) {
        var delta, fingers, now, touches;
        now = Date.now();
        delta = now - (GESTURE.last || now);
        TOUCH_TIMEOUT && clearTimeout(TOUCH_TIMEOUT);
        touches = _getTouches(event);
        fingers = touches.length;
        FIRST_TOUCH = _fingersPosition(touches, fingers);
        GESTURE.el = $(_parentIfText(touches[0].target));
        GESTURE.fingers = fingers;
        GESTURE.last = now;
        if (fingers === 1) {
            GESTURE.isDoubleTap = delta > 0 && delta <= 250;
            return setTimeout(_hold, HOLD_DELAY);
        } else if (fingers === 2) {
            GESTURE.initial_angle = parseInt(_angle(FIRST_TOUCH), 10);
            GESTURE.initial_distance = parseInt(_distance(FIRST_TOUCH), 10);
            GESTURE.angle_difference = 0;
            return GESTURE.distance_difference = 0;
        }
    };

    _onTouchMove = function (event) {
        var fingers, touches;
        if (GESTURE.el) {
            touches = _getTouches(event);
            fingers = touches.length;
            if (fingers === GESTURE.fingers) {
                CURRENT_TOUCH = _fingersPosition(touches, fingers);
                if (_isSwipe(event)) {
                    _trigger("swiping");
                }
                if (fingers === 2) {
                    _captureRotation();
                    _capturePinch();
                    event.preventDefault();
                }
            } else {
                _cleanGesture();
            }
        }
        return true;
    };

    _isSwipe = function () {
        var move_horizontal, move_vertical, ret;
        ret = false;
        if (CURRENT_TOUCH[0]) {
            move_horizontal = Math.abs(FIRST_TOUCH[0].x - CURRENT_TOUCH[0].x) > 30;
            move_vertical = Math.abs(FIRST_TOUCH[0].y - CURRENT_TOUCH[0].y) > 30;
            ret = GESTURE.el && (move_horizontal || move_vertical);
        }
        return ret;
    };

    _onTouchEnd = function () {
        var anyevent, drag_direction, pinch_direction, rotation_direction, swipe_direction;
        if (GESTURE.fingers === 1) {
            if (_isSwipe()) {
                _trigger("swipe");
                swipe_direction = _swipeDirection(FIRST_TOUCH[0].x, CURRENT_TOUCH[0].x, FIRST_TOUCH[0].y, CURRENT_TOUCH[0].y);
                _trigger("swipe" + swipe_direction);
                return _cleanGesture();
            } else {
                if (GESTURE.isDoubleTap) {
                    _trigger("doubleTap", {
                        x: FIRST_TOUCH[0].x,
                        y: FIRST_TOUCH[0].y
                    });
                    return _cleanGesture();
                } else {
                    TOUCH_TIMEOUT = setTimeout(function () {
                        _trigger("singleTap", {
                            x: FIRST_TOUCH[0].x,
                            y: FIRST_TOUCH[0].y
                        });
                        _cleanGesture();
                    }, 250);
                    return TOUCH_TIMEOUT;
                }
            }
        } else if (GESTURE.fingers === 2) {
            anyevent = false;

            if (GESTURE.angle_difference !== 0) {
                _trigger("rotate", {
                    angle: GESTURE.angle_difference
                });
                rotation_direction = GESTURE.angle_difference > 0 ? "rotateRight" : "rotateLeft";
                _trigger(rotation_direction, {
                    angle: GESTURE.angle_difference
                });
                anyevent = true;
            }

            if (GESTURE.distance_difference !== 0) {
                _trigger("pinch", {
                    angle: GESTURE.distance_difference
                });
                pinch_direction = GESTURE.distance_difference > 0 ? "pinchOut" : "pinchIn";
                _trigger(pinch_direction, {
                    distance: GESTURE.distance_difference
                });
                anyevent = true;
            }

            if (!anyevent && CURRENT_TOUCH[0]) {
                if (Math.abs(FIRST_TOUCH[0].x - CURRENT_TOUCH[0].x) > 10 || Math.abs(FIRST_TOUCH[0].y - CURRENT_TOUCH[0].y) > 10) {
                    _trigger("drag");
                    drag_direction = _swipeDirection(FIRST_TOUCH[0].x, CURRENT_TOUCH[0].x, FIRST_TOUCH[0].y, CURRENT_TOUCH[0].y);
                    _trigger("drag" + drag_direction);
                }
            }
            return _cleanGesture();
        }
    };

    _fingersPosition = function (touches, fingers) {
        var result = [],
            i = 0;
        touches = touches[0].targetTouches ? touches[0].targetTouches : touches;
        while (i < fingers) {
            result.push({
                x: touches[i].pageX,
                y: touches[i].pageY
            });
            i++;
        }
        return result;
    };

    _captureRotation = function () {
        var angle, diff, i, symbol;
        angle = parseInt(_angle(CURRENT_TOUCH), 10);
        diff = parseInt(GESTURE.initial_angle - angle, 10);
        if (Math.abs(diff) > 20 || GESTURE.angle_difference !== 0) {
            i = 0;
            symbol = GESTURE.angle_difference < 0 ? "-" : "+";
            while (Math.abs(diff - GESTURE.angle_difference) > 90 && i++ < 10) {
                eval("diff " + symbol + "= 180;");
            }
            GESTURE.angle_difference = parseInt(diff, 10);
            return _trigger("rotating", {
                angle: GESTURE.angle_difference
            });
        }
    };

    _capturePinch = function () {
        var diff, distance;
        distance = parseInt(_distance(CURRENT_TOUCH), 10);
        diff = GESTURE.initial_distance - distance;
        if (Math.abs(diff) > 10) {
            GESTURE.distance_difference = diff;
            return _trigger("pinching", {
                distance: diff
            });
        }
    };

    _trigger = function (type, params) {
        if (GESTURE.el) {
            params = params || {};
            if (CURRENT_TOUCH[0]) {
                params.iniTouch = (GESTURE.fingers > 1 ? FIRST_TOUCH : FIRST_TOUCH[0]);
                params.currentTouch = (GESTURE.fingers > 1 ? CURRENT_TOUCH : CURRENT_TOUCH[0]);
            }
            return GESTURE.el.trigger(type, params);
        }
    };

    _cleanGesture = function () {
        FIRST_TOUCH = [];
        CURRENT_TOUCH = [];
        GESTURE = {};
        return clearTimeout(TOUCH_TIMEOUT);
    };

    _angle = function (touches_data) {
        var A = touches_data[0],
            B = touches_data[1],
            angle = Math.atan((B.y - A.y) * -1 / (B.x - A.x)) * (180 / Math.PI);
        return (angle < 0) ? angle + 180 : angle;
    };

    _distance = function (touches_data) {
        var A = touches_data[0],
            B = touches_data[1];
        return Math.sqrt((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y)) * -1;
    };

    _getTouches = function (event) {
        var e = event.originalEvent || event;
        return e.touches || [e];
    };

    _parentIfText = function (node) {
        return ("tagName" in node) ? node : node.parentNode;
    };

    _swipeDirection = function (x1, x2, y1, y2) {
        if (Math.abs(x1 - x2) >= Math.abs(y1 - y2)) {
            return (x1 - x2 > 0) ? "Left" : "Right";
        } else {
            return (y1 - y2 > 0) ? "Up" : "Down";
        }
    };

    _hold = function() {
        if (GESTURE.last && (Date.now() - GESTURE.last >= HOLD_DELAY)) {
            _trigger("hold");
            return _cleanGesture();
        }
    };
}(jQuery));
<|endoftext|>


<commit_before>function Trace() {
	this.traces = [];
}

Trace.prototype.traceLine = function(lineNo) {
	this.traces.push({type: "line", line: lineNo});
}

Trace.prototype.traceWrite = function(varName, val, init) {
	this.traces.push({type: "write", varName: varName, value: val, init: init == null ? false : init});
	return val;
}

Trace.prototype.rollup = function() {
	return this.traces.reduce(function(states, trace) {
		switch (trace.type) {
			case "line":
				var newState = {line: trace.line, vars: {}};
				if (states.length > 0) {
					newState.vars = states[states.length - 1].vars;
				}
				states.push(newState);
				break;
			case "write":
				var curState = states[states.length - 1];
				curState.vars[trace.varName] = trace.value;
				break;
			default:
				throw "unknown type " + trace.type;
		}
		return states;
	}, []);
}

var currentTrace = null;

Trace.start = function() {
	currentTrace = new Trace();
}

function traceLine() { currentTrace.traceLine.apply(currentTrace, arguments); }
function traceWrite() { return currentTrace.traceWrite.apply(currentTrace, arguments); }
<commit_msg>support tracing without calling Trace.start first.<commit_after>function Trace() {
	this.traces = [];
}

Trace.prototype.traceLine = function(lineNo) {
	this.traces.push({type: "line", line: lineNo});
}

Trace.prototype.traceWrite = function(varName, val, init) {
	this.traces.push({type: "write", varName: varName, value: val, init: init == null ? false : init});
	return val;
}

Trace.prototype.rollup = function() {
	return this.traces.reduce(function(states, trace) {
		switch (trace.type) {
			case "line":
				var newState = {line: trace.line, vars: {}};
				if (states.length > 0) {
					newState.vars = states[states.length - 1].vars;
				}
				states.push(newState);
				break;
			case "write":
				var curState = states[states.length - 1];
				curState.vars[trace.varName] = trace.value;
				break;
			default:
				throw "unknown type " + trace.type;
		}
		return states;
	}, []);
}

var currentTrace = new Trace();

Trace.start = function() {
	currentTrace = new Trace();
}

function traceLine() { currentTrace.traceLine.apply(currentTrace, arguments); }
function traceWrite() { return currentTrace.traceWrite.apply(currentTrace, arguments); }
<|endoftext|>


<commit_before>(function ($) {
  "use strict";
  /*global alert: true, ODSA */
  $(document).ready(function () {
    var settings = new JSAV.utils.Settings($(".jsavsettings")),
    jsav = new JSAV($('.avcontainer'), {settings: settings}),
    exercise, graph, modelGraph,
    edgeCount = 9, graphNodes = [], gnodes = [], mstnodes = [];
    var exerciseStep, step;
    jsav.recorded();

    function init() {
      exerciseStep = 0;
	  step = 0;
      var i;
      if (graph) {
        graph.clear();
      }
      graph = jsav.ds.graph({width: 600, height: 600, layout: "manual", directed: false});
      initGraph("orig");
      graph.layout();
      jsav.displayInit();
      return graph;
    }
    function fixState(mGraph) {
	  var i;
      var node;
	  var prev;
	  for (i = 0; i <gnodes.length; i++) {
	    if(gnodes[i].addedStep === exerciseStep) {
		  node = graphNodes[i];
		  prev = graphNodes[gnodes.indexOf(gnodes[i].prev)];
		}
	  }
	  markIt(node, null);
	  if (prev) {
		  node.edgeFrom(prev).css({"stroke-width":"4", "stroke":"red"});
	  }
    }
    function model(modeljsav) {
      var i;
      modelGraph = modeljsav.ds.graph({width: 600, height: 600, layout: "manual", directed: false});
      initGraph("model");
      modelGraph.layout();
	  modeljsav.displayInit();
	  bfs(gnodes[0], modeljsav);
	  modeljsav.umsg("Final DFS graph");
	  for (i = 0; i < edgeCount; i++) {
	    if (!modelGraph.edges()[i].added) {
		  modelGraph.edges()[i].hide();
		}
	  }
	  modelGraph.layout();
	  modeljsav.step();
      return modelGraph;
    }
	// Mark a node in the graph.
    function markIt(node, av) {
	  var edge;
	  step++;
	  node.addedStep = step;
	  console.log(step);
      node.addClass("visited");
	  if (av)
        av.umsg("Visit node " + node.value());
      node.highlight();
	  if (av) {
        av.step();
		if(node.prev) {
	      av.umsg("Add edge ("+node.prev.value()+","+node.value()+") to the BFS graph");
	      edge = node.edgeFrom(node.prev).css({"stroke-width":"4", "stroke":"red"});
		  edge.added = true;
		  av.step();
	    }
		av.stepOption('grade', true);
      }
    }
    function bfs(start, av) {
      var adjacent = [];
	  var visitedNeighbors = 0;
      var next;
	  if (!start.hasClass("visited")) {
	    markIt(start, av);  //Visit
	  }
      adjacent = start.neighbors();
	  for (var i = 0; i < adjacent.length; i++) {
	    if (adjacent[i].hasClass("visited")) {
		  visitedNeighbors++;
		}
	  }
	  //Base Case
	  if (visitedNeighbors === adjacent.length) {
	    return;
	  }
	  else {
        for (next = adjacent.next(); next; next = adjacent.next()) {
          av.umsg("Process edge (" + start.value() + "," + next.value() + ")");
          if(next.hasClass("visited")) {
            av.umsg(" :Node " + next.value() + " already visited", {'preserve': true});
          }
		  av.step();
          if (!next.hasClass("visited")) {
		    next.prev = start;
            markIt(next, av);
          }
        }
	    bfs(adjacent[0], av);
	  }
    }
    function initGraph(type) {
      var a, b, c, d, e, f;
      //Nodes of the original graph
      if (type === "orig") {
        a = graph.addNode("A", {"left": 25, "top": 50});
        b = graph.addNode("B", {"left": 325, "top": 50});
        c = graph.addNode("C", {"left": 145, "top": 75});
        d = graph.addNode("D", {"left": 145, "top": 200});
        e = graph.addNode("E", {"left": 0, "top": 300});
        f = graph.addNode("F", {"left": 325, "top": 250});
        //Original graph edges
        graph.addEdge(a, c);
        graph.addEdge(a, e);
        graph.addEdge(c, b);
        graph.addEdge(c, d);
		graph.addEdge(c, e);
        graph.addEdge(c, f);
        graph.addEdge(f, b);
        graph.addEdge(d, f);
        graph.addEdge(e, f);
		graphNodes = graph.nodes();
      }
      else {
        //Nodes of the model Graph
        a = modelGraph.addNode("A", {"left": 25, "top": 50});
        b = modelGraph.addNode("B", {"left": 325, "top": 50});
        c = modelGraph.addNode("C", {"left": 145, "top": 75});
        d = modelGraph.addNode("D", {"left": 145, "top": 200});
        e = modelGraph.addNode("E", {"left": 0, "top": 300});
        f = modelGraph.addNode("F", {"left": 325, "top": 250});
        //Model graph edges
        modelGraph.addEdge(a, c);
        modelGraph.addEdge(a, e);
        modelGraph.addEdge(c, b);
        modelGraph.addEdge(c, d);
		modelGraph.addEdge(c, e);
        modelGraph.addEdge(c, f);
        modelGraph.addEdge(f, b);
        modelGraph.addEdge(d, f);
        modelGraph.addEdge(e, f);
        gnodes = modelGraph.nodes();
      }
    }
    // Process About button: Pop up a message with an Alert
    function about() {
      alert("Heapsort Proficiency Exercise\nWritten by Ville Karavirta\nCreated as part of the OpenDSA hypertextbook project\nFor more information, see http://algoviz.org/OpenDSA\nSource and development history available at\nhttps://github.com/cashaffer/OpenDSA\nCompiled with JSAV library version " + JSAV.version());
    }

    exercise = jsav.exercise(model, init, { css: "background-color" },
    {controls: $('.jsavexercisecontrols'), fix: fixState });
    exercise.reset();

    $(".jsavcontainer").on("click", ".jsavgraphnode", function () {
      var nodeIndex = $(this).parent(".jsavgraph").find(".jsavgraphnode").index(this);
      var node = graphNodes[nodeIndex];
	  var prev;
      if (!node.hasClass('visited')) {
	    markIt(node, null);
		exerciseStep++;
		prev = graphNodes[gnodes.indexOf(gnodes[nodeIndex].prev)];
		if (prev) {
		  node.edgeFrom(prev).css({"stroke-width":"4", "stroke":"red"});
		}
		exercise.gradeableStep();
      }
    });
    $("#about").click(about);
  });
}(jQuery));
<commit_msg>BFS proficiency working on Randomized graphs<commit_after>(function ($) {
  "use strict";
  /*global alert: true, ODSA */
  var nodes = [];
  var edgeStarts = [];
  var edgeEnds = [];
  var nNodes = 6;
  var nEdges = 10;
  var adjacencyMatrix = [,];
  var randomWeights = [];

  function sort(nodes) {
    for (var i = 0;i < nodes.length-1;i++) {
	  for (var j = i + 1;j < nodes.length; j++) {
	    if (nodes[i].value().charAt(0) > nodes[j].value().charAt(0)) {
		  var temp = nodes[i];
		  nodes[i] = nodes[j];
		  nodes[j] = temp;
		}
	  }
	}
  }
  function generateNodeEdgeCounts() {
    //generate the number of nodes and edges
    var n;
    var e;
    while (true) {
      n = Math.floor((Math.random() * 10));
      if (n >= 5) {    //Number Of nodes should be at least 5
        nNodes = n;
        break;
      }
    }
    while (true) {
      e = Math.floor((Math.random() * 50));
      // Number of Edges should at least two more than the number of nodes and at most
      // the number of nodes pair combination
      if (e > nNodes + 2 && e < (nNodes * (nNodes - 1)) / 2) {
        nEdges = e;
        break;
      }
    }
  }

  function generateRandomWeights() {
    var count = 0;
    var weight;
    randomWeights = new Array(nEdges);
    while (true) {
      weight = Math.floor((Math.random() * 10));
      if (weight === 0) {
        continue;
      }
      else {
        randomWeights[count] = weight;
        count++;
      }
      if (count === nEdges) {
        break;
      }
    }
  }

  function isEligibleEdge(startIndex, endIndex) {
    if ((startIndex === endIndex) || (startIndex >= nNodes) || (endIndex >= nNodes)) {
      return false;
    }
    if ((adjacencyMatrix[startIndex][endIndex] === 1) ||
        (adjacencyMatrix[endIndex][startIndex] === 1)) {
      return false;
    }
    else {
      return true;
    }
  }

  function generateRandomPairs() {
    var count = 0;
    var index1;
    var index2;
    //Initialize the adjacency matrix
    for (var i = 0; i < nNodes; i++) {
      for (var j = 0; j < nNodes; j++) {
        adjacencyMatrix[i][j] = 0;
      }
    }
    while (true) {
      index1 = Math.floor((Math.random() * 10));
      index2 = Math.floor((Math.random() * 10));
      if (!isEligibleEdge(index1, index2)) {
        continue;
      }
      else {
        edgeStarts[count] = index1;
        edgeEnds[count] = index2;
        count++;
        if (count === nEdges) {
          break;
        }
      }
      adjacencyMatrix[index1][index2] = 1;
      adjacencyMatrix[index2][index1] = 1;
    }
  }

  function generate(g, weighted) {
    var i;
    //generateNodeEdgeCounts();
    nodes = new Array(nNodes);
    edgeStarts = new Array(nEdges);
    edgeEnds = new Array(nEdges);
    //Generate the nodes
    for (i = 0; i < nNodes; i++) {
      nodes[i] = String.fromCharCode(i + 65);
    }
    //Create the adjacencyMatrix
    adjacencyMatrix = new Array(nNodes);
    for (i = 0; i < nNodes; i++) {
      adjacencyMatrix[i] = new Array(nNodes);
    }
    generateRandomPairs();
    if (weighted) {
      generateRandomWeights();
    }
    for (i = 0; i < nNodes; i++) {
      g.addNode(nodes[i]);
    }
    for (i = 0; i < nEdges; i++) {
      console.log(edgeStarts[i] + "  " + edgeEnds[i]);
      if (weighted) {
        g.addEdge(g.nodes()[edgeStarts[i]], g.nodes()[edgeEnds[i]],
                  {"weight": parseInt(randomWeights[i])});
      }
      else {
        g.addEdge(g.nodes()[edgeStarts[i]], g.nodes()[edgeEnds[i]]);
      }
    }
  }
  $(document).ready(function () {
    var settings = new JSAV.utils.Settings($(".jsavsettings")),
    jsav = new JSAV($('.avcontainer'), {settings: settings}),
    exercise, graph, modelGraph, graphNodes = [], gnodes = [], mstnodes = [];
    var exerciseStep, step;
    //jsav.recorded();

    function init() {
      exerciseStep = 0;
	  step = 0;
      var i;
      if (graph) {
        graph.clear();
      }
      graph = jsav.ds.graph({width: 400, height: 400, layout: "automatic", directed: false});
      generate(graph, false);
	  graphNodes = graph.nodes();
      graph.layout();
      jsav.displayInit();
      return graph;
    }
    function fixState(mGraph) {
	  var i;
      var node;
	  var prev;
	  for (i = 0; i <gnodes.length; i++) {
	    if(gnodes[i].addedStep === exerciseStep) {
		  node = graphNodes[i];
		  prev = graphNodes[gnodes.indexOf(gnodes[i].prev)];
		}
	  }
	  markIt(node, null);
	  if (prev) {
		  node.edgeFrom(prev).css({"stroke-width":"4", "stroke":"red"});
	  }
    }
    function model(modeljsav) {
      var i;
      modelGraph = modeljsav.ds.graph({width: 400, height: 400, layout: "automatic", directed: false});
      for (i = 0; i < graph.nodeCount(); i++) {
        modelGraph.addNode(graph.nodes()[i].value());
      }
      for (i = 0; i < graph.edges().length; i++) {
        modelGraph.addEdge(modelGraph.nodes()[graph.nodes().indexOf(graph.edges()[i].start())],
                           modelGraph.nodes()[graph.nodes().indexOf(graph.edges()[i].end())]);
      }
	  gnodes = modelGraph.nodes();
      modelGraph.layout();
	  modeljsav.displayInit();
	  bfs(gnodes[0], modeljsav);
	  modeljsav.umsg("Final BFS graph");
	  for (i = 0; i < nEdges; i++) {
	    if (!modelGraph.edges()[i].added) {
		  modelGraph.edges()[i].hide();
		}
	  }
	  modelGraph.layout();
	  modeljsav.step();
      return modelGraph;
    }
	// Mark a node in the graph.
    function markIt(node, av) {
	  var edge;
	  step++;
	  node.addedStep = step;
	  console.log(step);
      node.addClass("visited");
	  if (av)
        av.umsg("Visit node " + node.value());
      node.highlight();
	  if (av) {
        av.step();
		if(node.prev) {
	      av.umsg("Add edge ("+node.prev.value()+","+node.value()+") to the BFS graph");
	      edge = node.edgeFrom(node.prev).css({"stroke-width":"4", "stroke":"red"});
		  edge.added = true;
		  av.step();
	    }
		av.stepOption('grade', true);
      }
    }
    function bfs(start, av) {
      var adjacent = [];
	  var visitedNeighbors = 0;
      var next;
	  if (!start.hasClass("visited")) {
	    markIt(start, av);  //Visit
	  }
      adjacent = start.neighbors();
	  sort(adjacent); //Sort the neighbors according to their value
	  for (var i = 0; i < adjacent.length; i++) {
	    if (adjacent[i].hasClass("visited")) {
		  visitedNeighbors++;
		}
	  }
	  //Base Case
	  if (visitedNeighbors === adjacent.length) {
	    return;
	  }
	  else {
        for (next = adjacent.next(); next; next = adjacent.next()) {
          av.umsg("Process edge (" + start.value() + "," + next.value() + ")");
          if(next.hasClass("visited")) {
            av.umsg(" :Node " + next.value() + " already visited", {'preserve': true});
          }
		  av.step();
          if (!next.hasClass("visited")) {
		    next.prev = start;
            markIt(next, av);
          }
        }
		adjacent.reset();
		for (next = adjacent.next(); next; next = adjacent.next()) {
		  bfs(next, av);
		}

	  }
    }
    function initGraph(type) {
      var a, b, c, d, e, f;
      //Nodes of the original graph
      if (type === "orig") {
        a = graph.addNode("A", {"left": 25, "top": 50});
        b = graph.addNode("B", {"left": 325, "top": 50});
        c = graph.addNode("C", {"left": 145, "top": 75});
        d = graph.addNode("D", {"left": 145, "top": 200});
        e = graph.addNode("E", {"left": 0, "top": 300});
        f = graph.addNode("F", {"left": 325, "top": 250});
        //Original graph edges
        graph.addEdge(a, c);
        graph.addEdge(a, e);
        graph.addEdge(c, b);
        graph.addEdge(c, d);
		graph.addEdge(c, e);
        graph.addEdge(c, f);
        graph.addEdge(f, b);
        graph.addEdge(d, f);
        graph.addEdge(e, f);
		graphNodes = graph.nodes();
      }
      else {
        //Nodes of the model Graph
        a = modelGraph.addNode("A", {"left": 25, "top": 50});
        b = modelGraph.addNode("B", {"left": 325, "top": 50});
        c = modelGraph.addNode("C", {"left": 145, "top": 75});
        d = modelGraph.addNode("D", {"left": 145, "top": 200});
        e = modelGraph.addNode("E", {"left": 0, "top": 300});
        f = modelGraph.addNode("F", {"left": 325, "top": 250});
        //Model graph edges
        modelGraph.addEdge(a, c);
        modelGraph.addEdge(a, e);
        modelGraph.addEdge(c, b);
        modelGraph.addEdge(c, d);
		modelGraph.addEdge(c, e);
        modelGraph.addEdge(c, f);
        modelGraph.addEdge(f, b);
        modelGraph.addEdge(d, f);
        modelGraph.addEdge(e, f);
        gnodes = modelGraph.nodes();
      }
    }
    // Process About button: Pop up a message with an Alert
    function about() {
      alert("Heapsort Proficiency Exercise\nWritten by Ville Karavirta\nCreated as part of the OpenDSA hypertextbook project\nFor more information, see http://algoviz.org/OpenDSA\nSource and development history available at\nhttps://github.com/cashaffer/OpenDSA\nCompiled with JSAV library version " + JSAV.version());
    }

    exercise = jsav.exercise(model, init, { css: "background-color" },
    {controls: $('.jsavexercisecontrols'), fix: fixState });
    exercise.reset();

    $(".jsavcontainer").on("click", ".jsavgraphnode", function () {
      var nodeIndex = $(this).parent(".jsavgraph").find(".jsavgraphnode").index(this);
      var node = graphNodes[nodeIndex];
	  var prev;
      if (!node.hasClass('visited')) {
	    markIt(node, null);
		exerciseStep++;
		prev = graphNodes[gnodes.indexOf(gnodes[nodeIndex].prev)];
		if (prev) {
		  node.edgeFrom(prev).css({"stroke-width":"4", "stroke":"red"});
		}
		exercise.gradeableStep();
      }
    });
    $("#about").click(about);
  });
}(jQuery));
<|endoftext|>


<commit_before>/**
 * Route Mappings
 * (sails.config.routes)
 *
 * Your routes map URLs to views and controllers.
 *
 * If Sails receives a URL that doesn't match any of the routes below,
 * it will check for matching files (images, scripts, stylesheets, etc.)
 * in your assets directory.  e.g. `http://localhost:1337/images/foo.jpg`
 * might match an image file: `/assets/images/foo.jpg`
 *
 * Finally, if those don't match either, the default 404 handler is triggered.
 * See `api/responses/notFound.js` to adjust your app's 404 logic.
 *
 * Note: Sails doesn't ACTUALLY serve stuff from `assets`-- the default Gruntfile in Sails copies
 * flat files from `assets` to `.tmp/public`.  This allows you to do things like compile LESS or
 * CoffeeScript for the front-end.
 *
 * For more information on configuring custom routes, check out:
 * http://sailsjs.org/#/documentation/concepts/Routes/RouteTargetSyntax.html
 */

module.exports.routes = {

  /***************************************************************************
  *                                                                          *
  * Make the view located at `views/homepage.ejs` (or `views/homepage.jade`, *
  * etc. depending on your default view engine) your home page.              *
  *                                                                          *
  * (Alternatively, remove this and add an `index.html` file in your         *
  * `assets` directory)                                                      *
  *                                                                          *
  ***************************************************************************/

  '/': {
    view: 'homepage'
  }

  /***************************************************************************
  *                                                                          *
  * Custom routes here...                                                    *
  *                                                                          *
  *  If a request to a URL doesn't match any of the custom routes above, it  *
  * is matched against Sails route blueprints. See `config/blueprints.js`    *
  * for configuration options and examples.                                  *
  *                                                                          *
  ***************************************************************************/

};
<commit_msg>Passport routes<commit_after>/**
 * Route Mappings
 * (sails.config.routes)
 *
 * Your routes map URLs to views and controllers.
 *
 * If Sails receives a URL that doesn't match any of the routes below,
 * it will check for matching files (images, scripts, stylesheets, etc.)
 * in your assets directory.  e.g. `http://localhost:1337/images/foo.jpg`
 * might match an image file: `/assets/images/foo.jpg`
 *
 * Finally, if those don't match either, the default 404 handler is triggered.
 * See `api/responses/notFound.js` to adjust your app's 404 logic.
 *
 * Note: Sails doesn't ACTUALLY serve stuff from `assets`-- the default Gruntfile in Sails copies
 * flat files from `assets` to `.tmp/public`.  This allows you to do things like compile LESS or
 * CoffeeScript for the front-end.
 *
 * For more information on configuring custom routes, check out:
 * http://sailsjs.org/#/documentation/concepts/Routes/RouteTargetSyntax.html
 */

module.exports.routes = {

  /***************************************************************************
  *                                                                          *
  * Make the view located at `views/homepage.ejs` (or `views/homepage.jade`, *
  * etc. depending on your default view engine) your home page.              *
  *                                                                          *
  * (Alternatively, remove this and add an `index.html` file in your         *
  * `assets` directory)                                                      *
  *                                                                          *
  ***************************************************************************/

  '/': {
    view: 'homepage'
  },

  /***************************************************************************
  *                                                                          *
  * Custom routes here...                                                    *
  *                                                                          *
  *  If a request to a URL doesn't match any of the custom routes above, it  *
  * is matched against Sails route blueprints. See `config/blueprints.js`    *
  * for configuration options and examples.                                  *
  *                                                                          *
  ***************************************************************************/

  /***************************************************************************
   *                                                                         *
   * Passport Auth routes                                                    *
   *                                                                         *
   **************************************************************************/
  'get /login': 'AuthController.login',
  'get /logout': 'AuthController.logout',
  'get /register': 'AuthController.register',

  'post /auth/local': 'AuthController.callback',
  'post /auth/local/:action': 'AuthController.callback',

  'get /auth/:provider': 'AuthController.provider',
  'get /auth/:provider/callback': 'AuthController.callback',
  'get /auth/:provider/:action': 'AuthController.callback'

};
<|endoftext|>