<commit_before>/**
 * Copyright 2016 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {CSS} from '../../../build/amp-sticky-ad-1.0.css';
import {CommonSignals} from '../../../src/common-signals';
import {Services} from '../../../src/services';
import {
  computedStyle,
  removeAlphaFromColor,
  setStyle,
  toggle,
} from '../../../src/style';
import {dev, user, userAssert} from '../../../src/log';
import {removeElement, whenUpgradedToCustomElement} from '../../../src/dom';

class AmpStickyAd extends AMP.BaseElement {
  /** @param {!AmpElement} element */
  constructor(element) {
    super(element);

    /** @const @private {!../../../src/service/vsync-impl.Vsync} */
    this.vsync_ = this.getVsync();

    /** @private {?Element} */
    this.ad_ = null;

    /** @private {?../../../src/service/viewport/viewport-interface.ViewportInterface} */
    this.viewport_ = null;

    /** @private {boolean} */
    this.visible_ = false;

    /** @private {?UnlistenDef} */
    this.scrollUnlisten_ = null;

    /** @private {boolean} */
    this.collapsed_ = false;

    /** @private {?Promise} */
    this.adReadyPromise_ = null;
  }

  /** @override */
  buildCallback() {
    this.viewport_ = this.getViewport();
    this.element.classList.add('i-amphtml-sticky-ad-layout');

    const children = this.getRealChildren();
    userAssert(
      children.length == 1 && children[0].tagName == 'AMP-AD',
      'amp-sticky-ad must have a single amp-ad child'
    );

    this.ad_ = children[0];
    Services.ownersForDoc(this.element).setOwner(this.ad_, this.element);

    this.adReadyPromise_ = whenUpgradedToCustomElement(
      dev().assertElement(this.ad_)
    )
      .then((ad) => {
        return ad.whenBuilt();
      })
      .then(() => {
        return this.mutateElement(() => {
          toggle(this.element, true);
        });
      });

    const paddingBar = this.win.document.createElement(
      'amp-sticky-ad-top-padding'
    );
    paddingBar.classList.add('amp-sticky-ad-top-padding');
    this.element.insertBefore(paddingBar, this.ad_);

    // On viewport scroll, check requirements for amp-stick-ad to display.
    this.win.setTimeout(() => {
      this.scrollUnlisten_ = this.viewport_.onScroll(() => this.onScroll_());
    });
  }

  /** @override */
  layoutCallback() {
    // Reschedule layout for ad if layout sticky-ad again.
    if (this.visible_) {
      toggle(this.element, true);
      const borderBottom = this.element./*OK*/ offsetHeight;
      this.viewport_.updatePaddingBottom(borderBottom);
      const owners = Services.ownersForDoc(this.element);
      owners.updateInViewport(
        this.element,
        dev().assertElement(this.ad_),
        true
      );
      owners.scheduleLayout(this.element, dev().assertElement(this.ad_));
    }
    return Promise.resolve();
  }

  /** @override */
  isAlwaysFixed() {
    return true;
  }

  /** @override */
  unlayoutCallback() {
    this.viewport_.updatePaddingBottom(0);
    return true;
  }

  /** @override */
  detachedCallback() {
    this.removeOnScrollListener_();
  }

  /** @override */
  collapsedCallback() {
    this.collapsed_ = true;
    this.visible_ = false;
    toggle(this.element, false);
    this.vsync_.mutate(() => {
      this.viewport_.updatePaddingBottom(0);
    });
  }

  /**
   * The function that remove listener to viewport onScroll event.
   * @private
   */
  removeOnScrollListener_() {
    if (this.scrollUnlisten_) {
      this.scrollUnlisten_();
      this.scrollUnlisten_ = null;
    }
  }

  /**
   * The listener function that listen on viewport scroll event.
   * And decide when to display the ad.
   * @private
   */
  onScroll_() {
    const scrollTop = this.viewport_.getScrollTop();
    if (scrollTop > 1) {
      // Check greater than 1 because AMP set scrollTop to 1 in iOS.
      this.display_();
    }
  }

  /**
   * Display and load sticky ad.
   * @private
   */
  display_() {
    this.removeOnScrollListener_();
    this.adReadyPromise_.then(() => {
      // Wait for ad build ready. For example user dismiss user notification.
      this.mutateElement(() => {
        if (this.collapsed_) {
          // It's possible that if an AMP ad collapse before its layoutCallback.
          return;
        }
        this.visible_ = true;
        this.addCloseButton_();
        this.viewport_
          .addToFixedLayer(this.element, /* forceTransfer */ true)
          .then(() => this.scheduleLayoutForAd_());
      });
    });
  }

  /**
   * Function that check if ad has been built.  If not, wait for the "built"
   * signal. Otherwise schedule layout for ad.
   * @private
   */
  scheduleLayoutForAd_() {
    whenUpgradedToCustomElement(dev().assertElement(this.ad_)).then((ad) => {
      ad.whenBuilt().then(this.layoutAd_.bind(this));
    });
  }

  /**
   * Layout ad, and display sticky-ad container after layout complete.
   * @return {!Promise}
   * @private
   */
  layoutAd_() {
    const ad = dev().assertElement(this.ad_);
    const owners = Services.ownersForDoc(this.element);
    owners.updateInViewport(this.element, ad, true);
    owners.scheduleLayout(this.element, ad);
    // Wait for the earliest: `render-start` or `load-end` signals.
    // `render-start` is expected to arrive first, but it's not emitted by
    // all types of ads.
    const signals = ad.signals();
    return signals.whenSignal(CommonSignals.RENDER_START).then(() => {
      let backgroundColor;
      return this.measureElement(() => {
        backgroundColor = computedStyle(this.win, this.element)[
          'backgroundColor'
        ];
      }).then(() => {
        return this.vsync_.mutatePromise(() => {
          // Set sticky-ad to visible and change container style
          this.element.setAttribute('visible', '');
          // Add border-bottom to the body to compensate space that was taken
          // by sticky ad, so no content would be blocked by sticky ad unit.
          const borderBottom = this.element./*OK*/ offsetHeight;
          this.viewport_.updatePaddingBottom(borderBottom);
          this.forceOpacity_(backgroundColor);
        });
      });
    });
  }

  /**
   * The function that add a close button to sticky ad
   * @private
   */
  addCloseButton_() {
    const closeButton = this.win.document.createElement('button');
    closeButton.classList.add('amp-sticky-ad-close-button');
    closeButton.setAttribute(
      'aria-label',
      this.element.getAttribute('data-close-button-aria-label') ||
        'Close this ad'
    );
    const boundOnCloseButtonClick = this.onCloseButtonClick_.bind(this);
    closeButton.addEventListener('click', boundOnCloseButtonClick);
    this.element.appendChild(closeButton);
  }

  /**
   * The listener function that listen to click event and dismiss sticky ad
   * @private
   */
  onCloseButtonClick_() {
    this.vsync_.mutate(() => {
      this.visible_ = false;
      Services.ownersForDoc(this.element)./*OK*/ scheduleUnlayout(
        this.element,
        dev().assertElement(this.ad_)
      );
      this.viewport_.removeFromFixedLayer(this.element);
      removeElement(this.element);
      this.viewport_.updatePaddingBottom(0);
    });
  }

  /**
   * To check for background-color alpha and force it to be 1.
   * Whoever calls this needs to make sure it's in a vsync.
   * @param {string} backgroundColor
   * @private
   */
  forceOpacity_(backgroundColor) {
    const newBackgroundColor = removeAlphaFromColor(backgroundColor);
    if (backgroundColor == newBackgroundColor) {
      return;
    }
    user().warn(
      'AMP-STICKY-AD',
      'Do not allow container to be semitransparent'
    );
    setStyle(this.element, 'background-color', newBackgroundColor);
  }
}

AMP.extension('amp-sticky-ad', '1.0', (AMP) => {
  AMP.registerElement('amp-sticky-ad', AmpStickyAd, CSS);
});
<commit_msg>Only collapse sticky-ad when ad collapses (#30730)<commit_after>/**
 * Copyright 2016 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {CSS} from '../../../build/amp-sticky-ad-1.0.css';
import {CommonSignals} from '../../../src/common-signals';
import {Services} from '../../../src/services';
import {
  computedStyle,
  removeAlphaFromColor,
  setStyle,
  toggle,
} from '../../../src/style';
import {dev, user, userAssert} from '../../../src/log';
import {removeElement, whenUpgradedToCustomElement} from '../../../src/dom';

class AmpStickyAd extends AMP.BaseElement {
  /** @param {!AmpElement} element */
  constructor(element) {
    super(element);

    /** @const @private {!../../../src/service/vsync-impl.Vsync} */
    this.vsync_ = this.getVsync();

    /** @private {?Element} */
    this.ad_ = null;

    /** @private {?../../../src/service/viewport/viewport-interface.ViewportInterface} */
    this.viewport_ = null;

    /** @private {boolean} */
    this.visible_ = false;

    /** @private {?UnlistenDef} */
    this.scrollUnlisten_ = null;

    /** @private {boolean} */
    this.collapsed_ = false;

    /** @private {?Promise} */
    this.adReadyPromise_ = null;
  }

  /** @override */
  buildCallback() {
    this.viewport_ = this.getViewport();
    this.element.classList.add('i-amphtml-sticky-ad-layout');

    const children = this.getRealChildren();
    userAssert(
      children.length == 1 && children[0].tagName == 'AMP-AD',
      'amp-sticky-ad must have a single amp-ad child'
    );

    this.ad_ = children[0];
    Services.ownersForDoc(this.element).setOwner(this.ad_, this.element);

    this.adReadyPromise_ = whenUpgradedToCustomElement(
      dev().assertElement(this.ad_)
    )
      .then((ad) => {
        return ad.whenBuilt();
      })
      .then(() => {
        return this.mutateElement(() => {
          toggle(this.element, true);
        });
      });

    const paddingBar = this.win.document.createElement(
      'amp-sticky-ad-top-padding'
    );
    paddingBar.classList.add('amp-sticky-ad-top-padding');
    this.element.insertBefore(paddingBar, this.ad_);

    // On viewport scroll, check requirements for amp-stick-ad to display.
    this.win.setTimeout(() => {
      this.scrollUnlisten_ = this.viewport_.onScroll(() => this.onScroll_());
    });
  }

  /** @override */
  layoutCallback() {
    // Reschedule layout for ad if layout sticky-ad again.
    if (this.visible_) {
      toggle(this.element, true);
      const borderBottom = this.element./*OK*/ offsetHeight;
      this.viewport_.updatePaddingBottom(borderBottom);
      const owners = Services.ownersForDoc(this.element);
      owners.updateInViewport(
        this.element,
        dev().assertElement(this.ad_),
        true
      );
      owners.scheduleLayout(this.element, dev().assertElement(this.ad_));
    }
    return Promise.resolve();
  }

  /** @override */
  isAlwaysFixed() {
    return true;
  }

  /** @override */
  unlayoutCallback() {
    this.viewport_.updatePaddingBottom(0);
    return true;
  }

  /** @override */
  detachedCallback() {
    this.removeOnScrollListener_();
  }

  /** @override */
  collapsedCallback(element) {
    // We will only collapse the stick-ad when the ad collapses. The analytics
    // element will collapse after it's done initializing, which is normal.
    if (element !== this.ad_) {
      return;
    }
    this.collapsed_ = true;
    this.visible_ = false;
    toggle(this.element, false);
    this.vsync_.mutate(() => {
      this.viewport_.updatePaddingBottom(0);
    });
  }

  /**
   * The function that remove listener to viewport onScroll event.
   * @private
   */
  removeOnScrollListener_() {
    if (this.scrollUnlisten_) {
      this.scrollUnlisten_();
      this.scrollUnlisten_ = null;
    }
  }

  /**
   * The listener function that listen on viewport scroll event.
   * And decide when to display the ad.
   * @private
   */
  onScroll_() {
    const scrollTop = this.viewport_.getScrollTop();
    if (scrollTop > 1) {
      // Check greater than 1 because AMP set scrollTop to 1 in iOS.
      this.display_();
    }
  }

  /**
   * Display and load sticky ad.
   * @private
   */
  display_() {
    this.removeOnScrollListener_();
    this.adReadyPromise_.then(() => {
      // Wait for ad build ready. For example user dismiss user notification.
      this.mutateElement(() => {
        if (this.collapsed_) {
          // It's possible that if an AMP ad collapse before its layoutCallback.
          return;
        }
        this.visible_ = true;
        this.addCloseButton_();
        this.viewport_
          .addToFixedLayer(this.element, /* forceTransfer */ true)
          .then(() => this.scheduleLayoutForAd_());
      });
    });
  }

  /**
   * Function that check if ad has been built.  If not, wait for the "built"
   * signal. Otherwise schedule layout for ad.
   * @private
   */
  scheduleLayoutForAd_() {
    whenUpgradedToCustomElement(dev().assertElement(this.ad_)).then((ad) => {
      ad.whenBuilt().then(this.layoutAd_.bind(this));
    });
  }

  /**
   * Layout ad, and display sticky-ad container after layout complete.
   * @return {!Promise}
   * @private
   */
  layoutAd_() {
    const ad = dev().assertElement(this.ad_);
    const owners = Services.ownersForDoc(this.element);
    owners.updateInViewport(this.element, ad, true);
    owners.scheduleLayout(this.element, ad);
    // Wait for the earliest: `render-start` or `load-end` signals.
    // `render-start` is expected to arrive first, but it's not emitted by
    // all types of ads.
    const signals = ad.signals();
    return signals.whenSignal(CommonSignals.RENDER_START).then(() => {
      let backgroundColor;
      return this.measureElement(() => {
        backgroundColor = computedStyle(this.win, this.element)[
          'backgroundColor'
        ];
      }).then(() => {
        return this.vsync_.mutatePromise(() => {
          // Set sticky-ad to visible and change container style
          this.element.setAttribute('visible', '');
          // Add border-bottom to the body to compensate space that was taken
          // by sticky ad, so no content would be blocked by sticky ad unit.
          const borderBottom = this.element./*OK*/ offsetHeight;
          this.viewport_.updatePaddingBottom(borderBottom);
          this.forceOpacity_(backgroundColor);
        });
      });
    });
  }

  /**
   * The function that add a close button to sticky ad
   * @private
   */
  addCloseButton_() {
    const closeButton = this.win.document.createElement('button');
    closeButton.classList.add('amp-sticky-ad-close-button');
    closeButton.setAttribute(
      'aria-label',
      this.element.getAttribute('data-close-button-aria-label') ||
        'Close this ad'
    );
    const boundOnCloseButtonClick = this.onCloseButtonClick_.bind(this);
    closeButton.addEventListener('click', boundOnCloseButtonClick);
    this.element.appendChild(closeButton);
  }

  /**
   * The listener function that listen to click event and dismiss sticky ad
   * @private
   */
  onCloseButtonClick_() {
    this.vsync_.mutate(() => {
      this.visible_ = false;
      Services.ownersForDoc(this.element)./*OK*/ scheduleUnlayout(
        this.element,
        dev().assertElement(this.ad_)
      );
      this.viewport_.removeFromFixedLayer(this.element);
      removeElement(this.element);
      this.viewport_.updatePaddingBottom(0);
    });
  }

  /**
   * To check for background-color alpha and force it to be 1.
   * Whoever calls this needs to make sure it's in a vsync.
   * @param {string} backgroundColor
   * @private
   */
  forceOpacity_(backgroundColor) {
    const newBackgroundColor = removeAlphaFromColor(backgroundColor);
    if (backgroundColor == newBackgroundColor) {
      return;
    }
    user().warn(
      'AMP-STICKY-AD',
      'Do not allow container to be semitransparent'
    );
    setStyle(this.element, 'background-color', newBackgroundColor);
  }
}

AMP.extension('amp-sticky-ad', '1.0', (AMP) => {
  AMP.registerElement('amp-sticky-ad', AmpStickyAd, CSS);
});
<|endoftext|>


<commit_before>import { map } from './collection'
import _ from 'lodash/fp'
import { when } from './logic'

export const wrap = (pre, post, content) =>
  (pre || '') + content + (post || pre || '')
export const quote = _.partial(wrap, ['"', '"'])
export const parens = _.partial(wrap, ['(', ')'])
export const concatStrings = _.flow(
  _.compact,
  _.map(_.trim),
  _.join(' ')
)
export const trimStrings = map(when(_.isString, _.trim))

export let autoLabel = (string = '') => {
  // Prevent Acronyms from being lower cased
  let acronymRegex = new RegExp('([A-Z])([A-Z])($|.)', 'g')
  while (string.match(acronymRegex)) {
    string = string.replace(acronymRegex, '$1 $2$3')
  }
  string = _.startCase(string)
  // Remove Mid-Acronym Whitespace
  let whitespacedAcronymRegex = new RegExp('([A-Z])[ ]([A-Z])($|[^a-z])', 'g')
  while (string.match(whitespacedAcronymRegex)) {
    string = string.replace(whitespacedAcronymRegex, '$1$2$3')
  }
  // Put a space between words and numbers
  string = string.replace(/([a-z]|[A-Z])([0-9])/g, '$1 $2')

  return string
}
export let autoLabelOption = a => ({
  value: a.value || a,
  label: a.label || autoLabel(a.value || a),
})
export let autoLabelOptions = _.map(autoLabelOption)
<commit_msg>replace autoLabel implementation with _.startCase<commit_after>import { map } from './collection'
import _ from 'lodash/fp'
import { when } from './logic'

export const wrap = (pre, post, content) =>
  (pre || '') + content + (post || pre || '')
export const quote = _.partial(wrap, ['"', '"'])
export const parens = _.partial(wrap, ['(', ')'])
export const concatStrings = _.flow(
  _.compact,
  _.map(_.trim),
  _.join(' ')
)
export const trimStrings = map(when(_.isString, _.trim))

// _.startCase does the trick, deprecate it!
export let autoLabel = _.startCase
export let autoLabelOption = a => ({
  value: a.value || a,
  label: a.label || autoLabel(a.value || a),
})
export let autoLabelOptions = _.map(autoLabelOption)
<|endoftext|>


<commit_before>{
  "id":15,
  "name":"Apply for a Budgeting Loan",
  "description": "Lets users apply for a short-term cash loan so that they can afford to buy essential items or services.",
  "theme": "Working Age",
  "location": "Leeds",
  "phase":"beta",
  "phase_modifier":"Private",
  "sro":"Derek Hobbs",
  "service_man":"Zoe Gould",
  "phase-history":{
    "discovery": [
      {"label":"Completed", "date": "May 2015"}
    ],
    "alpha": [
      {"label":"Completed", "date": "November 2015"}
    ],
    "beta": [
      {"label":"Started", "date": "November 2015"},
      {"label":"Private beta started", "date": "February 2016"},
      {"label":"Public beta predicted", "date": "April 2016"}
    ]
  },
  "priority":"Low"
}
<commit_msg>Changed predicted public beta date<commit_after>{
  "id":15,
  "name":"Apply for a Budgeting Loan",
  "description": "Lets users apply for a short-term cash loan so that they can afford to buy essential items or services.",
  "theme": "Working Age",
  "location": "Leeds",
  "phase":"beta",
  "phase_modifier":"Private",
  "sro":"Derek Hobbs",
  "service_man":"Zoe Gould",
  "phase-history":{
    "discovery": [
      {"label":"Completed", "date": "May 2015"}
    ],
    "alpha": [
      {"label":"Completed", "date": "November 2015"}
    ],
    "beta": [
      {"label":"Started", "date": "November 2015"},
      {"label":"Private beta started", "date": "February 2016"},
      {"label":"Public beta predicted", "date": "May 2016"}
    ]
  },
  "priority":"Low"
}
<|endoftext|>


<commit_before>/*
* svg.rx.js
*/
/*jshint devel:true */

/*
* Note: The event handling code is based on 'svg.draggable.js' -> https://github.com/wout/svg.draggable.js
*       but we only enable stuff that we actually test (manually). I.e. 'SVG.Nested', 'SVG.Use', 'SVG.Text' support
*       remains disabled until we need it, and there are demos that exercise those things.
*/

(function () {
  "use strict";

  function assert(b,msg) {    // (boolish, String) =>
    if (!b) {
      throw ("Assert failed" + (msg ? ": "+msg : ""))
    }
  }
  assert(true);   // just use it up (jshint)

  // Note: RxJS does not seem to have what Scala calls '.collect': to both filter and convert.
  //
  // Ref. https://xgrommx.github.io/rx-book/content/guidelines/implementations/index.html#implement-new-operators-by-composing-existing-operators
  //
  // tbd. Is it true RxJS does not have a built-in operator for this? Ask at StackOverflow (pointing to this line). AKa271215
  //
  Rx.Observable.prototype.filterAndSelect = function (f) {
    return this.select(f).filter( function (x) { return x !== undefined; } );
  }

  // Helper function to give us an outer stream of drags. Either coming from desktop, or touch.
  //
  // '...Obs'  parameters are 'observable of x' where 'x' is either 'MouseEvent' or { pageX:Int, pageY:Int [,_ev:TouchEvent] }'.
  //
  // 'debugName': used for console output
  //
  // References:
  //    MouseEvent -> https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
  //    Touch -> https://developer.mozilla.org/en-US/docs/Web/API/Touch
  //
  var outerObs = function (el, startObs, moveObs, endObs, debugName) {    // (SVG.Element|SVG.G|SVG.Doc, observable of MouseEvent or {pageX: Int, pageY: Int, _ev:TouchEvent}, ...) -> observable of observable of {x:Int, y:Int}

    debugName = debugName | "unknown";

    var isDoc = (el instanceof SVG.Doc);

    if (! ((el instanceof SVG.Element) || (el instanceof SVG.G) || isDoc)) {
      throw "svg.rx.js does not support: "+ (typeof el);
    }

    /*** disabled
    // Prevent browser default behavior and propagation to parent entries.
    //
    // Note: Preventing default behaviour on mouse events is a bit weird, since the same event can carry multiple touch id
    //      information. We cannot just prevent one of them, but have to bite out all. Does this do any harm? AKa271215
    //
    function eatUp (o) {
      var ev = o._ev | o;

      debugger;
      ev.preventDefault();      // don't cause browser pan, refresh etc. (touch)
      ev.stopPropagation();     // needed eg. for demo1 (so corner events don't also drag the main element)
    }
    ***/

    // Note: keep helper functions within the '.select()' function, since things like the positioning of the element,
    //      its transformation etc. may change during the lifespan of the observable.

    return startObs.
      select( function (oStart) {   // (MouseEvent or {pageX: Int, pageY: Int, _ev: TouchEvent}) -> observable of {x:Int, y:Int}
        //console.log( debugName +": Outer observable started" );

        //eatUp(oStart);

        // Transform from screen to user coordinates
        //
        var transformP = (function () {    // scope

          // If 'el' is already the doc (or 'SVG.Nested', which we don't currently support), we can use that as the cradle
          // for our point.
          //
          var doc = isDoc ? el : /*el.parent(SVG.Nested) ||*/ el.parent(SVG.Doc);

          // tbd. Can we do these within 'svg.js', without using the '.node' (i.e. dropping to plain SVG APIs)?
          //
          var p = doc.node.createSVGPoint();     // point buffer (avoid reallocation per each coordinate change)
          var m = el.node.getScreenCTM().inverse();

          // 'o.pageX|Y' contain coordinates relative to the actual browser window (may be partly scrolled out),
          // for both 'MouseEvent' and 'Touch' objects.
          //
          return function (o /*, offset*/) {   // (MouseEvent or Touch) -> point
            p.x = o.pageX;  // - (offset || 0)
            p.y = o.pageY;

            return p.matrixTransform(m);
          };
        })();

        /** DISABLED text element support not needed, yet
        var anchorOffset;

        // fix text-anchor in text-element (svg.draggable.js #37)
        if (el instanceof SVG.Text) {
          anchorOffset = el.node.getComputedTextLength();

          switch (el.attr('text-anchor')) {
            case 'middle':
              anchorOffset /= 2;
              break;
            case 'start':
              anchorOffset = 0;
              break;
          }
        }
        **/

        //console.log("move clientXY "+ oStart.clientX + " "+ oStart.clientY);
        //console.log("move pageXY "+ oStart.pageX + " "+ oStart.pageY);
        //console.log("move screenXY "+ oStart.screenX + " "+ oStart.screenY);

        var p0 = transformP(oStart /*, anchorOffset*/);

        // With 'S.Doc', 'el.x()' and 'el.y()' are always 0 (well, unless viewport is used, likely..). Don't really
        // understand why the below is the right thing but it is. AKa271215
        //
        // Note: If the SVG element is slightly scrolled off window, the 0's don't work. AKa271215
        //
        var x_offset = isDoc ? 0 : p0.x - el.x(),
            y_offset = isDoc ? 0 : p0.y - el.y();

        //console.log("el "+ el.x() + " "+ el.y());
        //console.log("p0 "+ p0.x + " "+ p0.y);
        //console.log("x_offset "+ x_offset);
        //console.log("y_offset "+ y_offset);

        var endSingleObs = endObs.take(1);    // tbd. is there any benefit of doing this '.take(1)'? We're using '.takeUntil()' below.

        // tbd. How to optimize so that only the last event would ever be shipped, if multiple have gathered, i.e.
        //      we only need the last coordinates. AKa071015
        //
        // Note: some events actually come with the same x,y values (at least on Safari OS X) - removed by the
        //      '.distinctUntilChanged()'.
        //
        var innerObs = moveObs.select( function (o) {
          //console.log( debugName +": "+ o );

          // When moving outside of SVG area, shouldn't paint text. (not sure if this works for that; anyways a rather
          // marginal issue). AKa271215
          //
          //eatUp(o);

          var p = transformP(o);

          //console.log( o );
          //console.log( "move: "+ p.x +" "+ p.y );

          return {
            x: p.x - x_offset,
            y: p.y - y_offset
          };
        } )
          .distinctUntilChanged()
          .takeUntil( endSingleObs );

        return innerObs;
      } );
    };  // function outerObs

  SVG.extend( SVG.Element, {

    //---
    // Create an RxJS observable for touch events of a certain touch id.
    //
    // Returns:
    //  observable of observables of { x: Int, y: Int }
    //
    // 'n' is the touch index (0-based) to follow. Maximum number of touches that can be tracked depends on hardware.
    //
    rx_touch: function (n) {   // (n: Int) -> observable of observables of { x: Int, y: Int }

      var self = this;    // to be used within further inner functions

      /*** disabled (code below did not really work - 'remember' remembers nada. tbd. fix it. AKa271215
      ***/
      // Return an observable for certain touch events for the element. Also remember the observable for later calls
      // (only one is ever created, for a certain element and 'evName').
      //
      var cache = function (evName) {   // (String) -> observable of touchEvent
        var key = "svg.rx.js."+evName;

        var obs = self.remember(obs);
        if (!obs) {
          obs = Rx.Observable.fromEvent(self, evName);
          self.remember(key,obs);
          console.log( "cached: "+evName+" for "+ self );
        } else {
          console.log( "found from cache: "+ evName +" for "+self )
        }
        return obs;
      };
      /***
      var cache = function (evName) {   // temporary
        return Rx.Observable.fromEvent(self,evName);
      }
      ***/

      var startAllObs = cache( "touchstart" );
      var moveAllObs = cache( "touchmove" );
      var cancelAllObs = cache( "touchcancel" );
      var endAllObs = cache( "touchend" );

      // Note: If the one buffer for all touch id messages works fine, we can simplify this a bit (declare the 'buf'
      //      here instead of the caller). AKa271215

      // Derive the observable for just the given touch
      //
      var f = function (buf) {    // ({}) -> (TouchEvent) -> { pageX: Int, pageY: Int, _ev: event } or undefined

        return function (ev) {
          // tbd. Wasn't able to use normal array methods with 'ev.changedTouches'. We'd want '.find()'. AKa161215
          //
          for (var i=0; i<ev.changedTouches.length; i++) {
            var touch = ev.changedTouches[i];
            if (touch.identifier === n) {
              // the fields that 'outerObs' is expecting of us (similar names to a mouse event)
              //
              // Note: Using a buffer object allocated by the caller, to reduce lots of temporary allocations. The stream
              //      should always be bothered of the latest value only. AKa271215
              //
              buf.pageX = touch.pageX;
              buf.pageY = touch.pageY;
              buf._ev = ev;   // so 'outerObs' can prevent event default effects and bubbling up (*)

              return buf;
            }
          }
        }
      };

      // (*) preventing default events may be a context-sensitive thing so we don't want to do it here. E.g. mousemove
      //    events (for mouse, though) need to be default-prevented when dragging is occurring, but not if it's not.
      //    This simply buys us more freedom. AKa271215

      // Prevent browser drag behaviour for touch #0
      //
      // Note: Just preventing all "touchstart" events, if the application tracks id 0. This is simplest and works
      //      the way we want.
      //
      if (n===0) {
        startAllObs.subscribe( function (ev) {     // (TouchEvent)

          // prevent browser drag behavior
          //  - scrolling the whole web page (Android, iOS)
          //  - refresh gestures (Android)
          //
          ev.preventDefault();

          // prevent propagation to a parent that might also have dragging enabled (see demo1).
          //
          ev.stopPropagation();
        });
      }

      // Just one buffer seems to be enough (keep like this until tested on multiple platforms).
      //
      var buf = {};

      // Note: RxJS does not seem to have what Scala calls '.collect': to both filter and convert.
      //
      var startObs = startAllObs.filterAndSelect(f(buf));
      var moveObs = moveAllObs.filterAndSelect(f(buf));
      var cancelObs = cancelAllObs.filterAndSelect(f(buf));
      var endObs = endAllObs.filterAndSelect(f(buf));

      var cancelOrEndObs = Rx.Observable.merge( endObs, cancelObs );

      return outerObs( self, startObs, moveObs, cancelOrEndObs, "touch"+n );
    },

    //---
    // Pointer (button 1) tracking for the element
    //
    // Note: We currently only support button 1.
    //
    rx_mouse: function () {
      var self = this;

      var startObs =  Rx.Observable.fromEvent(self.node, "mousedown");
      var moveObs =   Rx.Observable.fromEvent(window, "mousemove");
      var endObs =    Rx.Observable.fromEvent(window, "mouseup");

      // Prevent browser drag behaviour and bubbling to parents.
      //
      startObs.subscribe( function (ev) {     // (MouseEvent)

        // prevent browser drag behavior
        //  - Makes sure text doesn't get "painted" when moving the cursor outside of the SVG cradle, on top of HTML text.
        //
        ev.preventDefault();

        // prevent propagation to a parent that might also have dragging enabled (see demo1).
        //
        ev.stopPropagation();
      });

      return outerObs( self, startObs, moveObs, endObs, "mouse" );
    },

    //---
    // Create an rxJS observable for either mouse or touch (index 0) drags
    //
    // Returns:
    //  observable of observables of { x: Int, y: Int }
    //
    // For each drag, a new inner observable is created. That streams unique {x,y} coordinates, and terminates when
    // the user ends the drag.
    //
    rx_draggable: function () {   // () -> observable of observables of { x: Int, y: Int }

      // Merge the two approaches. Only one inner drag active at any one time (desktop or touch)
      //
      // Note: the caller should dispose of this (and we need to check if we need to dispose some).
      //
      return Rx.Observable.merge(
        this.rx_mouse(),
        this.rx_touch(0)
      );
    }
  });

})();
<commit_msg>passes jshint<commit_after>/*
* svg.rx.js
*/
/*jshint devel:true */

(function () {
  "use strict";

  function assert(b,msg) {    // (boolish, String) =>
    if (!b) {
      throw ("Assert failed" + (msg ? ": "+msg : ""))
    }
  }
  assert(true);   // just use it up (jshint)

  // Note: RxJS does not seem to have what Scala calls '.collect': to both filter and convert.
  //
  // Ref. https://xgrommx.github.io/rx-book/content/guidelines/implementations/index.html#implement-new-operators-by-composing-existing-operators
  //
  // tbd. Is it true RxJS does not have a built-in operator for this? Ask at StackOverflow (pointing to this line). AKa271215
  //
  Rx.Observable.prototype.filterAndSelect = function (f) {
    return this.select(f).filter( function (x) { return x !== undefined; } );
  }

  // Inner stream of drags. Handles coordinate transforms etc.
  //
  // References:
  //    MouseEvent -> https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
  //    Touch -> https://developer.mozilla.org/en-US/docs/Web/API/Touch
  //
  // Note: The event handling code is originally based on 'svg.draggable.js' -> https://github.com/wout/svg.draggable.js
  //      but we only enable stuff that we actually test (manually). I.e. 'SVG.Nested', 'SVG.Use', 'SVG.Text' support
  //      remains disabled until we need it, and there are demos that exercise those things.
  //
  var innerObs = function (el, oStart, moveObs, endObs) {    // (SVG.Element|SVG.G|SVG.Doc, MouseEvent or TouchEvent, observable of MouseEvent or Touch, observable of MouseEvent or Touch) -> observable of {x:Int, y:Int}

    var isDoc = (el instanceof SVG.Doc);

    if (! ((el instanceof SVG.Element) || (el instanceof SVG.G) || isDoc)) {
      throw "svg.rx.js does not support: "+ (typeof el);
    }

    // Transform from screen to user coordinates
    //
    var transformP = (function () {    // scope

      // If 'el' is already the doc (or 'SVG.Nested', which we don't currently support), we can use that as the cradle
      // for our point.
      //
      var doc = isDoc ? el : /*el.parent(SVG.Nested) ||*/ el.parent(SVG.Doc);

      // tbd. Can we do these within 'svg.js', without using the '.node' (i.e. dropping to plain SVG APIs)?
      //
      var p = doc.node.createSVGPoint();     // point buffer (avoid reallocation per each coordinate change)
      var m = el.node.getScreenCTM().inverse();

      // 'o.pageX|Y' contain coordinates relative to the actual browser window (may be partly scrolled out),
      // for both 'MouseEvent' and 'Touch' objects.
      //
      return function (o /*, offset*/) {   // (MouseEvent or Touch) -> point
        p.x = o.pageX;  // - (offset || 0)
        p.y = o.pageY;

        return p.matrixTransform(m);
      };
    })();

    /** DISABLED text element support not needed, yet
    var anchorOffset;

    // fix text-anchor in text-element (svg.draggable.js #37)
    if (el instanceof SVG.Text) {
      anchorOffset = el.node.getComputedTextLength();

      switch (el.attr('text-anchor')) {
        case 'middle':
          anchorOffset /= 2;
          break;
        case 'start':
          anchorOffset = 0;
          break;
      }
    }
    **/

    var p0 = transformP(oStart /*, anchorOffset*/);

    // With 'S.Doc', 'el.x()' and 'el.y()' are always 0 (well, unless viewport is used, likely..). Don't really
    // understand why the below is the right thing but it is. AKa271215
    //
    // Note: If the SVG element is slightly scrolled off window, the 0's don't work. AKa271215
    //
    var x_offset = isDoc ? 0 : p0.x - el.x(),
        y_offset = isDoc ? 0 : p0.y - el.y();

    // tbd. How to optimize so that only the last event would ever be shipped, if multiple have gathered, i.e.
    //      we only need the last coordinates. AKa071015
    //
    // Note: some events actually come with the same x,y values (at least on Safari OS X) - removed by the
    //      '.distinctUntilChanged()'.
    //
    return moveObs.select( function (o) {
      //console.log( debugName +": "+ o );

      // When moving outside of SVG area, shouldn't paint text. (not sure if this works for that; anyways a rather
      // marginal issue). AKa271215
      //
      //eatUp(o);

      var p = transformP(o);

      //console.log( o );
      //console.log( "move: "+ p.x +" "+ p.y );

      return {
        x: p.x - x_offset,
        y: p.y - y_offset
      };
    } )
      .distinctUntilChanged()
      .takeUntil( endObs );
  };  // innerObs

  SVG.extend( SVG.Element, {

    //---
    // Create an observable for touch events.
    //
    // Returns:
    //  observable of observables of { x: Int, y: Int }
    //
    // Note: Touch id's are not reported, by design. The idea is to treat any touches alike, not mapping them to
    //      "fingers". Also, depending on the platform touch id's may or may not be in the 0...N-1 range (Android
    //      reuses id's whereas iOS does not). The model chosen seems to be a good fit for allowing e.g. multiple
    //      users to work on a touch interface simultaneously, i.e. it feels more generic and expandable. AKa060116
    //
    rx_touch: function () {   // () -> observable of observables of { x: Int, y: Int }

      var self = this;    // to be used within further inner functions

      /*** Code below did not really work - 'remember' remembers nada. tbd. fix it. AKa271215
      ***/
      // Return an observable for certain touch events for the element. Also remember the observable for later calls
      // (only one is ever created, for a certain element and 'evName').
      //
      var cache = function (evName) {   // (String) -> observable of touchEvent
        var key = "svg.rx.js."+evName;

        var obs = self.remember(obs);
        if (!obs) {
          obs = Rx.Observable.fromEvent(self, evName);
          self.remember(key,obs);
          console.log( "cached: "+evName+" for "+ self );
        } else {
          console.log( "found from cache: "+ evName +" for "+self )
        }
        return obs;
      };
      /***
      var cache = function (evName) {   // temporary
        return Rx.Observable.fromEvent(self,evName);
      }
      ***/

      var startAllObs = cache( "touchstart" );
      var moveAllObs = cache( "touchmove" );
      var cancelAllObs = cache( "touchcancel" );
      var endAllObs = cache( "touchend" );

      var touchDragObs = function (ev, wanted) {      // (TouchEvent, /*touchId*/ Int) -> observable of {x:Int, y:Int}

        /* Pick the 'wanted' move, cancel and end events to track
        */
        var f = function (ev) {   // (TouchEvent) -> Touch or undefined
          for (var i=0; i<ev.changedTouches.length; i++) {
            var touch = ev.changedTouches[i];

            if (touch.identifier === wanted) {
              return touch;
            }
          }
          return undefined;   // will not pass this event further for the particular stream
        }

        // Note: RxJS does not seem to have what Scala calls '.collect': to both filter and convert.
        //
        var moveObs = moveAllObs.filterAndSelect(f);
        var cancelObs = cancelAllObs.filterAndSelect(f);
        var endObs = endAllObs.filterAndSelect(f);

        var cancelOrEndObs = Rx.Observable.merge( endObs, cancelObs );

        return innerObs( self, ev, moveObs, cancelOrEndObs );

      }; // touchDragObs

      // tbd.
      //
      // 'startAllObs' gets events for every new touch start.
      // Each of such events creates 1..n-1 observables of {x,y}
      // How do we map this into a single observable of observables of {x,y}?
      //

      // tbd. How to transfer 1..n-1 observables of {x,y}
      //
      // Note: It is possible (though unlikely) that the same TouchEvent would start multiple touch chains. We'll keep
      //        that possibility available in the code, though (it's the right thing to do). AK060116
      //
      var outerObs = startAllObs.selectMany( function (ev) {  // (TouchEvent) -> Array(observable of {x:Int, y:Int}, ...)

        // tbd. Could use '.map' for this instead of i looping. But 'ev.changedTouches' is not a true Array. AKa060116
        //
        var arr = [];

        for (var i=0; i<ev.changedTouches.length; i++) {
          var touch = ev.changedTouches[i];

          // Note: 'touch.identifier' can be a 0..N-1 number (Android) or a freely running counter (iOS); we treat it as
          //      an opaque identifier to find the matching further move/cancel/end events.
          //
          var wanted = touch.identifier;

          arr.push( touchDragObs( ev, wanted ) );   // dragging observable for that particular touch
        }

        // tbd. How should we now (or in the loop above) push all the dragObs's to this observable?
        //
        return arr;
      } );

      return outerObs;
    },  // rx_touch

    //---
    // Pointer (button 1) tracking for the element
    //
    // Note: We currently only support button 1.
    //
    rx_mouse: function () {
      var self = this;

      var startObs =  Rx.Observable.fromEvent(self.node, "mousedown");
      var moveObs =   Rx.Observable.fromEvent(window, "mousemove");
      var endObs =    Rx.Observable.fromEvent(window, "mouseup");

      startObs.select( function (ev) {   // (MouseEvent) -> observable of {x:Int, y:Int}
        return innerObs( self, ev, moveObs, endObs );
      } );
    },

    //---
    // Create an observable for either mouse or touch drags
    //
    // Returns:
    //  observable of observables of { x: Int, y: Int }
    //
    rx_draggable: function () {   // () -> observable of observables of { x: Int, y: Int }

      return Rx.Observable.merge(
        this.rx_mouse(),
        this.rx_touch()
      );
    }
  });

})();
<|endoftext|>


<commit_before>/* global $ CKEDITOR */

$(function () {
  let submitted = false
  const changeHandler = function () {
    $(window).on('beforeunload', function () {
      if (!submitted) {
        return false
      }
    })
  }
  for (let key in CKEDITOR.instances) {
    if (CKEDITOR.instances.hasOwnProperty(key)) {
      CKEDITOR.instances[key].on('change', changeHandler)
    }
  }

  $(document).one('change', changeHandler)
    .on('submit', function () {
      submitted = true
    })
})
<commit_msg>add unload warning for ckeditor<commit_after>/* global $ CKEDITOR */

$(function () {
  let submitted = false
  const changeHandler = function () {
    $(window).on('beforeunload', function () {
      if (!submitted) {
        return false
      }
    })
  }

  if (CKEDITOR) {
    CKEDITOR.on('instanceReady', function (e) {
      e.editor.on('change', changeHandler)
    })
  }

  $(document).one('change', changeHandler)
    .on('submit', function () {
      submitted = true
    })
})
<|endoftext|>


<commit_before>'use strict'
var Connection = require('../connection')
var serverList = require('./serverlist')
var Internal = require('./internal')

module.exports = new Connection({
  upstream: {
    on: {
      data: {
        mock (data, event) {
          if(data === null) {
            console.warn('removing connection dont worry about it for now! -- add more here later')
            return
          }

          if (this.parent.internal) {
            this.parent.internal.emit('close')
            this.parent.internal._serverside.emit('close')
            this.parent.internal.remove(event)
          }

          var server = serverList[this.val]
          if (server) {
            var serverSide
            var internal = this.parent.internal = new Internal({
              on: {
                send (data) {
                  serverSide.emit('message', data)
                }
              }
            })
            // bad for mem fix this better later!
            internal.on('message', (data) => {
              this.parent.emit('message', data)
            })
            internal.on('connect', () => {
              this.parent.emit('connect')
            })
            serverSide = internal._serverside = new Internal({
              on: {
                send (data) {
                  internal.emit('message', data)
                },
                remove: () => {
                  this.parent.emit('close')
                }
              }
            })
            setTimeout(() => {
              server.emit('request', serverSide)
              internal.emit('connect')
            }, 10)
          } else {
            console.warn('!cannot find server mock reconnect!')
          }
        }
      }
    }
  },
  define: {
    send (data) {
      this.internal.emit('send', data)
    }
  }
}).Constructor
// connection does not know shit about clients etc -- just a connection!
<commit_msg>reduce mock reconnect timeout to 10ms (faster exec of tests)<commit_after>'use strict'
var Connection = require('../connection')
var serverList = require('./serverlist')
var Internal = require('./internal')

module.exports = new Connection({
  reconnectTime: 10,
  upstream: {
    on: {
      data: {
        mock (data, event) {
          if(data === null) {
            console.warn('removing connection dont worry about it for now! -- add more here later')
            return
          }

          if (this.parent.internal) {
            this.parent.internal.emit('close')
            this.parent.internal._serverside.emit('close')
            this.parent.internal.remove(event)
          }

          var server = serverList[this.val]
          if (server) {
            var serverSide
            var internal = this.parent.internal = new Internal({
              on: {
                send (data) {
                  serverSide.emit('message', data)
                }
              }
            })
            // bad for mem fix this better later!
            internal.on('message', (data) => {
              this.parent.emit('message', data)
            })
            internal.on('connect', () => {
              this.parent.emit('connect')
            })
            serverSide = internal._serverside = new Internal({
              on: {
                send (data) {
                  internal.emit('message', data)
                },
                remove: () => {
                  this.parent.emit('close')
                }
              }
            })
            setTimeout(() => {
              server.emit('request', serverSide)
              internal.emit('connect')
            }, 10)
          } else {
            console.warn('!cannot find server mock reconnect!')
          }
        }
      }
    }
  },
  define: {
    send (data) {
      this.internal.emit('send', data)
    }
  }
}).Constructor
// connection does not know shit about clients etc -- just a connection!
<|endoftext|>


<commit_before>(function(jQuery) {
"use strict";

var $ = jQuery;

var canvas = Echo.Control.manifest("Echo.Canvas");

if (Echo.Control.isDefined(canvas)) return;

/**
 * @class Echo.Canvas
 * Class which implements Canvas mechanics on the client side.
 * The instance of this class is created for each Canvas found on the page by
 * the Echo.Loader. The instance of the class can also be created manually in
 * case the Canvas data already exists on the page.
 *
 * @package environment.pack.js
 *
 * @extends Echo.Control
 *
 * @constructor
 * Canvas object constructor to initialize the Echo.Canvas instance
 *
 * @param {Object} config
 * Configuration options
 */

/** @hide @method getRelativeTime */
/** @hide @echo_label today */
/** @hide @echo_label yesterday */
/** @hide @echo_label lastWeek */
/** @hide @echo_label lastMonth */
/** @hide @echo_label secondAgo */
/** @hide @echo_label secondsAgo */
/** @hide @echo_label minuteAgo */
/** @hide @echo_label minutesAgo */
/** @hide @echo_label hourAgo */
/** @hide @echo_label hoursAgo */
/** @hide @echo_label dayAgo */
/** @hide @echo_label daysAgo */
/** @hide @echo_label weekAgo */
/** @hide @echo_label weeksAgo */
/** @hide @echo_label monthAgo */
/** @hide @echo_label monthsAgo */
/** @hide @echo_label loading */
/** @hide @echo_label retrying */
/** @hide @echo_label error_busy */
/** @hide @echo_label error_timeout */
/** @hide @echo_label error_waiting */
/** @hide @echo_label error_view_limit */
/** @hide @echo_label error_view_update_capacity_exceeded */
/** @hide @echo_label error_result_too_large */
/** @hide @echo_label error_wrong_query */
/** @hide @echo_label error_incorrect_appkey */
/** @hide @echo_label error_internal_error */
/** @hide @echo_label error_quota_exceeded */
/** @hide @echo_label error_incorrect_user_id */
/** @hide @echo_label error_unknown */

canvas.init = function() {
	var self = this, target = this.config.get("target");
	// parent init function takes care about init finalization (rendering
	// and the "onReady" event firing)
	var parent = $.proxy(this.parent, this);

	// check if the canvas was already initialized
	if (target.data("echo-canvas-initialized")) {
		this._error({
			"args": {"target": target},
			"code": "canvas_already_initialized"
		});
		return;
	}

	// define initialized state for the canvas
	// to prevent multiple initialization of the same canvas
	target.data("echo-canvas-initialized", true);
	this._loadAppResources(parent);
};

canvas.config = {
	/**
	 * @cfg {String} [id]
	 * Unique ID of the Canvas, used by the Echo.Canvas instance
	 * to retrieve the data from the Canvases data storage.
	 */

	/**
	 * @cfg {Object} [data]
	 * Object which contains the Canvas data in the format
	 * used to store the Canvas config in the Canvas storage.
	 */
	"data": {},

	/**
	 * @cfg {String} target(required)
	 * Specifies the DOM element where the control will be displayed.
	 *
	 * Note: if only the "target" config parameter is defined, the target DOM element
	 * should contain the following HTML attributes:
	 *
	 * + "data-appkey" with the necessary appkey value
	 * + "data-canvas-id" with the unique Canvas ID which should be initialized
	 *
	 * The values of the HTML parameters override the "appkey" and "id" parameter values
	 * (respectively) passed via the Canvas config.
	 */

	/**
	 * @cfg {String} appkey
	 * @inheritdoc
	 */

	/**
	 * @cfg {Object} [overrides]
	 * Object which contains the overrides applied for this Canvas on the page
	 * via Echo.Loader.override function call.
	 */
	"overrides": {},

	/**
	 * @ignore
	 */
	"storageURL": Echo.Loader.config.storageURL  // no docs, not supposed
						     // to be changed by the publishers
};

canvas.vars = {
	"apps": []
};

canvas.labels = {
	/**
	 * @echo_label
	 */
	"error_no_apps": "No applications defined for this canvas",
	/**
	 * @echo_label
	 */
	"error_no_config": "Unable to retrieve Canvas config",
	/**
	 * @echo_label
	 */
	"error_no_suitable_app_class": "Unable to init an app, no suitable JS class found",
	/**
	 * @echo_label
	 */
	"error_unable_to_retrieve_app_config": "Unable to retrieve Canvas config from the storage",
	/**
	 * @echo_label
	 */
	"error_incomplete_app_config": "Unable to init an app, config is incomplete",
	/**
	 * @echo_label
	 */
	"error_canvas_already_initialized": "Canvas has been initialized already",
	/**
	 * @echo_label
	 */
	"error_invalid_canvas_config": "Canvas with invalid configuration found"
};

canvas.templates.main =
	'

';

canvas.templates.app =
	'
' +
		'
{data:caption}
' +
		'

' +
	'
';

canvas.destroy = function() {
	$.map(this.get("apps"), $.proxy(this._destroyApp, this));
	this.config.get("target").data("echo-canvas-initialized", false);
};

/**
 * @echo_renderer
 */
canvas.renderers.container = function(element) {
	var self = this;
	$.map(this.get("data.apps"), function(app, id) {
		self._initApp(app, element, id);
	});
	return element;
};

canvas.methods._initApp = function(app, element, id) {
	var Application = Echo.Utils.getComponent(app.component);
	if (!Application) {
		this._error({
			"args": {"app": app},
			"code": "no_suitable_app_class"
		});
		return;
	}

	var view = this.view.fork({
		"renderer": null,
		"renderers": {
			"appHeader": function(element) {
				// show|hide app header depending on the caption existance
				return element[app.caption ? "show" : "hide"]();
			}
		}
	});
	element.append(view.render({
		"data": app,
		"template": this.templates.app
	}));

	app.id = app.id || id;  // define app position in array as id if not specified
	app.config = app.config || {};
	app.config.user = this.config.get("user");
	app.config.target = view.get("appBody");

	var overrides = this.config.get("overrides")[app.id];
	var config = overrides
		? $.extend(true, app.config, overrides)
		: app.config;
	this.apps.push(new Application(config));
};

canvas.methods._destroyApp = function(app) {
	if (app) app.destroy();
};

canvas.methods._isManuallyConfigured = function() {
	return !$.isEmptyObject(this.get("data"));
};

canvas.methods._getAppScriptURL = function(config) {
	return config.scripts && config.scripts.dev && config.scripts.prod
		? config.scripts[Echo.Loader.isDebug() ? "dev" : "prod"]
		: config.script;
};

canvas.methods._loadAppResources = function(callback) {
	var self = this, resources = [], isManual = this._isManuallyConfigured();
	$.map(this.get("data.apps"), function(app) {
		var script = self._getAppScriptURL(app);
		if (!app.component || !script || !(isManual || app.id)) {
			self._error({
				"args": {"app": app},
				"code": "incomplete_app_config"
			});
			return;
		}
		resources.push({
			"url": script,
			"loaded": function() {
				return Echo.Control.isDefined(app.component);
			}
		});
	});
	Echo.Loader.download(resources, callback);
};

canvas.methods._getOverrides = function(target, spec) {
	return Echo.Utils.foldl({}, spec || [], function(item, acc) {
		// We should convert spec item to lower case because of jQuery
		// HTML5 data attributes implementation http://api.jquery.com/data/#data-html5
		// Since we have config keys in camel case representation like "useSecureAPI",
		// we should follow to these rules.
		var key = "canvas-" + item.toLowerCase();
		var value = target.data(key);
		if (typeof value !== "undefined") {
			acc[item] = value;
		}
	});
};

canvas.methods._error = function(args) {
	args.message = args.message || this.labels.get("error_" + args.code);

	/**
	 * @echo_event Echo.Canvas.onError
	 * Event which is triggered in case of errors such as invalid configuration,
	 * problems fetching the data from the server side, etc.
	 *
	 * @param {String} topic
	 * Name of the event produced.
	 *
	 * @param {Object} data
	 * Object which contains debug information regarding the error.
	 */
	Echo.Events.publish({
		"topic": "Echo.Canvas.onError",
		"data": args
	});

	Echo.Utils.log($.extend(args, {"type": "error", "component": "Echo.Canvas"}));
	if (args.renderError) {
		this.showMessage({
			"type": "error",
			"message": args.message
		});
	}
};

Echo.Control.create(canvas);

// Echo.Canvas class initialization logic requires additional initializers.
// Before user initialization we should fetch canvas config from the server
// and init a backplane mechanism. According to it we should extend default
// control initializers list with own ones.
var initializers = $.extend(true, {}, Echo.Canvas.prototype._initializers);

var list = initializers.list;

$.each(list, function(i, spec) {
	if (spec[0] === "user:async") {
		list.splice(i, 0, ["fetchConfig:async", ["init", "refresh"]]);
		list.splice(i + 1, 0, ["initBackplane:async", ["init", "refresh"]]);
		return false;
	}
});

initializers.fetchConfig = function(callback) {
	var self = this, target = this.config.get("target");
	var isManual = this._isManuallyConfigured();

	// extending Canvas config with the "id" and "appkey" defined in the target
	var overrides = this._getOverrides(target, ["id", "appkey", "useSecureAPI"]);
	if (!$.isEmptyObject(overrides)) {
		this.config.extend(overrides);
	}

	// exit if no "id" or "appkey" is defined for the canvas,
	// skip this validation in case the "data" is defined explicitly in the config
	if (!isManual &&
		!(this.config.get("id") && this.config.get("appkey"))) {
			this._error({
				"args": {"target": target},
				"code": "invalid_canvas_config"
			});
			return;
	}

	// no need to perform server side request in case
	// we already have all the data on the client side
	if (isManual) {
		callback.call(this);
		return;
	}
	(new Echo.API.Request({
		"apiBaseURL": this.config.get("storageURL"),
		"secure": this.config.get("useSecureAPI"),
		"endpoint": this.config.get("id"),
		"data": Echo.Loader.isDebug() ? { "_": Math.random() } : {},
		"onData": function(config) {
			if (!config || !config.apps || !config.apps.length) {
				var message = self.labels.get("error_no_" + (config ? "apps" : "config"));
				self._error({
					"args": {"config": config, "target": target},
					"code": "invalid_canvas_config",
					"renderError": true,
					"message": message
				});
				return;
			}
			self.set("data", config); // store Canvas data into the instance
			callback.call(self);
		},
		"onError": function(response) {
			self._error({
				"args": response,
				"code": "unable_to_retrieve_app_config",
				"renderError": true
			});
		}
	})).request();
};

initializers.initBackplane = function(callback) {
	// Note: Backplane.init in v2 will be async,
	// so we need a callback to execute after Backplane init
	Backplane.init(this.get("data.backplane"));
	callback.call(this);
};

Echo.Canvas.prototype._initializers = initializers;

})(Echo.jQuery);
<commit_msg>Random part always is added<commit_after>(function(jQuery) {
"use strict";

var $ = jQuery;

var canvas = Echo.Control.manifest("Echo.Canvas");

if (Echo.Control.isDefined(canvas)) return;

/**
 * @class Echo.Canvas
 * Class which implements Canvas mechanics on the client side.
 * The instance of this class is created for each Canvas found on the page by
 * the Echo.Loader. The instance of the class can also be created manually in
 * case the Canvas data already exists on the page.
 *
 * @package environment.pack.js
 *
 * @extends Echo.Control
 *
 * @constructor
 * Canvas object constructor to initialize the Echo.Canvas instance
 *
 * @param {Object} config
 * Configuration options
 */

/** @hide @method getRelativeTime */
/** @hide @echo_label today */
/** @hide @echo_label yesterday */
/** @hide @echo_label lastWeek */
/** @hide @echo_label lastMonth */
/** @hide @echo_label secondAgo */
/** @hide @echo_label secondsAgo */
/** @hide @echo_label minuteAgo */
/** @hide @echo_label minutesAgo */
/** @hide @echo_label hourAgo */
/** @hide @echo_label hoursAgo */
/** @hide @echo_label dayAgo */
/** @hide @echo_label daysAgo */
/** @hide @echo_label weekAgo */
/** @hide @echo_label weeksAgo */
/** @hide @echo_label monthAgo */
/** @hide @echo_label monthsAgo */
/** @hide @echo_label loading */
/** @hide @echo_label retrying */
/** @hide @echo_label error_busy */
/** @hide @echo_label error_timeout */
/** @hide @echo_label error_waiting */
/** @hide @echo_label error_view_limit */
/** @hide @echo_label error_view_update_capacity_exceeded */
/** @hide @echo_label error_result_too_large */
/** @hide @echo_label error_wrong_query */
/** @hide @echo_label error_incorrect_appkey */
/** @hide @echo_label error_internal_error */
/** @hide @echo_label error_quota_exceeded */
/** @hide @echo_label error_incorrect_user_id */
/** @hide @echo_label error_unknown */

canvas.init = function() {
	var self = this, target = this.config.get("target");
	// parent init function takes care about init finalization (rendering
	// and the "onReady" event firing)
	var parent = $.proxy(this.parent, this);

	// check if the canvas was already initialized
	if (target.data("echo-canvas-initialized")) {
		this._error({
			"args": {"target": target},
			"code": "canvas_already_initialized"
		});
		return;
	}

	// define initialized state for the canvas
	// to prevent multiple initialization of the same canvas
	target.data("echo-canvas-initialized", true);
	this._loadAppResources(parent);
};

canvas.config = {
	/**
	 * @cfg {String} [id]
	 * Unique ID of the Canvas, used by the Echo.Canvas instance
	 * to retrieve the data from the Canvases data storage.
	 */

	/**
	 * @cfg {Object} [data]
	 * Object which contains the Canvas data in the format
	 * used to store the Canvas config in the Canvas storage.
	 */
	"data": {},

	/**
	 * @cfg {String} target(required)
	 * Specifies the DOM element where the control will be displayed.
	 *
	 * Note: if only the "target" config parameter is defined, the target DOM element
	 * should contain the following HTML attributes:
	 *
	 * + "data-appkey" with the necessary appkey value
	 * + "data-canvas-id" with the unique Canvas ID which should be initialized
	 *
	 * The values of the HTML parameters override the "appkey" and "id" parameter values
	 * (respectively) passed via the Canvas config.
	 */

	/**
	 * @cfg {String} appkey
	 * @inheritdoc
	 */

	/**
	 * @cfg {Object} [overrides]
	 * Object which contains the overrides applied for this Canvas on the page
	 * via Echo.Loader.override function call.
	 */
	"overrides": {},

	/**
	 * @ignore
	 */
	"storageURL": Echo.Loader.config.storageURL  // no docs, not supposed
						     // to be changed by the publishers
};

canvas.vars = {
	"apps": []
};

canvas.labels = {
	/**
	 * @echo_label
	 */
	"error_no_apps": "No applications defined for this canvas",
	/**
	 * @echo_label
	 */
	"error_no_config": "Unable to retrieve Canvas config",
	/**
	 * @echo_label
	 */
	"error_no_suitable_app_class": "Unable to init an app, no suitable JS class found",
	/**
	 * @echo_label
	 */
	"error_unable_to_retrieve_app_config": "Unable to retrieve Canvas config from the storage",
	/**
	 * @echo_label
	 */
	"error_incomplete_app_config": "Unable to init an app, config is incomplete",
	/**
	 * @echo_label
	 */
	"error_canvas_already_initialized": "Canvas has been initialized already",
	/**
	 * @echo_label
	 */
	"error_invalid_canvas_config": "Canvas with invalid configuration found"
};

canvas.templates.main =
	'

';

canvas.templates.app =
	'
' +
		'
{data:caption}
' +
		'

' +
	'
';

canvas.destroy = function() {
	$.map(this.get("apps"), $.proxy(this._destroyApp, this));
	this.config.get("target").data("echo-canvas-initialized", false);
};

/**
 * @echo_renderer
 */
canvas.renderers.container = function(element) {
	var self = this;
	$.map(this.get("data.apps"), function(app, id) {
		self._initApp(app, element, id);
	});
	return element;
};

canvas.methods._initApp = function(app, element, id) {
	var Application = Echo.Utils.getComponent(app.component);
	if (!Application) {
		this._error({
			"args": {"app": app},
			"code": "no_suitable_app_class"
		});
		return;
	}

	var view = this.view.fork({
		"renderer": null,
		"renderers": {
			"appHeader": function(element) {
				// show|hide app header depending on the caption existance
				return element[app.caption ? "show" : "hide"]();
			}
		}
	});
	element.append(view.render({
		"data": app,
		"template": this.templates.app
	}));

	app.id = app.id || id;  // define app position in array as id if not specified
	app.config = app.config || {};
	app.config.user = this.config.get("user");
	app.config.target = view.get("appBody");

	var overrides = this.config.get("overrides")[app.id];
	var config = overrides
		? $.extend(true, app.config, overrides)
		: app.config;
	this.apps.push(new Application(config));
};

canvas.methods._destroyApp = function(app) {
	if (app) app.destroy();
};

canvas.methods._isManuallyConfigured = function() {
	return !$.isEmptyObject(this.get("data"));
};

canvas.methods._getAppScriptURL = function(config) {
	return config.scripts && config.scripts.dev && config.scripts.prod
		? config.scripts[Echo.Loader.isDebug() ? "dev" : "prod"]
		: config.script;
};

canvas.methods._loadAppResources = function(callback) {
	var self = this, resources = [], isManual = this._isManuallyConfigured();
	$.map(this.get("data.apps"), function(app) {
		var script = self._getAppScriptURL(app);
		if (!app.component || !script || !(isManual || app.id)) {
			self._error({
				"args": {"app": app},
				"code": "incomplete_app_config"
			});
			return;
		}
		resources.push({
			"url": script,
			"loaded": function() {
				return Echo.Control.isDefined(app.component);
			}
		});
	});
	Echo.Loader.download(resources, callback);
};

canvas.methods._getOverrides = function(target, spec) {
	return Echo.Utils.foldl({}, spec || [], function(item, acc) {
		// We should convert spec item to lower case because of jQuery
		// HTML5 data attributes implementation http://api.jquery.com/data/#data-html5
		// Since we have config keys in camel case representation like "useSecureAPI",
		// we should follow to these rules.
		var key = "canvas-" + item.toLowerCase();
		var value = target.data(key);
		if (typeof value !== "undefined") {
			acc[item] = value;
		}
	});
};

canvas.methods._error = function(args) {
	args.message = args.message || this.labels.get("error_" + args.code);

	/**
	 * @echo_event Echo.Canvas.onError
	 * Event which is triggered in case of errors such as invalid configuration,
	 * problems fetching the data from the server side, etc.
	 *
	 * @param {String} topic
	 * Name of the event produced.
	 *
	 * @param {Object} data
	 * Object which contains debug information regarding the error.
	 */
	Echo.Events.publish({
		"topic": "Echo.Canvas.onError",
		"data": args
	});

	Echo.Utils.log($.extend(args, {"type": "error", "component": "Echo.Canvas"}));
	if (args.renderError) {
		this.showMessage({
			"type": "error",
			"message": args.message
		});
	}
};

Echo.Control.create(canvas);

// Echo.Canvas class initialization logic requires additional initializers.
// Before user initialization we should fetch canvas config from the server
// and init a backplane mechanism. According to it we should extend default
// control initializers list with own ones.
var initializers = $.extend(true, {}, Echo.Canvas.prototype._initializers);

var list = initializers.list;

$.each(list, function(i, spec) {
	if (spec[0] === "user:async") {
		list.splice(i, 0, ["fetchConfig:async", ["init", "refresh"]]);
		list.splice(i + 1, 0, ["initBackplane:async", ["init", "refresh"]]);
		return false;
	}
});

initializers.fetchConfig = function(callback) {
	var self = this, target = this.config.get("target");
	var isManual = this._isManuallyConfigured();

	// extending Canvas config with the "id" and "appkey" defined in the target
	var overrides = this._getOverrides(target, ["id", "appkey", "useSecureAPI"]);
	if (!$.isEmptyObject(overrides)) {
		this.config.extend(overrides);
	}

	// exit if no "id" or "appkey" is defined for the canvas,
	// skip this validation in case the "data" is defined explicitly in the config
	if (!isManual &&
		!(this.config.get("id") && this.config.get("appkey"))) {
			this._error({
				"args": {"target": target},
				"code": "invalid_canvas_config"
			});
			return;
	}

	// no need to perform server side request in case
	// we already have all the data on the client side
	if (isManual) {
		callback.call(this);
		return;
	}
	(new Echo.API.Request({
		"apiBaseURL": this.config.get("storageURL"),
		"secure": this.config.get("useSecureAPI"),
		"endpoint": this.config.get("id"),
		"data": { "_": Math.random() },
		"onData": function(config) {
			if (!config || !config.apps || !config.apps.length) {
				var message = self.labels.get("error_no_" + (config ? "apps" : "config"));
				self._error({
					"args": {"config": config, "target": target},
					"code": "invalid_canvas_config",
					"renderError": true,
					"message": message
				});
				return;
			}
			self.set("data", config); // store Canvas data into the instance
			callback.call(self);
		},
		"onError": function(response) {
			self._error({
				"args": response,
				"code": "unable_to_retrieve_app_config",
				"renderError": true
			});
		}
	})).request();
};

initializers.initBackplane = function(callback) {
	// Note: Backplane.init in v2 will be async,
	// so we need a callback to execute after Backplane init
	Backplane.init(this.get("data.backplane"));
	callback.call(this);
};

Echo.Canvas.prototype._initializers = initializers;

})(Echo.jQuery);
<|endoftext|>


<commit_before>import React, { PropTypes, Component } from 'react';
import {
  View,
  Image,
  NativeModules,
  StyleSheet,
  Dimensions,
  PanResponder,
  Animated
} from 'react-native';
import { calculateCornerResult, msToSec } from '../utils';

const { RNTrimmerManager: TrimmerManager } = NativeModules;
const { width } = Dimensions.get('window');

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'center'
  },
  row: {
    flexDirection: 'row'
  },
  imageItem: {
    flex: 1,
    width: 50,
    height: 50,
    resizeMode: 'cover'
  },
  corners: {
    position: 'absolute',
    height: 50,
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  rightCorner: {
    position: 'absolute',
    flex: 1,
  },
  leftCorner: {
    left: 0
  },
  bgBlack: {
    backgroundColor: 'rgba(0, 0, 0, 0.7)',
    width
  },
  cornerItem: {
    backgroundColor: 'gray',
    width: 20,
    height: 50
  }
});
export class Trimmer extends Component {
  static propTypes = {
    source: PropTypes.string.isRequired,
    onChange: PropTypes.func
  };
  static defaultProps = {
    onChange: () => null
  };

  constructor(props) {
    super(props);
    this.state = {
      images: [],
      duration: -1,
      leftCorner: new Animated.Value(0),
      rightCorner: new Animated.Value(0),
      layoutWidth: width
    };

    this.leftResponder = null;
    this.rigthResponder = null;

    this._startTime = 0;
    this._endTime = 0;
    this._handleRightCornerMove = this._handleRightCornerMove.bind(this);
    this._handleLeftCornerMove = this._handleLeftCornerMove.bind(this);
    this._retriveInfo = this._retriveInfo.bind(this);
    this._retrivePreviewImages = this._retrivePreviewImages.bind(this);
    this._handleRightCornerRelease = this._handleRightCornerRelease.bind(this);
    this._handleLeftCornerRelease = this._handleLeftCornerRelease.bind(this);
  }

  componentWillMount() {
    // @TODO: Cleanup on unmount
    this.state.leftCorner.addListener(({ value }) => this._leftCornerPos = value);
    this.state.rightCorner.addListener(({ value }) => this._rightCornerPos = value);

    this.leftResponder = PanResponder.create({
      onMoveShouldSetPanResponder: (e, gestureState) => Math.abs(gestureState.dx) > 0,
      onMoveShouldSetPanResponderCapture: (e, gestureState) => Math.abs(gestureState.dx) > 0,
      onPanResponderMove: this._handleLeftCornerMove,
      onPanResponderRelease: this._handleLeftCornerRelease
    });

    this.rightResponder = PanResponder.create({
      onMoveShouldSetPanResponder: (e, gestureState) => Math.abs(gestureState.dx) > 0,
      onMoveShouldSetPanResponderCapture: (e, gestureState) => Math.abs(gestureState.dx) > 0,
      onPanResponderMove: this._handleRightCornerMove,
      onPanResponderRelease: this._handleRightCornerRelease
    });
    const { source = '' } = this.props;
    if (!source.trim()) {
      throw new Error('source should be valid string');
    }
    if (!TrimmerManager && process.env.NODE_ENV !== 'test') {
      throw new Error('RNTrimmerManager: Native Module installed not correctly');
    }
    this._retrivePreviewImages();
    this._retriveInfo();
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.source !== this.props.source) {
      this._retrivePreviewImages();
      this._retriveInfo();
    }
  }

  componentWillUnmount() {
    this.state.leftCorner.removeAllListeners();
    this.state.rightCorner.removeAllListeners();
  }

  _handleLeftCornerRelease() {
    this.state.leftCorner.setOffset(this._leftCornerPos);
    this.state.leftCorner.setValue(0);
  }

  _handleRightCornerRelease() {
    this.state.rightCorner.setOffset(this._rightCornerPos);
    this.state.rightCorner.setValue(0);
  }

  _handleRightCornerMove(e, gestureState) {
    const { duration, layoutWidth } = this.state;
    const leftPos = this._leftCornerPos;
    const rightPos = layoutWidth - Math.abs(this._rightCornerPos);
    const moveLeft = gestureState.dx < 0;
    if (rightPos - leftPos <= 50 && moveLeft) {
      return;
    }
    this._endTime = calculateCornerResult(duration, this._rightCornerPos, layoutWidth, true);

    this._callOnChange();
    Animated.event([
      null, { dx: this.state.rightCorner }
     ])(e, gestureState);
  }

  _handleLeftCornerMove(e, gestureState) {
    const { duration, layoutWidth } = this.state;
    const leftPos = this._leftCornerPos;
    const rightPos = layoutWidth - Math.abs(this._rightCornerPos);
    const moveRight = gestureState.dx > 0;

    if (rightPos - leftPos <= 50 && moveRight) {
      return;
    }

    this._startTime = calculateCornerResult(duration, this._leftCornerPos, layoutWidth);
    this._callOnChange();

    Animated.event([
      null,
       { dx: this.state.leftCorner }
     ])(e, gestureState);
  }

  _callOnChange() {
    this.props.onChange({
      startTime: this._startTime,
      endTime: this._endTime
    });
  }

  _retriveInfo() {
    TrimmerManager
      .getVideoInfo(this.props.source)
      .then((info) => {
        this.setState(() => ({
          ...info,
          duration: msToSec(info.duration)
        }));
        this._endTime = msToSec(info.duration);
      });
  }

  _retrivePreviewImages() {
    TrimmerManager
      .getPreviewImages(this.props.source)
      .then(({ images }) => {
        this.setState({ images });
      })
      .catch((e) => console.error(e));
  }

  renderLeftSection() {
    const { leftCorner, layoutWidth } = this.state;
    return (
      <Animated.View
        style={[styles.container, styles.leftCorner, {
          left: - layoutWidth,
          transform: [{
            translateX: leftCorner,
          }]
        }]}
        {...this.leftResponder.panHandlers}
      >
        <View style={styles.row}>
          <View style={styles.bgBlack} />
          <View style={styles.cornerItem} />
        </View>
      </Animated.View>
    );
  }

  renderRightSection() {
    const { rightCorner, layoutWidth } = this.state;
    return (
      <Animated.View
        style={[styles.container, styles.rightCorner, { right: -layoutWidth + 20 }, {
          transform: [{
            translateX: rightCorner
          }]
        }]}
        {...this.rightResponder.panHandlers}
      >
        <View style={styles.row}>
          <View style={styles.cornerItem} />
          <View style={styles.bgBlack} />
        </View>
      </Animated.View>
    )
  }

  render() {
    const { images } = this.state;
    return (
      <View
        style={styles.container}
        onLayout={({ nativeEvent }) => {
          this.setState({
            layoutWidth: nativeEvent.layout.width
          });
        }}
      >
        {images.map((uri) => (
          <Image
            key={`preview-source-${uri}`}
            source={{ uri }}
            style={styles.imageItem}
          />
        ))}
        <View style={styles.corners}>
          {this.renderLeftSection()}
          {this.renderRightSection()}
        </View>
      </View>
    );
  }
}
<commit_msg>test(TrimmerAndroid): removed process object<commit_after>import React, { PropTypes, Component } from 'react';
import {
  View,
  Image,
  NativeModules,
  StyleSheet,
  Dimensions,
  PanResponder,
  Animated
} from 'react-native';
import { calculateCornerResult, msToSec } from '../utils';

const { RNTrimmerManager: TrimmerManager } = NativeModules;
const { width } = Dimensions.get('window');

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'center'
  },
  row: {
    flexDirection: 'row'
  },
  imageItem: {
    flex: 1,
    width: 50,
    height: 50,
    resizeMode: 'cover'
  },
  corners: {
    position: 'absolute',
    height: 50,
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  rightCorner: {
    position: 'absolute',
    flex: 1,
  },
  leftCorner: {
    left: 0
  },
  bgBlack: {
    backgroundColor: 'rgba(0, 0, 0, 0.7)',
    width
  },
  cornerItem: {
    backgroundColor: 'gray',
    width: 20,
    height: 50
  }
});
export class Trimmer extends Component {
  static propTypes = {
    source: PropTypes.string.isRequired,
    onChange: PropTypes.func
  };
  static defaultProps = {
    onChange: () => null
  };

  constructor(props) {
    super(props);
    this.state = {
      images: [],
      duration: -1,
      leftCorner: new Animated.Value(0),
      rightCorner: new Animated.Value(0),
      layoutWidth: width
    };

    this.leftResponder = null;
    this.rigthResponder = null;

    this._startTime = 0;
    this._endTime = 0;
    this._handleRightCornerMove = this._handleRightCornerMove.bind(this);
    this._handleLeftCornerMove = this._handleLeftCornerMove.bind(this);
    this._retriveInfo = this._retriveInfo.bind(this);
    this._retrivePreviewImages = this._retrivePreviewImages.bind(this);
    this._handleRightCornerRelease = this._handleRightCornerRelease.bind(this);
    this._handleLeftCornerRelease = this._handleLeftCornerRelease.bind(this);
  }

  componentWillMount() {
    // @TODO: Cleanup on unmount
    this.state.leftCorner.addListener(({ value }) => this._leftCornerPos = value);
    this.state.rightCorner.addListener(({ value }) => this._rightCornerPos = value);

    this.leftResponder = PanResponder.create({
      onMoveShouldSetPanResponder: (e, gestureState) => Math.abs(gestureState.dx) > 0,
      onMoveShouldSetPanResponderCapture: (e, gestureState) => Math.abs(gestureState.dx) > 0,
      onPanResponderMove: this._handleLeftCornerMove,
      onPanResponderRelease: this._handleLeftCornerRelease
    });

    this.rightResponder = PanResponder.create({
      onMoveShouldSetPanResponder: (e, gestureState) => Math.abs(gestureState.dx) > 0,
      onMoveShouldSetPanResponderCapture: (e, gestureState) => Math.abs(gestureState.dx) > 0,
      onPanResponderMove: this._handleRightCornerMove,
      onPanResponderRelease: this._handleRightCornerRelease
    });
    const { source = '' } = this.props;
    if (!source.trim()) {
      throw new Error('source should be valid string');
    }
    this._retrivePreviewImages();
    this._retriveInfo();
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.source !== this.props.source) {
      this._retrivePreviewImages();
      this._retriveInfo();
    }
  }

  componentWillUnmount() {
    this.state.leftCorner.removeAllListeners();
    this.state.rightCorner.removeAllListeners();
  }

  _handleLeftCornerRelease() {
    this.state.leftCorner.setOffset(this._leftCornerPos);
    this.state.leftCorner.setValue(0);
  }

  _handleRightCornerRelease() {
    this.state.rightCorner.setOffset(this._rightCornerPos);
    this.state.rightCorner.setValue(0);
  }

  _handleRightCornerMove(e, gestureState) {
    const { duration, layoutWidth } = this.state;
    const leftPos = this._leftCornerPos;
    const rightPos = layoutWidth - Math.abs(this._rightCornerPos);
    const moveLeft = gestureState.dx < 0;
    if (rightPos - leftPos <= 50 && moveLeft) {
      return;
    }
    this._endTime = calculateCornerResult(duration, this._rightCornerPos, layoutWidth, true);

    this._callOnChange();
    Animated.event([
      null, { dx: this.state.rightCorner }
     ])(e, gestureState);
  }

  _handleLeftCornerMove(e, gestureState) {
    const { duration, layoutWidth } = this.state;
    const leftPos = this._leftCornerPos;
    const rightPos = layoutWidth - Math.abs(this._rightCornerPos);
    const moveRight = gestureState.dx > 0;

    if (rightPos - leftPos <= 50 && moveRight) {
      return;
    }

    this._startTime = calculateCornerResult(duration, this._leftCornerPos, layoutWidth);
    this._callOnChange();

    Animated.event([
      null,
       { dx: this.state.leftCorner }
     ])(e, gestureState);
  }

  _callOnChange() {
    this.props.onChange({
      startTime: this._startTime,
      endTime: this._endTime
    });
  }

  _retriveInfo() {
    TrimmerManager
      .getVideoInfo(this.props.source)
      .then((info) => {
        this.setState(() => ({
          ...info,
          duration: msToSec(info.duration)
        }));
        this._endTime = msToSec(info.duration);
      });
  }

  _retrivePreviewImages() {
    TrimmerManager
      .getPreviewImages(this.props.source)
      .then(({ images }) => {
        this.setState({ images });
      })
      .catch((e) => console.error(e));
  }

  renderLeftSection() {
    const { leftCorner, layoutWidth } = this.state;
    return (
      <Animated.View
        style={[styles.container, styles.leftCorner, {
          left: - layoutWidth,
          transform: [{
            translateX: leftCorner,
          }]
        }]}
        {...this.leftResponder.panHandlers}
      >
        <View style={styles.row}>
          <View style={styles.bgBlack} />
          <View style={styles.cornerItem} />
        </View>
      </Animated.View>
    );
  }

  renderRightSection() {
    const { rightCorner, layoutWidth } = this.state;
    return (
      <Animated.View
        style={[styles.container, styles.rightCorner, { right: -layoutWidth + 20 }, {
          transform: [{
            translateX: rightCorner
          }]
        }]}
        {...this.rightResponder.panHandlers}
      >
        <View style={styles.row}>
          <View style={styles.cornerItem} />
          <View style={styles.bgBlack} />
        </View>
      </Animated.View>
    )
  }

  render() {
    const { images } = this.state;
    return (
      <View
        style={styles.container}
        onLayout={({ nativeEvent }) => {
          this.setState({
            layoutWidth: nativeEvent.layout.width
          });
        }}
      >
        {images.map((uri) => (
          <Image
            key={`preview-source-${uri}`}
            source={{ uri }}
            style={styles.imageItem}
          />
        ))}
        <View style={styles.corners}>
          {this.renderLeftSection()}
          {this.renderRightSection()}
        </View>
      </View>
    );
  }
}
<|endoftext|>


<commit_before>'use babel';

/* @flow*/

import { Observable } from 'rxjs';
import { UPDATE_EDITOR, errorAction } from '../actions';

export default function updateEditor(action$: Observable) {
  return action$.ofType(UPDATE_EDITOR)
    .map(action => console.log(action))
    .catch(err => Observable.of(errorAction(err)));
}
<commit_msg>handle gutter if editor changed<commit_after>'use babel';

/* @flow*/
import type { TextEditor } from 'atom';
import { Observable } from 'rxjs';
import { UPDATE_EDITOR, errorAction, testAction } from '../actions';

function handleGutter(textEditor: TextEditor) {
  if (!textEditor) {
    return null;
  }

  let gutter = textEditor.gutterWithName('tester');
  if (gutter) {
    try {
      gutter.destroy();
      gutter = null;
    } catch (error) {
      console.error('Tester: ', error);
    }
  }

  if (this.gutterEnabled) {
    const position = atom.config.get('tester.gutterPosition');
    if (textEditor && !textEditor.gutter) {
      gutter = textEditor.addGutter({
        name: 'tester',
        priority: position === 'Left' ? -100 : 100,
      });
    }
  }
  return gutter;
}

export default function updateEditor(action$: Observable) {
  return action$.ofType(UPDATE_EDITOR)
    .map((action) => {
      console.log(action);
      const isTextEditor = atom.workspace.isTextEditor(action.payload.editor);
      if (isTextEditor) {
        handleGutter(action.payload.editor);
      }
      if (atom.config.get('tester.testOnOpen')) {
        return Observable.of(testAction());
      }

      return Observable.empty();
    })
    .catch(err => Observable.of(errorAction(err)));
}
<|endoftext|>