<commit_before>// Adapted from d3-zoom to handle pointer events.
// https://github.com/d3/d3-zoom/blob/523ccff340187a3e3c044eaa4d4a7391ea97272b/src/zoom.js

import { dispatch as d3_dispatch } from 'd3-dispatch';
import { interpolateZoom } from 'd3-interpolate';
import { event as d3_event, customEvent as d3_customEvent, mouse as d3_mouse } from 'd3-selection';
import { interrupt as d3_interrupt } from 'd3-transition';
import ZoomEvent from '../../node_modules/d3-zoom/src/event.js';
import { Transform, identity } from '../../node_modules/d3-zoom/src/transform.js';

import { utilFunctor } from './util';

// Ignore right-click, since that should open the context menu.
function defaultFilter() {
  return !d3_event.ctrlKey && !d3_event.button;
}

function defaultExtent() {
  var e = this;
  if (e instanceof SVGElement) {
    e = e.ownerSVGElement || e;
    if (e.hasAttribute('viewBox')) {
      e = e.viewBox.baseVal;
      return [[e.x, e.y], [e.x + e.width, e.y + e.height]];
    }
    return [[0, 0], [e.width.baseVal.value, e.height.baseVal.value]];
  }
  return [[0, 0], [e.clientWidth, e.clientHeight]];
}

function defaultTransform() {
  return this.__zoom || identity;
}

function defaultWheelDelta() {
  return -d3_event.deltaY * (d3_event.deltaMode === 1 ? 0.05 : d3_event.deltaMode ? 1 : 0.002);
}

function defaultConstrain(transform, extent, translateExtent) {
  var dx0 = transform.invertX(extent[0][0]) - translateExtent[0][0],
      dx1 = transform.invertX(extent[1][0]) - translateExtent[1][0],
      dy0 = transform.invertY(extent[0][1]) - translateExtent[0][1],
      dy1 = transform.invertY(extent[1][1]) - translateExtent[1][1];
  return transform.translate(
    dx1 > dx0 ? (dx0 + dx1) / 2 : Math.min(0, dx0) || Math.max(0, dx1),
    dy1 > dy0 ? (dy0 + dy1) / 2 : Math.min(0, dy0) || Math.max(0, dy1)
  );
}

export function utilZoomPan() {
  var filter = defaultFilter,
      extent = defaultExtent,
      constrain = defaultConstrain,
      wheelDelta = defaultWheelDelta,
      scaleExtent = [0, Infinity],
      translateExtent = [[-Infinity, -Infinity], [Infinity, Infinity]],
      duration = 250,
      interpolate = interpolateZoom,
      listeners = d3_dispatch('start', 'zoom', 'end'),
      wheelDelay = 150;

  function zoom(selection) {
    selection
        .property('__zoom', defaultTransform)
        .on('pointerdown.zoom', pointerdown)
        .on('pointermove.zoom', pointermove)
        .on('pointerup.zoom pointercancel.zoom', pointerup)
        .on('wheel.zoom', wheeled)
        .style('touch-action', 'none')
        .style('-webkit-tap-highlight-color', 'rgba(0,0,0,0)');
  }

  zoom.transform = function(collection, transform, point) {
    var selection = collection.selection ? collection.selection() : collection;
    selection.property('__zoom', defaultTransform);
    if (collection !== selection) {
      schedule(collection, transform, point);
    } else {
      selection.interrupt().each(function() {
        gesture(this, arguments)
            .start()
            .zoom(null, typeof transform === 'function' ? transform.apply(this, arguments) : transform)
            .end();
      });
    }
  };

  zoom.scaleBy = function(selection, k, p) {
    zoom.scaleTo(selection, function() {
      var k0 = this.__zoom.k,
          k1 = typeof k === 'function' ? k.apply(this, arguments) : k;
      return k0 * k1;
    }, p);
  };

  zoom.scaleTo = function(selection, k, p) {
    zoom.transform(selection, function() {
      var e = extent.apply(this, arguments),
          t0 = this.__zoom,
          p0 = p == null ? centroid(e) : typeof p === 'function' ? p.apply(this, arguments) : p,
          p1 = t0.invert(p0),
          k1 = typeof k === 'function' ? k.apply(this, arguments) : k;
      return constrain(translate(scale(t0, k1), p0, p1), e, translateExtent);
    }, p);
  };

  zoom.translateBy = function(selection, x, y) {
    zoom.transform(selection, function() {
      return constrain(this.__zoom.translate(
        typeof x === 'function' ? x.apply(this, arguments) : x,
        typeof y === 'function' ? y.apply(this, arguments) : y
      ), extent.apply(this, arguments), translateExtent);
    });
  };

  zoom.translateTo = function(selection, x, y, p) {
    zoom.transform(selection, function() {
      var e = extent.apply(this, arguments),
          t = this.__zoom,
          p0 = p == null ? centroid(e) : typeof p === 'function' ? p.apply(this, arguments) : p;
      return constrain(identity.translate(p0[0], p0[1]).scale(t.k).translate(
        typeof x === 'function' ? -x.apply(this, arguments) : -x,
        typeof y === 'function' ? -y.apply(this, arguments) : -y
      ), e, translateExtent);
    }, p);
  };

  function scale(transform, k) {
    k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], k));
    return k === transform.k ? transform : new Transform(k, transform.x, transform.y);
  }

  function translate(transform, p0, p1) {
    var x = p0[0] - p1[0] * transform.k, y = p0[1] - p1[1] * transform.k;
    return x === transform.x && y === transform.y ? transform : new Transform(transform.k, x, y);
  }

  function centroid(extent) {
    return [(+extent[0][0] + +extent[1][0]) / 2, (+extent[0][1] + +extent[1][1]) / 2];
  }

  function schedule(transition, transform, point) {
    transition
        .on('start.zoom', function() { gesture(this, arguments).start(); })
        .on('interrupt.zoom end.zoom', function() { gesture(this, arguments).end(); })
        .tween('zoom', function() {
          var that = this,
              args = arguments,
              g = gesture(that, args),
              e = extent.apply(that, args),
              p = point == null ? centroid(e) : typeof point === 'function' ? point.apply(that, args) : point,
              w = Math.max(e[1][0] - e[0][0], e[1][1] - e[0][1]),
              a = that.__zoom,
              b = typeof transform === 'function' ? transform.apply(that, args) : transform,
              i = interpolate(a.invert(p).concat(w / a.k), b.invert(p).concat(w / b.k));
          return function(t) {
            if (t === 1) t = b; // Avoid rounding error on end.
            else { var l = i(t), k = w / l[2]; t = new Transform(k, p[0] - l[0] * k, p[1] - l[1] * k); }
            g.zoom(null, t);
          };
        });
  }

  function gesture(that, args, clean) {
    return (!clean && that.__zooming) || new Gesture(that, args);
  }

  function Gesture(that, args) {
    this.that = that;
    this.args = args;
    this.active = 0;
    this.extent = extent.apply(that, args);
  }

  Gesture.prototype = {
    start: function() {
      if (++this.active === 1) {
        this.that.__zooming = this;
        this.emit('start');
      }
      return this;
    },
    zoom: function(key, transform) {
      if (this.mouse && key !== 'mouse') this.mouse[1] = transform.invert(this.mouse[0]);
      if (this.touch0 && key !== 'touch') this.touch0[1] = transform.invert(this.touch0[0]);
      if (this.touch1 && key !== 'touch') this.touch1[1] = transform.invert(this.touch1[0]);
      this.that.__zoom = transform;
      this.emit('zoom');
      return this;
    },
    end: function() {
      if (--this.active === 0) {
        delete this.that.__zooming;
        this.emit('end');
      }
      return this;
    },
    emit: function(type) {
      d3_customEvent(new ZoomEvent(zoom, type, this.that.__zoom), listeners.apply, listeners, [type, this.that, this.args]);
    }
  };

  function wheeled() {
    if (!filter.apply(this, arguments)) return;
    var g = gesture(this, arguments),
        t = this.__zoom,
        k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], t.k * Math.pow(2, wheelDelta.apply(this, arguments)))),
        p = d3_mouse(this);

    // If the mouse is in the same location as before, reuse it.
    // If there were recent wheel events, reset the wheel idle timeout.
    if (g.wheel) {
      if (g.mouse[0][0] !== p[0] || g.mouse[0][1] !== p[1]) {
        g.mouse[1] = t.invert(g.mouse[0] = p);
      }
      clearTimeout(g.wheel);
    }

    // If this wheel event won’t trigger a transform change, ignore it.
    else if (t.k === k) return;

    // Otherwise, capture the mouse point and location at the start.
    else {
      g.mouse = [p, t.invert(p)];
      d3_interrupt(this);
      g.start();
    }

    d3_event.preventDefault();
    d3_event.stopImmediatePropagation();
    g.wheel = setTimeout(wheelidled, wheelDelay);
    g.zoom('mouse', constrain(translate(scale(t, k), g.mouse[0], g.mouse[1]), g.extent, translateExtent));

    function wheelidled() {
      g.wheel = null;
      g.end();
    }
  }

  var downPointerIDs = new Set();

  function pointerdown() {
    downPointerIDs.add(d3_event.pointerId);

    if (!filter.apply(this, arguments)) return;

    var g = gesture(this, arguments, downPointerIDs.size === 1);
    var started;

    d3_event.stopImmediatePropagation();
    var loc = d3_mouse(this);
    var p = [loc, this.__zoom.invert(loc), d3_event.pointerId];
    if (!g.touch0) {
       g.touch0 = p;
       started = true;

    } else if (!g.touch1 && g.touch0[2] !== p[2]) {
       g.touch1 = p;
    }

    if (started) {
      d3_interrupt(this);
      g.start();
    }
  }

  function pointermove() {
    if (!this.__zooming) return;

    var loc = d3_mouse(this);

    var g = gesture(this, arguments);
    var t, p, l;

    d3_event.preventDefault();
    d3_event.stopImmediatePropagation();

    if (g.touch0 && g.touch0[2] === d3_event.pointerId) g.touch0[0] = loc;
    else if (g.touch1 && g.touch1[2] === d3_event.pointerId) g.touch1[0] = loc;

    t = g.that.__zoom;
    if (g.touch1) {
      var p0 = g.touch0[0], l0 = g.touch0[1],
          p1 = g.touch1[0], l1 = g.touch1[1],
          dp = (dp = p1[0] - p0[0]) * dp + (dp = p1[1] - p0[1]) * dp,
          dl = (dl = l1[0] - l0[0]) * dl + (dl = l1[1] - l0[1]) * dl;
      t = scale(t, Math.sqrt(dp / dl));
      p = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2];
      l = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2];
    } else if (g.touch0) {
      p = g.touch0[0];
      l = g.touch0[1];
    }
    else return;
    g.zoom('touch', constrain(translate(t, p, l), g.extent, translateExtent));
  }

  function pointerup() {
    downPointerIDs.delete(d3_event.pointerId);

    if (!this.__zooming) return;

    var g = gesture(this, arguments);

    d3_event.stopImmediatePropagation();

    if (g.touch0 && g.touch0[2] === d3_event.pointerId) delete g.touch0;
    else if (g.touch1 && g.touch1[2] === d3_event.pointerId) delete g.touch1;

    if (g.touch1 && !g.touch0) {
      g.touch0 = g.touch1;
      delete g.touch1;
    }
    if (g.touch0) g.touch0[1] = this.__zoom.invert(g.touch0[0]);
    else {
      g.end();
    }
  }

  zoom.wheelDelta = function(_) {
    return arguments.length ? (wheelDelta = utilFunctor(+_), zoom) : wheelDelta;
  };

  zoom.filter = function(_) {
    return arguments.length ? (filter = utilFunctor(!!_), zoom) : filter;
  };

  zoom.extent = function(_) {
    return arguments.length ? (extent = utilFunctor([[+_[0][0], +_[0][1]], [+_[1][0], +_[1][1]]]), zoom) : extent;
  };

  zoom.scaleExtent = function(_) {
    return arguments.length ? (scaleExtent[0] = +_[0], scaleExtent[1] = +_[1], zoom) : [scaleExtent[0], scaleExtent[1]];
  };

  zoom.translateExtent = function(_) {
    return arguments.length ? (translateExtent[0][0] = +_[0][0], translateExtent[1][0] = +_[1][0], translateExtent[0][1] = +_[0][1], translateExtent[1][1] = +_[1][1], zoom) : [[translateExtent[0][0], translateExtent[0][1]], [translateExtent[1][0], translateExtent[1][1]]];
  };

  zoom.constrain = function(_) {
    return arguments.length ? (constrain = _, zoom) : constrain;
  };

  zoom.duration = function(_) {
    return arguments.length ? (duration = +_, zoom) : duration;
  };

  zoom.interpolate = function(_) {
    return arguments.length ? (interpolate = _, zoom) : interpolate;
  };

  zoom.on = function() {
    var value = listeners.on.apply(listeners, arguments);
    return value === listeners ? zoom : value;
  };

  return zoom;
}
<commit_msg>Fix issue where map could pan when moving the mouse without a down pointer<commit_after>// Adapted from d3-zoom to handle pointer events.
// https://github.com/d3/d3-zoom/blob/523ccff340187a3e3c044eaa4d4a7391ea97272b/src/zoom.js

import { dispatch as d3_dispatch } from 'd3-dispatch';
import { interpolateZoom } from 'd3-interpolate';
import { event as d3_event, customEvent as d3_customEvent, mouse as d3_mouse } from 'd3-selection';
import { interrupt as d3_interrupt } from 'd3-transition';
import ZoomEvent from '../../node_modules/d3-zoom/src/event.js';
import { Transform, identity } from '../../node_modules/d3-zoom/src/transform.js';

import { utilFunctor } from './util';

// Ignore right-click, since that should open the context menu.
function defaultFilter() {
  return !d3_event.ctrlKey && !d3_event.button;
}

function defaultExtent() {
  var e = this;
  if (e instanceof SVGElement) {
    e = e.ownerSVGElement || e;
    if (e.hasAttribute('viewBox')) {
      e = e.viewBox.baseVal;
      return [[e.x, e.y], [e.x + e.width, e.y + e.height]];
    }
    return [[0, 0], [e.width.baseVal.value, e.height.baseVal.value]];
  }
  return [[0, 0], [e.clientWidth, e.clientHeight]];
}

function defaultTransform() {
  return this.__zoom || identity;
}

function defaultWheelDelta() {
  return -d3_event.deltaY * (d3_event.deltaMode === 1 ? 0.05 : d3_event.deltaMode ? 1 : 0.002);
}

function defaultConstrain(transform, extent, translateExtent) {
  var dx0 = transform.invertX(extent[0][0]) - translateExtent[0][0],
      dx1 = transform.invertX(extent[1][0]) - translateExtent[1][0],
      dy0 = transform.invertY(extent[0][1]) - translateExtent[0][1],
      dy1 = transform.invertY(extent[1][1]) - translateExtent[1][1];
  return transform.translate(
    dx1 > dx0 ? (dx0 + dx1) / 2 : Math.min(0, dx0) || Math.max(0, dx1),
    dy1 > dy0 ? (dy0 + dy1) / 2 : Math.min(0, dy0) || Math.max(0, dy1)
  );
}

export function utilZoomPan() {
  var filter = defaultFilter,
      extent = defaultExtent,
      constrain = defaultConstrain,
      wheelDelta = defaultWheelDelta,
      scaleExtent = [0, Infinity],
      translateExtent = [[-Infinity, -Infinity], [Infinity, Infinity]],
      duration = 250,
      interpolate = interpolateZoom,
      listeners = d3_dispatch('start', 'zoom', 'end'),
      wheelDelay = 150;

  function zoom(selection) {
    selection
        .property('__zoom', defaultTransform)
        .on('pointerdown.zoom', pointerdown)
        .on('pointermove.zoom', pointermove)
        .on('pointerup.zoom pointercancel.zoom', pointerup)
        .on('wheel.zoom', wheeled)
        .style('touch-action', 'none')
        .style('-webkit-tap-highlight-color', 'rgba(0,0,0,0)');
  }

  zoom.transform = function(collection, transform, point) {
    var selection = collection.selection ? collection.selection() : collection;
    selection.property('__zoom', defaultTransform);
    if (collection !== selection) {
      schedule(collection, transform, point);
    } else {
      selection.interrupt().each(function() {
        gesture(this, arguments)
            .start()
            .zoom(null, typeof transform === 'function' ? transform.apply(this, arguments) : transform)
            .end();
      });
    }
  };

  zoom.scaleBy = function(selection, k, p) {
    zoom.scaleTo(selection, function() {
      var k0 = this.__zoom.k,
          k1 = typeof k === 'function' ? k.apply(this, arguments) : k;
      return k0 * k1;
    }, p);
  };

  zoom.scaleTo = function(selection, k, p) {
    zoom.transform(selection, function() {
      var e = extent.apply(this, arguments),
          t0 = this.__zoom,
          p0 = p == null ? centroid(e) : typeof p === 'function' ? p.apply(this, arguments) : p,
          p1 = t0.invert(p0),
          k1 = typeof k === 'function' ? k.apply(this, arguments) : k;
      return constrain(translate(scale(t0, k1), p0, p1), e, translateExtent);
    }, p);
  };

  zoom.translateBy = function(selection, x, y) {
    zoom.transform(selection, function() {
      return constrain(this.__zoom.translate(
        typeof x === 'function' ? x.apply(this, arguments) : x,
        typeof y === 'function' ? y.apply(this, arguments) : y
      ), extent.apply(this, arguments), translateExtent);
    });
  };

  zoom.translateTo = function(selection, x, y, p) {
    zoom.transform(selection, function() {
      var e = extent.apply(this, arguments),
          t = this.__zoom,
          p0 = p == null ? centroid(e) : typeof p === 'function' ? p.apply(this, arguments) : p;
      return constrain(identity.translate(p0[0], p0[1]).scale(t.k).translate(
        typeof x === 'function' ? -x.apply(this, arguments) : -x,
        typeof y === 'function' ? -y.apply(this, arguments) : -y
      ), e, translateExtent);
    }, p);
  };

  function scale(transform, k) {
    k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], k));
    return k === transform.k ? transform : new Transform(k, transform.x, transform.y);
  }

  function translate(transform, p0, p1) {
    var x = p0[0] - p1[0] * transform.k, y = p0[1] - p1[1] * transform.k;
    return x === transform.x && y === transform.y ? transform : new Transform(transform.k, x, y);
  }

  function centroid(extent) {
    return [(+extent[0][0] + +extent[1][0]) / 2, (+extent[0][1] + +extent[1][1]) / 2];
  }

  function schedule(transition, transform, point) {
    transition
        .on('start.zoom', function() { gesture(this, arguments).start(); })
        .on('interrupt.zoom end.zoom', function() { gesture(this, arguments).end(); })
        .tween('zoom', function() {
          var that = this,
              args = arguments,
              g = gesture(that, args),
              e = extent.apply(that, args),
              p = point == null ? centroid(e) : typeof point === 'function' ? point.apply(that, args) : point,
              w = Math.max(e[1][0] - e[0][0], e[1][1] - e[0][1]),
              a = that.__zoom,
              b = typeof transform === 'function' ? transform.apply(that, args) : transform,
              i = interpolate(a.invert(p).concat(w / a.k), b.invert(p).concat(w / b.k));
          return function(t) {
            if (t === 1) t = b; // Avoid rounding error on end.
            else { var l = i(t), k = w / l[2]; t = new Transform(k, p[0] - l[0] * k, p[1] - l[1] * k); }
            g.zoom(null, t);
          };
        });
  }

  function gesture(that, args, clean) {
    return (!clean && that.__zooming) || new Gesture(that, args);
  }

  function Gesture(that, args) {
    this.that = that;
    this.args = args;
    this.active = 0;
    this.extent = extent.apply(that, args);
  }

  Gesture.prototype = {
    start: function() {
      if (++this.active === 1) {
        this.that.__zooming = this;
        this.emit('start');
      }
      return this;
    },
    zoom: function(key, transform) {
      if (this.mouse && key !== 'mouse') this.mouse[1] = transform.invert(this.mouse[0]);
      if (this.touch0 && key !== 'touch') this.touch0[1] = transform.invert(this.touch0[0]);
      if (this.touch1 && key !== 'touch') this.touch1[1] = transform.invert(this.touch1[0]);
      this.that.__zoom = transform;
      this.emit('zoom');
      return this;
    },
    end: function() {
      if (--this.active === 0) {
        delete this.that.__zooming;
        this.emit('end');
      }
      return this;
    },
    emit: function(type) {
      d3_customEvent(new ZoomEvent(zoom, type, this.that.__zoom), listeners.apply, listeners, [type, this.that, this.args]);
    }
  };

  function wheeled() {
    if (!filter.apply(this, arguments)) return;
    var g = gesture(this, arguments),
        t = this.__zoom,
        k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], t.k * Math.pow(2, wheelDelta.apply(this, arguments)))),
        p = d3_mouse(this);

    // If the mouse is in the same location as before, reuse it.
    // If there were recent wheel events, reset the wheel idle timeout.
    if (g.wheel) {
      if (g.mouse[0][0] !== p[0] || g.mouse[0][1] !== p[1]) {
        g.mouse[1] = t.invert(g.mouse[0] = p);
      }
      clearTimeout(g.wheel);
    }

    // If this wheel event won’t trigger a transform change, ignore it.
    else if (t.k === k) return;

    // Otherwise, capture the mouse point and location at the start.
    else {
      g.mouse = [p, t.invert(p)];
      d3_interrupt(this);
      g.start();
    }

    d3_event.preventDefault();
    d3_event.stopImmediatePropagation();
    g.wheel = setTimeout(wheelidled, wheelDelay);
    g.zoom('mouse', constrain(translate(scale(t, k), g.mouse[0], g.mouse[1]), g.extent, translateExtent));

    function wheelidled() {
      g.wheel = null;
      g.end();
    }
  }

  var downPointerIDs = new Set();

  function pointerdown() {
    downPointerIDs.add(d3_event.pointerId);

    if (!filter.apply(this, arguments)) return;

    var g = gesture(this, arguments, downPointerIDs.size === 1);
    var started;

    d3_event.stopImmediatePropagation();
    var loc = d3_mouse(this);
    var p = [loc, this.__zoom.invert(loc), d3_event.pointerId];
    if (!g.touch0) {
       g.touch0 = p;
       started = true;

    } else if (!g.touch1 && g.touch0[2] !== p[2]) {
       g.touch1 = p;
    }

    if (started) {
      d3_interrupt(this);
      g.start();
    }
  }

  function pointermove() {
    if (!this.__zooming) return;

    // don't pan if no pointer is down, e.g. after a down mouse was moved off the map and released
    if ('buttons' in d3_event && !d3_event.buttons) return;

    var loc = d3_mouse(this);

    var g = gesture(this, arguments);
    var t, p, l;

    d3_event.preventDefault();
    d3_event.stopImmediatePropagation();

    if (g.touch0 && g.touch0[2] === d3_event.pointerId) g.touch0[0] = loc;
    else if (g.touch1 && g.touch1[2] === d3_event.pointerId) g.touch1[0] = loc;

    t = g.that.__zoom;
    if (g.touch1) {
      var p0 = g.touch0[0], l0 = g.touch0[1],
          p1 = g.touch1[0], l1 = g.touch1[1],
          dp = (dp = p1[0] - p0[0]) * dp + (dp = p1[1] - p0[1]) * dp,
          dl = (dl = l1[0] - l0[0]) * dl + (dl = l1[1] - l0[1]) * dl;
      t = scale(t, Math.sqrt(dp / dl));
      p = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2];
      l = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2];
    } else if (g.touch0) {
      p = g.touch0[0];
      l = g.touch0[1];
    }
    else return;
    g.zoom('touch', constrain(translate(t, p, l), g.extent, translateExtent));
  }

  function pointerup() {
    downPointerIDs.delete(d3_event.pointerId);

    if (!this.__zooming) return;

    var g = gesture(this, arguments);

    d3_event.stopImmediatePropagation();

    if (g.touch0 && g.touch0[2] === d3_event.pointerId) delete g.touch0;
    else if (g.touch1 && g.touch1[2] === d3_event.pointerId) delete g.touch1;

    if (g.touch1 && !g.touch0) {
      g.touch0 = g.touch1;
      delete g.touch1;
    }
    if (g.touch0) g.touch0[1] = this.__zoom.invert(g.touch0[0]);
    else {
      g.end();
    }
  }

  zoom.wheelDelta = function(_) {
    return arguments.length ? (wheelDelta = utilFunctor(+_), zoom) : wheelDelta;
  };

  zoom.filter = function(_) {
    return arguments.length ? (filter = utilFunctor(!!_), zoom) : filter;
  };

  zoom.extent = function(_) {
    return arguments.length ? (extent = utilFunctor([[+_[0][0], +_[0][1]], [+_[1][0], +_[1][1]]]), zoom) : extent;
  };

  zoom.scaleExtent = function(_) {
    return arguments.length ? (scaleExtent[0] = +_[0], scaleExtent[1] = +_[1], zoom) : [scaleExtent[0], scaleExtent[1]];
  };

  zoom.translateExtent = function(_) {
    return arguments.length ? (translateExtent[0][0] = +_[0][0], translateExtent[1][0] = +_[1][0], translateExtent[0][1] = +_[0][1], translateExtent[1][1] = +_[1][1], zoom) : [[translateExtent[0][0], translateExtent[0][1]], [translateExtent[1][0], translateExtent[1][1]]];
  };

  zoom.constrain = function(_) {
    return arguments.length ? (constrain = _, zoom) : constrain;
  };

  zoom.duration = function(_) {
    return arguments.length ? (duration = +_, zoom) : duration;
  };

  zoom.interpolate = function(_) {
    return arguments.length ? (interpolate = _, zoom) : interpolate;
  };

  zoom.on = function() {
    var value = listeners.on.apply(listeners, arguments);
    return value === listeners ? zoom : value;
  };

  return zoom;
}
<|endoftext|>


<commit_before>import angular from 'angular';

class Controller {

  constructor() {
    // from imhere-step-component
    this.steps = [];
    this.currentStep = 1;
    this.completeWizard = false;
  }
  $onInit() {
    this.mainColor = (this.setMainColor) ? this.setMainColor : '#59bfb3';
    this.mainHoverColor = (this.setMainHoverColor) ? this.setMainHoverColor : this.lightenDarkenColor(this.mainColor, -20);
  }

  lightenDarkenColor(col, amt) {
    this.col = col;
    let usePound = false;
    if (this.col[0] === '#') {
      this.col = this.col.slice(1);
      usePound = true;
    }
    const num = parseInt(this.col, 16);
    let r = (num >> 16) + amt;
    if (r > 255) {
      r = 255;
    } else if (r < 0) {
      r = 0;
    }
    let b = ((num >> 8) & 0x00FF) + amt;
    if (b > 255) {
      b = 255;
    } else if (b < 0) {
      b = 0;
    }
    let g = (num & 0x0000FF) + amt;
    if (g > 255) {
      g = 255;
    } else if (g < 0) {
      g = 0;
    }
    return (usePound ? '#' : '') + (g | (b << 8) | (r << 16)).toString(16);
  }

  isCurrentStep(index) {
    return index === this.currentStep;
  }

  setCurrentStep(index) {
    this.currentStep = index;
  }

  nextStep() {
    this.currentStep = (this.currentStep % this.steps.length) + 1;
  }
  preStep() {
    this.currentStep = this.currentStep - 1;
  }
  complete() {
    this.onFinish();
    this.completeWizard = true;
  }

}

const component = {
  bindings: {
    onFinish: '&',
    setMainColor: '@',
    setMainHoverColor: '@',
  },
  transclude: true,
  controller: Controller,
  controllerAs: 'vm',
  template: require('./imhere-wizard.html'),
};

export default angular
  .module('imhere-angular-wizard.imhere-wizard', [])
  .component('imhereWizard', component);
<commit_msg>Added function to be triggered on change of step.<commit_after>import angular from 'angular';

class Controller {

  constructor() {
    // from imhere-step-component
    this.steps = [];
    this.currentStep = 1;
    this.completeWizard = false;
  }
  $onInit() {
    this.mainColor = (this.setMainColor) ? this.setMainColor : '#59bfb3';
    this.mainHoverColor = (this.setMainHoverColor) ? this.setMainHoverColor : this.lightenDarkenColor(this.mainColor, -20);
  }

  lightenDarkenColor(col, amt) {
    this.col = col;
    let usePound = false;
    if (this.col[0] === '#') {
      this.col = this.col.slice(1);
      usePound = true;
    }
    const num = parseInt(this.col, 16);
    let r = (num >> 16) + amt;
    if (r > 255) {
      r = 255;
    } else if (r < 0) {
      r = 0;
    }
    let b = ((num >> 8) & 0x00FF) + amt;
    if (b > 255) {
      b = 255;
    } else if (b < 0) {
      b = 0;
    }
    let g = (num & 0x0000FF) + amt;
    if (g > 255) {
      g = 255;
    } else if (g < 0) {
      g = 0;
    }
    return (usePound ? '#' : '') + (g | (b << 8) | (r << 16)).toString(16);
  }

  isCurrentStep(index) {
    return index === this.currentStep;
  }

  setCurrentStep(index) {
    var lastStep = this.currentStep;
    this.currentStep = index;
    onStep(this.currentStep, lastStep);
  }

  nextStep() {
    var lastStep = this.currentStep;
    this.currentStep = (this.currentStep % this.steps.length) + 1;
    onStep(this.currentStep, lastStep);
  }
  preStep() {
    this.currentStep = this.currentStep - 1;
    onStep(this.currentStep, this.currentStep + 1);
  }
  complete() {
    this.onFinish();
    this.completeWizard = true;
    this.currentStep = 1;
  }

}

const component = {
  bindings: {
    onFinish: '&',
    onChangePage: '&',
    setMainColor: '@',
    setMainHoverColor: '@',
  },
  transclude: true,
  controller: Controller,
  controllerAs: 'vm',
  template: require('./imhere-wizard.html'),
};

export default angular
  .module('imhere-angular-wizard.imhere-wizard', [])
  .component('imhereWizard', component);
<|endoftext|>


<commit_before>import {transformApiSiteData, transformApiTiersData} from './helpers';

function getAnalyticsMetadata() {
    const analyticsTag = document.querySelector('meta[name=ghost-analytics-id]');
    const analyticsId = analyticsTag?.content;
    if (analyticsTag) {
        return {
            entry_id: analyticsId,
            source_url: window.location.href
        };
    }
    return null;
}

function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}) {
    const apiPath = 'members/api';

    function endpointFor({type, resource}) {
        if (type === 'members') {
            return `${siteUrl.replace(/\/$/, '')}/${apiPath}/${resource}/`;
        }
    }

    function contentEndpointFor({resource, params = ''}) {
        if (apiUrl && apiKey) {
            return `${apiUrl.replace(/\/$/, '')}/${resource}/?key=${apiKey}&limit=all${params}`;
        }
        return '';
    }

    function makeRequest({url, method = 'GET', headers = {}, credentials = undefined, body = undefined}) {
        const options = {
            method,
            headers,
            credentials,
            body
        };
        return fetch(url, options);
    }
    const api = {};

    api.analytics = {
        async pushEvent(event) {
            const url = endpointFor({type: 'members', resource: 'events'});
            const identity = await api.member.identity();
            event.identity = identity;
            const body = {
                events: [event]
            };
            return makeRequest({
                url,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(body)
            }).then(function (res) {
                if (!res.ok) {
                    return null;
                }
                return res.text();
            });
        }
    };

    api.site = {
        read() {
            const url = endpointFor({type: 'members', resource: 'site'});
            return makeRequest({
                url,
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json'
                }
            }).then(function (res) {
                if (res.ok) {
                    return res.json();
                } else {
                    throw new Error('Failed to fetch site data');
                }
            });
        },

        newsletters() {
            const url = contentEndpointFor({resource: 'newsletters'});
            return makeRequest({
                url,
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json'
                }
            }).then(function (res) {
                if (res.ok) {
                    return res.json();
                } else {
                    throw new Error('Failed to fetch site data');
                }
            });
        },

        tiers() {
            const url = contentEndpointFor({resource: 'tiers', params: '&include=monthly_price,yearly_price,benefits'});
            return makeRequest({
                url,
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json'
                }
            }).then(function (res) {
                if (res.ok) {
                    return res.json();
                } else {
                    throw new Error('Failed to fetch site data');
                }
            });
        },

        settings() {
            const url = contentEndpointFor({resource: 'settings'});
            return makeRequest({
                url,
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json'
                }
            }).then(function (res) {
                if (res.ok) {
                    return res.json();
                } else {
                    throw new Error('Failed to fetch site data');
                }
            });
        },

        offer({offerId}) {
            const url = contentEndpointFor({resource: `offers/${offerId}`});
            return makeRequest({
                url,
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json'
                }
            }).then(function (res) {
                if (res.ok) {
                    return res.json();
                } else {
                    throw new Error('Failed to fetch offer data');
                }
            });
        }
    };

    api.member = {
        identity() {
            const url = endpointFor({type: 'members', resource: 'session'});
            return makeRequest({
                url,
                credentials: 'same-origin'
            }).then(function (res) {
                if (!res.ok || res.status === 204) {
                    return null;
                }
                return res.text();
            });
        },

        sessionData() {
            const url = endpointFor({type: 'members', resource: 'member'});
            return makeRequest({
                url,
                credentials: 'same-origin'
            }).then(function (res) {
                if (!res.ok || res.status === 204) {
                    return null;
                }
                return res.json();
            });
        },

        update({name, subscribed, newsletters}) {
            const url = endpointFor({type: 'members', resource: 'member'});
            const body = {
                name,
                subscribed,
                newsletters
            };
            const analyticsData = getAnalyticsMetadata();
            if (analyticsData) {
                body.metadata = analyticsData;
            }
            return makeRequest({
                url,
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json'
                },
                credentials: 'same-origin',
                body: JSON.stringify(body)
            }).then(function (res) {
                if (!res.ok) {
                    return null;
                }
                return res.json();
            });
        },

        sendMagicLink({email, emailType, labels, name, oldEmail, newsletters}) {
            const url = endpointFor({type: 'members', resource: 'send-magic-link'});
            const body = {
                name,
                email,
                newsletters,
                oldEmail,
                emailType,
                labels,
                requestSrc: 'portal'
            };
            const analyticsData = getAnalyticsMetadata();
            if (analyticsData) {
                body.metadata = analyticsData;
            }
            return makeRequest({
                url,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(body)
            }).then(function (res) {
                if (res.ok) {
                    return 'Success';
                } else {
                    throw new Error('Failed to send magic link email');
                }
            });
        },

        signout() {
            const url = endpointFor({type: 'members', resource: 'session'});
            return makeRequest({
                url,
                method: 'DELETE'
            }).then(function (res) {
                if (res.ok) {
                    window.location.replace(siteUrl);
                    return 'Success';
                } else {
                    throw new Error('Failed to signout');
                }
            });
        },

        async newsletters({uuid}) {
            let url = endpointFor({type: 'members', resource: `member/newsletters`});
            url = url + `?uuid=${uuid}`;
            return makeRequest({
                url,
                credentials: 'same-origin'
            }).then(function (res) {
                if (!res.ok || res.status === 204) {
                    return null;
                }
                return res.json();
            });
        },

        async updateNewsletters({uuid, newsletters}) {
            let url = endpointFor({type: 'members', resource: `member/newsletters`});
            url = url + `?uuid=${uuid}`;
            const body = {
                newsletters
            };

            return makeRequest({
                url,
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(body)
            }).then(function (res) {
                if (res.ok) {
                    return res.json();
                } else {
                    throw new Error('Failed to upadte newsletter preferences');
                }
            });
        },

        async updateEmailAddress({email}) {
            const identity = await api.member.identity();
            const url = endpointFor({type: 'members', resource: 'member/email'});
            const body = {
                email,
                identity
            };

            return makeRequest({
                url,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(body)
            }).then(function (res) {
                if (res.ok) {
                    return 'Success';
                } else {
                    throw new Error('Failed to send email address verification email');
                }
            });
        },

        async checkoutPlan({plan, tierId, cadence, cancelUrl, successUrl, email: customerEmail, name, offerId, newsletters, metadata = {}} = {}) {
            const siteUrlObj = new URL(siteUrl);
            const identity = await api.member.identity();
            const url = endpointFor({type: 'members', resource: 'create-stripe-checkout-session'});

            if (!cancelUrl) {
                const checkoutCancelUrl = window.location.href.startsWith(siteUrlObj.href) ? new URL(window.location.href) : new URL(siteUrl);
                checkoutCancelUrl.searchParams.set('stripe', 'cancel');
                cancelUrl = checkoutCancelUrl.href;
            }
            const metadataObj = {
                name,
                newsletters: JSON.stringify(newsletters),
                requestSrc: 'portal',
                fp_tid: (window.FPROM || window.$FPROM)?.data?.tid,
                ...metadata
            };

            const body = {
                priceId: offerId ? null : plan,
                offerId,
                identity: identity,
                metadata: metadataObj,
                successUrl,
                cancelUrl,
                customerEmail: customerEmail
            };
            if (tierId && cadence) {
                delete body.priceId;
                body.tierId = offerId ? null : tierId;
                body.cadence = offerId ? null : cadence;
            }
            return makeRequest({
                url,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(body)
            }).then(function (res) {
                if (!res.ok) {
                    throw new Error('Could not create stripe checkout session');
                }
                return res.json();
            }).then(function (result) {
                const stripe = window.Stripe(result.publicKey);
                return stripe.redirectToCheckout({
                    sessionId: result.sessionId
                });
            }).then(function (result) {
                if (result.error) {
                    throw new Error(result.error.message);
                }
            }).catch(function (err) {
                throw err;
            });
        },

        async editBilling({successUrl, cancelUrl, subscriptionId} = {}) {
            const siteUrlObj = new URL(siteUrl);
            const identity = await api.member.identity();
            const url = endpointFor({type: 'members', resource: 'create-stripe-update-session'});
            if (!successUrl) {
                const checkoutSuccessUrl = new URL(siteUrl);
                checkoutSuccessUrl.searchParams.set('stripe', 'billing-update-success');
                successUrl = checkoutSuccessUrl.href;
            }

            if (!cancelUrl) {
                const checkoutCancelUrl = window.location.href.startsWith(siteUrlObj.href) ? new URL(window.location.href) : new URL(siteUrl);
                checkoutCancelUrl.searchParams.set('stripe', 'billing-update-cancel');
                cancelUrl = checkoutCancelUrl.href;
            }
            return makeRequest({
                url,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    identity: identity,
                    subscription_id: subscriptionId,
                    successUrl,
                    cancelUrl
                })
            }).then(function (res) {
                if (!res.ok) {
                    throw new Error('Could not create stripe checkout session');
                }
                return res.json();
            }).then(function (result) {
                const stripe = window.Stripe(result.publicKey);
                return stripe.redirectToCheckout({
                    sessionId: result.sessionId
                });
            }).then(function (result) {
                if (result.error) {
                    throw new Error(result.error.message);
                }
            }).catch(function (err) {
                throw err;
            });
        },

        async updateSubscription({subscriptionId, tierId, cadence, planId, smartCancel, cancelAtPeriodEnd, cancellationReason}) {
            const identity = await api.member.identity();
            const url = endpointFor({type: 'members', resource: 'subscriptions'}) + subscriptionId + '/';
            const body = {
                smart_cancel: smartCancel,
                cancel_at_period_end: cancelAtPeriodEnd,
                cancellation_reason: cancellationReason,
                identity: identity,
                priceId: planId
            };
            const analyticsData = getAnalyticsMetadata();
            if (body) {
                body.metadata = analyticsData;
            }

            if (tierId && cadence) {
                delete body.priceId;
                body.tierId = tierId;
                body.cadence = cadence;
            }

            return makeRequest({
                url,
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(body)
            });
        }
    };

    api.init = async () => {
        let [member] = await Promise.all([
            api.member.sessionData()
        ]);
        let site = {};
        let newsletters = [];
        let tiers = [];
        let settings = {};

        try {
            [{settings}, {tiers}, {newsletters}] = await Promise.all([
                api.site.settings(),
                api.site.tiers(),
                api.site.newsletters()
            ]);
            site = {
                ...settings,
                newsletters,
                tiers: transformApiTiersData({tiers})
            };
        } catch (e) {
            // Ignore
        }
        site = transformApiSiteData({site});
        return {site, member};
    };

    return api;
}

export default setupGhostApi;
<commit_msg>Removed empty customer email from checkout session payload<commit_after>import {transformApiSiteData, transformApiTiersData} from './helpers';

function getAnalyticsMetadata() {
    const analyticsTag = document.querySelector('meta[name=ghost-analytics-id]');
    const analyticsId = analyticsTag?.content;
    if (analyticsTag) {
        return {
            entry_id: analyticsId,
            source_url: window.location.href
        };
    }
    return null;
}

function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}) {
    const apiPath = 'members/api';

    function endpointFor({type, resource}) {
        if (type === 'members') {
            return `${siteUrl.replace(/\/$/, '')}/${apiPath}/${resource}/`;
        }
    }

    function contentEndpointFor({resource, params = ''}) {
        if (apiUrl && apiKey) {
            return `${apiUrl.replace(/\/$/, '')}/${resource}/?key=${apiKey}&limit=all${params}`;
        }
        return '';
    }

    function makeRequest({url, method = 'GET', headers = {}, credentials = undefined, body = undefined}) {
        const options = {
            method,
            headers,
            credentials,
            body
        };
        return fetch(url, options);
    }
    const api = {};

    api.analytics = {
        async pushEvent(event) {
            const url = endpointFor({type: 'members', resource: 'events'});
            const identity = await api.member.identity();
            event.identity = identity;
            const body = {
                events: [event]
            };
            return makeRequest({
                url,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(body)
            }).then(function (res) {
                if (!res.ok) {
                    return null;
                }
                return res.text();
            });
        }
    };

    api.site = {
        read() {
            const url = endpointFor({type: 'members', resource: 'site'});
            return makeRequest({
                url,
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json'
                }
            }).then(function (res) {
                if (res.ok) {
                    return res.json();
                } else {
                    throw new Error('Failed to fetch site data');
                }
            });
        },

        newsletters() {
            const url = contentEndpointFor({resource: 'newsletters'});
            return makeRequest({
                url,
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json'
                }
            }).then(function (res) {
                if (res.ok) {
                    return res.json();
                } else {
                    throw new Error('Failed to fetch site data');
                }
            });
        },

        tiers() {
            const url = contentEndpointFor({resource: 'tiers', params: '&include=monthly_price,yearly_price,benefits'});
            return makeRequest({
                url,
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json'
                }
            }).then(function (res) {
                if (res.ok) {
                    return res.json();
                } else {
                    throw new Error('Failed to fetch site data');
                }
            });
        },

        settings() {
            const url = contentEndpointFor({resource: 'settings'});
            return makeRequest({
                url,
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json'
                }
            }).then(function (res) {
                if (res.ok) {
                    return res.json();
                } else {
                    throw new Error('Failed to fetch site data');
                }
            });
        },

        offer({offerId}) {
            const url = contentEndpointFor({resource: `offers/${offerId}`});
            return makeRequest({
                url,
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json'
                }
            }).then(function (res) {
                if (res.ok) {
                    return res.json();
                } else {
                    throw new Error('Failed to fetch offer data');
                }
            });
        }
    };

    api.member = {
        identity() {
            const url = endpointFor({type: 'members', resource: 'session'});
            return makeRequest({
                url,
                credentials: 'same-origin'
            }).then(function (res) {
                if (!res.ok || res.status === 204) {
                    return null;
                }
                return res.text();
            });
        },

        sessionData() {
            const url = endpointFor({type: 'members', resource: 'member'});
            return makeRequest({
                url,
                credentials: 'same-origin'
            }).then(function (res) {
                if (!res.ok || res.status === 204) {
                    return null;
                }
                return res.json();
            });
        },

        update({name, subscribed, newsletters}) {
            const url = endpointFor({type: 'members', resource: 'member'});
            const body = {
                name,
                subscribed,
                newsletters
            };
            const analyticsData = getAnalyticsMetadata();
            if (analyticsData) {
                body.metadata = analyticsData;
            }
            return makeRequest({
                url,
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json'
                },
                credentials: 'same-origin',
                body: JSON.stringify(body)
            }).then(function (res) {
                if (!res.ok) {
                    return null;
                }
                return res.json();
            });
        },

        sendMagicLink({email, emailType, labels, name, oldEmail, newsletters}) {
            const url = endpointFor({type: 'members', resource: 'send-magic-link'});
            const body = {
                name,
                email,
                newsletters,
                oldEmail,
                emailType,
                labels,
                requestSrc: 'portal'
            };
            const analyticsData = getAnalyticsMetadata();
            if (analyticsData) {
                body.metadata = analyticsData;
            }
            return makeRequest({
                url,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(body)
            }).then(function (res) {
                if (res.ok) {
                    return 'Success';
                } else {
                    throw new Error('Failed to send magic link email');
                }
            });
        },

        signout() {
            const url = endpointFor({type: 'members', resource: 'session'});
            return makeRequest({
                url,
                method: 'DELETE'
            }).then(function (res) {
                if (res.ok) {
                    window.location.replace(siteUrl);
                    return 'Success';
                } else {
                    throw new Error('Failed to signout');
                }
            });
        },

        async newsletters({uuid}) {
            let url = endpointFor({type: 'members', resource: `member/newsletters`});
            url = url + `?uuid=${uuid}`;
            return makeRequest({
                url,
                credentials: 'same-origin'
            }).then(function (res) {
                if (!res.ok || res.status === 204) {
                    return null;
                }
                return res.json();
            });
        },

        async updateNewsletters({uuid, newsletters}) {
            let url = endpointFor({type: 'members', resource: `member/newsletters`});
            url = url + `?uuid=${uuid}`;
            const body = {
                newsletters
            };

            return makeRequest({
                url,
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(body)
            }).then(function (res) {
                if (res.ok) {
                    return res.json();
                } else {
                    throw new Error('Failed to upadte newsletter preferences');
                }
            });
        },

        async updateEmailAddress({email}) {
            const identity = await api.member.identity();
            const url = endpointFor({type: 'members', resource: 'member/email'});
            const body = {
                email,
                identity
            };

            return makeRequest({
                url,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(body)
            }).then(function (res) {
                if (res.ok) {
                    return 'Success';
                } else {
                    throw new Error('Failed to send email address verification email');
                }
            });
        },

        async checkoutPlan({plan, tierId, cadence, cancelUrl, successUrl, email: customerEmail, name, offerId, newsletters, metadata = {}} = {}) {
            const siteUrlObj = new URL(siteUrl);
            const identity = await api.member.identity();
            const url = endpointFor({type: 'members', resource: 'create-stripe-checkout-session'});

            if (!cancelUrl) {
                const checkoutCancelUrl = window.location.href.startsWith(siteUrlObj.href) ? new URL(window.location.href) : new URL(siteUrl);
                checkoutCancelUrl.searchParams.set('stripe', 'cancel');
                cancelUrl = checkoutCancelUrl.href;
            }
            const metadataObj = {
                name,
                newsletters: JSON.stringify(newsletters),
                requestSrc: 'portal',
                fp_tid: (window.FPROM || window.$FPROM)?.data?.tid,
                ...metadata
            };

            const body = {
                priceId: offerId ? null : plan,
                offerId,
                identity: identity,
                metadata: metadataObj,
                successUrl,
                cancelUrl
            };

            if (customerEmail) {
                body.customerEmail = customerEmail;
            }

            if (tierId && cadence) {
                delete body.priceId;
                body.tierId = offerId ? null : tierId;
                body.cadence = offerId ? null : cadence;
            }
            return makeRequest({
                url,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(body)
            }).then(function (res) {
                if (!res.ok) {
                    throw new Error('Could not create stripe checkout session');
                }
                return res.json();
            }).then(function (result) {
                const stripe = window.Stripe(result.publicKey);
                return stripe.redirectToCheckout({
                    sessionId: result.sessionId
                });
            }).then(function (result) {
                if (result.error) {
                    throw new Error(result.error.message);
                }
            }).catch(function (err) {
                throw err;
            });
        },

        async editBilling({successUrl, cancelUrl, subscriptionId} = {}) {
            const siteUrlObj = new URL(siteUrl);
            const identity = await api.member.identity();
            const url = endpointFor({type: 'members', resource: 'create-stripe-update-session'});
            if (!successUrl) {
                const checkoutSuccessUrl = new URL(siteUrl);
                checkoutSuccessUrl.searchParams.set('stripe', 'billing-update-success');
                successUrl = checkoutSuccessUrl.href;
            }

            if (!cancelUrl) {
                const checkoutCancelUrl = window.location.href.startsWith(siteUrlObj.href) ? new URL(window.location.href) : new URL(siteUrl);
                checkoutCancelUrl.searchParams.set('stripe', 'billing-update-cancel');
                cancelUrl = checkoutCancelUrl.href;
            }
            return makeRequest({
                url,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    identity: identity,
                    subscription_id: subscriptionId,
                    successUrl,
                    cancelUrl
                })
            }).then(function (res) {
                if (!res.ok) {
                    throw new Error('Could not create stripe checkout session');
                }
                return res.json();
            }).then(function (result) {
                const stripe = window.Stripe(result.publicKey);
                return stripe.redirectToCheckout({
                    sessionId: result.sessionId
                });
            }).then(function (result) {
                if (result.error) {
                    throw new Error(result.error.message);
                }
            }).catch(function (err) {
                throw err;
            });
        },

        async updateSubscription({subscriptionId, tierId, cadence, planId, smartCancel, cancelAtPeriodEnd, cancellationReason}) {
            const identity = await api.member.identity();
            const url = endpointFor({type: 'members', resource: 'subscriptions'}) + subscriptionId + '/';
            const body = {
                smart_cancel: smartCancel,
                cancel_at_period_end: cancelAtPeriodEnd,
                cancellation_reason: cancellationReason,
                identity: identity,
                priceId: planId
            };
            const analyticsData = getAnalyticsMetadata();
            if (body) {
                body.metadata = analyticsData;
            }

            if (tierId && cadence) {
                delete body.priceId;
                body.tierId = tierId;
                body.cadence = cadence;
            }

            return makeRequest({
                url,
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(body)
            });
        }
    };

    api.init = async () => {
        let [member] = await Promise.all([
            api.member.sessionData()
        ]);
        let site = {};
        let newsletters = [];
        let tiers = [];
        let settings = {};

        try {
            [{settings}, {tiers}, {newsletters}] = await Promise.all([
                api.site.settings(),
                api.site.tiers(),
                api.site.newsletters()
            ]);
            site = {
                ...settings,
                newsletters,
                tiers: transformApiTiersData({tiers})
            };
        } catch (e) {
            // Ignore
        }
        site = transformApiSiteData({site});
        return {site, member};
    };

    return api;
}

export default setupGhostApi;
<|endoftext|>


<commit_before>'use strict';

/**
* ESLint rules specific to stdlib.
*
* @namespace rules
*/
var rules = {};

/**
* Require an empty line before single-line comments.
*
* @name empty-line-before-comment
* @memberof rules
* @type {string}
* @default 'error'
*
* @example
* // Bad...
* function square( x ) {
*     var out;
*     // Square the number:
*     out = x*x;
*     return out;
* }
*
* @example
* // Good...
* function square( x ) {
*     var out;
*
*     // Square the number:
*     out = x*x;
*     return out;
* }
*/
rules[ 'stdlib/empty-line-before-comment' ] = 'error';

/**
* Enforce that require() calls have only string literals as parameters.
*
* @name no-dynamic-require
* @memberof rules
* @type {string}
* @default 'error'
*
* @example
* // Bad...
* var pkg = '@stdlib/math/base/special/betainc';
* var betainc = require( pkg );
*
* @example
* // Good...
* var betainc = require( '@stdlib/math/base/special/betainc' );
*/
rules[ 'stdlib/no-dynamic-require' ] = 'error';

/**
* Disallow require() calls of another package's internals.
*
* @name no-internal-require
* @memberof rules
* @type {string}
* @default 'error'
*
* @example
* // Bad...
* var betainc = require( '@stdlib/math/base/special/betainc/lib/index.js' );
*
* @example
* // Good...
* var betainc = require( '@stdlib/math/base/special/betainc' );
*/
rules[ 'stdlib/no-internal-require' ] = 'error';

/**
* Never allow a variable to be declared multiple times within the same scope or for built-in globals to be redeclared.
*
* @name no-redeclare
* @memberof rules
* @type {Array}
* @default [ 'error', { 'builtinGlobals': false, 'globalsWhitelist': [] } ]
* @see [no-redeclare]{@link http://eslint.org/docs/rules/no-redeclare}
*
* @example
* // Bad...
* var a = 'beep';
* // ...
* var a = 'boop';
*
* @example
* // Good...
* var a = 'beep';
* // ...
* a = 'boop';
*/
rules[ 'stdlib/no-redeclare' ] = [ 'error', {
	'builtinGlobals': true,
	'globalsWhitelist': [
		'ArrayBuffer',
		'Buffer',
		'DataView',
		'Error',
		'EvalError',
		'Float32Array',
		'Float64Array',
		'Int8Array',
		'Int16Array',
		'Int32Array',
		'RangeError',
		'ReferenceError',
		'SharedArrayBuffer',
		'SyntaxError',
		'TypeError',
		'Uint8Array',
		'Uint8ClampedArray',
		'Uint16Array',
		'Uint32Array',
		'URIError'
	]
}];

/**
* Enforce that require() calls of files end with a whitelisted file extension.
*
* @name require-file-extensions
* @memberof rules
* @type {Array}
* @default [ 'error', { 'extensionsWhitelist': [ '.js', '.json', '.node' ] } ]
*
* @example
* // Bad...
*
* // Invalid file extension:
* var readme = require( '@stdlib/types/array/int32/README.md' );
*
* // Missing file extension:
* var debug = require( 'debug/src/browser' );
*
* @example
* // Good...
*
* var Int32Array = require( '@stdlib/types/array/int32' );
*
* var debug = require( 'debug/src/browser.js' );
*/
rules[ 'stdlib/require-file-extensions' ] = [ 'off', { // TODO: Enable once all @stdlib/tools packages are moved to @stdlib/_tools
	'extensionsWhitelist': [
		'.js',
		'.json',
		'.node'
	]
}];

/**
* Enforce that typed array constructors are explicitly required.
*
* @name require-typed-arrays
* @memberof rules
* @type {string}
* @default 'error'
*
* @example
* // Bad...
* var arr = new Float32Array();
*
* @example
* // Good...
* var Float32Array = require( '@stdlib/types/array/float32' );
*
* var arr = new Float32Array();
*/
rules[ 'stdlib/require-typed-arrays' ] = 'error';

/**
* Enforce that typed array constructors are explicitly required.
*
* @name section-headers
* @memberof rules
* @type {string}
* @default 'error'
*
* @example
* // Bad... (require two trailing slashes)
*
* // EXPORTS
*
* module.exports = {};
*
* @example
* // Bad... (require a known header type)
*
* // EXPRTS //
*
* module.exports = {};
*
* @example
* // Bad... (require all uppercase letters)
*
* // EXPorts //
*
* module.exports = {};
*
* @example
* // Good...
*
* // EXPORTS //
*
* module.exports = {};
*/
rules[ 'stdlib/section-headers' ] = 'error';

// EXPORTS //

module.exports = rules;
<commit_msg>Add generic `Array` to list of exceptions<commit_after>'use strict';

/**
* ESLint rules specific to stdlib.
*
* @namespace rules
*/
var rules = {};

/**
* Require an empty line before single-line comments.
*
* @name empty-line-before-comment
* @memberof rules
* @type {string}
* @default 'error'
*
* @example
* // Bad...
* function square( x ) {
*     var out;
*     // Square the number:
*     out = x*x;
*     return out;
* }
*
* @example
* // Good...
* function square( x ) {
*     var out;
*
*     // Square the number:
*     out = x*x;
*     return out;
* }
*/
rules[ 'stdlib/empty-line-before-comment' ] = 'error';

/**
* Enforce that require() calls have only string literals as parameters.
*
* @name no-dynamic-require
* @memberof rules
* @type {string}
* @default 'error'
*
* @example
* // Bad...
* var pkg = '@stdlib/math/base/special/betainc';
* var betainc = require( pkg );
*
* @example
* // Good...
* var betainc = require( '@stdlib/math/base/special/betainc' );
*/
rules[ 'stdlib/no-dynamic-require' ] = 'error';

/**
* Disallow require() calls of another package's internals.
*
* @name no-internal-require
* @memberof rules
* @type {string}
* @default 'error'
*
* @example
* // Bad...
* var betainc = require( '@stdlib/math/base/special/betainc/lib/index.js' );
*
* @example
* // Good...
* var betainc = require( '@stdlib/math/base/special/betainc' );
*/
rules[ 'stdlib/no-internal-require' ] = 'error';

/**
* Never allow a variable to be declared multiple times within the same scope or for built-in globals to be redeclared.
*
* @name no-redeclare
* @memberof rules
* @type {Array}
* @default [ 'error', { 'builtinGlobals': false, 'globalsWhitelist': [] } ]
* @see [no-redeclare]{@link http://eslint.org/docs/rules/no-redeclare}
*
* @example
* // Bad...
* var a = 'beep';
* // ...
* var a = 'boop';
*
* @example
* // Good...
* var a = 'beep';
* // ...
* a = 'boop';
*/
rules[ 'stdlib/no-redeclare' ] = [ 'error', {
	'builtinGlobals': true,
	'globalsWhitelist': [
		'Array',
		'ArrayBuffer',
		'Buffer',
		'DataView',
		'Error',
		'EvalError',
		'Float32Array',
		'Float64Array',
		'Int8Array',
		'Int16Array',
		'Int32Array',
		'RangeError',
		'ReferenceError',
		'SharedArrayBuffer',
		'SyntaxError',
		'TypeError',
		'Uint8Array',
		'Uint8ClampedArray',
		'Uint16Array',
		'Uint32Array',
		'URIError'
	]
}];

/**
* Enforce that require() calls of files end with a whitelisted file extension.
*
* @name require-file-extensions
* @memberof rules
* @type {Array}
* @default [ 'error', { 'extensionsWhitelist': [ '.js', '.json', '.node' ] } ]
*
* @example
* // Bad...
*
* // Invalid file extension:
* var readme = require( '@stdlib/types/array/int32/README.md' );
*
* // Missing file extension:
* var debug = require( 'debug/src/browser' );
*
* @example
* // Good...
*
* var Int32Array = require( '@stdlib/types/array/int32' );
*
* var debug = require( 'debug/src/browser.js' );
*/
rules[ 'stdlib/require-file-extensions' ] = [ 'off', { // TODO: Enable once all @stdlib/tools packages are moved to @stdlib/_tools
	'extensionsWhitelist': [
		'.js',
		'.json',
		'.node'
	]
}];

/**
* Enforce that typed array constructors are explicitly required.
*
* @name require-typed-arrays
* @memberof rules
* @type {string}
* @default 'error'
*
* @example
* // Bad...
* var arr = new Float32Array();
*
* @example
* // Good...
* var Float32Array = require( '@stdlib/types/array/float32' );
*
* var arr = new Float32Array();
*/
rules[ 'stdlib/require-typed-arrays' ] = 'error';

/**
* Enforce that typed array constructors are explicitly required.
*
* @name section-headers
* @memberof rules
* @type {string}
* @default 'error'
*
* @example
* // Bad... (require two trailing slashes)
*
* // EXPORTS
*
* module.exports = {};
*
* @example
* // Bad... (require a known header type)
*
* // EXPRTS //
*
* module.exports = {};
*
* @example
* // Bad... (require all uppercase letters)
*
* // EXPorts //
*
* module.exports = {};
*
* @example
* // Good...
*
* // EXPORTS //
*
* module.exports = {};
*/
rules[ 'stdlib/section-headers' ] = 'error';

// EXPORTS //

module.exports = rules;
<|endoftext|>


<commit_before>export default {
  servers: {
    websocket: function (api) {
      return {
        // ---------------------------------------------------------------------
        // Enable the server?
        // ---------------------------------------------------------------------
        enable: true,

        // ---------------------------------------------------------------------
        // Client location.
        //
        // This can also be a FQDN.
        // ---------------------------------------------------------------------
        clientUrl: 'window.location.origin',

        // ---------------------------------------------------------------------
        // The name of the client-side JS file to render.
        //
        // Both '.js' and '.min.js' versions will be created do not include the
        // file extension.
        // ---------------------------------------------------------------------
        clientJsName: 'stellar-client',

        // ---------------------------------------------------------------------
        // Should the server signal clients to not reconnect when the server is
        // shutdown/reboot.
        // ---------------------------------------------------------------------
        destroyClientsOnShutdown: false
      }
    }
  }
}
<commit_msg>fix missing property<commit_after>export default {
  servers: {
    websocket: function (api) {
      return {
        // ---------------------------------------------------------------------
        // Enable the server?
        // ---------------------------------------------------------------------
        enable: true,

        // ---------------------------------------------------------------------
        // Client location.
        //
        // This can also be a FQDN.
        // ---------------------------------------------------------------------
        clientUrl: 'window.location.origin',

        // ---------------------------------------------------------------------
        // The name of the client-side JS file to render.
        //
        // Both '.js' and '.min.js' versions will be created do not include the
        // file extension.
        // ---------------------------------------------------------------------
        clientJsName: 'stellar-client',

        // ---------------------------------------------------------------------
        // Should the server signal clients to not reconnect when the server is
        // shutdown/reboot.
        // ---------------------------------------------------------------------
        destroyClientsOnShutdown: false,

        // ---------------------------------------------------------------------
        // WebSocket client options
        // ---------------------------------------------------------------------
        client: {
            // the api base endpoint on your actionhero server
            apiPath: '/api',
        }
      }
    }
  }
}
<|endoftext|>


<commit_before>/**
 * Spins up two web servers
 *   - http-server sends the static assets in dist and handles the /config.json endpoint
 *   - cors-proxy adds the necessary headers for xdomain access to the REST API
 */

var pkg = require('./package.json'),
    fs = require('fs'),
    morgan = require('morgan'),
    http = require('http'),
    https = require('https'),
    express = require('express'),
    finalhandler = require('finalhandler'),
    serveFavicon = require('serve-favicon'),
    corsAnywhere = require('cors-anywhere'),

    COOPR_HOME = process.env.COOPR_HOME;
    COOPR_SSL = ('true' == process.env.COOPR_SSL);
    COOPR_UI_PORT = parseInt(process.env.COOPR_UI_PORT || 8080, 10),
    COOPR_UI_SSL_PORT = parseInt(process.env.COOPR_UI_SSL_PORT || 8443, 10),
    COOPR_UI_KEY_FILE = process.env.COOPR_UI_KEY_FILE || COOPR_HOME + 'cert/server.key',
    COOPR_UI_CERT_FILE = process.env.COOPR_UI_CERT_FILE || COOPR_HOME + 'cert/server.crt',
    COOPR_CORS_PORT = parseInt(process.env.COOPR_CORS_PORT || 8081, 10),
    COOPR_SERVER_URI = process.env.COOPR_SERVER_URI || 'http://127.0.0.1:55054',

    color = {
        hilite: function (v) {
            return '\x1B[7m' + v + '\x1B[27m';
        },
        green: function (v) {
            return '\x1B[40m\x1B[32m' + v + '\x1B[39m\x1B[49m';
        },
        pink: function (v) {
            return '\x1B[40m\x1B[35m' + v + '\x1B[39m\x1B[49m';
        }
    },
    sslStarted = false;

morgan.token('cooprcred', function (req, res) {
    return color.pink(req.headers['coopr-userid'] + '/' + req.headers['coopr-tenantid']);
});

var httpLabel = color.green('http'),
    corsLabel = color.pink('cors'),
    httpStaticLogger = morgan(httpLabel + ' :method :url :status'),
    httpIndexLogger = morgan(httpLabel + ' :method ' + color.hilite(':url') + ' :status'),
    corsLogger = morgan(corsLabel + ' :method :url :cooprcred :status', {
        skip: function (req, res) {
            return req.method === 'OPTIONS'
        }
    });

console.log(color.hilite(pkg.name) + ' v' + pkg.version + ' starting up...');

/**
 * HTTP server
 */

var app = express(),
    sslCredentials = null,
    httpServer = http.createServer(app),
    httpsServer = null;

if (COOPR_SSL) {
    sslCredentials = {
        key: fs.readFileSync(COOPR_UI_KEY_FILE, 'utf-8'),
        cert: fs.readFileSync(COOPR_UI_CERT_FILE, 'utf-8')
    };
    httpsServer = https.createServer(sslCredentials, app);
}

try { app.use(serveFavicon(__dirname + '/dist/assets/img/favicon.png')); }
catch(e) { console.error("Favicon missing! Did you run `gulp build`?"); }

// serve the config file
app.get('/config.js', function (req, res) {
    var data = JSON.stringify({
        // the following will be available in angular via the "MY_CONFIG" injectable

        COOPR_SERVER_URI: COOPR_SERVER_URI,
        COOPR_CORS_PORT: COOPR_CORS_PORT,
        authorization: req.headers.authorization

    });
    res.header({
        'Content-Type': 'text/javascript',
        'Cache-Control': 'no-store, must-revalidate'
    });
    res.send('angular.module("' + pkg.name + '.config", [])' +
        '.constant("MY_CONFIG",' + data + ');');
});

// serve static assets
app.use('/assets', [
  httpStaticLogger,
  express.static(__dirname + '/dist/assets', {
    index: false
  }),
  function(req, res) {
    finalhandler(req, res)(false); // 404
  }
]);

app.get('/robots.txt', [
    httpStaticLogger,
    function (req, res) {
        res.type('text/plain');
        res.send('User-agent: *\nDisallow: /');
    }
]);

// any other path, serve index.html
app.all('*', [
    httpIndexLogger,
    function (req, res) {
        res.sendFile(__dirname + '/dist/index.html');
    }
]);

if (!COOPR_SSL) {
    httpServer.listen(COOPR_UI_PORT, null, null, function () {
        console.info(httpLabel + ' listening on port %s', COOPR_UI_PORT);
    });
}

if (COOPR_SSL) {
    httpsServer.listen(COOPR_UI_SSL_PORT, null, null, function () {
        console.info(httpLabel + ' listening on port %s', COOPR_UI_SSL_PORT);
        sslStarted = true;
    });
}

/**
 * CORS proxy
 */
corsAnywhere.createServer({
    requireHeader: ['x-requested-with'],
    removeHeaders: ['cookie', 'cookie2']
})
    .on('request', function (req, res) {
        corsLogger(req, res, function noop() {
        });
    })
    .listen(COOPR_CORS_PORT, '0.0.0.0', function () {
        console.info(corsLabel + ' listening on port %s', COOPR_CORS_PORT);
    });
<commit_msg>cleanup server SSL<commit_after>/**
 * Spins up two web servers
 *   - http-server sends the static assets in dist and handles the /config.json endpoint
 *   - cors-proxy adds the necessary headers for xdomain access to the REST API
 */

var pkg = require('./package.json'),
    fs = require('fs'),
    morgan = require('morgan'),
    http = require('http'),
    https = require('https'),
    express = require('express'),
    finalhandler = require('finalhandler'),
    serveFavicon = require('serve-favicon'),
    corsAnywhere = require('cors-anywhere'),

    COOPR_UI_PORT = parseInt(process.env.COOPR_UI_PORT || 8080, 10),
    COOPR_CORS_PORT = parseInt(process.env.COOPR_CORS_PORT || 8081, 10),
    COOPR_SERVER_URI = process.env.COOPR_SERVER_URI || 'http://127.0.0.1:55054',

    color = {
        hilite: function (v) {
            return '\x1B[7m' + v + '\x1B[27m';
        },
        green: function (v) {
            return '\x1B[40m\x1B[32m' + v + '\x1B[39m\x1B[49m';
        },
        pink: function (v) {
            return '\x1B[40m\x1B[35m' + v + '\x1B[39m\x1B[49m';
        }
    },
    sslStarted = false;

morgan.token('cooprcred', function (req, res) {
    return color.pink(req.headers['coopr-userid'] + '/' + req.headers['coopr-tenantid']);
});

var httpLabel = color.green('http'),
    corsLabel = color.pink('cors'),
    httpStaticLogger = morgan(httpLabel + ' :method :url :status'),
    httpIndexLogger = morgan(httpLabel + ' :method ' + color.hilite(':url') + ' :status'),
    corsLogger = morgan(corsLabel + ' :method :url :cooprcred :status', {
        skip: function (req, res) {
            return req.method === 'OPTIONS'
        }
    });

console.log(color.hilite(pkg.name) + ' v' + pkg.version + ' starting up...');

/**
 * HTTP server
 */

var app = express();

try { app.use(serveFavicon(__dirname + '/dist/assets/img/favicon.png')); }
catch(e) { console.error("Favicon missing! Did you run `gulp build`?"); }

// serve the config file
app.get('/config.js', function (req, res) {
    var data = JSON.stringify({
        // the following will be available in angular via the "MY_CONFIG" injectable

        COOPR_SERVER_URI: COOPR_SERVER_URI,
        COOPR_CORS_PORT: COOPR_CORS_PORT,
        authorization: req.headers.authorization

    });
    res.header({
        'Content-Type': 'text/javascript',
        'Cache-Control': 'no-store, must-revalidate'
    });
    res.send('angular.module("' + pkg.name + '.config", [])' +
        '.constant("MY_CONFIG",' + data + ');');
});

// serve static assets
app.use('/assets', [
  httpStaticLogger,
  express.static(__dirname + '/dist/assets', {
    index: false
  }),
  function(req, res) {
    finalhandler(req, res)(false); // 404
  }
]);

app.get('/robots.txt', [
    httpStaticLogger,
    function (req, res) {
        res.type('text/plain');
        res.send('User-agent: *\nDisallow: /');
    }
]);

// any other path, serve index.html
app.all('*', [
    httpIndexLogger,
    function (req, res) {
        res.sendFile(__dirname + '/dist/index.html');
    }
]);

var httpServer = null;
if ('true' == process.env.COOPR_SSL) {
  httpServer = https.createServer({
      key: fs.readFileSync(process.env.COOPR_UI_KEY_FILE, 'utf-8'),
      cert: fs.readFileSync(process.env.COOPR_UI_CERT_FILE, 'utf-8')
  }, app);
  COOPR_UI_PORT = parseInt(process.env.COOPR_UI_SSL_PORT || 8443, 10);
}
else {
  httpServer = http.createServer(app);
}

httpServer.listen(COOPR_UI_PORT, null, null, function () {
  console.info(httpLabel + ' listening on port %s', COOPR_UI_PORT);
});

/**
 * CORS proxy
 */
corsAnywhere.createServer({
    requireHeader: ['x-requested-with'],
    removeHeaders: ['cookie', 'cookie2']
})
    .on('request', function (req, res) {
        corsLogger(req, res, function noop() {});
    })
    .listen(COOPR_CORS_PORT, '0.0.0.0', function () {
        console.info(corsLabel + ' listening on port %s', COOPR_CORS_PORT);
    });
<|endoftext|>


<commit_before>import d3 from 'd3';

export default function openmetadesign_viz(data) {

    // The container for the viz
    var d3Container = document.getElementById("d3-container");

    // Margins
    // https://bl.ocks.org/mbostock/3019563
    var margin = {
        top: 15,
        right: 10,
        bottom: 10,
        left: 10
    };
    var gutter = 10;

    // Get dimensions of the container on window resize
    window.addEventListener("resize", function(d) {
        width = d3Container.clientWidth;
        height = d3Container.clientHeight;
        console.log(width, height);
    });

    // Add the SVG to the container
    var svg = d3.select('#d3-container').append("svg")
        .attr("width", "100%")
        .attr("height", "100%")
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // Functions for reusable elements
    // Create a button with a FontAwesome icon
    var addButton = function(x, y, radius, parent, iconCode) {

        var button = parent.append("g");

        // Add the circle
        button.append("circle")
            .attr("cx", x)
            .attr("cy", y)
            .attr("r", radius);

        // Add the icon
        button.append('text')
            .attr("x", x)
            .attr("y", y)
            .attr("text-anchor", "middle")
            .attr("dominant-baseline", "central")
            .style("font-family", "FontAwesome")
            .style("font-size", radius.toString() + "px")
            .text(iconCode);

        // Add classes
        button.attr("class", "svg-button")
            .on("mouseover", function() {
                d3.select(this)
                    .attr("filter", "url(#glow)");
            })
            .on("mouseout", function() {
                d3.select(this)
                    .attr("filter", null);
            })

        return button;

    }

    // Create an activity
    var addActivity = function(x, y, parent) {

        // Dimensions
        var participationContainerWidth = 30;
        var mainContainerWidth = 170;

        var activity = parent.append("g");

        // Add the participation container
        var participationContainer = activity.append("g")

        participationContainer.append("rect")
            .attr("x", x)
            .attr("y", y)
            .attr("width", participationContainerWidth)
            .attr("height", 600);

        // Add the main container
        var mainContainer = activity.append("g");

        mainContainer.append("rect")
            .attr("x", x)
            .attr("y", y)
            .attr("width", mainContainerWidth)
            .attr("height", 600);

        // Move the main container beside the participation container
        var mainContainerX = participationContainerWidth + 5;
        mainContainer.attr("transform", "translate(" + mainContainerX + ",0)");

        // Add the title
        var activityTitle = mainContainer.append('foreignObject')
            .attr("x", x)
            .attr("y", y)
            .attr("width", mainContainerWidth)
            .append('xhtml:div')
            .html("
prova asd asd ad as da
")
            .attr("class", "svg-activity-title");

        // Add the control buttons
        var activityButtons = mainContainer.append("g");
        // Discuss Button
        var discussButton = addButton(x + 5, y, 10, activityButtons, '\uf086');
        discussButton.attr("data-toggle", "modal")
            .classed("discuss-button", true);
        // Edit Button
        var editButton = addButton(x + 30, y, 10, activityButtons, '\uf044');
        editButton.attr("data-toggle", "modal")
            .classed("edit-button", true);
        // Flows Button
        var flowsButton = addButton(x + 55, y, 10, activityButtons, '\uf074');
        flowsButton.attr("data-toggle", "modal")
            .classed("flows-button", true);
        // Issues Button
        var issuesButton = addButton(x + 80, y, 10, activityButtons, '\uf071');
        issuesButton.attr("data-toggle", "modal")
            .classed("issues-button", true);
        // Delete Button
        var deleteButton = addButton(x + 105, y, 10, activityButtons, '\uf068');
        deleteButton.attr("data-toggle", "modal")
            .classed("delete-button", true);
        // Move the buttons below the title
        activityButtonY = 10 + // padding
            20 + // button size
            parseInt(activityTitle.style("height")); // title size
        activityButtons.attr("transform", "translate(15," + activityButtonY + ")");

        // Add classes
        mainContainer.attr("class", "svg-activity");
        participationContainer.attr("class", "svg-activity-participation");

        return activity;

    }

    // Filters
    var defs = svg.append("defs");
    // Glow filter
    var glow = defs.append("filter")
        .attr("id", "glow");
    glow.append("feGaussianBlur")
        .attr("stdDeviation", "3.5")
        .attr("result", "coloredBlur");
    var feMerge = glow.append("feMerge");
    feMerge.append("feMergeNode")
        .attr("in", "coloredBlur");
    feMerge.append("feMergeNode")
        .attr("in", "SourceGraphic");

    // Draw everything

    // Layout initialization
    var timeG = svg.append("g");
    var journeyG = svg.append("g");
    var blueprintG = svg.append("g");

    // Debug: see the border of each group
    svg.attr("style", "outline: thin solid black;");
    timeG.attr("style", "outline: thin solid green;");
    journeyG.attr("style", "outline: thin solid red;");
    blueprintG.attr("style", "outline: thin solid blue;");

    // Draw the Time section
    var yScale = d3.scaleTime()
        .domain([new Date(2000, 0, 1), new Date(2001, 0, 11)])
        .range([0, 800]);
    yAxis = d3.axisLeft().scale(yScale)
        .ticks(16)
        .tickSize(10);
    timeG.attr("id", "yAxisG")
        .call(yAxis);

    // Draw the Journey section
    journeyG.append("rect")
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", 100)
        .attr("height", 20)
        .attr("fill", "orange");

    // Create a sample button
    var button2 = addButton(20, 120, 20, journeyG, '\uf06e');
    // Make it open a modal
    button2.attr("data-toggle", "modal");
    // Add its functioning on click
    button2.on("click", function() {
        blueprintG.transition()
            .duration(500)
            .attr("transform", "scale(0.2,1)");
    });

    // Draw the Blueprint section
    blueprintG.append("rect")
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", 340)
        .attr("height", 50)
        .attr("fill", "red");

    // Create a sample activity
    var activity2 = addActivity(20, 120, blueprintG);

    // Layout: organize sections
    // In case we need to get the transform of an element: https://stackoverflow.com/a/38753017/2237113
    // Translate timeG according to the label width
    var timeGX = timeG.node().getBBox().width;
    timeG.attr("transform", "translate(" + timeGX + "," + 0 + ")");
    // Translate journeyG it after the timeG section
    var journeyGX = timeGX + timeG.node().getBBox().x + timeG.node().getBBox().width + gutter;
    journeyG.attr("transform", "translate(" + journeyGX + "," + 0 + ")");
    // Translate blueprintG after the journeyG section
    var blueprintGX = journeyGX + journeyG.node().getBBox().width + gutter;
    blueprintG.attr("transform", "translate(" + blueprintGX + "," + 0 + ")");

}
<commit_msg>Viz: fix glow size for small buttons<commit_after>import d3 from 'd3';

export default function openmetadesign_viz(data) {

    // The container for the viz
    var d3Container = document.getElementById("d3-container");

    // Margins
    // https://bl.ocks.org/mbostock/3019563
    var margin = {
        top: 15,
        right: 10,
        bottom: 10,
        left: 10
    };
    var gutter = 10;

    // Get dimensions of the container on window resize
    window.addEventListener("resize", function(d) {
        width = d3Container.clientWidth;
        height = d3Container.clientHeight;
        console.log(width, height);
    });

    // Add the SVG to the container
    var svg = d3.select('#d3-container').append("svg")
        .attr("width", "100%")
        .attr("height", "100%")
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // Functions for reusable elements
    // Create a button with a FontAwesome icon
    var addButton = function(x, y, radius, parent, iconCode) {

        var button = parent.append("g");

        // Add the circle
        button.append("circle")
            .attr("cx", x)
            .attr("cy", y)
            .attr("r", radius);

        // Add the icon
        button.append('text')
            .attr("x", x)
            .attr("y", y)
            .attr("text-anchor", "middle")
            .attr("dominant-baseline", "central")
            .style("font-family", "FontAwesome")
            .style("font-size", radius.toString() + "px")
            .text(iconCode);

        // Add classes
        button.attr("class", "svg-button")
            .on("mouseover", function() {
                d3.select(this)
                    .attr("filter", "url(#glow)");
            })
            .on("mouseout", function() {
                d3.select(this)
                    .attr("filter", null);
            })

        return button;

    }

    // Create an activity
    var addActivity = function(x, y, parent) {

        // Dimensions
        var participationContainerWidth = 30;
        var mainContainerWidth = 170;

        var activity = parent.append("g");

        // Add the participation container
        var participationContainer = activity.append("g")

        participationContainer.append("rect")
            .attr("x", x)
            .attr("y", y)
            .attr("width", participationContainerWidth)
            .attr("height", 600);

        // Add the main container
        var mainContainer = activity.append("g");

        mainContainer.append("rect")
            .attr("x", x)
            .attr("y", y)
            .attr("width", mainContainerWidth)
            .attr("height", 600);

        // Move the main container beside the participation container
        var mainContainerX = participationContainerWidth + 5;
        mainContainer.attr("transform", "translate(" + mainContainerX + ",0)");

        // Add the title
        var activityTitle = mainContainer.append('foreignObject')
            .attr("x", x)
            .attr("y", y)
            .attr("width", mainContainerWidth)
            .append('xhtml:div')
            .html("
prova asd asd ad as da
")
            .attr("class", "svg-activity-title");

        // Add the control buttons
        var activityButtons = mainContainer.append("g");
        // Discuss Button
        var discussButton = addButton(x + 5, y, 10, activityButtons, '\uf086');
        discussButton.attr("data-toggle", "modal")
            .classed("discuss-button", true);
        // Edit Button
        var editButton = addButton(x + 30, y, 10, activityButtons, '\uf044');
        editButton.attr("data-toggle", "modal")
            .classed("edit-button", true);
        // Flows Button
        var flowsButton = addButton(x + 55, y, 10, activityButtons, '\uf074');
        flowsButton.attr("data-toggle", "modal")
            .classed("flows-button", true);
        // Issues Button
        var issuesButton = addButton(x + 80, y, 10, activityButtons, '\uf071');
        issuesButton.attr("data-toggle", "modal")
            .classed("issues-button", true);
        // Delete Button
        var deleteButton = addButton(x + 105, y, 10, activityButtons, '\uf068');
        deleteButton.attr("data-toggle", "modal")
            .classed("delete-button", true);
        // Move the buttons below the title
        activityButtonY = 10 + // padding
            20 + // button size
            parseInt(activityTitle.style("height")); // title size
        activityButtons.attr("transform", "translate(15," + activityButtonY + ")");

        // Add classes
        mainContainer.attr("class", "svg-activity");
        participationContainer.attr("class", "svg-activity-participation");

        return activity;

    }

    // Filters
    var defs = svg.append("defs");
    // Glow filter
    var glow = defs.append("filter")
        .attr("id", "glow");
    glow.append("feGaussianBlur")
        .attr("stdDeviation", "1.5")
        .attr("result", "coloredBlur");
    var feMerge = glow.append("feMerge");
    feMerge.append("feMergeNode")
        .attr("in", "coloredBlur");
    feMerge.append("feMergeNode")
        .attr("in", "SourceGraphic");

    // Draw everything

    // Layout initialization
    var timeG = svg.append("g");
    var journeyG = svg.append("g");
    var blueprintG = svg.append("g");

    // Debug: see the border of each group
    svg.attr("style", "outline: thin solid black;");
    timeG.attr("style", "outline: thin solid green;");
    journeyG.attr("style", "outline: thin solid red;");
    blueprintG.attr("style", "outline: thin solid blue;");

    // Draw the Time section
    var yScale = d3.scaleTime()
        .domain([new Date(2000, 0, 1), new Date(2001, 0, 11)])
        .range([0, 800]);
    yAxis = d3.axisLeft().scale(yScale)
        .ticks(16)
        .tickSize(10);
    timeG.attr("id", "yAxisG")
        .call(yAxis);

    // Draw the Journey section
    journeyG.append("rect")
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", 100)
        .attr("height", 20)
        .attr("fill", "orange");

    // Create a sample button
    var button2 = addButton(20, 120, 20, journeyG, '\uf06e');
    // Make it open a modal
    button2.attr("data-toggle", "modal");
    // Add its functioning on click
    button2.on("click", function() {
        blueprintG.transition()
            .duration(500)
            .attr("transform", "scale(0.2,1)");
    });

    // Draw the Blueprint section
    blueprintG.append("rect")
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", 340)
        .attr("height", 50)
        .attr("fill", "red");

    // Create a sample activity
    var activity2 = addActivity(20, 120, blueprintG);

    // Layout: organize sections
    // In case we need to get the transform of an element: https://stackoverflow.com/a/38753017/2237113
    // Translate timeG according to the label width
    var timeGX = timeG.node().getBBox().width;
    timeG.attr("transform", "translate(" + timeGX + "," + 0 + ")");
    // Translate journeyG it after the timeG section
    var journeyGX = timeGX + timeG.node().getBBox().x + timeG.node().getBBox().width + gutter;
    journeyG.attr("transform", "translate(" + journeyGX + "," + 0 + ")");
    // Translate blueprintG after the journeyG section
    var blueprintGX = journeyGX + journeyG.node().getBBox().width + gutter;
    blueprintG.attr("transform", "translate(" + blueprintGX + "," + 0 + ")");

}
<|endoftext|>