<commit_before>'use strict';

import Backbone from 'backbone';
import _ from 'underscore';
import $ from 'jquery';
import d3 from 'd3';
import moment from 'moment';

import utils from '../../scripts/helpers/utils';
import './styles.postcss';

const defaults = {
  milestones: [
    // { date: new Date(2012, 3, 15) },
    // { date: new Date(2012, 4, 27) },
    // { date: new Date(2013, 2, 21) },
    // { date: new Date(2014, 8, 2)  }
  ],
  svgPadding: {
    top: 0,
    right: 15,
    bottom: 0,
    left: 15
  },
  ticksAtExtremities: false
};

class TimelineView extends Backbone.View {

  events() {
    return {
      'click .js-button': 'togglePlay'
    };
  }

  initialize(options) {
    this.options = _.extend(defaults, options);

    /* Cache */
    this.svgContainer = this.el.querySelector('.js-svg-container');
    this.button = this.el.querySelector('.js-button');
    this.buttonIcon = this.el.querySelector('.js-button-icon');

    /* Position of the cursor
     * NOTE: doesn't contain a position in pixels but a date */
    if(this.options.cursorPosition) {
      this.cursorPosition = this.options.cursorPosition
    } else {
      this.cursorPosition = this.options.domain[this.options.domain.length - 1];
    }

    this.render();
    this.setListeners();
  }

  setListeners() {
    $(window).resize(_.debounce(this.render, 50).bind(this));
  }

  render() {
    const smallScreen = utils.checkDevice().mobile ||
      utils.checkDevice().tablet;

    const svgContainerDimensions = this.svgContainer.getBoundingClientRect();

    const svgPadding = Object.assign({}, this.options.svgPadding);

    /* When we have ticks at the extremities (whose format is longer), we add
     * more padding */
    if(this.options.ticksAtExtremities) {
      svgPadding.left  += 15;
      svgPadding.right += 15;
    }

    const svgWidth = svgContainerDimensions.width - svgPadding.left
      - svgPadding.right;
    const svgHeight = svgContainerDimensions.height - svgPadding.top
      - svgPadding.bottom;

    /* Because d3 doesn't display the first tick, we subtract 1 day to it.
     * NOTE: concat and clone are used to not modify the original array */
    const domain = this.options.domain.concat([]);
    domain[0] = moment.utc(domain[0]).clone().subtract(1, 'days').toDate();

    /* We force the cursor to be within the domain */
    if(+this.cursorPosition > +this.options.domain[1]) this.cursorPosition = this.options.domain[1];
    if(+this.cursorPosition < +this.options.domain[0]) this.cursorPosition = this.options.domain[0];

    this.scale = d3.time.scale.utc()
      .domain(domain)
      .range([0, svgWidth]);

    /* List of the dates for which we want ticks */
    let ticksDates = d3.time.year.utc.range(domain[0], domain[1], 1);

    if(this.options.ticksAtExtremities) {
      ticksDates = ticksDates.concat(domain)
        .sort((a, b) => (+a) - (+b)); /* Compulsory */
    }

    this.axis = d3.svg.axis()
      .scale(this.scale)
      .orient('top')
      .tickValues(ticksDates)
      .tickFormat((d, i) => {
        /* The ticks at the extremities are the whole date and not just the
         * year */
        if(this.options.ticksAtExtremities) {
          if(i === 0 || i === ticksDates.length - 1) {
            return moment.utc(d).format('MM·DD·YYYY');
          }
          return;
        }
        return d.getUTCFullYear();
      })
      .outerTickSize(0);

    this.svgContainer.innerHTML = '';

    this.svg = d3.select(this.svgContainer)
      .append('svg')
        .attr('width', svgContainerDimensions.width)
        .attr('height', svgContainerDimensions.height)
        .append('g')
          .attr('transform', `translate(${svgPadding.left}, ${svgPadding.top})`);

    this.d3Axis = this.svg
        .append('g')
          .attr('class', 'axis')
          .style('stroke-dasharray', '6, 6')
          .attr('transform', 'translate(0, ' + (svgContainerDimensions.height / 2 + 4) + ')')
          .call(this.axis);

    /* We need it to calculate the position of the brush */
    this.axis = this.d3Axis[0][0];

    this.brush = d3.svg.brush()
      .x(this.scale)
      .extent([this.options.domain[1], this.options.domain[1]])
      .clamp(true)
      .on('brushstart', this.onCursorStartDrag.bind(this))
      .on('brush', this.onCursorDrag.bind(this))
      .on('brushend', this.onCursorEndDrag.bind(this));

    /* Cursor line - needs to be under the ticks */
    this.d3CursorLine = this.d3Axis
      .append('line')
      .attr('x1', 0)
      .attr('x2', this.scale(this.cursorPosition))
      .attr('y1', 0)
      .attr('y2', 0)
      .style('stroke-dasharray', 'none')
      .attr('class', 'cursor-line');

    /* We add the ticks for the report */
    this.d3Axis.selectAll('.tick')
      .append('rect')
      .attr('width', smallScreen ? 5 : 6)
      .attr('height', smallScreen ? 5 : 6)
      .attr('x', smallScreen ? -2.5 : -3)
      .attr('y', smallScreen ? -2.5 : -3)
      .attr('transform', 'rotate(45)')
      .attr('class', 'report');

    /* We slightly move the ticks' text to the top and center it */
    this.d3Axis.selectAll('.tick text')
      .attr('y', smallScreen ? -11 : -15)
      .style('text-anchor', 'middle');

    /* We add the milestones */
    this.d3Axis.selectAll('.milestone')
      .data(this.options.milestones)
      .enter()
        .append('rect')
        .attr('x', d => this.scale(d.date))
        .attr('y', -4)
        .attr('width', 8)
        .attr('height', 8)
        .attr('class', 'milestone');

    /* We add the cursor */
    const d3Slider = this.d3Axis
      .append('g')
      .attr('class', 'slider')
      .call(this.brush);

    d3Slider.selectAll('.extent, .resize, .background')
      .remove();

    this.d3Cursor = d3Slider
      .attr('transform', () => `translate(${this.scale(this.cursorPosition)})`);

    /* We add the blurred shadow of the cursor */
    this.svg
      .append('defs')
        .append('filter')
        .attr('id', 'cursorShadow')
          .append('feGaussianBlur')
          .attr('stdDeviation', !~navigator.userAgent.toLowerCase().indexOf('firefox') ? 2 : 1);

    this.cursorShadow = this.d3Cursor
      .append('circle')
      .attr('cx', 0)
      .attr('cy', 0)
      .attr('r', smallScreen ? 6 : 10)
      .attr('fill', '#686354')
      .attr('class', 'cursor-shadow');

    /* We add the real cursor */
    this.d3Cursor
      .append('circle')
      .on('mouseover', () => this.cursorShadow.attr('filter', 'url(#cursorShadow)'))
      .on('mouseout', () => this.cursorShadow.attr('filter', ''))
      .attr('cx', 0)
      .attr('cy', 0)
      .attr('r', smallScreen ? 6 : 10)
      .attr('class', 'cursor')
      .call(this.brush.event);

    this.options.data = this.options.interval.unit.utc.range.apply(null, this.options.domain.concat(this.options.interval.count))
      .map(date => ({ date }));

    this.currentDataIndex = this.getClosestDataIndex(this.cursorPosition);
    this.triggerDate()
  }

  togglePlay() {
    if(!this.playing) {
      this.play();
    } else {
      this.stop();
    }
  }

  play() {
    if(this.playing) return;

    this.playing = true;
    this.buttonIcon.setAttribute('xlink:href', '#icon-pause');

    /* We move the cursor at the beginning if it's at the end */
    if(this.cursorPosition === this.options.domain[1]) {
      this.moveCursor(this.options.domain[0]);
    }

    /* We compute the number of day we need to jump for each animation frame,
     * assuming that the animation loop is called at 60 FPS
     * We prefer this methods than taking into account the position of the
     * cursor (in pixels) as on small screen the approximation of one pixel
     * can represent a greater error/jump. */
    this.dayPerFrame = 0.017 * 360 / this.options.cursor.speed;

    this.animationFrame = requestAnimationFrame(this.renderAnimationFrame.bind(this));
  }

  stop() {
    if(!this.playing) return;

    this.playing = false;
    this.buttonIcon.setAttribute('xlink:href', '#icon-play');

    cancelAnimationFrame(this.animationFrame);
    this.animationFrame = null;

    /* This functionality has been removed because it was looking weird on
     * the projects mode */
    // /* We place the cursor at the end of the timeline if we reached the end */
    // if(this.currentDataIndex === this.options.data.length - 1) {
    //   this.cursorPosition = this.options.domain[1];
    //   this.moveCursor(this.cursorPosition);
    // }
  }

  renderAnimationFrame() {
    /* The first time the animation is requested, we place the cursor at the
     * beginning of the timeline */
    if(this.currentDataIndex === null || this.currentDataIndex === undefined ||
      this.cursorPosition === this.options.domain[1]) {
      this.currentDataIndex = -1;
      this.cursorPosition = this.options.domain[0];
      this.triggerDate();
      this.moveCursor(this.cursorPosition);
    } else {
      this.cursorPosition = this.dayOffset(this.cursorPosition, this.dayPerFrame);

      /* We don't want to overpass the domain */
      if(this.cursorPosition > this.options.domain[1]) {
        this.cursorPosition = this.options.domain[1];
      }

      this.moveCursor(this.cursorPosition);
    }

    /* If the cursor has been moved above the next date with data, we set the
     * next data as the current ones and trigger them */
    if(this.currentDataIndex < this.options.data.length - 1 &&
      this.cursorPosition >= this.options.data[this.currentDataIndex + 1].date) {
      this.currentDataIndex++;
      this.triggerDate();
    }

    /* If we don't reach the end, we request another animation, otherwise we move
     * the cursor to its last position on the timeline */
    if(this.cursorPosition < this.options.domain[1]) {
      this.animationFrame = requestAnimationFrame(this.renderAnimationFrame.bind(this));
    } else {
      this.stop();
    }
  }

  moveCursor(date) {
    this.brush.extent([date, date]);
    this.d3Cursor.attr('transform', () => `translate(${this.scale(date)})`);
    this.d3CursorLine.attr('x2', this.scale(date));
  }

  /* Compute and return date with the passed offset
   * NOTE: d3.time.day.offset can't be used because the use of float numbers are
   * not crossbrowser-standardized yet on d3 3.5.16:
   * https://github.com/mbostock/d3/issues/2790 */
  dayOffset(date, offset) {
    return new Date(+date + offset * 24 * 60 * 60 * 1000);
  }

  onCursorStartDrag() {
    this.stop();
    document.body.classList.add('-grabbing');
  }

  onCursorEndDrag() {
    this.cursorShadow.attr('filter', '')
    document.body.classList.remove('-grabbing');
  }

  onCursorDrag() {
    if(!d3.event.sourceEvent) return;

    this.cursorShadow.attr('filter', 'url(#cursorShadow)')

    let date = this.scale.invert(d3.mouse(this.axis)[0]);
    if(date > this.options.domain[1]) date = this.options.domain[1];
    if(date < this.options.domain[0]) date = this.options.domain[0];

    const dataIndex = this.getClosestDataIndex(date);
    if(dataIndex !== this.currentDataIndex) {
      this.currentDataIndex = this.getClosestDataIndex(date);
      /* We trigger the range of the currently available data */
      this.triggerDate();
    }

    this.cursorPosition = date;
    this.moveCursor(date);
  }

  /* Return the index of the data item with the closest date to the one passed
   * as argument */
  getClosestDataIndex(date) {
    var current = 0;
    while(current <= this.options.data.length - 1) {
      if(this.options.data[current].date > date) {
        if(current === 0) return -1;
        return current - 1;
      }
      current++;
    }
    return this.options.data.length - 1;
  }

  setCursorPosition(date) {
    if(!moment.utc(this.cursorPosition).isSame(date)) {
      this.cursorPosition = date;
      this.render();
      this.currentDataIndex = this.getClosestDataIndex(this.cursorPosition);
      this.triggerDate()
    }
  }

};

TimelineView.prototype.triggerDate = (function() {

  const trigger = _.debounce(function(date) {
    this.options.triggerDate(date);
  }, 100, true);

  return function() {
    if(this.currentDataIndex < 0) {
      trigger.call(this, this.options.domain[0]);
    } else {
      trigger.call(this, this.options.data[this.currentDataIndex].date);
    }
  };

})();

export default TimelineView;
<commit_msg>removed substract one day to timeline domain to avoid draw invalid dates when aplying filters<commit_after>'use strict';

import Backbone from 'backbone';
import _ from 'underscore';
import $ from 'jquery';
import d3 from 'd3';
import moment from 'moment';

import utils from '../../scripts/helpers/utils';
import './styles.postcss';

const defaults = {
  milestones: [
    // { date: new Date(2012, 3, 15) },
    // { date: new Date(2012, 4, 27) },
    // { date: new Date(2013, 2, 21) },
    // { date: new Date(2014, 8, 2)  }
  ],
  svgPadding: {
    top: 0,
    right: 15,
    bottom: 0,
    left: 15
  },
  ticksAtExtremities: false
};

class TimelineView extends Backbone.View {

  events() {
    return {
      'click .js-button': 'togglePlay'
    };
  }

  initialize(options) {
    this.options = _.extend(defaults, options);

    /* Cache */
    this.svgContainer = this.el.querySelector('.js-svg-container');
    this.button = this.el.querySelector('.js-button');
    this.buttonIcon = this.el.querySelector('.js-button-icon');

    /* Position of the cursor
     * NOTE: doesn't contain a position in pixels but a date */
    if(this.options.cursorPosition) {
      this.cursorPosition = this.options.cursorPosition
    } else {
      this.cursorPosition = this.options.domain[this.options.domain.length - 1];
    }

    this.render();
    this.setListeners();
  }

  setListeners() {
    $(window).resize(_.debounce(this.render, 50).bind(this));
  }

  render() {
    const smallScreen = utils.checkDevice().mobile ||
      utils.checkDevice().tablet;

    const svgContainerDimensions = this.svgContainer.getBoundingClientRect();

    const svgPadding = Object.assign({}, this.options.svgPadding);

    /* When we have ticks at the extremities (whose format is longer), we add
     * more padding */
    if(this.options.ticksAtExtremities) {
      svgPadding.left  += 15;
      svgPadding.right += 15;
    }

    const svgWidth = svgContainerDimensions.width - svgPadding.left
      - svgPadding.right;
    const svgHeight = svgContainerDimensions.height - svgPadding.top
      - svgPadding.bottom;

    /* Because d3 doesn't display the first tick, we subtract 1 day to it.
     ** I removed this solution because it was showing 31-12-2010, that is not an existing date when aplying filters and it draws final points anyway.
     * NOTE: concat and clone are used to not modify the original array */
    const domain = this.options.domain.concat([]);
    domain[0] = moment.utc(domain[0]).clone().toDate();
    /* We force the cursor to be within the domain */
    if(+this.cursorPosition > +this.options.domain[1]) this.cursorPosition = this.options.domain[1];
    if(+this.cursorPosition < +this.options.domain[0]) this.cursorPosition = this.options.domain[0];

    this.scale = d3.time.scale.utc()
      .domain(domain)
      .range([0, svgWidth]);

    /* List of the dates for which we want ticks */
    let ticksDates = d3.time.year.utc.range(domain[0], domain[1], 1);

    if(this.options.ticksAtExtremities) {
      ticksDates = ticksDates.concat(domain)
        .sort((a, b) => (+a) - (+b)); /* Compulsory */
    }

    this.axis = d3.svg.axis()
      .scale(this.scale)
      .orient('top')
      .tickValues(ticksDates)
      .tickFormat((d, i) => {
        /* The ticks at the extremities are the whole date and not just the
         * year */
        if(this.options.ticksAtExtremities) {
          if(i === 0 || i === ticksDates.length - 1) {
            return moment.utc(d).format('MM·DD·YYYY');
          }
          return;
        }
        return d.getUTCFullYear();
      })
      .outerTickSize(0);

    this.svgContainer.innerHTML = '';

    this.svg = d3.select(this.svgContainer)
      .append('svg')
        .attr('width', svgContainerDimensions.width)
        .attr('height', svgContainerDimensions.height)
        .append('g')
          .attr('transform', `translate(${svgPadding.left}, ${svgPadding.top})`);

    this.d3Axis = this.svg
        .append('g')
          .attr('class', 'axis')
          .style('stroke-dasharray', '6, 6')
          .attr('transform', 'translate(0, ' + (svgContainerDimensions.height / 2 + 4) + ')')
          .call(this.axis);

    /* We need it to calculate the position of the brush */
    this.axis = this.d3Axis[0][0];

    this.brush = d3.svg.brush()
      .x(this.scale)
      .extent([this.options.domain[1], this.options.domain[1]])
      .clamp(true)
      .on('brushstart', this.onCursorStartDrag.bind(this))
      .on('brush', this.onCursorDrag.bind(this))
      .on('brushend', this.onCursorEndDrag.bind(this));

    /* Cursor line - needs to be under the ticks */
    this.d3CursorLine = this.d3Axis
      .append('line')
      .attr('x1', 0)
      .attr('x2', this.scale(this.cursorPosition))
      .attr('y1', 0)
      .attr('y2', 0)
      .style('stroke-dasharray', 'none')
      .attr('class', 'cursor-line');

    /* We add the ticks for the report */
    this.d3Axis.selectAll('.tick')
      .append('rect')
      .attr('width', smallScreen ? 5 : 6)
      .attr('height', smallScreen ? 5 : 6)
      .attr('x', smallScreen ? -2.5 : -3)
      .attr('y', smallScreen ? -2.5 : -3)
      .attr('transform', 'rotate(45)')
      .attr('class', 'report');

    /* We slightly move the ticks' text to the top and center it */
    this.d3Axis.selectAll('.tick text')
      .attr('y', smallScreen ? -11 : -15)
      .style('text-anchor', 'middle');

    /* We add the milestones */
    this.d3Axis.selectAll('.milestone')
      .data(this.options.milestones)
      .enter()
        .append('rect')
        .attr('x', d => this.scale(d.date))
        .attr('y', -4)
        .attr('width', 8)
        .attr('height', 8)
        .attr('class', 'milestone');

    /* We add the cursor */
    const d3Slider = this.d3Axis
      .append('g')
      .attr('class', 'slider')
      .call(this.brush);

    d3Slider.selectAll('.extent, .resize, .background')
      .remove();

    this.d3Cursor = d3Slider
      .attr('transform', () => `translate(${this.scale(this.cursorPosition)})`);

    /* We add the blurred shadow of the cursor */
    this.svg
      .append('defs')
        .append('filter')
        .attr('id', 'cursorShadow')
          .append('feGaussianBlur')
          .attr('stdDeviation', !~navigator.userAgent.toLowerCase().indexOf('firefox') ? 2 : 1);

    this.cursorShadow = this.d3Cursor
      .append('circle')
      .attr('cx', 0)
      .attr('cy', 0)
      .attr('r', smallScreen ? 6 : 10)
      .attr('fill', '#686354')
      .attr('class', 'cursor-shadow');

    /* We add the real cursor */
    this.d3Cursor
      .append('circle')
      .on('mouseover', () => this.cursorShadow.attr('filter', 'url(#cursorShadow)'))
      .on('mouseout', () => this.cursorShadow.attr('filter', ''))
      .attr('cx', 0)
      .attr('cy', 0)
      .attr('r', smallScreen ? 6 : 10)
      .attr('class', 'cursor')
      .call(this.brush.event);

    this.options.data = this.options.interval.unit.utc.range.apply(null, this.options.domain.concat(this.options.interval.count))
      .map(date => ({ date }));

    this.currentDataIndex = this.getClosestDataIndex(this.cursorPosition);
    this.triggerDate()
  }

  togglePlay() {
    if(!this.playing) {
      this.play();
    } else {
      this.stop();
    }
  }

  play() {
    if(this.playing) return;

    this.playing = true;
    this.buttonIcon.setAttribute('xlink:href', '#icon-pause');

    /* We move the cursor at the beginning if it's at the end */
    if(this.cursorPosition === this.options.domain[1]) {
      this.moveCursor(this.options.domain[0]);
    }

    /* We compute the number of day we need to jump for each animation frame,
     * assuming that the animation loop is called at 60 FPS
     * We prefer this methods than taking into account the position of the
     * cursor (in pixels) as on small screen the approximation of one pixel
     * can represent a greater error/jump. */
    this.dayPerFrame = 0.017 * 360 / this.options.cursor.speed;

    this.animationFrame = requestAnimationFrame(this.renderAnimationFrame.bind(this));
  }

  stop() {
    if(!this.playing) return;

    this.playing = false;
    this.buttonIcon.setAttribute('xlink:href', '#icon-play');

    cancelAnimationFrame(this.animationFrame);
    this.animationFrame = null;

    /* This functionality has been removed because it was looking weird on
     * the projects mode */
    // /* We place the cursor at the end of the timeline if we reached the end */
    // if(this.currentDataIndex === this.options.data.length - 1) {
    //   this.cursorPosition = this.options.domain[1];
    //   this.moveCursor(this.cursorPosition);
    // }
  }

  renderAnimationFrame() {
    /* The first time the animation is requested, we place the cursor at the
     * beginning of the timeline */
    if(this.currentDataIndex === null || this.currentDataIndex === undefined ||
      this.cursorPosition === this.options.domain[1]) {
      this.currentDataIndex = -1;
      this.cursorPosition = this.options.domain[0];
      this.triggerDate();
      this.moveCursor(this.cursorPosition);
    } else {
      this.cursorPosition = this.dayOffset(this.cursorPosition, this.dayPerFrame);

      /* We don't want to overpass the domain */
      if(this.cursorPosition > this.options.domain[1]) {
        this.cursorPosition = this.options.domain[1];
      }

      this.moveCursor(this.cursorPosition);
    }

    /* If the cursor has been moved above the next date with data, we set the
     * next data as the current ones and trigger them */
    if(this.currentDataIndex < this.options.data.length - 1 &&
      this.cursorPosition >= this.options.data[this.currentDataIndex + 1].date) {
      this.currentDataIndex++;
      this.triggerDate();
    }

    /* If we don't reach the end, we request another animation, otherwise we move
     * the cursor to its last position on the timeline */
    if(this.cursorPosition < this.options.domain[1]) {
      this.animationFrame = requestAnimationFrame(this.renderAnimationFrame.bind(this));
    } else {
      this.stop();
    }
  }

  moveCursor(date) {
    this.brush.extent([date, date]);
    this.d3Cursor.attr('transform', () => `translate(${this.scale(date)})`);
    this.d3CursorLine.attr('x2', this.scale(date));
  }

  /* Compute and return date with the passed offset
   * NOTE: d3.time.day.offset can't be used because the use of float numbers are
   * not crossbrowser-standardized yet on d3 3.5.16:
   * https://github.com/mbostock/d3/issues/2790 */
  dayOffset(date, offset) {
    return new Date(+date + offset * 24 * 60 * 60 * 1000);
  }

  onCursorStartDrag() {
    this.stop();
    document.body.classList.add('-grabbing');
  }

  onCursorEndDrag() {
    this.cursorShadow.attr('filter', '')
    document.body.classList.remove('-grabbing');
  }

  onCursorDrag() {
    if(!d3.event.sourceEvent) return;

    this.cursorShadow.attr('filter', 'url(#cursorShadow)')

    let date = this.scale.invert(d3.mouse(this.axis)[0]);
    if(date > this.options.domain[1]) date = this.options.domain[1];
    if(date < this.options.domain[0]) date = this.options.domain[0];

    const dataIndex = this.getClosestDataIndex(date);
    if(dataIndex !== this.currentDataIndex) {
      this.currentDataIndex = this.getClosestDataIndex(date);
      /* We trigger the range of the currently available data */
      this.triggerDate();
    }

    this.cursorPosition = date;
    this.moveCursor(date);
  }

  /* Return the index of the data item with the closest date to the one passed
   * as argument */
  getClosestDataIndex(date) {
    var current = 0;
    while(current <= this.options.data.length - 1) {
      if(this.options.data[current].date > date) {
        if(current === 0) return -1;
        return current - 1;
      }
      current++;
    }
    return this.options.data.length - 1;
  }

  setCursorPosition(date) {
    if(!moment.utc(this.cursorPosition).isSame(date)) {
      this.cursorPosition = date;
      this.render();
      this.currentDataIndex = this.getClosestDataIndex(this.cursorPosition);
      this.triggerDate()
    }
  }

};

TimelineView.prototype.triggerDate = (function() {

  const trigger = _.debounce(function(date) {
    this.options.triggerDate(date);
  }, 100, true);

  return function() {
    if(this.currentDataIndex < 0) {
      trigger.call(this, this.options.domain[0]);
    } else {
      trigger.call(this, this.options.data[this.currentDataIndex].date);
    }
  };

})();

export default TimelineView;
<|endoftext|>


<commit_before>/**
 * @author lijh
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 位置动画组件，起始位置和目标位置由两个Node决定，无须指定绝对位置
 * @class qc.TweenTransform
 */
var TweenTransform = defineBehaviour('qc.TweenTransform', qc.Tween, function() {
    var self = this;

    // 默认情况下不可用
    self.enable = false;

    // 起始位置的本地坐标
    self.localFrom = new qc.Point();

    // 目标位置的本地坐标
    self.localTo = new qc.Point();
},{
    from : qc.Serializer.NODE,
    to : qc.Serializer.NODE
});

// 菜单上的显示
TweenTransform.__menu = 'Tween/TweenTransform';

// 初始化：计算起点和目标点位置对应的本地坐标
TweenTransform.prototype.awake = function() {
    var self = this;

    if (!self.from || !self.to)
        return;

    var worldFrom = self.from.getWorldPosition();
    var worldTo   = self.to.getWorldPosition();

    self.localFrom = this.gameObject.parent.toLocal(worldFrom);
    self.localTo   = this.gameObject.parent.toLocal(worldTo);
};

// 帧调度: 驱动位置
TweenTransform.prototype.onUpdate = function(factor, isFinished) {
    var self = this;

    self.gameObject.x = self.localFrom.x + factor * (self.localTo.x - self.localFrom.x);
    self.gameObject.y = self.localFrom.y + factor * (self.localTo.y - self.localFrom.y);
};
<commit_msg>TweenTransform 支持动态设置 from/to<commit_after>/**
 * @author lijh
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 位置动画组件，起始位置和目标位置由两个Node决定，无须指定绝对位置
 * @class qc.TweenTransform
 */
var TweenTransform = defineBehaviour('qc.TweenTransform', qc.Tween, function() {
    var self = this;

    // 默认情况下不可用
    self.enable = false;

    // 起始位置的本地坐标
    self.localFrom = new qc.Point();

    // 目标位置的本地坐标
    self.localTo = new qc.Point();
},{
    from : qc.Serializer.NODE,
    to : qc.Serializer.NODE
});

// 菜单上的显示
TweenTransform.__menu = 'Tween/TweenTransform';

Object.defineProperties(TweenTransform.prototype, {
    /**
     * @property {qc.Node} from - 起始位置
     */
    from : {
        get : function() {
            return this._from;
        },
        set : function(v) {
            this._from = v;

           var worldFrom = this._from.getWorldPosition();
           this.localFrom = this.gameObject.parent.toLocal(worldFrom);
        }
    },

    /**
     * @property {qc.Node} to - 终点位置
     */
    to : {
        get : function() {
            return this._to;
        },
        set : function(v) {
            this._to = v;

           var worldTo = this._to.getWorldPosition();
           this.localTo = this.gameObject.parent.toLocal(worldTo);
        }
    }
});

// 帧调度: 驱动位置
TweenTransform.prototype.onUpdate = function(factor, isFinished) {
    var self = this;

    self.gameObject.x = self.localFrom.x + factor * (self.localTo.x - self.localFrom.x);
    self.gameObject.y = self.localFrom.y + factor * (self.localTo.y - self.localFrom.y);
};
<|endoftext|>


<commit_before>import { PolymerElement, html } from '@polymer/polymer/polymer-element';
import './bnb-divider';

class BnbValueChip extends PolymerElement {
  static get template() {
    return html`

[[computeValue(value)]][[suffix]]

[[text]]

    <bnb-divider></bnb-divider>
    `;
  }

  static get properties() {
    return {
      text: {
        type: String,
        value: null,
      },

      value: {
        type: Number,
        value: null,
      },

      suffix: {
        type: String,
        value: null,
      },
    };
  }

  computeValue(value) {
    return Math.round(this.value);
  }
}

window.customElements.define('bnb-value-chip', BnbValueChip);
<commit_msg>Fix uptime rounding error<commit_after>import { PolymerElement, html } from '@polymer/polymer/polymer-element';
import './bnb-divider';

class BnbValueChip extends PolymerElement {
  static get template() {
    return html`

[[computeValue(value)]][[suffix]]

[[text]]

    <bnb-divider></bnb-divider>
    `;
  }

  static get properties() {
    return {
      text: {
        type: String,
        value: null,
      },

      value: {
        type: Number,
        value: null,
      },

      suffix: {
        type: String,
        value: null,
      },
    };
  }

  computeValue(value) {
    return this.value;
  }
}

window.customElements.define('bnb-value-chip', BnbValueChip);
<|endoftext|>


<commit_before>import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import {
  Text,
  View,
  FlatList,
  Animated,
  Modal,
  TouchableWithoutFeedback,
  Dimensions,
  Platform,
  ViewPropTypes,
  I18nManager,
} from 'react-native';
import Ripple from 'react-native-material-ripple';
import { TextField } from 'react-native-material-textfield';

import DropdownItem from '../item';
import styles from './styles';

export default class Dropdown extends PureComponent {
  static defaultProps = {
    hitSlop: { top: 6, right: 4, bottom: 6, left: 4 },

    disabled: false,

    data: [],

    valueExtractor: ({ value } = {}, index) => value,
    labelExtractor: ({ label } = {}, index) => label,

    absoluteRTLLayout: false,

    dropdownOffset: {
      top: 32,
      left: 0,
    },

    dropdownMargins: {
      min: 8,
      max: 16,
    },

    rippleCentered: false,
    rippleSequential: true,

    rippleInsets: {
      top: 16,
      right: 0,
      bottom: -8,
      left: 0,
    },

    rippleOpacity: 0.54,
    shadeOpacity: 0.12,

    rippleDuration: 400,
    animationDuration: 225,

    fontSize: 16,

    textColor: 'rgba(0, 0, 0, .87)',
    itemColor: 'rgba(0, 0, 0, .54)',
    baseColor: 'rgba(0, 0, 0, .38)',

    itemCount: 4,
    itemPadding: 8,

    supportedOrientations: [
      'portrait',
      'portrait-upside-down',
      'landscape',
      'landscape-left',
      'landscape-right',
    ],
  };

  static propTypes = {
    ...TouchableWithoutFeedback.propTypes,

    disabled: PropTypes.bool,

    value: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.number,
    ]),

    data: PropTypes.arrayOf(PropTypes.object),

    valueExtractor: PropTypes.func,
    labelExtractor: PropTypes.func,

    absoluteRTLLayout: PropTypes.bool,

    dropdownOffset: PropTypes.shape({
      top: PropTypes.number.isRequired,
      left: PropTypes.number.isRequired,
    }),

    dropdownMargins: PropTypes.shape({
      min: PropTypes.number.isRequired,
      max: PropTypes.number.isRequired,
    }),

    dropdownPosition: PropTypes.number,

    rippleCentered: PropTypes.bool,
    rippleSequential: PropTypes.bool,

    rippleInsets: PropTypes.shape({
      top: PropTypes.number,
      right: PropTypes.number,
      bottom: PropTypes.number,
      left: PropTypes.number,
    }),

    rippleOpacity: PropTypes.number,
    shadeOpacity: PropTypes.number,

    rippleDuration: PropTypes.number,
    animationDuration: PropTypes.number,

    fontSize: PropTypes.number,

    textColor: PropTypes.string,
    itemColor: PropTypes.string,
    selectedItemColor: PropTypes.string,
    baseColor: PropTypes.string,

    itemTextStyle: Text.propTypes.style,

    itemCount: PropTypes.number,
    itemPadding: PropTypes.number,

    onLayout: PropTypes.func,
    onFocus: PropTypes.func,
    onBlur: PropTypes.func,
    onChangeText: PropTypes.func,

    renderBase: PropTypes.func,
    renderAccessory: PropTypes.func,

    containerStyle: (ViewPropTypes || View.propTypes).style,
    pickerStyle: (ViewPropTypes || View.propTypes).style,

    supportedOrientations: PropTypes.arrayOf(PropTypes.string),
  };

  constructor(props) {
    super(props);

    this.onPress = this.onPress.bind(this);
    this.onClose = this.onClose.bind(this);
    this.onSelect = this.onSelect.bind(this);
    this.onLayout = this.onLayout.bind(this);

    this.updateRippleRef = this.updateRef.bind(this, 'ripple');
    this.updateContainerRef = this.updateRef.bind(this, 'container');
    this.updateScrollRef = this.updateRef.bind(this, 'scroll');

    this.renderAccessory = this.renderAccessory.bind(this);
    this.renderItem = this.renderItem.bind(this);

    this.keyExtractor = this.keyExtractor.bind(this);

    this.blur = this.onClose;
    this.focus = this.onPress;

    let { value } = this.props;

    this.mounted = false;
    this.focused = false;

    this.state = {
      opacity: new Animated.Value(0),
      selected: -1,
      modal: false,
      value,
    };
  }

  componentWillReceiveProps({ value }) {
    if (value !== this.props.value) {
      this.setState({ value });
    }
  }

  componentDidMount() {
    this.mounted = true;
  }

  componentWillUnmount() {
    this.mounted = false;
  }

  onPress(event) {
    let {
      data,
      disabled,
      onFocus,
      itemPadding,
      rippleDuration,
      dropdownOffset,
      dropdownMargins: { min: minMargin, max: maxMargin },
      dropdownPosition,
      animationDuration,
      absoluteRTLLayout,
    } = this.props;

    if (disabled) {
      return;
    }

    let itemCount = data.length;
    let visibleItemCount = this.visibleItemCount();
    let tailItemCount = this.tailItemCount();
    let timestamp = Date.now();

    if (null != event) {
      /* Adjust event location */
      event.nativeEvent.locationY -= this.rippleInsets().top;

      /* Start ripple directly from event */
      this.ripple.startRipple(event);
    }

    if (!itemCount) {
      return;
    }

    this.focused = true;

    if ('function' === typeof onFocus) {
      onFocus();
    }

    let dimensions = Dimensions.get('window');

    this.container.measureInWindow((x, y, containerWidth, containerHeight) => {
      let { opacity } = this.state;

      /* Adjust coordinates for relative layout in RTL locale */
      if (I18nManager.isRTL && !absoluteRTLLayout) {
        x = dimensions.width - (x + containerWidth);
      }

      let delay = Math.max(0, rippleDuration - animationDuration - (Date.now() - timestamp));
      let selected = this.selectedIndex();
      let offset = 0;

      if (itemCount > visibleItemCount) {
        if (null == dropdownPosition) {
          switch (selected) {
            case -1:
              break;

            case 0:
            case 1:
              break;

            default:
              if (selected >= itemCount - tailItemCount) {
                offset = this.itemSize() * (itemCount - visibleItemCount);
              } else {
                offset = this.itemSize() * (selected - 1);
              }
          }
        } else {
          if (~selected) {
            if (dropdownPosition < 0) {
              offset = this.itemSize() * (selected - visibleItemCount - dropdownPosition);
            } else {
              offset = this.itemSize() * (selected - dropdownPosition);
            }
          }
        }
      }

      let leftInset;
      let left = x
        + dropdownOffset.left
        - maxMargin;

      if (left > minMargin) {
        leftInset = maxMargin;
      } else {
        left = minMargin;
        leftInset = minMargin;
      }

      let right = x + containerWidth + maxMargin;
      let rightInset;

      if (dimensions.width - right > minMargin) {
        rightInset = maxMargin;
      } else {
        right = dimensions.width - minMargin;
        rightInset = minMargin;
      }

      let top = y
        + Platform.select({ ios: 1, android: 2 })
        + dropdownOffset.top
        - itemPadding;

      this.setState({
        modal: true,
        width: right - left,
        top,
        left,
        leftInset,
        rightInset,
        selected,
      });

      setTimeout((() => {
        if (this.mounted) {
          if (this.scroll) {
            this.scroll.scrollToOffset({ offset, animated: false });
          }

          Animated
            .timing(opacity, {
              duration: animationDuration,
              toValue: 1,
            })
            .start(() => {
              if (this.mounted && 'ios' === Platform.OS) {
                let { flashScrollIndicators } = this.scroll || {};

                if ('function' === typeof flashScrollIndicators) {
                  flashScrollIndicators.call(this.scroll);
                }
              }
            });
        }
      }), delay);
    });
  }

  onClose() {
    let { onBlur, animationDuration } = this.props;
    let { opacity } = this.state;

    Animated
      .timing(opacity, {
        duration: animationDuration,
        toValue: 0,
      })
      .start(() => {
        this.focused = false;

        if ('function' === typeof onBlur) {
          onBlur();
        }

        if (this.mounted) {
          this.setState({ modal: false });
        }
      });
  }

  onSelect(index) {
    let { data, valueExtractor, onChangeText, animationDuration } = this.props;
    let value = valueExtractor(data[index], index);

    this.setState({ value });

    if ('function' === typeof onChangeText) {
      onChangeText(value, index, data);
    }

    setTimeout(this.onClose, animationDuration);
  }

  onLayout(event) {
    let { onLayout } = this.props;

    if ('function' === typeof onLayout) {
      onLayout(event);
    }
  }

  value() {
    let { value } = this.state;

    return value;
  }

  selectedIndex() {
    let { value } = this.state;
    let { data, valueExtractor } = this.props;

    return data
      .findIndex((item, index) => null != item && value === valueExtractor(item, index));
  }

  selectedItem() {
    let { data } = this.props;

    return data[this.selectedIndex()];
  }

  isFocused() {
    return this.focused;
  }

  itemSize() {
    let { fontSize, itemPadding } = this.props;

    return Math.ceil(fontSize * 1.5 + itemPadding * 2);
  }

  visibleItemCount() {
    let { data, itemCount } = this.props;

    return Math.min(data.length, itemCount);
  }

  tailItemCount() {
    return Math.max(this.visibleItemCount() - 2, 0);
  }

  rippleInsets() {
    let {
      top = 16,
      right = 0,
      bottom = -8,
      left = 0,
    } = this.props.rippleInsets || {};

    return { top, right, bottom, left };
  }

  updateRef(name, ref) {
    this[name] = ref;
  }

  keyExtractor(item, index) {
    let { valueExtractor } = this.props;

    return `${index}-${valueExtractor(item, index)}`;
  }

  renderBase(props) {
    let { value } = this.state;
    let {
      data,
      renderBase,
      labelExtractor,
      dropdownOffset,
      renderAccessory = this.renderAccessory,
    } = this.props;

    let index = this.selectedIndex();
    let label;

    if (~index) {
      label = labelExtractor(data[index], index);
    }

    if (null == label) {
      label = value;
    }

    if ('function' === typeof renderBase) {
      return renderBase({ ...props, label, value, renderAccessory });
    }

    let title = null == label || 'string' === typeof label?
      label:
      String(label);

    return (
      <TextField
        labelHeight={dropdownOffset.top}

        {...props}

        value={title}
        editable={false}
        onChangeText={undefined}
        renderAccessory={renderAccessory}
      />
    );
  }

  renderRipple() {
    let {
      baseColor,
      rippleOpacity,
      rippleDuration,
      rippleCentered,
      rippleSequential,
    } = this.props;

    let { bottom, ...insets } = this.rippleInsets();
    let style = {
      ...insets,

      height: this.itemSize() - bottom,
      position: 'absolute',
    };

    return (
      <Ripple
        style={style}
        rippleColor={baseColor}
        rippleDuration={rippleDuration}
        rippleOpacity={rippleOpacity}
        rippleCentered={rippleCentered}
        rippleSequential={rippleSequential}
        ref={this.updateRippleRef}
      />
    );
  }

  renderAccessory() {
    let { baseColor: backgroundColor } = this.props;
    let triangleStyle = { backgroundColor };

    return (
      <View style={styles.accessory}>
        <View style={styles.triangleContainer}>
          <View style={[styles.triangle, triangleStyle]} />
        </View>
      </View>
    );
  }

  renderItem({ item, index }) {
    let { selected, leftInset, rightInset } = this.state;

    let {
      valueExtractor,
      labelExtractor,
      textColor,
      itemColor,
      selectedItemColor = textColor,
      baseColor,
      fontSize,
      itemTextStyle,
      rippleOpacity,
      rippleDuration,
      shadeOpacity,
    } = this.props;

    let props = {
      baseColor,
      fontSize,
      rippleDuration,
      rippleOpacity,
      shadeOpacity,
      onPress: this.onSelect,
      style: {
        height: this.itemSize(),
        paddingLeft: leftInset,
        paddingRight: rightInset,
      },
    };

    if (null == item) {
      return null;
    }

    let value = valueExtractor(item, index);
    let label = labelExtractor(item, index);

    let title = null == label?
      value:
      label;

    let color = ~selected?
      index === selected?
        selectedItemColor:
        itemColor:
      selectedItemColor;

    let style = { color, fontSize };

    return (
      <DropdownItem index={index} {...props}>
        <Text style={[styles.item, itemTextStyle, style]} numberOfLines={1}>
          {title}
        </Text>
      </DropdownItem>
    );
  }

  render() {
    let {
      renderBase,
      renderAccessory,
      containerStyle,
      pickerStyle: pickerStyleOverrides,

      rippleInsets,
      rippleOpacity,
      rippleCentered,
      rippleSequential,

      hitSlop,
      pressRetentionOffset,
      testID,
      nativeID,
      accessible,
      accessibilityLabel,

      supportedOrientations,

      ...props
    } = this.props;

    let {
      data,
      disabled,
      itemPadding,
      dropdownPosition,
    } = props;

    let { left, top, width, opacity, selected, modal } = this.state;

    let dimensions = Dimensions.get('window');

    let itemCount = data.length;
    let visibleItemCount = this.visibleItemCount();
    let tailItemCount = this.tailItemCount();
    let itemSize = this.itemSize();

    let overlayStyle = {
      width: dimensions.width,
      height: dimensions.height,
    };

    let height = 2 * itemPadding + itemSize * visibleItemCount;
    let translateY = -itemPadding;

    if (null == dropdownPosition) {
      switch (selected) {
        case -1:
          translateY -= 1 === itemCount? 0 : itemSize;
          break;

        case 0:
          break;

        default:
          if (selected >= itemCount - tailItemCount) {
            translateY -= itemSize * (visibleItemCount - (itemCount - selected));
          } else {
            translateY -= itemSize;
          }
      }
    } else {
      if (dropdownPosition < 0) {
        translateY -= itemSize * (visibleItemCount + dropdownPosition);
      } else {
        translateY -= itemSize * dropdownPosition;
      }
    }

    let pickerStyle = {
      width,
      height,
      top,
      left,
      opacity,
      transform: [{ translateY }],
    };

    let touchableProps = {
      disabled,
      hitSlop,
      pressRetentionOffset,
      onPress: this.onPress,
      testID,
      nativeID,
      accessible,
      accessibilityLabel,
    };

    return (
      <View onLayout={this.onLayout} ref={this.updateContainerRef} style={containerStyle}>
        <TouchableWithoutFeedback {...touchableProps}>
          <View pointerEvents='box-only'>
            {this.renderBase(props)}
            {this.renderRipple()}
          </View>
        </TouchableWithoutFeedback>

        <Modal
          visible={modal}
          transparent={true}
          onRequestClose={this.onClose}
          supportedOrientations={supportedOrientations}
        >
          <TouchableWithoutFeedback onPress={this.onClose}>
            <View style={overlayStyle}>
              <Animated.View
                style={[styles.picker, pickerStyle, pickerStyleOverrides]}
              >
                <FlatList
                  ref={this.updateScrollRef}
                  data={data}
                  style={styles.scroll}
                  renderItem={this.renderItem}
                  keyExtractor={this.keyExtractor}
                  scrollEnabled={visibleItemCount < itemCount}
                  contentContainerStyle={styles.scrollContainer}
                />
              </Animated.View>
            </View>
          </TouchableWithoutFeedback>
        </Modal>
      </View>
    );
  }
}
<commit_msg>Moved scroll position calculation to resetScrollOffset method<commit_after>import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import {
  Text,
  View,
  FlatList,
  Animated,
  Modal,
  TouchableWithoutFeedback,
  Dimensions,
  Platform,
  ViewPropTypes,
  I18nManager,
} from 'react-native';
import Ripple from 'react-native-material-ripple';
import { TextField } from 'react-native-material-textfield';

import DropdownItem from '../item';
import styles from './styles';

export default class Dropdown extends PureComponent {
  static defaultProps = {
    hitSlop: { top: 6, right: 4, bottom: 6, left: 4 },

    disabled: false,

    data: [],

    valueExtractor: ({ value } = {}, index) => value,
    labelExtractor: ({ label } = {}, index) => label,

    absoluteRTLLayout: false,

    dropdownOffset: {
      top: 32,
      left: 0,
    },

    dropdownMargins: {
      min: 8,
      max: 16,
    },

    rippleCentered: false,
    rippleSequential: true,

    rippleInsets: {
      top: 16,
      right: 0,
      bottom: -8,
      left: 0,
    },

    rippleOpacity: 0.54,
    shadeOpacity: 0.12,

    rippleDuration: 400,
    animationDuration: 225,

    fontSize: 16,

    textColor: 'rgba(0, 0, 0, .87)',
    itemColor: 'rgba(0, 0, 0, .54)',
    baseColor: 'rgba(0, 0, 0, .38)',

    itemCount: 4,
    itemPadding: 8,

    supportedOrientations: [
      'portrait',
      'portrait-upside-down',
      'landscape',
      'landscape-left',
      'landscape-right',
    ],
  };

  static propTypes = {
    ...TouchableWithoutFeedback.propTypes,

    disabled: PropTypes.bool,

    value: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.number,
    ]),

    data: PropTypes.arrayOf(PropTypes.object),

    valueExtractor: PropTypes.func,
    labelExtractor: PropTypes.func,

    absoluteRTLLayout: PropTypes.bool,

    dropdownOffset: PropTypes.shape({
      top: PropTypes.number.isRequired,
      left: PropTypes.number.isRequired,
    }),

    dropdownMargins: PropTypes.shape({
      min: PropTypes.number.isRequired,
      max: PropTypes.number.isRequired,
    }),

    dropdownPosition: PropTypes.number,

    rippleCentered: PropTypes.bool,
    rippleSequential: PropTypes.bool,

    rippleInsets: PropTypes.shape({
      top: PropTypes.number,
      right: PropTypes.number,
      bottom: PropTypes.number,
      left: PropTypes.number,
    }),

    rippleOpacity: PropTypes.number,
    shadeOpacity: PropTypes.number,

    rippleDuration: PropTypes.number,
    animationDuration: PropTypes.number,

    fontSize: PropTypes.number,

    textColor: PropTypes.string,
    itemColor: PropTypes.string,
    selectedItemColor: PropTypes.string,
    baseColor: PropTypes.string,

    itemTextStyle: Text.propTypes.style,

    itemCount: PropTypes.number,
    itemPadding: PropTypes.number,

    onLayout: PropTypes.func,
    onFocus: PropTypes.func,
    onBlur: PropTypes.func,
    onChangeText: PropTypes.func,

    renderBase: PropTypes.func,
    renderAccessory: PropTypes.func,

    containerStyle: (ViewPropTypes || View.propTypes).style,
    pickerStyle: (ViewPropTypes || View.propTypes).style,

    supportedOrientations: PropTypes.arrayOf(PropTypes.string),
  };

  constructor(props) {
    super(props);

    this.onPress = this.onPress.bind(this);
    this.onClose = this.onClose.bind(this);
    this.onSelect = this.onSelect.bind(this);
    this.onLayout = this.onLayout.bind(this);

    this.updateRippleRef = this.updateRef.bind(this, 'ripple');
    this.updateContainerRef = this.updateRef.bind(this, 'container');
    this.updateScrollRef = this.updateRef.bind(this, 'scroll');

    this.renderAccessory = this.renderAccessory.bind(this);
    this.renderItem = this.renderItem.bind(this);

    this.keyExtractor = this.keyExtractor.bind(this);

    this.blur = this.onClose;
    this.focus = this.onPress;

    let { value } = this.props;

    this.mounted = false;
    this.focused = false;

    this.state = {
      opacity: new Animated.Value(0),
      selected: -1,
      modal: false,
      value,
    };
  }

  componentWillReceiveProps({ value }) {
    if (value !== this.props.value) {
      this.setState({ value });
    }
  }

  componentDidMount() {
    this.mounted = true;
  }

  componentWillUnmount() {
    this.mounted = false;
  }

  onPress(event) {
    let {
      data,
      disabled,
      onFocus,
      itemPadding,
      rippleDuration,
      dropdownOffset,
      dropdownMargins: { min: minMargin, max: maxMargin },
      animationDuration,
      absoluteRTLLayout,
    } = this.props;

    if (disabled) {
      return;
    }

    let itemCount = data.length;
    let timestamp = Date.now();

    if (null != event) {
      /* Adjust event location */
      event.nativeEvent.locationY -= this.rippleInsets().top;

      /* Start ripple directly from event */
      this.ripple.startRipple(event);
    }

    if (!itemCount) {
      return;
    }

    this.focused = true;

    if ('function' === typeof onFocus) {
      onFocus();
    }

    let dimensions = Dimensions.get('window');

    this.container.measureInWindow((x, y, containerWidth, containerHeight) => {
      let { opacity } = this.state;

      /* Adjust coordinates for relative layout in RTL locale */
      if (I18nManager.isRTL && !absoluteRTLLayout) {
        x = dimensions.width - (x + containerWidth);
      }

      let delay = Math.max(0, rippleDuration - animationDuration - (Date.now() - timestamp));
      let selected = this.selectedIndex();

      let leftInset;
      let left = x
        + dropdownOffset.left
        - maxMargin;

      if (left > minMargin) {
        leftInset = maxMargin;
      } else {
        left = minMargin;
        leftInset = minMargin;
      }

      let right = x + containerWidth + maxMargin;
      let rightInset;

      if (dimensions.width - right > minMargin) {
        rightInset = maxMargin;
      } else {
        right = dimensions.width - minMargin;
        rightInset = minMargin;
      }

      let top = y
        + Platform.select({ ios: 1, android: 2 })
        + dropdownOffset.top
        - itemPadding;

      this.setState({
        modal: true,
        width: right - left,
        top,
        left,
        leftInset,
        rightInset,
        selected,
      });

      setTimeout((() => {
        if (this.mounted) {
          this.resetScrollOffset();

          Animated
            .timing(opacity, {
              duration: animationDuration,
              toValue: 1,
            })
            .start(() => {
              if (this.mounted && 'ios' === Platform.OS) {
                let { flashScrollIndicators } = this.scroll || {};

                if ('function' === typeof flashScrollIndicators) {
                  flashScrollIndicators.call(this.scroll);
                }
              }
            });
        }
      }), delay);
    });
  }

  onClose() {
    let { onBlur, animationDuration } = this.props;
    let { opacity } = this.state;

    Animated
      .timing(opacity, {
        duration: animationDuration,
        toValue: 0,
      })
      .start(() => {
        this.focused = false;

        if ('function' === typeof onBlur) {
          onBlur();
        }

        if (this.mounted) {
          this.setState({ modal: false });
        }
      });
  }

  onSelect(index) {
    let { data, valueExtractor, onChangeText, animationDuration } = this.props;
    let value = valueExtractor(data[index], index);

    this.setState({ value });

    if ('function' === typeof onChangeText) {
      onChangeText(value, index, data);
    }

    setTimeout(this.onClose, animationDuration);
  }

  onLayout(event) {
    let { onLayout } = this.props;

    if ('function' === typeof onLayout) {
      onLayout(event);
    }
  }

  value() {
    let { value } = this.state;

    return value;
  }

  selectedIndex() {
    let { value } = this.state;
    let { data, valueExtractor } = this.props;

    return data
      .findIndex((item, index) => null != item && value === valueExtractor(item, index));
  }

  selectedItem() {
    let { data } = this.props;

    return data[this.selectedIndex()];
  }

  isFocused() {
    return this.focused;
  }

  itemSize() {
    let { fontSize, itemPadding } = this.props;

    return Math.ceil(fontSize * 1.5 + itemPadding * 2);
  }

  visibleItemCount() {
    let { data, itemCount } = this.props;

    return Math.min(data.length, itemCount);
  }

  tailItemCount() {
    return Math.max(this.visibleItemCount() - 2, 0);
  }

  rippleInsets() {
    let {
      top = 16,
      right = 0,
      bottom = -8,
      left = 0,
    } = this.props.rippleInsets || {};

    return { top, right, bottom, left };
  }

  resetScrollOffset() {
    let { selected } = this.state;
    let { data, dropdownPosition } = this.props;

    let offset = 0;
    let itemCount = data.length;
    let itemSize = this.itemSize();
    let tailItemCount = this.tailItemCount();
    let visibleItemCount = this.visibleItemCount();

    if (itemCount > visibleItemCount) {
      if (null == dropdownPosition) {
        switch (selected) {
          case -1:
            break;

          case 0:
          case 1:
            break;

          default:
            if (selected >= itemCount - tailItemCount) {
              offset = itemSize * (itemCount - visibleItemCount);
            } else {
              offset = itemSize * (selected - 1);
            }
        }
      } else {
        if (~selected) {
          if (dropdownPosition < 0) {
            offset = itemSize * (selected - visibleItemCount - dropdownPosition);
          } else {
            offset = itemSize * (selected - dropdownPosition);
          }
        }
      }
    }

    if (this.scroll) {
      this.scroll.scrollToOffset({ offset, animated: false });
    }
  }

  updateRef(name, ref) {
    this[name] = ref;
  }

  keyExtractor(item, index) {
    let { valueExtractor } = this.props;

    return `${index}-${valueExtractor(item, index)}`;
  }

  renderBase(props) {
    let { value } = this.state;
    let {
      data,
      renderBase,
      labelExtractor,
      dropdownOffset,
      renderAccessory = this.renderAccessory,
    } = this.props;

    let index = this.selectedIndex();
    let label;

    if (~index) {
      label = labelExtractor(data[index], index);
    }

    if (null == label) {
      label = value;
    }

    if ('function' === typeof renderBase) {
      return renderBase({ ...props, label, value, renderAccessory });
    }

    let title = null == label || 'string' === typeof label?
      label:
      String(label);

    return (
      <TextField
        labelHeight={dropdownOffset.top}

        {...props}

        value={title}
        editable={false}
        onChangeText={undefined}
        renderAccessory={renderAccessory}
      />
    );
  }

  renderRipple() {
    let {
      baseColor,
      rippleOpacity,
      rippleDuration,
      rippleCentered,
      rippleSequential,
    } = this.props;

    let { bottom, ...insets } = this.rippleInsets();
    let style = {
      ...insets,

      height: this.itemSize() - bottom,
      position: 'absolute',
    };

    return (
      <Ripple
        style={style}
        rippleColor={baseColor}
        rippleDuration={rippleDuration}
        rippleOpacity={rippleOpacity}
        rippleCentered={rippleCentered}
        rippleSequential={rippleSequential}
        ref={this.updateRippleRef}
      />
    );
  }

  renderAccessory() {
    let { baseColor: backgroundColor } = this.props;
    let triangleStyle = { backgroundColor };

    return (
      <View style={styles.accessory}>
        <View style={styles.triangleContainer}>
          <View style={[styles.triangle, triangleStyle]} />
        </View>
      </View>
    );
  }

  renderItem({ item, index }) {
    let { selected, leftInset, rightInset } = this.state;

    let {
      valueExtractor,
      labelExtractor,
      textColor,
      itemColor,
      selectedItemColor = textColor,
      baseColor,
      fontSize,
      itemTextStyle,
      rippleOpacity,
      rippleDuration,
      shadeOpacity,
    } = this.props;

    let props = {
      baseColor,
      fontSize,
      rippleDuration,
      rippleOpacity,
      shadeOpacity,
      onPress: this.onSelect,
      style: {
        height: this.itemSize(),
        paddingLeft: leftInset,
        paddingRight: rightInset,
      },
    };

    if (null == item) {
      return null;
    }

    let value = valueExtractor(item, index);
    let label = labelExtractor(item, index);

    let title = null == label?
      value:
      label;

    let color = ~selected?
      index === selected?
        selectedItemColor:
        itemColor:
      selectedItemColor;

    let style = { color, fontSize };

    return (
      <DropdownItem index={index} {...props}>
        <Text style={[styles.item, itemTextStyle, style]} numberOfLines={1}>
          {title}
        </Text>
      </DropdownItem>
    );
  }

  render() {
    let {
      renderBase,
      renderAccessory,
      containerStyle,
      pickerStyle: pickerStyleOverrides,

      rippleInsets,
      rippleOpacity,
      rippleCentered,
      rippleSequential,

      hitSlop,
      pressRetentionOffset,
      testID,
      nativeID,
      accessible,
      accessibilityLabel,

      supportedOrientations,

      ...props
    } = this.props;

    let {
      data,
      disabled,
      itemPadding,
      dropdownPosition,
    } = props;

    let { left, top, width, opacity, selected, modal } = this.state;

    let dimensions = Dimensions.get('window');

    let itemCount = data.length;
    let visibleItemCount = this.visibleItemCount();
    let tailItemCount = this.tailItemCount();
    let itemSize = this.itemSize();

    let overlayStyle = {
      width: dimensions.width,
      height: dimensions.height,
    };

    let height = 2 * itemPadding + itemSize * visibleItemCount;
    let translateY = -itemPadding;

    if (null == dropdownPosition) {
      switch (selected) {
        case -1:
          translateY -= 1 === itemCount? 0 : itemSize;
          break;

        case 0:
          break;

        default:
          if (selected >= itemCount - tailItemCount) {
            translateY -= itemSize * (visibleItemCount - (itemCount - selected));
          } else {
            translateY -= itemSize;
          }
      }
    } else {
      if (dropdownPosition < 0) {
        translateY -= itemSize * (visibleItemCount + dropdownPosition);
      } else {
        translateY -= itemSize * dropdownPosition;
      }
    }

    let pickerStyle = {
      width,
      height,
      top,
      left,
      opacity,
      transform: [{ translateY }],
    };

    let touchableProps = {
      disabled,
      hitSlop,
      pressRetentionOffset,
      onPress: this.onPress,
      testID,
      nativeID,
      accessible,
      accessibilityLabel,
    };

    return (
      <View onLayout={this.onLayout} ref={this.updateContainerRef} style={containerStyle}>
        <TouchableWithoutFeedback {...touchableProps}>
          <View pointerEvents='box-only'>
            {this.renderBase(props)}
            {this.renderRipple()}
          </View>
        </TouchableWithoutFeedback>

        <Modal
          visible={modal}
          transparent={true}
          onRequestClose={this.onClose}
          supportedOrientations={supportedOrientations}
        >
          <TouchableWithoutFeedback onPress={this.onClose}>
            <View style={overlayStyle}>
              <Animated.View
                style={[styles.picker, pickerStyle, pickerStyleOverrides]}
              >
                <FlatList
                  ref={this.updateScrollRef}
                  data={data}
                  style={styles.scroll}
                  renderItem={this.renderItem}
                  keyExtractor={this.keyExtractor}
                  scrollEnabled={visibleItemCount < itemCount}
                  contentContainerStyle={styles.scrollContainer}
                />
              </Animated.View>
            </View>
          </TouchableWithoutFeedback>
        </Modal>
      </View>
    );
  }
}
<|endoftext|>


<commit_before>
const RG = require('../src/rg');
RG.Random = require('../src/random');

const Castle = {};

const corridorDoorThr = 0.2;

Castle.filler = `
name:FILLER
X=.
Y=.

.X...X.
Y......
.......
.......
.......
Y......
.......`;

Castle.templates = [

// Terminals
`
dir:N
name:term_n
X=#
Y=#

#X#+#X#
#.....#
Y.....#
#.....#
Y.....#
#.....#
#######`,

`
dir:S
name:term_s
X=#
Y=#

#X###X#
#.....#
Y.....#
#.....#
Y.....#
#.....#
###+###`,

`
dir:E
name:term_e
X=#
Y=#

#X###X#
#.....#
Y.....#
#.....+
Y.....#
#.....#
#######`,

`
dir:W
name:term_w
X=#
Y=#

#X###X#
#.....#
Y.....#
+.....#
Y.....#
#.....#
#######`,

// Corridors

// Corners
`
dir:NW
name:corner_se
X=.
Y=#

#X...X#
#.....#
Y.....#
......#
Y.....#
......#
#######`,

`
dir:NE
name:corner_sw
X=.
Y=#

#X...X#
#.....#
Y......
#......
Y......
#.....#
#######`,

`
dir:SW
name:corner_ne
X=#
Y=.

#X###X#
Y.....#
......#
......#
......#
Y.....#
#.....#`,

`
dir:SE
name:corner_nw
X=#
Y=#

#X###X#
#......
Y......
#......
Y......
#......
#.....#`,

// Corridors
`
dir:NS
name:corridor_ns
X=.
Y=#

#X...X#
#.....#
Y.....#
#.....#
Y.....#
#.....#
#.....#`,

`
dir:NSE
name:corridor_nse
X=.
Y=#

#X...X#
#.....#
Y.....#
#......
Y.....#
#.....#
#.....#`,

`
dir:NSW
name:corridor_nsw
X=#
Y=#

#X...X#
#.....#
Y.....#
......#
Y.....#
#.....#
##...##`,

`
dir:EW
name:corridor_ew
X=#
Y=.

#X###X#
.......
Y......
.......
Y......
.......
#######`,

`
dir:NEW
name:corridor_new
X=#
Y=.

#X#.#X#
.......
Y......
.......
Y......
.......
#######`,

`
dir:SEW
name:corridor_sew
X=#
Y=.

#X###X#
.......
Y......
.......
Y......
.......
###.###`

];

Castle.getStartRoom = function() {
    console.log('### Castle.getStartRoom');
    const templ = this.findTemplate({name: 'corner_nw'});
    return {
        x: 0, y: 0, room: templ
    };
};

/* Constraint function how to generate the castle level. */
Castle.constraintFunc = function(x, y, exitReqd) {
    if (x === 0 && y === 0) {
        return this.findTemplate({name: 'corner_nw'});
    }
    if (x === 0 && y === this.tilesY - 1) {
        return this.findTemplate({name: 'corner_sw'});
    }
    if (x === this.tilesX - 1 && y === 0) {
        return this.findTemplate({name: 'corner_ne'});
    }
    if (x === this.tilesX - 1 && y === this.tilesY - 1) {
        return this.findTemplate({name: 'corner_se'});
    }

    if (y === 0 ) {
        const ew = this.findTemplate({name: 'corridor_ew'});
        const sew = this.findTemplate({name: 'corridor_sew'});
        if (exitReqd === 'S') {
            return sew;
        }
        if (RG.RAND.getUniform() < corridorDoorThr) {
            return sew;
        }
        return ew;
    }
    else if (y === this.tilesY - 1) {
        const ew = this.findTemplate({name: 'corridor_ew'});
        const corrNew = this.findTemplate({name: 'corridor_new'});
        if (exitReqd === 'N') {
            return corrNew;
        }
        if (RG.RAND.getUniform() < corridorDoorThr) {
            return corrNew;
        }
        return ew;
    }

    if (x === 0) {
        const corrNs = this.findTemplate({name: 'corridor_ns'});
        const corrNse = this.findTemplate({name: 'corridor_nse'});
        if (exitReqd === 'E') {
            return corrNse;
        }
        if (RG.RAND.getUniform() < corridorDoorThr) {
            return corrNse;
        }
        return corrNs;
    }
    else if (x === this.tilesX - 1) {
        const corrNs = this.findTemplate({name: 'corridor_ns'});
        const corrNsw = this.findTemplate({name: 'corridor_nsw'});
        if (exitReqd === 'W') {
            return corrNsw;
        }
        if (RG.RAND.getUniform() < corridorDoorThr) {
            return corrNsw;
        }
        return corrNs;
    }
    console.log(`RETURN NULL for ${x},${y}`);
    return null;
};

module.exports = Castle;
<commit_msg>Castle has now 2 flavors: complex with inner rooms and outer wall only.<commit_after>
const RG = require('../src/rg');
RG.Random = require('../src/random');

const Castle = {};

const corridorDoorThr = 0.2;

// Corners
Castle.corners = [
`
dir:NW
name:corner_se
X=.
Y=#

#X...X#
#.....#
Y.....#
......#
Y.....#
......#
#######`,

`
dir:NE
name:corner_sw
X=.
Y=#

#X...X#
#.....#
Y......
#......
Y......
#.....#
#######`,

`
dir:SW
name:corner_ne
X=#
Y=.

#X###X#
Y.....#
......#
......#
......#
Y.....#
#.....#`,

`
dir:SE
name:corner_nw
X=#
Y=#

#X###X#
#......
Y......
#......
Y......
#......
#.....#`
];

// Terminals
Castle.terms = [
`
dir:N
name:term_n
X=#
Y=#

#X#+#X#
#.....#
Y.....#
#.....#
Y.....#
#.....#
#######`,

`
dir:S
name:term_s
X=#
Y=#

#X###X#
#.....#
Y.....#
#.....#
Y.....#
#.....#
###+###`,

`
dir:E
name:term_e
X=#
Y=#

#X###X#
#.....#
Y.....#
#.....+
Y.....#
#.....#
#######`,

`
dir:W
name:term_w
X=#
Y=#

#X###X#
#.....#
Y.....#
+.....#
Y.....#
#.....#
#######`
];

// Entrances
Castle.entrances = [
`
dir:NEW
name:entrance_n
X=.
Y=#

#X...X#
##...##
Y##.###
..#+#..
.......
Y.....#
#######`,

`
dir:SEW
name:entrance_s
X=#
Y=#

#X###X#
.......
Y.#.##.
.##+##.
Y#...##
#.....#
##...##`
];

// Corridors
Castle.corridors = [
`
dir:NS
name:corridor_ns
X=.
Y=#

#X...X#
#.....#
Y.....#
#.....#
Y.....#
#.....#
#.....#`,
`
dir:EW
name:corridor_ew
X=#
Y=.

#X###X#
.......
Y......
.......
Y......
.......
#######`
];

// Branching from the main wall
Castle.branches = [
`
dir:NSE
name:corridor_nse
X=.
Y=#

#X...X#
#.....#
Y.....#
#......
Y.....#
#.....#
#.....#`,

`
dir:NSW
name:corridor_nsw
X=#
Y=#

#X...X#
#.....#
Y.....#
......#
Y.....#
#.....#
##...##`,

`
dir:NEW
name:corridor_new
X=#
Y=.

#X#.#X#
.......
Y......
.......
Y......
.......
#######`,

`
dir:SEW
name:corridor_sew
X=#
Y=.

#X###X#
.......
Y......
.......
Y......
.......
###.###`

];

// Filler cell
Castle.fillerFloor = `
name:FILLER
X=.
Y=.

.X...X.
Y......
.......
.......
.......
Y......
.......`;

Castle.fillerWall = `
name:FILLER
X=#
Y=#

#X###X#
Y######
#######
#######
#######
Y######
#######`;

Castle.templates = [];

/* Returns the starting room for castle generation. */
Castle.getStartRoom = function() {
    console.log('### Castle.getStartRoom');
    // const templ = this.findTemplate({name: 'corner_nw'});
    const templ = this.findTemplate({name: 'entrance_n'});
    return {
        x: Math.floor(this.tilesX / 2), y: 0, room: templ
    };
};

/* Constraint function how to generate the castle level. */
Castle.constraintFunc = function(x, y, exitReqd) {
    if (x === 0 && y === 0) {
        return this.findTemplate({name: 'corner_nw'});
    }
    if (x === 0 && y === this.tilesY - 1) {
        return this.findTemplate({name: 'corner_sw'});
    }
    if (x === this.tilesX - 1 && y === 0) {
        return this.findTemplate({name: 'corner_ne'});
    }
    if (x === this.tilesX - 1 && y === this.tilesY - 1) {
        return this.findTemplate({name: 'corner_se'});
    }

    if (y === 0 ) {
        const ew = this.findTemplate({name: 'corridor_ew'});
        const sew = this.findTemplate({name: 'corridor_sew'});
        if (exitReqd === 'S') {
            return sew;
        }
        if (RG.RAND.getUniform() < corridorDoorThr) {
            return sew;
        }
        return ew;
    }
    else if (y === this.tilesY - 1) {
        const ew = this.findTemplate({name: 'corridor_ew'});
        const corrNew = this.findTemplate({name: 'corridor_new'});
        if (exitReqd === 'N') {
            return corrNew;
        }
        if (RG.RAND.getUniform() < corridorDoorThr) {
            return corrNew;
        }
        return ew;
    }

    if (x === 0) {
        const corrNs = this.findTemplate({name: 'corridor_ns'});
        const corrNse = this.findTemplate({name: 'corridor_nse'});
        if (exitReqd === 'E') {
            return corrNse;
        }
        if (RG.RAND.getUniform() < corridorDoorThr) {
            return corrNse;
        }
        return corrNs;
    }
    else if (x === this.tilesX - 1) {
        const corrNs = this.findTemplate({name: 'corridor_ns'});
        const corrNsw = this.findTemplate({name: 'corridor_nsw'});
        if (exitReqd === 'W') {
            return corrNsw;
        }
        if (RG.RAND.getUniform() < corridorDoorThr) {
            return corrNsw;
        }
        return corrNs;
    }
    console.log(`RETURN NULL for ${x},${y}`);
    return null;
};

Castle.templates = []
    .concat(Castle.branches)
    .concat(Castle.corners)
    .concat(Castle.terms)
    .concat(Castle.entrances)
    .concat(Castle.corridors);

Castle.templatesWall = []
    .concat(Castle.corners)
    .concat(Castle.corridors);

module.exports = Castle;
<|endoftext|>


<commit_before>import React from 'react';
import { ListItem } from 'material-ui/List';
import SvgIcon from 'material-ui/SvgIcon';
import ICONS from '../styles/icons';

const styles = {
  ListItem: {
    text: {
      color: "#000000",
    }
  }
}

const IceboxListItem = ({ name, foodGroup, expiration, key }) => {
  if (!name) {
    return <ListItem primaryText="Loading..." />
  }

  console.log("This is d: ", ICONS.Dairy.d)

  return (

    <ListItem
      key={key}
      className="iceboxListItem"
      primaryText={name}
      leftIcon={
        <SvgIcon className="food-group-icon">
          <path className="icon" d={ICONS.Dairy.d} />
         </SvgIcon>
       }
      children={

{expiration}

      }
      style={styles.ListItem.text}
    />
  );

};

export default IceboxListItem;

<commit_msg>[fix] reformats list items and divs to correctly align text horizontally<commit_after>import React from 'react';
import { ListItem } from 'material-ui/List';
import SvgIcon from 'material-ui/SvgIcon';
import ICONS from '../styles/icons';

const styles = {
  ListItem: {
    text: {
      color: "#000000"
    }
  }
}

const IceboxListItem = ({ name, foodGroup, expiration, key }) => {
  if (!name) {
    return <ListItem primaryText="Loading..." />
  }

  return (

    <ListItem
      key={key}
      className="iceboxListItem"
      leftIcon={
        <SvgIcon className="food-group-icon">
          <path className="icon" d={ICONS.Dairy.d} />
        </SvgIcon>
       }
      style={styles.ListItem.text}
    >

      {name}

        {expiration}

    </ListItem>
  );

};

export default IceboxListItem;

<|endoftext|>


<commit_before>//simple code that allows you to browser hair/horns or fanart.
window.onload = function() {
    console.log ("hellow world");
	if (getParameterByName("hair")  == "true"){
		$("#header").html("Hair");
		renderAllHair();
	}
	if (getParameterByName("horns")  == "true"){
		renderAllHorns();
		$("#header").html("Horns");
	}

	if (getParameterByName("eyes")  == "true"){
    		renderAllEyes();
    		$("#header").html("Eyes");
    	}

    if (getParameterByName("mouths")  == "true"){
            renderAllMouths();
            $("#header").html("Mouths");
    }

     if (getParameterByName("consorts")  == "true"){
                renderAllConsorts();
                $("#header").html("consorts");
        }

    if (getParameterByName("bodies")  == "true"){
                renderAllBodies();
                $("#header").html("Bodies");
    }

    if (getParameterByName("symbols")  == "true"){
        renderAllSymbols();
        $("#header").html("Symbols");
    }

    if (getParameterByName("glasses")  == "true"){
        renderAllGlasses();
        $("#header").html("Glasses");
    }

}

function getParameterByName(name, url) {
    if (!url) {
      url = window.location.href;
    }
    name = name.replace(/[\[\]]/g, "\\$&");
    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
        results = regex.exec(url);
    if (!results) return null;
    if (!results[2]) return '';
    return decodeURIComponent(results[2].replace(/\+/g, " "));
}

function getRawParameterByName(name, url) {
    if (!url) {
      url = window.location.href;
    }
    name = name.replace(/[\[\]]/g, "\\$&");
    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
        results = regex.exec(url);
    if (!results) return null;
    if (!results[2]) return '';
    return results[2];
}

//use ajax to get index file, then do yo thang.
//https://stackoverflow.com/questions/22061073/how-do-i-get-images-file-name-from-a-given-folder
function renderAllImagesInFolder(folder){
	console.log("tring to get folder: " + folder)
	var fileExt = {};
    fileExt[0]=".png";
    fileExt[1]=".jpg";
    fileExt[2]=".gif";
		fileExt[3]=".jpeg";
		$.ajax({
			//This will retrieve the contents of the folder if the folder is configured as 'browsable'
			url: folder,
			success: function (data) {
			   //List all png or jpg or gif file names in the page
			   $(data).find("a:contains(" + fileExt[0] + "),a:contains(" + fileExt[1] + "),a:contains(" + fileExt[2] + ")").each(function () {
				  var split = this.href.split("/")
					var filename =  split[split.length-1];
					console.log("found: " + filename);
					//var filename = this.href;
				   renderRegularSprite(new SpritePart(folder+filename, filename));
			   });
			 }

		  });
}

function renderRegularSprite(spritePart){
	$("#images").append("

"+spritePart.name+"
");
}

function renderAllHair(){
	var minHair = 0;
	var maxHair = 100;
	//don't use canvas, but still layer?  bg, like for KR.
	for(var i = minHair; i<= maxHair; i++){
		renderLayeredSprites([new SpritePart("images/Homestuck/HairBack/"+i+".png", "Hair " +i),new SpritePart("images/Homestuck/Body/head.png", ""), new SpritePart("images/Homestuck/HairTop/"+i+".png", "")]);
	}
}

function renderAllHorns(){
	var minHorn = 0
	var maxHorn = 100;
	for(var i = minHorn; i<= maxHorn; i++){
			renderLayeredSprites([new SpritePart("images/Homestuck/Body/head.png", ""),new SpritePart("images/Homestuck/LeftHorn/"+i+".png","leftHorn "+i),new SpritePart("images/Homestuck/RightHorn/"+i+".png", "rightHorn" +i)]);
	}
}

function renderAllEyes(){
	var minHorn = 1
	var maxHorn = 73;
	for(var i = minHorn; i<= maxHorn; i++){
			renderLayeredSprites([new SpritePart("images/Homestuck/Body/head.png", ""),new SpritePart("images/Homestuck/LeftEye/"+i+".png","leftEye "+i),new SpritePart("images/Homestuck/RightEye/"+i+".png", "rightEye" +i)]);
	}
}

function renderAllMouths(){
	var minHorn = 1
	var maxHorn = 73;
	for(var i = minHorn; i<= maxHorn; i++){
			renderLayeredSprites([new SpritePart("images/Homestuck/Body/head.png", ""),new SpritePart("images/Homestuck/Mouth/"+i+".png","mouth "+i)]);
	}
}

function renderAllConsorts(){
	var minHorn = 0
	var maxHorn = 9;
	for(var i = minHorn; i<= maxHorn; i++){
			renderLayeredSprites([new SpritePart("images/Homestuck/Consort/"+i+".png","consort "+i)]);
	}
}

function renderAllGlasses(){
	var minHorn = 1
	var maxHorn = 73;
	for(var i = minHorn; i<= maxHorn; i++){
			renderLayeredSprites([new SpritePart("images/Homestuck/Body/head.png", ""),new SpritePart("images/Homestuck/Glasses/"+i+".png","mouth "+i)]);
	}
}

function renderAllBodies(){
	var minHorn = 1
	var maxHorn = 73;
	for(var i = minHorn; i<= maxHorn; i++){
			renderLayeredSprites([new SpritePart("images/Homestuck/Body/"+i+".png","body "+i)]);
	}
}

function renderAllSymbols(){
	var minHorn = 1
	var maxHorn = 148;
	for(var i = minHorn; i<= maxHorn; i++){
			renderLayeredSprites([new SpritePart("images/Homestuck/Symbol/bg.png", ""),new SpritePart("images/Homestuck/Symbol/"+i+".png","symbol "+i)]);
	}
}

//first thing on bottom, last thing on top
function renderLayeredSprites(spriteArray){
	var html = "
"; //all images should be rendered at same position in sprite parent
	for(var i = 0; i<spriteArray.length; i++){
		html += "
"+spriteArray[i].name;
	}
	html += "
"
	$("#images").append(html);
}

function SpritePart(location, name){
	this.location = location;
	this.name = name;
}
<commit_msg>better visualizer<commit_after>//simple code that allows you to browser hair/horns or fanart.
window.onload = function() {
    console.log ("hellow world");
	if (getParameterByName("hair")  == "true"){
		$("#header").html("Hair");
		renderAllHair();
	}
	if (getParameterByName("horns")  == "true"){
		renderAllHorns();
		$("#header").html("Horns");
	}

	if (getParameterByName("eyes")  == "true"){
    		renderAllEyes();
    		$("#header").html("Eyes");
    	}

    if (getParameterByName("mouths")  == "true"){
            renderAllMouths();
            $("#header").html("Mouths");
    }

     if (getParameterByName("consorts")  == "true"){
                renderAllConsorts();
                $("#header").html("consorts");
        }

    if (getParameterByName("bodies")  == "true"){
                renderAllBodies();
                $("#header").html("Bodies");
    }

    if (getParameterByName("symbols")  == "true"){
        renderAllSymbols();
        $("#header").html("Symbols");
    }

    if (getParameterByName("glasses")  == "true"){
        renderAllGlasses();
        $("#header").html("Glasses");
    }

}

function getParameterByName(name, url) {
    if (!url) {
      url = window.location.href;
    }
    name = name.replace(/[\[\]]/g, "\\$&");
    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
        results = regex.exec(url);
    if (!results) return null;
    if (!results[2]) return '';
    return decodeURIComponent(results[2].replace(/\+/g, " "));
}

function getRawParameterByName(name, url) {
    if (!url) {
      url = window.location.href;
    }
    name = name.replace(/[\[\]]/g, "\\$&");
    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
        results = regex.exec(url);
    if (!results) return null;
    if (!results[2]) return '';
    return results[2];
}

//use ajax to get index file, then do yo thang.
//https://stackoverflow.com/questions/22061073/how-do-i-get-images-file-name-from-a-given-folder
function renderAllImagesInFolder(folder){
	console.log("tring to get folder: " + folder)
	var fileExt = {};
    fileExt[0]=".png";
    fileExt[1]=".jpg";
    fileExt[2]=".gif";
		fileExt[3]=".jpeg";
		$.ajax({
			//This will retrieve the contents of the folder if the folder is configured as 'browsable'
			url: folder,
			success: function (data) {
			   //List all png or jpg or gif file names in the page
			   $(data).find("a:contains(" + fileExt[0] + "),a:contains(" + fileExt[1] + "),a:contains(" + fileExt[2] + ")").each(function () {
				  var split = this.href.split("/")
					var filename =  split[split.length-1];
					console.log("found: " + filename);
					//var filename = this.href;
				   renderRegularSprite(new SpritePart(folder+filename, filename));
			   });
			 }

		  });
}

function renderRegularSprite(spritePart){
	$("#images").append("

"+spritePart.name+"
");
}

function renderAllHair(){
	var minHair = 0;
	var maxHair = 100;
	//don't use canvas, but still layer?  bg, like for KR.
	for(var i = minHair; i<= maxHair; i++){
		renderLayeredSprites([new SpritePart("images/Homestuck/HairBack/"+i+".png", "Hair " +i),new SpritePart("images/Homestuck/Body/head.png", ""), new SpritePart("images/Homestuck/HairTop/"+i+".png", "")]);
	}
}

function renderAllHorns(){
	var minHorn = 0
	var maxHorn = 100;
	for(var i = minHorn; i<= maxHorn; i++){
			renderLayeredSprites([new SpritePart("images/Homestuck/Body/head.png", ""),new SpritePart("images/Homestuck/LeftHorn/"+i+".png","leftHorn "+i),new SpritePart("images/Homestuck/RightHorn/"+i+".png", "rightHorn" +i)]);
	}
}

function renderAllEyes(){
	var minHorn = 1
	var maxHorn = 73;
	for(var i = minHorn; i<= maxHorn; i++){
			renderLayeredSprites([new SpritePart("images/Homestuck/Body/head.png", ""),new SpritePart("images/Homestuck/LeftEye/"+i+".png","leftEye "+i),new SpritePart("images/Homestuck/RightEye/"+i+".png", "rightEye" +i)]);
	}
}

function renderAllMouths(){
	var minHorn = 1
	var maxHorn = 73;
	for(var i = minHorn; i<= maxHorn; i++){
			renderLayeredSprites([new SpritePart("images/Homestuck/Body/head.png", ""),new SpritePart("images/Homestuck/Mouth/"+i+".png","mouth "+i)]);
	}
}

function renderAllConsorts(){
	var minHorn = 0
	var maxHorn = 9;
	for(var i = minHorn; i<= maxHorn; i++){
			renderLayeredSprites([new SpritePart("images/Homestuck/Consort/"+i+".png","consort "+i)]);
	}
}

function renderAllGlasses(){
	var minHorn = 1
	var maxHorn = 73;
	for(var i = minHorn; i<= maxHorn; i++){
			renderLayeredSprites([new SpritePart("images/Homestuck/Body/head.png", ""),new SpritePart("images/Homestuck/Glasses/"+i+".png","mouth "+i)]);
	}
}

function renderAllBodies(){
	var minHorn = 1
	var maxHorn = 97;
	for(var i = minHorn; i<= maxHorn; i++){
			renderLayeredSprites([new SpritePart("images/Homestuck/Body/"+i+".png","body "+i)]);
	}
}

function renderAllSymbols(){
	var minHorn = 1
	var maxHorn = 152;
	for(var i = minHorn; i<= maxHorn; i++){
			renderLayeredSprites([new SpritePart("images/Homestuck/Symbol/bg.png", ""),new SpritePart("images/Homestuck/Symbol/"+i+".png","symbol "+i)]);
	}
}

//first thing on bottom, last thing on top
function renderLayeredSprites(spriteArray){
	var html = "
"; //all images should be rendered at same position in sprite parent
	for(var i = 0; i<spriteArray.length; i++){
		html += "
"+spriteArray[i].name;
	}
	html += "
"
	$("#images").append(html);
}

function SpritePart(location, name){
	this.location = location;
	this.name = name;
}
<|endoftext|>


<commit_before>"use strict";

class gsuiSlicer extends HTMLElement {
	static #resW = 1000
	static #resH = 64

	#dur = 4
	#slices = new Map()
	#buffer = null
	#cropSide = "A"
	#cropSave = null
	#slicesWidth = 100
	#slicesHeight = 100
	#onresizeBind = this.#onresize.bind( this )
	#onmouseupCropBind = this.#onmouseupCrop.bind( this )
	#onmousemoveCropBind = this.#onmousemoveCrop.bind( this )
	#dispatch = GSUI.dispatchEvent.bind( null, this, "gsuiSlicer" )
	#children = GSUI.getTemplate( "gsui-slicer" )
	#waveDef = GSUI.createElementNS( "polyline" )
	#elements = GSUI.findElements( this.#children, {
		beatlines: [
			"gsui-beatlines:first-child",
			"gsui-beatlines:last-child",
		],
		srcSample: ".gsuiSlicer-source-sample",
		srcName: ".gsuiSlicer-source-name",
		srcWave: ".gsuiSlicer-source-wave",
		cropA: ".gsuiSlicer-source-cropA",
		cropB: ".gsuiSlicer-source-cropB",
		diagonalLine: ".gsuiSlicer-slices-line",
		preview: ".gsuiSlicer-preview",
		slices: ".gsuiSlicer-slices-wrap",
		inputDuration: ".gsuiSlicer-duration-input",
		stepBtn: ".gsuiSlicer-step",
	} )

	constructor() {
		const defs = document.querySelector( "#gsuiSlicer-waveDefs defs" );

		super();
		Object.seal( this );

		this.#elements.srcSample.onmousedown = this.#onmousedownCrop.bind( this );
		this.#elements.inputDuration.onchange = this.#onchangeDuration.bind( this );
		this.#elements.stepBtn.onclick = this.#onclickStep.bind( this );
		if ( !defs ) {
			document.body.prepend( GSUI.createElementNS( "svg", { id: "gsuiSlicer-waveDefs" },
				GSUI.createElementNS( "defs" ),
			) );
			this.#waveDef.dataset.id = 1;
		} else {
			this.#waveDef.dataset.id = 1 + Array.prototype.reduce.call( defs.children,
				( max, p ) => Math.max( max, p.dataset.id ), 0 );
		}
		this.#waveDef.id = `gsuiSlicer-waveDef-${ this.#waveDef.dataset.id }`;
	}

	// .........................................................................
	connectedCallback() {
		if ( this.#children ) {
			this.append( ...this.#children );
			this.#children = null;
			GSUI.recallAttributes( this, {
				cropa: 0,
				cropb: 1,
				step: 1,
				duration: 4,
				timedivision: "4/4",
			} );
		}
		document.querySelector( "#gsuiSlicer-waveDefs defs" ).append( this.#waveDef );
		GSUI.observeSizeOf( this, this.#onresizeBind );
	}
	disconnectedCallback() {
		this.#waveDef.remove();
		GSUI.unobserveSizeOf( this, this.#onresizeBind );
	}
	static get observedAttributes() {
		return [ "duration", "step", "timedivision", "cropa", "cropb" ];
	}
	attributeChangedCallback( prop, prev, val ) {
		if ( !this.#children && prev !== val ) {
			switch ( prop ) {
				case "timedivision":
					this.#elements.beatlines[ 0 ].setAttribute( "timedivision", val );
					this.#elements.beatlines[ 1 ].setAttribute( "timedivision", val );
					break;
				case "cropa":
 					this.#elements.cropA.style.left = `${ val * 100 }%`;
 					this.#updateCroppedWaveform();
					break;
				case "cropb":
 					this.#elements.cropB.style.left = `${ val * 100 }%`;
 					this.#updateCroppedWaveform();
					break;
				case "duration":
					this.#elements.inputDuration.value =
					this.#dur = +val;
					this.#updatePxPerBeat();
					break;
				case "step":
					this.#elements.stepBtn.firstChild.textContent = this.#convertStepToFrac( +val );
					break;
			}
		}
	}

	// .........................................................................
	setBufferName( name ) {
		this.#elements.srcName.textContent = name;
	}
	setBuffer( buf ) {
		this.#buffer = buf;
		this.#updateCroppedWaveform();
		gsuiWaveform.drawBuffer( this.#elements.srcWave.firstChild, gsuiSlicer.#resW, gsuiSlicer.#resH, buf );
	}
	addSlice( id, obj ) {
		const svg = GSUI.createElementNS( "svg", { class: "gsuiSlicer-preview-wave", "data-id": id, preserveAspectRatio: "none" },
				GSUI.createElementNS( "use" ),
			),
			sli = GSUI.createElement( "div", { class: "gsuiSlicer-slices-slice", "data-id": id } );

		svg.firstChild.setAttributeNS( "http://www.w3.org/1999/xlink", "href", `#${ this.#waveDef.id }` );
		this.#slices.set( id, [ svg, sli ] );
		this.changeSlice( id, obj );
		this.#elements.preview.append( svg );
		this.#elements.slices.append( sli );
	}
	changeSlice( id, obj ) {
		const [ svg, sli ] = this.#slices.get( id ),
			x = obj.x ?? +sli.dataset.x,
			y = obj.y ?? +sli.dataset.y,
			w = obj.w ?? +sli.dataset.w;

		if ( "x" in obj || "w" in obj ) {
			svg.style.left =
			sli.style.left = `${ x * 100 }%`;
			svg.style.width =
			sli.style.width = `${ w * 100 }%`;
		}
		if ( "y" in obj ) {
			sli.style.height = `${ ( 1 - y ) * 100 }%`;
		}
		svg.setAttribute( "viewBox", `${ ( x - ( x - y ) ) * gsuiSlicer.#resW } 0 ${ w * gsuiSlicer.#resW } ${ gsuiSlicer.#resH }` );
	}
	deleteSlice( id ) {
		const arr = this.#slices.get( id );

		if ( arr ) {
			this.#slices.delete( id );
			arr[ 0 ].remove();
			arr[ 1 ].remove();
		}
	}

	// .........................................................................
	#updateCroppedWaveform() {
		if ( this.#buffer ) {
			const cropa = this.#buffer.duration * this.getAttribute( "cropa" ),
				cropb = this.#buffer.duration * this.getAttribute( "cropb" );

			gsuiWaveform.drawBuffer( this.#waveDef, gsuiSlicer.#resW, gsuiSlicer.#resH, this.#buffer, cropa, cropb - cropa );
		}
	}
	#updatePxPerBeat( dur ) {
		this.#elements.beatlines[ 0 ].setAttribute( "pxperbeat", this.#slicesWidth / ( dur || this.#dur ) );
		this.#elements.beatlines[ 1 ].setAttribute( "pxperbeat", this.#slicesHeight / ( dur || this.#dur ) );
	}
	#getPercMouseX( pageX ) {
		const bcr = this.#elements.srcSample.getBoundingClientRect();

		return GSUI.clamp( +( ( pageX - bcr.left ) / bcr.width ).toFixed( 3 ), 0, 1 );
	}
	#updateCropSide( pageX ) {
		const perc = this.#getPercMouseX( pageX ),
			percA = +this.getAttribute( "cropa" ),
			percB = +this.getAttribute( "cropb" ),
			ave = ( percB - percA ) / 2;

		return perc < percA + ave ? "cropa" : "cropb";
	}
	#convertStepToFrac( step ) {
		return (
			step >= 1 ? "1" :
			step >= .5 ? "1 / 2" :
			step >= .25 ? "1 / 4" : "1 / 8"
		);
	}

	// .........................................................................
	#onresize() {
		const svg = this.#elements.diagonalLine,
			{ width: w, height: h } = svg.getBoundingClientRect();

		this.#slicesWidth = w;
		this.#slicesHeight = h;
		GSUI.setAttribute( svg, "viewBox", `0 0 ${ w } ${ h }` );
		GSUI.setAttribute( svg.firstChild, "x2", w );
		GSUI.setAttribute( svg.firstChild, "y2", h );
		this.#updatePxPerBeat();
	}
	#onchangeDuration( e ) {
		const dur = +e.target.value;

		GSUI.setAttribute( this, "duration", dur );
		this.#dispatch( "changeDuration", dur );
	}
	#onclickStep() {
		const v = +this.getAttribute( "step" ),
			frac =
				v >= 1 ? 2 :
				v >= .5 ? 4 :
				v >= .25 ? 8 : 1;

		GSUI.setAttribute( this, "step", 1 / frac );
	}
	#onmousedownCrop( e ) {
		GSUI.unselectText();
		GSUI.dragshield.show( "pointer" );
		document.addEventListener( "mousemove", this.#onmousemoveCropBind );
		document.addEventListener( "mouseup", this.#onmouseupCropBind );
		this.#cropSide = this.#updateCropSide( e.pageX );
		this.#cropSave = this.getAttribute( this.#cropSide );
		this.#onmousemoveCrop( e );
	}
	#onmousemoveCrop( e ) {
		GSUI.setAttribute( this, this.#cropSide, this.#getPercMouseX( e.pageX ) );
	}
	#onmouseupCrop( e ) {
		const val = this.getAttribute( this.#cropSide );

		document.removeEventListener( "mousemove", this.#onmousemoveCropBind );
		document.removeEventListener( "mouseup", this.#onmouseupCropBind );
		GSUI.dragshield.hide();
		if ( this.#cropSave !== val ) {
			this.#dispatch( this.#cropSide === "cropa" ? "cropA" : "cropB", +val );
		}
	}
}

customElements.define( "gsui-slicer", gsuiSlicer );

Object.freeze( gsuiSlicer );
<commit_msg>gsuiSlicer: use setPointerCapture<commit_after>"use strict";

class gsuiSlicer extends HTMLElement {
	static #resW = 1000
	static #resH = 64

	#dur = 4
	#slices = new Map()
	#buffer = null
	#cropSide = "A"
	#cropSave = null
	#slicesWidth = 100
	#slicesHeight = 100
	#onresizeBind = this.#onresize.bind( this )
	#dispatch = GSUI.dispatchEvent.bind( null, this, "gsuiSlicer" )
	#children = GSUI.getTemplate( "gsui-slicer" )
	#waveDef = GSUI.createElementNS( "polyline" )
	#elements = GSUI.findElements( this.#children, {
		beatlines: [
			"gsui-beatlines:first-child",
			"gsui-beatlines:last-child",
		],
		srcSample: ".gsuiSlicer-source-sample",
		srcName: ".gsuiSlicer-source-name",
		srcWave: ".gsuiSlicer-source-wave",
		cropA: ".gsuiSlicer-source-cropA",
		cropB: ".gsuiSlicer-source-cropB",
		diagonalLine: ".gsuiSlicer-slices-line",
		preview: ".gsuiSlicer-preview",
		slices: ".gsuiSlicer-slices-wrap",
		inputDuration: ".gsuiSlicer-duration-input",
		stepBtn: ".gsuiSlicer-step",
	} )

	constructor() {
		const defs = document.querySelector( "#gsuiSlicer-waveDefs defs" );

		super();
		Object.seal( this );

		if ( !defs ) {
			document.body.prepend( GSUI.createElementNS( "svg", { id: "gsuiSlicer-waveDefs" },
				GSUI.createElementNS( "defs" ),
			) );
			this.#waveDef.dataset.id = 1;
		} else {
			this.#waveDef.dataset.id = 1 + Array.prototype.reduce.call( defs.children,
				( max, p ) => Math.max( max, p.dataset.id ), 0 );
		}
		this.#waveDef.id = `gsuiSlicer-waveDef-${ this.#waveDef.dataset.id }`;
		this.#elements.srcSample.onpointerdown = this.#onpointerdownCrop.bind( this );
		this.#elements.inputDuration.onchange = this.#onchangeDuration.bind( this );
		this.#elements.stepBtn.onclick = this.#onclickStep.bind( this );
	}

	// .........................................................................
	connectedCallback() {
		if ( this.#children ) {
			this.append( ...this.#children );
			this.#children = null;
			GSUI.recallAttributes( this, {
				cropa: 0,
				cropb: 1,
				step: 1,
				duration: 4,
				timedivision: "4/4",
			} );
		}
		document.querySelector( "#gsuiSlicer-waveDefs defs" ).append( this.#waveDef );
		GSUI.observeSizeOf( this, this.#onresizeBind );
	}
	disconnectedCallback() {
		this.#waveDef.remove();
		GSUI.unobserveSizeOf( this, this.#onresizeBind );
	}
	static get observedAttributes() {
		return [ "duration", "step", "timedivision", "cropa", "cropb" ];
	}
	attributeChangedCallback( prop, prev, val ) {
		if ( !this.#children && prev !== val ) {
			switch ( prop ) {
				case "timedivision":
					this.#elements.beatlines[ 0 ].setAttribute( "timedivision", val );
					this.#elements.beatlines[ 1 ].setAttribute( "timedivision", val );
					break;
				case "cropa":
 					this.#elements.cropA.style.left = `${ val * 100 }%`;
 					this.#updateCroppedWaveform();
					break;
				case "cropb":
 					this.#elements.cropB.style.left = `${ val * 100 }%`;
 					this.#updateCroppedWaveform();
					break;
				case "duration":
					this.#elements.inputDuration.value =
					this.#dur = +val;
					this.#updatePxPerBeat();
					break;
				case "step":
					this.#elements.stepBtn.firstChild.textContent = this.#convertStepToFrac( +val );
					break;
			}
		}
	}

	// .........................................................................
	setBufferName( name ) {
		this.#elements.srcName.textContent = name;
	}
	setBuffer( buf ) {
		this.#buffer = buf;
		this.#updateCroppedWaveform();
		gsuiWaveform.drawBuffer( this.#elements.srcWave.firstChild, gsuiSlicer.#resW, gsuiSlicer.#resH, buf );
	}
	addSlice( id, obj ) {
		const svg = GSUI.createElementNS( "svg", { class: "gsuiSlicer-preview-wave", "data-id": id, preserveAspectRatio: "none" },
				GSUI.createElementNS( "use" ),
			),
			sli = GSUI.createElement( "div", { class: "gsuiSlicer-slices-slice", "data-id": id } );

		svg.firstChild.setAttributeNS( "http://www.w3.org/1999/xlink", "href", `#${ this.#waveDef.id }` );
		this.#slices.set( id, [ svg, sli ] );
		this.changeSlice( id, obj );
		this.#elements.preview.append( svg );
		this.#elements.slices.append( sli );
	}
	changeSlice( id, obj ) {
		const [ svg, sli ] = this.#slices.get( id ),
			x = obj.x ?? +sli.dataset.x,
			y = obj.y ?? +sli.dataset.y,
			w = obj.w ?? +sli.dataset.w;

		if ( "x" in obj || "w" in obj ) {
			svg.style.left =
			sli.style.left = `${ x * 100 }%`;
			svg.style.width =
			sli.style.width = `${ w * 100 }%`;
		}
		if ( "y" in obj ) {
			sli.style.height = `${ ( 1 - y ) * 100 }%`;
		}
		svg.setAttribute( "viewBox", `${ ( x - ( x - y ) ) * gsuiSlicer.#resW } 0 ${ w * gsuiSlicer.#resW } ${ gsuiSlicer.#resH }` );
	}
	deleteSlice( id ) {
		const arr = this.#slices.get( id );

		if ( arr ) {
			this.#slices.delete( id );
			arr[ 0 ].remove();
			arr[ 1 ].remove();
		}
	}

	// .........................................................................
	#updateCroppedWaveform() {
		if ( this.#buffer ) {
			const cropa = this.#buffer.duration * this.getAttribute( "cropa" ),
				cropb = this.#buffer.duration * this.getAttribute( "cropb" );

			gsuiWaveform.drawBuffer( this.#waveDef, gsuiSlicer.#resW, gsuiSlicer.#resH, this.#buffer, cropa, cropb - cropa );
		}
	}
	#updatePxPerBeat( dur ) {
		this.#elements.beatlines[ 0 ].setAttribute( "pxperbeat", this.#slicesWidth / ( dur || this.#dur ) );
		this.#elements.beatlines[ 1 ].setAttribute( "pxperbeat", this.#slicesHeight / ( dur || this.#dur ) );
	}
	#getPercMouseX( pageX ) {
		const bcr = this.#elements.srcSample.getBoundingClientRect();

		return GSUI.clamp( +( ( pageX - bcr.left ) / bcr.width ).toFixed( 3 ), 0, 1 );
	}
	#updateCropSide( pageX ) {
		const perc = this.#getPercMouseX( pageX ),
			percA = +this.getAttribute( "cropa" ),
			percB = +this.getAttribute( "cropb" ),
			ave = ( percB - percA ) / 2;

		return perc < percA + ave ? "cropa" : "cropb";
	}
	#convertStepToFrac( step ) {
		return (
			step >= 1 ? "1" :
			step >= .5 ? "1 / 2" :
			step >= .25 ? "1 / 4" : "1 / 8"
		);
	}

	// .........................................................................
	#onresize() {
		const svg = this.#elements.diagonalLine,
			{ width: w, height: h } = svg.getBoundingClientRect();

		this.#slicesWidth = w;
		this.#slicesHeight = h;
		GSUI.setAttribute( svg, "viewBox", `0 0 ${ w } ${ h }` );
		GSUI.setAttribute( svg.firstChild, "x2", w );
		GSUI.setAttribute( svg.firstChild, "y2", h );
		this.#updatePxPerBeat();
	}
	#onchangeDuration( e ) {
		const dur = +e.target.value;

		GSUI.setAttribute( this, "duration", dur );
		this.#dispatch( "changeDuration", dur );
	}
	#onclickStep() {
		const v = +this.getAttribute( "step" ),
			frac =
				v >= 1 ? 2 :
				v >= .5 ? 4 :
				v >= .25 ? 8 : 1;

		GSUI.setAttribute( this, "step", 1 / frac );
	}
	#onpointerdownCrop( e ) {
		GSUI.unselectText();
		this.#elements.srcSample.setPointerCapture( e.pointerId );
		this.#elements.srcSample.onpointermove = this.#onpointermoveCrop.bind( this );
		this.#elements.srcSample.onpointerup = this.#onpointerupCrop.bind( this );
		this.#cropSide = this.#updateCropSide( e.pageX );
		this.#cropSave = this.getAttribute( this.#cropSide );
		this.#onpointermoveCrop( e );
	}
	#onpointermoveCrop( e ) {
		GSUI.setAttribute( this, this.#cropSide, this.#getPercMouseX( e.pageX ) );
	}
	#onpointerupCrop( e ) {
		const val = this.getAttribute( this.#cropSide );

		this.#elements.srcSample.releasePointerCapture( e.pointerId );
		this.#elements.srcSample.onpointermove =
		this.#elements.srcSample.onpointerup = null;
		if ( this.#cropSave !== val ) {
			this.#dispatch( this.#cropSide === "cropa" ? "cropA" : "cropB", +val );
		}
	}
}

customElements.define( "gsui-slicer", gsuiSlicer );

Object.freeze( gsuiSlicer );
<|endoftext|>


<commit_before>Template.registerHelper('pluralize', function (n, thing) {
    if (n === 1) {
        return '1 ' + thing;
    } else {
        return n + ' ' + thing + 's';
    }
});
<commit_msg>refactor: better names in pluralize<commit_after>Template.registerHelper('pluralize', function (qty, baseWord) {
    if (qty === 1) {
        return '1 ' + baseWord;
    } else {
        return qty + ' ' + baseWord + 's';
    }
});
<|endoftext|>