<commit_before>import { Meteor } from 'meteor/meteor'
import { isModern } from "meteor/modern-browsers";
import crypto from 'crypto';
import fs from 'fs';
import path from 'path';

let _disableSizeCheck = false;
let disabledBrowsers = {};

Meteor.AppCache = {
  config: options => {
    Object.keys(options).forEach(option => {
      value = options[option];
      if (option === 'browsers') {
        disabledBrowsers = {};
        value.each(browser => disabledBrowsers[browser] = false);
      }
      else if (option === 'onlineOnly') {
        value.forEach(urlPrefix =>
          RoutePolicy.declare(urlPrefix, 'static-online')
        );
      }
      // option to suppress warnings for tests.
      else if (option === '_disableSizeCheck') {
        _disableSizeCheck = value;
      }
      else if (value === false) {
        disabledBrowsers[option] = true;
      }
      else if (value === true) {
        disabledBrowsers[option] = false;
      } else {
        throw new Error('Invalid AppCache config option: ' + option);
      }
    });
  }
};

const browserDisabled = request => disabledBrowsers[request.browser.name];

const isDynamic = resource =>
  resource.type === 'dynamic js' ||
    (resource.type === 'json' &&
     resource.url.endsWith('.map') &&
     (resource.url.startsWith('/dynamic/') ||
      resource.url.startsWith('/__browser.legacy/dynamic/')));

WebApp.addHtmlAttributeHook(request =>
  browserDisabled(request) ?
    null :
    { manifest: "/app.manifest" }
);

WebApp.connectHandlers.use((req, res, next) => {
  if (req.url !== '/app.manifest') {
    return next();
  }

  const request = WebApp.categorizeRequest(req);

  // Browsers will get confused if we unconditionally serve the
  // manifest and then disable the app cache for that browser.  If
  // the app cache had previously been enabled for a browser, it
  // will continue to fetch the manifest as long as it's available,
  // even if we now are not including the manifest attribute in the
  // app HTML.  (Firefox for example will continue to display "this
  // website is asking to store data on your computer for offline
  // use").  Returning a 404 gets the browser to really turn off the
  // app cache.

  if (browserDisabled(request)) {
    res.writeHead(404);
    res.end();
    return;
  }

  let manifest = "CACHE MANIFEST\n\n";

  // After the browser has downloaded the app files from the server and
  // has populated the browser's application cache, the browser will
  // *only* connect to the server and reload the application if the
  // *contents* of the app manifest file has changed.
  //
  // So to ensure that the client updates if client resources change,
  // include a hash of client resources in the manifest.

  manifest += `# ${WebApp.clientHash()}\n`;

  // When using the autoupdate package, also include
  // AUTOUPDATE_VERSION.  Otherwise the client will get into an
  // infinite loop of reloads when the browser doesn't fetch the new
  // app HTML which contains the new version, and autoupdate will
  // reload again trying to get the new code.

  if (Package.autoupdate) {
    const version = Package.autoupdate.Autoupdate.autoupdateVersion;
    if (version !== WebApp.clientHash())
      manifest += `# ${version}\n`;
  }

  manifest += "\n";

  manifest += "CACHE:\n";
  manifest += "/\n";
  const reqArch = isModern(request.browser) ? 'web.browser' : 'web.browser.legacy';
  WebApp.clientPrograms[reqArch].manifest.forEach(resource => {
    if (resource.where === 'client' &&
        ! RoutePolicy.classify(resource.url) &&
        ! isDynamic(resource)) {
      manifest += resource.url;
      // If the resource is not already cacheable (has a query
      // parameter, presumably with a hash or version of some sort),
      // put a version with a hash in the cache.
      //
      // Avoid putting a non-cacheable asset into the cache, otherwise
      // the user can't modify the asset until the cache headers
      // expire.
      if (!resource.cacheable)
        manifest += `?${resource.hash}`;

      manifest += "\n";
    }
  });
  manifest += "\n";

  manifest += "FALLBACK:\n";
  manifest += "/ /\n";
  // Add a fallback entry for each uncacheable asset we added above.
  //
  // This means requests for the bare url ("/image.png" instead of
  // "/image.png?hash") will work offline. Online, however, the browser
  // will send a request to the server. Users can remove this extra
  // request to the server and have the asset served from cache by
  // specifying the full URL with hash in their code (manually, with
  // some sort of URL rewriting helper)
  WebApp.clientPrograms[reqArch].manifest.forEach(resource => {
    if (resource.where === 'client' &&
        ! RoutePolicy.classify(resource.url) &&
        ! resource.cacheable &&
        ! isDynamic(resource)) {
      manifest += `${resource.url} ${resource.url}?${resource.hash}\n`;
    }
  });

  manifest += "\n";

  manifest += "NETWORK:\n";
  // TODO adding the manifest file to NETWORK should be unnecessary?
  // Want more testing to be sure.
  manifest += "/app.manifest\n";
  [
    ...RoutePolicy.urlPrefixesFor('network'),
    ...RoutePolicy.urlPrefixesFor('static-online')
  ].forEach(urlPrefix => manifest += `${urlPrefix}\n`);
  manifest += "*\n";

  // content length needs to be based on bytes
  const body = Buffer.from(manifest);

  res.setHeader('Content-Type', 'text/cache-manifest');
  res.setHeader('Content-Length', body.length);
  return res.end(body);
});

const sizeCheck = () => WebApp.connectHandlers.use((req, res, next) => {
  let totalSize = 0;
  const request = WebApp.categorizeRequest(req);
  const reqArch = isModern(request.browser) ? 'web.browser' : 'web.browser.legacy';
  WebApp.clientPrograms[reqArch].manifest.forEach(resource => {
    if (resource.where === 'client' &&
        ! RoutePolicy.classify(resource.url) &&
        ! isDynamic(resource)) {
      totalSize += resource.size;
    }
  });
  if (totalSize > 5 * 1024 * 1024) {
    Meteor._debug(
      "** You are using the appcache package but the total size of the\n" +
      "** cached resources is " +
      `${(totalSize / 1024 / 1024).toFixed(1)}MB.\n` +
      "**\n" +
      "** This is over the recommended maximum of 5 MB and may break your\n" +
      "** app in some browsers! See http://docs.meteor.com/#appcache\n" +
      "** for more information and fixes.\n"
    );
  }
});

// Run the size check after user code has had a chance to run. That way,
// the size check can take into account files that the user does not
// want cached. Otherwise, the size check warning will still print even
// if the user excludes their large files with
// `Meteor.AppCache.config({onlineOnly: files})`.
Meteor.startup(() => ! _disableSizeCheck ? sizeCheck() : null);
<commit_msg>Always omit .map files from app.manifest<commit_after>import { Meteor } from 'meteor/meteor'
import { isModern } from "meteor/modern-browsers";
import crypto from 'crypto';
import fs from 'fs';
import path from 'path';

let _disableSizeCheck = false;
let disabledBrowsers = {};

Meteor.AppCache = {
  config: options => {
    Object.keys(options).forEach(option => {
      value = options[option];
      if (option === 'browsers') {
        disabledBrowsers = {};
        value.each(browser => disabledBrowsers[browser] = false);
      }
      else if (option === 'onlineOnly') {
        value.forEach(urlPrefix =>
          RoutePolicy.declare(urlPrefix, 'static-online')
        );
      }
      // option to suppress warnings for tests.
      else if (option === '_disableSizeCheck') {
        _disableSizeCheck = value;
      }
      else if (value === false) {
        disabledBrowsers[option] = true;
      }
      else if (value === true) {
        disabledBrowsers[option] = false;
      } else {
        throw new Error('Invalid AppCache config option: ' + option);
      }
    });
  }
};

const browserDisabled = request => disabledBrowsers[request.browser.name];

const isDynamicOrMap = resource =>
  resource.type === 'dynamic js' ||
    (resource.type === 'json' &&
     resource.url.endsWith('.map'));

WebApp.addHtmlAttributeHook(request =>
  browserDisabled(request) ?
    null :
    { manifest: "/app.manifest" }
);

WebApp.connectHandlers.use((req, res, next) => {
  if (req.url !== '/app.manifest') {
    return next();
  }

  const request = WebApp.categorizeRequest(req);

  // Browsers will get confused if we unconditionally serve the
  // manifest and then disable the app cache for that browser.  If
  // the app cache had previously been enabled for a browser, it
  // will continue to fetch the manifest as long as it's available,
  // even if we now are not including the manifest attribute in the
  // app HTML.  (Firefox for example will continue to display "this
  // website is asking to store data on your computer for offline
  // use").  Returning a 404 gets the browser to really turn off the
  // app cache.

  if (browserDisabled(request)) {
    res.writeHead(404);
    res.end();
    return;
  }

  let manifest = "CACHE MANIFEST\n\n";

  // After the browser has downloaded the app files from the server and
  // has populated the browser's application cache, the browser will
  // *only* connect to the server and reload the application if the
  // *contents* of the app manifest file has changed.
  //
  // So to ensure that the client updates if client resources change,
  // include a hash of client resources in the manifest.

  manifest += `# ${WebApp.clientHash()}\n`;

  // When using the autoupdate package, also include
  // AUTOUPDATE_VERSION.  Otherwise the client will get into an
  // infinite loop of reloads when the browser doesn't fetch the new
  // app HTML which contains the new version, and autoupdate will
  // reload again trying to get the new code.

  if (Package.autoupdate) {
    const version = Package.autoupdate.Autoupdate.autoupdateVersion;
    if (version !== WebApp.clientHash())
      manifest += `# ${version}\n`;
  }

  manifest += "\n";

  manifest += "CACHE:\n";
  manifest += "/\n";
  const reqArch = isModern(request.browser) ? 'web.browser' : 'web.browser.legacy';
  WebApp.clientPrograms[reqArch].manifest.forEach(resource => {
    if (resource.where === 'client' &&
        ! RoutePolicy.classify(resource.url) &&
        ! isDynamicOrMap(resource)) {
      manifest += resource.url;
      // If the resource is not already cacheable (has a query
      // parameter, presumably with a hash or version of some sort),
      // put a version with a hash in the cache.
      //
      // Avoid putting a non-cacheable asset into the cache, otherwise
      // the user can't modify the asset until the cache headers
      // expire.
      if (!resource.cacheable)
        manifest += `?${resource.hash}`;

      manifest += "\n";
    }
  });
  manifest += "\n";

  manifest += "FALLBACK:\n";
  manifest += "/ /\n";
  // Add a fallback entry for each uncacheable asset we added above.
  //
  // This means requests for the bare url ("/image.png" instead of
  // "/image.png?hash") will work offline. Online, however, the browser
  // will send a request to the server. Users can remove this extra
  // request to the server and have the asset served from cache by
  // specifying the full URL with hash in their code (manually, with
  // some sort of URL rewriting helper)
  WebApp.clientPrograms[reqArch].manifest.forEach(resource => {
    if (resource.where === 'client' &&
        ! RoutePolicy.classify(resource.url) &&
        ! resource.cacheable &&
        ! isDynamicOrMap(resource)) {
      manifest += `${resource.url} ${resource.url}?${resource.hash}\n`;
    }
  });

  manifest += "\n";

  manifest += "NETWORK:\n";
  // TODO adding the manifest file to NETWORK should be unnecessary?
  // Want more testing to be sure.
  manifest += "/app.manifest\n";
  [
    ...RoutePolicy.urlPrefixesFor('network'),
    ...RoutePolicy.urlPrefixesFor('static-online')
  ].forEach(urlPrefix => manifest += `${urlPrefix}\n`);
  manifest += "*\n";

  // content length needs to be based on bytes
  const body = Buffer.from(manifest);

  res.setHeader('Content-Type', 'text/cache-manifest');
  res.setHeader('Content-Length', body.length);
  return res.end(body);
});

const sizeCheck = () => WebApp.connectHandlers.use((req, res, next) => {
  let totalSize = 0;
  const request = WebApp.categorizeRequest(req);
  const reqArch = isModern(request.browser) ? 'web.browser' : 'web.browser.legacy';
  WebApp.clientPrograms[reqArch].manifest.forEach(resource => {
    if (resource.where === 'client' &&
        ! RoutePolicy.classify(resource.url) &&
        ! isDynamicOrMap(resource)) {
      totalSize += resource.size;
    }
  });
  if (totalSize > 5 * 1024 * 1024) {
    Meteor._debug(
      "** You are using the appcache package but the total size of the\n" +
      "** cached resources is " +
      `${(totalSize / 1024 / 1024).toFixed(1)}MB.\n` +
      "**\n" +
      "** This is over the recommended maximum of 5 MB and may break your\n" +
      "** app in some browsers! See http://docs.meteor.com/#appcache\n" +
      "** for more information and fixes.\n"
    );
  }
});

// Run the size check after user code has had a chance to run. That way,
// the size check can take into account files that the user does not
// want cached. Otherwise, the size check warning will still print even
// if the user excludes their large files with
// `Meteor.AppCache.config({onlineOnly: files})`.
Meteor.startup(() => ! _disableSizeCheck ? sizeCheck() : null);
<|endoftext|>


<commit_before>'use strict';

var createScatterLine = require('./line-with-markers'),
    tinycolor = require('tinycolor2'),
    arrtools = require('arraytools'),
    arrayCopy1D = arrtools.copy1D,
    calculateError = require('./calc-errors');

function Scatter3D (config) {

    this.config = config;

}

module.exports = Scatter3D;

var proto = Scatter3D.prototype;

proto.attributes = {
    x: {type: 'data_array'},
    y: {type: 'data_array'},
    z: {type: 'data_array'},
    text: {from: 'Scatter'},
    scene: {
        type: 'sceneid',
        dflt: 'scene'
    },
    surfaceaxis: {
        type: 'enumerated',
        values: [-1,0,1,2],
        dflt: -1
    },
    surfacecolor: {
        type: 'color'
    },
    showprojection: [
        {type: 'boolean', dflt: false},
        {type: 'boolean', dflt: false},
        {type: 'boolean', dflt: false}
    ],
    projectopacity: {
        type: 'number',
        min: 0,
        max: 1,
        dflt: 1
    },
    projectscale: {
        type: 'number',
        min: 0,
        max: 10,
        dflt: 2/3
    },
    mode: {from: 'Scatter'},
    line: {
        color: {from: 'Scatter'},
        width: {from: 'Scatter'},
        dash: {from: 'Scatter'}
    },
    marker: {
        symbol: {from: 'Scatter'},
        size: {from: 'Scatter'},
        opacity: {from: 'Scatter'},
        line: {
            color: {from: 'Scatter'},
            width: {from: 'Scatter'}
        }

    },
    textposition: {from: 'Scatter'},
    textfont: {from: 'Scatter'}
};

proto.supplyXYZ = function (traceIn, traceOut) {
    var _this = this;
    var Plotly = this.config.Plotly;

    function coerce(attr, dflt) {
        return Plotly.Lib.coerce(traceIn, traceOut, _this.attributes, attr, dflt);
    }

    var len = 0,
        x = coerce('x'),
        y = coerce('y'),
        z = coerce('z');

    if (x && y && z) {
        len = Math.min(x.length, y.length, z.length);
        if(len<x.length) traceOut.x = x.slice(0, len);
        if(len<y.length) traceOut.y = y.slice(0, len);
        if(len<z.length) traceOut.z = z.slice(0, len);
    }

    return len;
};

proto.supplyDefaults = function (traceIn, traceOut, defaultColor, layout) {
    var _this = this;
    var Plotly = this.config.Plotly;
    var Scatter = Plotly.Scatter;

    function coerce(attr, dflt) {
        return Plotly.Lib.coerce(traceIn, traceOut, _this.attributes, attr, dflt);
    }

    function coerceScatter(attr, dflt) {
        return Plotly.Lib.coerce(traceIn, traceOut, Scatter.attributes, attr, dflt);
    }

    this.supplyXYZ(traceIn, traceOut);

    coerceScatter('mode', 'lines+markers');
    coerceScatter('text');

    if (Scatter.hasLines(traceOut)) {
        coerceScatter('line.color', (traceIn.marker||{}).color || defaultColor);
        coerceScatter('line.width');
        coerceScatter('line.dash');
    }

    if (Scatter.hasMarkers(traceOut)) {
        coerceScatter('marker.color', defaultColor);
        coerceScatter('marker.symbol');
        coerceScatter('marker.size');
        coerceScatter('marker.opacity', 1);
        coerceScatter('marker.line.width', 0);
        // TODO parity with scatter.js
        coerceScatter('marker.line.color', 'rgb(0,0,0)');

    }

    if (Scatter.hasText(traceOut)) {
        coerceScatter('textposition');
        coerceScatter('textfont', layout.font);
    }

    coerce('scene');
    coerce('surfaceaxis');
    coerce('surfacecolor');
    coerce('showprojection[0]');
    coerce('showprojection[1]');
    coerce('showprojection[2]');
    if (traceOut.showprojection.some(isTrue)) {
        coerce('projectopacity');
        coerce('projectscale');
    }

    Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'z'});
    Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y', inherit: 'z'});
    Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'z'});

};

function isTrue (bool) {
    return bool;
}

function calculateErrorCapSize(errors) {
    /*jshint camelcase: false */
    var result = [0.0,0.0,0.0], i, e;
    for(i=0; i<3; ++i) {
        e = errors[i];
        if (e && e.copy_zstyle !== false) {
            e = errors[2];
        }
        if(!e) {
            continue;
        }
        if(e && 'width' in e) {
            result[i] = e.width / 100.0;  //Ballpark rescaling, attempt to make consistent with plot.ly
        }
    }
    return result;
}

function calculateTextOffset(textposition) {
    //Read out text properties
    var textOffset = [0,0];
    if (textposition.indexOf('bottom') >= 0) {
        textOffset[1] += 1;
    }
    if (textposition.indexOf('top') >= 0) {
        textOffset[1] -= 1;
    }
    if (textposition.indexOf('left') >= 0) {
        textOffset[0] -= 1;
    }
    if (textposition.indexOf('right') >= 0) {
        textOffset[0] += 1;
    }
    return textOffset;
}

function str2RgbaArray(color) {
    color = tinycolor(color);
    return arrtools.str2RgbaArray(color.toRgbString());
}

proto.plot = function Scatter (scene, sceneLayout, data) {
    /*jshint camelcase: false */
    /*
     * data object {x,y,z and  marker: {size:size, color:color}}
     */

    // if (!('marker' in data)) data.marker = {};

    var params, scatter, idx, i,
        points = [],
        xaxis = sceneLayout.xaxis,
        yaxis = sceneLayout.yaxis,
        zaxis = sceneLayout.zaxis,
        errorProperties = [ data.error_x, data.error_y, data.error_z ],
        xc, x = data.x,
        yc, y = data.y,
        zc, z = data.z,
        len = x.length;

    //Convert points
    idx = 0;
    for (i = 0; i < len; i++) {
        // sanitize numbers
        xc = xaxis.d2c(x[i]);
        yc = yaxis.d2c(y[i]);
        zc = zaxis.d2c(z[i]);

        // apply any axis transforms
        if (xaxis.type === 'log') xc = xaxis.c2l(xc);
        if (yaxis.type === 'log') yc = yaxis.c2l(yc);
        if (zaxis.type === 'log') zc = zaxis.c2l(zc);

        points[idx] = [xc, yc, zc];
        ++idx;
    }
    if (!points.length) {
        return void 0;
    }

    //Build object parameters
    params = {
        position: points,
        mode:     data.mode
    };

    if ('line' in data) {
        params.lineColor     = str2RgbaArray(data.line.color);
        params.lineWidth     = data.line.width;
        params.lineDashes    = data.line.dash;
    }

    if ('marker' in data) {
        params.scatterColor         = str2RgbaArray(data.marker.color);
        params.scatterColor[3]     *= data.marker.opacity;
        params.scatterSize          = 2*data.marker.size;  // rough parity with Plotly 2D markers
        params.scatterMarker        = this.markerSymbols[data.marker.symbol];
        params.scatterLineWidth     = data.marker.line.width;
        params.scatterLineColor     = str2RgbaArray(data.marker.line.color);
        params.scatterLineColor[3] *= data.marker.opacity;
        params.scatterAngle         = 0;
    }

    if ('error_z' in data) {
        params.errorBounds    = calculateError(data),
        params.errorColor     = errorProperties.map( function (e) {
            return str2RgbaArray(e.color);
        });
        params.errorLineWidth = errorProperties.map( function (e) {
            return e.thickness;
        });
        params.errorCapSize   = calculateErrorCapSize(errorProperties);
    }

    if ('textposition' in data) {
        params.text           = data.text;
        params.textOffset     = calculateTextOffset(data.position);
        params.textColor      = str2RgbaArray(data.textfont.color);
        params.textSize       = data.textfont.size;
        params.textFont       = data.textfont.family;
        params.textAngle      = 0;
    }

    if (data.showprojection) {
        params.project = [true, true, true]; //arrayCopy1D(data.showprojection);
        params.projectOpacity = data.projectopacity;
        params.projectScale = data.projectscale;
    }

    params.delaunayAxis       = data.surfaceaxis;
    params.delaunayColor      = str2RgbaArray(data.surfacecolor);

    scatter = scene.glDataMap[data.uid];

    if (scatter) {
        /*
         * We already have drawn this surface,
         * lets just update it with the latest params
         */
        scatter.update(params);
    } else {
        /*
         * Push it onto the render queue
         */
        params.pickId0   = (scene.objectCount++)%256;
        params.pickId1   = (scene.objectCount++)%256;
        params.pickId2   = (scene.objectCount++)%256;
        params.pickId3   = (scene.objectCount++)%256;
        scatter          = createScatterLine(scene.shell.gl, params);
        scatter.groupId  = (scene.objectCount-1)>>8;
        scatter.plotlyType  = data.type;

        scene.glDataMap[data.uid] = scatter;
    }
    // uids determine which data is tied to which gl-object
    scatter.uid = data.uid;
    scatter.visible = data.visible;
    scene.update(sceneLayout, scatter);
};

proto.markerSymbols = {
    'circle': '●',
    'circle-open': '○',
    'square': '■',
    'square-open': '□',
    'diamond': '◆',
    'diamond-open': '◇',
    'cross': '+',
    'x': '❌'
};
<commit_msg>restore surfacecolor edit buttons<commit_after>'use strict';

var createScatterLine = require('./line-with-markers'),
    tinycolor = require('tinycolor2'),
    arrtools = require('arraytools'),
    arrayCopy1D = arrtools.copy1D,
    calculateError = require('./calc-errors');

function Scatter3D (config) {

    this.config = config;

}

module.exports = Scatter3D;

var proto = Scatter3D.prototype;

proto.attributes = {
    x: {type: 'data_array'},
    y: {type: 'data_array'},
    z: {type: 'data_array'},
    text: {from: 'Scatter'},
    scene: {
        type: 'sceneid',
        dflt: 'scene'
    },
    surfaceaxis: {
        type: 'enumerated',
        values: [-1,0,1,2],
        dflt: -1
    },
    surfacecolor: {
        type: 'color'
    },
    showprojection: [
        {type: 'boolean', dflt: false},
        {type: 'boolean', dflt: false},
        {type: 'boolean', dflt: false}
    ],
    projectopacity: {
        type: 'number',
        min: 0,
        max: 1,
        dflt: 1
    },
    projectscale: {
        type: 'number',
        min: 0,
        max: 10,
        dflt: 2/3
    },
    mode: {from: 'Scatter'},
    line: {
        color: {from: 'Scatter'},
        width: {from: 'Scatter'},
        dash: {from: 'Scatter'}
    },
    marker: {
        symbol: {from: 'Scatter'},
        size: {from: 'Scatter'},
        opacity: {from: 'Scatter'},
        line: {
            color: {from: 'Scatter'},
            width: {from: 'Scatter'}
        }

    },
    textposition: {from: 'Scatter'},
    textfont: {from: 'Scatter'}
};

proto.supplyXYZ = function (traceIn, traceOut) {
    var _this = this;
    var Plotly = this.config.Plotly;

    function coerce(attr, dflt) {
        return Plotly.Lib.coerce(traceIn, traceOut, _this.attributes, attr, dflt);
    }

    var len = 0,
        x = coerce('x'),
        y = coerce('y'),
        z = coerce('z');

    if (x && y && z) {
        len = Math.min(x.length, y.length, z.length);
        if(len<x.length) traceOut.x = x.slice(0, len);
        if(len<y.length) traceOut.y = y.slice(0, len);
        if(len<z.length) traceOut.z = z.slice(0, len);
    }

    return len;
};

proto.supplyDefaults = function (traceIn, traceOut, defaultColor, layout) {
    var _this = this;
    var Plotly = this.config.Plotly;
    var Scatter = Plotly.Scatter;

    function coerce(attr, dflt) {
        return Plotly.Lib.coerce(traceIn, traceOut, _this.attributes, attr, dflt);
    }

    function coerceScatter(attr, dflt) {
        return Plotly.Lib.coerce(traceIn, traceOut, Scatter.attributes, attr, dflt);
    }

    this.supplyXYZ(traceIn, traceOut);

    coerceScatter('mode', 'lines+markers');
    coerceScatter('text');

    if (Scatter.hasLines(traceOut)) {
        var linecolor = coerceScatter('line.color', (traceIn.marker||{}).color || defaultColor);
        coerceScatter('line.width');
        coerceScatter('line.dash');
    }

    if (Scatter.hasMarkers(traceOut)) {
        var markercolor = coerceScatter('marker.color', defaultColor);
        coerceScatter('marker.symbol');
        coerceScatter('marker.size');
        coerceScatter('marker.opacity', 1);
        coerceScatter('marker.line.width', 0);
        // TODO parity with scatter.js
        coerceScatter('marker.line.color', 'rgb(0,0,0)');

    }

    if (Scatter.hasText(traceOut)) {
        coerceScatter('textposition');
        coerceScatter('textfont', layout.font);
    }

    coerce('scene');
    if (coerce('surfaceaxis') >= 0) coerce('surfacecolor', linecolor || markercolor);
    coerce('showprojection[0]');
    coerce('showprojection[1]');
    coerce('showprojection[2]');
    if (traceOut.showprojection.some(isTrue)) {
        coerce('projectopacity');
        coerce('projectscale');
    }

    Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'z'});
    Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y', inherit: 'z'});
    Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'z'});

};

function isTrue (bool) {
    return bool;
}

function calculateErrorCapSize(errors) {
    /*jshint camelcase: false */
    var result = [0.0,0.0,0.0], i, e;
    for(i=0; i<3; ++i) {
        e = errors[i];
        if (e && e.copy_zstyle !== false) {
            e = errors[2];
        }
        if(!e) {
            continue;
        }
        if(e && 'width' in e) {
            result[i] = e.width / 100.0;  //Ballpark rescaling, attempt to make consistent with plot.ly
        }
    }
    return result;
}

function calculateTextOffset(textposition) {
    //Read out text properties
    var textOffset = [0,0];
    if (textposition.indexOf('bottom') >= 0) {
        textOffset[1] += 1;
    }
    if (textposition.indexOf('top') >= 0) {
        textOffset[1] -= 1;
    }
    if (textposition.indexOf('left') >= 0) {
        textOffset[0] -= 1;
    }
    if (textposition.indexOf('right') >= 0) {
        textOffset[0] += 1;
    }
    return textOffset;
}

function str2RgbaArray(color) {
    color = tinycolor(color);
    return arrtools.str2RgbaArray(color.toRgbString());
}

proto.plot = function Scatter (scene, sceneLayout, data) {
    /*jshint camelcase: false */
    /*
     * data object {x,y,z and  marker: {size:size, color:color}}
     */

    // if (!('marker' in data)) data.marker = {};

    var params, scatter, idx, i,
        points = [],
        xaxis = sceneLayout.xaxis,
        yaxis = sceneLayout.yaxis,
        zaxis = sceneLayout.zaxis,
        errorProperties = [ data.error_x, data.error_y, data.error_z ],
        xc, x = data.x,
        yc, y = data.y,
        zc, z = data.z,
        len = x.length;

    //Convert points
    idx = 0;
    for (i = 0; i < len; i++) {
        // sanitize numbers
        xc = xaxis.d2c(x[i]);
        yc = yaxis.d2c(y[i]);
        zc = zaxis.d2c(z[i]);

        // apply any axis transforms
        if (xaxis.type === 'log') xc = xaxis.c2l(xc);
        if (yaxis.type === 'log') yc = yaxis.c2l(yc);
        if (zaxis.type === 'log') zc = zaxis.c2l(zc);

        points[idx] = [xc, yc, zc];
        ++idx;
    }
    if (!points.length) {
        return void 0;
    }

    //Build object parameters
    params = {
        position: points,
        mode:     data.mode
    };

    if ('line' in data) {
        params.lineColor     = str2RgbaArray(data.line.color);
        params.lineWidth     = data.line.width;
        params.lineDashes    = data.line.dash;
    }

    if ('marker' in data) {
        params.scatterColor         = str2RgbaArray(data.marker.color);
        params.scatterColor[3]     *= data.marker.opacity;
        params.scatterSize          = 2*data.marker.size;  // rough parity with Plotly 2D markers
        params.scatterMarker        = this.markerSymbols[data.marker.symbol];
        params.scatterLineWidth     = data.marker.line.width;
        params.scatterLineColor     = str2RgbaArray(data.marker.line.color);
        params.scatterLineColor[3] *= data.marker.opacity;
        params.scatterAngle         = 0;
    }

    if ('error_z' in data) {
        params.errorBounds    = calculateError(data),
        params.errorColor     = errorProperties.map( function (e) {
            return str2RgbaArray(e.color);
        });
        params.errorLineWidth = errorProperties.map( function (e) {
            return e.thickness;
        });
        params.errorCapSize   = calculateErrorCapSize(errorProperties);
    }

    if ('textposition' in data) {
        params.text           = data.text;
        params.textOffset     = calculateTextOffset(data.position);
        params.textColor      = str2RgbaArray(data.textfont.color);
        params.textSize       = data.textfont.size;
        params.textFont       = data.textfont.family;
        params.textAngle      = 0;
    }

    if (data.showprojection) {
        params.project = [true, true, true]; //arrayCopy1D(data.showprojection);
        params.projectOpacity = data.projectopacity;
        params.projectScale = data.projectscale;
    }

    params.delaunayAxis       = data.surfaceaxis;
    params.delaunayColor      = str2RgbaArray(data.surfacecolor);

    scatter = scene.glDataMap[data.uid];

    if (scatter) {
        /*
         * We already have drawn this surface,
         * lets just update it with the latest params
         */
        scatter.update(params);
    } else {
        /*
         * Push it onto the render queue
         */
        params.pickId0   = (scene.objectCount++)%256;
        params.pickId1   = (scene.objectCount++)%256;
        params.pickId2   = (scene.objectCount++)%256;
        params.pickId3   = (scene.objectCount++)%256;
        scatter          = createScatterLine(scene.shell.gl, params);
        scatter.groupId  = (scene.objectCount-1)>>8;
        scatter.plotlyType  = data.type;

        scene.glDataMap[data.uid] = scatter;
    }
    // uids determine which data is tied to which gl-object
    scatter.uid = data.uid;
    scatter.visible = data.visible;
    scene.update(sceneLayout, scatter);
};

proto.markerSymbols = {
    'circle': '●',
    'circle-open': '○',
    'square': '■',
    'square-open': '□',
    'diamond': '◆',
    'diamond-open': '◇',
    'cross': '+',
    'x': '❌'
};
<|endoftext|>


<commit_before>/*
steps to creating a bookmark:

 - bookmarks.bookmark(tabId) is called
 - webview_preload.js sends an ipc to webviews.js
 - webviews.js detects the channel is "bookmarksData", and calls bookmarks.onDataRecieved(data)
 - The worker creates a bookmark, and adds it to the search index

*/

var bookmarks = {
	authBookmarkTab: null,
	updateHistory: function (tabId) {
		var tab = tabs.get(tabId);
		var data = {
			url: tab.url,
			title: tab.title,
			color: tab.backgroundColor,
		}
		bookmarks.historyWorker.postMessage({
			action: "updateHistory",
			data: data
		});

	},
	currentCallback: function () {},
	onDataRecieved: function (data) {
		//we can't trust that the data we get from webview_preload.js isn't malicious. Because of this, when we call bookmarks.bookmark(), we set authBookmarkTab to the bookmarked tab id. Then, we check if the url we get back actually matches the url of the tabtab we want to bookmark. This way, we know that the user actually wants to bookmark this url.
		if (!bookmarks.authBookmarkTab || getWebview(bookmarks.authBookmarkTab)[0].getURL() != data.url) {
			throw new Error("Bookmark operation is unauthoritized.");
		}

		data.title = getWebview(bookmarks.authBookmarkTab)[0].getTitle();
		bookmarks.bookmarksWorker.postMessage({
			action: "addBookmark",
			data: data
		})
		bookmarks.authBookmarkTab = null;
	},
	deleteBookmark: function (url) {
		bookmarks.bookmarksWorker.postMessage({
			action: "deleteBookmark",
			data: {
				url: url
			}
		});
	},
	deleteHistory: function (url) {
		bookmarks.historyWorker.postMessage({
			action: "deleteHistory",
			data: {
				url: url
			}
		});
	},
	searchBookmarks: function (text, callback) {
		bookmarks.currentCallback = callback; //save for later, we run in onMessage
		bookmarks.bookmarksWorker.postMessage({
			action: "searchBookmarks",
			text: text,
		});
	},
	searchHistory: function (text, callback) {
		bookmarks.currentHistoryCallback = callback; //save for later, we run in onMessage
		bookmarks.historyWorker.postMessage({
			action: "searchHistory",
			text: text,
		});
	},
	onMessage: function (e) { //assumes this is from a search operation
		if (e.data.scope == "bookmarks") {
			//TODO this (and the rest) should use unique callback id's
			bookmarks.currentCallback(e.data.result);
		} else if (e.data.scope == "history") { //history search
			bookmarks.currentHistoryCallback(e.data.result);
		}
	},
	bookmark: function (tabId) {

		bookmarks.authBookmarkTab = tabId;
		getWebview(tabId)[0].send("sendData");
		//rest happens in onDataRecieved and worker
	},
	toggleBookmarked: function (tabId) { //toggles a bookmark. If it is bookmarked, delete the bookmark. Otherwise, add it.
		var url = tabs.get(tabId).url,
			exists = false;

		bookmarks.searchBookmarks(url, function (d) {

			d.forEach(function (item) {
				if (item.url == url) {
					exists = true;
				}
			});

			if (exists) {
				console.log("deleting bookmark " + tabs.get(tabId).url);
				bookmarks.deleteBookmark(tabs.get(tabId).url);
			} else {
				bookmarks.bookmark(tabId);
			}
		});
	},
	getStar: function (tabId) {
		//alternative icon is fa-bookmark

		var star = $("<i class='fa fa-star-o bookmarks-button theme-text-color'>").attr("data-tab", tabId);

		star.on("click", function (e) {
			$(this).toggleClass("fa-star").toggleClass("fa-star-o");

			bookmarks.toggleBookmarked($(this).attr("data-tab"));
		});

		return bookmarks.renderStar(tabId, star);
	},
	renderStar: function (tabId, star) { //star is optional
		star = star || $(".bookmarks-button[data-tab={id}]".replace("{id}", tabId));

		var currentURL = tabs.get(tabId).url;

		if (!currentURL || currentURL == "about:blank") { //no url, can't be bookmarked
			star.prop("hidden", true);
		} else {
			star.prop("hidden", false);
		}

		//check if the page is bookmarked or not, and update the star to match

		bookmarks.searchBookmarks(currentURL, function (results) {
			if (results && results[0] && results[0].url == currentURL) {
				star.removeClass("fa-star-o").addClass("fa-star");
			} else {
				star.removeClass("fa-star").addClass("fa-star-o");
			}
		});
		return star;
	},
	init: function () {
		bookmarks.historyWorker = new Worker("js/bookmarkshistory/historyworker.js");
		bookmarks.historyWorker.onmessage = bookmarks.onMessage;

		bookmarks.bookmarksWorker = new Worker("js/bookmarkshistory/bookmarksworker.js");
		bookmarks.bookmarksWorker.onmessage = bookmarks.onMessage;
	},

}

bookmarks.init();
<commit_msg>bug fix<commit_after>/*
steps to creating a bookmark:

 - bookmarks.bookmark(tabId) is called
 - webview_preload.js sends an ipc to webviews.js
 - webviews.js detects the channel is "bookmarksData", and calls bookmarks.onDataRecieved(data)
 - The worker creates a bookmark, and adds it to the search index

*/

var bookmarks = {
	authBookmarkTab: null,
	updateHistory: function (tabId) {
		setTimeout(function () { //this prevents pages that are immediately left from being saved to history, and also gives the page-favicon-updated event time to fire (so the colors saved to history are correct).
			var tab = tabs.get(tabId);
			if (tab) {
				var data = {
					url: tab.url,
					title: tab.title,
					color: tab.backgroundColor,
				}
				bookmarks.historyWorker.postMessage({
					action: "updateHistory",
					data: data
				});
			}

		}, 2000);
	},
	currentCallback: function () {},
	onDataRecieved: function (data) {
		//we can't trust that the data we get from webview_preload.js isn't malicious. Because of this, when we call bookmarks.bookmark(), we set authBookmarkTab to the bookmarked tab id. Then, we check if the url we get back actually matches the url of the tabtab we want to bookmark. This way, we know that the user actually wants to bookmark this url.
		if (!bookmarks.authBookmarkTab || getWebview(bookmarks.authBookmarkTab)[0].getURL() != data.url) {
			throw new Error("Bookmark operation is unauthoritized.");
		}

		data.title = getWebview(bookmarks.authBookmarkTab)[0].getTitle();
		bookmarks.bookmarksWorker.postMessage({
			action: "addBookmark",
			data: data
		})
		bookmarks.authBookmarkTab = null;
	},
	deleteBookmark: function (url) {
		bookmarks.bookmarksWorker.postMessage({
			action: "deleteBookmark",
			data: {
				url: url
			}
		});
	},
	deleteHistory: function (url) {
		bookmarks.historyWorker.postMessage({
			action: "deleteHistory",
			data: {
				url: url
			}
		});
	},
	searchBookmarks: function (text, callback) {
		bookmarks.currentCallback = callback; //save for later, we run in onMessage
		bookmarks.bookmarksWorker.postMessage({
			action: "searchBookmarks",
			text: text,
		});
	},
	searchHistory: function (text, callback) {
		bookmarks.currentHistoryCallback = callback; //save for later, we run in onMessage
		bookmarks.historyWorker.postMessage({
			action: "searchHistory",
			text: text,
		});
	},
	onMessage: function (e) { //assumes this is from a search operation
		if (e.data.scope == "bookmarks") {
			//TODO this (and the rest) should use unique callback id's
			bookmarks.currentCallback(e.data.result);
		} else if (e.data.scope == "history") { //history search
			bookmarks.currentHistoryCallback(e.data.result);
		}
	},
	bookmark: function (tabId) {

		bookmarks.authBookmarkTab = tabId;
		getWebview(tabId)[0].send("sendData");
		//rest happens in onDataRecieved and worker
	},
	toggleBookmarked: function (tabId) { //toggles a bookmark. If it is bookmarked, delete the bookmark. Otherwise, add it.
		var url = tabs.get(tabId).url,
			exists = false;

		bookmarks.searchBookmarks(url, function (d) {

			d.forEach(function (item) {
				if (item.url == url) {
					exists = true;
				}
			});

			if (exists) {
				console.log("deleting bookmark " + tabs.get(tabId).url);
				bookmarks.deleteBookmark(tabs.get(tabId).url);
			} else {
				bookmarks.bookmark(tabId);
			}
		});
	},
	getStar: function (tabId) {
		//alternative icon is fa-bookmark

		var star = $("<i class='fa fa-star-o bookmarks-button theme-text-color'>").attr("data-tab", tabId);

		star.on("click", function (e) {
			$(this).toggleClass("fa-star").toggleClass("fa-star-o");

			bookmarks.toggleBookmarked($(this).attr("data-tab"));
		});

		return bookmarks.renderStar(tabId, star);
	},
	renderStar: function (tabId, star) { //star is optional
		star = star || $(".bookmarks-button[data-tab={id}]".replace("{id}", tabId));

		var currentURL = tabs.get(tabId).url;

		if (!currentURL || currentURL == "about:blank") { //no url, can't be bookmarked
			star.prop("hidden", true);
		} else {
			star.prop("hidden", false);
		}

		//check if the page is bookmarked or not, and update the star to match

		bookmarks.searchBookmarks(currentURL, function (results) {
			if (results && results[0] && results[0].url == currentURL) {
				star.removeClass("fa-star-o").addClass("fa-star");
			} else {
				star.removeClass("fa-star").addClass("fa-star-o");
			}
		});
		return star;
	},
	init: function () {
		bookmarks.historyWorker = new Worker("js/bookmarkshistory/historyworker.js");
		bookmarks.historyWorker.onmessage = bookmarks.onMessage;

		bookmarks.bookmarksWorker = new Worker("js/bookmarkshistory/bookmarksworker.js");
		bookmarks.bookmarksWorker.onmessage = bookmarks.onMessage;
	},

}

bookmarks.init();
<|endoftext|>


<commit_before>'use strict';

/**
 * Browser tabs and related controls
 *
 * @param hackBrowserWindow
 * @constructor
 */
function BrowserTabBar(hackBrowserWindow) {
	var _this = this;

	/* ====================================
	 private member variables
	 ====================================== */
	var browserTabsWrapperEl;
	var addTabBtnEl;

	/* ====================================
	 private methods
	 ====================================== */
	var init = function() {
		browserTabsWrapperEl = document.getElementById("navtabs");
		addTabBtnEl = document.getElementById("add-tab");

		attachEventHandlers();
	};

	/**
	 * attach event handlers for menu bar buttons
	 */
	var attachEventHandlers = function() {
		addTabBtnEl.addEventListener("click", function(e) {
			hackBrowserWindow.addNewTab(null, true);

			e.preventDefault();
		});
	};

	/* ====================================
	 public methods
	 ====================================== */
	/**
	 * Create and add a new tab
	 */
	_this.createTab = function(tabViewId, title) {
		var newTab = new BrowserTab(hackBrowserWindow, tabViewId, title);
		var newTabEl = newTab.getTabEl();

		browserTabsWrapperEl.insertBefore(newTabEl, addTabBtnEl);

		return newTab;
	};

	_this.removeTab = function(tabViewId) {
		var tabEl = browserTabsWrapperEl.querySelector("[data-webview-id='" + tabViewId + "']");

		// find the index of tab being closed
		var tabIndex = Array.prototype.indexOf.call(browserTabsWrapperEl.querySelectorAll(".tab"), tabEl);

		hackBrowserWindow.handleTabCloseById(tabViewId, tabIndex);

		browserTabsWrapperEl.removeChild(tabEl);

		var tabIdToActivate;

		var openTabsElArr = browserTabsWrapperEl.querySelectorAll(".tab");

		if (tabIndex >= openTabsElArr.length) {
			tabIdToActivate = openTabsElArr[tabIndex - 1].dataset.webviewId;
		} else {
			tabIdToActivate = openTabsElArr[tabIndex].dataset.webviewId;
		}

		hackBrowserWindow.activateTabById(tabIdToActivate);
	};

	init();
}<commit_msg>Fix unused argument passed to handleTabCloseById()<commit_after>'use strict';

/**
 * Browser tabs and related controls
 *
 * @param hackBrowserWindow
 * @constructor
 */
function BrowserTabBar(hackBrowserWindow) {
	var _this = this;

	/* ====================================
	 private member variables
	 ====================================== */
	var browserTabsWrapperEl;
	var addTabBtnEl;

	/* ====================================
	 private methods
	 ====================================== */
	var init = function() {
		browserTabsWrapperEl = document.getElementById("navtabs");
		addTabBtnEl = document.getElementById("add-tab");

		attachEventHandlers();
	};

	/**
	 * attach event handlers for menu bar buttons
	 */
	var attachEventHandlers = function() {
		addTabBtnEl.addEventListener("click", function(e) {
			hackBrowserWindow.addNewTab(null, true);

			e.preventDefault();
		});
	};

	/* ====================================
	 public methods
	 ====================================== */
	/**
	 * Create and add a new tab
	 */
	_this.createTab = function(tabViewId, title) {
		var newTab = new BrowserTab(hackBrowserWindow, tabViewId, title);
		var newTabEl = newTab.getTabEl();

		browserTabsWrapperEl.insertBefore(newTabEl, addTabBtnEl);

		return newTab;
	};

	_this.removeTab = function(tabViewId) {
		var tabEl = browserTabsWrapperEl.querySelector("[data-webview-id='" + tabViewId + "']");

		// find the index of tab being closed
		// var tabIndex = Array.prototype.indexOf.call(browserTabsWrapperEl.querySelectorAll(".tab"), tabEl);

		// hackBrowserWindow.handleTabCloseById(tabViewId, tabIndex);
		hackBrowserWindow.handleTabCloseById(tabViewId);

		browserTabsWrapperEl.removeChild(tabEl);

		var tabIdToActivate;

		var openTabsElArr = browserTabsWrapperEl.querySelectorAll(".tab");

		if (tabIndex >= openTabsElArr.length) {
			tabIdToActivate = openTabsElArr[tabIndex - 1].dataset.webviewId;
		} else {
			tabIdToActivate = openTabsElArr[tabIndex].dataset.webviewId;
		}

		hackBrowserWindow.activateTabById(tabIdToActivate);
	};

	init();
}
<|endoftext|>


<commit_before>com.magadanski.mSimpleNav.Compass;

//////////////////////////////////////
// Compass Class Implementation //////
//////////////////////////////////////
(function () {
	// import Class
	var that;
	var Compass = function () {
		that = this;

		if (window.DeviceOrientationEvent) {
			window.addEventListener('deviceorientation', function (e) {
				if (e.absolute) {
					compassHeading = -e.alpha;
				} else if (typeof(e.webkitCompassHeading) != 'undefined') {
					compassHeading = e.webkitCompassHeading;
				}

				tilt = e[tiltProperty] * tiltQuantifier;

				that.dispatchEvent('compassupdate', {
					message: 'compass data has changed',
					deviceBearing: compassHeading,
					deviceTilt: tilt
				});
			});
		}

		window.addEventListener('orientationchange', function (e) {
			if (window.orientation == 90 || window.orientation == -90) {
				tiltProperty = 'gamma';
			} else {
				tiltProperty = 'beta';
			}

			if (window.orientation > 0) {
				tiltQuantifier = -1;
			} else {
				tiltQuantifier = 1;
			}
		});
	}
	Compass.inherits(com.magadanski.EventDispatcher);
	com.magadanski.mSimpleNav.Compass = Compass;

	// private properties
	var compassHeading = 0,
		tilt = 0,
		tiltProperty = 'beta',
		tiltQuantifier = 1;

	// public properties

	// private methods

	// public methods
	Compass.prototype.getDeviceBearing = function () {
		return compassHeading;
	}

	Compass.prototype.getDeviceTilt = function () {
		return tilt;
	}
})();
<commit_msg>access fixes for Compass class<commit_after>com.magadanski.mSimpleNav.Compass;

//////////////////////////////////////
// Compass Class Implementation //////
//////////////////////////////////////
(function () {
	// import Class
	var Compass = function () {
		var that = this;

		if (window.DeviceOrientationEvent) {
			window.addEventListener('deviceorientation', function (e) {
				if (e.absolute) {
					that.compassHeading = -e.alpha;
				} else if (typeof(e.webkitCompassHeading) != 'undefined') {
					that.compassHeading = e.webkitCompassHeading;
				}

				that.tilt = e[that.tiltProperty] * that.tiltQuantifier;

				that.dispatchEvent('compassupdate', {
					message: 'compass data has changed',
					deviceBearing: that.compassHeading,
					deviceTilt: that.tilt
				});
			});
		}

		window.addEventListener('orientationchange', function (e) {
			if (window.orientation == 90 || window.orientation == -90) {
				that.tiltProperty = 'gamma';
			} else {
				that.tiltProperty = 'beta';
			}

			if (window.orientation > 0) {
				that.tiltQuantifier = -1;
			} else {
				that.tiltQuantifier = 1;
			}
		});
	}
	Compass.inherits(com.magadanski.EventDispatcher);
	com.magadanski.mSimpleNav.Compass = Compass;

	// private properties

	// public properties
	Compass.prototype.compassHeading = 0;
	Compass.prototype.tilt = 0;
	Compass.prototype.tiltProperty = 'beta';
	Compass.prototype.tiltQuantifier = 1;

	// private methods

	// public methods
	Compass.prototype.getDeviceBearing = function () {
		var that = this;

		return that.compassHeading;
	}

	Compass.prototype.getDeviceTilt = function () {
		var that = this;

		return that.tilt;
	}
})();
<|endoftext|>


<commit_before>dojo.provide("dijit.tests._data.SlowStore");

dojo.require("dojo.data.ItemFileReadStore");

dojo.declare("dijit.tests._data.SlowStore", dojo.data.ItemFileReadStore, {
	// summary:
	//		This wrapper decorates an ItemFileReadStorere by delaying queries issued according to the
	//		length of the query:
	//
	//			empty query: 2000ms,
	//			1 or 2 characters: 1000ms,
	//			3 characters: 500ms,
	//			4 or more characters: 100ms.
	//

	constructor: function(){
		this.log = [];
	},

	fetch: function(/* Object */ keywordArgs){
		// Get the query phrase (store into first), and the # of chars it has
		var count = 0;
		var first;
		if("query" in keywordArgs){
			var query = keywordArgs.query;
			for(var attr in query){
				first = query[attr];
				break;
			}
			if(dojo.isString(first)){
					count = first.length;
			}
		}

		var delay = 100;
		switch(count || 0){
			case 0:
				delay = 2000;
				break;
			case 1:
			case 2:
				delay = 1000;
				break;
			case 3:
				delay = 500;
				break;
			case 4:
				delay = 100;
				break;
		}

		this.log.push({
			type: "start",
			date: new Date(),
			query: query,
			count: count,
			delay: delay
		});
		console.log("START query on " + (first || "{}") + " (" + count + " chars), delay = " + delay);

		var that = this,
			thatArgs = arguments;
		setTimeout(function(){
			that.log.push({
				type: "end",
				date: new Date(),
				query: query,
				count: count,
				delay: delay
			});
			console.log("END query on " + (first || "{}") + " (" + count + " chars), delay = " + delay);
			dojo.data.ItemFileReadStore.prototype.fetch.apply(that, thatArgs);
		}, delay);
	}
});
<commit_msg>Add abort() to my testing data store, as per data store spec.   Refs #8950.<commit_after>dojo.provide("dijit.tests._data.SlowStore");

dojo.require("dojo.data.ItemFileReadStore");

dojo.declare("dijit.tests._data.SlowStore", dojo.data.ItemFileReadStore, {
	// summary:
	//		This wrapper decorates an ItemFileReadStorere by delaying queries issued according to the
	//		length of the query:
	//
	//			empty query: 2000ms,
	//			1 or 2 characters: 1000ms,
	//			3 characters: 500ms,
	//			4 or more characters: 100ms.
	//

	constructor: function(){
		this.log = [];
	},

	fetch: function(/* Object */ keywordArgs){
		// Get the query phrase (store into first), and the # of chars it has
		var count = 0;
		var first;
		if("query" in keywordArgs){
			var query = keywordArgs.query;
			for(var attr in query){
				first = query[attr];
				break;
			}
			if(dojo.isString(first)){
					count = first.length;
			}
		}

		var delay = 100;
		switch(count || 0){
			case 0:
				delay = 2000;
				break;
			case 1:
			case 2:
				delay = 1000;
				break;
			case 3:
				delay = 500;
				break;
			case 4:
				delay = 100;
				break;
		}

		this.log.push({
			type: "start",
			date: new Date(),
			query: query,
			count: count,
			delay: delay
		});
		console.log("START query on " + (first || "{}") + " (" + count + " chars), delay = " + delay);

		var that = this,
			thatArgs = arguments;
		var handle = setTimeout(function(){
			that.log.push({
				type: "end",
				date: new Date(),
				query: query,
				count: count,
				delay: delay
			});
			console.log("END query on " + (first || "{}") + " (" + count + " chars), delay = " + delay);
			dojo.data.ItemFileReadStore.prototype.fetch.apply(that, thatArgs);
		}, delay);

		// This abort() method cancels a request before it has even been sent to ItemFileReadStore.
		// (Since ItemFileReadStore has already loaded the data (as per code in the test file),
		// it operates synchronously; there is never a case to send the cancel request to that object)
		keywordArgs.abort = function(){
			clearTimeout(handle);
			console.log("CANCEL query on " + (first || "{}") + " (" + count + " chars), delay = " + delay);
		};

		return keywordArgs;
	}
});
<|endoftext|>


<commit_before><commit_msg>Finished missing change for member_active<commit_after><|endoftext|>


<commit_before>/*jslint indent: 2, nomen: true, maxlen: 100 */
/*global _, require, db, exports */

// `Codename Frank` is a classy way to create APIs and simple web applications
// from within **ArangoDB**.
// It is inspired by Sinatra, the classy Ruby web framework. If Frank is Sinatra,
// [ArangoDB Actions](http://www.arangodb.org/manuals/current/UserManualActions.html)
// are the corresponding `Rack`. They provide all the HTTP goodness.
//
// Please be aware of the following:
//
// > From now on Frank is only a codename.
// > But don't tell it to anyone.
// > Because it is a codename, you know.
//
// So let's get started, shall we?

// Frank uses Underscore internally. This library is wonderful.
var Frank,
  BaseMiddleware,
  _ = require("underscore"),
  db = require("org/arangodb").db,
  internal = {};

// ArangoDB uses a certain structure we refer to as `UrlObject`.
// With the following function (which is only internal, and not
// exported) you can create an UrlObject with a given URL,
// a constraint and a method. For example:
//
//     internal.createUrlObject('/rat/pack', null, 'get')
internal.createUrlObject = function (url, constraint, method) {
  'use strict';
  var urlObject = {};

  if (!_.isString(url)) {
    throw "URL has to be a String";
  }

  urlObject.match = url;
  urlObject.methods = [method];

  if (!_.isNull(constraint)) {
    urlObject.constraint = constraint;
  }

  return urlObject;
};

// ## Creating a new application
// And that's Frank. He's a constructor, so call him like this:
//
//     app = new Frank({
//       urlPrefix: "/classyroutes",
//       templateCollection: "my_templates"
//     })
//
// It takes two optional arguments as displayed above:
//
// * **The URL Prefix:** All routes you define within will be prefixed with it
// * **The Template Collection:** More information in the template section
Frank = function (options) {
  'use strict';
  var urlPrefix, templateCollection;

  options = options || {};

  urlPrefix = options.urlPrefix;
  templateCollection = options.templateCollection;

  this.routingInfo = {
    routes: []
  };

  if (_.isString(urlPrefix)) {
    this.routingInfo.urlPrefix = urlPrefix;
  }

  if (_.isString(templateCollection)) {
    this.routingInfo.templateCollection = db._collection(templateCollection) ||
      db._create(templateCollection);
  }
};

// ## The functions on a created Frank
//
// When you have created your Frank you can now define routes
// on it.
_.extend(Frank.prototype, {
  // The `handleRequest` method is the raw way to create a new
  // route. You probably wont call it directly, but it is used
  // in the other request methods:
  //
  //     app.handleRequest("get", "/come/fly/with/me", function (req, res) {
  //       //handle the request
  //     });
  handleRequest: function (method, route, argument1, argument2) {
    'use strict';
    var newRoute = {}, options, callback;

    if (_.isUndefined(argument2)) {
      callback = argument1;
      options = {};
    } else {
      options = argument1;
      callback = argument2;
    }

    newRoute.url = internal.createUrlObject(route, options.constraint, method);
    newRoute.action = {
      callback: String(callback)
    };

    this.routingInfo.routes.push(newRoute);
  },

  // ### Handle a `head` request
  // This handles requests from the HTTP verb `head`.
  // As with all other requests you can give an option as the third
  // argument, or leave it blank. You have to give a function as
  // the last argument however. It will get a request and response
  // object as its arguments
  head: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("head", route, argument1, argument2);
  },

  // ### Handle a `get` request
  // This handles requests from the HTTP verb `get`.
  // See above for the arguments you can give. An example:
  //
  //     app.get('/high/society', function (req, res) {
  //       // Take this request and deal with it!
  //     });
  get: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("get", route, argument1, argument2);
  },

  // ### Handle a `post` request
  // This handles requests from the HTTP verb `post`.
  // See above for the arguments you can give. An example:
  //
  //     app.post('/high/society', function (req, res) {
  //       // Take this request and deal with it!
  //     });
  post: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("post", route, argument1, argument2);
  },

  // ### Handle a `put` request
  // This handles requests from the HTTP verb `put`.
  // See above for the arguments you can give. An example:
  //
  //     app.put('/high/society', function (req, res) {
  //       // Take this request and deal with it!
  //     });
  put: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("put", route, argument1, argument2);
  },

  // ### Handle a `patch` request
  // This handles requests from the HTTP verb `patch`.
  // See above for the arguments you can give. An example:
  //
  //     app.patch('/high/society', function (req, res) {
  //       // Take this request and deal with it!
  //     });
  patch: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("patch", route, argument1, argument2);
  },

  // ### Handle a `delete` request
  // This handles requests from the HTTP verb `delete`.
  // See above for the arguments you can give.
  // **A word of warning:** Do not forget that `delete` is
  // a reserved word in JavaScript so call it as follows:
  //
  //     app['delete']('/high/society', function (req, res) {
  //       // Take this request and deal with it!
  //     });
  'delete': function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("delete", route, argument1, argument2);
  }
});

// ## The Base Middleware
// The `BaseMiddleware` manipulates the request and response
// objects to give you a nicer API.
BaseMiddleware = function (templateCollection) {
  'use strict';
  var middleware = function (request, response, options, next) {
    var responseFunctions;

    // ### The Response Functions
    // Those are the functions added to the response objects.
    responseFunctions = {
      // ### The straightforward `status` function
      // Set the status code of your response, for example:
      //
      //     response.status(404);
      status: function (code) {
        this.responseCode = code;
      },

      // ### The radical `set` function
      // Set a header attribute, for example:
      //
      //     response.set("Content-Length", 123);
      //     response.set("Content-Type", "text/plain");
      //
      // or alternatively:
      //
      //     response.set({
      //       "Content-Length": "123",
      //       "Content-Type": "text/plain"
      //     });
      set: function (key, value) {
        var attributes = {};
        if (_.isUndefined(value)) {
          attributes = key;
        } else {
          attributes[key] = value;
        }

        _.each(attributes, function (value, key) {
          key = key.toLowerCase();
          this.headers = this.headers || {};
          this.headers[key] = value;

          if (key === "content-type") {
            this.contentType = value;
          }
        }, this);
      },

      // ### The magical `json` function
      // Set the content type to JSON and the body to the
      // JSON encoded object you provided.
      //
      //     response.json({'born': 'December 12, 1915'});
      json: function (obj) {
        this.contentType = "application/json";
        this.body = JSON.stringify(obj);
      },

      // ### The mysterious `render` function
      // If you initialize your Frank with a `templateCollection`,
      // you're in luck now.
      // It expects documents in the following form in this collection:
      //
      //     {
      //       path: "my/way",
      //       content: "hallo <%= username %>",
      //       contentType: "text/plain",
      //       templateLanguage: "underscore"
      //     }
      //
      // The `content` is the string that will be rendered by the template
      // processor. The `contentType` is the type of content that results
      // from this call. And with the `templateLanguage` you can choose
      // your template processor. There is only one choice now: `underscore`.
      //
      // If you call render, Frank will look
      // into the this collection and search by the path attribute.
      // It will then render the template with the given data:
      //
      //     response.render("my/way", {username: 'Frank'})
      //
      // Which would set the body of the response to `hello Frank` with the
      // template defined above. It will also set the `contentType` to
      // `text/plain` in this case.
      render: function (templatePath, data) {
        var template;

        if (_.isUndefined(templateCollection)) {
          throw "No template collection has been provided when creating a new Frank";
        }

        template = templateCollection.firstExample({path: templatePath });

        if (_.isNull(template)) {
          throw "Template '" + templatePath + "' does not exist";
        }

        if (template.templateLanguage !== "underscore") {
          throw "Unknown template language '" + template.templateLanguage + "'";
        }

        this.body = _.template(template.content, data);
        this.contentType = template.contentType;
      }
    };

    // Now enhance the response as described above and call next at the
    // end of this middleware (otherwise your application would never
    // be executed. Would be a shame, really).
    response = _.extend(response, responseFunctions);
    next();
  };

  return middleware;
};

// We finish off with exporting Frank and the BaseMiddleware.
// Everything else will remain our secret.
//
// Fin.
exports.Frank = Frank;
exports.BaseMiddleware = BaseMiddleware;
<commit_msg>Frank: Some clarifications about Request and Response.<commit_after>/*jslint indent: 2, nomen: true, maxlen: 100 */
/*global _, require, db, exports */

// `Codename Frank` is a classy way to create APIs and simple web applications
// from within **ArangoDB**.
// It is inspired by Sinatra, the classy Ruby web framework. If Frank is Sinatra,
// [ArangoDB Actions](http://www.arangodb.org/manuals/current/UserManualActions.html)
// are the corresponding `Rack`. They provide all the HTTP goodness.
//
// Please be aware of the following:
//
// > From now on Frank is only a codename.
// > But don't tell it to anyone.
// > Because it is a codename, you know.
//
// So let's get started, shall we?

// Frank uses Underscore internally. This library is wonderful.
var Frank,
  BaseMiddleware,
  _ = require("underscore"),
  db = require("org/arangodb").db,
  internal = {};

// ArangoDB uses a certain structure we refer to as `UrlObject`.
// With the following function (which is only internal, and not
// exported) you can create an UrlObject with a given URL,
// a constraint and a method. For example:
//
//     internal.createUrlObject('/rat/pack', null, 'get')
internal.createUrlObject = function (url, constraint, method) {
  'use strict';
  var urlObject = {};

  if (!_.isString(url)) {
    throw "URL has to be a String";
  }

  urlObject.match = url;
  urlObject.methods = [method];

  if (!_.isNull(constraint)) {
    urlObject.constraint = constraint;
  }

  return urlObject;
};

// ## Creating a new application
// And that's Frank. He's a constructor, so call him like this:
//
//     app = new Frank({
//       urlPrefix: "/classyroutes",
//       templateCollection: "my_templates"
//     })
//
// It takes two optional arguments as displayed above:
//
// * **The URL Prefix:** All routes you define within will be prefixed with it
// * **The Template Collection:** More information in the template section
Frank = function (options) {
  'use strict';
  var urlPrefix, templateCollection;

  options = options || {};

  urlPrefix = options.urlPrefix;
  templateCollection = options.templateCollection;

  this.routingInfo = {
    routes: []
  };

  if (_.isString(urlPrefix)) {
    this.routingInfo.urlPrefix = urlPrefix;
  }

  if (_.isString(templateCollection)) {
    this.routingInfo.templateCollection = db._collection(templateCollection) ||
      db._create(templateCollection);
  }
};

// ## The functions on a created Frank
//
// When you have created your Frank you can now define routes
// on it. You provide each with a function that will handle
// the request. It gets two arguments (four, to be honest. But the
// other two are not relevant for now):
//
// * The request object
// * The response object.
//
// You can find more about those in their individual sections.
_.extend(Frank.prototype, {
  // The `handleRequest` method is the raw way to create a new
  // route. You probably wont call it directly, but it is used
  // in the other request methods:
  //
  //     app.handleRequest("get", "/come/fly/with/me", function (req, res) {
  //       //handle the request
  //     });
  handleRequest: function (method, route, argument1, argument2) {
    'use strict';
    var newRoute = {}, options, callback;

    if (_.isUndefined(argument2)) {
      callback = argument1;
      options = {};
    } else {
      options = argument1;
      callback = argument2;
    }

    newRoute.url = internal.createUrlObject(route, options.constraint, method);
    newRoute.action = {
      callback: String(callback)
    };

    this.routingInfo.routes.push(newRoute);
  },

  // ### Handle a `head` request
  // This handles requests from the HTTP verb `head`.
  // As with all other requests you can give an option as the third
  // argument, or leave it blank. You have to give a function as
  // the last argument however. It will get a request and response
  // object as its arguments
  head: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("head", route, argument1, argument2);
  },

  // ### Handle a `get` request
  // This handles requests from the HTTP verb `get`.
  // See above for the arguments you can give. An example:
  //
  //     app.get('/high/society', function (req, res) {
  //       // Take this request and deal with it!
  //     });
  get: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("get", route, argument1, argument2);
  },

  // ### Handle a `post` request
  // This handles requests from the HTTP verb `post`.
  // See above for the arguments you can give. An example:
  //
  //     app.post('/high/society', function (req, res) {
  //       // Take this request and deal with it!
  //     });
  post: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("post", route, argument1, argument2);
  },

  // ### Handle a `put` request
  // This handles requests from the HTTP verb `put`.
  // See above for the arguments you can give. An example:
  //
  //     app.put('/high/society', function (req, res) {
  //       // Take this request and deal with it!
  //     });
  put: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("put", route, argument1, argument2);
  },

  // ### Handle a `patch` request
  // This handles requests from the HTTP verb `patch`.
  // See above for the arguments you can give. An example:
  //
  //     app.patch('/high/society', function (req, res) {
  //       // Take this request and deal with it!
  //     });
  patch: function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("patch", route, argument1, argument2);
  },

  // ### Handle a `delete` request
  // This handles requests from the HTTP verb `delete`.
  // See above for the arguments you can give.
  // **A word of warning:** Do not forget that `delete` is
  // a reserved word in JavaScript so call it as follows:
  //
  //     app['delete']('/high/society', function (req, res) {
  //       // Take this request and deal with it!
  //     });
  'delete': function (route, argument1, argument2) {
    'use strict';
    this.handleRequest("delete", route, argument1, argument2);
  }
});

// ## The Base Middleware
// The `BaseMiddleware` manipulates the request and response
// objects to give you a nicer API.
BaseMiddleware = function (templateCollection) {
  'use strict';
  var middleware = function (request, response, options, next) {
    var responseFunctions, requestFunctions;

    // ### The Request Object
    // Every request object has the following attributes from the underlying Actions:
    //
    // * path: The complete path as supplied by the user
    // * prefix
    // * suffix
    // * urlParameters
    //
    // Frank currently leaves those unmodified.
    responseFunctions = {};

    // ### The Response Object
    // Every response object has the following attributes from the underlying Actions:
    //
    // * contentType
    // * responseCode
    // * body
    // * headers (an object)
    //
    // Frank adds the following methods to this response object.
    responseFunctions = {
      // ### The straightforward `status` function
      // Set the status code of your response, for example:
      //
      //     response.status(404);
      status: function (code) {
        this.responseCode = code;
      },

      // ### The radical `set` function
      // Set a header attribute, for example:
      //
      //     response.set("Content-Length", 123);
      //     response.set("Content-Type", "text/plain");
      //
      // or alternatively:
      //
      //     response.set({
      //       "Content-Length": "123",
      //       "Content-Type": "text/plain"
      //     });
      set: function (key, value) {
        var attributes = {};
        if (_.isUndefined(value)) {
          attributes = key;
        } else {
          attributes[key] = value;
        }

        _.each(attributes, function (value, key) {
          key = key.toLowerCase();
          this.headers = this.headers || {};
          this.headers[key] = value;

          if (key === "content-type") {
            this.contentType = value;
          }
        }, this);
      },

      // ### The magical `json` function
      // Set the content type to JSON and the body to the
      // JSON encoded object you provided.
      //
      //     response.json({'born': 'December 12, 1915'});
      json: function (obj) {
        this.contentType = "application/json";
        this.body = JSON.stringify(obj);
      },

      // ### The mysterious `render` function
      // If you initialize your Frank with a `templateCollection`,
      // you're in luck now.
      // It expects documents in the following form in this collection:
      //
      //     {
      //       path: "my/way",
      //       content: "hallo <%= username %>",
      //       contentType: "text/plain",
      //       templateLanguage: "underscore"
      //     }
      //
      // The `content` is the string that will be rendered by the template
      // processor. The `contentType` is the type of content that results
      // from this call. And with the `templateLanguage` you can choose
      // your template processor. There is only one choice now: `underscore`.
      //
      // If you call render, Frank will look
      // into the this collection and search by the path attribute.
      // It will then render the template with the given data:
      //
      //     response.render("my/way", {username: 'Frank'})
      //
      // Which would set the body of the response to `hello Frank` with the
      // template defined above. It will also set the `contentType` to
      // `text/plain` in this case.
      render: function (templatePath, data) {
        var template;

        if (_.isUndefined(templateCollection)) {
          throw "No template collection has been provided when creating a new Frank";
        }

        template = templateCollection.firstExample({path: templatePath });

        if (_.isNull(template)) {
          throw "Template '" + templatePath + "' does not exist";
        }

        if (template.templateLanguage !== "underscore") {
          throw "Unknown template language '" + template.templateLanguage + "'";
        }

        this.body = _.template(template.content, data);
        this.contentType = template.contentType;
      }
    };

    // Now enhance the request and response as described above and call next at the
    // end of this middleware (otherwise your application would never
    // be executed. Would be a shame, really).
    request = _.extend(request, requestFunctions);
    response = _.extend(response, responseFunctions);
    next();
  };

  return middleware;
};

// We finish off with exporting Frank and the BaseMiddleware.
// Everything else will remain our secret.
//
// Fin.
exports.Frank = Frank;
exports.BaseMiddleware = BaseMiddleware;
<|endoftext|>


<commit_before>function getRandomIndex(arr) {
  return Math.floor(Math.random() * arr.length);
}
function getRandomValue(arr) {
  return arr[getRandomIndex(arr)];
}

function getLast(arr) {
  return arr[arr.length - 1];
}

async function readFileAsText(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = function(e) {
      resolve(e.target.result);
    };
    reader.onerror = function(e) {
      reject(e);
    };

    reader.readAsText(file);
  });
}

/**
 * Track a collection of async loaded presets
 *
 * Presets can be changed via `next`, `previous` or `selectIndex`. In each case,
 * a promise is returned. If the promise resolves to `null` it means the
 * selection was canceled by a subsequent request before it could be fulfilled.
 * If the request is successful, the promise resolves to the selected preset.
 *
 * We assume a model where some portion of the preset are supplied at initialization
 * and the remainder can be loaded async via the function `getRest`.
 */
export default class Presets {
  constructor({ keys, initialPresets, getRest, randomize = true }) {
    this._keys = keys; // Alphabetical list of preset names
    this._presets = initialPresets; // Presets indexed by name
    this._getRest = getRest; // An async function to get the rest of the presets
    this._history = []; // Indexes into _keys

    this._randomize = randomize;

    // Initialize with a key that we already have.
    const avaliableKeys = Object.keys(initialPresets);
    const currentKey = randomize
      ? getRandomValue(avaliableKeys)
      : avaliableKeys[0];
    this._currentIndex = this._keys.indexOf(currentKey);
    this._history.push(this._currentIndex);
  }

  toggleRandomize() {
    this._randomize = !this._randomize;
  }

  setRandomize(val) {
    this._randomize = val;
  }

  addPresets(presets) {
    const startIdx = this._keys.length;
    this._keys = this._keys.concat(Object.keys(presets));
    const endIndx = this._keys.length;

    this._presets = Object.assign(this._presets, presets);

    return [startIdx, endIndx];
  }

  async next() {
    let idx;
    if (this._randomize || this._history.length === 0) {
      idx = getRandomIndex(this._keys);
    } else {
      idx = (getLast(this._history) + 1) % this._keys.length;
    }
    this._history.push(idx);
    return this._selectIndex(idx);
  }

  async previous() {
    if (this._history.length > 1) {
      this._history.pop();
      return this._selectIndex(getLast(this._history));
    }
    // We are at the very beginning. There is no "previous" preset.
    return Promise.resolve();
  }

  async selectIndex(idx) {
    // The public version of this method must add to the history
    this._history.push(idx);
    return this._selectIndex(idx);
  }

  async _convertPreset(file) {
    return new Promise((resolve, reject) => {
      require.ensure(
        ["milkdrop-preset-converter-aws"],
        async require => {
          const { convertPreset } = require("milkdrop-preset-converter-aws");
          try {
            resolve(convertPreset(file));
          } catch (e) {
            reject(e);
          }
        },
        reject,
        "milkdrop-preset-converter"
      );
    });
  }

  async _selectIndex(idx) {
    const preset = this._presets[this._keys[idx]];
    if (!preset) {
      const rest = await this._getRest();
      this._presets = Object.assign(this._presets, rest);
      if (getLast(this._history) !== idx) {
        // This selection must be obsolete. Return null so that
        // the caller knows this request got canceled.
        return null;
      }
    }
    if (preset && preset.file) {
      try {
        const fileContents = await readFileAsText(preset.file);
        const convertedPreset = await this._convertPreset(fileContents);
        this._presets[this._keys[idx]] = convertedPreset;
      } catch (e) {
        alert(`Unable to convert MilkDrop preset ${this._keys[idx]}`);
      }
    }
    this._currentIndex = idx;
    return this.getCurrent();
  }

  getKeys() {
    return this._keys;
  }

  getCurrentIndex() {
    return this._currentIndex;
  }

  getCurrent() {
    // #matryoshka
    return this._presets[this._keys[this._currentIndex]];
  }
}
<commit_msg>Log Milkdrop preset conversion errors to console<commit_after>function getRandomIndex(arr) {
  return Math.floor(Math.random() * arr.length);
}
function getRandomValue(arr) {
  return arr[getRandomIndex(arr)];
}

function getLast(arr) {
  return arr[arr.length - 1];
}

async function readFileAsText(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = function(e) {
      resolve(e.target.result);
    };
    reader.onerror = function(e) {
      reject(e);
    };

    reader.readAsText(file);
  });
}

/**
 * Track a collection of async loaded presets
 *
 * Presets can be changed via `next`, `previous` or `selectIndex`. In each case,
 * a promise is returned. If the promise resolves to `null` it means the
 * selection was canceled by a subsequent request before it could be fulfilled.
 * If the request is successful, the promise resolves to the selected preset.
 *
 * We assume a model where some portion of the preset are supplied at initialization
 * and the remainder can be loaded async via the function `getRest`.
 */
export default class Presets {
  constructor({ keys, initialPresets, getRest, randomize = true }) {
    this._keys = keys; // Alphabetical list of preset names
    this._presets = initialPresets; // Presets indexed by name
    this._getRest = getRest; // An async function to get the rest of the presets
    this._history = []; // Indexes into _keys

    this._randomize = randomize;

    // Initialize with a key that we already have.
    const avaliableKeys = Object.keys(initialPresets);
    const currentKey = randomize
      ? getRandomValue(avaliableKeys)
      : avaliableKeys[0];
    this._currentIndex = this._keys.indexOf(currentKey);
    this._history.push(this._currentIndex);
  }

  toggleRandomize() {
    this._randomize = !this._randomize;
  }

  setRandomize(val) {
    this._randomize = val;
  }

  addPresets(presets) {
    const startIdx = this._keys.length;
    this._keys = this._keys.concat(Object.keys(presets));
    const endIndx = this._keys.length;

    this._presets = Object.assign(this._presets, presets);

    return [startIdx, endIndx];
  }

  async next() {
    let idx;
    if (this._randomize || this._history.length === 0) {
      idx = getRandomIndex(this._keys);
    } else {
      idx = (getLast(this._history) + 1) % this._keys.length;
    }
    this._history.push(idx);
    return this._selectIndex(idx);
  }

  async previous() {
    if (this._history.length > 1) {
      this._history.pop();
      return this._selectIndex(getLast(this._history));
    }
    // We are at the very beginning. There is no "previous" preset.
    return Promise.resolve();
  }

  async selectIndex(idx) {
    // The public version of this method must add to the history
    this._history.push(idx);
    return this._selectIndex(idx);
  }

  async _convertPreset(file) {
    return new Promise((resolve, reject) => {
      require.ensure(
        ["milkdrop-preset-converter-aws"],
        async require => {
          const { convertPreset } = require("milkdrop-preset-converter-aws");
          try {
            resolve(convertPreset(file));
          } catch (e) {
            reject(e);
          }
        },
        reject,
        "milkdrop-preset-converter"
      );
    });
  }

  async _selectIndex(idx) {
    const preset = this._presets[this._keys[idx]];
    if (!preset) {
      const rest = await this._getRest();
      this._presets = Object.assign(this._presets, rest);
      if (getLast(this._history) !== idx) {
        // This selection must be obsolete. Return null so that
        // the caller knows this request got canceled.
        return null;
      }
    }
    if (preset && preset.file) {
      try {
        const fileContents = await readFileAsText(preset.file);
        const convertedPreset = await this._convertPreset(fileContents);
        this._presets[this._keys[idx]] = convertedPreset;
      } catch (e) {
        console.error(e);
        alert(`Unable to convert MilkDrop preset ${this._keys[idx]}`);
      }
    }
    this._currentIndex = idx;
    return this.getCurrent();
  }

  getKeys() {
    return this._keys;
  }

  getCurrentIndex() {
    return this._currentIndex;
  }

  getCurrent() {
    // #matryoshka
    return this._presets[this._keys[this._currentIndex]];
  }
}
<|endoftext|>


<commit_before>/*global define*/
define([
        '../Core/defaultValue',
        '../Core/DeveloperError',
        '../Core/Math',
        '../Core/BoundingSphere',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartographic',
        '../Core/Ellipsoid',
        '../Core/ExtentTessellator',
        '../Core/Occluder',
        '../Core/PlaneTessellator',
        '../Core/TaskProcessor',
        './TerrainProvider',
        './TileState',
        './GeographicTilingScheme',
        '../ThirdParty/when'
    ], function(
        defaultValue,
        DeveloperError,
        CesiumMath,
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        Cartographic,
        Ellipsoid,
        ExtentTessellator,
        Occluder,
        PlaneTessellator,
        TaskProcessor,
        TerrainProvider,
        TileState,
        GeographicTilingScheme,
        when) {
    "use strict";

    /**
     * A very simple {@link TerrainProvider} that produces geometry by tessellating an ellipsoidal
     * surface.
     *
     * @alias EllipsoidTerrainProvider
     * @constructor
     * @private
     *
     * @param {TilingScheme} [description.tilingScheme] The tiling scheme specifying how the ellipsoidal
     * surface is broken into tiles.  If this parameter is not provided, a {@link GeographicTilingScheme}
     * is used.
     * @param {Ellipsoid} [description.ellipsoid] The ellipsoid.  If the tilingScheme is specified,
     * this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
     * parameter is specified, the WGS84 ellipsoid is used.
     *
     * @see TerrainProvider
     */
    function EllipsoidTerrainProvider(description) {
        description = defaultValue(description, {});

        /**
         * The tiling scheme used to tile the surface.
         *
         * @type TilingScheme
         */
        this.tilingScheme = defaultValue(description.tilingScheme, new GeographicTilingScheme({ ellipsoid : defaultValue(description.ellipsoid, Ellipsoid.WGS84) }));

        // Note: the 64 below does NOT need to match the actual vertex dimensions.
        this.levelZeroMaximumGeometricError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(this.tilingScheme.getEllipsoid(), 64, this.tilingScheme.getNumberOfXTilesAtLevel(0));

        this.ready = true;
    }

    /**
     * Gets the maximum geometric error allowed in a tile at a given level.
     *
     * @param {Number} level The tile level for which to get the maximum geometric error.
     * @returns {Number} The maximum geometric error.
     */
    EllipsoidTerrainProvider.prototype.getLevelMaximumGeometricError = TerrainProvider.prototype.getLevelMaximumGeometricError;

    /**
     * Request the tile geometry from the remote server.  Once complete, the
     * tile state should be set to RECEIVED.  Alternatively, tile state can be set to
     * UNLOADED to indicate that the request should be attempted again next update, if the tile
     * is still needed.
     *
     * @param {Tile} The tile to request geometry for.
     */
    EllipsoidTerrainProvider.prototype.requestTileGeometry = function(tile) {
        tile.state = TileState.RECEIVED;
    };

    var taskProcessor = new TaskProcessor('createVerticesFromExtent');

    /**
     * Transform the tile geometry from the format requested from the remote server
     * into a format suitable for resource creation.  Once complete, the tile
     * state should be set to TRANSFORMED.  Alternatively, tile state can be set to
     * RECEIVED to indicate that the transformation should be attempted again next update, if the tile
     * is still needed.
     *
     * @param {Context} context The context to use to create resources.
     * @param {Tile} tile The tile to transform geometry for.
     */
    EllipsoidTerrainProvider.prototype.transformGeometry = function(context, tile) {
        var tilingScheme = this.tilingScheme;
        var ellipsoid = tilingScheme.getEllipsoid();
        var extent = tile.extent;

        tile.center = ellipsoid.cartographicToCartesian(extent.getCenter());

        var width = 16;
        var height = 16;

        var verticesPromise = taskProcessor.scheduleTask({
            extent : extent,
            altitude : 0,
            width : width,
            height : height,
            relativeToCenter : tile.center,
            radiiSquared : ellipsoid.getRadiiSquared()
        });

        if (typeof verticesPromise === 'undefined') {
            //postponed
            tile.state = TileState.RECEIVED;
            return;
        }

        when(verticesPromise, function(result) {
            tile.transientData = {
                vertices : result,
                indices : TerrainProvider.getRegularGridIndices(width, height)
            };
            tile.state = TileState.TRANSFORMED;
        }, function(e) {
            /*global console*/
            console.error('failed to transform geometry: ' + e);
            tile.state = TileState.FAILED;
        });
    };

    var scratch = new Cartesian3();

    /**
     * Create WebGL resources for the tile using whatever data the transformGeometry step produced.
     * Once complete, the tile state should be set to READY.  Alternatively, tile state can be set to
     * TRANSFORMED to indicate that resource creation should be attempted again next update, if the tile
     * is still needed.
     *
     * @param {Context} context The context to use to create resources.
     * @param {Tile} tile The tile to create resources for.
     */
    EllipsoidTerrainProvider.prototype.createResources = function(context, tile) {
        var buffers = tile.transientData;
        tile.transientData = undefined;

        TerrainProvider.createTileEllipsoidGeometryFromBuffers(context, tile, buffers);
        tile.maxHeight = 0;
        tile.boundingSphere3D = BoundingSphere.fromPointsAsFlatArray(buffers.vertices, tile.center, 5);

        var ellipsoid = this.tilingScheme.getEllipsoid();
        var extent = tile.extent;
        tile.southwestCornerCartesian = ellipsoid.cartographicToCartesian(extent.getSouthwest());
        tile.southeastCornerCartesian = ellipsoid.cartographicToCartesian(extent.getSoutheast());
        tile.northeastCornerCartesian = ellipsoid.cartographicToCartesian(extent.getNortheast());
        tile.northwestCornerCartesian = ellipsoid.cartographicToCartesian(extent.getNorthwest());

        tile.westNormal = Cartesian3.UNIT_Z.cross(tile.southwestCornerCartesian.negate(scratch), scratch).normalize();
        tile.eastNormal = tile.northeastCornerCartesian.negate(scratch).cross(Cartesian3.UNIT_Z, scratch).normalize();
        tile.southNormal = ellipsoid.geodeticSurfaceNormal(tile.southeastCornerCartesian).cross(tile.southwestCornerCartesian.subtract(tile.southeastCornerCartesian, scratch)).normalize();
        tile.northNormal = ellipsoid.geodeticSurfaceNormal(tile.northwestCornerCartesian).cross(tile.northeastCornerCartesian.subtract(tile.northwestCornerCartesian, scratch)).normalize();

        var occludeePoint = Occluder.computeOccludeePointFromExtent(tile.extent, ellipsoid);
        if (typeof occludeePoint !== 'undefined') {
            Cartesian3.multiplyComponents(occludeePoint, ellipsoid.getOneOverRadii(), occludeePoint);
        }
        tile.occludeePoint = occludeePoint;

        tile.state = TileState.READY;
    };

    return EllipsoidTerrainProvider;
});<commit_msg>Fix horizon culling.<commit_after>/*global define*/
define([
        '../Core/defaultValue',
        '../Core/DeveloperError',
        '../Core/Math',
        '../Core/BoundingSphere',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartographic',
        '../Core/Ellipsoid',
        '../Core/ExtentTessellator',
        '../Core/Occluder',
        '../Core/PlaneTessellator',
        '../Core/TaskProcessor',
        './TerrainProvider',
        './TileState',
        './GeographicTilingScheme',
        '../ThirdParty/when'
    ], function(
        defaultValue,
        DeveloperError,
        CesiumMath,
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        Cartographic,
        Ellipsoid,
        ExtentTessellator,
        Occluder,
        PlaneTessellator,
        TaskProcessor,
        TerrainProvider,
        TileState,
        GeographicTilingScheme,
        when) {
    "use strict";

    /**
     * A very simple {@link TerrainProvider} that produces geometry by tessellating an ellipsoidal
     * surface.
     *
     * @alias EllipsoidTerrainProvider
     * @constructor
     * @private
     *
     * @param {TilingScheme} [description.tilingScheme] The tiling scheme specifying how the ellipsoidal
     * surface is broken into tiles.  If this parameter is not provided, a {@link GeographicTilingScheme}
     * is used.
     * @param {Ellipsoid} [description.ellipsoid] The ellipsoid.  If the tilingScheme is specified,
     * this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
     * parameter is specified, the WGS84 ellipsoid is used.
     *
     * @see TerrainProvider
     */
    function EllipsoidTerrainProvider(description) {
        description = defaultValue(description, {});

        /**
         * The tiling scheme used to tile the surface.
         *
         * @type TilingScheme
         */
        this.tilingScheme = defaultValue(description.tilingScheme, new GeographicTilingScheme({ ellipsoid : defaultValue(description.ellipsoid, Ellipsoid.WGS84) }));

        // Note: the 64 below does NOT need to match the actual vertex dimensions.
        this.levelZeroMaximumGeometricError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(this.tilingScheme.getEllipsoid(), 64, this.tilingScheme.getNumberOfXTilesAtLevel(0));

        this.ready = true;
    }

    /**
     * Gets the maximum geometric error allowed in a tile at a given level.
     *
     * @param {Number} level The tile level for which to get the maximum geometric error.
     * @returns {Number} The maximum geometric error.
     */
    EllipsoidTerrainProvider.prototype.getLevelMaximumGeometricError = TerrainProvider.prototype.getLevelMaximumGeometricError;

    /**
     * Request the tile geometry from the remote server.  Once complete, the
     * tile state should be set to RECEIVED.  Alternatively, tile state can be set to
     * UNLOADED to indicate that the request should be attempted again next update, if the tile
     * is still needed.
     *
     * @param {Tile} The tile to request geometry for.
     */
    EllipsoidTerrainProvider.prototype.requestTileGeometry = function(tile) {
        tile.state = TileState.RECEIVED;
    };

    var taskProcessor = new TaskProcessor('createVerticesFromExtent');

    /**
     * Transform the tile geometry from the format requested from the remote server
     * into a format suitable for resource creation.  Once complete, the tile
     * state should be set to TRANSFORMED.  Alternatively, tile state can be set to
     * RECEIVED to indicate that the transformation should be attempted again next update, if the tile
     * is still needed.
     *
     * @param {Context} context The context to use to create resources.
     * @param {Tile} tile The tile to transform geometry for.
     */
    EllipsoidTerrainProvider.prototype.transformGeometry = function(context, tile) {
        var tilingScheme = this.tilingScheme;
        var ellipsoid = tilingScheme.getEllipsoid();
        var extent = tile.extent;

        tile.center = ellipsoid.cartographicToCartesian(extent.getCenter());

        var width = 16;
        var height = 16;

        var verticesPromise = taskProcessor.scheduleTask({
            extent : extent,
            altitude : 0,
            width : width,
            height : height,
            relativeToCenter : tile.center,
            radiiSquared : ellipsoid.getRadiiSquared()
        });

        if (typeof verticesPromise === 'undefined') {
            //postponed
            tile.state = TileState.RECEIVED;
            return;
        }

        when(verticesPromise, function(result) {
            tile.transientData = {
                vertices : result,
                indices : TerrainProvider.getRegularGridIndices(width, height)
            };
            tile.state = TileState.TRANSFORMED;
        }, function(e) {
            /*global console*/
            console.error('failed to transform geometry: ' + e);
            tile.state = TileState.FAILED;
        });
    };

    var scratch = new Cartesian3();

    /**
     * Create WebGL resources for the tile using whatever data the transformGeometry step produced.
     * Once complete, the tile state should be set to READY.  Alternatively, tile state can be set to
     * TRANSFORMED to indicate that resource creation should be attempted again next update, if the tile
     * is still needed.
     *
     * @param {Context} context The context to use to create resources.
     * @param {Tile} tile The tile to create resources for.
     */
    EllipsoidTerrainProvider.prototype.createResources = function(context, tile) {
        var buffers = tile.transientData;
        tile.transientData = undefined;

        TerrainProvider.createTileEllipsoidGeometryFromBuffers(context, tile, buffers);
        tile.maxHeight = 0;
        tile.boundingSphere3D = BoundingSphere.fromPointsAsFlatArray(buffers.vertices, tile.center, 5);

        var ellipsoid = this.tilingScheme.getEllipsoid();
        var extent = tile.extent;
        tile.southwestCornerCartesian = ellipsoid.cartographicToCartesian(extent.getSouthwest());
        tile.southeastCornerCartesian = ellipsoid.cartographicToCartesian(extent.getSoutheast());
        tile.northeastCornerCartesian = ellipsoid.cartographicToCartesian(extent.getNortheast());
        tile.northwestCornerCartesian = ellipsoid.cartographicToCartesian(extent.getNorthwest());

        tile.westNormal = Cartesian3.UNIT_Z.cross(tile.southwestCornerCartesian.negate(scratch), scratch).normalize();
        tile.eastNormal = tile.northeastCornerCartesian.negate(scratch).cross(Cartesian3.UNIT_Z, scratch).normalize();
        tile.southNormal = ellipsoid.geodeticSurfaceNormal(tile.southeastCornerCartesian).cross(tile.southwestCornerCartesian.subtract(tile.southeastCornerCartesian, scratch)).normalize();
        tile.northNormal = ellipsoid.geodeticSurfaceNormal(tile.northwestCornerCartesian).cross(tile.northeastCornerCartesian.subtract(tile.northwestCornerCartesian, scratch)).normalize();

        var occludeePoint = Occluder.computeOccludeePointFromExtent(tile.extent, ellipsoid);
        if (typeof occludeePoint !== 'undefined') {
            Cartesian3.multiplyComponents(occludeePoint, ellipsoid.getOneOverRadii(), occludeePoint);
        }
        tile.occludeePointInScaledSpace = occludeePoint;

        tile.state = TileState.READY;
    };

    return EllipsoidTerrainProvider;
});<|endoftext|>


<commit_before><commit_msg>Fix bug when fragment data is null<commit_after><|endoftext|>