<commit_before>import Ember from 'ember';

const {
  Component,
  run,
  computed,
  $
} = Ember;

export default Component.extend({
  headerTemplate: null,
  footerTemplate: null,
  activeViewport: true,
  scale: undefined,
  minScale: 0.1,
  maxScale: 5.0,
  increment: 0.25,
  originalWidth: undefined,
  originalHeight: undefined,
  width: undefined,
  height: undefined,
  panX: 0,
  panY: 0,
  centerOnFit: true,
  delay: 250,

  didInsertElement() {
    const viewport = this.$('.zoom-viewport');
    const content = viewport.children('.zoom-content');
    const scale = this.get('scale');
    const start = (e) => {
      if(!normalizeEvent(e).touches && e.which !== 1) { return; }

      startPinch(e);
      e.preventDefault();
    };

    this._super(...arguments);

    run.scheduleOnce('afterRender', () => {
      this.set('$viewport', content.parent());
      this.set('$content', content);
      this.set('originalWidth', content.width());
      this.set('originalHeight', content.height());

      viewport.on('touchstart mousedown', {zone: this}, (e) => {
        if(this.get('activeViewport')) { start(e); }
      });

      content.on('touchstart mousedown', {zone: this}, start);

      if(scale) { this.zoomTo(scale); }
      else { this.zoomFit(); }
    });
  },

  matrix: computed('panX', 'panY', 'scale', function () {
    const [scale, x, y] = [this.get('scale'), this.get('panX'), this.get('panY')];
    return `matrix(${scale}, 0, 0, ${scale}, ${x}, ${y})`;
  }),

  zoomFit() {
    const viewport = this.get('$viewport');
    const content = this.get('$content');
    const [ratioX, ratioY] = [
      viewport.width() / content.width(),
      viewport.height() / content.height()
    ];
    const ratio = Math.min(ratioX, ratioY);
    let [dx, dy] = [
      (content.width() / 2) * (ratio - 1),
      (content.height() / 2) * (ratio - 1)
    ];

    if(this.get('centerOnFit')) {
      if(ratioX > ratioY) { dx = (viewport.width() - content.width()) / 2; }
      if(ratioX < ratioY) { dy = (viewport.height() - content.height()) / 2; }
    }

    this.set('panX', dx);
    this.set('panY', dy);
    this.zoomTo(ratio);
  },

  zoomTo(ratio) {
    const content = this.get('$content');
    const [min, max] = [this.get('minScale'), this.get('maxScale')];

    if(ratio > max) { ratio = max; }
    else if(ratio < min) { ratio = min; }

    this.set('scale', ratio);
    this.set('width', this.get('originalWidth') * ratio);
    this.set('height', this.get('originalHeight') * ratio);

    content.css({transform: this.get('matrix')});
  },

  zoomBy(delta) {
    const content = this.get('$content');
    const delay = +this.get('delay');

    if(delay && delta) {
      content.css({transition: `${delay}ms ease`});
      setTimeout(() => content.css({transition: ''}), delay);
    }
    this.zoomTo(this.get('scale') + delta);
  },

  actions: {
    zoomTo(scale=1) { this.zoomTo(scale); },
    zoomIn() { this.zoomBy(this.get('increment')); },
    zoomOut() { this.zoomBy(-this.get('increment')); },
    zoomFit() { this.zoomFit(); }
  }
});

function startPinch(event) {
  const {zone} = event.data;
  const touch0 = normalizeTouches(event);
  const [scale0, x0, y0] = [
    zone.get('scale'),
    zone.get('panX') - touch0.x,
    zone.get('panY') - touch0.y
  ];

  function move(e) {
    run.debounce(zone, () => {
      const {x, y, distance} = normalizeTouches(normalizeEvent(e));

      zone.set('scale', scale0 * distance / touch0.distance);
      zone.set('panX', x0 + x);
      zone.set('panY', y0 + y);
      zone.zoomBy(0);
    }, 7, true);
  }

  $(document).on('mousemove touchmove', move);

  $(document).one('mouseup touchend', () =>
    $(document).off('mousemove touchmove', move)
  );
}

function normalizeEvent(event) {
  if(event.originalEvent) {
    event.touches = Array.prototype.slice.call(event.originalEvent.touches || []);
    event.pageX = event.originalEvent.pageX;
    event.pageY = event.originalEvent.pageY;
  }
  return event;
}<commit_msg>Fix links within zoom-zone on touch devices<commit_after>import Ember from 'ember';

const {
  Component,
  run,
  computed,
  $
} = Ember;

export default Component.extend({
  headerTemplate: null,
  footerTemplate: null,
  activeViewport: true,
  scale: undefined,
  minScale: 0.1,
  maxScale: 5.0,
  increment: 0.25,
  originalWidth: undefined,
  originalHeight: undefined,
  width: undefined,
  height: undefined,
  panX: 0,
  panY: 0,
  centerOnFit: true,
  delay: 250,

  didInsertElement() {
    const viewport = this.$('.zoom-viewport');
    const content = viewport.children('.zoom-content');
    const scale = this.get('scale');
    const start = (e) => {
      if(normalizeEvent(e).touches || e.which !== 1) { startPinch(e); }
    };

    this._super(...arguments);

    run.scheduleOnce('afterRender', () => {
      this.set('$viewport', content.parent());
      this.set('$content', content);
      this.set('originalWidth', content.width());
      this.set('originalHeight', content.height());

      viewport.on('touchstart mousedown', {zone: this}, (e) => {
        if(this.get('activeViewport')) { start(e); }
      });

      content.on('touchstart mousedown', {zone: this}, start);

      if(scale) { this.zoomTo(scale); }
      else { this.zoomFit(); }
    });
  },

  matrix: computed('panX', 'panY', 'scale', function () {
    const [scale, x, y] = [this.get('scale'), this.get('panX'), this.get('panY')];
    return `matrix(${scale}, 0, 0, ${scale}, ${x}, ${y})`;
  }),

  zoomFit() {
    const viewport = this.get('$viewport');
    const content = this.get('$content');
    const [ratioX, ratioY] = [
      viewport.width() / content.width(),
      viewport.height() / content.height()
    ];
    const ratio = Math.min(ratioX, ratioY);
    let [dx, dy] = [
      (content.width() / 2) * (ratio - 1),
      (content.height() / 2) * (ratio - 1)
    ];

    if(this.get('centerOnFit')) {
      if(ratioX > ratioY) { dx = (viewport.width() - content.width()) / 2; }
      if(ratioX < ratioY) { dy = (viewport.height() - content.height()) / 2; }
    }

    this.set('panX', dx);
    this.set('panY', dy);
    this.zoomTo(ratio);
  },

  zoomTo(ratio) {
    const content = this.get('$content');
    const [min, max] = [this.get('minScale'), this.get('maxScale')];

    if(ratio > max) { ratio = max; }
    else if(ratio < min) { ratio = min; }

    this.set('scale', ratio);
    this.set('width', this.get('originalWidth') * ratio);
    this.set('height', this.get('originalHeight') * ratio);

    content.css({transform: this.get('matrix')});
  },

  zoomBy(delta) {
    const content = this.get('$content');
    const delay = +this.get('delay');

    if(delay && delta) {
      content.css({transition: `${delay}ms ease`});
      setTimeout(() => content.css({transition: ''}), delay);
    }
    this.zoomTo(this.get('scale') + delta);
  },

  actions: {
    zoomTo(scale=1) { this.zoomTo(scale); },
    zoomIn() { this.zoomBy(this.get('increment')); },
    zoomOut() { this.zoomBy(-this.get('increment')); },
    zoomFit() { this.zoomFit(); }
  }
});

function startPinch(event) {
  const {zone} = event.data;
  const touch0 = normalizeTouches(event);
  const [scale0, x0, y0] = [
    zone.get('scale'),
    zone.get('panX') - touch0.x,
    zone.get('panY') - touch0.y
  ];

  function move(e) {
    e.preventDefault();
    run.debounce(zone, () => {
      const {x, y, distance} = normalizeTouches(normalizeEvent(e));

      zone.set('scale', scale0 * distance / touch0.distance);
      zone.set('panX', x0 + x);
      zone.set('panY', y0 + y);
      zone.zoomBy(0);
    }, 7, true);
  }

  $(document).on('mousemove touchmove', move);

  $(document).one('mouseup touchend', () =>
    $(document).off('mousemove touchmove', move)
  );
}

function normalizeEvent(event) {
  if(event.originalEvent) {
    event.touches = Array.prototype.slice.call(event.originalEvent.touches || []);
    event.pageX = event.originalEvent.pageX;
    event.pageY = event.originalEvent.pageY;
  }
  return event;
}
{'fraction_non_alphanumeric': 0.1993268511593119, 'fraction_numerical': 0.008476689104961356, 'mean_word_length': 3.1228160328879753, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5778028', 'n_tokens_mistral': 2862, 'n_tokens_neox': 2767, 'n_words': 533}